Skip to content

Commit ccb6e9d

Browse files
committed
VLAN filtering on VIF
- update idl/datamodel to add trunks property on VIF - add validation constraints (trunks filtering on access port isn't valid) - add support inside `vif-real` for initial configuration - add support inside xenopsd for dynamic changes - add test coverage to new attributes References: - Design document: https://github.com/xapi-project/xen-api/blob/788869e5a92e10332ba2428eb91e5a2caf4c7131/doc/content/design/vlan-filtering.md Signed-off-by: Sebastien Marie <semarie@kapouay.eu.org>
1 parent bc3434c commit ccb6e9d

35 files changed

Lines changed: 546 additions & 22 deletions

ocaml/idl/datamodel.ml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3831,6 +3831,48 @@ module VIF = struct
38313831
]
38323832
~allowed_roles:_R_VM_OP ()
38333833

3834+
let add_trunks =
3835+
call ~name:"add_trunks" ~lifecycle:[]
3836+
~doc:"Associates a 802.1Q VLAN with this VIF"
3837+
~params:
3838+
[
3839+
( Ref _vif
3840+
, "self"
3841+
, "The VIF which the 802.1Q VLAN will be associated with"
3842+
)
3843+
; (Int, "value", "The 802.1Q VLAN which will be associated with the VIF")
3844+
]
3845+
~allowed_roles:_R_POOL_OP ()
3846+
3847+
let remove_trunks =
3848+
call ~name:"remove_trunks" ~lifecycle:[]
3849+
~doc:"Removes a 802.1Q VLAN from this VIF"
3850+
~params:
3851+
[
3852+
( Ref _vif
3853+
, "self"
3854+
, "The VIF from which the 802.1Q VLAN will be removed"
3855+
)
3856+
; (Int, "value", "The 802.1Q VLAN which will be removed from the VIF")
3857+
]
3858+
~allowed_roles:_R_POOL_OP ()
3859+
3860+
let set_trunks =
3861+
call ~name:"set_trunks" ~lifecycle:[]
3862+
~doc:"Set the 802.1Q VLANs to which traffic on this VIF can be restricted"
3863+
~params:
3864+
[
3865+
( Ref _vif
3866+
, "self"
3867+
, "The VIF which the 802.1Q VLANs will be associated with"
3868+
)
3869+
; ( Set Int
3870+
, "value"
3871+
, "The 802.1Q VLANs which will be associated with the VIF"
3872+
)
3873+
]
3874+
~allowed_roles:_R_POOL_OP ()
3875+
38343876
(** A virtual network interface *)
38353877
let t =
38363878
create_obj ~in_db:true
@@ -3854,6 +3896,9 @@ module VIF = struct
38543896
; remove_ipv6_allowed
38553897
; configure_ipv4
38563898
; configure_ipv6
3899+
; add_trunks
3900+
; remove_trunks
3901+
; set_trunks
38573902
]
38583903
~contents:
38593904
([
@@ -4044,6 +4089,10 @@ module VIF = struct
40444089
~internal_only:true ~qualifier:DynamicRO "reserved_pci"
40454090
"pci of network SR-IOV VF which is reserved for this vif"
40464091
~default_value:(Some (VRef null_ref))
4092+
; field ~qualifier:StaticRO ~lifecycle:[] ~ty:(Set Int)
4093+
~default_value:(Some (VSet [])) "trunks"
4094+
"the 802.1Q VLANs that this port trunks (if available) ; if it \
4095+
is empty, then the port trunks all VLANs."
40474096
]
40484097
)
40494098
()

ocaml/idl/datamodel_common.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ open Datamodel_roles
1010
to leave a gap for potential hotfixes needing to increment the schema version.*)
1111
let schema_major_vsn = 5
1212

13-
let schema_minor_vsn = 905
13+
let schema_minor_vsn = 906
1414

1515
(* Historical schema versions just in case this is useful later *)
1616
let rio_schema_major_vsn = 5

ocaml/idl/datamodel_errors.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ let _ =
230230
~doc:"The network is incompatible with bond" () ;
231231
error Api_errors.network_incompatible_with_tunnel ["network"]
232232
~doc:"The network is incompatible with tunnel" () ;
233+
error Api_errors.network_incompatible_with_trunks ["network"]
234+
~doc:"The network is incompatible with VIF using trunks." () ;
233235
error Api_errors.pool_joining_host_has_network_sriovs []
234236
~doc:"The host joining the pool must not have any network SR-IOVs." () ;
235237

