]> git.proxmox.com Git - pve-network.git/commitdiff
split transport/controllers/vnet to separate plugins
authorAlexandre Derumier <aderumier@odiso.com>
Tue, 26 Nov 2019 09:00:16 +0000 (10:00 +0100)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Tue, 26 Nov 2019 11:33:40 +0000 (12:33 +0100)
Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
26 files changed:
PVE/Network/SDN.pm
PVE/Network/SDN/Controllers.pm [new file with mode: 0644]
PVE/Network/SDN/Controllers/FaucetPlugin.pm [new file with mode: 0644]
PVE/Network/SDN/Controllers/FrrEvpnPlugin.pm [new file with mode: 0644]
PVE/Network/SDN/Controllers/Makefile [new file with mode: 0644]
PVE/Network/SDN/Controllers/Plugin.pm [new file with mode: 0644]
PVE/Network/SDN/EvpnControllerPlugin.pm [deleted file]
PVE/Network/SDN/EvpnPlugin.pm [deleted file]
PVE/Network/SDN/FaucetControllerPlugin.pm [deleted file]
PVE/Network/SDN/FaucetPlugin.pm [deleted file]
PVE/Network/SDN/Makefile
PVE/Network/SDN/Plugin.pm [deleted file]
PVE/Network/SDN/QinQPlugin.pm [deleted file]
PVE/Network/SDN/VlanPlugin.pm [deleted file]
PVE/Network/SDN/VnetPlugin.pm
PVE/Network/SDN/Vnets.pm [new file with mode: 0644]
PVE/Network/SDN/VxlanPlugin.pm [deleted file]
PVE/Network/SDN/Zones.pm [new file with mode: 0644]
PVE/Network/SDN/Zones/EvpnPlugin.pm [new file with mode: 0644]
PVE/Network/SDN/Zones/FaucetPlugin.pm [new file with mode: 0644]
PVE/Network/SDN/Zones/Makefile [new file with mode: 0644]
PVE/Network/SDN/Zones/Plugin.pm [new file with mode: 0644]
PVE/Network/SDN/Zones/QinQPlugin.pm [new file with mode: 0644]
PVE/Network/SDN/Zones/VlanPlugin.pm [new file with mode: 0644]
PVE/Network/SDN/Zones/VxlanPlugin.pm [new file with mode: 0644]
test/generateconfig.pl

index 96f76d1821293b4707daf552ff728833e824ff4e..408822117c65631f9723b63098e7f6768cbf67f0 100644 (file)
@@ -6,74 +6,12 @@ use warnings;
 use Data::Dumper;
 use JSON;
 
+use PVE::Network::SDN::Zones;
+
 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::FaucetPlugin;
-use PVE::Network::SDN::FaucetControllerPlugin;
-use PVE::Network::SDN::EvpnPlugin;
-use PVE::Network::SDN::EvpnControllerPlugin;
-use PVE::Network::SDN::QinQPlugin;
-
-PVE::Network::SDN::VnetPlugin->register();
-PVE::Network::SDN::VlanPlugin->register();
-PVE::Network::SDN::VxlanPlugin->register();
-PVE::Network::SDN::FaucetControllerPlugin->register();
-PVE::Network::SDN::FaucetPlugin->register();
-PVE::Network::SDN::EvpnPlugin->register();
-PVE::Network::SDN::EvpnControllerPlugin->register();
-PVE::Network::SDN::QinQPlugin->register();
-PVE::Network::SDN::Plugin->init();
-
-
-sub sdn_config {
-    my ($cfg, $sdnid, $noerr) = @_;
-
-    die "no sdn ID specified\n" if !$sdnid;
-
-    my $scfg = $cfg->{ids}->{$sdnid};
-    die "sdn '$sdnid' does not exists\n" if (!$noerr && !$scfg);
-
-    return $scfg;
-}
-
-sub config {
-    my $config = cfs_read_file("sdn.cfg.new");
-    $config = cfs_read_file("sdn.cfg") if !keys %{$config->{ids}};
-    return $config;
-}
-
-sub write_config {
-    my ($cfg) = @_;
-
-    cfs_write_file("sdn.cfg.new", $cfg);
-}
-
-sub lock_sdn_config {
-    my ($code, $errmsg) = @_;
-
-    cfs_lock_file("sdn.cfg.new", undef, $code);
-    if (my $err = $@) {
-        $errmsg ? die "$errmsg: $err" : die $err;
-    }
-}
-
-sub sdn_ids {
-    my ($cfg) = @_;
-
-    return keys %{$cfg->{ids}};
-}
 
-sub complete_sdn {
-    my ($cmdname, $pname, $cvalue) = @_;
-
-    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 {
 
@@ -101,236 +39,9 @@ sub ifquery_check {
     return $interfaces;
 }
 
-
-sub generate_etc_network_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;
-       }
-    }
-
-    my $vnet_cfg = undef;
-    my $transport_cfg = undef;
-
-    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};
-       }
-    }
-
-    #generate configuration
-    my $config = {};
-    foreach my $id (keys %{$vnet_cfg->{ids}}) {
-       my $vnet = $vnet_cfg->{ids}->{$id};
-       my $zone = $vnet->{transportzone};
-
-       if(!$zone) {
-           warn "can't generate vnet $vnet : zone $zone don't exist";
-           next;
-       }
-
-       my $plugin_config = $transport_cfg->{ids}->{$zone};
-
-       if (!defined($plugin_config)) {
-           warn "can't generate vnet $vnet : zone $zone don't exist";
-           next;
-       }
-
-       my $plugin = PVE::Network::SDN::Plugin->lookup($plugin_config->{type});
-       $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $uplinks, $config);
-    }
-
-    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";
-       }
-    }
-
-    return $raw_network_config;
-}
-
-sub generate_controller_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;
-       }
-    }
-
-    #generate configuration
-    my $config = {};
-
-    foreach my $id (keys %{$sdn_cfg->{ids}}) {
-       my $plugin_config = $sdn_cfg->{ids}->{$id};
-       my $plugin = PVE::Network::SDN::Plugin->lookup($plugin_config->{type});
-       my $pd = $plugin->plugindata();
-       my $role = $pd->{role};
-       if ($role eq 'controller') {
-           $plugin->generate_controller_config($plugin_config, $plugin_config, $id, $uplinks, $config);
-       } elsif ($role eq 'transport') {
-           my $controllerid = $plugin_config->{controller};
-           if ($controllerid) {
-               my $controller = $sdn_cfg->{ids}->{$controllerid};
-               if ($controller) {
-                   my $controller_plugin = PVE::Network::SDN::Plugin->lookup($controller->{type});
-                   $controller_plugin->generate_controller_transport_config($plugin_config, $controller, $id, $uplinks, $config);
-               }
-           }
-       } elsif ($role eq 'vnet') {
-           my $transportid = $plugin_config->{transportzone};
-           if ($transportid) {
-               my $transport = $sdn_cfg->{ids}->{$transportid};
-               if ($transport) {
-                   my $controllerid = $transport->{controller};
-                   if ($controllerid) {
-                       my $controller = $sdn_cfg->{ids}->{$controllerid};
-                       if ($controller) {
-                           my $controller_plugin = PVE::Network::SDN::Plugin->lookup($controller->{type});
-                           $controller_plugin->generate_controller_vnet_config($plugin_config, $controller, $transportid, $id, $config);
-                       }
-                   }
-               }
-           }
-       }
-    }
-
-    return $config;
-}
-
-
-sub reload_controller {
-
-    my $sdn_cfg = PVE::Cluster::cfs_read_file('sdn.cfg');
-    return if !$sdn_cfg;
-
-    foreach my $id (keys %{$sdn_cfg->{ids}}) {
-       my $plugin_config = $sdn_cfg->{ids}->{$id};
-       my $plugin = PVE::Network::SDN::Plugin->lookup($plugin_config->{type});
-       my $pd = $plugin->plugindata();
-       my $role = $pd->{role};
-       if ($role eq 'controller') {
-           $plugin->reload_controller();
-       }
-    }
-}
-
-sub write_etc_network_config {
-    my ($rawconfig) = @_;
-
-    return if !$rawconfig;
-    my $sdn_interfaces_file = "/etc/network/interfaces.d/sdn";
-
-    my $writefh = IO::File->new($sdn_interfaces_file,">");
-    print $writefh $rawconfig;
-    $writefh->close();
-}
-
-sub write_controller_config {
-    my ($config) = @_;
-
-    my $sdn_cfg = PVE::Cluster::cfs_read_file('sdn.cfg');
-    return if !$sdn_cfg;
-
-    foreach my $id (keys %{$sdn_cfg->{ids}}) {
-       my $plugin_config = $sdn_cfg->{ids}->{$id};
-       my $plugin = PVE::Network::SDN::Plugin->lookup($plugin_config->{type});
-       my $pd = $plugin->plugindata();
-       my $role = $pd->{role};
-       if ($role eq 'controller') {
-               $plugin->write_controller_config($plugin_config, $config);
-       }
-    }
-}
-
 sub status {
 
-    my $cluster_sdn_file = "/etc/pve/sdn.cfg";
-    my $local_sdn_file = "/etc/network/interfaces.d/sdn";
-    my $err_config = undef;
-
-    return if !-e $cluster_sdn_file;
-
-    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];
-
-       if ($local_sdn_timestamp < $cluster_sdn_timestamp) {
-           warn "local sdn network configuration is too old, please reload";
-           $err_config = 'unknown';
-        }
-    }
-
-    my $status = ifquery_check();
-
-    my $network_cfg = PVE::Cluster::cfs_read_file('sdn.cfg');
-    my $vnet_cfg = undef;
-    my $transport_cfg = undef;
-
-    my $vnet_status = {};
-    my $transport_status = {};
-
-    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});
-
-           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'};
-
-               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';
-           }
-       }
-    }
+    my ($transport_status, $vnet_status) = PVE::Network::SDN::Zones::status();
     return($transport_status, $vnet_status);
 }
 
