1 package PVE
::Network
::SDN
;
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
::SDN
::Plugin
;
12 use PVE
::Network
::SDN
::VnetPlugin
;
13 use PVE
::Network
::SDN
::VlanPlugin
;
14 use PVE
::Network
::SDN
::VxlanPlugin
;
15 use PVE
::Network
::SDN
::FrrPlugin
;
17 PVE
::Network
::SDN
::VnetPlugin-
>register();
18 PVE
::Network
::SDN
::VlanPlugin-
>register();
19 PVE
::Network
::SDN
::VxlanPlugin-
>register();
20 PVE
::Network
::SDN
::FrrPlugin-
>register();
21 PVE
::Network
::SDN
::Plugin-
>init();
25 my ($cfg, $sdnid, $noerr) = @_;
27 die "no sdn ID specified\n" if !$sdnid;
29 my $scfg = $cfg->{ids
}->{$sdnid};
30 die "sdn '$sdnid' does not exists\n" if (!$noerr && !$scfg);
36 my $config = cfs_read_file
("sdn.cfg.new");
37 $config = cfs_read_file
("sdn.cfg") if !keys %{$config->{ids
}};
44 cfs_write_file
("sdn.cfg.new", $cfg);
48 my ($code, $errmsg) = @_;
50 cfs_lock_file
("sdn.cfg.new", undef, $code);
52 $errmsg ?
die "$errmsg: $err" : die $err;
59 return keys %{$cfg->{ids
}};
63 my ($cmdname, $pname, $cvalue) = @_;
65 my $cfg = PVE
::Network
::SDN
::config
();
67 return $cmdname eq 'add' ?
[] : [ PVE
::Network
::SDN
::sdn_ids
($cfg) ];
72 my $cmd = ['ifquery', '-a', '-c', '-o','json'];
75 my $reader = sub { $result .= shift };
78 run_command
($cmd, outfunc
=> $reader);
81 my $resultjson = decode_json
($result);
84 foreach my $interface (@$resultjson) {
85 my $name = $interface->{name
};
86 $interfaces->{$name} = {
87 status
=> $interface->{status
},
88 config
=> $interface->{config
},
89 config_status
=> $interface->{config_status
},
97 sub generate_etc_network_config
{
99 my $sdn_cfg = PVE
::Cluster
::cfs_read_file
('sdn.cfg');
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);
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;
119 my $vnet_cfg = undef;
120 my $transport_cfg = undef;
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};
126 $transport_cfg->{ids
}->{$id} = $sdn_cfg->{ids
}->{$id};
130 #generate configuration
132 foreach my $id (keys %{$vnet_cfg->{ids
}}) {
133 my $vnet = $vnet_cfg->{ids
}->{$id};
134 my $zone = $vnet->{transportzone
};
137 warn "can't generate vnet $vnet : zone $zone don't exist";
141 my $plugin_config = $transport_cfg->{ids
}->{$zone};
143 if (!defined($plugin_config)) {
144 warn "can't generate vnet $vnet : zone $zone don't exist";
148 my $plugin = PVE
::Network
::SDN
::Plugin-
>lookup($plugin_config->{type
});
149 $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $uplinks, $config);
152 my $raw_network_config = "";
153 foreach my $iface (keys %$config) {
154 $raw_network_config .= "\n";
155 $raw_network_config .= "auto $iface\n";
156 $raw_network_config .= "iface $iface\n";
157 foreach my $option (@{$config->{$iface}}) {
158 $raw_network_config .= "\t$option\n";
162 return $raw_network_config;
165 sub generate_frr_config
{
167 my $sdn_cfg = PVE
::Cluster
::cfs_read_file
('sdn.cfg');
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);
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;
188 my $transport_cfg = undef;
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};
198 return undef if !$frr_cfg;
200 #generate configuration
203 foreach my $id (keys %{$frr_cfg->{ids
}}) {
204 my $plugin_config = $frr_cfg->{ids
}->{$id};
205 my $asn = $plugin_config->{asn
};
207 my $plugin = PVE
::Network
::SDN
::Plugin-
>lookup($plugin_config->{type
});
208 $plugin->generate_frr_config($plugin_config, $asn, $id, $uplinks, $config);
212 foreach my $id (keys %{$transport_cfg->{ids
}}) {
213 my $plugin_config = $transport_cfg->{ids
}->{$id};
214 my $router = $plugin_config->{router
};
216 my $asn = $frr_cfg->{ids
}->{$router}->{asn
};
218 my $plugin = PVE
::Network
::SDN
::Plugin-
>lookup($plugin_config->{type
});
219 $plugin->generate_frr_config($plugin_config, $asn, $id, $uplinks, $config);
224 my $final_config = [];
225 push @{$final_config}, "log syslog informational";
227 generate_frr_recurse
($final_config, $config, undef, 0);
229 push @{$final_config}, "!";
230 push @{$final_config}, "line vty";
231 push @{$final_config}, "!";
233 my $raw_frr_config = join("\n", @{$final_config});
234 return $raw_frr_config;
237 sub sort_frr_config
{
241 $order->{'ipv4 unicast'} = 1;
242 $order->{'l2vpn evpn'} = 2;
247 $a_val = $order->{$a} if defined($order->{$a});
248 $b_val = $order->{$b} if defined($order->{$b});
250 if($a =~ /bgp (\d+)$/) {
254 if($b =~ /bgp (\d+)$/) {
258 return $a_val <=> $b_val;
261 sub generate_frr_recurse
{
262 my ($final_config, $content, $parentkey, $level) = @_;
266 $keylist->{'address-family'} = 1;
267 $keylist->{router
} = 1;
269 my $exitkeylist = {};
270 $exitkeylist->{vrf
} = 1;
271 $exitkeylist->{'address-family'} = 1;
273 #fix me, make this generic
274 my $paddinglevel = undef;
275 if($level == 1 || $level == 2) {
276 $paddinglevel = $level - 1;
277 } elsif ($level == 3 || $level == 4) {
278 $paddinglevel = $level - 2;
282 $padding = ' ' x
($paddinglevel) if $paddinglevel;
284 if (ref $content eq ref {}) {
285 foreach my $key (sort sort_frr_config
keys %$content) {
286 if ($parentkey && defined($keylist->{$parentkey})) {
287 push @{$final_config}, $padding."!";
288 push @{$final_config}, $padding."$parentkey $key";
290 push @{$final_config}, $padding."$key" if $key ne '' && !defined($keylist->{$key});
293 my $option = $content->{$key};
294 generate_frr_recurse
($final_config, $option, $key, $level+1);
296 push @{$final_config}, $padding."exit-$parentkey" if $parentkey && defined($exitkeylist->{$parentkey});
300 if (ref $content eq 'ARRAY') {
301 foreach my $value (@$content) {
302 push @{$final_config}, $padding."$value";
306 sub write_etc_network_config
{
307 my ($rawconfig) = @_;
309 return if !$rawconfig;
310 my $sdn_interfaces_file = "/etc/network/interfaces.d/sdn";
312 my $writefh = IO
::File-
>new($sdn_interfaces_file,">");
313 print $writefh $rawconfig;
317 sub write_frr_config
{
318 my ($rawconfig) = @_;
320 return if !$rawconfig;
321 return if !-d
"/etc/frr";
323 my $frr_config_file = "/etc/frr/frr.conf";
325 my $writefh = IO
::File-
>new($frr_config_file,">");
326 print $writefh $rawconfig;
333 my $cluster_sdn_file = "/etc/pve/sdn.cfg";
334 my $local_sdn_file = "/etc/network/interfaces.d/sdn";
335 my $err_config = undef;
337 return if !-e
$cluster_sdn_file;
339 if (!-e
$local_sdn_file) {
340 warn "local sdn network configuration is not yet generated, please reload";
341 $err_config = 'pending';
343 # fixme : use some kind of versioning info?
344 my $cluster_sdn_timestamp = (stat($cluster_sdn_file))[9];
345 my $local_sdn_timestamp = (stat($local_sdn_file))[9];
347 if ($local_sdn_timestamp < $cluster_sdn_timestamp) {
348 warn "local sdn network configuration is too old, please reload";
349 $err_config = 'unknown';
353 my $status = ifquery_check
();
355 my $network_cfg = PVE
::Cluster
::cfs_read_file
('sdn.cfg');
356 my $vnet_cfg = undef;
357 my $transport_cfg = undef;
359 my $vnet_status = {};
360 my $transport_status = {};
362 foreach my $id (keys %{$network_cfg->{ids
}}) {
363 if ($network_cfg->{ids
}->{$id}->{type
} eq 'vnet') {
364 my $transportzone = $network_cfg->{ids
}->{$id}->{transportzone
};
365 $vnet_status->{$id}->{transportzone
} = $transportzone;
366 $transport_status->{$transportzone}->{status
} = 'available' if !defined($transport_status->{$transportzone}->{status
});
369 $vnet_status->{$id}->{status
} = $err_config;
370 $transport_status->{$transportzone}->{status
} = $err_config;
371 } elsif ($status->{$id}->{status
} && $status->{$id}->{status
} eq 'pass') {
372 $vnet_status->{$id}->{status
} = 'available';
373 my $bridgeport = $status->{$id}->{config
}->{'bridge-ports'};
375 if ($status->{$bridgeport}->{status
} && $status->{$bridgeport}->{status
} ne 'pass') {
376 $vnet_status->{$id}->{status
} = 'error';
377 $transport_status->{$transportzone}->{status
} = 'error';
380 $vnet_status->{$id}->{status
} = 'error';
381 $transport_status->{$transportzone}->{status
} = 'error';
385 return($transport_status, $vnet_status);