ocaml/idl/datamodel_lifecycle.ml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,12 @@ let prototyped_of_field = function
9797
Some "22.26.0"
9898
| "VTPM", "persistence_backend" ->
9999
Some "22.26.0"
100+
| "SM", "supported_image_formats" ->
101+
Some "26.15.0-next"
100102
| "SM", "host_pending_features" ->
101103
Some "24.37.0"
104+
| "VIF", "trunks" ->
105+
Some "26.15.0-next"
102106
| "host", "timezone" ->
103107
Some "26.0.0"
104108
| "host", "ntp_custom_servers" ->
@@ -249,6 +253,12 @@ let prototyped_of_message = function
249253
Some "22.26.0"
250254
| "VTPM", "create" ->
251255
Some "22.26.0"
256+
| "VIF", "set_trunks" ->
257+
Some "26.15.0-next"
258+
| "VIF", "remove_trunks" ->
259+
Some "26.15.0-next"
260+
| "VIF", "add_trunks" ->
261+
Some "26.15.0-next"
252262
| "host", "set_servertime" ->
253263
Some "26.0.0"
254264
| "host", "get_ntp_synchronized" ->

ocaml/idl/schematest.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ let hash x = Digest.string x |> Digest.to_hex
33
(* BEWARE: if this changes, check that schema has been bumped accordingly in
44
ocaml/idl/datamodel_common.ml, usually schema_minor_vsn *)
55

6-
let last_known_schema_hash = "62c803c7341a736eef8293337105206f"
6+
let last_known_schema_hash = "b925fd7ff522998de8510d791314d535"
77

88
let current_schema_hash : string =
99
let open Datamodel_types in

ocaml/tests/common/test_common.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,15 +278,15 @@ let make_vif ~__context ?(ref = Ref.make ()) ?(uuid = make_uuid ())
278278
?(ipv4_allowed = []) ?(ipv6_allowed = []) ?(ipv4_configuration_mode = `None)
279279
?(ipv4_addresses = []) ?(ipv4_gateway = "")
280280
?(ipv6_configuration_mode = `None) ?(ipv6_addresses = [])
281-
?(ipv6_gateway = "") () =
281+
?(ipv6_gateway = "") ?(trunks = []) () =
282282
Db.VIF.create ~__context ~ref ~uuid ~current_operations ~allowed_operations
283283
~reserved ~device ~network ~vM ~mAC ~mAC_autogenerated ~mTU
284284
~qos_algorithm_type ~qos_algorithm_params ~qos_supported_algorithms
285285
~currently_attached ~status_code ~status_detail ~runtime_properties
286286
~other_config ~metrics ~locking_mode ~ipv4_allowed ~ipv6_allowed
287287
~ipv4_configuration_mode ~ipv4_addresses ~ipv4_gateway
288288
~ipv6_configuration_mode ~ipv6_addresses ~ipv6_gateway
289-
~reserved_pci:Ref.null ;
289+
~reserved_pci:Ref.null ~trunks ;
290290
ref
291291

292292
let make_pool ~__context ~master ?(name_label = "") ?(name_description = "")

ocaml/tests/suite_alcotest.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ let () =
3838
; ("Test_pvs_proxy", Test_pvs_proxy.test)
3939
; ("Test_pvs_server", Test_pvs_server.test)
4040
; ("Test_vif_helpers", Test_vif_helpers.test)
41+
; ("Test_vif_trunks", Test_vif_trunks.test)
4142
; ("Test_vm_memory_constraints", Test_vm_memory_constraints.test)
4243
; ("Test_xapi_xenops", Test_xapi_xenops.test)
4344
; ("Test_network_event_loop", Test_network_event_loop.test)

