Adds a new file priv/macs.db for caching the queries to IPAM.

Additionally adds and imeplements methods to the IPAM plugins that
are required for the DHCP functionality.

Co-Authored-By: Alexandre Derumier <aderum...@odiso.com>
Signed-off-by: Stefan Hanreich <s.hanre...@proxmox.com>
---
 src/PVE/Network/SDN/Ipams.pm               | 80 +++++++++++++++++++-
 src/PVE/Network/SDN/Ipams/NetboxPlugin.pm  | 86 ++++++++++++++++++++--
 src/PVE/Network/SDN/Ipams/PVEPlugin.pm     | 85 +++++++++++++++++++--
 src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm | 29 ++++++++
 src/PVE/Network/SDN/Ipams/Plugin.pm        | 19 ++++-
 5 files changed, 281 insertions(+), 18 deletions(-)

diff --git a/src/PVE/Network/SDN/Ipams.pm b/src/PVE/Network/SDN/Ipams.pm
index e8a4b0b..926df90 100644
--- a/src/PVE/Network/SDN/Ipams.pm
+++ b/src/PVE/Network/SDN/Ipams.pm
@@ -4,9 +4,10 @@ use strict;
 use warnings;
 
 use JSON;
+use Net::IP;
 
 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::Cluster qw(cfs_register_file cfs_read_file cfs_write_file 
cfs_lock_file);
 use PVE::Network;
 
 use PVE::Network::SDN::Ipams::PVEPlugin;
@@ -19,6 +20,64 @@ PVE::Network::SDN::Ipams::NetboxPlugin->register();
 PVE::Network::SDN::Ipams::PhpIpamPlugin->register();
 PVE::Network::SDN::Ipams::Plugin->init();
 
+my $macdb_filename = 'priv/macs.db';
+
+cfs_register_file($macdb_filename, \&json_reader, \&json_writer);
+
+sub json_reader {
+    my ($filename, $data) = @_;
+
+    return defined($data) && length($data) > 0 ? decode_json($data) : {};
+}
+
+sub json_writer {
+    my ($filename, $data) = @_;
+
+    return encode_json($data);
+}
+
+sub read_macdb {
+    my () = @_;
+
+    return cfs_read_file($macdb_filename);
+}
+
+sub write_macdb {
+    my ($data) = @_;
+
+    cfs_write_file($macdb_filename, $data);
+}
+
+sub add_cache_mac_ip {
+    my ($mac, $ip) = @_;
+
+    cfs_lock_file($macdb_filename, undef, sub {
+       my $db = read_macdb();
+       if (Net::IP::ip_is_ipv4($ip)) {
+           $db->{macs}->{$mac}->{ip4} = $ip;
+       } else {
+           $db->{macs}->{$mac}->{ip6} = $ip;
+       }
+       write_macdb($db);
+    });
+    warn "$@" if $@;
+}
+
+sub del_cache_mac_ip {
+    my ($mac, $ip) = @_;
+
+    cfs_lock_file($macdb_filename, undef, sub {
+       my $db = read_macdb();
+       if (Net::IP::ip_is_ipv4($ip)) {
+           delete $db->{macs}->{$mac}->{ip4};
+       } else {
+           delete $db->{macs}->{$mac}->{ip6};
+       }
+        delete $db->{macs}->{$mac} if !defined($db->{macs}->{$mac}->{ip4}) && 
!defined($db->{macs}->{$mac}->{ip6});
+       write_macdb($db);
+    });
+    warn "$@" if $@;
+}
 
 sub sdn_ipams_config {
     my ($cfg, $id, $noerr) = @_;
@@ -39,8 +98,8 @@ sub config {
 }
 
 sub get_plugin_config {
-    my ($vnet) = @_;
-    my $ipamid = $vnet->{ipam};
+    my ($zone) = @_;
+    my $ipamid = $zone->{ipam};
     my $ipam_cfg = PVE::Network::SDN::Ipams::config();
     return $ipam_cfg->{ids}->{$ipamid};
 }
@@ -65,5 +124,20 @@ sub complete_sdn_vnet {
     return  $cmdname eq 'add' ? [] : [ 
PVE::Network::SDN::Vnets::sdn_ipams_ids($cfg) ];
 }
 
+sub get_ips_from_mac {
+    my ($mac, $zoneid, $zone) = @_;
+
+    my $macdb = read_macdb();
+    return ($macdb->{macs}->{$mac}->{ip4}, $macdb->{macs}->{$mac}->{ip6}) if 
$macdb->{macs}->{$mac};
+
+    my $plugin_config = get_plugin_config($zone);
+    my $plugin = 
PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
+    ($macdb->{macs}->{$mac}->{ip4}, $macdb->{macs}->{$mac}->{ip6}) = 
$plugin->get_ips_from_mac($plugin_config, $mac, $zoneid);
+
+    write_macdb($macdb);
+
+    return ($macdb->{macs}->{$mac}->{ip4}, $macdb->{macs}->{$mac}->{ip6});
+}
+
 1;
 
diff --git a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm 
b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
index f0e7168..91010bb 100644
--- a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
@@ -77,14 +77,20 @@ sub del_subnet {
 }
 
 sub add_ip {
-    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, 
$description, $is_gateway, $noerr) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, 
$vmid, $is_gateway, $noerr) = @_;
 
     my $mask = $subnet->{mask};
     my $url = $plugin_config->{url};
     my $token = $plugin_config->{token};
     my $section = $plugin_config->{section};
     my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 