diff --git a/PVE/Network/SDN/Controllers.pm b/PVE/Network/SDN/Controllers.pm
new file mode 100644 (file)
index 0000000..19ad15a
--- /dev/null
@@ -0,0 +1,158 @@
+package PVE::Network::SDN::Controllers;
+
+use strict;
+use warnings;
+
+use Data::Dumper;
+use JSON;
+
+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::Vnets;
+use PVE::Network::SDN::Zones;
+
+use PVE::Network::SDN::Controllers::FrrEvpnPlugin;
+use PVE::Network::SDN::Controllers::FaucetPlugin;
+use PVE::Network::SDN::Controllers::Plugin;
+PVE::Network::SDN::Controllers::FrrEvpnPlugin->register();
+PVE::Network::SDN::Controllers::FaucetPlugin->register();
+PVE::Network::SDN::Controllers::Plugin->init();
+
+
+sub sdn_controllers_config {
+    my ($cfg, $id, $noerr) = @_;
+
+    die "no sdn controller ID specified\n" if !$id;
+
+    my $scfg = $cfg->{ids}->{$id};
+    die "sdn '$id' does not exists\n" if (!$noerr && !$scfg);
+
+    return $scfg;
+}
+
+sub config {
+    my $config = cfs_read_file("sdn/controllers.cfg.new");
+    $config = cfs_read_file("sdn/controllers.cfg") if !keys %{$config->{ids}};
+    return $config;
+}
+
+sub write_config {
+    my ($cfg) = @_;
+
+    cfs_write_file("sdn/controllers.cfg.new", $cfg);
+}
+
+sub lock_sdn_controllers_config {
+    my ($code, $errmsg) = @_;
+
+    cfs_lock_file("sdn/controllers.cfg.new", undef, $code);
+    if (my $err = $@) {
+        $errmsg ? die "$errmsg: $err" : die $err;
+    }
+}
+
+sub sdn_controllers_ids {
+    my ($cfg) = @_;
+
+    return keys %{$cfg->{ids}};
+}
+
+sub complete_sdn_controller {
+    my ($cmdname, $pname, $cvalue) = @_;
+
+    my $cfg = PVE::Network::SDN::config();
+
+    return  $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_controllers_ids($cfg) ];
+}
+
+sub generate_controller_config {
+
+    my $vnet_cfg = PVE::Cluster::cfs_read_file('sdn/vnets.cfg');
+    my $transport_cfg = PVE::Cluster::cfs_read_file('sdn/zones.cfg');
+    my $controller_cfg = PVE::Cluster::cfs_read_file('sdn/controllers.cfg');
+    return if !$vnet_cfg && !$transport_cfg && !$controller_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;
+       }
+    }
+
+    #generate configuration
+    my $config = {};
+
+    foreach my $id (keys %{$controller_cfg->{ids}}) {
+       my $plugin_config = $controller_cfg->{ids}->{$id};
+       my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
+       $plugin->generate_controller_config($plugin_config, $plugin_config, $id, $uplinks, $config);
+    }
+
+    foreach my $id (keys %{$transport_cfg->{ids}}) {
+       my $plugin_config = $transport_cfg->{ids}->{$id};
+       my $controllerid = $plugin_config->{controller};
+       next if !$controllerid;
+       my $controller = $transport_cfg->{ids}->{$controllerid};
+       if ($controller) {
+           my $controller_plugin = PVE::Network::SDN::Controllers::Plugin->lookup($controller->{type});
+           $controller_plugin->generate_controller_transport_config($plugin_config, $controller, $id, $uplinks, $config);
+       }
+    }
+
+    foreach my $id (keys %{$vnet_cfg->{ids}}) {
+       my $plugin_config = $vnet_cfg->{ids}->{$id};
+       my $transportid = $plugin_config->{transportzone};
+       next if !$transportid;
+       my $transport = $transport_cfg->{ids}->{$transportid};
+       next if !$transport;
+       my $controllerid = $transport->{controller};
+       next if !$controllerid;
+       my $controller = $controller_cfg->{ids}->{$controllerid};
+       if ($controller) {
+           my $controller_plugin = PVE::Network::SDN::Controllers::Plugin->lookup($controller->{type});
+           $controller_plugin->generate_controller_vnet_config($plugin_config, $controller, $transportid, $id, $config);
+       }
+    }
+
+    return $config;
+}
+
+
+sub reload_controller {
+
+    my $controller_cfg = PVE::Cluster::cfs_read_file('sdn/controllers.cfg');
+    return if !$controller_cfg;
+
+    foreach my $id (keys %{$controller_cfg->{ids}}) {
+       my $plugin_config = $controller_cfg->{ids}->{$id};
+       my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
+       $plugin->reload_controller();
+    }
+}
+
+sub write_controller_config {
+    my ($config) = @_;
+
+    my $controller_cfg = PVE::Cluster::cfs_read_file('sdn/controllers.cfg');
+    return if !$controller_cfg;
+
+    foreach my $id (keys %{$controller_cfg->{ids}}) {
+       my $plugin_config = $controller_cfg->{ids}->{$id};
+       my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
+       $plugin->write_controller_config($plugin_config, $config);
+    }
+}
+
+1;
+
diff --git a/PVE/Network/SDN/Controllers/FaucetPlugin.pm b/PVE/Network/SDN/Controllers/FaucetPlugin.pm
new file mode 100644 (file)
index 0000000..38f9abf
--- /dev/null
@@ -0,0 +1,107 @@
+package PVE::Network::SDN::Controllers::FaucetPlugin;
+
+use strict;
+use warnings;
+use PVE::Network::SDN::Controllers::Plugin;
+use PVE::Tools;
+use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option);
+use CPAN::Meta::YAML;
+use Encode;
+
+use base('PVE::Network::SDN::Controllers::Plugin');
+
+sub type {
+    return 'faucet';
+}
+
+sub properties {
+    return {
+    };
+}
+
+# Plugin implementation
+sub generate_controller_config {
+    my ($class, $plugin_config, $router, $id, $uplinks, $config) = @_;
+
+}
+
+sub generate_controller_transport_config {
+    my ($class, $plugin_config, $router, $id, $uplinks, $config) = @_;
+
+    my $dpid = $plugin_config->{'dp-id'};
+    my $dphex = printf("%x",$dpid);
+
+    my $transport_config = {
+                               dp_id => $dphex,
+                               hardware => "Open vSwitch",
+                          };
+
+    $config->{faucet}->{dps}->{$id} = $transport_config;
+
+}
+
+
+sub generate_controller_vnet_config {
+    my ($class, $plugin_config, $controller, $transportid, $vnetid, $config) = @_;
+
+    my $mac = $plugin_config->{mac};
+    my $ipv4 = $plugin_config->{ipv4};
+    my $ipv6 = $plugin_config->{ipv6};
+    my $tag = $plugin_config->{tag};
+    my $alias = $plugin_config->{alias};
+
+    my @ips = ();
+    push @ips, $ipv4 if $ipv4;
+    push @ips, $ipv6 if $ipv6;
+
+    my $vlan_config = { vid => $tag };
+
+    $vlan_config->{description} = $alias if $alias;
+    $vlan_config->{faucet_mac} = $mac if $mac;
+    $vlan_config->{faucet_vips} = \@ips if scalar @ips > 0;
+
+    $config->{faucet}->{vlans}->{$vnetid} = $vlan_config;
+
+    push(@{$config->{faucet}->{routers}->{$transportid}->{vlans}} , $vnetid);
+
+}
+
+sub on_delete_hook {
+    my ($class, $routerid, $sdn_cfg) = @_;
+
+}
+
+sub on_update_hook {
+    my ($class, $routerid, $sdn_cfg) = @_;
+
+}
+
+sub write_controller_config {
+    my ($class, $plugin_config, $config) = @_;
+
+    my $rawconfig = encode('UTF-8', CPAN::Meta::YAML::Dump($config->{faucet}));
+
+    return if !$rawconfig;
+    return if !-d "/etc/faucet";
+
+    my $frr_config_file = "/etc/faucet/faucet.yaml";
+
+    my $writefh = IO::File->new($frr_config_file,">");
+    print $writefh $rawconfig;
+    $writefh->close();
+}
+
+sub reload_controller {
+    my ($class) = @_;
+
+    my $conf_file = "/etc/faucet/faucet.yaml";
+    my $bin_path = "/usr/bin/faucet";
+
+    if (-e $conf_file && -e $bin_path) {
+        PVE::Tools::run_command(['systemctl', 'reload', 'faucet']);
+    }
+}
+
+1;
+
diff --git a/PVE/Network/SDN/Controllers/FrrEvpnPlugin.pm b/PVE/Network/SDN/Controllers/FrrEvpnPlugin.pm
new file mode 100644 (file)
index 0000000..052c77e
--- /dev/null
@@ -0,0 +1,312 @@
+package PVE::Network::SDN::Controllers::FrrEvpnPlugin;
+
+use strict;
+use warnings;
+use PVE::Network::SDN::Controllers::Plugin;
+use PVE::Tools;
+use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option);
+
+use base('PVE::Network::SDN::Controllers::Plugin');
+
+sub type {
+    return 'frrevpn';
+}
+
+sub properties {
+    return {
+       'uplink-id' => {
+           type => 'integer',
+           minimum => 1, maximum => 4096,
+           description => 'Uplink interface',
+       },
+        'asn' => {
+            type => 'integer',
+            description => "autonomous system number",
+        },
+        'peers' => {
+            description => "peers address list.",
+            type => 'string', format => 'ip-list'
+        },
+       'gateway-nodes' => get_standard_option('pve-node-list'),
+        'gateway-external-peers' => {
+            description => "upstream bgp peers address list.",
+            type => 'string', format => 'ip-list'
+        },
+    };
+}
+
+sub options {
+
+    return {
+       'uplink-id' => { optional => 0 },
+        'asn' => { optional => 0 },
+        'peers' => { optional => 0 },
+       'gateway-nodes' => { optional => 1 },
+       'gateway-external-peers' => { optional => 1 },
+    };
+}
+
+# Plugin implementation
+sub generate_controller_config {
+    my ($class, $plugin_config, $router, $id, $uplinks, $config) = @_;
+
+    my @peers = split(',', $plugin_config->{'peers'}) if $plugin_config->{'peers'};
+
+    my $asn = $plugin_config->{asn};
+    my $uplink = $plugin_config->{'uplink-id'};
+    my $gatewaynodes = $plugin_config->{'gateway-nodes'};
+    my @gatewaypeers = split(',', $plugin_config->{'gateway-external-peers'}) if $plugin_config->{'gateway-external-peers'};
+
+    return if !$asn;
+
+    my $iface = "uplink$uplink";
+    my $ifaceip = "";
+
+    if($uplinks->{$uplink}->{name}) {
+       $iface = $uplinks->{$uplink}->{name};
+        $ifaceip = PVE::Network::SDN::Controllers::Plugin::get_first_local_ipv4_from_interface($iface);
+    }
+
+    my $is_gateway = undef;
+    my $local_node = PVE::INotify::nodename();
+
+    foreach my $gatewaynode (PVE::Tools::split_list($gatewaynodes)) {
+        $is_gateway = 1 if $gatewaynode eq $local_node;
+    }
+
+    my @router_config = ();
+
+    push @router_config, "bgp router-id $ifaceip";
+    push @router_config, "no bgp default ipv4-unicast";
+    push @router_config, "coalesce-time 1000";
+
+    foreach my $address (@peers) {
+       next if $address eq $ifaceip;
+       push @router_config, "neighbor $address remote-as $asn";
+    }
+
+    if ($is_gateway) {
+       foreach my $address (@gatewaypeers) {
+           push @router_config, "neighbor $address remote-as external";
+       }
+    }
+    push(@{$config->{frr}->{router}->{"bgp $asn"}->{""}}, @router_config);
+
+    @router_config = ();
+    foreach my $address (@peers) {
+       next if $address eq $ifaceip;
+       push @router_config, "neighbor $address activate";
+    }
+    push @router_config, "advertise-all-vni";
+    push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"l2vpn evpn"}}, @router_config);
+
+    if ($is_gateway) {
+
+        @router_config = ();
+        #import /32 routes of evpn network from vrf1 to default vrf (for packet return)
+        #frr 7.1 tag is bugged -> works fine with 7.1 stable branch(20190829-02-g6ba76bbc1)
+        #https://github.com/FRRouting/frr/issues/4905
+       foreach my $address (@gatewaypeers) {
+           push @router_config, "neighbor $address activate";
+       }
+        push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv4 unicast"}}, @router_config);
+        push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv6 unicast"}}, @router_config);
+
+    }
+
+    return $config;
+}
+
+sub generate_controller_transport_config {
+    my ($class, $plugin_config, $router, $id, $uplinks, $config) = @_;
+
+    my $vrf = $plugin_config->{'vrf'};
+    my $vrfvxlan = $plugin_config->{'vrf-vxlan'};
+    my $asn = $router->{asn};
+    my $gatewaynodes = $router->{'gateway-nodes'};
+
+    return if !$vrf || !$vrfvxlan || !$asn;
+
+    #vrf
+    my @router_config = ();
+    push @router_config, "vni $vrfvxlan";
+    push(@{$config->{frr}->{vrf}->{"$vrf"}}, @router_config);
+
+    @router_config = ();
+
+    my $is_gateway = undef;
+    my $local_node = PVE::INotify::nodename();
+
+    foreach my $gatewaynode (PVE::Tools::split_list($gatewaynodes)) {
+       $is_gateway = 1 if $gatewaynode eq $local_node;
+    }
+
+    if ($is_gateway) {
+
+       @router_config = ();
+       #import /32 routes of evpn network from vrf1 to default vrf (for packet return)
+       #frr 7.1 tag is bugged -> works fine with 7.1 stable branch(20190829-02-g6ba76bbc1)
+       #https://github.com/FRRouting/frr/issues/4905
+       push @router_config, "import vrf $vrf";
+       push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv4 unicast"}}, @router_config);
+       push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv6 unicast"}}, @router_config);
+
+       @router_config = ();
+       #redistribute connected to be able to route to local vms on the gateway
+       push @router_config, "redistribute connected";
+       push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv4 unicast"}}, @router_config);
+       push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv6 unicast"}}, @router_config);
+
+       @router_config = ();
+       #add default originate to announce 0.0.0.0/0 type5 route in evpn
+       push @router_config, "default-originate ipv4";
+       push @router_config, "default-originate ipv6";
+       push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @router_config);
+    }
+
+    return $config;
+}
+
+sub on_delete_hook {
+    my ($class, $routerid, $sdn_cfg) = @_;
+
+    # verify that transport is associated to this router
+    foreach my $id (keys %{$sdn_cfg->{ids}}) {
+        my $sdn = $sdn_cfg->{ids}->{$id};
+        die "router $routerid is used by $id"
+            if (defined($sdn->{router}) && $sdn->{router} eq $routerid);
+    }
+}
+
+sub on_update_hook {
+    my ($class, $routerid, $sdn_cfg) = @_;
+
+    # verify that asn is not already used by another router
+    my $asn = $sdn_cfg->{ids}->{$routerid}->{asn};
+    foreach my $id (keys %{$sdn_cfg->{ids}}) {
+       next if $id eq $routerid;
+        my $sdn = $sdn_cfg->{ids}->{$id};
+        die "asn $asn is already used by $id"
+            if (defined($sdn->{asn}) && $sdn->{asn} eq $asn);
+    }
+}
+
+sub sort_frr_config {
+    my $order = {};
+    $order->{''} = 0;
+    $order->{'vrf'} = 1;
+    $order->{'ipv4 unicast'} = 1;
+    $order->{'ipv6 unicast'} = 2;
+    $order->{'l2vpn evpn'} = 3;
+
+    my $a_val = 100;
+    my $b_val = 100;
+
+    $a_val = $order->{$a} if defined($order->{$a});
+    $b_val = $order->{$b} if defined($order->{$b});
+
+    if($a =~ /bgp (\d+)$/) {
+       $a_val = 2;
+    }
+
+    if($b =~ /bgp (\d+)$/) {
+       $b_val = 2;
+    }
+
+    return $a_val <=> $b_val;
+}
+
+sub generate_frr_recurse{
+   my ($final_config, $content, $parentkey, $level) = @_;
+
+   my $keylist = {};
+   $keylist->{vrf} = 1;
+   $keylist->{'address-family'} = 1;
+   $keylist->{router} = 1;
+
+   my $exitkeylist = {};
+   $exitkeylist->{vrf} = 1;
+   $exitkeylist->{'address-family'} = 1;
+
+   #fix me, make this generic
+   my $paddinglevel = undef;
+   if($level == 1 || $level == 2) {
+     $paddinglevel = $level - 1;
+   } elsif ($level == 3 || $level ==  4) {
+     $paddinglevel = $level - 2;
+   }
+
+   my $padding = "";
+   $padding = ' ' x ($paddinglevel) if $paddinglevel;
+
+   if (ref $content eq ref {}) {
+       foreach my $key (sort sort_frr_config keys %$content) {
+           if ($parentkey && defined($keylist->{$parentkey})) {
+                   push @{$final_config}, $padding."!";
+                   push @{$final_config}, $padding."$parentkey $key";
+           } else {
+                   push @{$final_config}, $padding."$key" if $key ne '' && !defined($keylist->{$key});
+           }
+
+           my $option = $content->{$key};
+           generate_frr_recurse($final_config, $option, $key, $level+1);
+
+           push @{$final_config}, $padding."exit-$parentkey" if $parentkey && defined($exitkeylist->{$parentkey});
+       }
+    }
+
+    if (ref $content eq 'ARRAY') {
+       foreach my $value (@$content) {
+           push @{$final_config}, $padding."$value";
+       }
+    }
+}
+
+sub write_controller_config {
+    my ($class, $plugin_config, $config) = @_;
+
+    my $final_config = [];
+    push @{$final_config}, "log syslog informational";
+    push @{$final_config}, "!";
+
+    generate_frr_recurse($final_config, $config->{frr}, undef, 0);
+
+    push @{$final_config}, "!";
+    push @{$final_config}, "line vty";
+    push @{$final_config}, "!";
+
+    my $rawconfig = join("\n", @{$final_config});
+
+
+    return if !$rawconfig;
+    return if !-d "/etc/frr";
+
+    my $frr_config_file = "/etc/frr/frr.conf";
+
+    my $writefh = IO::File->new($frr_config_file,">");
+    print $writefh $rawconfig;
+    $writefh->close();
+}
+
+sub reload_controller {
+    my ($class) = @_;
+
+    my $conf_file = "/etc/frr/frr.conf";
+    my $bin_path = "/usr/bin/vtysh";
+
+    my $err = sub {
+       my $line = shift;
+       if ($line =~ /^line (\S+)/) {
+           print "$line \n";
+       }
+    };
+
+    if (-e $conf_file && -e $bin_path) {
+       PVE::Tools::run_command([$bin_path, '-m', '-f', $conf_file], outfunc => {}, errfunc => $err);
+    }
+}
+
+1;
+
+
diff --git a/PVE/Network/SDN/Controllers/Makefile b/PVE/Network/SDN/Controllers/Makefile
new file mode 100644 (file)
index 0000000..73c3b7b
--- /dev/null
@@ -0,0 +1,8 @@
+SOURCES=Plugin.pm FaucetPlugin.pm FrrEvpnPlugin.pm
+
+
+PERL5DIR=${DESTDIR}/usr/share/perl5
+
+.PHONY: install
+install:
+       for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Controllers/$$i; done
diff --git a/PVE/Network/SDN/Controllers/Plugin.pm b/PVE/Network/SDN/Controllers/Plugin.pm
new file mode 100644 (file)
index 0000000..df385f1
--- /dev/null
@@ -0,0 +1,133 @@
+package PVE::Network::SDN::Controllers::Plugin;
+
+use strict;
+use warnings;
+
+use PVE::Tools;
+use PVE::JSONSchema;
+use PVE::Cluster;
+
+use Data::Dumper;
+use PVE::JSONSchema qw(get_standard_option);
+use base qw(PVE::SectionConfig);
+
+PVE::Cluster::cfs_register_file('sdn/controllers.cfg',
+                                sub { __PACKAGE__->parse_config(@_); });
+
+PVE::Cluster::cfs_register_file('sdn/controllers.cfg.new',
+                                sub { __PACKAGE__->parse_config(@_); },
+                                sub { __PACKAGE__->write_config(@_); });
+
+PVE::JSONSchema::register_standard_option('pve-sdn-controller-id', {
+    description => "The SDN controller object identifier.",
+    type => 'string', format => 'pve-sdn-controller-id',
+});
+
+PVE::JSONSchema::register_format('pve-sdn-controller-id', \&parse_sdn_controller_id);
+sub parse_sdn_controller_id {
+    my ($id, $noerr) = @_;
+
+    if ($id !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) {
+        return undef if $noerr;
+        die "SDN controller object ID '$id' contains illegal characters\n";
+    }
+    return $id;
+}
+
+my $defaultData = {
+
+    propertyList => {
+       type => {
+           description => "Plugin type.",
+           type => 'string', format => 'pve-configid',
+           type => 'string',
+       },
+        controller => get_standard_option('pve-sdn-controller-id',
+            { completion => \&PVE::Network::SDN::complete_sdn_controller }),
+    },
+};
+
+sub private {
+    return $defaultData;
+}
+
+sub parse_section_header {
+    my ($class, $line) = @_;
+
+    if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
+        my ($type, $id) = (lc($1), $2);
+       my $errmsg = undef; # set if you want to skip whole section
+       eval { PVE::JSONSchema::pve_verify_configid($type); };
+       $errmsg = $@ if $@;
+       my $config = {}; # to return additional attributes
+       return ($type, $id, $errmsg, $config);
+    }
+    return undef;
+}
+
+sub generate_sdn_config {
+    my ($class, $plugin_config, $node, $data, $ctime) = @_;
+
+    die "please implement inside plugin";
+}
+
+sub generate_controller_config {
+    my ($class, $plugin_config, $router, $id, $uplinks, $config) = @_;
+
+    die "please implement inside plugin";
+}
+
+sub generate_controller_vnet_config {
+    my ($class, $plugin_config, $controller, $transportid, $vnetid, $config) = @_;
+
+}
+
+sub write_controller_config {
+    my ($class, $plugin_config, $config) = @_;
+
+    die "please implement inside plugin";
+}
+
+sub controller_reload {
+    my ($class) = @_;
+
+    die "please implement inside plugin";
+}
+
+sub on_delete_hook {
+    my ($class, $sndid, $scfg) = @_;
+
+    # do nothing by default
+}
+
+sub on_update_hook {
+    my ($class, $sdnid, $scfg) = @_;
+
+    # do nothing by default
+}
+
+#helpers
+
+#to be move to Network.pm helper
+sub get_first_local_ipv4_from_interface {
+    my ($interface) = @_;
+
+    my $cmd = ['/sbin/ip', 'address', 'show', 'dev', $interface];
+
+    my $IP = "";
+
+    my $code = sub {
+       my $line = shift;
+
+       if ($line =~ m!^\s*inet\s+($PVE::Tools::IPRE)(?:/\d+|\s+peer\s+)!) {
+           $IP = $1;
+           return;
+       }
+    };
+
+    PVE::Tools::run_command($cmd, outfunc => $code);
+
+    return $IP;
+}
+
+1;
diff --git a/PVE/Network/SDN/EvpnControllerPlugin.pm b/PVE/Network/SDN/EvpnControllerPlugin.pm
deleted file mode 100644 (file)
index b2c9345..0000000
+++ /dev/null
@@ -1,313 +0,0 @@
-package PVE::Network::SDN::EvpnControllerPlugin;
-
-use strict;
-use warnings;
-use PVE::Network::SDN::Plugin;
-use PVE::Tools;
-use PVE::INotify;
-use PVE::JSONSchema qw(get_standard_option);
-
-use base('PVE::Network::SDN::Plugin');
-
-sub type {
-    return 'evpncontroller';
-}
-
-sub plugindata {
-    return {
-        role => 'controller',
-    };
-}
-
-sub properties {
-    return {
-        'asn' => {
-            type => 'integer',
-            description => "autonomous system number",
-        },
-        'peers' => {
-            description => "peers address list.",
-            type => 'string', format => 'ip-list'
-        },
-       'gateway-nodes' => get_standard_option('pve-node-list'),
-        'gateway-external-peers' => {
-            description => "upstream bgp peers address list.",
-            type => 'string', format => 'ip-list'
-        },
-    };
-}
-
-sub options {
-
-    return {
-       'uplink-id' => { optional => 0 },
-        'asn' => { optional => 0 },
-        'peers' => { optional => 0 },
-       'gateway-nodes' => { optional => 1 },
-       'gateway-external-peers' => { optional => 1 },
-    };
-}
-
-# Plugin implementation
-sub generate_controller_config {
-    my ($class, $plugin_config, $router, $id, $uplinks, $config) = @_;
-
-    my @peers = split(',', $plugin_config->{'peers'}) if $plugin_config->{'peers'};
-
-    my $asn = $plugin_config->{asn};
-    my $uplink = $plugin_config->{'uplink-id'};
-    my $gatewaynodes = $plugin_config->{'gateway-nodes'};
-    my @gatewaypeers = split(',', $plugin_config->{'gateway-external-peers'}) if $plugin_config->{'gateway-external-peers'};
-
-    return if !$asn;
-
-    my $iface = "uplink$uplink";
-    my $ifaceip = "";
-
-    if($uplinks->{$uplink}->{name}) {
-       $iface = $uplinks->{$uplink}->{name};
-        $ifaceip = PVE::Network::SDN::Plugin::get_first_local_ipv4_from_interface($iface);
-    }
-
-    my $is_gateway = undef;
-    my $local_node = PVE::INotify::nodename();
-
-    foreach my $gatewaynode (PVE::Tools::split_list($gatewaynodes)) {
-        $is_gateway = 1 if $gatewaynode eq $local_node;
-    }
-
-    my @router_config = ();
-
-    push @router_config, "bgp router-id $ifaceip";
-    push @router_config, "no bgp default ipv4-unicast";
-    push @router_config, "coalesce-time 1000";
-
-    foreach my $address (@peers) {
-       next if $address eq $ifaceip;
-       push @router_config, "neighbor $address remote-as $asn";
-    }
-
-    if ($is_gateway) {
-       foreach my $address (@gatewaypeers) {
-           push @router_config, "neighbor $address remote-as external";
-       }
-    }
-    push(@{$config->{frr}->{router}->{"bgp $asn"}->{""}}, @router_config);
-
-    @router_config = ();
-    foreach my $address (@peers) {
-       next if $address eq $ifaceip;
-       push @router_config, "neighbor $address activate";
-    }
-    push @router_config, "advertise-all-vni";
-    push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"l2vpn evpn"}}, @router_config);
-
-    if ($is_gateway) {
-
-        @router_config = ();
-        #import /32 routes of evpn network from vrf1 to default vrf (for packet return)
-        #frr 7.1 tag is bugged -> works fine with 7.1 stable branch(20190829-02-g6ba76bbc1)
-        #https://github.com/FRRouting/frr/issues/4905
-       foreach my $address (@gatewaypeers) {
-           push @router_config, "neighbor $address activate";
-       }
-        push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv4 unicast"}}, @router_config);
-        push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv6 unicast"}}, @router_config);
-
-    }
-
-    return $config;
-}
-
-sub generate_controller_transport_config {
-    my ($class, $plugin_config, $router, $id, $uplinks, $config) = @_;
-
-    my $vrf = $plugin_config->{'vrf'};
-    my $vrfvxlan = $plugin_config->{'vrf-vxlan'};
-    my $asn = $router->{asn};
-    my $gatewaynodes = $router->{'gateway-nodes'};
-
-    return if !$vrf || !$vrfvxlan || !$asn;
-
-    #vrf
-    my @router_config = ();
-    push @router_config, "vni $vrfvxlan";
-    push(@{$config->{frr}->{vrf}->{"$vrf"}}, @router_config);
-
-    @router_config = ();
-
-    my $is_gateway = undef;
-    my $local_node = PVE::INotify::nodename();
-
-    foreach my $gatewaynode (PVE::Tools::split_list($gatewaynodes)) {
-       $is_gateway = 1 if $gatewaynode eq $local_node;
-    }
-
-    if ($is_gateway) {
-
-       @router_config = ();
-       #import /32 routes of evpn network from vrf1 to default vrf (for packet return)
-       #frr 7.1 tag is bugged -> works fine with 7.1 stable branch(20190829-02-g6ba76bbc1)
-       #https://github.com/FRRouting/frr/issues/4905
-       push @router_config, "import vrf $vrf";
-       push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv4 unicast"}}, @router_config);
-       push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv6 unicast"}}, @router_config);
-
-       @router_config = ();
-       #redistribute connected to be able to route to local vms on the gateway
-       push @router_config, "redistribute connected";
-       push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv4 unicast"}}, @router_config);
-       push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv6 unicast"}}, @router_config);
-
-       @router_config = ();
-       #add default originate to announce 0.0.0.0/0 type5 route in evpn
-       push @router_config, "default-originate ipv4";
-       push @router_config, "default-originate ipv6";
-       push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @router_config);
-    }
-
-    return $config;
-}
-
-sub on_delete_hook {
-    my ($class, $routerid, $sdn_cfg) = @_;
-
-    # verify that transport is associated to this router
-    foreach my $id (keys %{$sdn_cfg->{ids}}) {
-        my $sdn = $sdn_cfg->{ids}->{$id};
-        die "router $routerid is used by $id"
-            if (defined($sdn->{router}) && $sdn->{router} eq $routerid);
-    }
-}
-
-sub on_update_hook {
-    my ($class, $routerid, $sdn_cfg) = @_;
-
-    # verify that asn is not already used by another router
-    my $asn = $sdn_cfg->{ids}->{$routerid}->{asn};
-    foreach my $id (keys %{$sdn_cfg->{ids}}) {
-       next if $id eq $routerid;
-        my $sdn = $sdn_cfg->{ids}->{$id};
-        die "asn $asn is already used by $id"
-            if (defined($sdn->{asn}) && $sdn->{asn} eq $asn);
-    }
-}
-
-sub sort_frr_config {
-    my $order = {};
-    $order->{''} = 0;
-    $order->{'vrf'} = 1;
-    $order->{'ipv4 unicast'} = 1;
-    $order->{'ipv6 unicast'} = 2;
-    $order->{'l2vpn evpn'} = 3;
-
-    my $a_val = 100;
-    my $b_val = 100;
-
-    $a_val = $order->{$a} if defined($order->{$a});
-    $b_val = $order->{$b} if defined($order->{$b});
-
-    if($a =~ /bgp (\d+)$/) {
-       $a_val = 2;
-    }
-
-    if($b =~ /bgp (\d+)$/) {
-       $b_val = 2;
-    }
-
-    return $a_val <=> $b_val;
-}
-
-sub generate_frr_recurse{
-   my ($final_config, $content, $parentkey, $level) = @_;
-
-   my $keylist = {};
-   $keylist->{vrf} = 1;
-   $keylist->{'address-family'} = 1;
-   $keylist->{router} = 1;
-
-   my $exitkeylist = {};
-   $exitkeylist->{vrf} = 1;
-   $exitkeylist->{'address-family'} = 1;
-
-   #fix me, make this generic
-   my $paddinglevel = undef;
-   if($level == 1 || $level == 2) {
-     $paddinglevel = $level - 1;
-   } elsif ($level == 3 || $level ==  4) {
-     $paddinglevel = $level - 2;
-   }
-
-   my $padding = "";
-   $padding = ' ' x ($paddinglevel) if $paddinglevel;
-
-   if (ref $content eq ref {}) {
-       foreach my $key (sort sort_frr_config keys %$content) {
-           if ($parentkey && defined($keylist->{$parentkey})) {
-                   push @{$final_config}, $padding."!";
-                   push @{$final_config}, $padding."$parentkey $key";
-           } else {
-                   push @{$final_config}, $padding."$key" if $key ne '' && !defined($keylist->{$key});
-           }
-
-           my $option = $content->{$key};
-           generate_frr_recurse($final_config, $option, $key, $level+1);
-
-           push @{$final_config}, $padding."exit-$parentkey" if $parentkey && defined($exitkeylist->{$parentkey});
-       }
-    }
-
-    if (ref $content eq 'ARRAY') {
-       foreach my $value (@$content) {
-           push @{$final_config}, $padding."$value";
-       }
-    }
-}
-
-sub write_controller_config {
-    my ($class, $plugin_config, $config) = @_;
-
-    my $final_config = [];
-    push @{$final_config}, "log syslog informational";
-    push @{$final_config}, "!";
-
-    generate_frr_recurse($final_config, $config->{frr}, undef, 0);
-
-    push @{$final_config}, "!";
-    push @{$final_config}, "line vty";
-    push @{$final_config}, "!";
-
-    my $rawconfig = join("\n", @{$final_config});
-
-
-    return if !$rawconfig;
-    return if !-d "/etc/frr";
-
-    my $frr_config_file = "/etc/frr/frr.conf";
-
-    my $writefh = IO::File->new($frr_config_file,">");
-    print $writefh $rawconfig;
-    $writefh->close();
-}
-
-sub reload_controller {
-    my ($class) = @_;
-
-    my $conf_file = "/etc/frr/frr.conf";
-    my $bin_path = "/usr/bin/vtysh";
-
-    my $err = sub {
-       my $line = shift;
-       if ($line =~ /^line (\S+)/) {
-           print "$line \n";
-       }
-    };
-
-    if (-e $conf_file && -e $bin_path) {
-       PVE::Tools::run_command([$bin_path, '-m', '-f', $conf_file], outfunc => {}, errfunc => $err);
-    }
-}
-
-1;
-
-
diff --git a/PVE/Network/SDN/EvpnPlugin.pm b/PVE/Network/SDN/EvpnPlugin.pm
deleted file mode 100644 (file)
index f570f2f..0000000
+++ /dev/null
@@ -1,200 +0,0 @@
-package PVE::Network::SDN::EvpnPlugin;
-
-use strict;
-use warnings;
-use PVE::Network::SDN::Plugin;
-use PVE::Tools qw($IPV4RE);
-use PVE::INotify;
-
-use base('PVE::Network::SDN::VxlanPlugin');
-
-sub type {
-    return 'evpn';
-}
-
-sub plugindata {
-    return {
-        role => 'transport',
-    };
-}
-
-sub properties {
-    return {
-       'vrf' => {
-           description => "vrf name.",
-           type => 'string',  #fixme: format
-       },
-       'vrf-vxlan' => {
-           type => 'integer',
-           description => "l3vni.",
-       },
-       'controller' => {
-           type => 'string',
-           description => "Frr router name",
-       },
-    };
-}
-
-sub options {
-
-    return {
-       'uplink-id' => { optional => 0 },
-        'vxlan-allowed' => { optional => 1 },
-        'vrf' => { optional => 0 },
-        'vrf-vxlan' => { optional => 0 },
-        'controller' => { optional => 0 },
-    };
-}
-
-# Plugin implementation
-sub generate_sdn_config {
-    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $config) = @_;
-
-    my $tag = $vnet->{tag};
-    my $alias = $vnet->{alias};
-    my $ipv4 = $vnet->{ipv4};
-    my $ipv6 = $vnet->{ipv6};
-    my $mac = $vnet->{mac};
-
-    my $uplink = $plugin_config->{'uplink-id'};
-    my $vxlanallowed = $plugin_config->{'vxlan-allowed'};
-    my $vrf = $plugin_config->{'vrf'};
-    my $vrfvxlan = $plugin_config->{'vrf-vxlan'};
-
-    die "missing vxlan tag" if !$tag;
-    my $iface = "uplink$uplink";
-    my $ifaceip = "";
-
-    if($uplinks->{$uplink}->{name}) {
-       $iface = $uplinks->{$uplink}->{name};
-       $ifaceip = PVE::Network::SDN::Plugin::get_first_local_ipv4_from_interface($iface);
-    }
-
-    my $mtu = 1450;
-    $mtu = $uplinks->{$uplink}->{mtu} - 50 if $uplinks->{$uplink}->{mtu};
-    $mtu = $vnet->{mtu} if $vnet->{mtu};
-
-    #vxlan interface
-    my @iface_config = ();
-    push @iface_config, "vxlan-id $tag";
-
-    push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip;
-    push @iface_config, "bridge-learning off";
-    push @iface_config, "bridge-arp-nd-suppress on";
-
-    push @iface_config, "mtu $mtu" if $mtu;
-    push(@{$config->{"vxlan$vnetid"}}, @iface_config) if !$config->{"vxlan$vnetid"};
-
-    #vnet bridge
-    @iface_config = ();
-    push @iface_config, "address $ipv4" if $ipv4;
-    push @iface_config, "address $ipv6" if $ipv6;
-    push @iface_config, "hwaddress $mac" if $mac;
-    push @iface_config, "bridge_ports vxlan$vnetid";
-    push @iface_config, "bridge_stp off";
-    push @iface_config, "bridge_fd 0";
-    push @iface_config, "mtu $mtu" if $mtu;
-    push @iface_config, "alias $alias" if $alias;
-    push @iface_config, "ip-forward on" if $ipv4;
-    push @iface_config, "ip6-forward on" if $ipv6;
-    push @iface_config, "arp-accept on" if $ipv4||$ipv6;
-    push @iface_config, "vrf $vrf" if $vrf;
-    push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
-
-    if ($vrf) {
-       #vrf interface
-       @iface_config = ();
-       push @iface_config, "vrf-table auto";
-       push(@{$config->{$vrf}}, @iface_config) if !$config->{$vrf};
-
-       if ($vrfvxlan) {
-           #l3vni vxlan interface
-           my $iface_vxlan = "vxlan$vrf";
-           @iface_config = ();
-           push @iface_config, "vxlan-id $vrfvxlan";
-           push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip;
-           push @iface_config, "bridge-learning off";
-           push @iface_config, "bridge-arp-nd-suppress on";
-           push @iface_config, "mtu $mtu" if $mtu;
-           push(@{$config->{$iface_vxlan}}, @iface_config) if !$config->{$iface_vxlan};
-
-           #l3vni bridge
-           my $brvrf = "br$vrf";
-           @iface_config = ();
-           push @iface_config, "bridge-ports $iface_vxlan";
-           push @iface_config, "bridge_stp off";
-           push @iface_config, "bridge_fd 0";
-           push @iface_config, "mtu $mtu" if $mtu;
-           push @iface_config, "vrf $vrf";
-           push(@{$config->{$brvrf}}, @iface_config) if !$config->{$brvrf};
-       }
-    }
-
-    return $config;
-}
-
-sub on_update_hook {
-    my ($class, $transportid, $sdn_cfg) = @_;
-
-    my $transport = $sdn_cfg->{ids}->{$transportid};
-
-    # verify that vxlan-allowed don't conflict with another vxlan-allowed transport
-
-    # verify that vxlan-allowed is matching currently vnet tag in this transport
-    my $vxlanallowed = $transport->{'vxlan-allowed'};
-    if ($vxlanallowed) {
-       foreach my $id (keys %{$sdn_cfg->{ids}}) {
-           my $sdn = $sdn_cfg->{ids}->{$id};
-           if ($sdn->{type} eq 'vnet' && defined($sdn->{tag})) {
-               if(defined($sdn->{transportzone}) && $sdn->{transportzone} eq $transportid) {
-                   my $tag = $sdn->{tag};
-                   eval {
-                       PVE::Network::SDN::Plugin::parse_tag_number_or_range($vxlanallowed, '16777216', $tag);
-                   };
-                   if($@) {
-                       die "vnet $id - vlan $tag is not allowed in transport $transportid";
-                   }
-               }
-           }
-       }
-    }
-
-    # verify that router exist
-    if (defined($sdn_cfg->{ids}->{$transportid}->{router})) {
-       my $router = $sdn_cfg->{ids}->{$transportid}->{router};
-       if (!defined($sdn_cfg->{ids}->{$router})) {
-           die "router $router don't exist";
-       } else {
-           die "$router is not a router type" if $sdn_cfg->{ids}->{$router}->{type} ne 'frr';
-       }
-
-       #vrf && vrf-vxlan need to be defined with router
-       my $vrf = $sdn_cfg->{ids}->{$transportid}->{vrf};
-       if (!defined($vrf)) {
-           die "missing vrf option";
-       } else {
-           # verify that vrf is not already declared in another transport
-           foreach my $id (keys %{$sdn_cfg->{ids}}) {
-               next if $id eq $transportid;
-               die "vrf $vrf is already declared in $id"
-                       if (defined($sdn_cfg->{ids}->{$id}->{vrf}) && $sdn_cfg->{ids}->{$id}->{vrf} eq $vrf);
-           }
-       }
-
-       my $vrfvxlan = $sdn_cfg->{ids}->{$transportid}->{'vrf-vxlan'};
-       if (!defined($vrfvxlan)) {
-           die "missing vrf-vxlan option";
-       } else {
-           # verify that vrf-vxlan is not already declared in another transport
-           foreach my $id (keys %{$sdn_cfg->{ids}}) {
-               next if $id eq $transportid;
-               die "vrf-vxlan $vrfvxlan is already declared in $id"
-                       if (defined($sdn_cfg->{ids}->{$id}->{'vrf-vxlan'}) && $sdn_cfg->{ids}->{$id}->{'vrf-vxlan'} eq $vrfvxlan);
-           }
-       }
-    }
-}
-
-1;
-
-
diff --git a/PVE/Network/SDN/FaucetControllerPlugin.pm b/PVE/Network/SDN/FaucetControllerPlugin.pm
deleted file mode 100644 (file)
index ee15bdf..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-package PVE::Network::SDN::FaucetControllerPlugin;
-
-use strict;
-use warnings;
-use PVE::Network::SDN::Plugin;
-use PVE::Tools;
-use PVE::INotify;
-use PVE::JSONSchema qw(get_standard_option);
-use CPAN::Meta::YAML;
-use Encode;
-
-use base('PVE::Network::SDN::Plugin');
-
-sub type {
-    return 'faucetcontroller';
-}
-
-sub plugindata {
-    return {
-        role => 'controller',
-    };
-}
-
-sub properties {
-    return {
-    };
-}
-
-# Plugin implementation
-sub generate_controller_config {
-    my ($class, $plugin_config, $router, $id, $uplinks, $config) = @_;
-
-}
-
-sub generate_controller_transport_config {
-    my ($class, $plugin_config, $router, $id, $uplinks, $config) = @_;
-
-    my $dpid = $plugin_config->{'dp-id'};
-    my $dphex = printf("%x",$dpid);
-
-    my $transport_config = {
-                               dp_id => $dphex,
-                               hardware => "Open vSwitch",
-                          };
-
-    $config->{faucet}->{dps}->{$id} = $transport_config;
-
-}
-
-
-sub generate_controller_vnet_config {
-    my ($class, $plugin_config, $controller, $transportid, $vnetid, $config) = @_;
-
-    my $mac = $plugin_config->{mac};
-    my $ipv4 = $plugin_config->{ipv4};
-    my $ipv6 = $plugin_config->{ipv6};
-    my $tag = $plugin_config->{tag};
-    my $alias = $plugin_config->{alias};
-
-    my @ips = ();
-    push @ips, $ipv4 if $ipv4;
-    push @ips, $ipv6 if $ipv6;
-
-    my $vlan_config = { vid => $tag };
-
-    $vlan_config->{description} = $alias if $alias;
-    $vlan_config->{faucet_mac} = $mac if $mac;
-    $vlan_config->{faucet_vips} = \@ips if scalar @ips > 0;
-
-    $config->{faucet}->{vlans}->{$vnetid} = $vlan_config;
-
-    push(@{$config->{faucet}->{routers}->{$transportid}->{vlans}} , $vnetid);
-
-}
-
-sub on_delete_hook {
-    my ($class, $routerid, $sdn_cfg) = @_;
-
-}
-
-sub on_update_hook {
-    my ($class, $routerid, $sdn_cfg) = @_;
-
-}
-
-sub write_controller_config {
-    my ($class, $plugin_config, $config) = @_;
-
-    my $rawconfig = encode('UTF-8', CPAN::Meta::YAML::Dump($config->{faucet}));
-
-    return if !$rawconfig;
-    return if !-d "/etc/faucet";
-
-    my $frr_config_file = "/etc/faucet/faucet.yaml";
-
-    my $writefh = IO::File->new($frr_config_file,">");
-    print $writefh $rawconfig;
-    $writefh->close();
-}
-
-sub reload_controller {
-    my ($class) = @_;
-
-    my $conf_file = "/etc/faucet/faucet.yaml";
-    my $bin_path = "/usr/bin/faucet";
-
-    if (-e $conf_file && -e $bin_path) {
-        PVE::Tools::run_command(['systemctl', 'reload', 'faucet']);
-    }
-}
-
-1;
-
diff --git a/PVE/Network/SDN/FaucetPlugin.pm b/PVE/Network/SDN/FaucetPlugin.pm
deleted file mode 100644 (file)
index 9422ee7..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-package PVE::Network::SDN::FaucetPlugin;
-
-use strict;
-use warnings;
-use PVE::Network::SDN::VlanPlugin;
-
-use base('PVE::Network::SDN::VlanPlugin');
-
-sub type {
-    return 'faucet';
-}
-
-sub plugindata {
-    return {
-       role => 'transport',
-    };
-}
-
-sub properties {
-    return {
-        'dp-id' => {
-            type => 'integer',
-            description => 'Faucet dataplane id',
-        },
-    };
-}
-
-sub options {
-
-    return {
-       'dp-id' => { optional => 0 },
-       'uplink-id' => { optional => 0 },
-        'controller' => { optional => 0 },
-        'vlan-allowed' => { optional => 1 },
-    };
-}
-
-# Plugin implementation
-sub generate_sdn_config {
-    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $config) = @_;
-
-    my $mtu = $vnet->{mtu};
-    my $uplink = $plugin_config->{'uplink-id'};
-    my $dpid = $plugin_config->{'dp-id'};
-    my $dphex = printf("%x",$dpid);  #fixme :should be 16characters hex
-
-    my $iface = $uplinks->{$uplink}->{name};
-    $iface = "uplink${uplink}" if !$iface;
-
-    #tagged interface
-    my @iface_config = ();
-    push @iface_config, "ovs_type OVSPort";
-    push @iface_config, "ovs_bridge $zoneid";
-    push @iface_config, "ovs_mtu $mtu" if $mtu;
-    push(@{$config->{$iface}}, @iface_config) if !$config->{$iface};
-
-    #vnet bridge
-    @iface_config = ();
-    push @iface_config, "ovs_port $iface";
-    push @iface_config, "ovs_type OVSBridge";
-    push @iface_config, "ovs_mtu $mtu" if $mtu;
-
-    push @iface_config, "ovs_extra set bridge $zoneid other-config:datapath-id=$dphex";
-    push @iface_config, "ovs_extra set bridge $zoneid other-config:disable-in-band=true";
-    push @iface_config, "ovs_extra set bridge $zoneid fail_mode=secure";
-    push @iface_config, "ovs_extra set-controller $vnetid tcp:127.0.0.1:6653";
-
-    push(@{$config->{$zoneid}}, @iface_config) if !$config->{$zoneid};
-
-    return $config;
-}
-
-
-1;
-
-
index 232db52bc02ba6f5db09510cbe7488d85ab8b65a..7622255e1954dfc4966265698a1a256585b46332 100644 (file)
@@ -1,4 +1,4 @@
-SOURCES=Plugin.pm VnetPlugin.pm VlanPlugin.pm VxlanPlugin.pm FaucetControllerPlugin.pm FaucetPlugin.pm EvpnPlugin.pm EvpnControllerPlugin.pm QinQPlugin.pm
+SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm
 
 
 PERL5DIR=${DESTDIR}/usr/share/perl5
