diff options
Diffstat (limited to 'src/PVE/Network/SDN/Zones/Plugin.pm')
| -rw-r--r-- | src/PVE/Network/SDN/Zones/Plugin.pm | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/src/PVE/Network/SDN/Zones/Plugin.pm b/src/PVE/Network/SDN/Zones/Plugin.pm new file mode 100644 index 0000000..2c707b3 --- /dev/null +++ b/src/PVE/Network/SDN/Zones/Plugin.pm @@ -0,0 +1,340 @@ +package PVE::Network::SDN::Zones::Plugin; + +use strict; +use warnings; + +use PVE::Tools qw(run_command); +use PVE::JSONSchema; +use PVE::Cluster; +use PVE::Network; + +use PVE::JSONSchema qw(get_standard_option); +use base qw(PVE::SectionConfig); + +PVE::Cluster::cfs_register_file( + 'sdn/zones.cfg', + 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 "zone ID '$id' contains illegal characters\n"; + } + die "zone ID '$id' can't be more length than 8 characters\n" if length($id) > 8; + return $id; +} + +my $defaultData = { + + propertyList => { + type => { + description => "Plugin type.", + type => 'string', format => 'pve-configid', + type => 'string', + }, + nodes => get_standard_option('pve-node-list', { optional => 1 }), + zone => get_standard_option('pve-sdn-zone-id', { + completion => \&PVE::Network::SDN::Zones::complete_sdn_zone, + }), + ipam => { + type => 'string', + description => "use a specific ipam", + optional => 1, + }, + }, +}; + +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 decode_value { + my ($class, $type, $key, $value) = @_; + + if ($key eq 'nodes' || $key eq 'exitnodes') { + my $res = {}; + + foreach my $node (PVE::Tools::split_list($value)) { + if (PVE::JSONSchema::pve_verify_node_name($node)) { + $res->{$node} = 1; + } + } + + return $res; + } + + return $value; +} + +sub encode_value { + my ($class, $type, $key, $value) = @_; + + if ($key eq 'nodes' || $key eq 'exitnodes') { + return join(',', keys(%$value)); + } + + return $value; +} + +sub generate_sdn_config { + my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; + + die "please implement inside plugin"; +} + +sub generate_controller_config { + my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_; + + die "please implement inside plugin"; +} + +sub generate_controller_vnet_config { + my ($class, $plugin_config, $controller, $zoneid, $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, $zoneid, $vnet_cfg) = @_; + + # verify that no vnet are associated to this zone + foreach my $id (keys %{$vnet_cfg->{ids}}) { + my $vnet = $vnet_cfg->{ids}->{$id}; + die "zone $zoneid is used by vnet $id" + if ($vnet->{type} eq 'vnet' && defined($vnet->{zone}) && $vnet->{zone} eq $zoneid); + } +} + +sub on_update_hook { + my ($class, $zoneid, $zone_cfg, $controller_cfg) = @_; + + # do nothing by default +} + +sub vnet_update_hook { + my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; + + # 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); +} + +sub status { + my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_; + + my $err_msg = []; + + # ifaces to check + my $ifaces = [ $vnetid ]; + + foreach my $iface (@{$ifaces}) { + if (!$status->{$iface}->{status}) { + push @$err_msg, "missing $iface"; + } elsif ($status->{$iface}->{status} ne 'pass') { + push @$err_msg, "error $iface"; + } + } + return $err_msg; +} + + +sub tap_create { + my ($class, $plugin_config, $vnet, $iface, $vnetid) = @_; + + PVE::Network::tap_create($iface, $vnetid); +} + +sub veth_create { + my ($class, $plugin_config, $vnet, $veth, $vethpeer, $vnetid, $hwaddr) = @_; + + PVE::Network::veth_create($veth, $vethpeer, $vnetid, $hwaddr); +} + +sub tap_plug { + my ($class, $plugin_config, $vnet, $tag, $iface, $vnetid, $firewall, $trunks, $rate) = @_; + + my $vlan_aware = PVE::Tools::file_read_firstline("/sys/class/net/$vnetid/bridge/vlan_filtering"); + die "vm vlans are not allowed on vnet $vnetid" if !$vlan_aware && ($tag || $trunks); + + my $opts = {}; + $opts->{learning} = 0 if $plugin_config->{'bridge-disable-mac-learning'}; + PVE::Network::tap_plug($iface, $vnetid, $tag, $firewall, $trunks, $rate, $opts); +} + +#helper + +sub get_uplink_iface { + my ($interfaces_config, $uplink) = @_; + + my $iface = undef; + foreach my $id (keys %{$interfaces_config->{ifaces}}) { + my $interface = $interfaces_config->{ifaces}->{$id}; + if (my $iface_uplink = $interface->{'uplink-id'}) { + next if $iface_uplink ne $uplink; + if($interface->{type} ne 'eth' && $interface->{type} ne 'bond') { + warn "uplink $uplink is not a physical or bond interface"; + next; + } + $iface = $id; + } + } + + #create a dummy uplink interface if no uplink found + if(!$iface) { + warn "can't find uplink $uplink in physical interface"; + $iface = "uplink${uplink}"; + } + + return $iface; +} + +sub get_local_route_ip { + my ($targetip) = @_; + + my $ip = undef; + my $interface = undef; + + run_command(['/sbin/ip', 'route', 'get', $targetip], outfunc => sub { + if ($_[0] =~ m/src ($PVE::Tools::IPRE)/) { + $ip = $1; + } + if ($_[0] =~ m/dev (\S+)/) { + $interface = $1; + } + + }); + return ($ip, $interface); +} + + +sub find_local_ip_interface_peers { + my ($peers, $iface) = @_; + + my $network_config = PVE::INotify::read_file('interfaces'); + my $ifaces = $network_config->{ifaces}; + + #if iface is defined, return ip if exist (if not,try to find it on other ifaces) + if ($iface) { + my $ip = $ifaces->{$iface}->{address}; + return ($ip,$iface) if $ip; + } + + #is a local ip member of peers list ? + foreach my $address (@{$peers}) { + while (my $interface = each %$ifaces) { + my $ip = $ifaces->{$interface}->{address}; + if ($ip && $ip eq $address) { + return ($ip, $interface); + } + } + } + + #if peer is remote, find source with ip route + foreach my $address (@{$peers}) { + my ($ip, $interface) = get_local_route_ip($address); + return ($ip, $interface); + } +} + +sub find_bridge { + my ($bridge) = @_; + + die "can't find bridge $bridge" if !-d "/sys/class/net/$bridge"; +} + +sub is_vlanaware { + my ($bridge) = @_; + + return PVE::Tools::file_read_firstline("/sys/class/net/$bridge/bridge/vlan_filtering"); +} + +sub is_ovs { + my ($bridge) = @_; + + my $is_ovs = !-d "/sys/class/net/$bridge/brif"; + return $is_ovs; +} + +sub get_bridge_ifaces { + my ($bridge) = @_; + + my @bridge_ifaces = (); + my $dir = "/sys/class/net/$bridge/brif"; + PVE::Tools::dir_glob_foreach($dir, '(((eth|bond)\d+|en[^.]+)(\.\d+)?)', sub { + push @bridge_ifaces, $_[0]; + }); + + return @bridge_ifaces; +} +1; |
