]> git.proxmox.com Git - pve-network.git/blob - PVE/Network/SDN/Zones.pm
api: add running/pending zones/vnets/subnets/controllers
[pve-network.git] / PVE / Network / SDN / Zones.pm
1 package PVE::Network::SDN::Zones;
2
3 use strict;
4 use warnings;
5
6 use Data::Dumper;
7 use JSON;
8
9 use PVE::Tools qw(extract_param dir_glob_regex run_command);
10 use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
11 use PVE::Network;
12
13 use PVE::Network::SDN::Vnets;
14 use PVE::Network::SDN::Zones::VlanPlugin;
15 use PVE::Network::SDN::Zones::QinQPlugin;
16 use PVE::Network::SDN::Zones::VxlanPlugin;
17 use PVE::Network::SDN::Zones::EvpnPlugin;
18 use PVE::Network::SDN::Zones::FaucetPlugin;
19 use PVE::Network::SDN::Zones::SimplePlugin;
20 use PVE::Network::SDN::Zones::Plugin;
21
22 PVE::Network::SDN::Zones::VlanPlugin->register();
23 PVE::Network::SDN::Zones::QinQPlugin->register();
24 PVE::Network::SDN::Zones::VxlanPlugin->register();
25 PVE::Network::SDN::Zones::EvpnPlugin->register();
26 PVE::Network::SDN::Zones::FaucetPlugin->register();
27 PVE::Network::SDN::Zones::SimplePlugin->register();
28 PVE::Network::SDN::Zones::Plugin->init();
29
30 my $local_network_sdn_file = "/etc/network/interfaces.d/sdn";
31
32 sub sdn_zones_config {
33 my ($cfg, $id, $noerr) = @_;
34
35 die "no sdn zone ID specified\n" if !$id;
36
37 my $scfg = $cfg->{ids}->{$id};
38 die "sdn '$id' does not exist\n" if (!$noerr && !$scfg);
39
40 return $scfg;
41 }
42
43 sub config {
44 my $config = cfs_read_file("sdn/zones.cfg");
45 return $config;
46 }
47
48 sub get_plugin_config {
49 my ($vnet) = @_;
50 my $zoneid = $vnet->{zone};
51 my $zone_cfg = PVE::Network::SDN::Zones::config();
52 return $zone_cfg->{ids}->{$zoneid};
53 }
54
55 sub write_config {
56 my ($cfg) = @_;
57
58 cfs_write_file("sdn/zones.cfg", $cfg);
59 }
60
61 sub sdn_zones_ids {
62 my ($cfg) = @_;
63
64 return keys %{$cfg->{ids}};
65 }
66
67 sub complete_sdn_zone {
68 my ($cmdname, $pname, $cvalue) = @_;
69
70 my $cfg = PVE::Network::SDN::config();
71
72 return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_zones_ids($cfg) ];
73 }
74
75
76 sub generate_etc_network_config {
77
78 my $cfg = PVE::Network::SDN::config();
79
80 my $version = $cfg->{version};
81 my $vnet_cfg = $cfg->{vnets};
82 my $zone_cfg = $cfg->{zones};
83 my $subnet_cfg = $cfg->{subnets};
84 my $controller_cfg = $cfg->{controllers};
85 return if !$vnet_cfg && !$zone_cfg;
86
87 my $interfaces_config = PVE::INotify::read_file('interfaces');
88
89 #generate configuration
90 my $config = {};
91 my $nodename = PVE::INotify::nodename();
92
93 for my $id (sort keys %{$vnet_cfg->{ids}}) {
94 my $vnet = $vnet_cfg->{ids}->{$id};
95 my $zone = $vnet->{zone};
96
97 if (!$zone) {
98 warn "can't generate vnet '$id': no zone assigned!\n";
99 next;
100 }
101
102 my $plugin_config = $zone_cfg->{ids}->{$zone};
103
104 if (!defined($plugin_config)) {
105 warn "can't generate vnet '$id': zone $zone don't exist\n";
106 next;
107 }
108
109 next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename};
110
111 my $controller;
112 if (my $controllerid = $plugin_config->{controller}) {
113 $controller = $controller_cfg->{ids}->{$controllerid};
114 }
115
116 my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
117 eval {
118 $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $controller, $subnet_cfg, $interfaces_config, $config);
119 };
120 if (my $err = $@) {
121 warn "zone $zone : vnet $id : $err\n";
122 next;
123 }
124 }
125
126 my $raw_network_config = "\#version:$version\n";
127 foreach my $iface (sort keys %$config) {
128 $raw_network_config .= "\n";
129 $raw_network_config .= "auto $iface\n";
130 $raw_network_config .= "iface $iface\n";
131 foreach my $option (@{$config->{$iface}}) {
132 $raw_network_config .= "\t$option\n";
133 }
134 }
135
136 return $raw_network_config;
137 }
138
139 sub write_etc_network_config {
140 my ($rawconfig) = @_;
141
142 return if !$rawconfig;
143
144 my $writefh = IO::File->new($local_network_sdn_file,">");
145 print $writefh $rawconfig;
146 $writefh->close();
147 }
148
149 sub read_etc_network_config_version {
150 my $versionstr = PVE::Tools::file_read_firstline($local_network_sdn_file);
151
152 return if !defined($versionstr);
153
154 if ($versionstr =~ m/^\#version:(\d+)$/) {
155 return $1;
156 }
157 }
158
159 sub ifquery_check {
160
161 my $cmd = ['ifquery', '-a', '-c', '-o','json'];
162
163 my $result = '';
164 my $reader = sub { $result .= shift };
165
166 eval {
167 run_command($cmd, outfunc => $reader);
168 };
169
170 my $resultjson = decode_json($result);
171 my $interfaces = {};
172
173 foreach my $interface (@$resultjson) {
174 my $name = $interface->{name};
175 $interfaces->{$name} = {
176 status => $interface->{status},
177 config => $interface->{config},
178 config_status => $interface->{config_status},
179 };
180 }
181
182 return $interfaces;
183 }
184
185 my $warned_about_reload;
186
187 sub status {
188
189 my $err_config = undef;
190
191 my $local_version = PVE::Network::SDN::Zones::read_etc_network_config_version();
192 my $cfg = PVE::Network::SDN::config();
193 my $sdn_version = $cfg->{version};
194
195 return if !$sdn_version;
196
197 if (!$local_version) {
198 $err_config = "local sdn network configuration is not yet generated, please reload";
199 if (!$warned_about_reload) {
200 $warned_about_reload = 1;
201 warn "$err_config\n";
202 }
203 } elsif ($local_version < $sdn_version) {
204 $err_config = "local sdn network configuration is too old, please reload";
205 if (!$warned_about_reload) {
206 $warned_about_reload = 1;
207 warn "$err_config\n";
208 }
209 } else {
210 $warned_about_reload = 0;
211 }
212
213 my $status = ifquery_check();
214
215
216 my $vnet_cfg = $cfg->{vnets};
217 my $zone_cfg = $cfg->{zones};
218 my $nodename = PVE::INotify::nodename();
219
220 my $vnet_status = {};
221 my $zone_status = {};
222
223 for my $id (sort keys %{$zone_cfg->{ids}}) {
224 $zone_status->{$id}->{status} = $err_config ? 'pending' : 'available';
225 }
226
227 foreach my $id (sort keys %{$vnet_cfg->{ids}}) {
228 my $vnet = $vnet_cfg->{ids}->{$id};
229 my $zone = $vnet->{zone};
230 next if !$zone;
231
232 my $plugin_config = $zone_cfg->{ids}->{$zone};
233 next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename};
234
235 $vnet_status->{$id}->{zone} = $zone;
236 $vnet_status->{$id}->{status} = 'available';
237
238 if ($err_config) {
239 $vnet_status->{$id}->{status} = 'pending';
240 $vnet_status->{$id}->{statusmsg} = $err_config;
241 next;
242 }
243
244 my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
245 my $err_msg = $plugin->status($plugin_config, $zone, $id, $vnet, $status);
246 if (@{$err_msg} > 0) {
247 $vnet_status->{$id}->{status} = 'error';
248 $vnet_status->{$id}->{statusmsg} = join(',', @{$err_msg});
249 $zone_status->{$id}->{status} = 'error';
250 }
251 }
252
253 return ($zone_status, $vnet_status);
254 }
255
256 sub tap_create {
257 my ($iface, $bridge) = @_;
258
259 my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
260 if (!$vnet) { # fallback for classic bridge
261 PVE::Network::tap_create($iface, $bridge);
262 return;
263 }
264
265 my $plugin_config = get_plugin_config($vnet);
266 my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
267 $plugin->tap_create($plugin_config, $vnet, $iface, $bridge);
268 }
269
270 sub veth_create {
271 my ($veth, $vethpeer, $bridge, $hwaddr) = @_;
272
273 my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
274 if (!$vnet) { # fallback for classic bridge
275 PVE::Network::veth_create($veth, $vethpeer, $bridge, $hwaddr);
276 return;
277 }
278
279 my $plugin_config = get_plugin_config($vnet);
280 my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
281 $plugin->veth_create($plugin_config, $vnet, $veth, $vethpeer, $bridge, $hwaddr);
282 }
283
284 sub tap_plug {
285 my ($iface, $bridge, $tag, $firewall, $trunks, $rate) = @_;
286
287 my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
288 if (!$vnet) { # fallback for classic bridge
289 PVE::Network::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate);
290 return;
291 }
292
293 my $plugin_config = get_plugin_config($vnet);
294 my $nodename = PVE::INotify::nodename();
295
296 die "vnet $bridge is not allowed on this node\n"
297 if $plugin_config->{nodes} && !defined($plugin_config->{nodes}->{$nodename});
298
299 my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
300 $plugin->tap_plug($plugin_config, $vnet, $tag, $iface, $bridge, $firewall, $trunks, $rate);
301 }
302
303 1;
304