@@ -6,4 +6,6 @@ PERL5DIR=${DESTDIR}/usr/share/perl5
 .PHONY: install
 install:
        for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/$$i; done
+       make -C Controllers install
+       make -C Zones install
 
diff --git a/PVE/Network/SDN/Plugin.pm b/PVE/Network/SDN/Plugin.pm
deleted file mode 100644 (file)
index 0c6eaf0..0000000
+++ /dev/null
@@ -1,169 +0,0 @@
-package PVE::Network::SDN::Plugin;
-
-use strict;
-use warnings;
-
-use PVE::Tools;
-use PVE::JSONSchema;
-use PVE::Cluster;
-
-use Data::Dumper;
-use PVE::JSONSchema qw(get_standard_option);
-use base qw(PVE::SectionConfig);
-
-PVE::Cluster::cfs_register_file('sdn.cfg',
-                                sub { __PACKAGE__->parse_config(@_); });
-
-PVE::Cluster::cfs_register_file('sdn.cfg.new',
-                                sub { __PACKAGE__->parse_config(@_); },
-                                sub { __PACKAGE__->write_config(@_); });
-
-PVE::JSONSchema::register_standard_option('pve-sdn-id', {
-    description => "The SDN object identifier.",
-    type => 'string', format => 'pve-sdn-id',
-});
-
-PVE::JSONSchema::register_format('pve-sdn-id', \&parse_sdn_id);
-sub parse_sdn_id {
-    my ($sdnid, $noerr) = @_;
-
-    if ($sdnid !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) {
-        return undef if $noerr;
-        die "SDN object ID '$sdnid' contains illegal characters\n";
-    }
-    return $sdnid;
-}
-
-my $defaultData = {
-
-    propertyList => {
-       type => {
-           description => "Plugin type.",
-           type => 'string', format => 'pve-configid',
-           type => 'string',
-       },
-        sdn => get_standard_option('pve-sdn-id',
-            { completion => \&PVE::Network::SDN::complete_sdn }),
-    },
-};
-
-sub private {
-    return $defaultData;
-}
-
-sub parse_section_header {
-    my ($class, $line) = @_;
-
-    if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
-        my ($type, $sdnid) = (lc($1), $2);
-       my $errmsg = undef; # set if you want to skip whole section
-       eval { PVE::JSONSchema::pve_verify_configid($type); };
-       $errmsg = $@ if $@;
-       my $config = {}; # to return additional attributes
-       return ($type, $sdnid, $errmsg, $config);
-    }
-    return undef;
-}
-
-sub generate_sdn_config {
-    my ($class, $plugin_config, $node, $data, $ctime) = @_;
-
-    die "please implement inside plugin";
-}
-
-sub generate_controller_config {
-    my ($class, $plugin_config, $router, $id, $uplinks, $config) = @_;
-
-    die "please implement inside plugin";
-}
-
-sub generate_controller_vnet_config {
-    my ($class, $plugin_config, $controller, $transportid, $vnetid, $config) = @_;
-
-}
-
-sub write_controller_config {
-    my ($class, $plugin_config, $config) = @_;
-
-    die "please implement inside plugin";
-}
-
-sub controller_reload {
-    my ($class) = @_;
-
-    die "please implement inside plugin";
-}
-
-sub on_delete_hook {
-    my ($class, $sndid, $scfg) = @_;
-
-    # do nothing by default
-}
-
-sub on_update_hook {
-    my ($class, $sdnid, $scfg) = @_;
-
-    # do nothing by default
-}
-
-#helpers
-sub parse_tag_number_or_range {
-    my ($str, $max, $tag) = @_;
-
-    my @elements = split(/,/, $str);
-    my $count = 0;
-    my $allowed = undef;
-
-    die "extraneous commas in list\n" if $str ne join(',', @elements);
-    foreach my $item (@elements) {
-       if ($item =~ m/^([0-9]+)-([0-9]+)$/) {
-           $count += 2;
-           my ($port1, $port2) = ($1, $2);
-           die "invalid port '$port1'\n" if $port1 > $max;
-           die "invalid port '$port2'\n" if $port2 > $max;
-           die "backwards range '$port1:$port2' not allowed, did you mean '$port2:$port1'?\n" if $port1 > $port2;
-
-           if ($tag && $tag >= $port1 && $tag <= $port2){
-               $allowed = 1;
-               last;
-           }
-
-       } elsif ($item =~ m/^([0-9]+)$/) {
-           $count += 1;
-           my $port = $1;
-           die "invalid port '$port'\n" if $port > $max;
-
-           if ($tag && $tag == $port){
-               $allowed = 1;
-               last;
-           }
-       }
-    }
-    die "tag $tag is not allowed" if $tag && !$allowed;
-
-    return (scalar(@elements) > 1);
-}
-
-#to be move to Network.pm helper
-sub get_first_local_ipv4_from_interface {
-    my ($interface) = @_;
-
-    my $cmd = ['/sbin/ip', 'address', 'show', 'dev', $interface];
-
-    my $IP = "";
-
-    my $code = sub {
-       my $line = shift;
-
-       if ($line =~ m!^\s*inet\s+($PVE::Tools::IPRE)(?:/\d+|\s+peer\s+)!) {
-           $IP = $1;
-           return;
-       }
-    };
-
-    PVE::Tools::run_command($cmd, outfunc => $code);
-
-    return $IP;
-}
-
-1;
diff --git a/PVE/Network/SDN/QinQPlugin.pm b/PVE/Network/SDN/QinQPlugin.pm
deleted file mode 100644 (file)
index 9f40e84..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-package PVE::Network::SDN::QinQPlugin;
-
-use strict;
-use warnings;
-use PVE::Network::SDN::VlanPlugin;
-
-use base('PVE::Network::SDN::VlanPlugin');
-
-sub type {
-    return 'qinq';
-}
-
-sub plugindata {
-    return {
-       role => 'transport',
-    };
-}
-
-sub properties {
-    return {
-       'vlan-protocol' => {
-           type => 'string',
-            enum => ['802.1q', '802.1ad'],
-           default => '802.1q',
-           optional => 1,
-           description => "vlan protocol",
-       }
-    };
-}
-
-sub options {
-
-    return {
-       'uplink-id' => { optional => 0 },
-       'tag' => { optional => 0 },
-        'vlan-allowed' => { optional => 1 },
-       'vlan-protocol' => { optional => 1 },
-    };
-}
-
-# Plugin implementation
-sub generate_sdn_config {
-    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $config) = @_;
-
-    my $tag = $vnet->{tag};
-    my $transport_tag = $plugin_config->{tag};
-    my $mtu = $vnet->{mtu};
-    my $alias = $vnet->{alias};
-    my $vlanprotocol = $plugin_config->{'vlan-protocol'};
-    my $uplink = $plugin_config->{'uplink-id'};
-    my $vlanallowed = $plugin_config->{'vlan-allowed'};
-
-    die "missing vlan tag" if !$tag;
-    die "missing transport vlan tag" if !$transport_tag;
-
-    my $iface = $uplinks->{$uplink}->{name};
-    $iface = "uplink${uplink}" if !$iface;
-    $iface .= ".$transport_tag";
-
-    #tagged interface
-    my @iface_config = ();
-    push @iface_config, "vlan-protocol $vlanprotocol" if $vlanprotocol;
-    push @iface_config, "mtu $mtu" if $mtu;
-    push(@{$config->{$iface}}, @iface_config) if !$config->{$iface};
-
-    $iface .= ".$tag";
-    #vnet bridge
-    @iface_config = ();
-    push @iface_config, "bridge_ports $iface";
-    push @iface_config, "bridge_stp off";
-    push @iface_config, "bridge_fd 0";
-    push @iface_config, "mtu $mtu" if $mtu;
-    push @iface_config, "alias $alias" if $alias;
-    push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
-
-    return $config;
-}
-
-1;
-
-
diff --git a/PVE/Network/SDN/VlanPlugin.pm b/PVE/Network/SDN/VlanPlugin.pm
deleted file mode 100644 (file)
index 5a38f59..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-package PVE::Network::SDN::VlanPlugin;
-
-use strict;
-use warnings;
-use PVE::Network::SDN::Plugin;
-
-use base('PVE::Network::SDN::Plugin');
-
-sub type {
-    return 'vlan';
-}
-
-sub plugindata {
-    return {
-       role => 'transport',
-    };
-}
-
-PVE::JSONSchema::register_format('pve-sdn-vlanrange', \&pve_verify_sdn_vlanrange);
-sub pve_verify_sdn_vlanrange {
-   my ($vlanstr) = @_;
-
-   PVE::Network::SDN::Plugin::parse_tag_number_or_range($vlanstr, '4096');
-
-   return $vlanstr;
-}
-
-sub properties {
-    return {
-       'uplink-id' => {
-           type => 'integer',
-           minimum => 1, maximum => 4096,
-           description => 'Uplink interface',
-       },
-       'vlan-allowed' => {
-           type => 'string', format => 'pve-sdn-vlanrange',
-           description => "Allowed vlan range",
-       },
-    };
-}
-
-sub options {
-
-    return {
-       'uplink-id' => { optional => 0 },
-        'vlan-allowed' => { optional => 1 },
-    };
-}
-
-# Plugin implementation
-sub generate_sdn_config {
-    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $config) = @_;
-
-    my $tag = $vnet->{tag};
-    my $mtu = $vnet->{mtu};
-    my $alias = $vnet->{alias};
-    my $uplink = $plugin_config->{'uplink-id'};
-
-    die "missing vlan tag" if !$tag;
-
-    my $iface = $uplinks->{$uplink}->{name};
-    $iface = "uplink${uplink}" if !$iface;
-    $iface .= ".$tag";
-
-    #tagged interface
-    my @iface_config = ();
-    push @iface_config, "mtu $mtu" if $mtu;
-    push(@{$config->{$iface}}, @iface_config) if !$config->{$iface};
-
-    #vnet bridge
-    @iface_config = ();
-    push @iface_config, "bridge_ports $iface";
-    push @iface_config, "bridge_stp off";
-    push @iface_config, "bridge_fd 0";
-    push @iface_config, "mtu $mtu" if $mtu;
-    push @iface_config, "alias $alias" if $alias;
-    push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
-
-    return $config;
-}
-
-sub on_delete_hook {
-    my ($class, $transportid, $sdn_cfg) = @_;
-
-    # verify that no vnet are associated to this transport
-    foreach my $id (keys %{$sdn_cfg->{ids}}) {
-       my $sdn = $sdn_cfg->{ids}->{$id};
-       die "transport $transportid is used by vnet $id"
-           if ($sdn->{type} eq 'vnet' && defined($sdn->{transportzone}) && $sdn->{transportzone} eq $transportid);
-    }
-}
-
-sub on_update_hook {
-    my ($class, $transportid, $sdn_cfg) = @_;
-
-    my $transport = $sdn_cfg->{ids}->{$transportid};
-
-    # verify that vlan-allowed don't conflict with another vlan-allowed transport
-
-    # verify that vlan-allowed is matching currently vnet tag in this transport
-    my $vlanallowed = $transport->{'vlan-allowed'};
-    if ($vlanallowed) {
-       foreach my $id (keys %{$sdn_cfg->{ids}}) {
-           my $sdn = $sdn_cfg->{ids}->{$id};
-           if ($sdn->{type} eq 'vnet' && defined($sdn->{tag})) {
-               if(defined($sdn->{transportzone}) && $sdn->{transportzone} eq $transportid) {
-                   my $tag = $sdn->{tag};
-                   eval {
-                       PVE::Network::SDN::Plugin::parse_tag_number_or_range($vlanallowed, '4096', $tag);
-                   };
-                   if($@) {
-                       die "vlan $tag is not allowed in transport $transportid";
-                   }
-               }
-           }
-       }
-    }
-}
-
-1;
-
-
index e9185645116ceb1f1d8303e3f493ad8954292233..28a7b59f19caa0f5bb390ff6b37bcd4efa60b23d 100644 (file)
@@ -2,28 +2,60 @@ package PVE::Network::SDN::VnetPlugin;
 
 use strict;
 use warnings;