ocaml/tests/test_vif_helpers.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ let create ~__context ~device ~network ~vM ?(mAC = "00:00:00:00:00:00")
2121
?(locking_mode = `unlocked) ?(ipv4_allowed = []) ?(ipv6_allowed = [])
2222
?(ipv4_configuration_mode = `None) ?(ipv4_addresses = [])
2323
?(ipv4_gateway = "") ?(ipv6_configuration_mode = `None)
24-
?(ipv6_addresses = []) ?(ipv6_gateway = "") () =
24+
?(ipv6_addresses = []) ?(ipv6_gateway = "") ?(trunks = []) () =
2525
Xapi_vif_helpers.create ~__context ~device ~network ~vM ~mAC ~mTU
2626
~other_config ~qos_algorithm_type ~qos_algorithm_params ~currently_attached
2727
~locking_mode ~ipv4_allowed ~ipv6_allowed ~ipv4_configuration_mode
2828
~ipv4_addresses ~ipv4_gateway ~ipv6_configuration_mode ~ipv6_addresses
29-
~ipv6_gateway
29+
~ipv6_gateway ~trunks
3030

3131
let test_create_ok () =
3232
let __context = T.make_test_database () in

ocaml/tests/test_vif_trunks.ml

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
(*
2+
* Copyright (C) 2026 Vates
3+
* Copyright (C) Citrix Systems Inc.
4+
*
5+
* This program is free software; you can redistribute it and/or modify
6+
* it under the terms of the GNU Lesser General Public License as published
7+
* by the Free Software Foundation; version 2.1 only. with the special
8+
* exception on linking described in file LICENSE.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Lesser General Public License for more details.
14+
*)
15+
16+
module T = Test_common
17+
18+
let test_trunks_parameter () =
19+
let __context = T.make_test_database () in
20+
let vM = T.make_vm ~__context () in
21+
let network = T.make_network ~__context () in
22+
let vif = T.make_vif ~__context ~device:"1" ~vM ~network ~trunks:[2201L] () in
23+
Alcotest.(check (list int64))
24+
"test_trunks_parameter testing add_trunks" [3201L; 2201L]
25+
( Xapi_vif.add_trunks ~__context ~self:vif ~value:3201L ;
26+
Db.VIF.get_trunks ~__context ~self:vif
27+
) ;
28+
Alcotest.(check (list int64))
29+
"test_trunks_parameter testing remove_trunks" [2201L]
30+
( Xapi_vif.remove_trunks ~__context ~self:vif ~value:3201L ;
31+
Db.VIF.get_trunks ~__context ~self:vif
32+
) ;
33+
Alcotest.(check (list int64))
34+
"test_trunks_parameter testing set_trunks" [3201L]
35+
( Xapi_vif.set_trunks ~__context ~self:vif ~value:[3201L] ;
36+
Db.VIF.get_trunks ~__context ~self:vif
37+
) ;
38+
Alcotest.(check (list int64))
39+
"test_trunks_parameter testing set_trunks empty" []
40+
( Xapi_vif.set_trunks ~__context ~self:vif ~value:[] ;
41+
Db.VIF.get_trunks ~__context ~self:vif
42+
) ;
43+
Alcotest.(check_raises)
44+
"test_trunks_parameter testing invalid VLAN tag"
45+
Api_errors.(Server_error (Api_errors.vlan_tag_invalid, ["9999"]))
46+
(fun () -> Xapi_vif.add_trunks ~__context ~self:vif ~value:9999L)
47+
48+
(** try to set trunks on VIF on incompatible network *)
49+
let test_trunks_coherence_vif_set () =
50+
let __context = T.make_test_database () in
51+
(* create a VLAN *)
52+
let host = T.make_host ~__context () in
53+
let network = T.make_network ~__context () in
54+
let tagged_PIF = T.make_pif ~__context ~network ~host () in
55+
let tag = 3201L in
56+
let vlan_network = T.make_network ~__context ~bridge:"xapi0" () in
57+
let untagged_PIF =
58+
T.make_pif ~__context ~network:vlan_network ~host ~vLAN:tag ()
59+
in
60+
let _vlan = T.make_vlan ~__context ~tagged_PIF ~untagged_PIF ~tag () in
61+
(* create VM + VIF using this network *)
62+
let vM = T.make_vm ~__context () in
63+
let vif = T.make_vif ~__context ~device:"1" ~vM ~network:vlan_network () in
64+
Alcotest.(check_raises)
65+
"test_trunks_coherence_vif_set testing"
66+
Api_errors.(
67+
Server_error
68+
( Api_errors.network_incompatible_with_trunks
69+
, [Ref.string_of vlan_network]
70+
)
71+
)
72+
(fun () -> Xapi_vif.add_trunks ~__context ~self:vif ~value:2201L)
73+
74+
(** try to add VIF (with trunks) on incompatible network *)
75+
let test_trunks_coherence_vif_add () =
76+
let __context = T.make_test_database () in
77+
(* create a VLAN *)
78+
let host = T.make_host ~__context () in
79+
let network = T.make_network ~__context () in
80+
let tagged_PIF = T.make_pif ~__context ~network ~host () in
81+
let tag = 3201L in
82+
let vlan_network = T.make_network ~__context ~bridge:"xapi0" () in
83+
let untagged_PIF =
84+
T.make_pif ~__context ~network:vlan_network ~host ~vLAN:tag ()
85+
in
86+
let _vlan = T.make_vlan ~__context ~tagged_PIF ~untagged_PIF ~tag () in
87+
(* create VM + VIF using this network *)
88+
let vM = T.make_vm ~__context () in
89+
Alcotest.(check_raises)
90+
"test_trunks_coherence_vif_add testing"
91+
Api_errors.(
92+
Server_error
93+
( Api_errors.network_incompatible_with_trunks
94+
, [Ref.string_of vlan_network]
95+
)
96+
)
97+
(fun () ->
98+
let _ : API.ref_VIF =
99+
Xapi_vif.create ~__context ~device:"1" ~network:vlan_network ~vM
100+
~mAC:"00:00:00:00:00:00" ~mTU:1500L ~other_config:[]
101+
~currently_attached:true ~qos_algorithm_type:""
102+
~qos_algorithm_params:[] ~locking_mode:`unlocked ~ipv4_allowed:[]
103+
~ipv6_allowed:[] ~trunks:[2201L]
104+
in
105+
()
106+
)
107+
108+
(** try to associate PIF (with VLAN) on Network with trunked-VIF *)
109+
let test_trunks_coherence_pif_vlan () =
110+
let __context = T.make_test_database () in
111+
(* create VM + VIF on plain network *)
112+
let network = T.make_network ~__context () in
113+
let vM = T.make_vm ~__context () in
114+
let _vif =
115+
T.make_vif ~__context ~device:"1" ~vM ~network ~trunks:[2201L] ()
116+
in
117+
let host = T.make_host ~__context () in
118+
Alcotest.(check_raises)
119+
"test_trunks_coherence_pif_vlan testing"
120+
Api_errors.(
121+
Server_error
122+
(Api_errors.network_incompatible_with_trunks, [Ref.string_of network])
123+
)
124+
(fun () ->
125+
let _ : API.ref_PIF =
126+
T.make_pif ~__context ~network ~host ~vLAN:2201L ()
127+
in
128+
()
129+
)
130+
131+
let test_trunks_move () =
132+
let __context = T.make_test_database () in
133+
(* create VM + VIF using this network *)
134+
let network1 = T.make_network ~__context () in
135+
let vM = T.make_vm ~__context () in
136+
let vif = T.make_vif ~__context ~network:network1 ~vM () in
137+
let network2 = T.make_network ~__context () in
138+
Alcotest.(check unit)
139+
"test_trunks_move testing" ()
140+
(Xapi_vif.move ~__context ~self:vif ~network:network2)
141+
142+
let test_trunks_move_to_vlan () =
143+
let __context = T.make_test_database () in
144+
(* create VM + VIF (with trunks) *)
145+
let network1 = T.make_network ~__context () in
146+
let vM = T.make_vm ~__context () in
147+
let vif = T.make_vif ~__context ~network:network1 ~vM ~trunks:[3201L] () in
148+
(* create a VLAN *)
149+
let host = T.make_host ~__context () in
150+
let network2 = T.make_network ~__context () in
151+
let tagged_PIF = T.make_pif ~__context ~network:network2 ~host () in
152+
let tag = 3201L in
153+
let vlan_network2 = T.make_network ~__context ~bridge:"xapi0" () in
154+
let untagged_PIF =
155+
T.make_pif ~__context ~network:vlan_network2 ~host ~vLAN:tag ()
156+
in
157+
let _vlan = T.make_vlan ~__context ~tagged_PIF ~untagged_PIF ~tag () in
158+
(* move the VIF to the vlan_network *)
159+
Alcotest.(check_raises)
160+
"test_trunks_move_to_vlan testing"
161+
Api_errors.(
162+
Server_error
163+
( Api_errors.network_incompatible_with_trunks
164+
, [Ref.string_of vlan_network2]
165+
)
166+
)
167+
(fun () -> Xapi_vif.move ~__context ~self:vif ~network:vlan_network2)
168+
169+
let test =
170+
[
171+
("test_trunks_parameter", `Quick, test_trunks_parameter)
172+
; ("test_trunks_coherence_vif_set", `Quick, test_trunks_coherence_vif_set)
173+
; ("test_trunks_coherence_vif_add", `Quick, test_trunks_coherence_vif_add)
174+
; ("test_trunks_coherence_pif_vlan", `Quick, test_trunks_coherence_pif_vlan)
175+
; ("test_trunks_move", `Quick, test_trunks_move)
176+
; ("test_trunks_move_to_vlan", `Quick, test_trunks_move_to_vlan)
177+
]

ocaml/xapi-cli-server/cli_operations.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2859,7 +2859,7 @@ let vif_create printer rpc session_id params =
28592859
Client.VIF.create ~rpc ~session_id ~device ~network ~vM ~mAC ~mTU
28602860
~other_config:[] ~currently_attached:false ~qos_algorithm_type:""
28612861
~qos_algorithm_params:[] ~locking_mode:`network_default ~ipv4_allowed:[]
2862-
~ipv6_allowed:[]
2862+
~ipv6_allowed:[] ~trunks:[]
28632863
in
28642864
let uuid = Client.VIF.get_uuid ~rpc ~session_id ~self:vif in
28652865
printer (Cli_printer.PList [uuid])

0 commit comments

Comments
 (0)