'Authorization' => "token $token"];
-    $description .= " mac:$mac" if $mac && $description;
+
+    my $description = undef;
+    if ($is_gateway) {
+       $description = 'gateway'
+    } elsif ($mac) {
+       $description = "mac:$mac";
+    }
 
     my $params = { address => "$ip/$mask", dns_name => $hostname, description 
=> $description };
 
@@ -102,14 +108,20 @@ sub add_ip {
 }
 
 sub update_ip {
-    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, 
$description, $is_gateway, $noerr) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, 
$vmid, $is_gateway, $noerr) = @_;
 
     my $mask = $subnet->{mask};
     my $url = $plugin_config->{url};
     my $token = $plugin_config->{token};
     my $section = $plugin_config->{section};
     my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 
'Authorization' => "token $token"];
-    $description .= " mac:$mac" if $mac && $description;
+
+    my $description = undef;
+    if ($is_gateway) {
+       $description = 'gateway'
+    } elsif ($mac) {
+       $description = "mac:$mac";
+    }
 
     my $params = { address => "$ip/$mask", dns_name => $hostname, description 
=> $description };
 
@@ -125,7 +137,7 @@ sub update_ip {
 }
 
 sub add_next_freeip {
-    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, 
$description, $noerr) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid, 
$noerr) = @_;
 
     my $cidr = $subnet->{cidr};
 
@@ -134,7 +146,8 @@ sub add_next_freeip {
     my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 
'Authorization' => "token $token"];
 
     my $internalid = get_prefix_id($url, $cidr, $headers);
-    $description .= " mac:$mac" if $mac && $description;
+
+    my $description = "mac:$mac" if $mac;
 
     my $params = { dns_name => $hostname, description => $description };
 
@@ -151,6 +164,33 @@ sub add_next_freeip {
     return $ip;
 }
 
+sub add_range_next_freeip {
+    my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
+
+    my $url = $plugin_config->{url};
+    my $token = $plugin_config->{token};
+    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 
'Authorization' => "token $token"];
+
+    my $internalid = get_iprange_id($url, $range, $headers);
+    my $description = "mac:$data->{mac}" if $data->{mac};
+
+    my $params = { dns_name => $data->{hostname}, description => $description 
};
+
+    my $ip = undef;
+    eval {
+       my $result = PVE::Network::SDN::api_request("POST", 
"$url/ipam/ip-ranges/$internalid/available-ips/", $headers, $params);
+       $ip = $result->{address};
+       print "found ip free $ip in range 
$range->{'start-address'}-$range->{'end-address'}\n" if $ip;
+    };
+
+    if ($@) {
+       die "can't find free ip in range 
$range->{'start-address'}-$range->{'end-address'}: $@" if !$noerr;
+    }
+
+    return $ip;
+
+}
+
 sub del_ip {
     my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_;
 
@@ -171,6 +211,31 @@ sub del_ip {
     }
 }
 