-use PVE::Network::SDN::Plugin;
 
-use base('PVE::Network::SDN::Plugin');
+use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use base qw(PVE::SectionConfig);
+use PVE::JSONSchema qw(get_standard_option);
 
-use PVE::Cluster;
+PVE::Cluster::cfs_register_file('sdn/vnets.cfg',
+                                 sub { __PACKAGE__->parse_config(@_); });
+
+PVE::Cluster::cfs_register_file('sdn/vnets.cfg.new',
+                                 sub { __PACKAGE__->parse_config(@_); },
+                                 sub { __PACKAGE__->write_config(@_); });
+
+PVE::JSONSchema::register_standard_option('pve-sdn-vnet-id', {
+    description => "The SDN vnet object identifier.",
+    type => 'string', format => 'pve-sdn-vnet-id',
+});
+
+PVE::JSONSchema::register_format('pve-sdn-vnet-id', \&parse_sdn_vnet_id);
+sub parse_sdn_vnet_id {
+    my ($id, $noerr) = @_;
+
+    if ($id !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) {
+        return undef if $noerr;
+        die "SDN object vnet ID '$id' contains illegal characters\n";
+    }
+    return $id;
+}
+
+my $defaultData = {
+
+    propertyList => {
+        vnet => get_standard_option('pve-sdn-vnet-id',
+            { completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnet }),
+    },
+};
 
 sub type {
     return 'vnet';
 }
 
