]>
Commit | Line | Data |
---|---|---|
f5eabba0 | 1 | package PVE::Network::SDN::Zones::QinQPlugin; |
20e19696 AD |
2 | |
3 | use strict; | |
4 | use warnings; | |
26dedb18 | 5 | |
1d44ce70 | 6 | use PVE::Exception qw(raise raise_param_exc); |
20e19696 | 7 | |
26dedb18 TL |
8 | use PVE::Network::SDN::Zones::Plugin; |
9 | ||
3dfdc684 | 10 | use base('PVE::Network::SDN::Zones::Plugin'); |
20e19696 AD |
11 | |
12 | sub type { | |
13 | return 'qinq'; | |
14 | } | |
15 | ||
20e19696 AD |
16 | sub properties { |
17 | return { | |
1c95991c TL |
18 | tag => { |
19 | type => 'integer', | |
20 | minimum => 0, | |
21 | description => "Service-VLAN Tag", | |
22 | }, | |
823f2e2a AD |
23 | mtu => { |
24 | type => 'integer', | |
1c95991c | 25 | description => "MTU", |
823f2e2a AD |
26 | optional => 1, |
27 | }, | |
26dedb18 | 28 | 'vlan-protocol' => { |
3dfdc684 AD |
29 | type => 'string', |
30 | enum => ['802.1q', '802.1ad'], | |
31 | default => '802.1q', | |
32 | optional => 1, | |
33 | } | |
20e19696 AD |
34 | }; |
35 | } | |
36 | ||
37 | sub options { | |
20e19696 | 38 | return { |
26dedb18 | 39 | nodes => { optional => 1}, |
20e19696 | 40 | 'tag' => { optional => 0 }, |
938ebef7 | 41 | 'bridge' => { optional => 0 }, |
823f2e2a | 42 | 'mtu' => { optional => 1 }, |
3dfdc684 | 43 | 'vlan-protocol' => { optional => 1 }, |
4ad78442 AD |
44 | dns => { optional => 1 }, |
45 | reversedns => { optional => 1 }, | |
46 | dnszone => { optional => 1 }, | |
57a335c4 | 47 | ipam => { optional => 1 }, |
20e19696 AD |
48 | }; |
49 | } | |
50 | ||
51 | # Plugin implementation | |
52 | sub generate_sdn_config { | |
efffa0ff | 53 | my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; |
20e19696 | 54 | |
26dedb18 | 55 | my ($bridge, $mtu, $stag) = $plugin_config->@{'bridge', 'mtu', 'tag'}; |
3dfdc684 | 56 | my $vlanprotocol = $plugin_config->{'vlan-protocol'}; |
20e19696 | 57 | |
fdf22d5f AD |
58 | PVE::Network::SDN::Zones::Plugin::find_bridge($bridge); |
59 | ||
60 | my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge); | |
61 | my $is_ovs = PVE::Network::SDN::Zones::Plugin::is_ovs($bridge); | |
20e19696 | 62 | |
3dfdc684 | 63 | my @iface_config = (); |
26dedb18 TL |
64 | my $zone_notag_uplink = "ln_${zoneid}"; |
65 | my $zone_notag_uplinkpeer = "pr_${zoneid}"; | |
66 | my $zone = "z_${zoneid}"; | |
403b05e2 | 67 | |
26dedb18 TL |
68 | my $vnet_bridge_ports = ""; |
69 | if (my $ctag = $vnet->{tag}) { | |
403b05e2 AD |
70 | $vnet_bridge_ports = "$zone.$ctag"; |
71 | } else { | |
72 | $vnet_bridge_ports = $zone_notag_uplinkpeer; | |
73 | } | |
3dfdc684 | 74 | |
26dedb18 TL |
75 | my $zone_bridge_ports = ""; |
76 | if ($is_ovs) { | |
77 | # ovs--->ovsintport(dot1q-tunnel tag)------->vlanawarebrige-----(tag)--->vnet | |
3dfdc684 AD |
78 | |
79 | $vlanprotocol = "802.1q" if !$vlanprotocol; | |
80 | my $svlan_iface = "sv_".$zoneid; | |
3dfdc684 | 81 | |
26dedb18 | 82 | # ovs dot1q-tunnel port |
3dfdc684 AD |
83 | @iface_config = (); |
84 | push @iface_config, "ovs_type OVSIntPort"; | |
85 | push @iface_config, "ovs_bridge $bridge"; | |
82159388 | 86 | push @iface_config, "ovs_mtu $mtu" if $mtu; |
3dfdc684 AD |
87 | push @iface_config, "ovs_options vlan_mode=dot1q-tunnel tag=$stag other_config:qinq-ethtype=$vlanprotocol"; |
88 | push(@{$config->{$svlan_iface}}, @iface_config) if !$config->{$svlan_iface}; | |
89 | ||
26dedb18 | 90 | # redefine main ovs bridge, ifupdown2 will merge ovs_ports |
d8c88170 AD |
91 | @{$config->{$bridge}}[0] = "ovs_ports" if !@{$config->{$bridge}}[0]; |
92 | my @ovs_ports = split / / , @{$config->{$bridge}}[0]; | |
93 | @{$config->{$bridge}}[0] .= " $svlan_iface" if !grep( $_ eq $svlan_iface, @ovs_ports ); | |
3dfdc684 | 94 | |
403b05e2 | 95 | $zone_bridge_ports = $svlan_iface; |
3dfdc684 AD |
96 | |
97 | } elsif ($vlan_aware) { | |
26dedb18 | 98 | # VLAN_aware_brige-(tag)----->vlanwarebridge-(tag)----->vnet |
3dfdc684 | 99 | |
26dedb18 | 100 | if ($vlanprotocol) { |
3dfdc684 AD |
101 | @iface_config = (); |
102 | push @iface_config, "bridge-vlan-protocol $vlanprotocol"; | |
103 | push(@{$config->{$bridge}}, @iface_config) if !$config->{$bridge}; | |
104 | } | |
105 | ||
403b05e2 | 106 | $zone_bridge_ports = "$bridge.$stag"; |
3dfdc684 AD |
107 | |
108 | } else { | |
26dedb18 | 109 | # eth--->eth.x(svlan)----->vlanwarebridge-(tag)----->vnet---->vnet |
3dfdc684 | 110 | |
fdf22d5f | 111 | my @bridge_ifaces = PVE::Network::SDN::Zones::Plugin::get_bridge_ifaces($bridge); |
3dfdc684 | 112 | |
26dedb18 | 113 | for my $bridge_iface (@bridge_ifaces) { |
3dfdc684 | 114 | # use named vlan interface to avoid too long names |
2fa5d392 | 115 | my $svlan_iface = "sv_$zoneid"; |
3dfdc684 | 116 | |
26dedb18 | 117 | # svlan |
3dfdc684 AD |
118 | @iface_config = (); |
119 | push @iface_config, "vlan-raw-device $bridge_iface"; | |
120 | push @iface_config, "vlan-id $stag"; | |
121 | push @iface_config, "vlan-protocol $vlanprotocol" if $vlanprotocol; | |
122 | push(@{$config->{$svlan_iface}}, @iface_config) if !$config->{$svlan_iface}; | |
123 | ||
403b05e2 AD |
124 | $zone_bridge_ports = $svlan_iface; |
125 | last; | |
3dfdc684 AD |
126 | } |
127 | } | |
128 | ||
26dedb18 | 129 | # veth peer for notag vnet |
403b05e2 AD |
130 | @iface_config = (); |
131 | push @iface_config, "link-type veth"; | |
132 | push @iface_config, "veth-peer-name $zone_notag_uplinkpeer"; | |
133 | push(@{$config->{$zone_notag_uplink}}, @iface_config) if !$config->{$zone_notag_uplink}; | |
134 | ||
135 | @iface_config = (); | |
136 | push @iface_config, "link-type veth"; | |
137 | push @iface_config, "veth-peer-name $zone_notag_uplink"; | |
138 | push(@{$config->{$zone_notag_uplinkpeer}}, @iface_config) if !$config->{$zone_notag_uplinkpeer}; | |
139 | ||
26dedb18 | 140 | # zone vlan aware bridge |
403b05e2 AD |
141 | @iface_config = (); |
142 | push @iface_config, "mtu $mtu" if $mtu; | |
143 | push @iface_config, "bridge-stp off"; | |
144 | push @iface_config, "bridge-ports $zone_bridge_ports $zone_notag_uplink"; | |
145 | push @iface_config, "bridge-fd 0"; | |
146 | push @iface_config, "bridge-vlan-aware yes"; | |
147 | push @iface_config, "bridge-vids 2-4094"; | |
148 | push(@{$config->{$zone}}, @iface_config) if !$config->{$zone}; | |
149 | ||
26dedb18 | 150 | # vnet bridge |
3dfdc684 AD |
151 | @iface_config = (); |
152 | push @iface_config, "bridge_ports $vnet_bridge_ports"; | |
153 | push @iface_config, "bridge_stp off"; | |
154 | push @iface_config, "bridge_fd 0"; | |
912fb443 AD |
155 | if($vnet->{vlanaware}) { |
156 | push @iface_config, "bridge-vlan-aware yes"; | |
157 | push @iface_config, "bridge-vids 2-4094"; | |
158 | } | |
3dfdc684 | 159 | push @iface_config, "mtu $mtu" if $mtu; |
26dedb18 | 160 | push @iface_config, "alias $vnet->{alias}" if $vnet->{alias}; |
3dfdc684 | 161 | push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid}; |
20e19696 AD |
162 | } |
163 | ||
58433186 | 164 | sub status { |
4d7cc94f | 165 | my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_; |
58433186 AD |
166 | |
167 | my $bridge = $plugin_config->{bridge}; | |
4d7cc94f AD |
168 | my $err_msg = []; |
169 | ||
170 | if (!-d "/sys/class/net/$bridge") { | |
171 | push @$err_msg, "missing $bridge"; | |
172 | return $err_msg; | |
173 | } | |
174 | ||
fdf22d5f | 175 | my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge); |
4d7cc94f AD |
176 | |
177 | my $tag = $vnet->{tag}; | |
178 | my $vnet_uplink = "ln_".$vnetid; | |
179 | my $vnet_uplinkpeer = "pr_".$vnetid; | |
403b05e2 AD |
180 | my $zone_notag_uplink = "ln_".$zone; |
181 | my $zone_notag_uplinkpeer = "pr_".$zone; | |
182 | my $zonebridge = "z_$zone"; | |
4d7cc94f AD |
183 | |
184 | # ifaces to check | |
185 | my $ifaces = [ $vnetid, $bridge ]; | |
403b05e2 AD |
186 | |
187 | push @$ifaces, $zonebridge; | |
188 | push @$ifaces, $zone_notag_uplink; | |
189 | push @$ifaces, $zone_notag_uplinkpeer; | |
190 | ||
191 | if (!$vlan_aware) { | |
192 | my $svlan_iface = "sv_$zone"; | |
4d7cc94f | 193 | push @$ifaces, $svlan_iface; |
4d7cc94f AD |
194 | } |
195 | ||
196 | foreach my $iface (@{$ifaces}) { | |
197 | if (!$status->{$iface}->{status}) { | |
198 | push @$err_msg, "missing $iface"; | |
199 | } elsif ($status->{$iface}->{status} ne 'pass') { | |
200 | push @$err_msg, "error $iface"; | |
201 | } | |
58433186 | 202 | } |
4d7cc94f | 203 | return $err_msg; |
58433186 AD |
204 | } |
205 | ||
5ca07ed9 | 206 | sub vnet_update_hook { |
88d9562b AD |
207 | my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; |
208 | ||
209 | my $vnet = $vnet_cfg->{ids}->{$vnetid}; | |
1d44ce70 | 210 | |
26dedb18 TL |
211 | my $tag = $vnet->{tag}; |
212 | raise_param_exc({ tag => "VLAN tag maximal value is 4096" }) if $tag && $tag > 4096; | |
88d9562b AD |
213 | |
214 | # verify that tag is not already defined in another vnet on same zone | |
26dedb18 | 215 | for my $id (sort keys %{$vnet_cfg->{ids}}) { |
88d9562b | 216 | next if $id eq $vnetid; |
26dedb18 TL |
217 | my $other_vnet = $vnet_cfg->{ids}->{$id}; |
218 | next if $vnet->{zone} ne $other_vnet->{zone}; | |
219 | my $other_tag = $other_vnet->{tag}; | |
220 | if ($tag) { | |
221 | raise_param_exc({ tag => "tag $tag already exist in zone $vnet->{zone} vnet $id"}) | |
222 | if $other_tag && $tag eq $other_tag; | |
223 | } else { | |
224 | raise_param_exc({ tag => "tag-less vnet already exists in zone $vnet->{zone} vnet $id"}) | |
225 | if !$other_tag; | |
226 | } | |
88d9562b | 227 | } |
1d44ce70 AD |
228 | } |
229 | ||
20e19696 AD |
230 | 1; |
231 | ||
232 |