+sub get_ips_from_mac {
+    my ($class, $plugin_config, $mac, $zoneid) = @_;
+
+    my $url = $plugin_config->{url};
+    my $token = $plugin_config->{token};
+    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 
'Authorization' => "token $token"];
+
+    my $ip4 = undef;
+    my $ip6 = undef;
+
+    my $data = PVE::Network::SDN::api_request("GET", 
"$url/ipam/ip-addresses/?description__ic=$mac", $headers);
+    for my $ip (@{$data->{results}}) {
+       if ($ip->{family}->{value} == 4 && !$ip4) {
+           ($ip4, undef) = split(/\//, $ip->{address});
+       }
+
+       if ($ip->{family}->{value} == 6 && !$ip6) {
+           ($ip6, undef) = split(/\//, $ip->{address});
+       }
+    }
+
+    return ($ip4, $ip6);
+}
+
+
 sub verify_api {
     my ($class, $plugin_config) = @_;
 
@@ -204,6 +269,15 @@ sub get_prefix_id {
     return $internalid;
 }
 
+sub get_iprange_id {
+    my ($url, $range, $headers) = @_;
+
+    my $result = PVE::Network::SDN::api_request("GET", 
"$url/ipam/ip-ranges/?start_address=$range->{'start-address'}&end_address=$range->{'end-address'}",
 $headers);
+    my $data = @{$result->{results}}[0];
+    my $internalid = $data->{id};
+    return $internalid;
+}
+
 sub get_ip_id {
     my ($url, $ip, $headers) = @_;
     my $result = PVE::Network::SDN::api_request("GET", 
"$url/ipam/ip-addresses/?q=$ip", $headers);
diff --git a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm 
b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm
index 3e8ffc5..a5b4fe7 100644
--- a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm
@@ -82,7 +82,7 @@ sub del_subnet {
 }
 
 sub add_ip {
-    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, 
$description, $is_gateway) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, 
$vmid, $is_gateway) = @_;
 
     my $cidr = $subnet->{cidr};
     my $zone = $subnet->{zone};
@@ -96,8 +96,17 @@ sub add_ip {
        die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet;
 
        die "IP '$ip' already exist\n" if (!$is_gateway && 
defined($dbsubnet->{ips}->{$ip})) || ($is_gateway && 
defined($dbsubnet->{ips}->{$ip}) && 
!defined($dbsubnet->{ips}->{$ip}->{gateway}));
-       $dbsubnet->{ips}->{$ip} = {};
-       $dbsubnet->{ips}->{$ip} = {gateway => 1} if $is_gateway;
+
+        my $data = {};
+       if ($is_gateway) {
+           $data->{gateway} = 1;
+       } else {
+           $data->{vmid} = $vmid if $vmid;
+           $data->{hostname} = $hostname if $hostname;
+           $data->{mac} = $mac if $mac;
+       }
+
+       $dbsubnet->{ips}->{$ip} = $data;
 
        write_db($db);
     });
@@ -105,12 +114,12 @@ sub add_ip {
 }
 
 sub update_ip {
-    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, 
$description, $is_gateway) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, 
$vmid, $is_gateway) = @_;
     return;
 }
 
 sub add_next_freeip {
-    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, 
$description) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid, 
$noerr) = @_;
 
     my $cidr = $subnet->{cidr};
     my $network = $subnet->{network};
@@ -156,6 +165,39 @@ sub add_next_freeip {
     return "$freeip/$mask";
 }
 
+sub add_range_next_freeip {
+    my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
+
+    my $cidr = $subnet->{cidr};
+    my $zone = $subnet->{zone};
+
+    cfs_lock_file($ipamdb_file, undef, sub {
+       my $db = read_db();
+
+       my $dbzone = $db->{zones}->{$zone};
+       die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone;
+
+       my $dbsubnet = $dbzone->{subnets}->{$cidr};
+       die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet;
+
+       my $ip = new Net::IP ("$range->{'start-address'} - 
$range->{'end-address'}")
+           or die "Invalid IP address(es) in Range!\n";
+       my $mac = $data->{mac};
+
+       do {
+           my $ip_address = $ip->version() == 6 ? $ip->short() : $ip->ip();
+           if (!$dbsubnet->{ips}->{$ip_address}) {
+               $dbsubnet->{ips}->{$ip_address} = $data;
+               write_db($db);
+
+               return $ip_address;
+           }
+       } while (++$ip);
+
+       die "No free IP left in Range 
$range->{'start-address'}:$range->{'end-address'}}\n";
+    });
+}
+
 sub del_ip {
     my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_;
 
@@ -172,12 +214,43 @@ sub del_ip {
 
        die "IP '$ip' does not exist in IPAM DB\n" if 
!defined($dbsubnet->{ips}->{$ip});
        delete $dbsubnet->{ips}->{$ip};
-
        write_db($db);
     });
     die "$@" if $@;
 }
 
