]> git.proxmox.com Git - pve-network.git/blame - src/PVE/Network/SDN/Zones.pm
subnet: vnet: refactor IPAM related methods
[pve-network.git] / src / PVE / Network / SDN / Zones.pm
CommitLineData
f5eabba0
AD
1package PVE::Network::SDN::Zones;
2
3use strict;
4use warnings;
5
f5eabba0
AD
6use JSON;
7
8use PVE::Tools qw(extract_param dir_glob_regex run_command);
9use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
eb1549e7 10use PVE::Network;
f5eabba0
AD
11
12use PVE::Network::SDN::Vnets;
13use PVE::Network::SDN::Zones::VlanPlugin;
14use PVE::Network::SDN::Zones::QinQPlugin;
15use PVE::Network::SDN::Zones::VxlanPlugin;
16use PVE::Network::SDN::Zones::EvpnPlugin;
17use PVE::Network::SDN::Zones::FaucetPlugin;
880ae857 18use PVE::Network::SDN::Zones::SimplePlugin;
f5eabba0
AD
19use PVE::Network::SDN::Zones::Plugin;
20
21PVE::Network::SDN::Zones::VlanPlugin->register();
22PVE::Network::SDN::Zones::QinQPlugin->register();
23PVE::Network::SDN::Zones::VxlanPlugin->register();
24PVE::Network::SDN::Zones::EvpnPlugin->register();
25PVE::Network::SDN::Zones::FaucetPlugin->register();
880ae857 26PVE::Network::SDN::Zones::SimplePlugin->register();
f5eabba0
AD
27PVE::Network::SDN::Zones::Plugin->init();
28
0814c9a9 29my $local_network_sdn_file = "/etc/network/interfaces.d/sdn";
f5eabba0
AD
30
31sub sdn_zones_config {
32 my ($cfg, $id, $noerr) = @_;
33
34 die "no sdn zone ID specified\n" if !$id;
35
36 my $scfg = $cfg->{ids}->{$id};
b2d83056 37 die "sdn '$id' does not exist\n" if (!$noerr && !$scfg);
f5eabba0
AD
38
39 return $scfg;
40}
41
42sub config {
a1845dad
SH
43 my ($running) = @_;
44
45 if ($running) {
46 my $cfg = PVE::Network::SDN::running_config();
47 return $cfg->{zones};
48 }
49
50 return cfs_read_file("sdn/zones.cfg");
f5eabba0
AD
51}
52
41d40fb1
TL
53sub get_plugin_config {
54 my ($vnet) = @_;
55 my $zoneid = $vnet->{zone};
56 my $zone_cfg = PVE::Network::SDN::Zones::config();
57 return $zone_cfg->{ids}->{$zoneid};
58}
59
f5eabba0
AD
60sub write_config {
61 my ($cfg) = @_;
62
f703d2ae 63 cfs_write_file("sdn/zones.cfg", $cfg);
f5eabba0
AD
64}
65
f5eabba0
AD
66sub sdn_zones_ids {
67 my ($cfg) = @_;
68
b184ebc3 69 return sort keys %{$cfg->{ids}};
f5eabba0
AD
70}
71
72sub complete_sdn_zone {
73 my ($cmdname, $pname, $cvalue) = @_;
74
d73c7c36 75 my $cfg = PVE::Network::SDN::running_config();
f5eabba0
AD
76
77 return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_zones_ids($cfg) ];
78}
79
4ad78442
AD
80sub get_zone {
81 my ($zoneid, $running) = @_;
82
a1845dad 83 my $cfg = PVE::Network::SDN::Zones::config($running);
4ad78442
AD
84
85 my $zone = PVE::Network::SDN::Zones::sdn_zones_config($cfg, $zoneid, 1);
86
87 return $zone;
88}
89
a1845dad
SH
90sub get_vnets {
91 my ($zoneid, $running) = @_;
92
93 return if !$zoneid;
94
95 my $vnets_config = PVE::Network::SDN::Vnets::config($running);
96 my $vnets = undef;
97
98 for my $vnetid (keys %{$vnets_config->{ids}}) {
99 my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($vnets_config, $vnetid);
100 next if !$vnet->{zone} || $vnet->{zone} ne $zoneid;
101 $vnets->{$vnetid} = $vnet;
102 }
103
104 return $vnets;
105}
f5eabba0
AD
106
107sub generate_etc_network_config {
108
d73c7c36 109 my $cfg = PVE::Network::SDN::running_config();
5d3e0248
AD
110
111 my $version = $cfg->{version};
112 my $vnet_cfg = $cfg->{vnets};
113 my $zone_cfg = $cfg->{zones};
114 my $subnet_cfg = $cfg->{subnets};
115 my $controller_cfg = $cfg->{controllers};
56cdcac9 116 return if !$vnet_cfg && !$zone_cfg;
f5eabba0 117
ba7ac021 118 my $interfaces_config = PVE::INotify::read_file('interfaces');
f5eabba0
AD
119
120 #generate configuration
121 my $config = {};
c2b9c173
AD
122 my $nodename = PVE::INotify::nodename();
123
e0c19383 124 for my $id (sort keys %{$vnet_cfg->{ids}}) {
f5eabba0 125 my $vnet = $vnet_cfg->{ids}->{$id};
3fd3e917 126 my $zone = $vnet->{zone};
f5eabba0 127
e0c19383
TL
128 if (!$zone) {
129 warn "can't generate vnet '$id': no zone assigned!\n";
f5eabba0
AD
130 next;
131 }
132
56cdcac9 133 my $plugin_config = $zone_cfg->{ids}->{$zone};
f5eabba0
AD
134
135 if (!defined($plugin_config)) {
e0c19383 136 warn "can't generate vnet '$id': zone $zone don't exist\n";
f5eabba0
AD
137 next;
138 }
139
c2b9c173
AD
140 next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename};
141
e0c19383
TL
142 my $controller;
143 if (my $controllerid = $plugin_config->{controller}) {
144 $controller = $controller_cfg->{ids}->{$controllerid};
4405f2de
AD
145 }
146
f5eabba0 147 my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
ae3f4de8 148 eval {
efffa0ff 149 $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config);
ae3f4de8 150 };
cd01a0c0
TL
151 if (my $err = $@) {
152 warn "zone $zone : vnet $id : $err\n";
ae3f4de8
AD
153 next;
154 }
f5eabba0
AD
155 }
156
0814c9a9 157 my $raw_network_config = "\#version:$version\n";
2d4c068e 158 foreach my $iface (sort keys %$config) {
f5eabba0
AD
159 $raw_network_config .= "\n";
160 $raw_network_config .= "auto $iface\n";
161 $raw_network_config .= "iface $iface\n";
162 foreach my $option (@{$config->{$iface}}) {
163 $raw_network_config .= "\t$option\n";
164 }
165 }
166
167 return $raw_network_config;
168}
169
170sub write_etc_network_config {
171 my ($rawconfig) = @_;
172
173 return if !$rawconfig;
f5eabba0 174
0814c9a9 175 my $writefh = IO::File->new($local_network_sdn_file,">");
f5eabba0
AD
176 print $writefh $rawconfig;
177 $writefh->close();
178}
179
0814c9a9
AD
180sub read_etc_network_config_version {
181 my $versionstr = PVE::Tools::file_read_firstline($local_network_sdn_file);
06c87086
SI
182
183 return if !defined($versionstr);
184
0814c9a9
AD
185 if ($versionstr =~ m/^\#version:(\d+)$/) {
186 return $1;
187 }
188}
189
f5eabba0
AD
190sub ifquery_check {
191
192 my $cmd = ['ifquery', '-a', '-c', '-o','json'];
193
194 my $result = '';
195 my $reader = sub { $result .= shift };
196
197 eval {
198 run_command($cmd, outfunc => $reader);
199 };
200
201 my $resultjson = decode_json($result);
202 my $interfaces = {};
203
204 foreach my $interface (@$resultjson) {
205 my $name = $interface->{name};
206 $interfaces->{$name} = {
207 status => $interface->{status},
208 config => $interface->{config},
209 config_status => $interface->{config_status},
210 };
211 }
212
213 return $interfaces;
214}
215
9ddc4a6f 216my $warned_about_reload;
4d7cc94f 217
f5eabba0
AD
218sub status {
219
f5eabba0
AD
220 my $err_config = undef;
221
6f4d0610 222 my $local_version = PVE::Network::SDN::Zones::read_etc_network_config_version();
d73c7c36 223 my $cfg = PVE::Network::SDN::running_config();
5d3e0248 224 my $sdn_version = $cfg->{version};
f5eabba0 225
6f4d0610 226 return if !$sdn_version;
3709a203 227
6f4d0610 228 if (!$local_version) {
3709a203 229 $err_config = "local sdn network configuration is not yet generated, please reload";
9ddc4a6f
TL
230 if (!$warned_about_reload) {
231 $warned_about_reload = 1;
232 warn "$err_config\n";
233 }
6f4d0610
AD
234 } elsif ($local_version < $sdn_version) {
235 $err_config = "local sdn network configuration is too old, please reload";
9ddc4a6f
TL
236 if (!$warned_about_reload) {
237 $warned_about_reload = 1;
238 warn "$err_config\n";
239 }
240 } else {
241 $warned_about_reload = 0;
f5eabba0
AD
242 }
243
244 my $status = ifquery_check();
245
5d3e0248
AD
246 my $vnet_cfg = $cfg->{vnets};
247 my $zone_cfg = $cfg->{zones};
c2b9c173 248 my $nodename = PVE::INotify::nodename();
f5eabba0 249
0f700635 250 my $vnet_status = {};
56cdcac9 251 my $zone_status = {};
f5eabba0 252
fc197ef2 253 for my $id (sort keys %{$zone_cfg->{ids}}) {
1608c165 254 next if defined($zone_cfg->{ids}->{$id}->{nodes}) && !$zone_cfg->{ids}->{$id}->{nodes}->{$nodename};
fc197ef2 255 $zone_status->{$id}->{status} = $err_config ? 'pending' : 'available';
4d7cc94f
AD
256 }
257
70da0442
TL
258 foreach my $id (sort keys %{$vnet_cfg->{ids}}) {
259 my $vnet = $vnet_cfg->{ids}->{$id};
260 my $zone = $vnet->{zone};
6b0bf99c 261 next if !defined($zone);
627b1694 262
70da0442 263 my $plugin_config = $zone_cfg->{ids}->{$zone};
153cb80d
TL
264
265 if (!defined($plugin_config)) {
266 $vnet_status->{$id}->{status} = 'error';
267 $vnet_status->{$id}->{statusmsg} = "unknown zone '$zone' configured";
268 next;
269 }
270
70da0442
TL
271 next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename};
272
4d7cc94f
AD
273 $vnet_status->{$id}->{zone} = $zone;
274 $vnet_status->{$id}->{status} = 'available';
275
fc197ef2 276 if ($err_config) {
4d7cc94f
AD
277 $vnet_status->{$id}->{status} = 'pending';
278 $vnet_status->{$id}->{statusmsg} = $err_config;
279 next;
280 }
281
70da0442 282 my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
4d7cc94f
AD
283 my $err_msg = $plugin->status($plugin_config, $zone, $id, $vnet, $status);
284 if (@{$err_msg} > 0) {
285 $vnet_status->{$id}->{status} = 'error';
286 $vnet_status->{$id}->{statusmsg} = join(',', @{$err_msg});
fd1ae504 287 $zone_status->{$zone}->{status} = 'error';
0f700635 288 }
f5eabba0 289 }
627b1694 290
fc197ef2 291 return ($zone_status, $vnet_status);
f5eabba0
AD
292}
293
eb1549e7
AD
294sub tap_create {
295 my ($iface, $bridge) = @_;
296
5d3e0248 297 my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
a1ef0eb9 298 if (!$vnet) { # fallback for classic bridge
eb1549e7 299 PVE::Network::tap_create($iface, $bridge);
a1ef0eb9 300 return;
eb1549e7
AD
301 }
302
41d40fb1 303 my $plugin_config = get_plugin_config($vnet);
eb1549e7
AD
304 my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
305 $plugin->tap_create($plugin_config, $vnet, $iface, $bridge);
306}
307
308sub veth_create {
309 my ($veth, $vethpeer, $bridge, $hwaddr) = @_;
310
5d3e0248 311 my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
a1ef0eb9 312 if (!$vnet) { # fallback for classic bridge
eb1549e7 313 PVE::Network::veth_create($veth, $vethpeer, $bridge, $hwaddr);
a1ef0eb9 314 return;
eb1549e7
AD
315 }
316
41d40fb1 317 my $plugin_config = get_plugin_config($vnet);
eb1549e7
AD
318 my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
319 $plugin->veth_create($plugin_config, $vnet, $veth, $vethpeer, $bridge, $hwaddr);
320}
321
322sub tap_plug {
323 my ($iface, $bridge, $tag, $firewall, $trunks, $rate) = @_;
324
5d3e0248 325 my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
a1ef0eb9 326 if (!$vnet) { # fallback for classic bridge
56a9e2b3 327 my $interfaces_config = PVE::INotify::read_file('interfaces');
55e68b4b
AD
328 my $opts = {};
329 $opts->{learning} = 0 if $interfaces_config->{ifaces}->{$bridge} && $interfaces_config->{ifaces}->{$bridge}->{'bridge-disable-mac-learning'};
330 PVE::Network::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate, $opts);
eb1549e7
AD
331 return;
332 }
333
41d40fb1 334 my $plugin_config = get_plugin_config($vnet);
eb1549e7
AD
335 my $nodename = PVE::INotify::nodename();
336
41d40fb1
TL
337 die "vnet $bridge is not allowed on this node\n"
338 if $plugin_config->{nodes} && !defined($plugin_config->{nodes}->{$nodename});
2ba9613b 339
2ba9613b 340 my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
912fb443 341 $plugin->tap_plug($plugin_config, $vnet, $tag, $iface, $bridge, $firewall, $trunks, $rate);
2ba9613b
AD
342}
343
fef63d88 344sub add_bridge_fdb {
22ac8058 345 my ($iface, $macaddr, $bridge) = @_;
fef63d88
AD
346
347 my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
348 if (!$vnet) { # fallback for classic bridge
22ac8058 349 PVE::Network::add_bridge_fdb($iface, $macaddr);
fef63d88
AD
350 return;
351 }
352
353 my $plugin_config = get_plugin_config($vnet);
354 my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
9a03342a 355 $plugin->add_bridge_fdb($plugin_config, $iface, $macaddr);
fef63d88
AD
356}
357
824252f6 358sub del_bridge_fdb {
22ac8058 359 my ($iface, $macaddr, $bridge) = @_;
824252f6
AD
360
361 my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
362 if (!$vnet) { # fallback for classic bridge
22ac8058 363 PVE::Network::del_bridge_fdb($iface, $macaddr);
824252f6
AD
364 return;
365 }
366
367 my $plugin_config = get_plugin_config($vnet);
368 my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
9a03342a 369 $plugin->del_bridge_fdb($plugin_config, $iface, $macaddr);
824252f6
AD
370}
371
f5eabba0
AD
3721;
373