-sub plugindata {
-    return {
-        role => 'vnet',
-    };
+sub private {
+    return $defaultData;
 }
 
 sub properties {
     return {
-       transportzone => {
+       zone => {
             type => 'string',
-            description => "transportzone id",
+            description => "zone id",
        },
+        type => {
+            description => "Type",
+            optional => 1,
+        },
        tag => {
             type => 'integer',
             description => "vlan or vxlan id",
@@ -58,7 +90,7 @@ sub properties {
 
 sub options {
     return {
-        transportzone => { optional => 0},
+        zone => { optional => 0},
         tag => { optional => 0},
         alias => { optional => 1 },
         ipv4 => { optional => 1 },
diff --git a/PVE/Network/SDN/Vnets.pm b/PVE/Network/SDN/Vnets.pm
new file mode 100644 (file)
index 0000000..95a74f5
--- /dev/null
@@ -0,0 +1,59 @@
+package PVE::Network::SDN::Vnets;
+
+use strict;
+use warnings;
+
+use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+
+
+use PVE::Network::SDN::VnetPlugin;
+PVE::Network::SDN::VnetPlugin->register();
+PVE::Network::SDN::VnetPlugin->init();
+
+sub sdn_vnets_config {
+    my ($cfg, $id, $noerr) = @_;
+
+    die "no sdn vnet ID specified\n" if !$id;
+
+    my $scfg = $cfg->{ids}->{$id};
+    die "sdn vnet '$id' does not exists\n" if (!$noerr && !$scfg);
+
+    return $scfg;
+}
+
+sub config {
+    my $config = cfs_read_file("sdn/vnets.cfg.new");
+    $config = cfs_read_file("sdn/vnets.cfg") if !keys %{$config->{ids}};
+    return $config;
+}
+
+sub write_config {
+    my ($cfg) = @_;
+
+    cfs_write_file("sdn/vnets.cfg.new", $cfg);
+}
+
+sub lock_sdn_vnets_config {
+    my ($code, $errmsg) = @_;
+
+    cfs_lock_file("sdn/vnets.cfg.new", undef, $code);
+    if (my $err = $@) {
+        $errmsg ? die "$errmsg: $err" : die $err;
+    }
+}
+
+sub sdn_vnets_ids {
+    my ($cfg) = @_;
+
+    return keys %{$cfg->{ids}};
+}
+
+sub complete_sdn_vnet {
+    my ($cmdname, $pname, $cvalue) = @_;
+
+    my $cfg = PVE::Network::SDN::Vnets::config();
+
+    return  $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Vnets::sdn_vnet_ids($cfg) ];
+}
+
+1;
diff --git a/PVE/Network/SDN/VxlanPlugin.pm b/PVE/Network/SDN/VxlanPlugin.pm
deleted file mode 100644 (file)
index 5a259b0..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-package PVE::Network::SDN::VxlanPlugin;
-
-use strict;
-use warnings;
-use PVE::Network::SDN::Plugin;
-use PVE::Tools qw($IPV4RE);
-use PVE::INotify;
-
-use base('PVE::Network::SDN::Plugin');
-
-PVE::JSONSchema::register_format('pve-sdn-vxlanrange', \&pve_verify_sdn_vxlanrange);
-sub pve_verify_sdn_vxlanrange {
-   my ($vxlanstr) = @_;
-
-   PVE::Network::SDN::Plugin::parse_tag_number_or_range($vxlanstr, '16777216');
-
-   return $vxlanstr;
-}
-
-PVE::JSONSchema::register_format('ipv4-multicast', \&parse_ipv4_multicast);
-sub parse_ipv4_multicast {
-    my ($ipv4, $noerr) = @_;
-
-    if ($ipv4 !~ m/^(?:$IPV4RE)$/) {
-        return undef if $noerr;
-        die "value does not look like a valid multicast IPv4 address\n";
-    }
-
-    if ($ipv4 =~ m/^(\d+)\.\d+.\d+.\d+/) {
-       if($1 < 224 || $1 > 239) {
-           return undef if $noerr;
-           die "value does not look like a valid multicast IPv4 address\n";
-       }
-    }
-
-    return $ipv4;
-}
-
-sub type {
-    return 'vxlan';
-}
-
-sub plugindata {
-    return {
-        role => 'transport',
-    };
-}
-
-sub properties {
-    return {
-        'vxlan-allowed' => {
-            type => 'string', format => 'pve-sdn-vxlanrange',
-            description => "Allowed vlan range",
-        },
-        'multicast-address' => {
-            description => "Multicast address.",
-            type => 'string', format => 'ipv4-multicast'
-        },
-       'unicast-address' => {
-           description => "Unicast peers address ip list.",
-           type => 'string',  format => 'ip-list'
-       },
-    };
-}
-
-sub options {
-
-    return {
-       'uplink-id' => { optional => 0 },
-        'multicast-address' => { optional => 1 },
-        'unicast-address' => { optional => 1 },
-        'vxlan-allowed' => { optional => 1 },
-    };
-}
-
-# Plugin implementation
-sub generate_sdn_config {
-    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $config) = @_;
-
-    my $tag = $vnet->{tag};
-    my $alias = $vnet->{alias};
-    my $ipv4 = $vnet->{ipv4};
-    my $ipv6 = $vnet->{ipv6};
-    my $mac = $vnet->{mac};
-    my $multicastaddress = $plugin_config->{'multicast-address'};
-    my @unicastaddress = split(',', $plugin_config->{'unicast-address'}) if $plugin_config->{'unicast-address'};
-
-    my $uplink = $plugin_config->{'uplink-id'};
-    my $vxlanallowed = $plugin_config->{'vxlan-allowed'};
-
-    die "missing vxlan tag" if !$tag;
-    my $iface = "uplink$uplink";
-    my $ifaceip = "";
-
-    if($uplinks->{$uplink}->{name}) {
-       $iface = $uplinks->{$uplink}->{name};
-       $ifaceip = PVE::Network::SDN::Plugin::get_first_local_ipv4_from_interface($iface);
-    }
-
-    my $mtu = 1450;
-    $mtu = $uplinks->{$uplink}->{mtu} - 50 if $uplinks->{$uplink}->{mtu};
-    $mtu = $vnet->{mtu} if $vnet->{mtu};
-
-    #vxlan interface
-    my @iface_config = ();
-    push @iface_config, "vxlan-id $tag";
-
-    if($multicastaddress) {
-       push @iface_config, "vxlan-svcnodeip $multicastaddress";
-       push @iface_config, "vxlan-physdev $iface";
-    } elsif (@unicastaddress) {
-
-       foreach my $address (@unicastaddress) {
-           next if $address eq $ifaceip;
-           push @iface_config, "vxlan_remoteip $address";
-       }
-    }
-
-    push @iface_config, "mtu $mtu" if $mtu;
-    push(@{$config->{"vxlan$vnetid"}}, @iface_config) if !$config->{"vxlan$vnetid"};
-
-    #vnet bridge
-    @iface_config = ();
-    push @iface_config, "address $ipv4" if $ipv4;
-    push @iface_config, "address $ipv6" if $ipv6;
-    push @iface_config, "hwaddress $mac" if $mac;
-    push @iface_config, "bridge_ports vxlan$vnetid";
-    push @iface_config, "bridge_stp off";
-    push @iface_config, "bridge_fd 0";
-    push @iface_config, "mtu $mtu" if $mtu;
-    push @iface_config, "alias $alias" if $alias;
-    push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
-
-    return $config;
-}
-
-sub on_delete_hook {
-    my ($class, $transportid, $sdn_cfg) = @_;
-
-    # verify that no vnet are associated to this transport
-    foreach my $id (keys %{$sdn_cfg->{ids}}) {
-       my $sdn = $sdn_cfg->{ids}->{$id};
-       die "transport $transportid is used by vnet $id"
-           if ($sdn->{type} eq 'vnet' && defined($sdn->{transportzone}) && $sdn->{transportzone} eq $transportid);
-    }
-}
-
-sub on_update_hook {
-    my ($class, $transportid, $sdn_cfg) = @_;
-
-    my $transport = $sdn_cfg->{ids}->{$transportid};
-
-    # verify that vxlan-allowed don't conflict with another vxlan-allowed transport
-
-    # verify that vxlan-allowed is matching currently vnet tag in this transport
-    my $vxlanallowed = $transport->{'vxlan-allowed'};
-    if ($vxlanallowed) {
-       foreach my $id (keys %{$sdn_cfg->{ids}}) {
-           my $sdn = $sdn_cfg->{ids}->{$id};
-           if ($sdn->{type} eq 'vnet' && defined($sdn->{tag})) {
-               if(defined($sdn->{transportzone}) && $sdn->{transportzone} eq $transportid) {
-                   my $tag = $sdn->{tag};
-                   eval {
-                       PVE::Network::SDN::Plugin::parse_tag_number_or_range($vxlanallowed, '16777216', $tag);
-                   };
-                   if($@) {
-                       die "vnet $id - vlan $tag is not allowed in transport $transportid";
-                   }
-               }
-           }
-       }
-    }
-
-    # verify that router exist
-    if (defined($sdn_cfg->{ids}->{$transportid}->{router})) {
-       my $router = $sdn_cfg->{ids}->{$transportid}->{router};
-       if (!defined($sdn_cfg->{ids}->{$router})) {
-           die "router $router don't exist";
-       } else {
-           die "$router is not a router type" if $sdn_cfg->{ids}->{$router}->{type} ne 'frr';
-       }
-
-       #vrf && vrf-vxlan need to be defined with router
-       my $vrf = $sdn_cfg->{ids}->{$transportid}->{vrf};
-       if (!defined($vrf)) {
-           die "missing vrf option";
-       } else {
-           # verify that vrf is not already declared in another transport
-           foreach my $id (keys %{$sdn_cfg->{ids}}) {
-               next if $id eq $transportid;
-               die "vrf $vrf is already declared in $id"
-                       if (defined($sdn_cfg->{ids}->{$id}->{vrf}) && $sdn_cfg->{ids}->{$id}->{vrf} eq $vrf);
-           }
-       }
-
-       my $vrfvxlan = $sdn_cfg->{ids}->{$transportid}->{'vrf-vxlan'};
-       if (!defined($vrfvxlan)) {
-           die "missing vrf-vxlan option";
-       } else {
-           # verify that vrf-vxlan is not already declared in another transport
-           foreach my $id (keys %{$sdn_cfg->{ids}}) {
-               next if $id eq $transportid;
-               die "vrf-vxlan $vrfvxlan is already declared in $id"
-                       if (defined($sdn_cfg->{ids}->{$id}->{'vrf-vxlan'}) && $sdn_cfg->{ids}->{$id}->{'vrf-vxlan'} eq $vrfvxlan);
-           }
-       }
-    }
-}
-
-1;
-
-
diff --git a/PVE/Network/SDN/Zones.pm b/PVE/Network/SDN/Zones.pm
new file mode 100644 (file)
index 0000000..6638712
--- /dev/null
@@ -0,0 +1,227 @@
+package PVE::Network::SDN::Zones;
+
+use strict;
+use warnings;
+
+use Data::Dumper;
+use JSON;
+
+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::Vnets;
+use PVE::Network::SDN::Zones::VlanPlugin;
+use PVE::Network::SDN::Zones::QinQPlugin;
+use PVE::Network::SDN::Zones::VxlanPlugin;
+use PVE::Network::SDN::Zones::EvpnPlugin;
+use PVE::Network::SDN::Zones::FaucetPlugin;
+use PVE::Network::SDN::Zones::Plugin;
+
+PVE::Network::SDN::Zones::VlanPlugin->register();
+PVE::Network::SDN::Zones::QinQPlugin->register();
+PVE::Network::SDN::Zones::VxlanPlugin->register();
+PVE::Network::SDN::Zones::EvpnPlugin->register();
+PVE::Network::SDN::Zones::FaucetPlugin->register();
+PVE::Network::SDN::Zones::Plugin->init();
+
+
+sub sdn_zones_config {
+    my ($cfg, $id, $noerr) = @_;
+
+    die "no sdn zone ID specified\n" if !$id;
+
+    my $scfg = $cfg->{ids}->{$id};
+    die "sdn '$id' does not exists\n" if (!$noerr && !$scfg);
+
+    return $scfg;
+}
+
+sub config {
+    my $config = cfs_read_file("sdn/zones.cfg.new");
+    $config = cfs_read_file("sdn/zones.cfg") if !keys %{$config->{ids}};
+    return $config;
+}
+
+sub write_config {
+    my ($cfg) = @_;
+
+    cfs_write_file("sdn/zones.cfg.new", $cfg);
+}
+
+sub lock_sdn_zones_config {
+    my ($code, $errmsg) = @_;
+
+    cfs_lock_file("sdn/zones.cfg.new", undef, $code);
+    if (my $err = $@) {
+        $errmsg ? die "$errmsg: $err" : die $err;
+    }
+}
+
+sub sdn_zones_ids {
+    my ($cfg) = @_;
+
+    return keys %{$cfg->{ids}};
+}
+
+sub complete_sdn_zone {
+    my ($cmdname, $pname, $cvalue) = @_;
+
+    my $cfg = PVE::Network::SDN::config();
+
+    return  $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_zones_ids($cfg) ];
+}
+
+
+sub generate_etc_network_config {
+
+    my $vnet_cfg = PVE::Cluster::cfs_read_file('sdn/vnets.cfg');
+    my $transport_cfg = PVE::Cluster::cfs_read_file('sdn/zones.cfg');
+    return if !$vnet_cfg && !$transport_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;
+       }
+    }
+
+    #generate configuration
+    my $config = {};
+    foreach my $id (keys %{$vnet_cfg->{ids}}) {
+       my $vnet = $vnet_cfg->{ids}->{$id};
+       my $zone = $vnet->{transportzone};
+
+       if(!$zone) {
+           warn "can't generate vnet $vnet : zone $zone don't exist";
+           next;
+       }
+
+       my $plugin_config = $transport_cfg->{ids}->{$zone};
+
+       if (!defined($plugin_config)) {
+           warn "can't generate vnet $vnet : zone $zone don't exist";
+           next;
+       }
+
+       my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
+       $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $uplinks, $config);
+    }
+
+    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";
+       }
+    }
+
+    return $raw_network_config;
+}
+
+sub write_etc_network_config {
+    my ($rawconfig) = @_;
+
+    return if !$rawconfig;
+    my $sdn_interfaces_file = "/etc/network/interfaces.d/sdn";
+
+    my $writefh = IO::File->new($sdn_interfaces_file,">");
+    print $writefh $rawconfig;
+    $writefh->close();
+}
+
+sub ifquery_check {
+
+    my $cmd = ['ifquery', '-a', '-c', '-o','json'];
+
+    my $result = '';
+    my $reader = sub { $result .= shift };
+
+    eval {
+       run_command($cmd, outfunc => $reader);
+    };
+
+    my $resultjson = decode_json($result);
+    my $interfaces = {};
+
+    foreach my $interface (@$resultjson) {
+       my $name = $interface->{name};
+       $interfaces->{$name} = {
+           status => $interface->{status},
+           config => $interface->{config},
+           config_status => $interface->{config_status},
+       };
+    }
+
+    return $interfaces;
+}
+
+# improve me : move status code inside plugins ?
+sub status {
+
+    my $cluster_vnet_file = "/etc/pve/sdn/vnets.cfg";
+    my $cluster_transport_file = "/etc/pve/sdn/zones.cfg";
+    my $local_sdn_file = "/etc/network/interfaces.d/sdn";
+    my $err_config = undef;
+
+    return if !-e $cluster_vnet_file && !-e $cluster_transport_file;
+
+    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_vnet_timestamp = (stat($cluster_vnet_file))[9];
+       my $cluster_transport_timestamp = (stat($cluster_transport_file))[9];
+       my $local_sdn_timestamp = (stat($local_sdn_file))[9];
+
+       if ($local_sdn_timestamp < $cluster_vnet_timestamp || $local_sdn_timestamp < $cluster_transport_timestamp) {
+           warn "local sdn network configuration is too old, please reload";
+           $err_config = 'pending';
+        }
+    }
+
+    my $status = ifquery_check();
+
+    my $vnet_cfg = PVE::Cluster::cfs_read_file('sdn/vnets.cfg');
+
+    my $vnet_status = {};
+    my $transport_status = {};
+
+    foreach my $id (keys %{$vnet_cfg->{ids}}) {
+       my $transportzone = $vnet_cfg->{ids}->{$id}->{transportzone};
+       $vnet_status->{$id}->{transportzone} = $transportzone;
+       $transport_status->{$transportzone}->{status} = 'available' if !defined($transport_status->{$transportzone}->{status});
+
+       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'};
+
+           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);
+}
+
+1;
+
diff --git a/PVE/Network/SDN/Zones/EvpnPlugin.pm b/PVE/Network/SDN/Zones/EvpnPlugin.pm
new file mode 100644 (file)
index 0000000..179ecc1
--- /dev/null
@@ -0,0 +1,200 @@
+package PVE::Network::SDN::Zones::EvpnPlugin;
+
+use strict;
+use warnings;
+use PVE::Network::SDN::Zones::VxlanPlugin;
+use PVE::Tools qw($IPV4RE);
+use PVE::INotify;
+
+use base('PVE::Network::SDN::Zones::VxlanPlugin');
+
+sub type {
+    return 'evpn';
+}
+
+sub plugindata {
+    return {
+        role => 'transport',
+    };
+}
+
+sub properties {
+    return {
+       'vrf' => {
+           description => "vrf name.",
+           type => 'string',  #fixme: format
+       },
+       'vrf-vxlan' => {
+           type => 'integer',
+           description => "l3vni.",
+       },
+       'controller' => {
+           type => 'string',
+           description => "Frr router name",
+       },
+    };
+}
+
+sub options {
+
+    return {
+       'uplink-id' => { optional => 0 },
+        'vxlan-allowed' => { optional => 1 },
+        'vrf' => { optional => 0 },
+        'vrf-vxlan' => { optional => 0 },
+        'controller' => { optional => 0 },
+    };
+}
+
+# Plugin implementation
+sub generate_sdn_config {
+    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $config) = @_;
+
+    my $tag = $vnet->{tag};
+    my $alias = $vnet->{alias};
+    my $ipv4 = $vnet->{ipv4};
+    my $ipv6 = $vnet->{ipv6};
+    my $mac = $vnet->{mac};
+
+    my $uplink = $plugin_config->{'uplink-id'};
+    my $vxlanallowed = $plugin_config->{'vxlan-allowed'};
+    my $vrf = $plugin_config->{'vrf'};
+    my $vrfvxlan = $plugin_config->{'vrf-vxlan'};
+
+    die "missing vxlan tag" if !$tag;
+    my $iface = "uplink$uplink";
+    my $ifaceip = "";
+
+    if($uplinks->{$uplink}->{name}) {
+       $iface = $uplinks->{$uplink}->{name};
+       $ifaceip = PVE::Network::SDN::Zones::Plugin::get_first_local_ipv4_from_interface($iface);
+    }
+
+    my $mtu = 1450;
+    $mtu = $uplinks->{$uplink}->{mtu} - 50 if $uplinks->{$uplink}->{mtu};
+    $mtu = $vnet->{mtu} if $vnet->{mtu};
+
+    #vxlan interface
+    my @iface_config = ();
+    push @iface_config, "vxlan-id $tag";
+
+    push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip;
+    push @iface_config, "bridge-learning off";
+    push @iface_config, "bridge-arp-nd-suppress on";
+
+    push @iface_config, "mtu $mtu" if $mtu;
+    push(@{$config->{"vxlan$vnetid"}}, @iface_config) if !$config->{"vxlan$vnetid"};
+
+    #vnet bridge
+    @iface_config = ();
+    push @iface_config, "address $ipv4" if $ipv4;
+    push @iface_config, "address $ipv6" if $ipv6;
+    push @iface_config, "hwaddress $mac" if $mac;
+    push @iface_config, "bridge_ports vxlan$vnetid";
+    push @iface_config, "bridge_stp off";
+    push @iface_config, "bridge_fd 0";
+    push @iface_config, "mtu $mtu" if $mtu;
+    push @iface_config, "alias $alias" if $alias;
+    push @iface_config, "ip-forward on" if $ipv4;
+    push @iface_config, "ip6-forward on" if $ipv6;
+    push @iface_config, "arp-accept on" if $ipv4||$ipv6;
+    push @iface_config, "vrf $vrf" if $vrf;
+    push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
+
+    if ($vrf) {
+       #vrf interface
+       @iface_config = ();
+       push @iface_config, "vrf-table auto";
+       push(@{$config->{$vrf}}, @iface_config) if !$config->{$vrf};
+
+       if ($vrfvxlan) {
+           #l3vni vxlan interface
+           my $iface_vxlan = "vxlan$vrf";
+           @iface_config = ();
+           push @iface_config, "vxlan-id $vrfvxlan";
+           push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip;
+           push @iface_config, "bridge-learning off";
+           push @iface_config, "bridge-arp-nd-suppress on";
+           push @iface_config, "mtu $mtu" if $mtu;
+           push(@{$config->{$iface_vxlan}}, @iface_config) if !$config->{$iface_vxlan};
+
+           #l3vni bridge
+           my $brvrf = "br$vrf";
+           @iface_config = ();
+           push @iface_config, "bridge-ports $iface_vxlan";
+           push @iface_config, "bridge_stp off";
+           push @iface_config, "bridge_fd 0";
+           push @iface_config, "mtu $mtu" if $mtu;
+           push @iface_config, "vrf $vrf";
+           push(@{$config->{$brvrf}}, @iface_config) if !$config->{$brvrf};
+       }
+    }
+
+    return $config;
+}
+
+sub on_update_hook {
+    my ($class, $transportid, $sdn_cfg) = @_;
+
+    my $transport = $sdn_cfg->{ids}->{$transportid};
+
+    # verify that vxlan-allowed don't conflict with another vxlan-allowed transport
+
+    # verify that vxlan-allowed is matching currently vnet tag in this transport
+    my $vxlanallowed = $transport->{'vxlan-allowed'};
+    if ($vxlanallowed) {
+       foreach my $id (keys %{$sdn_cfg->{ids}}) {
+           my $sdn = $sdn_cfg->{ids}->{$id};
+           if ($sdn->{type} eq 'vnet' && defined($sdn->{tag})) {
+               if(defined($sdn->{transportzone}) && $sdn->{transportzone} eq $transportid) {
+                   my $tag = $sdn->{tag};
+                   eval {
+                       PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vxlanallowed, '16777216', $tag);
+                   };
+                   if($@) {
+                       die "vnet $id - vlan $tag is not allowed in transport $transportid";
+                   }
+               }
+           }
+       }
+    }
+
+    # verify that router exist
+    if (defined($sdn_cfg->{ids}->{$transportid}->{router})) {
+       my $router = $sdn_cfg->{ids}->{$transportid}->{router};
+       if (!defined($sdn_cfg->{ids}->{$router})) {
+           die "router $router don't exist";
+       } else {
+           die "$router is not a router type" if $sdn_cfg->{ids}->{$router}->{type} ne 'frr';
+       }
+
+       #vrf && vrf-vxlan need to be defined with router
+       my $vrf = $sdn_cfg->{ids}->{$transportid}->{vrf};
+       if (!defined($vrf)) {
+           die "missing vrf option";
+       } else {
+           # verify that vrf is not already declared in another transport
+           foreach my $id (keys %{$sdn_cfg->{ids}}) {
+               next if $id eq $transportid;
+               die "vrf $vrf is already declared in $id"
+                       if (defined($sdn_cfg->{ids}->{$id}->{vrf}) && $sdn_cfg->{ids}->{$id}->{vrf} eq $vrf);
+           }
+       }
+
+       my $vrfvxlan = $sdn_cfg->{ids}->{$transportid}->{'vrf-vxlan'};
+       if (!defined($vrfvxlan)) {
+           die "missing vrf-vxlan option";
+       } else {
+           # verify that vrf-vxlan is not already declared in another transport
+           foreach my $id (keys %{$sdn_cfg->{ids}}) {
+               next if $id eq $transportid;
+               die "vrf-vxlan $vrfvxlan is already declared in $id"
+                       if (defined($sdn_cfg->{ids}->{$id}->{'vrf-vxlan'}) && $sdn_cfg->{ids}->{$id}->{'vrf-vxlan'} eq $vrfvxlan);
+           }
+       }
+    }
+}
+
+1;
+
+
diff --git a/PVE/Network/SDN/Zones/FaucetPlugin.pm b/PVE/Network/SDN/Zones/FaucetPlugin.pm
new file mode 100644 (file)
index 0000000..e914d4d
--- /dev/null
@@ -0,0 +1,76 @@
+package PVE::Network::SDN::Zones::FaucetPlugin;
+
+use strict;
+use warnings;
+use PVE::Network::SDN::Zones::VlanPlugin;
+
+use base('PVE::Network::SDN::Zones::VlanPlugin');
+
+sub type {
+    return 'faucet';
+}
+
+sub plugindata {
+    return {
+       role => 'transport',
+    };
+}
+
+sub properties {
+    return {
+        'dp-id' => {
+            type => 'integer',
+            description => 'Faucet dataplane id',
+        },
+    };
+}
+
+sub options {
+
+    return {
+       'dp-id' => { optional => 0 },
+       'uplink-id' => { optional => 0 },
+        'controller' => { optional => 0 },
+        'vlan-allowed' => { optional => 1 },
+    };
+}
+
+# Plugin implementation
+sub generate_sdn_config {
+    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $config) = @_;
+
+    my $mtu = $vnet->{mtu};
+    my $uplink = $plugin_config->{'uplink-id'};
+    my $dpid = $plugin_config->{'dp-id'};
+    my $dphex = printf("%x",$dpid);  #fixme :should be 16characters hex
+
+    my $iface = $uplinks->{$uplink}->{name};
+    $iface = "uplink${uplink}" if !$iface;
+
+    #tagged interface
+    my @iface_config = ();
+    push @iface_config, "ovs_type OVSPort";
+    push @iface_config, "ovs_bridge $zoneid";
+    push @iface_config, "ovs_mtu $mtu" if $mtu;
+    push(@{$config->{$iface}}, @iface_config) if !$config->{$iface};
+
+    #vnet bridge
+    @iface_config = ();
+    push @iface_config, "ovs_port $iface";
+    push @iface_config, "ovs_type OVSBridge";
+    push @iface_config, "ovs_mtu $mtu" if $mtu;
+
+    push @iface_config, "ovs_extra set bridge $zoneid other-config:datapath-id=$dphex";
+    push @iface_config, "ovs_extra set bridge $zoneid other-config:disable-in-band=true";
+    push @iface_config, "ovs_extra set bridge $zoneid fail_mode=secure";
+    push @iface_config, "ovs_extra set-controller $vnetid tcp:127.0.0.1:6653";
+
+    push(@{$config->{$zoneid}}, @iface_config) if !$config->{$zoneid};
+
+    return $config;
+}
+
+
+1;
+
+
diff --git a/PVE/Network/SDN/Zones/Makefile b/PVE/Network/SDN/Zones/Makefile
new file mode 100644 (file)
index 0000000..ba9a4b5
--- /dev/null
@@ -0,0 +1,8 @@
+SOURCES=Plugin.pm VlanPlugin.pm VxlanPlugin.pm FaucetPlugin.pm EvpnPlugin.pm QinQPlugin.pm
+
+
+PERL5DIR=${DESTDIR}/usr/share/perl5
+
+.PHONY: install
+install:
+       for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Zones/$$i; done
diff --git a/PVE/Network/SDN/Zones/Plugin.pm b/PVE/Network/SDN/Zones/Plugin.pm
new file mode 100644 (file)
index 0000000..7e820cd
--- /dev/null
@@ -0,0 +1,169 @@
+package PVE::Network::SDN::Zones::Plugin;
+
+use strict;
+use warnings;
+
+use PVE::Tools;
+use PVE::JSONSchema;
+use PVE::Cluster;
+
+use Data::Dumper;
+use PVE::JSONSchema qw(get_standard_option);
+use base qw(PVE::SectionConfig);
+
+PVE::Cluster::cfs_register_file('sdn/zones.cfg',
+                                sub { __PACKAGE__->parse_config(@_); });
+
+PVE::Cluster::cfs_register_file('sdn/zones.cfg.new',
+                                sub { __PACKAGE__->parse_config(@_); },
+                                sub { __PACKAGE__->write_config(@_); });
+
+PVE::JSONSchema::register_standard_option('pve-sdn-zone-id', {
+    description => "The SDN zone object identifier.",
+    type => 'string', format => 'pve-sdn-zone-id',
+});
+
+PVE::JSONSchema::register_format('pve-sdn-zone-id', \&parse_sdn_zone_id);
+sub parse_sdn_zone_id {
+    my ($id, $noerr) = @_;
+
+    if ($id !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) {
+        return undef if $noerr;
+        die "SDN zone object ID '$id' contains illegal characters\n";
+    }
+    return $id;
+}
+
+my $defaultData = {
+
+    propertyList => {
+       type => {
+           description => "Plugin type.",
+           type => 'string', format => 'pve-configid',
+           type => 'string',
+       },
+        zone => get_standard_option('pve-sdn-zone-id',
+            { completion => \&PVE::Network::SDN::Zones::complete_sdn_zone }),
+    },
+};
+
+sub private {
+    return $defaultData;
+}
+
+sub parse_section_header {
+    my ($class, $line) = @_;
+
+    if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
+        my ($type, $id) = (lc($1), $2);
+       my $errmsg = undef; # set if you want to skip whole section
+       eval { PVE::JSONSchema::pve_verify_configid($type); };
+       $errmsg = $@ if $@;
+       my $config = {}; # to return additional attributes
+       return ($type, $id, $errmsg, $config);
+    }
+    return undef;
+}
+
+sub generate_sdn_config {
+    my ($class, $plugin_config, $node, $data, $ctime) = @_;
+
+    die "please implement inside plugin";
+}
+
+sub generate_controller_config {
+    my ($class, $plugin_config, $router, $id, $uplinks, $config) = @_;
+
+    die "please implement inside plugin";
+}
+
+sub generate_controller_vnet_config {
+    my ($class, $plugin_config, $controller, $transportid, $vnetid, $config) = @_;
+
+}
+
+sub write_controller_config {
+    my ($class, $plugin_config, $config) = @_;
+
+    die "please implement inside plugin";
+}
+
+sub controller_reload {
+    my ($class) = @_;
+
+    die "please implement inside plugin";
+}
+
+sub on_delete_hook {
+    my ($class, $sndid, $scfg) = @_;
+
+    # do nothing by default
+}
+
+sub on_update_hook {
+    my ($class, $sdnid, $scfg) = @_;
+
+    # do nothing by default
+}
+
+#helpers
+sub parse_tag_number_or_range {
+    my ($str, $max, $tag) = @_;
+
+    my @elements = split(/,/, $str);
+    my $count = 0;
+    my $allowed = undef;
+
+    die "extraneous commas in list\n" if $str ne join(',', @elements);
+    foreach my $item (@elements) {
+       if ($item =~ m/^([0-9]+)-([0-9]+)$/) {
+           $count += 2;
+           my ($port1, $port2) = ($1, $2);
+           die "invalid port '$port1'\n" if $port1 > $max;
+           die "invalid port '$port2'\n" if $port2 > $max;
+           die "backwards range '$port1:$port2' not allowed, did you mean '$port2:$port1'?\n" if $port1 > $port2;
+
+           if ($tag && $tag >= $port1 && $tag <= $port2){
+               $allowed = 1;
+               last;
+           }
+
+       } elsif ($item =~ m/^([0-9]+)$/) {
+           $count += 1;
+           my $port = $1;
+           die "invalid port '$port'\n" if $port > $max;
+
+           if ($tag && $tag == $port){
+               $allowed = 1;
+               last;
+           }
+       }
+    }
+    die "tag $tag is not allowed" if $tag && !$allowed;
+
+    return (scalar(@elements) > 1);
+}
+
+#to be move to Network.pm helper
+sub get_first_local_ipv4_from_interface {
+    my ($interface) = @_;
+
+    my $cmd = ['/sbin/ip', 'address', 'show', 'dev', $interface];
+
+    my $IP = "";
+
+    my $code = sub {
+       my $line = shift;
+
+       if ($line =~ m!^\s*inet\s+($PVE::Tools::IPRE)(?:/\d+|\s+peer\s+)!) {
+           $IP = $1;
+           return;
+       }
+    };
+
+    PVE::Tools::run_command($cmd, outfunc => $code);
+
+    return $IP;
+}
+
+1;
diff --git a/PVE/Network/SDN/Zones/QinQPlugin.pm b/PVE/Network/SDN/Zones/QinQPlugin.pm
new file mode 100644 (file)
index 0000000..d90382c
--- /dev/null
@@ -0,0 +1,79 @@
+package PVE::Network::SDN::Zones::QinQPlugin;
+
+use strict;
+use warnings;
+use PVE::Network::SDN::Zones::VlanPlugin;
+
+use base('PVE::Network::SDN::Zones::VlanPlugin');
+
+sub type {
+    return 'qinq';
+}
+
+sub properties {
+    return {
+        tag => {
+            type => 'integer',
+            description => "vlan tag",
+        },
+       'vlan-protocol' => {
+           type => 'string',
+            enum => ['802.1q', '802.1ad'],
+           default => '802.1q',
+           optional => 1,
+           description => "vlan protocol",
+       }
+    };
+}
+
+sub options {
+
+    return {
+       'uplink-id' => { optional => 0 },
+       'tag' => { optional => 0 },
+        'vlan-allowed' => { optional => 1 },
+       'vlan-protocol' => { optional => 1 },
+    };
+}
+
+# Plugin implementation
+sub generate_sdn_config {
+    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $config) = @_;
+
+    my $tag = $vnet->{tag};
+    my $transport_tag = $plugin_config->{tag};
+    my $mtu = $vnet->{mtu};
+    my $alias = $vnet->{alias};
+    my $vlanprotocol = $plugin_config->{'vlan-protocol'};
+    my $uplink = $plugin_config->{'uplink-id'};
+    my $vlanallowed = $plugin_config->{'vlan-allowed'};
+
+    die "missing vlan tag" if !$tag;
+    die "missing transport vlan tag" if !$transport_tag;
+
+    my $iface = $uplinks->{$uplink}->{name};
+    $iface = "uplink${uplink}" if !$iface;
+    $iface .= ".$transport_tag";
+
+    #tagged interface
+    my @iface_config = ();
+    push @iface_config, "vlan-protocol $vlanprotocol" if $vlanprotocol;
+    push @iface_config, "mtu $mtu" if $mtu;
+    push(@{$config->{$iface}}, @iface_config) if !$config->{$iface};
+
+    $iface .= ".$tag";
+    #vnet bridge
+    @iface_config = ();
+    push @iface_config, "bridge_ports $iface";
+    push @iface_config, "bridge_stp off";
+    push @iface_config, "bridge_fd 0";
+    push @iface_config, "mtu $mtu" if $mtu;
+    push @iface_config, "alias $alias" if $alias;
+    push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
+
+    return $config;
+}
+
+1;
+
+
diff --git a/PVE/Network/SDN/Zones/VlanPlugin.pm b/PVE/Network/SDN/Zones/VlanPlugin.pm
new file mode 100644 (file)
index 0000000..ab46d32
--- /dev/null
@@ -0,0 +1,116 @@
+package PVE::Network::SDN::Zones::VlanPlugin;
+
+use strict;
+use warnings;
+use PVE::Network::SDN::Zones::Plugin;
+
+use base('PVE::Network::SDN::Zones::Plugin');
+
+sub type {
+    return 'vlan';
+}
+
+PVE::JSONSchema::register_format('pve-sdn-vlanrange', \&pve_verify_sdn_vlanrange);
+sub pve_verify_sdn_vlanrange {
+   my ($vlanstr) = @_;
+
+   PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vlanstr, '4096');
+
+   return $vlanstr;
+}
+
+sub properties {
+    return {
+       'uplink-id' => {
+           type => 'integer',
+           minimum => 1, maximum => 4096,
+           description => 'Uplink interface',
+       },
+       'vlan-allowed' => {
+           type => 'string', format => 'pve-sdn-vlanrange',
+           description => "Allowed vlan range",
+       },
+    };
+}
+
+sub options {
+
+    return {
+       'uplink-id' => { optional => 0 },
+        'vlan-allowed' => { optional => 1 },
+    };
+}
+
+# Plugin implementation
+sub generate_sdn_config {
+    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $config) = @_;
+
+    my $tag = $vnet->{tag};
+    my $mtu = $vnet->{mtu};
+    my $alias = $vnet->{alias};
+    my $uplink = $plugin_config->{'uplink-id'};
+
+    die "missing vlan tag" if !$tag;
+
+    my $iface = $uplinks->{$uplink}->{name};
+    $iface = "uplink${uplink}" if !$iface;
+    $iface .= ".$tag";
+
+    #tagged interface
+    my @iface_config = ();
+    push @iface_config, "mtu $mtu" if $mtu;
+    push(@{$config->{$iface}}, @iface_config) if !$config->{$iface};
+
+    #vnet bridge
+    @iface_config = ();
+    push @iface_config, "bridge_ports $iface";
+    push @iface_config, "bridge_stp off";
+    push @iface_config, "bridge_fd 0";
+    push @iface_config, "mtu $mtu" if $mtu;
+    push @iface_config, "alias $alias" if $alias;
+    push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
+
+    return $config;
+}
+
+sub on_delete_hook {
+    my ($class, $transportid, $sdn_cfg) = @_;
+
+    # verify that no vnet are associated to this transport
+    foreach my $id (keys %{$sdn_cfg->{ids}}) {
+       my $sdn = $sdn_cfg->{ids}->{$id};
+       die "transport $transportid is used by vnet $id"
+           if ($sdn->{type} eq 'vnet' && defined($sdn->{transportzone}) && $sdn->{transportzone} eq $transportid);
+    }
+}
+
+sub on_update_hook {
+    my ($class, $transportid, $sdn_cfg) = @_;
+
+    my $transport = $sdn_cfg->{ids}->{$transportid};
+
+    # verify that vlan-allowed don't conflict with another vlan-allowed transport
+
+    # verify that vlan-allowed is matching currently vnet tag in this transport
+    my $vlanallowed = $transport->{'vlan-allowed'};
+    if ($vlanallowed) {
+       foreach my $id (keys %{$sdn_cfg->{ids}}) {
+           my $sdn = $sdn_cfg->{ids}->{$id};
+           if ($sdn->{type} eq 'vnet' && defined($sdn->{tag})) {
+               if(defined($sdn->{transportzone}) && $sdn->{transportzone} eq $transportid) {
+                   my $tag = $sdn->{tag};
+                   eval {
+                       PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vlanallowed, '4096', $tag);
+                   };
+                   if($@) {
+                       die "vlan $tag is not allowed in transport $transportid";
+                   }
+               }
+           }
+       }
+    }
+}
+
+1;
+
+
diff --git a/PVE/Network/SDN/Zones/VxlanPlugin.pm b/PVE/Network/SDN/Zones/VxlanPlugin.pm
new file mode 100644 (file)
index 0000000..cccf93a
--- /dev/null
@@ -0,0 +1,206 @@
+package PVE::Network::SDN::Zones::VxlanPlugin;
+
+use strict;
+use warnings;
+use PVE::Network::SDN::Zones::Plugin;
+use PVE::Tools qw($IPV4RE);
+use PVE::INotify;
+
+use base('PVE::Network::SDN::Zones::Plugin');
+
+PVE::JSONSchema::register_format('pve-sdn-vxlanrange', \&pve_verify_sdn_vxlanrange);
+sub pve_verify_sdn_vxlanrange {
+   my ($vxlanstr) = @_;
+
+   PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vxlanstr, '16777216');
+
+   return $vxlanstr;
+}
+
+PVE::JSONSchema::register_format('ipv4-multicast', \&parse_ipv4_multicast);
+sub parse_ipv4_multicast {
+    my ($ipv4, $noerr) = @_;
+
+    if ($ipv4 !~ m/^(?:$IPV4RE)$/) {
+        return undef if $noerr;
+        die "value does not look like a valid multicast IPv4 address\n";
+    }
+
+    if ($ipv4 =~ m/^(\d+)\.\d+.\d+.\d+/) {
+       if($1 < 224 || $1 > 239) {
+           return undef if $noerr;
+           die "value does not look like a valid multicast IPv4 address\n";
+       }
+    }
+
+    return $ipv4;
+}
+
+sub type {
+    return 'vxlan';
+}
+
+sub properties {
+    return {
+        'vxlan-allowed' => {
+            type => 'string', format => 'pve-sdn-vxlanrange',
+            description => "Allowed vlan range",
+        },
+        'multicast-address' => {
+            description => "Multicast address.",
+            type => 'string', format => 'ipv4-multicast'
+        },
+       'unicast-address' => {
+           description => "Unicast peers address ip list.",
+           type => 'string',  format => 'ip-list'
+       },
+    };
+}
+
+sub options {
+
+    return {
+       'uplink-id' => { optional => 0 },
+        'multicast-address' => { optional => 1 },
+        'unicast-address' => { optional => 1 },
+        'vxlan-allowed' => { optional => 1 },
+    };
+}
+
+# Plugin implementation
+sub generate_sdn_config {
+    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $config) = @_;
+
+    my $tag = $vnet->{tag};
+    my $alias = $vnet->{alias};
+    my $ipv4 = $vnet->{ipv4};
+    my $ipv6 = $vnet->{ipv6};
+    my $mac = $vnet->{mac};
+    my $multicastaddress = $plugin_config->{'multicast-address'};
+    my @unicastaddress = split(',', $plugin_config->{'unicast-address'}) if $plugin_config->{'unicast-address'};
+
+    my $uplink = $plugin_config->{'uplink-id'};
+    my $vxlanallowed = $plugin_config->{'vxlan-allowed'};
+
+    die "missing vxlan tag" if !$tag;
+    my $iface = "uplink$uplink";
+    my $ifaceip = "";
+
+    if($uplinks->{$uplink}->{name}) {
+       $iface = $uplinks->{$uplink}->{name};
+       $ifaceip = PVE::Network::SDN::Zones::Plugin::get_first_local_ipv4_from_interface($iface);
+    }
+
+    my $mtu = 1450;
+    $mtu = $uplinks->{$uplink}->{mtu} - 50 if $uplinks->{$uplink}->{mtu};
+    $mtu = $vnet->{mtu} if $vnet->{mtu};
+
+    #vxlan interface
+    my @iface_config = ();
+    push @iface_config, "vxlan-id $tag";
+
+    if($multicastaddress) {
+       push @iface_config, "vxlan-svcnodeip $multicastaddress";
+       push @iface_config, "vxlan-physdev $iface";
+    } elsif (@unicastaddress) {
+
+       foreach my $address (@unicastaddress) {
+           next if $address eq $ifaceip;
+           push @iface_config, "vxlan_remoteip $address";
+       }
+    }
+
+    push @iface_config, "mtu $mtu" if $mtu;
+    push(@{$config->{"vxlan$vnetid"}}, @iface_config) if !$config->{"vxlan$vnetid"};
+
+    #vnet bridge
+    @iface_config = ();
+    push @iface_config, "address $ipv4" if $ipv4;
+    push @iface_config, "address $ipv6" if $ipv6;
+    push @iface_config, "hwaddress $mac" if $mac;
+    push @iface_config, "bridge_ports vxlan$vnetid";
+    push @iface_config, "bridge_stp off";
+    push @iface_config, "bridge_fd 0";
+    push @iface_config, "mtu $mtu" if $mtu;
+    push @iface_config, "alias $alias" if $alias;
+    push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
+
+    return $config;
+}
+
+sub on_delete_hook {
+    my ($class, $transportid, $sdn_cfg) = @_;
+
+    # verify that no vnet are associated to this transport
+    foreach my $id (keys %{$sdn_cfg->{ids}}) {
+       my $sdn = $sdn_cfg->{ids}->{$id};
+       die "transport $transportid is used by vnet $id"
+           if ($sdn->{type} eq 'vnet' && defined($sdn->{transportzone}) && $sdn->{transportzone} eq $transportid);
+    }
+}
+
+sub on_update_hook {
+    my ($class, $transportid, $sdn_cfg) = @_;
+
+    my $transport = $sdn_cfg->{ids}->{$transportid};
+
+    # verify that vxlan-allowed don't conflict with another vxlan-allowed transport
+
+    # verify that vxlan-allowed is matching currently vnet tag in this transport
+    my $vxlanallowed = $transport->{'vxlan-allowed'};
+    if ($vxlanallowed) {
+       foreach my $id (keys %{$sdn_cfg->{ids}}) {
+           my $sdn = $sdn_cfg->{ids}->{$id};
+           if ($sdn->{type} eq 'vnet' && defined($sdn->{tag})) {
+               if(defined($sdn->{transportzone}) && $sdn->{transportzone} eq $transportid) {
+                   my $tag = $sdn->{tag};
+                   eval {
+                       PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vxlanallowed, '16777216', $tag);
+                   };
+                   if($@) {
+                       die "vnet $id - vlan $tag is not allowed in transport $transportid";
+                   }
+               }
+           }
+       }
+    }
+
+    # verify that router exist
+    if (defined($sdn_cfg->{ids}->{$transportid}->{router})) {
+       my $router = $sdn_cfg->{ids}->{$transportid}->{router};
+       if (!defined($sdn_cfg->{ids}->{$router})) {
+           die "router $router don't exist";
+       } else {
+           die "$router is not a router type" if $sdn_cfg->{ids}->{$router}->{type} ne 'frr';
+       }
+
+       #vrf && vrf-vxlan need to be defined with router
+       my $vrf = $sdn_cfg->{ids}->{$transportid}->{vrf};
+       if (!defined($vrf)) {
+           die "missing vrf option";
+       } else {
+           # verify that vrf is not already declared in another transport
+           foreach my $id (keys %{$sdn_cfg->{ids}}) {
+               next if $id eq $transportid;
+               die "vrf $vrf is already declared in $id"
+                       if (defined($sdn_cfg->{ids}->{$id}->{vrf}) && $sdn_cfg->{ids}->{$id}->{vrf} eq $vrf);
+           }
+       }
+
+       my $vrfvxlan = $sdn_cfg->{ids}->{$transportid}->{'vrf-vxlan'};
+       if (!defined($vrfvxlan)) {
+           die "missing vrf-vxlan option";
+       } else {
+           # verify that vrf-vxlan is not already declared in another transport
+           foreach my $id (keys %{$sdn_cfg->{ids}}) {
+               next if $id eq $transportid;
+               die "vrf-vxlan $vrfvxlan is already declared in $id"
+                       if (defined($sdn_cfg->{ids}->{$id}->{'vrf-vxlan'}) && $sdn_cfg->{ids}->{$id}->{'vrf-vxlan'} eq $vrfvxlan);
+           }
+       }
+    }
+}
+
+1;
+
+
index da826727a229d0caaac7b73acf971e98bbb704c6..36880ba1c796733f46281cfafedde5445b9c9459 100644 (file)
@@ -3,20 +3,19 @@ use warnings;
 use File::Copy;
 use PVE::Cluster qw(cfs_read_file);
 
-use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
 use Data::Dumper;
 
-
-my $network_config = PVE::Network::SDN::generate_etc_network_config();
-PVE::Network::SDN::write_etc_network_config($network_config);
-print "/etc/network/interfaces\n";
+my $network_config = PVE::Network::SDN::Zones::generate_etc_network_config();
+PVE::Network::SDN::Zones::write_etc_network_config($network_config);
+print "/etc/network/interfaces.d/sdn\n";
 print $network_config;
 print "\n";
 
 
-my $controller_config = PVE::Network::SDN::generate_controller_config();
+my $controller_config = PVE::Network::SDN::Controllers::generate_controller_config();
 if ($controller_config) {
     print Dumper($controller_config);
-    PVE::Network::SDN::write_controller_config($controller_config);
-    print "/etc/frr/frr.conf\n";
+    PVE::Network::SDN::Controllers::write_controller_config($controller_config);
 }