]> git.proxmox.com Git - pve-network.git/blobdiff - PVE/Network/SDN/Zones.pm
zones : tap_plug: add support for disable bridge learning
[pve-network.git] / PVE / Network / SDN / Zones.pm
index 3f2e140a0a64b46c2ee09c1a0eeb53ad1e936c4b..32586e7f778268218e944a374285c8865ed3ed00 100644 (file)
@@ -3,7 +3,6 @@ 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);
@@ -16,6 +15,7 @@ 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::SimplePlugin;
 use PVE::Network::SDN::Zones::Plugin;
 
 PVE::Network::SDN::Zones::VlanPlugin->register();
@@ -23,8 +23,10 @@ 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::SimplePlugin->register();
 PVE::Network::SDN::Zones::Plugin->init();
 
+my $local_network_sdn_file = "/etc/network/interfaces.d/sdn";
 
 sub sdn_zones_config {
     my ($cfg, $id, $noerr) = @_;
@@ -38,46 +40,63 @@ sub sdn_zones_config {
 }
 
 sub config {
-    my $config = cfs_read_file("sdn/zones.cfg.new");
-    $config = cfs_read_file("sdn/zones.cfg") if !keys %{$config->{ids}};
+    my $config = cfs_read_file("sdn/zones.cfg");
     return $config;
 }
 
-sub write_config {
-    my ($cfg) = @_;
-
-    cfs_write_file("sdn/zones.cfg.new", $cfg);
+sub get_plugin_config {
+    my ($vnet) = @_;
+    my $zoneid = $vnet->{zone};
+    my $zone_cfg = PVE::Network::SDN::Zones::config();
+    return $zone_cfg->{ids}->{$zoneid};
 }
 
-sub lock_sdn_zones_config {
-    my ($code, $errmsg) = @_;
+sub write_config {
+    my ($cfg) = @_;
 
-    cfs_lock_file("sdn/zones.cfg.new", undef, $code);
-    if (my $err = $@) {
-        $errmsg ? die "$errmsg: $err" : die $err;
-    }
+    cfs_write_file("sdn/zones.cfg", $cfg);
 }
 
 sub sdn_zones_ids {
     my ($cfg) = @_;
 
-    return keys %{$cfg->{ids}};
+    return sort keys %{$cfg->{ids}};
 }
 
 sub complete_sdn_zone {
     my ($cmdname, $pname, $cvalue) = @_;
 
-    my $cfg = PVE::Network::SDN::config();
+    my $cfg = PVE::Network::SDN::running_config();
 
     return  $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_zones_ids($cfg) ];
 }
 
+sub get_zone {
+    my ($zoneid, $running) = @_;
+
+    my $cfg = {};
+    if($running) {
+        my $cfg = PVE::Network::SDN::running_config();
+        $cfg = $cfg->{vnets};
+    } else {
+        $cfg = PVE::Network::SDN::Zones::config();
+    }
+
+    my $zone = PVE::Network::SDN::Zones::sdn_zones_config($cfg, $zoneid, 1);
+
+    return $zone;
+}
+
 
 sub generate_etc_network_config {
 
-    my $vnet_cfg = PVE::Cluster::cfs_read_file('sdn/vnets.cfg');
-    my $zone_cfg = PVE::Cluster::cfs_read_file('sdn/zones.cfg');
-    my $controller_cfg = PVE::Cluster::cfs_read_file('sdn/controllers.cfg');
+    my $cfg = PVE::Network::SDN::running_config();
+
+    my $version = $cfg->{version};
+    my $vnet_cfg = $cfg->{vnets};
+    my $zone_cfg = $cfg->{zones};
+    my $subnet_cfg = $cfg->{subnets};
+    my $controller_cfg = $cfg->{controllers};
     return if !$vnet_cfg && !$zone_cfg;
 
     my $interfaces_config = PVE::INotify::read_file('interfaces');
@@ -86,36 +105,41 @@ sub generate_etc_network_config {
     my $config = {};
     my $nodename = PVE::INotify::nodename();
 
-    foreach my $id (keys %{$vnet_cfg->{ids}}) {
+    for my $id (sort keys %{$vnet_cfg->{ids}}) {
        my $vnet = $vnet_cfg->{ids}->{$id};
        my $zone = $vnet->{zone};
 
-       if(!$zone) {
-           warn "can't generate vnet $vnet : zone $zone don't exist";
+       if (!$zone) {
+           warn "can't generate vnet '$id': no zone assigned!\n";
            next;
        }
 
        my $plugin_config = $zone_cfg->{ids}->{$zone};
 
        if (!defined($plugin_config)) {
-           warn "can't generate vnet $vnet : zone $zone don't exist";
+           warn "can't generate vnet '$id': zone $zone don't exist\n";
            next;
        }
 
        next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename};
 
-       my $controller = undef;
-       if($plugin_config->{controller}) {
-           my $controllerid = $plugin_config->{controller};
-           $controller = $controller_cfg->{ids}->{$controllerid};
+       my $controller;
+       if (my $controllerid = $plugin_config->{controller}) {
+           $controller = $controller_cfg->{ids}->{$controllerid};
        }
 
        my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
-       $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $controller, $interfaces_config, $config);
+       eval {
+           $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config);
+       };
+       if (my $err = $@) {
+           warn "zone $zone : vnet $id : $err\n";
+           next;
+       }
     }
 
-    my $raw_network_config = "";
-    foreach my $iface (keys %$config) {
+    my $raw_network_config = "\#version:$version\n";
+    foreach my $iface (sort keys %$config) {
        $raw_network_config .= "\n";
        $raw_network_config .= "auto $iface\n";
        $raw_network_config .= "iface $iface\n";
@@ -131,13 +155,22 @@ 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,">");
+    my $writefh = IO::File->new($local_network_sdn_file,">");
     print $writefh $rawconfig;
     $writefh->close();
 }
 
+sub read_etc_network_config_version {
+    my $versionstr = PVE::Tools::file_read_firstline($local_network_sdn_file);
+
+    return if !defined($versionstr);
+
+    if ($versionstr =~ m/^\#version:(\d+)$/) {
+       return $1;
+    }
+}
+
 sub ifquery_check {
 
     my $cmd = ['ifquery', '-a', '-c', '-o','json'];
@@ -164,89 +197,94 @@ sub ifquery_check {
     return $interfaces;
 }
 
-# improve me : move status code inside plugins ?
+my $warned_about_reload;
+
 sub status {
 
-    my $cluster_vnet_file = "/etc/pve/sdn/vnets.cfg";
-    my $cluster_zone_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_zone_file;
+    my $local_version = PVE::Network::SDN::Zones::read_etc_network_config_version();
+    my $cfg = PVE::Network::SDN::running_config();
+    my $sdn_version = $cfg->{version};
 
-    if (!-e $local_sdn_file) {
+    return if !$sdn_version;
 
+    if (!$local_version) {
        $err_config = "local sdn network configuration is not yet generated, please reload";
-       warn "$err_config\n";
-    } else {
-       # fixme : use some kind of versioning info?
-       my $cluster_vnet_timestamp = (stat($cluster_vnet_file))[9];
-       my $cluster_zone_timestamp = (stat($cluster_zone_file))[9];
-       my $local_sdn_timestamp = (stat($local_sdn_file))[9];
-
-       if ($local_sdn_timestamp < $cluster_vnet_timestamp || $local_sdn_timestamp < $cluster_zone_timestamp) {
-           $err_config = "local sdn network configuration is too old, please reload";
+       if (!$warned_about_reload) {
+           $warned_about_reload = 1;
+           warn "$err_config\n";
+       }
+    } elsif ($local_version < $sdn_version) {
+       $err_config = "local sdn network configuration is too old, please reload";
+       if (!$warned_about_reload) {
+           $warned_about_reload = 1;
            warn "$err_config\n";
        }
+    } else {
+       $warned_about_reload = 0;
     }
 
     my $status = ifquery_check();
 
-    my $vnet_cfg = PVE::Cluster::cfs_read_file('sdn/vnets.cfg');
-    my $zone_cfg = PVE::Cluster::cfs_read_file('sdn/zones.cfg');
+    my $vnet_cfg = $cfg->{vnets};
+    my $zone_cfg = $cfg->{zones};
     my $nodename = PVE::INotify::nodename();
 
-
     my $vnet_status = {};
     my $zone_status = {};
 
-    foreach my $id (keys %{$vnet_cfg->{ids}}) {
-        my $vnet = $vnet_cfg->{ids}->{$id};
-        my $zone = $vnet->{zone};
+    for my $id (sort keys %{$zone_cfg->{ids}}) {
+       next if defined($zone_cfg->{ids}->{$id}->{nodes}) && !$zone_cfg->{ids}->{$id}->{nodes}->{$nodename};
+       $zone_status->{$id}->{status} = $err_config ? 'pending' : 'available';
+    }
 
-       next if !$zone;
-        my $plugin_config = $zone_cfg->{ids}->{$zone};
-        next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename};
+    foreach my $id (sort keys %{$vnet_cfg->{ids}}) {
+       my $vnet = $vnet_cfg->{ids}->{$id};
+       my $zone = $vnet->{zone};
+       next if !defined($zone);
 
-        my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
-        $plugin->status($plugin_config, $zone, $id, $vnet, $err_config, $status, $vnet_status, $zone_status);
-    }
+       my $plugin_config = $zone_cfg->{ids}->{$zone};
 
-    return($zone_status, $vnet_status);
-}
+       if (!defined($plugin_config)) {
+           $vnet_status->{$id}->{status} = 'error';
+           $vnet_status->{$id}->{statusmsg} = "unknown zone '$zone' configured";
+           next;
+       }
 
-sub get_bridge_vlan {
-    my ($vnetid) = @_;
+       next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename};
 
-    my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
+       $vnet_status->{$id}->{zone} = $zone;
+       $vnet_status->{$id}->{status} = 'available';
 
-    #fallback if classic bridge
-    return ($vnetid, undef) if !$vnet;
+       if ($err_config) {
+           $vnet_status->{$id}->{status} = 'pending';
+           $vnet_status->{$id}->{statusmsg} = $err_config;
+           next;
+       }
 
-    my $zone_cfg = PVE::Network::SDN::Zones::config();
-    my $zoneid = $vnet->{zone};
-    my $tag = $vnet->{tag};
+       my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
+       my $err_msg = $plugin->status($plugin_config, $zone, $id, $vnet, $status);
+       if (@{$err_msg} > 0) {
+           $vnet_status->{$id}->{status} = 'error';
+           $vnet_status->{$id}->{statusmsg} = join(',', @{$err_msg});
+           $zone_status->{$id}->{status} = 'error';
+       }
+    }
 
-    my $plugin_config = $zone_cfg->{ids}->{$zoneid};
-    my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
-    return $plugin->get_bridge_vlan($plugin_config, $vnetid, $tag);
+    return ($zone_status, $vnet_status);
 }
 
 sub tap_create {
     my ($iface, $bridge) = @_;
 
-    my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge);
-
-    #fallback if classic bridge
-    if(!$vnet) {
+    my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
+    if (!$vnet) { # fallback for classic bridge
        PVE::Network::tap_create($iface, $bridge);
-        return;
+       return;
     }
 
-    my $zone_cfg = PVE::Network::SDN::Zones::config();
-    my $zoneid = $vnet->{zone};
-
-    my $plugin_config = $zone_cfg->{ids}->{$zoneid};
+    my $plugin_config = get_plugin_config($vnet);
     my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
     $plugin->tap_create($plugin_config, $vnet, $iface, $bridge);
 }
@@ -254,18 +292,13 @@ sub tap_create {
 sub veth_create {
     my ($veth, $vethpeer, $bridge, $hwaddr) = @_;
 
-    my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge);
-
-    #fallback if classic bridge
-    if(!$vnet) {
+    my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
+    if (!$vnet) { # fallback for classic bridge
        PVE::Network::veth_create($veth, $vethpeer, $bridge, $hwaddr);
-        return;
+       return;
     }
 
-    my $zone_cfg = PVE::Network::SDN::Zones::config();
-    my $zoneid = $vnet->{zone};
-
-    my $plugin_config = $zone_cfg->{ids}->{$zoneid};
+    my $plugin_config = get_plugin_config($vnet);
     my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
     $plugin->veth_create($plugin_config, $vnet, $veth, $vethpeer, $bridge, $hwaddr);
 }
@@ -273,25 +306,22 @@ sub veth_create {
 sub tap_plug {
     my ($iface, $bridge, $tag, $firewall, $trunks, $rate) = @_;
 
-    my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge);
-
-    #fallback if classic bridge
-    if(!$vnet) {
-       PVE::Network::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate);
+    my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
+    if (!$vnet) { # fallback for classic bridge
+       my $interfaces_config = PVE::INotify::read_file('interfaces');
+       my $disablelearning = 1 if $interfaces_config->{ifaces}->{$bridge} && $interfaces_config->{ifaces}->{$bridge}->{'bridge-disable-mac-learning'};
+       PVE::Network::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate, $disablelearning);
        return;
     }
 
-    my $zone_cfg = PVE::Network::SDN::Zones::config();
+    my $plugin_config = get_plugin_config($vnet);
     my $nodename = PVE::INotify::nodename();
 
-    my $zoneid = $vnet->{zone};
-    $tag = $vnet->{tag};
-
-    die "vnet $bridge is not allowed on this node" if defined($zone_cfg->{ids}->{$zoneid}->{nodes}) && !$zone_cfg->{ids}->{$zoneid}->{nodes}->{$nodename};
+    die "vnet $bridge is not allowed on this node\n"
+       if $plugin_config->{nodes} && !defined($plugin_config->{nodes}->{$nodename});
 
-    my $plugin_config = $zone_cfg->{ids}->{$zoneid};
     my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
-    $plugin->tap_plug($plugin_config, $vnet, $iface, $bridge, $firewall, $rate);
+    $plugin->tap_plug($plugin_config, $vnet, $tag, $iface, $bridge, $firewall, $trunks, $rate);
 }
 
 1;