Skip to content

Commit 01fc692

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 fdfcea1 commit 01fc692

34 files changed

Lines changed: 553 additions & 21 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_VM_ADMIN ()
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_VM_ADMIN ()
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_VM_ADMIN ()
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_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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ let prototyped_of_field = function
101101
Some "26.15.0"
102102
| "SM", "host_pending_features" ->
103103
Some "24.37.0"
104+
| "VIF", "trunks" ->
105+
Some "26.15.0-next"
104106
| "host", "timezone" ->
105107
Some "26.0.0"
106108
| "host", "ntp_custom_servers" ->
@@ -253,6 +255,12 @@ let prototyped_of_message = function
253255
Some "22.26.0"
254256
| "VDI", "revert" ->
255257
Some "26.15.0-next"
258+
| "VIF", "set_trunks" ->
259+
Some "26.15.0-next"
260+
| "VIF", "remove_trunks" ->
261+
Some "26.15.0-next"
262+
| "VIF", "add_trunks" ->
263+
Some "26.15.0-next"
256264
| "host", "set_servertime" ->
257265
Some "26.0.0"
258266
| "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 = "6147ef4f0f9c3bbbf0c2061e0a0d0010"
6+
let last_known_schema_hash = "e0cc6f552213dc9f47f367b9db97c218"
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
@@ -37,6 +37,7 @@ let () =
3737
; ("Test_pvs_proxy", Test_pvs_proxy.test)
3838
; ("Test_pvs_server", Test_pvs_server.test)
3939
; ("Test_vif_helpers", Test_vif_helpers.test)
40+
; ("Test_vif_trunks", Test_vif_trunks.test)
4041
; ("Test_vm_memory_constraints", Test_vm_memory_constraints.test)
4142
; ("Test_xapi_xenops", Test_xapi_xenops.test)
4243
; ("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: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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:[3201L] ()
116+
in
117+
Alcotest.(check_raises)
118+
"test_trunks_coherence_pif_vlan testing"
119+
Api_errors.(
120+
Server_error
121+
(Api_errors.network_incompatible_with_trunks, [Ref.string_of network])
122+
)
123+
(fun () ->
124+
(* create a VLAN *)
125+
let host = T.make_host ~__context () in
126+
let network2 = T.make_network ~__context () in
127+
let tagged_PIF = T.make_pif ~__context ~network:network2 ~host () in
128+
let tag = 2201L in
129+
let untagged_PIF = T.make_pif ~__context ~network ~host ~vLAN:tag () in
130+
let _vlan = T.make_vlan ~__context ~tagged_PIF ~untagged_PIF ~tag () in
131+
()
132+
)
133+
134+
let test_trunks_move () =
135+
let __context = T.make_test_database () in
136+
(* create VM + VIF using this network *)
137+
let network1 = T.make_network ~__context () in
138+
let vM = T.make_vm ~__context () in
139+
let vif = T.make_vif ~__context ~network:network1 ~vM () in
140+
let network2 = T.make_network ~__context () in
141+
Alcotest.(check unit)
142+
"test_trunks_move testing" ()
143+
(Xapi_vif.move ~__context ~self:vif ~network:network2)
144+
145+
let test_trunks_move_to_vlan () =
146+
let __context = T.make_test_database () in
147+
(* create VM + VIF (with trunks) *)
148+
let network1 = T.make_network ~__context () in
149+
let vM = T.make_vm ~__context () in
150+
let vif = T.make_vif ~__context ~network:network1 ~vM ~trunks:[3201L] () in
151+
(* create a VLAN *)
152+
let host = T.make_host ~__context () in
153+
let network2 = T.make_network ~__context () in
154+
let tagged_PIF = T.make_pif ~__context ~network:network2 ~host () in
155+
let tag = 3201L in
156+
let vlan_network2 = T.make_network ~__context ~bridge:"xapi0" () in
157+
let untagged_PIF =
158+
T.make_pif ~__context ~network:vlan_network2 ~host ~vLAN:tag ()
159+
in
160+
let _vlan = T.make_vlan ~__context ~tagged_PIF ~untagged_PIF ~tag () in
161+
(* move the VIF to the vlan_network *)
162+
Alcotest.(check_raises)
163+
"test_trunks_move_to_vlan testing"
164+
Api_errors.(
165+
Server_error
166+
( Api_errors.network_incompatible_with_trunks
167+
, [Ref.string_of vlan_network2]
168+
)
169+
)
170+
(fun () -> Xapi_vif.move ~__context ~self:vif ~network:vlan_network2)
171+
172+
let test =
173+
[
174+
("test_trunks_parameter", `Quick, test_trunks_parameter)
175+
; ("test_trunks_coherence_vif_set", `Quick, test_trunks_coherence_vif_set)
176+
; ("test_trunks_coherence_vif_add", `Quick, test_trunks_coherence_vif_add)
177+
; ("test_trunks_coherence_pif_vlan", `Quick, test_trunks_coherence_pif_vlan)
178+
; ("test_trunks_move", `Quick, test_trunks_move)
179+
; ("test_trunks_move_to_vlan", `Quick, test_trunks_move_to_vlan)
180+
]

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])

ocaml/xapi-cli-server/records.ml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,23 @@ let vif_record rpc session_id vif =
10141014
; make_field ~name:"ipv6-gateway"
10151015
~get:(fun () -> (x ()).API.vIF_ipv6_gateway)
10161016
()
1017+
; make_field ~name:"trunks"
1018+
~get:(fun () -> map_and_concat Int64.to_string (x ()).API.vIF_trunks)
1019+
~get_set:(fun () -> List.map Int64.to_string (x ()).API.vIF_trunks)
1020+
~add_to_set:(fun value ->
1021+
let value = safe_i64_of_string "value" value in
1022+
Client.VIF.add_trunks ~rpc ~session_id ~self:vif ~value
1023+
)
1024+
~remove_from_set:(fun value ->
1025+
let value = safe_i64_of_string "value" value in
1026+
Client.VIF.remove_trunks ~rpc ~session_id ~self:vif ~value
1027+
)
1028+
~set:(fun value ->
1029+
Client.VIF.set_trunks ~rpc ~session_id ~self:vif
1030+
~value:
1031+
(List.map (safe_i64_of_string "value") (get_words ',' value))
1032+
)
1033+
()
10171034
]
10181035
}
10191036

0 commit comments

Comments
 (0)