+sub get_ips_from_mac {
+    my ($class, $plugin_config, $mac, $zoneid) = @_;
+
+    #just in case, as this should already be cached in local macs.db
+
+    my $ip4 = undef;
+    my $ip6 = undef;
+
+    my $db = read_db();
+    die "zone $zoneid don't exist in ipam db" if !$db->{zones}->{$zoneid};
+    my $dbzone = $db->{zones}->{$zoneid};
+    my $subnets = $dbzone->{subnets};
+
+    for my $subnet ( keys %$subnets) {
+       next if Net::IP::ip_is_ipv4($subnet) && $ip4;
+       next if $ip6;
+       my $ips = $subnets->{$subnet}->{ips};
+       for my $ip (keys %$ips) {
+           my $ipobject = $ips->{$ip};
+           if ($ipobject->{mac} && $ipobject->{mac} eq $mac) {
+               if (Net::IP::ip_is_ipv4($ip)) {
+                   $ip4 = $ip;
+               } else {
+                   $ip6 = $ip;
+               }
+           }
+       }
+       last if $ip4 && $ip6;
+    }
+    return ($ip4, $ip6);
+}
+
 #helpers
 
 sub read_db {
diff --git a/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm 
b/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm
index ad5286b..1b7b666 100644
--- a/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm
@@ -204,6 +204,35 @@ sub del_ip {
     }
 }
 
+sub get_ips_from_mac {
+    my ($class, $plugin_config, $mac, $zoneid) = @_;
+
+
+    my $url = $plugin_config->{url};
+    my $token = $plugin_config->{token};
+    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 
'Token' => $token];
+
+    my $ip4 = undef;
+    my $ip6 = undef;
+
+    my $ips = PVE::Network::SDN::api_request("GET", 
"$url/addresses/search_mac/$mac", $headers);
+
+    #fixme
+    die "parsing of result not yet implemented";
+
+    for my $ip (@$ips) {
+#        if ($ip->{family}->{value} == 4 && !$ip4) {
+#            ($ip4, undef) = split(/\//, $ip->{address});
+#        }
+#
+#        if ($ip->{family}->{value} == 6 && !$ip6) {
+#            ($ip6, undef) = split(/\//, $ip->{address});
+#        }
+    }
+
+    return ($ip4, $ip6);
+}
+
 sub verify_api {
     my ($class, $plugin_config) = @_;
 
diff --git a/src/PVE/Network/SDN/Ipams/Plugin.pm 
b/src/PVE/Network/SDN/Ipams/Plugin.pm
index c96eeda..05d1416 100644
--- a/src/PVE/Network/SDN/Ipams/Plugin.pm
+++ b/src/PVE/Network/SDN/Ipams/Plugin.pm
@@ -79,13 +79,13 @@ sub del_subnet {
 }
 
 sub add_ip {
-    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, 
$description, $is_gateway, $noerr) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, 
$vmid, $is_gateway, $noerr) = @_;
 
     die "please implement inside plugin";
 }
 
 sub update_ip {
-    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, 
$description, $is_gateway, $noerr) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, 
$vmid, $is_gateway, $noerr) = @_;
     # only update ip attributes (mac,hostname,..). Don't change the ip 
addresses itself, as some ipam
     # don't allow ip address change without del/add
 
@@ -93,7 +93,14 @@ sub update_ip {
 }
 
 sub add_next_freeip {
-    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, 
$description, $noerr) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid, 
$noerr) = @_;
+
+    die "please implement inside plugin";
+}
+
+
+sub add_range_next_freeip {
+    my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
 
     die "please implement inside plugin";
 }
@@ -104,6 +111,12 @@ sub del_ip {
     die "please implement inside plugin";
 }
 
+sub get_ips_from_mac {
+    my ($class, $plugin_config, $mac, $zone) = @_;
+
+    die "please implement inside plugin";
+}
+
 sub on_update_hook {
     my ($class, $plugin_config)  = @_;
 }
-- 
2.39.2


_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to