]> git.proxmox.com Git - pve-network.git/blame - PVE/Network/SDN.pm
followup trailing whitespace cleanup
[pve-network.git] / PVE / Network / SDN.pm
CommitLineData
86d22462 1package PVE::Network::SDN;
92b6f291
AD
2
3use strict;
4use warnings;
434125ce 5
92b6f291 6use Data::Dumper;
c665cefc 7use JSON;
434125ce
TL
8
9use PVE::Tools qw(extract_param dir_glob_regex run_command);
92b6f291 10use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
86d22462
AD
11use PVE::Network::SDN::Plugin;
12use PVE::Network::SDN::VnetPlugin;
13use PVE::Network::SDN::VlanPlugin;
3ee45e4c 14use PVE::Network::SDN::VxlanPlugin;
32602a38 15use PVE::Network::SDN::FrrPlugin;
92b6f291 16
86d22462
AD
17PVE::Network::SDN::VnetPlugin->register();
18PVE::Network::SDN::VlanPlugin->register();
3ee45e4c 19PVE::Network::SDN::VxlanPlugin->register();
32602a38 20PVE::Network::SDN::FrrPlugin->register();
86d22462 21PVE::Network::SDN::Plugin->init();
92b6f291
AD
22
23
6bffe819
AD
24sub sdn_config {
25 my ($cfg, $sdnid, $noerr) = @_;
92b6f291 26
6bffe819 27 die "no sdn ID specified\n" if !$sdnid;
92b6f291 28
6bffe819
AD
29 my $scfg = $cfg->{ids}->{$sdnid};
30 die "sdn '$sdnid' does not exists\n" if (!$noerr && !$scfg);
92b6f291
AD
31
32 return $scfg;
33}
34
35sub config {
6bffe819
AD
36 my $config = cfs_read_file("sdn.cfg.new");
37 $config = cfs_read_file("sdn.cfg") if !keys %{$config->{ids}};
39d04c82 38 return $config;
92b6f291
AD
39}
40
41sub write_config {
42 my ($cfg) = @_;
43
6bffe819 44 cfs_write_file("sdn.cfg.new", $cfg);
92b6f291
AD
45}
46
6bffe819 47sub lock_sdn_config {
92b6f291
AD
48 my ($code, $errmsg) = @_;
49
6bffe819 50 cfs_lock_file("sdn.cfg.new", undef, $code);
0c5021ad 51 if (my $err = $@) {
92b6f291
AD
52 $errmsg ? die "$errmsg: $err" : die $err;
53 }
54}
55
6bffe819 56sub sdn_ids {
92b6f291
AD
57 my ($cfg) = @_;
58
59 return keys %{$cfg->{ids}};
60}
61
6bffe819 62sub complete_sdn {
92b6f291
AD
63 my ($cmdname, $pname, $cvalue) = @_;
64
86d22462 65 my $cfg = PVE::Network::SDN::config();
92b6f291 66
6bffe819 67 return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_ids($cfg) ];
92b6f291
AD
68}
69
e424c7ac 70sub ifquery_check {
c665cefc
AD
71
72 my $cmd = ['ifquery', '-a', '-c', '-o','json'];
c665cefc 73
0c5021ad
TL
74 my $result = '';
75 my $reader = sub { $result .= shift };
c665cefc
AD
76
77 eval {
6e9fff39 78 run_command($cmd, outfunc => $reader);
c665cefc
AD
79 };
80
6e9fff39 81 my $resultjson = decode_json($result);
c665cefc
AD
82 my $interfaces = {};
83
84 foreach my $interface (@$resultjson) {
6e9fff39
AD
85 my $name = $interface->{name};
86 $interfaces->{$name} = {
87 status => $interface->{status},
88 config => $interface->{config},
89 config_status => $interface->{config_status},
90 };
c665cefc
AD
91 }
92
93 return $interfaces;
94}
95
80348b2d
AD
96
97sub generate_etc_network_config {
98
99 my $sdn_cfg = PVE::Cluster::cfs_read_file('sdn.cfg');
100 return if !$sdn_cfg;
101
102 #read main config for physical interfaces
103 my $current_config_file = "/etc/network/interfaces";
104 my $fh = IO::File->new($current_config_file);
105 my $interfaces_config = PVE::INotify::read_etc_network_interfaces(1,$fh);
106 $fh->close();
107
108 #check uplinks
109 my $uplinks = {};
110 foreach my $id (keys %{$interfaces_config->{ifaces}}) {
111 my $interface = $interfaces_config->{ifaces}->{$id};
112 if (my $uplink = $interface->{'uplink-id'}) {
113 die "uplink-id $uplink is already defined on $uplinks->{$uplink}" if $uplinks->{$uplink};
114 $interface->{name} = $id;
115 $uplinks->{$interface->{'uplink-id'}} = $interface;
116 }
117 }
118
119 my $vnet_cfg = undef;
120 my $transport_cfg = undef;
121
122 foreach my $id (keys %{$sdn_cfg->{ids}}) {
123 if ($sdn_cfg->{ids}->{$id}->{type} eq 'vnet') {
124 $vnet_cfg->{ids}->{$id} = $sdn_cfg->{ids}->{$id};
125 } else {
126 $transport_cfg->{ids}->{$id} = $sdn_cfg->{ids}->{$id};
127 }
128 }
129
130 #generate configuration
93dea3aa 131 my $config = {};
80348b2d
AD
132 foreach my $id (keys %{$vnet_cfg->{ids}}) {
133 my $vnet = $vnet_cfg->{ids}->{$id};
134 my $zone = $vnet->{transportzone};
135
136 if(!$zone) {
137 warn "can't generate vnet $vnet : zone $zone don't exist";
138 next;
139 }
140
141 my $plugin_config = $transport_cfg->{ids}->{$zone};
142
143 if (!defined($plugin_config)) {
144 warn "can't generate vnet $vnet : zone $zone don't exist";
145 next;
146 }
147
148 my $plugin = PVE::Network::SDN::Plugin->lookup($plugin_config->{type});
93dea3aa 149 $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $uplinks, $config);
80348b2d
AD
150 }
151
93dea3aa 152 my $raw_network_config = "";
87d8b623 153 foreach my $iface (keys %$config) {
93dea3aa
AD
154 $raw_network_config .= "\n";
155 $raw_network_config .= "auto $iface\n";
156 $raw_network_config .= "iface $iface\n";
87d8b623 157 foreach my $option (@{$config->{$iface}}) {
93dea3aa
AD
158 $raw_network_config .= "\t$option\n";
159 }
160 }
161
87d8b623
AD
162 return $raw_network_config;
163}
164
165sub generate_frr_config {
166
167 my $sdn_cfg = PVE::Cluster::cfs_read_file('sdn.cfg');
168 return if !$sdn_cfg;
169
170 #read main config for physical interfaces
171 my $current_config_file = "/etc/network/interfaces";
172 my $fh = IO::File->new($current_config_file);
173 my $interfaces_config = PVE::INotify::read_etc_network_interfaces(1,$fh);
174 $fh->close();
175
176 #check uplinks
177 my $uplinks = {};
178 foreach my $id (keys %{$interfaces_config->{ifaces}}) {
179 my $interface = $interfaces_config->{ifaces}->{$id};
180 if (my $uplink = $interface->{'uplink-id'}) {
181 die "uplink-id $uplink is already defined on $uplinks->{$uplink}" if $uplinks->{$uplink};
182 $interface->{name} = $id;
183 $uplinks->{$interface->{'uplink-id'}} = $interface;
184 }
185 }
186
187 my $frr_cfg = undef;
188 my $transport_cfg = undef;
189
190 foreach my $id (keys %{$sdn_cfg->{ids}}) {
191 if ($sdn_cfg->{ids}->{$id}->{type} eq 'frr') {
192 $frr_cfg->{ids}->{$id} = $sdn_cfg->{ids}->{$id};
193 } elsif ($sdn_cfg->{ids}->{$id}->{type} ne 'vnet') {
194 $transport_cfg->{ids}->{$id} = $sdn_cfg->{ids}->{$id};
195 }
196 }
197
62e54fb7
AD
198 return undef if !$frr_cfg;
199
87d8b623
AD
200 #generate configuration
201 my $config = {};
202
074d270b 203 foreach my $id (sort keys %{$frr_cfg->{ids}}) {
87d8b623 204 my $plugin_config = $frr_cfg->{ids}->{$id};
074d270b
AD
205 my $plugin = PVE::Network::SDN::Plugin->lookup($plugin_config->{type});
206 $plugin->generate_frr_config($plugin_config, $plugin_config, $id, $uplinks, $config);
87d8b623
AD
207 }
208
074d270b 209 foreach my $id (sort keys %{$transport_cfg->{ids}}) {
87d8b623 210 my $plugin_config = $transport_cfg->{ids}->{$id};
074d270b
AD
211 my $routerid = $plugin_config->{router};
212 if ($routerid) {
213 my $router = $frr_cfg->{ids}->{$routerid};
214 if ($router) {
87d8b623 215 my $plugin = PVE::Network::SDN::Plugin->lookup($plugin_config->{type});
074d270b 216 $plugin->generate_frr_config($plugin_config, $router, $id, $uplinks, $config);
87d8b623
AD
217 }
218 }
219 }
220
17854295
AD
221 my $final_config = [];
222 push @{$final_config}, "log syslog informational";
6c8d2382 223 push @{$final_config}, "!";
93dea3aa 224
17854295 225 generate_frr_recurse($final_config, $config, undef, 0);
87d8b623 226
17854295
AD
227 push @{$final_config}, "!";
228 push @{$final_config}, "line vty";
229 push @{$final_config}, "!";
87d8b623 230
17854295 231 my $raw_frr_config = join("\n", @{$final_config});
87d8b623 232 return $raw_frr_config;
80348b2d
AD
233}
234
17854295
AD
235sub sort_frr_config {
236 my $order = {};
237 $order->{''} = 0;
238 $order->{'vrf'} = 1;
239 $order->{'ipv4 unicast'} = 1;
074d270b
AD
240 $order->{'ipv6 unicast'} = 2;
241 $order->{'l2vpn evpn'} = 3;
17854295
AD
242
243 my $a_val = 100;
244 my $b_val = 100;
245
246 $a_val = $order->{$a} if defined($order->{$a});
247 $b_val = $order->{$b} if defined($order->{$b});
ede0f04a 248
17854295
AD
249 if($a =~ /bgp (\d+)$/) {
250 $a_val = 2;
251 }
252
253 if($b =~ /bgp (\d+)$/) {
254 $b_val = 2;
255 }
256
257 return $a_val <=> $b_val;
258}
259
260sub generate_frr_recurse{
261 my ($final_config, $content, $parentkey, $level) = @_;
262
263 my $keylist = {};
264 $keylist->{vrf} = 1;
265 $keylist->{'address-family'} = 1;
266 $keylist->{router} = 1;
267
268 my $exitkeylist = {};
269 $exitkeylist->{vrf} = 1;
270 $exitkeylist->{'address-family'} = 1;
271
272 #fix me, make this generic
273 my $paddinglevel = undef;
274 if($level == 1 || $level == 2) {
275 $paddinglevel = $level - 1;
276 } elsif ($level == 3 || $level == 4) {
277 $paddinglevel = $level - 2;
278 }
279
280 my $padding = "";
281 $padding = ' ' x ($paddinglevel) if $paddinglevel;
282
283 if (ref $content eq ref {}) {
284 foreach my $key (sort sort_frr_config keys %$content) {
285 if ($parentkey && defined($keylist->{$parentkey})) {
286 push @{$final_config}, $padding."!";
287 push @{$final_config}, $padding."$parentkey $key";
288 } else {
289 push @{$final_config}, $padding."$key" if $key ne '' && !defined($keylist->{$key});
290 }
291
292 my $option = $content->{$key};
293 generate_frr_recurse($final_config, $option, $key, $level+1);
ede0f04a 294
17854295
AD
295 push @{$final_config}, $padding."exit-$parentkey" if $parentkey && defined($exitkeylist->{$parentkey});
296 }
297 }
298
299 if (ref $content eq 'ARRAY') {
300 foreach my $value (@$content) {
301 push @{$final_config}, $padding."$value";
302 }
303 }
304}
80348b2d
AD
305sub write_etc_network_config {
306 my ($rawconfig) = @_;
307
308 return if !$rawconfig;
309 my $sdn_interfaces_file = "/etc/network/interfaces.d/sdn";
310
311 my $writefh = IO::File->new($sdn_interfaces_file,">");
312 print $writefh $rawconfig;
313 $writefh->close();
314}
315
ecdd8c12
AD
316sub write_frr_config {
317 my ($rawconfig) = @_;
318
319 return if !$rawconfig;
320 return if !-d "/etc/frr";
321
322 my $frr_config_file = "/etc/frr/frr.conf";
323
324 my $writefh = IO::File->new($frr_config_file,">");
325 print $writefh $rawconfig;
326 $writefh->close();
327}
328
80348b2d 329
e424c7ac
AD
330sub status {
331
332 my $cluster_sdn_file = "/etc/pve/sdn.cfg";
333 my $local_sdn_file = "/etc/network/interfaces.d/sdn";
334 my $err_config = undef;
335
336 return if !-e $cluster_sdn_file;
337
338 if (!-e $local_sdn_file) {
339 warn "local sdn network configuration is not yet generated, please reload";
340 $err_config = 'pending';
341 } else {
342 # fixme : use some kind of versioning info?
343 my $cluster_sdn_timestamp = (stat($cluster_sdn_file))[9];
344 my $local_sdn_timestamp = (stat($local_sdn_file))[9];
345
346 if ($local_sdn_timestamp < $cluster_sdn_timestamp) {
347 warn "local sdn network configuration is too old, please reload";
348 $err_config = 'unknown';
349 }
350 }
351
352 my $status = ifquery_check();
80348b2d 353
e424c7ac
AD
354 my $network_cfg = PVE::Cluster::cfs_read_file('sdn.cfg');
355 my $vnet_cfg = undef;
356 my $transport_cfg = undef;
357
358 my $vnet_status = {};
359 my $transport_status = {};
360
361 foreach my $id (keys %{$network_cfg->{ids}}) {
362 if ($network_cfg->{ids}->{$id}->{type} eq 'vnet') {
363 my $transportzone = $network_cfg->{ids}->{$id}->{transportzone};
364 $vnet_status->{$id}->{transportzone} = $transportzone;
365 $transport_status->{$transportzone}->{status} = 'available' if !defined($transport_status->{$transportzone}->{status});
366
367 if($err_config) {
368 $vnet_status->{$id}->{status} = $err_config;
369 $transport_status->{$transportzone}->{status} = $err_config;
370 } elsif ($status->{$id}->{status} && $status->{$id}->{status} eq 'pass') {
371 $vnet_status->{$id}->{status} = 'available';
372 my $bridgeport = $status->{$id}->{config}->{'bridge-ports'};
373
374 if ($status->{$bridgeport}->{status} && $status->{$bridgeport}->{status} ne 'pass') {
375 $vnet_status->{$id}->{status} = 'error';
376 $transport_status->{$transportzone}->{status} = 'error';
377 }
378 } else {
379 $vnet_status->{$id}->{status} = 'error';
380 $transport_status->{$transportzone}->{status} = 'error';
381 }
382 }
383 }
384 return($transport_status, $vnet_status);
385}
386
3871;
80348b2d 388