1 package PVE
::Network
::SDN
::Zones
;
8 use PVE
::Tools
qw(extract_param dir_glob_regex run_command);
9 use PVE
::Cluster
qw(cfs_read_file cfs_write_file cfs_lock_file);
12 use PVE
::Network
::SDN
::Vnets
;
13 use PVE
::Network
::SDN
::Zones
::VlanPlugin
;
14 use PVE
::Network
::SDN
::Zones
::QinQPlugin
;
15 use PVE
::Network
::SDN
::Zones
::VxlanPlugin
;
16 use PVE
::Network
::SDN
::Zones
::EvpnPlugin
;
17 use PVE
::Network
::SDN
::Zones
::FaucetPlugin
;
18 use PVE
::Network
::SDN
::Zones
::SimplePlugin
;
19 use PVE
::Network
::SDN
::Zones
::Plugin
;
21 PVE
::Network
::SDN
::Zones
::VlanPlugin-
>register();
22 PVE
::Network
::SDN
::Zones
::QinQPlugin-
>register();
23 PVE
::Network
::SDN
::Zones
::VxlanPlugin-
>register();
24 PVE
::Network
::SDN
::Zones
::EvpnPlugin-
>register();
25 PVE
::Network
::SDN
::Zones
::FaucetPlugin-
>register();
26 PVE
::Network
::SDN
::Zones
::SimplePlugin-
>register();
27 PVE
::Network
::SDN
::Zones
::Plugin-
>init();
29 my $local_network_sdn_file = "/etc/network/interfaces.d/sdn";
31 sub sdn_zones_config
{
32 my ($cfg, $id, $noerr) = @_;
34 die "no sdn zone ID specified\n" if !$id;
36 my $scfg = $cfg->{ids
}->{$id};
37 die "sdn '$id' does not exist\n" if (!$noerr && !$scfg);
46 my $cfg = PVE
::Network
::SDN
::running_config
();
50 return cfs_read_file
("sdn/zones.cfg");
53 sub get_plugin_config
{
55 my $zoneid = $vnet->{zone
};
56 my $zone_cfg = PVE
::Network
::SDN
::Zones
::config
();
57 return $zone_cfg->{ids
}->{$zoneid};
63 cfs_write_file
("sdn/zones.cfg", $cfg);
69 return sort keys %{$cfg->{ids
}};
72 sub complete_sdn_zone
{
73 my ($cmdname, $pname, $cvalue) = @_;
75 my $cfg = PVE
::Network
::SDN
::running_config
();
77 return $cmdname eq 'add' ?
[] : [ PVE
::Network
::SDN
::sdn_zones_ids
($cfg) ];
81 my ($zoneid, $running) = @_;
83 my $cfg = PVE
::Network
::SDN
::Zones
::config
($running);
85 my $zone = PVE
::Network
::SDN
::Zones
::sdn_zones_config
($cfg, $zoneid, 1);
91 my ($zoneid, $running) = @_;
95 my $vnets_config = PVE
::Network
::SDN
::Vnets
::config
($running);
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;
107 sub generate_etc_network_config
{
109 my $cfg = PVE
::Network
::SDN
::running_config
();
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
};
116 return if !$vnet_cfg && !$zone_cfg;
118 my $interfaces_config = PVE
::INotify
::read_file
('interfaces');
120 #generate configuration
122 my $nodename = PVE
::INotify
::nodename
();
124 for my $id (sort keys %{$vnet_cfg->{ids
}}) {
125 my $vnet = $vnet_cfg->{ids
}->{$id};
126 my $zone = $vnet->{zone
};
129 warn "can't generate vnet '$id': no zone assigned!\n";
133 my $plugin_config = $zone_cfg->{ids
}->{$zone};
135 if (!defined($plugin_config)) {
136 warn "can't generate vnet '$id': zone $zone don't exist\n";
140 next if defined($plugin_config->{nodes
}) && !$plugin_config->{nodes
}->{$nodename};
143 if (my $controllerid = $plugin_config->{controller
}) {
144 $controller = $controller_cfg->{ids
}->{$controllerid};
147 my $plugin = PVE
::Network
::SDN
::Zones
::Plugin-
>lookup($plugin_config->{type
});
149 $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config);
152 warn "zone $zone : vnet $id : $err\n";
157 my $raw_network_config = "\#version:$version\n";
158 foreach my $iface (sort keys %$config) {
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";
167 return $raw_network_config;
170 sub write_etc_network_config
{
171 my ($rawconfig) = @_;
173 return if !$rawconfig;
175 my $writefh = IO
::File-
>new($local_network_sdn_file,">");
176 print $writefh $rawconfig;
180 sub read_etc_network_config_version
{
181 my $versionstr = PVE
::Tools
::file_read_firstline
($local_network_sdn_file);
183 return if !defined($versionstr);
185 if ($versionstr =~ m/^\#version:(\d+)$/) {
192 my $cmd = ['ifquery', '-a', '-c', '-o','json'];
195 my $reader = sub { $result .= shift };
198 run_command
($cmd, outfunc
=> $reader);
201 my $resultjson = decode_json
($result);
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
},
216 my $warned_about_reload;
220 my $err_config = undef;
222 my $local_version = PVE
::Network
::SDN
::Zones
::read_etc_network_config_version
();
223 my $cfg = PVE
::Network
::SDN
::running_config
();
224 my $sdn_version = $cfg->{version
};
226 return if !$sdn_version;
228 if (!$local_version) {
229 $err_config = "local sdn network configuration is not yet generated, please reload";
230 if (!$warned_about_reload) {
231 $warned_about_reload = 1;
232 warn "$err_config\n";
234 } elsif ($local_version < $sdn_version) {
235 $err_config = "local sdn network configuration is too old, please reload";
236 if (!$warned_about_reload) {
237 $warned_about_reload = 1;
238 warn "$err_config\n";
241 $warned_about_reload = 0;
244 my $status = ifquery_check
();
246 my $vnet_cfg = $cfg->{vnets
};
247 my $zone_cfg = $cfg->{zones
};
248 my $nodename = PVE
::INotify
::nodename
();
250 my $vnet_status = {};
251 my $zone_status = {};
253 for my $id (sort keys %{$zone_cfg->{ids
}}) {
254 next if defined($zone_cfg->{ids
}->{$id}->{nodes
}) && !$zone_cfg->{ids
}->{$id}->{nodes
}->{$nodename};
255 $zone_status->{$id}->{status
} = $err_config ?
'pending' : 'available';
258 foreach my $id (sort keys %{$vnet_cfg->{ids
}}) {
259 my $vnet = $vnet_cfg->{ids
}->{$id};
260 my $zone = $vnet->{zone
};
261 next if !defined($zone);
263 my $plugin_config = $zone_cfg->{ids
}->{$zone};
265 if (!defined($plugin_config)) {
266 $vnet_status->{$id}->{status
} = 'error';
267 $vnet_status->{$id}->{statusmsg
} = "unknown zone '$zone' configured";
271 next if defined($plugin_config->{nodes
}) && !$plugin_config->{nodes
}->{$nodename};
273 $vnet_status->{$id}->{zone
} = $zone;
274 $vnet_status->{$id}->{status
} = 'available';
277 $vnet_status->{$id}->{status
} = 'pending';
278 $vnet_status->{$id}->{statusmsg
} = $err_config;
282 my $plugin = PVE
::Network
::SDN
::Zones
::Plugin-
>lookup($plugin_config->{type
});
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});
287 $zone_status->{$zone}->{status
} = 'error';
291 return ($zone_status, $vnet_status);
295 my ($iface, $bridge) = @_;
297 my $vnet = PVE
::Network
::SDN
::Vnets
::get_vnet
($bridge, 1);
298 if (!$vnet) { # fallback for classic bridge
299 PVE
::Network
::tap_create
($iface, $bridge);
303 my $plugin_config = get_plugin_config
($vnet);
304 my $plugin = PVE
::Network
::SDN
::Zones
::Plugin-
>lookup($plugin_config->{type
});
305 $plugin->tap_create($plugin_config, $vnet, $iface, $bridge);
309 my ($veth, $vethpeer, $bridge, $hwaddr) = @_;
311 my $vnet = PVE
::Network
::SDN
::Vnets
::get_vnet
($bridge, 1);
312 if (!$vnet) { # fallback for classic bridge
313 PVE
::Network
::veth_create
($veth, $vethpeer, $bridge, $hwaddr);
317 my $plugin_config = get_plugin_config
($vnet);
318 my $plugin = PVE
::Network
::SDN
::Zones
::Plugin-
>lookup($plugin_config->{type
});
319 $plugin->veth_create($plugin_config, $vnet, $veth, $vethpeer, $bridge, $hwaddr);
323 my ($iface, $bridge, $tag, $firewall, $trunks, $rate) = @_;
325 my $vnet = PVE
::Network
::SDN
::Vnets
::get_vnet
($bridge, 1);
326 if (!$vnet) { # fallback for classic bridge
327 my $interfaces_config = PVE
::INotify
::read_file
('interfaces');
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);
334 my $plugin_config = get_plugin_config
($vnet);
335 my $nodename = PVE
::INotify
::nodename
();
337 die "vnet $bridge is not allowed on this node\n"
338 if $plugin_config->{nodes
} && !defined($plugin_config->{nodes
}->{$nodename});
340 my $plugin = PVE
::Network
::SDN
::Zones
::Plugin-
>lookup($plugin_config->{type
});
341 $plugin->tap_plug($plugin_config, $vnet, $tag, $iface, $bridge, $firewall, $trunks, $rate);
345 my ($iface, $macaddr, $bridge) = @_;
347 my $vnet = PVE
::Network
::SDN
::Vnets
::get_vnet
($bridge, 1);
348 if (!$vnet) { # fallback for classic bridge
349 PVE
::Network
::add_bridge_fdb
($iface, $macaddr);
353 my $plugin_config = get_plugin_config
($vnet);
354 my $plugin = PVE
::Network
::SDN
::Zones
::Plugin-
>lookup($plugin_config->{type
});
355 $plugin->add_bridge_fdb($plugin_config, $iface, $macaddr);
359 my ($iface, $macaddr, $bridge) = @_;
361 my $vnet = PVE
::Network
::SDN
::Vnets
::get_vnet
($bridge, 1);
362 if (!$vnet) { # fallback for classic bridge
363 PVE
::Network
::del_bridge_fdb
($iface, $macaddr);
367 my $plugin_config = get_plugin_config
($vnet);
368 my $plugin = PVE
::Network
::SDN
::Zones
::Plugin-
>lookup($plugin_config->{type
});
369 $plugin->del_bridge_fdb($plugin_config, $iface, $macaddr);