use Data::Dumper;
use JSON;
+use PVE::Network::SDN::Vnets;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use PVE::Network::SDN::Subnets;
+
use PVE::Tools qw(extract_param dir_glob_regex run_command);
use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
-use PVE::Network::SDN::Plugin;
-use PVE::Network::SDN::VnetPlugin;
-use PVE::Network::SDN::VlanPlugin;
-use PVE::Network::SDN::VxlanPlugin;
-use PVE::Network::SDN::FrrPlugin;
-
-PVE::Network::SDN::VnetPlugin->register();
-PVE::Network::SDN::VlanPlugin->register();
-PVE::Network::SDN::VxlanPlugin->register();
-PVE::Network::SDN::FrrPlugin->register();
-PVE::Network::SDN::Plugin->init();
-
-sub sdn_config {
- my ($cfg, $sdnid, $noerr) = @_;
- die "no sdn ID specified\n" if !$sdnid;
+my $running_cfg = "sdn/.running-config";
- my $scfg = $cfg->{ids}->{$sdnid};
- die "sdn '$sdnid' does not exists\n" if (!$noerr && !$scfg);
-
- return $scfg;
-}
+my $parse_running_cfg = sub {
+ my ($filename, $raw) = @_;
-sub config {
- my $config = cfs_read_file("sdn.cfg.new");
- $config = cfs_read_file("sdn.cfg") if !keys %{$config->{ids}};
- return $config;
-}
+ my $cfg = {};
-sub write_config {
- my ($cfg) = @_;
+ return $cfg if !defined($raw) || $raw eq '';
- cfs_write_file("sdn.cfg.new", $cfg);
-}
+ eval {
+ $cfg = from_json($raw);
+ };
+ return {} if $@;
-sub lock_sdn_config {
- my ($code, $errmsg) = @_;
+ return $cfg;
+};
- cfs_lock_file("sdn.cfg.new", undef, $code);
- if (my $err = $@) {
- $errmsg ? die "$errmsg: $err" : die $err;
- }
-}
+my $write_running_cfg = sub {
+ my ($filename, $cfg) = @_;
-sub sdn_ids {
- my ($cfg) = @_;
+ my $json = to_json($cfg);
- return keys %{$cfg->{ids}};
-}
+ return $json;
+};
-sub complete_sdn {
- my ($cmdname, $pname, $cvalue) = @_;
+PVE::Cluster::cfs_register_file($running_cfg, $parse_running_cfg, $write_running_cfg);
- my $cfg = PVE::Network::SDN::config();
- return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_ids($cfg) ];
-}
+# improve me : move status code inside plugins ?
sub ifquery_check {
return $interfaces;
}
+sub status {
-sub generate_etc_network_config {
-
- my $sdn_cfg = PVE::Cluster::cfs_read_file('sdn.cfg');
- return if !$sdn_cfg;
+ my ($zone_status, $vnet_status) = PVE::Network::SDN::Zones::status();
+ return($zone_status, $vnet_status);
+}
- #read main config for physical interfaces
- my $current_config_file = "/etc/network/interfaces";
- my $fh = IO::File->new($current_config_file);
- my $interfaces_config = PVE::INotify::read_etc_network_interfaces(1,$fh);
- $fh->close();
+sub config {
+ return cfs_read_file($running_cfg);
+}
- #check uplinks
- my $uplinks = {};
- foreach my $id (keys %{$interfaces_config->{ifaces}}) {
- my $interface = $interfaces_config->{ifaces}->{$id};
- if (my $uplink = $interface->{'uplink-id'}) {
- die "uplink-id $uplink is already defined on $uplinks->{$uplink}" if $uplinks->{$uplink};
- $interface->{name} = $id;
- $uplinks->{$interface->{'uplink-id'}} = $interface;
+sub pending_config {
+ my ($running_cfg, $cfg, $type) = @_;
+
+ my $pending = {};
+
+ my $running_objects = $running_cfg->{$type}->{ids};
+ my $config_objects = $cfg->{ids};
+
+ foreach my $id (sort keys %{$running_objects}) {
+ my $running_object = $running_objects->{$id};
+ my $config_object = $config_objects->{$id};
+ foreach my $key (sort keys %{$running_object}) {
+ $pending->{$id}->{$key} = $running_object->{$key};
+ if(!keys %{$config_object}) {
+ $pending->{$id}->{state} = "deleted";
+ } elsif (!defined($config_object->{$key})) {
+ $pending->{$id}->{"pending"}->{$key} = 'deleted';
+ $pending->{$id}->{state} = "changed";
+ } elsif (PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key})
+ ne PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key})) {
+ $pending->{$id}->{state} = "changed";
+ }
}
+ $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"});
}
- my $vnet_cfg = undef;
- my $transport_cfg = undef;
+ foreach my $id (sort keys %{$config_objects}) {
+ my $running_object = $running_objects->{$id};
+ my $config_object = $config_objects->{$id};
- foreach my $id (keys %{$sdn_cfg->{ids}}) {
- if ($sdn_cfg->{ids}->{$id}->{type} eq 'vnet') {
- $vnet_cfg->{ids}->{$id} = $sdn_cfg->{ids}->{$id};
- } else {
- $transport_cfg->{ids}->{$id} = $sdn_cfg->{ids}->{$id};
+ foreach my $key (sort keys %{$config_object}) {
+ my $config_value = PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key}) if $config_object->{$key};
+ my $running_value = PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key}) if $running_object->{$key};
+ if($key eq 'type' || $key eq 'vnet') {
+ $pending->{$id}->{$key} = $config_value;
+ } else {
+ $pending->{$id}->{"pending"}->{$key} = $config_value if !defined($running_value) || ($config_value ne $running_value);
+ }
+ if(!keys %{$running_object}) {
+ $pending->{$id}->{state} = "new";
+ } elsif (!defined($running_value) && defined($config_value)) {
+ $pending->{$id}->{state} = "changed";
+ }
}
- }
+ $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"});
+ }
- #generate configuration
- my $config = {};
- foreach my $id (keys %{$vnet_cfg->{ids}}) {
- my $vnet = $vnet_cfg->{ids}->{$id};
- my $zone = $vnet->{transportzone};
+ return {ids => $pending};
- if(!$zone) {
- warn "can't generate vnet $vnet : zone $zone don't exist";
- next;
- }
+}
- my $plugin_config = $transport_cfg->{ids}->{$zone};
+sub commit_config {
- if (!defined($plugin_config)) {
- warn "can't generate vnet $vnet : zone $zone don't exist";
- next;
- }
+ my $cfg = cfs_read_file($running_cfg);
+ my $version = $cfg->{version};
- my $plugin = PVE::Network::SDN::Plugin->lookup($plugin_config->{type});
- $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $uplinks, $config);
+ if ($version) {
+ $version++;
+ } else {
+ $version = 1;
}
- my $raw_network_config = "";
- foreach my $iface (keys %$config) {
- $raw_network_config .= "\n";
- $raw_network_config .= "auto $iface\n";
- $raw_network_config .= "iface $iface\n";
- foreach my $option (@{$config->{$iface}}) {
- $raw_network_config .= "\t$option\n";
- }
- }
+ my $vnets_cfg = PVE::Network::SDN::Vnets::config();
+ my $zones_cfg = PVE::Network::SDN::Zones::config();
+ my $controllers_cfg = PVE::Network::SDN::Controllers::config();
+ my $subnets_cfg = PVE::Network::SDN::Subnets::config();
- return $raw_network_config;
+ my $vnets = { ids => $vnets_cfg->{ids} };
+ my $zones = { ids => $zones_cfg->{ids} };
+ my $controllers = { ids => $controllers_cfg->{ids} };
+ my $subnets = { ids => $subnets_cfg->{ids} };
+
+ $cfg = { version => $version, vnets => $vnets, zones => $zones, controllers => $controllers, subnets => $subnets };
+
+ cfs_write_file($running_cfg, $cfg);
}
-sub generate_frr_config {
-
- my $sdn_cfg = PVE::Cluster::cfs_read_file('sdn.cfg');
- return if !$sdn_cfg;
-
- #read main config for physical interfaces
- my $current_config_file = "/etc/network/interfaces";
- my $fh = IO::File->new($current_config_file);
- my $interfaces_config = PVE::INotify::read_etc_network_interfaces(1,$fh);
- $fh->close();
-
- #check uplinks
- my $uplinks = {};
- foreach my $id (keys %{$interfaces_config->{ifaces}}) {
- my $interface = $interfaces_config->{ifaces}->{$id};
- if (my $uplink = $interface->{'uplink-id'}) {
- die "uplink-id $uplink is already defined on $uplinks->{$uplink}" if $uplinks->{$uplink};
- $interface->{name} = $id;
- $uplinks->{$interface->{'uplink-id'}} = $interface;
- }
- }
+sub lock_sdn_config {
+ my ($code, $errmsg) = @_;
- my $frr_cfg = undef;
- my $transport_cfg = undef;
+ cfs_lock_file($running_cfg, undef, $code);
- foreach my $id (keys %{$sdn_cfg->{ids}}) {
- if ($sdn_cfg->{ids}->{$id}->{type} eq 'frr') {
- $frr_cfg->{ids}->{$id} = $sdn_cfg->{ids}->{$id};
- } elsif ($sdn_cfg->{ids}->{$id}->{type} ne 'vnet') {
- $transport_cfg->{ids}->{$id} = $sdn_cfg->{ids}->{$id};
- }
+ if (my $err = $@) {
+ $errmsg ? die "$errmsg: $err" : die $err;
}
+}
- return undef if !$frr_cfg;
+sub get_local_vnets {
- #generate configuration
- my $config = {};
+ my $rpcenv = PVE::RPCEnvironment::get();
- foreach my $id (keys %{$frr_cfg->{ids}}) {
- my $plugin_config = $frr_cfg->{ids}->{$id};
- my $asn = $plugin_config->{asn};
- if ($asn) {
- my $plugin = PVE::Network::SDN::Plugin->lookup($plugin_config->{type});
- $plugin->generate_frr_config($plugin_config, $asn, $id, $uplinks, $config);
- }
- }
+ my $authuser = $rpcenv->get_user();
- foreach my $id (keys %{$transport_cfg->{ids}}) {
- my $plugin_config = $transport_cfg->{ids}->{$id};
- my $router = $plugin_config->{router};
- if ($router) {
- my $asn = $frr_cfg->{ids}->{$router}->{asn};
- if ($asn) {
- my $plugin = PVE::Network::SDN::Plugin->lookup($plugin_config->{type});
- $plugin->generate_frr_config($plugin_config, $asn, $id, $uplinks, $config);
- }
- }
- }
+ my $nodename = PVE::INotify::nodename();
- my $raw_frr_config = "log syslog informational\n";
- $raw_frr_config .= "!\n";
+ my $cfg = PVE::Network::SDN::config();
+ my $vnets_cfg = $cfg->{vnets};
+ my $zones_cfg = $cfg->{zones};
- #vrf first
- my $vrfconfig = $config->{vrf};
- foreach my $vrf (sort keys %$vrfconfig) {
- $raw_frr_config .= "$vrf\n";
- foreach my $option (@{$vrfconfig->{$vrf}}) {
- $raw_frr_config .= " $option\n";
- }
- $raw_frr_config .= "!\n";
- }
+ my @vnetids = PVE::Network::SDN::Vnets::sdn_vnets_ids($vnets_cfg);
- #routers
- my $routerconfig = $config->{router};
- foreach my $router (sort keys %$routerconfig) {
- $raw_frr_config .= "$router\n";
- foreach my $option (@{$routerconfig->{$router}}) {
- $raw_frr_config .= " $option\n";
- }
- $raw_frr_config .= "!\n";
+ my $vnets = {};
+
+ foreach my $vnetid (@vnetids) {
+
+ my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($vnets_cfg, $vnetid);
+ my $zoneid = $vnet->{zone};
+ my $comments = $vnet->{alias};
+
+ my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
+
+ next if !$zoneid;
+ next if !$rpcenv->check_any($authuser, "/sdn/zones/$zoneid", $privs, 1);
+
+ my $zone_config = PVE::Network::SDN::Zones::sdn_zones_config($zones_cfg, $zoneid);
+
+ next if defined($zone_config->{nodes}) && !$zone_config->{nodes}->{$nodename};
+ $vnets->{$vnetid} = { type => 'vnet', active => '1', comments => $comments };
}
- $raw_frr_config .= "line vty\n";
- $raw_frr_config .= "!\n";
+ return $vnets;
+}
- return $raw_frr_config;
+sub generate_zone_config {
+ my $raw_config = PVE::Network::SDN::Zones::generate_etc_network_config();
+ PVE::Network::SDN::Zones::write_etc_network_config($raw_config);
}
-sub write_etc_network_config {
- my ($rawconfig) = @_;
+sub generate_controller_config {
+ my ($reload) = @_;
- return if !$rawconfig;
- my $sdn_interfaces_file = "/etc/network/interfaces.d/sdn";
+ my $raw_config = PVE::Network::SDN::Controllers::generate_controller_config();
+ PVE::Network::SDN::Controllers::write_controller_config($raw_config);
- my $writefh = IO::File->new($sdn_interfaces_file,">");
- print $writefh $rawconfig;
- $writefh->close();
+ PVE::Network::SDN::Controllers::reload_controller() if $reload;
}
-sub write_frr_config {
- my ($rawconfig) = @_;
-
- return if !$rawconfig;
- return if !-d "/etc/frr";
+sub encode_value {
+ my ($type, $key, $value) = @_;
- my $frr_config_file = "/etc/frr/frr.conf";
+ if ($key eq 'nodes' || $key eq 'exitnodes') {
+ if(ref($value) eq 'HASH') {
+ return join(',', sort keys(%$value));
+ } else {
+ return $value;
+ }
+ }
- my $writefh = IO::File->new($frr_config_file,">");
- print $writefh $rawconfig;
- $writefh->close();
+ return $value;
}
-sub status {
+#helpers
+sub api_request {
+ my ($method, $url, $headers, $data) = @_;
- my $cluster_sdn_file = "/etc/pve/sdn.cfg";
- my $local_sdn_file = "/etc/network/interfaces.d/sdn";
- my $err_config = undef;
+ my $encoded_data = to_json($data) if $data;
- return if !-e $cluster_sdn_file;
+ my $req = HTTP::Request->new($method,$url, $headers, $encoded_data);
- if (!-e $local_sdn_file) {
- warn "local sdn network configuration is not yet generated, please reload";
- $err_config = 'pending';
- } else {
- # fixme : use some kind of versioning info?
- my $cluster_sdn_timestamp = (stat($cluster_sdn_file))[9];
- my $local_sdn_timestamp = (stat($local_sdn_file))[9];
+ my $ua = LWP::UserAgent->new(protocols_allowed => ['http', 'https'], timeout => 30);
+ my $proxy = undef;
- if ($local_sdn_timestamp < $cluster_sdn_timestamp) {
- warn "local sdn network configuration is too old, please reload";
- $err_config = 'unknown';
- }
+ if ($proxy) {
+ $ua->proxy(['http', 'https'], $proxy);
+ } else {
+ $ua->env_proxy;
}
- my $status = ifquery_check();
+ $ua->ssl_opts(verify_hostname => 0, SSL_verify_mode => 0x00);
- my $network_cfg = PVE::Cluster::cfs_read_file('sdn.cfg');
- my $vnet_cfg = undef;
- my $transport_cfg = undef;
+ my $response = $ua->request($req);
+ my $code = $response->code;
- my $vnet_status = {};
- my $transport_status = {};
+ if ($code !~ /^2(\d+)$/) {
+ my $msg = $response->message || 'unknown';
+ die "Invalid response from server: $code $msg\n";
+ }
- foreach my $id (keys %{$network_cfg->{ids}}) {
- if ($network_cfg->{ids}->{$id}->{type} eq 'vnet') {
- my $transportzone = $network_cfg->{ids}->{$id}->{transportzone};
- $vnet_status->{$id}->{transportzone} = $transportzone;
- $transport_status->{$transportzone}->{status} = 'available' if !defined($transport_status->{$transportzone}->{status});
+ my $raw = '';
+ if (defined($response->decoded_content)) {
+ $raw = $response->decoded_content;
+ } else {
+ $raw = $response->content;
+ }
- if($err_config) {
- $vnet_status->{$id}->{status} = $err_config;
- $transport_status->{$transportzone}->{status} = $err_config;
- } elsif ($status->{$id}->{status} && $status->{$id}->{status} eq 'pass') {
- $vnet_status->{$id}->{status} = 'available';
- my $bridgeport = $status->{$id}->{config}->{'bridge-ports'};
+ return if $raw eq '';
- if ($status->{$bridgeport}->{status} && $status->{$bridgeport}->{status} ne 'pass') {
- $vnet_status->{$id}->{status} = 'error';
- $transport_status->{$transportzone}->{status} = 'error';
- }
- } else {
- $vnet_status->{$id}->{status} = 'error';
- $transport_status->{$transportzone}->{status} = 'error';
- }
- }
- }
- return($transport_status, $vnet_status);
+ my $json = '';
+ eval {
+ $json = from_json($raw);
+ };
+ die "api response is not a json" if $@;
+
+ return $json;
}
1;
-