public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] SPAM: [PATCH pve-network v2 1/7] ipam: nautobot support initial commit
       [not found] <20250108121529.5813-1-lou.lecrivain@wdz.de>
@ 2025-01-08 12:15 ` Lou Lecrivain via pve-devel
  2025-02-19 16:36   ` Hannes Dürr
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 2/7] ipam: nautobot: implement plain prefix allocation Lou Lecrivain via pve-devel
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 18+ messages in thread
From: Lou Lecrivain via pve-devel @ 2025-01-08 12:15 UTC (permalink / raw)
  To: pve-devel; +Cc: Lou Lecrivain

[-- Attachment #1: Type: message/rfc822, Size: 11229 bytes --]

From: Lou Lecrivain <lou.lecrivain@wdz.de>
To: pve-devel@lists.proxmox.com
Subject: SPAM: [PATCH pve-network v2 1/7] ipam: nautobot support initial commit
Date: Wed,  8 Jan 2025 13:15:23 +0100
Message-ID: <20250108121529.5813-2-lou.lecrivain@wdz.de>

This is the initial Nautobot plugin, based on the Netbox
plugin implementation.

Signed-off-by: lou lecrivain <lou.lecrivain@wdz.de>
---
 src/PVE/API2/Network/SDN/Ipams.pm           |   1 +
 src/PVE/Network/SDN/Ipams.pm                |   3 +
 src/PVE/Network/SDN/Ipams/Makefile          |   2 +-
 src/PVE/Network/SDN/Ipams/NautobotPlugin.pm | 195 ++++++++++++++++++++
 4 files changed, 200 insertions(+), 1 deletion(-)
 create mode 100644 src/PVE/Network/SDN/Ipams/NautobotPlugin.pm

diff --git a/src/PVE/API2/Network/SDN/Ipams.pm b/src/PVE/API2/Network/SDN/Ipams.pm
index 27ead02..8074512 100644
--- a/src/PVE/API2/Network/SDN/Ipams.pm
+++ b/src/PVE/API2/Network/SDN/Ipams.pm
@@ -12,6 +12,7 @@ use PVE::Network::SDN::Ipams::Plugin;
 use PVE::Network::SDN::Ipams::PVEPlugin;
 use PVE::Network::SDN::Ipams::PhpIpamPlugin;
 use PVE::Network::SDN::Ipams::NetboxPlugin;
+use PVE::Network::SDN::Ipams::NautobotPlugin;
 use PVE::Network::SDN::Dhcp;
 use PVE::Network::SDN::Vnets;
 use PVE::Network::SDN::Zones;
diff --git a/src/PVE/Network/SDN/Ipams.pm b/src/PVE/Network/SDN/Ipams.pm
index c689b8f..2ecb75e 100644
--- a/src/PVE/Network/SDN/Ipams.pm
+++ b/src/PVE/Network/SDN/Ipams.pm
@@ -12,11 +12,14 @@ use PVE::Network;
 
 use PVE::Network::SDN::Ipams::PVEPlugin;
 use PVE::Network::SDN::Ipams::NetboxPlugin;
+use PVE::Network::SDN::Ipams::NautobotPlugin;
 use PVE::Network::SDN::Ipams::PhpIpamPlugin;
 use PVE::Network::SDN::Ipams::Plugin;
 
+
 PVE::Network::SDN::Ipams::PVEPlugin->register();
 PVE::Network::SDN::Ipams::NetboxPlugin->register();
+PVE::Network::SDN::Ipams::NautobotPlugin->register();
 PVE::Network::SDN::Ipams::PhpIpamPlugin->register();
 PVE::Network::SDN::Ipams::Plugin->init();
 
diff --git a/src/PVE/Network/SDN/Ipams/Makefile b/src/PVE/Network/SDN/Ipams/Makefile
index 4e7d65f..75e5b9a 100644
--- a/src/PVE/Network/SDN/Ipams/Makefile
+++ b/src/PVE/Network/SDN/Ipams/Makefile
@@ -1,4 +1,4 @@
-SOURCES=Plugin.pm PhpIpamPlugin.pm NetboxPlugin.pm PVEPlugin.pm
+SOURCES=Plugin.pm PhpIpamPlugin.pm NetboxPlugin.pm PVEPlugin.pm NautobotPlugin.pm
 
 
 PERL5DIR=${DESTDIR}/usr/share/perl5
diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
new file mode 100644
index 0000000..69e7897
--- /dev/null
+++ b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
@@ -0,0 +1,195 @@
+package PVE::Network::SDN::Ipams::NautobotPlugin;
+
+use strict;
+use warnings;
+use PVE::INotify;
+use PVE::Cluster;
+use PVE::Tools;
+
+use base('PVE::Network::SDN::Ipams::NetboxPlugin');
+
+sub type {
+    return 'nautobot';
+}
+
+sub properties {
+    return {
+	namespace => {
+	    type => 'string',
+	},
+    };
+}
+
+sub options {
+    return {
+	url => { optional => 0 },
+	token => { optional => 0 },
+	namespace => { optional => 0 },
+    };
+}
+
+sub default_ip_status {
+    return 'Active';
+}
+
+sub default_headers {
+    my ($plugin_config) = @_;
+    my $token = $plugin_config->{token};
+
+    return ['Content-Type' => "application/json", 'Authorization' => "token $token", 'Accept' => "application/json"];
+}
+
+# implem
+
+sub add_subnet {
+    my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
+
+    my $cidr = $subnet->{cidr};
+    my $gateway = $subnet->{gateway};
+    my $url = $plugin_config->{url};
+    my $namespace = $plugin_config->{namespace};
+    my $headers = default_headers($plugin_config);
+
+    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
+
+    #create subnet
+    if (!$internalid) {
+	my $params = { prefix => $cidr, namespace => $namespace, status => default_ip_status()};
+
+	eval {
+		my $result = PVE::Network::SDN::api_request("POST", "$url/ipam/prefixes/", $headers, $params);
+	};
+	if ($@) {
+	    die "error adding subnet to ipam: $@" if !$noerr;
+	}
+    }
+}
+
+sub add_ip {
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
+
+    my $mask = $subnet->{mask};
+    my $url = $plugin_config->{url};
+    my $namespace = $plugin_config->{namespace};
+    my $headers = default_headers($plugin_config);
+
+    my $description = undef;
+    if ($is_gateway) {
+	$description = 'gateway'
+    } elsif ($mac) {
+	$description = "mac:$mac";
+    }
+
+    my $params = { address => "$ip/$mask", type => "dhcp", dns_name => $hostname, description => $description, namespace => $namespace, status => default_ip_status()};
+
+    eval {
+	PVE::Network::SDN::api_request("POST", "$url/ipam/ip-addresses/", $headers, $params);
+    };
+
+    if ($@) {
+	if($is_gateway) {
+	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !PVE::Network::SDN::Ipams::NetboxPlugin::is_ip_gateway($url, $ip, $headers) && !$noerr;
+	} else {
+	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !$noerr;
+	}
+    }
+}
+
+
+sub update_ip {
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
+
+    my $mask = $subnet->{mask};
+    my $url = $plugin_config->{url};
+    my $namespace = $plugin_config->{namespace};
+    my $headers = default_headers($plugin_config);
+
+    my $description = undef;
+    if ($is_gateway) {
+	$description = 'gateway'
+    } elsif ($mac) {
+	$description = "mac:$mac";
+    }
+
+    my $params = { address => "$ip/$mask", type => "dhcp", dns_name => $hostname, description => $description, namespace => $namespace, status => default_ip_status()};
+
+    my $ip_id = PVE::Network::SDN::Ipams::NetboxPlugin::get_ip_id($url, $ip, $headers);
+    die "can't find ip $ip in ipam" if !$ip_id;
+
+    eval {
+	PVE::Network::SDN::api_request("PATCH", "$url/ipam/ip-addresses/$ip_id/", $headers, $params);
+    };
+    if ($@) {
+	die "error updating ip $ip: $@" if !$noerr;
+    }
+}
+
+
+sub verify_api {
+    my ($class, $plugin_config) = @_;
+
+    my $url = $plugin_config->{url};
+    my $namespace = $plugin_config->{namespace};
+    my $headers = default_headers($plugin_config);
+
+    # check that the namespace exists AND that default IP active status
+    # exists AND that we have indeed API access
+    eval {
+	get_namespace_id($url, $namespace, $headers) // die "namespace $namespace does not exist";
+	get_status_id($url, default_ip_status(), $headers) // die "default IP status ". default_ip_status() . " not found";
+    };
+    if ($@) {
+	die "Can't use nautobot api: $@";
+    }
+}
+
+sub get_ips_from_mac {
+    my ($class, $plugin_config, $mac, $zoneid) = @_;
+
+    my $url = $plugin_config->{url};
+    my $namespace = $plugin_config->{namespace};
+    my $headers = default_headers($plugin_config);
+
+    my $ip4 = undef;
+    my $ip6 = undef;
+
+    my $data = PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$mac", $headers);
+    for my $ip (@{$data->{results}}) {
+	if ($ip->{ip_version} == 4 && !$ip4) {
+	    ($ip4, undef) = split(/\//, $ip->{address});
+	}
+
+	if ($ip->{ip_version} == 6 && !$ip6) {
+	    ($ip6, undef) = split(/\//, $ip->{address});
+	}
+    }
+
+    return ($ip4, $ip6);
+}
+
+sub on_update_hook {
+    my ($class, $plugin_config) = @_;
+
+    PVE::Network::SDN::Ipams::NautobotPlugin::verify_api($class, $plugin_config);
+}
+
+# helpers
+sub get_namespace_id {
+    my ($url, $namespace, $headers) = @_;
+
+    my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/namespaces/?q=$namespace", $headers);
+    my $data = @{$result->{results}}[0];
+    my $internalid = $data->{id};
+    return $internalid;
+}
+
+sub get_status_id {
+    my ($url, $status, $headers) = @_;
+
+    my $result = PVE::Network::SDN::api_request("GET", "$url/extras/statuses/?q=$status", $headers);
+    my $data = @{$result->{results}}[0];
+    my $internalid = $data->{id};
+    return $internalid;
+}
+
+1;
-- 
2.39.5



[-- Attachment #2: Type: text/plain, Size: 160 bytes --]

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

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [pve-devel] SPAM: [PATCH pve-network v2 2/7] ipam: nautobot: implement plain prefix allocation
       [not found] <20250108121529.5813-1-lou.lecrivain@wdz.de>
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 1/7] ipam: nautobot support initial commit Lou Lecrivain via pve-devel
@ 2025-01-08 12:15 ` Lou Lecrivain via pve-devel
  2025-02-19 16:37   ` Hannes Dürr
  2025-02-20 13:24   ` Hannes Dürr
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 3/7] ipam: nautobot: add testing for nautobot plugin Lou Lecrivain via pve-devel
                   ` (5 subsequent siblings)
  7 siblings, 2 replies; 18+ messages in thread
From: Lou Lecrivain via pve-devel @ 2025-01-08 12:15 UTC (permalink / raw)
  To: pve-devel; +Cc: Lou Lecrivain

[-- Attachment #1: Type: message/rfc822, Size: 6927 bytes --]

From: Lou Lecrivain <lou.lecrivain@wdz.de>
To: pve-devel@lists.proxmox.com
Subject: SPAM: [PATCH pve-network v2 2/7] ipam: nautobot: implement plain prefix allocation
Date: Wed,  8 Jan 2025 13:15:24 +0100
Message-ID: <20250108121529.5813-3-lou.lecrivain@wdz.de>

add support for subnet allocation without ranges,
where it was previously not supported.

Signed-off-by: lou lecrivain <lou.lecrivain@wdz.de>
---
 src/PVE/Network/SDN/Ipams/NautobotPlugin.pm | 68 +++++++++++++++++++++
 1 file changed, 68 insertions(+)

diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
index 69e7897..22867df 100644
--- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
@@ -5,6 +5,7 @@ use warnings;
 use PVE::INotify;
 use PVE::Cluster;
 use PVE::Tools;
+use NetAddr::IP;
 
 use base('PVE::Network::SDN::Ipams::NetboxPlugin');
 
@@ -95,6 +96,66 @@ sub add_ip {
     }
 }
 
+sub add_next_freeip {
+    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid, $noerr) = @_;
+
+    my $cidr = $subnet->{cidr};
+
+    my $url = $plugin_config->{url};
+    my $namespace = $plugin_config->{namespace};
+    my $headers = default_headers($plugin_config);
+
+    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
+
+    my $description = "mac:$mac" if $mac;
+
+    my $params = { type => "dhcp", dns_name => $hostname, description => $description, namespace => $namespace, status => default_ip_status() };
+
+    my $ip = eval {
+	my $result = PVE::Network::SDN::api_request("POST", "$url/ipam/prefixes/$internalid/available-ips/", $headers, $params);
+	my ($ip, undef) = split(/\//, $result->{address});
+	return $ip;
+    };
+
+    if ($@) {
+	die "can't find free ip in subnet $cidr: $@" if !$noerr;
+    }
+    return $ip;
+}
+
+sub add_range_next_freeip {
+    my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
+
+    my $url = $plugin_config->{url};
+    my $namespace = $plugin_config->{namespace};
+    my $headers = default_headers($plugin_config);
+    my $cidr = $subnet->{cidr};
+
+    # ranges are not supported natively in nautobot, hence why we have to get a little hacky.
+    my $minimal_size = NetAddr::IP->new($range->{'start-address'}) - NetAddr::IP->new($cidr);
+    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
+
+    my $ip = eval {
+	my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/$internalid/available-ips/?limit=$minimal_size", $headers);
+	# v important for NetAddr::IP comparison!
+	my @ips = map((split(/\//,$_->{address}))[0], @{$result});
+	# get 1st result
+	my $ip = (get_ips_within_range($range->{'start-address'}, $range->{'end-address'}, @ips))[0];
+
+	if ($ip) {
+	    print "found free ip $ip in range $range->{'start-address'}-$range->{'end-address'}\n"
+	} else { die "prefix out of space in range"; }
+
+	$class->add_ip($plugin_config, undef,  $subnet, $ip, $data->{hostname}, $data->{mac}, undef, 0, 0);
+	return $ip;
+    };
+
+    if ($@) {
+	die "can't find free ip in range $range->{'start-address'}-$range->{'end-address'}: $@" if !$noerr;
+    }
+    return $ip;
+}
+
 
 sub update_ip {
     my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
@@ -174,6 +235,13 @@ sub on_update_hook {
 }
 
 # helpers
+sub get_ips_within_range {
+    my ($start_address, $end_address, @list) = @_;
+    $start_address = NetAddr::IP->new($start_address);
+    $end_address = NetAddr::IP->new($end_address);
+    return grep($start_address <= NetAddr::IP->new($_) <= $end_address, @list);
+}
+
 sub get_namespace_id {
     my ($url, $namespace, $headers) = @_;
 
-- 
2.39.5



[-- Attachment #2: Type: text/plain, Size: 160 bytes --]

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

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [pve-devel] SPAM: [PATCH pve-network v2 3/7] ipam: nautobot: add testing for nautobot plugin
       [not found] <20250108121529.5813-1-lou.lecrivain@wdz.de>
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 1/7] ipam: nautobot support initial commit Lou Lecrivain via pve-devel
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 2/7] ipam: nautobot: implement plain prefix allocation Lou Lecrivain via pve-devel
@ 2025-01-08 12:15 ` Lou Lecrivain via pve-devel
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors Lou Lecrivain via pve-devel
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 18+ messages in thread
From: Lou Lecrivain via pve-devel @ 2025-01-08 12:15 UTC (permalink / raw)
  To: pve-devel; +Cc: Lou Lecrivain

[-- Attachment #1: Type: message/rfc822, Size: 13539 bytes --]

From: Lou Lecrivain <lou.lecrivain@wdz.de>
To: pve-devel@lists.proxmox.com
Subject: SPAM: [PATCH pve-network v2 3/7] ipam: nautobot: add testing for nautobot plugin
Date: Wed,  8 Jan 2025 13:15:25 +0100
Message-ID: <20250108121529.5813-4-lou.lecrivain@wdz.de>

Signed-off-by: lou lecrivain <lou.lecrivain@wdz.de>
---
 src/test/ipams/nautobot/expected.add_ip       | 11 +++++++++
 .../ipams/nautobot/expected.add_ip_notgateway | 11 +++++++++
 .../ipams/nautobot/expected.add_next_freeip   | 11 +++++++++
 src/test/ipams/nautobot/expected.add_subnet   | 11 +++++++++
 src/test/ipams/nautobot/expected.del_ip       | 11 +++++++++
 src/test/ipams/nautobot/expected.update_ip    | 11 +++++++++
 src/test/ipams/nautobot/ipam_config           | 24 +++++++++++++++++++
 src/test/ipams/nautobot/sdn_config            | 20 ++++++++++++++++
 src/test/ipams/netbox/ipam_config             |  8 ++++++-
 src/test/ipams/phpipam/ipam_config            |  8 ++++++-
 10 files changed, 124 insertions(+), 2 deletions(-)
 create mode 100644 src/test/ipams/nautobot/expected.add_ip
 create mode 100644 src/test/ipams/nautobot/expected.add_ip_notgateway
 create mode 100644 src/test/ipams/nautobot/expected.add_next_freeip
 create mode 100644 src/test/ipams/nautobot/expected.add_subnet
 create mode 100644 src/test/ipams/nautobot/expected.del_ip
 create mode 100644 src/test/ipams/nautobot/expected.update_ip
 create mode 100644 src/test/ipams/nautobot/ipam_config
 create mode 100644 src/test/ipams/nautobot/sdn_config

diff --git a/src/test/ipams/nautobot/expected.add_ip b/src/test/ipams/nautobot/expected.add_ip
new file mode 100644
index 0000000..60c62d4
--- /dev/null
+++ b/src/test/ipams/nautobot/expected.add_ip
@@ -0,0 +1,11 @@
+bless( {
+                  '_content' => '{"address":"10.0.0.1/24","description":"gateway","dns_name":"myhostname","namespace":"TestNamespace","status":"Active","type":"dhcp"}',
+                  '_headers' => bless( {
+                                         'authorization' => 'token FAKETESTTOKEN',
+                                         'content-type' => 'application/json',
+					 'accept' => 'application/json'
+                                       }, 'HTTP::Headers' ),
+                  '_max_body_size' => undef,
+                  '_method' => 'POST',
+                  '_uri' => bless( do{\(my $o = 'http://localhost:8080/api/ipam/ip-addresses/')}, 'URI::http' )
+                }, 'HTTP::Request' );
diff --git a/src/test/ipams/nautobot/expected.add_ip_notgateway b/src/test/ipams/nautobot/expected.add_ip_notgateway
new file mode 100644
index 0000000..355ccde
--- /dev/null
+++ b/src/test/ipams/nautobot/expected.add_ip_notgateway
@@ -0,0 +1,11 @@
+bless( {
+                  '_content' => '{"address":"10.0.0.1/24","description":"mac:da:65:8f:18:9b:6f","dns_name":"myhostname","namespace":"TestNamespace","status":"Active","type":"dhcp"}',
+                  '_headers' => bless( {
+                                         'authorization' => 'token FAKETESTTOKEN',
+                                         'content-type' => 'application/json',
+					 'accept' => 'application/json'
+                                       }, 'HTTP::Headers' ),
+                  '_max_body_size' => undef,
+                  '_method' => 'POST',
+                  '_uri' => bless( do{\(my $o = 'http://localhost:8080/api/ipam/ip-addresses/')}, 'URI::http' )
+                }, 'HTTP::Request' );
diff --git a/src/test/ipams/nautobot/expected.add_next_freeip b/src/test/ipams/nautobot/expected.add_next_freeip
new file mode 100644
index 0000000..da79a27
--- /dev/null
+++ b/src/test/ipams/nautobot/expected.add_next_freeip
@@ -0,0 +1,11 @@
+bless( {
+                  '_content' => '{"description":"mac:da:65:8f:18:9b:6f","dns_name":"myhostname","namespace":"TestNamespace","status":"Active","type":"dhcp"}',
+                  '_headers' => bless( {
+                                         'authorization' => 'token FAKETESTTOKEN',
+                                         'content-type' => 'application/json',
+					 'accept' => 'application/json'
+                                       }, 'HTTP::Headers' ),
+                  '_max_body_size' => undef,
+                  '_method' => 'POST',
+                  '_uri' => bless( do{\(my $o = 'http://localhost:8080/api/ipam/prefixes/1/available-ips/')}, 'URI::http' )
+                }, 'HTTP::Request' );
diff --git a/src/test/ipams/nautobot/expected.add_subnet b/src/test/ipams/nautobot/expected.add_subnet
new file mode 100644
index 0000000..af4bc8e
--- /dev/null
+++ b/src/test/ipams/nautobot/expected.add_subnet
@@ -0,0 +1,11 @@
+bless({
+	'_content' => '{"namespace":"TestNamespace","prefix":"10.0.0.0/24","status":"Active"}',
+	'_headers' => bless({
+		   'authorization' => 'token FAKETESTTOKEN',
+		   'content-type' => 'application/json',
+		   'accept' => 'application/json',
+	}, 'HTTP::Headers'),
+	'_max_body_size' => undef,
+	'_method' => 'POST',
+	'_uri' => bless(do{\(my $o = 'http://localhost:8080/api/ipam/prefixes/')}, 'URI::http'),
+}, 'HTTP::Request');
diff --git a/src/test/ipams/nautobot/expected.del_ip b/src/test/ipams/nautobot/expected.del_ip
new file mode 100644
index 0000000..f4bc182
--- /dev/null
+++ b/src/test/ipams/nautobot/expected.del_ip
@@ -0,0 +1,11 @@
+bless( {
+                  '_content' => '',
+                  '_headers' => bless( {
+                                         'authorization' => 'token FAKETESTTOKEN',
+                                         'content-type' => 'application/json',
+					 'accept' => 'application/json'
+                                       }, 'HTTP::Headers' ),
+                  '_max_body_size' => undef,
+                  '_method' => 'DELETE',
+                  '_uri' => bless( do{\(my $o = 'http://localhost:8080/api/ipam/ip-addresses/1/')}, 'URI::http' )
+                }, 'HTTP::Request' );
diff --git a/src/test/ipams/nautobot/expected.update_ip b/src/test/ipams/nautobot/expected.update_ip
new file mode 100644
index 0000000..58e0ac6
--- /dev/null
+++ b/src/test/ipams/nautobot/expected.update_ip
@@ -0,0 +1,11 @@
+bless( {
+                  '_content' => '{"address":"10.0.0.1/24","description":"gateway","dns_name":"myhostname","namespace":"TestNamespace","status":"Active","type":"dhcp"}',
+                  '_headers' => bless( {
+                                         'authorization' => 'token FAKETESTTOKEN',
+                                         'content-type' => 'application/json',
+					 'accept' => 'application/json'
+                                       }, 'HTTP::Headers' ),
+                  '_max_body_size' => undef,
+                  '_method' => 'PATCH',
+                  '_uri' => bless( do{\(my $o = 'http://localhost:8080/api/ipam/ip-addresses/1/')}, 'URI::http' )
+                }, 'HTTP::Request' );
diff --git a/src/test/ipams/nautobot/ipam_config b/src/test/ipams/nautobot/ipam_config
new file mode 100644
index 0000000..014d6b1
--- /dev/null
+++ b/src/test/ipams/nautobot/ipam_config
@@ -0,0 +1,24 @@
+{
+          'ids' => {
+                     'phpipam' => {
+                                    'url' => 'https://localhost/api/apiadmin',
+                                    'type' => 'phpipam',
+                                    'section' => 1,
+                                    'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+                                  },
+                     'pve' => {
+                                'type' => 'pve'
+                              },
+                     'netbox' => {
+                                   'token' => '0123456789abcdef0123456789abcdef01234567',
+                                   'type' => 'netbox',
+                                   'url' => 'http://localhost:8000/api'
+                                 },
+		      'nautobot' => {
+				    'url' => 'http://localhost:8080/api',
+				    'type' => 'nautobot',
+				    'token' => 'FAKETESTTOKEN',
+				    'namespace' => 'TestNamespace'
+		      }
+                   },
+}
diff --git a/src/test/ipams/nautobot/sdn_config b/src/test/ipams/nautobot/sdn_config
new file mode 100644
index 0000000..784cd95
--- /dev/null
+++ b/src/test/ipams/nautobot/sdn_config
@@ -0,0 +1,20 @@
+{
+  version => 1,
+  vnets   => {
+               ids => {
+                        myvnet => { type => "vnet", zone => "myzone" },
+                      },
+             },
+
+  zones   => {
+               ids => { myzone => { ipam => "nautobot" } },
+             },
+
+  subnets => {
+              ids => { 'myzone-10.0.0.0-24' => {
+                                                        'type' => 'subnet',
+                                                        'vnet' => 'myvnet',
+                                                  }
+                     }
+             }
+}
diff --git a/src/test/ipams/netbox/ipam_config b/src/test/ipams/netbox/ipam_config
index a33be30..014d6b1 100644
--- a/src/test/ipams/netbox/ipam_config
+++ b/src/test/ipams/netbox/ipam_config
@@ -13,6 +13,12 @@
                                    'token' => '0123456789abcdef0123456789abcdef01234567',
                                    'type' => 'netbox',
                                    'url' => 'http://localhost:8000/api'
-                                 }
+                                 },
+		      'nautobot' => {
+				    'url' => 'http://localhost:8080/api',
+				    'type' => 'nautobot',
+				    'token' => 'FAKETESTTOKEN',
+				    'namespace' => 'TestNamespace'
+		      }
                    },
 }
diff --git a/src/test/ipams/phpipam/ipam_config b/src/test/ipams/phpipam/ipam_config
index a33be30..014d6b1 100644
--- a/src/test/ipams/phpipam/ipam_config
+++ b/src/test/ipams/phpipam/ipam_config
@@ -13,6 +13,12 @@
                                    'token' => '0123456789abcdef0123456789abcdef01234567',
                                    'type' => 'netbox',
                                    'url' => 'http://localhost:8000/api'
-                                 }
+                                 },
+		      'nautobot' => {
+				    'url' => 'http://localhost:8080/api',
+				    'type' => 'nautobot',
+				    'token' => 'FAKETESTTOKEN',
+				    'namespace' => 'TestNamespace'
+		      }
                    },
 }
-- 
2.39.5



[-- Attachment #2: Type: text/plain, Size: 160 bytes --]

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

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [pve-devel] SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors
       [not found] <20250108121529.5813-1-lou.lecrivain@wdz.de>
                   ` (2 preceding siblings ...)
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 3/7] ipam: nautobot: add testing for nautobot plugin Lou Lecrivain via pve-devel
@ 2025-01-08 12:15 ` Lou Lecrivain via pve-devel
  2025-02-19 16:37   ` Hannes Dürr
                     ` (2 more replies)
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 5/7] ipam: nautobot: add checks for prefix deletion Lou Lecrivain via pve-devel
                   ` (3 subsequent siblings)
  7 siblings, 3 replies; 18+ messages in thread
From: Lou Lecrivain via pve-devel @ 2025-01-08 12:15 UTC (permalink / raw)
  To: pve-devel; +Cc: Lou Lecrivain

[-- Attachment #1: Type: message/rfc822, Size: 11008 bytes --]

From: Lou Lecrivain <lou.lecrivain@wdz.de>
To: pve-devel@lists.proxmox.com
Subject: SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors
Date: Wed,  8 Jan 2025 13:15:26 +0100
Message-ID: <20250108121529.5813-5-lou.lecrivain@wdz.de>

added error handling in helpers

Signed-off-by: lou lecrivain <lou.lecrivain@wdz.de>
---
 src/PVE/Network/SDN/Ipams/NautobotPlugin.pm | 126 ++++++++++++++++++--
 1 file changed, 113 insertions(+), 13 deletions(-)

diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
index 22867df..79ac04d 100644
--- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
@@ -7,7 +7,7 @@ use PVE::Cluster;
 use PVE::Tools;
 use NetAddr::IP;
 
-use base('PVE::Network::SDN::Ipams::NetboxPlugin');
+use base('PVE::Network::SDN::Ipams::Plugin');
 
 sub type {
     return 'nautobot';
@@ -51,7 +51,7 @@ sub add_subnet {
     my $namespace = $plugin_config->{namespace};
     my $headers = default_headers($plugin_config);
 
-    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
+    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
 
     #create subnet
     if (!$internalid) {
@@ -66,6 +66,27 @@ sub add_subnet {
     }
 }
 
+sub del_subnet {
+    my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
+
+    my $cidr = $subnet->{cidr};
+    my $url = $plugin_config->{url};
+    my $headers = default_headers($plugin_config);
+
+    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
+    return if !$internalid;
+
+    # TODO check that prefix is empty before deletion
+    return;
+
+    eval {
+	PVE::Network::SDN::api_request("DELETE", "$url/ipam/prefixes/$internalid/", $headers);
+    };
+    if ($@) {
+	die "error deleting subnet in Nautobot: $@" if !$noerr;
+    }
+}
+
 sub add_ip {
     my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
 
@@ -89,7 +110,7 @@ sub add_ip {
 
     if ($@) {
 	if($is_gateway) {
-	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !PVE::Network::SDN::Ipams::NetboxPlugin::is_ip_gateway($url, $ip, $headers) && !$noerr;
+	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !$noerr && !is_ip_gateway($url, $ip, $headers, $noerr);
 	} else {
 	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !$noerr;
 	}
@@ -105,7 +126,8 @@ sub add_next_freeip {
     my $namespace = $plugin_config->{namespace};
     my $headers = default_headers($plugin_config);
 
-    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
+    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
+    die "cannot find prefix $cidr in Nautobot" if !$internalid;
 
     my $description = "mac:$mac" if $mac;
 
@@ -133,7 +155,7 @@ sub add_range_next_freeip {
 
     # ranges are not supported natively in nautobot, hence why we have to get a little hacky.
     my $minimal_size = NetAddr::IP->new($range->{'start-address'}) - NetAddr::IP->new($cidr);
-    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
+    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
 
     my $ip = eval {
 	my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/$internalid/available-ips/?limit=$minimal_size", $headers);
@@ -174,8 +196,8 @@ sub update_ip {
 
     my $params = { address => "$ip/$mask", type => "dhcp", dns_name => $hostname, description => $description, namespace => $namespace, status => default_ip_status()};
 
-    my $ip_id = PVE::Network::SDN::Ipams::NetboxPlugin::get_ip_id($url, $ip, $headers);
-    die "can't find ip $ip in ipam" if !$ip_id;
+    my $ip_id = get_ip_id($url, $ip, $headers, $noerr);
+    die "can't find ip $ip in ipam" if !$noerr && !$ip_id;
 
     eval {
 	PVE::Network::SDN::api_request("PATCH", "$url/ipam/ip-addresses/$ip_id/", $headers, $params);
@@ -186,6 +208,26 @@ sub update_ip {
 }
 
 
+sub del_ip {
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_;
+
+    return if !$ip;
+
+    my $url = $plugin_config->{url};
+    my $headers = default_headers($plugin_config);
+
+    my $ip_id = get_ip_id($url, $ip, $headers, $noerr);
+    die "can't find ip $ip in ipam" if !$ip_id && !$noerr;
+
+    eval {
+	PVE::Network::SDN::api_request("DELETE", "$url/ipam/ip-addresses/$ip_id/", $headers);
+    };
+    if ($@) {
+	die "error deleting ip $ip : $@" if !$noerr;
+    }
+}
+
+
 sub verify_api {
     my ($class, $plugin_config) = @_;
 
@@ -196,8 +238,8 @@ sub verify_api {
     # check that the namespace exists AND that default IP active status
     # exists AND that we have indeed API access
     eval {
-	get_namespace_id($url, $namespace, $headers) // die "namespace $namespace does not exist";
-	get_status_id($url, default_ip_status(), $headers) // die "default IP status ". default_ip_status() . " not found";
+	get_namespace_id($url, $namespace, $headers, 0) // die "namespace $namespace does not exist";
+	get_status_id($url, default_ip_status(), $headers, 0) // die "default IP status ". default_ip_status() . " not found";
     };
     if ($@) {
 	die "Can't use nautobot api: $@";
@@ -242,22 +284,80 @@ sub get_ips_within_range {
     return grep($start_address <= NetAddr::IP->new($_) <= $end_address, @list);
 }
 
+sub get_ip_id {
+    my ($url, $ip, $headers, $noerr) = @_;
+
+    my $result = eval {
+	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers);
+    };
+    if ($@) {
+	die "error while querying for ip $ip id: $@" if !$noerr;
+    }
+
+    my $data = @{$result->{results}}[0];
+    my $ip_id = $data->{id};
+    return $ip_id;
+}
+
+sub get_prefix_id {
+    my ($url, $cidr, $headers, $noerr) = @_;
+
+    my $result = eval {
+	return PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/?q=$cidr", $headers);
+    };
+    if ($@) {
+	die "error while querying for cidr $cidr prefix id: $@" if !$noerr;
+    }
+
+    my $data = @{$result->{results}}[0];
+    my $internalid = $data->{id};
+    return $internalid;
+}
+
 sub get_namespace_id {
-    my ($url, $namespace, $headers) = @_;
+    my ($url, $namespace, $headers, $noerr) = @_;
+
+    my $result = eval {
+	return PVE::Network::SDN::api_request("GET", "$url/ipam/namespaces/?q=$namespace", $headers);
+    };
+    if ($@) {
+	die "error while querying for namespace $namespace id: $@" if !$noerr;
+    }
 
-    my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/namespaces/?q=$namespace", $headers);
     my $data = @{$result->{results}}[0];
     my $internalid = $data->{id};
     return $internalid;
 }
 
 sub get_status_id {
-    my ($url, $status, $headers) = @_;
+    my ($url, $status, $headers, $noerr) = @_;
+
+    my $result = eval {
+	return PVE::Network::SDN::api_request("GET", "$url/extras/statuses/?q=$status", $headers);
+    };
+    if ($@) {
+	die "error while querying for status $status id: $@" if !$noerr;
+    }
 
-    my $result = PVE::Network::SDN::api_request("GET", "$url/extras/statuses/?q=$status", $headers);
     my $data = @{$result->{results}}[0];
     my $internalid = $data->{id};
     return $internalid;
 }
 
+sub is_ip_gateway {
+    my ($url, $ip, $headers, $noerr) = @_;
+
+    my $result = eval {
+	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers);
+    };
+    if ($@) {
+	die "error while checking if $ip is a gateway" if !$noerr;
+    }
+
+    my $data = @{$result->{results}}[0];
+    my $description = $data->{description};
+    my $is_gateway = 1 if $description eq 'gateway';
+    return $is_gateway;
+}
+
 1;
-- 
2.39.5



[-- Attachment #2: Type: text/plain, Size: 160 bytes --]

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

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [pve-devel] SPAM: [PATCH pve-network v2 5/7] ipam: nautobot: add checks for prefix deletion
       [not found] <20250108121529.5813-1-lou.lecrivain@wdz.de>
                   ` (3 preceding siblings ...)
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors Lou Lecrivain via pve-devel
@ 2025-01-08 12:15 ` Lou Lecrivain via pve-devel
  2025-02-19 16:37   ` Hannes Dürr
                     ` (2 more replies)
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 6/7] ipam: nautobot: add documentation Lou Lecrivain via pve-devel
                   ` (2 subsequent siblings)
  7 siblings, 3 replies; 18+ messages in thread
From: Lou Lecrivain via pve-devel @ 2025-01-08 12:15 UTC (permalink / raw)
  To: pve-devel; +Cc: Lou Lecrivain

[-- Attachment #1: Type: message/rfc822, Size: 6392 bytes --]

From: Lou Lecrivain <lou.lecrivain@wdz.de>
To: pve-devel@lists.proxmox.com
Subject: SPAM: [PATCH pve-network v2 5/7] ipam: nautobot: add checks for prefix deletion
Date: Wed,  8 Jan 2025 13:15:27 +0100
Message-ID: <20250108121529.5813-6-lou.lecrivain@wdz.de>

check that prefix/subnet is empty (only gateway IPs should remain)
before deletion.

Signed-off-by: lou lecrivain <lou.lecrivain@wdz.de>
---
 src/PVE/Network/SDN/Ipams/NautobotPlugin.pm | 60 ++++++++++++++++++++-
 1 file changed, 58 insertions(+), 2 deletions(-)

diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
index 79ac04d..f736bad 100644
--- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
@@ -5,6 +5,7 @@ use warnings;
 use PVE::INotify;
 use PVE::Cluster;
 use PVE::Tools;
+use List::Util qw(all);
 use NetAddr::IP;
 
 use base('PVE::Network::SDN::Ipams::Plugin');
@@ -76,8 +77,11 @@ sub del_subnet {
     my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
     return if !$internalid;
 
-    # TODO check that prefix is empty before deletion
-    return;
+    if (!subnet_is_deletable($class, $plugin_config, $subnetid, $subnet, $internalid, $noerr)) {
+	die "cannot delete prefix $cidr, not empty!";
+    }
+
+    empty_subnet($class, $plugin_config, $subnetid, $subnet, $internalid, $noerr);
 
     eval {
 	PVE::Network::SDN::api_request("DELETE", "$url/ipam/prefixes/$internalid/", $headers);
@@ -227,6 +231,58 @@ sub del_ip {
     }
 }
 
+sub empty_subnet {
+    my ($class, $plugin_config, $subnetid, $subnet, $subnetuuid, $noerr) = @_;
+
+    my $url = $plugin_config->{url};
+    my $namespace = $plugin_config->{namespace};
+    my $headers = default_headers($plugin_config);
+
+    my $response = eval {
+	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?namespace=$namespace&parent=$subnetuuid", $headers)
+    };
+    if ($@) {
+	die "error querying prefix $subnet: $@" if !$noerr;
+    }
+
+    for my $ip (@{$response->{results}}) {
+	del_ip($class, $plugin_config, $subnetid, $subnet, $ip->{host}, $noerr);
+    }
+}
+
+sub subnet_is_deletable {
+    my ($class, $plugin_config, $subnetid, $subnet, $subnetuuid, $noerr) = @_;
+
+    my $url = $plugin_config->{url};
+    my $namespace = $plugin_config->{namespace};
+    my $headers = default_headers($plugin_config);
+
+
+    my $response = eval {
+	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?namespace=$namespace&parent=$subnetuuid", $headers)
+    };
+    if ($@) {
+	die "error querying prefix $subnet: $@" if !$noerr;
+    }
+    my $n_ips = scalar $response->{results}->@*;
+
+    # least costly check operation 1st
+    if ($n_ips == 0) {
+	# completely empty, delete ok
+	return 1;
+    } elsif (
+	!(all {$_ == 1} (
+	    map {
+		is_ip_gateway($url, $_->{host}, $headers, $noerr)
+	    } $response->{results}->@*
+	))) {
+	# some remaining IPs are not gateway, nok
+	return 0;
+    } else {
+	# remaining IPs are all gateway, delete ok
+	return 1;
+    }
+}
 
 sub verify_api {
     my ($class, $plugin_config) = @_;
-- 
2.39.5



[-- Attachment #2: Type: text/plain, Size: 160 bytes --]

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

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [pve-devel] SPAM: [PATCH pve-network v2 6/7] ipam: nautobot: add documentation
       [not found] <20250108121529.5813-1-lou.lecrivain@wdz.de>
                   ` (4 preceding siblings ...)
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 5/7] ipam: nautobot: add checks for prefix deletion Lou Lecrivain via pve-devel
@ 2025-01-08 12:15 ` Lou Lecrivain via pve-devel
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 7/7] ipam: nautobot: systematically use namespace Lou Lecrivain via pve-devel
       [not found] ` <20250108121529.5813-3-lou.lecrivain@wdz.de>
  7 siblings, 0 replies; 18+ messages in thread
From: Lou Lecrivain via pve-devel @ 2025-01-08 12:15 UTC (permalink / raw)
  To: pve-devel; +Cc: Lou Lecrivain

[-- Attachment #1: Type: message/rfc822, Size: 4976 bytes --]

From: Lou Lecrivain <lou.lecrivain@wdz.de>
To: pve-devel@lists.proxmox.com
Subject: SPAM: [PATCH pve-network v2 6/7] ipam: nautobot: add documentation
Date: Wed,  8 Jan 2025 13:15:28 +0100
Message-ID: <20250108121529.5813-7-lou.lecrivain@wdz.de>

Signed-off-by: lou lecrivain <lou.lecrivain@wdz.de>
---
 src/PVE/Network/SDN/Ipams/NautobotPlugin.pm | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
index f736bad..3d60265 100644
--- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
@@ -54,7 +54,7 @@ sub add_subnet {
 
     my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
 
-    #create subnet
+    #create subnet if it doesn't already exists
     if (!$internalid) {
 	my $params = { prefix => $cidr, namespace => $namespace, status => default_ip_status()};
 
@@ -81,6 +81,7 @@ sub del_subnet {
 	die "cannot delete prefix $cidr, not empty!";
     }
 
+    # delete associated IP addresses (normally should only be gateway IPs)
     empty_subnet($class, $plugin_config, $subnetid, $subnet, $internalid, $noerr);
 
     eval {
@@ -163,7 +164,7 @@ sub add_range_next_freeip {
 
     my $ip = eval {
 	my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/$internalid/available-ips/?limit=$minimal_size", $headers);
-	# v important for NetAddr::IP comparison!
+	# v important for NetAddr::IP comparison! (otherwise we would be comparing subnets)
 	my @ips = map((split(/\//,$_->{address}))[0], @{$result});
 	# get 1st result
 	my $ip = (get_ips_within_range($range->{'start-address'}, $range->{'end-address'}, @ips))[0];
-- 
2.39.5



[-- Attachment #2: Type: text/plain, Size: 160 bytes --]

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

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [pve-devel] SPAM: [PATCH pve-network v2 7/7] ipam: nautobot: systematically use namespace
       [not found] <20250108121529.5813-1-lou.lecrivain@wdz.de>
                   ` (5 preceding siblings ...)
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 6/7] ipam: nautobot: add documentation Lou Lecrivain via pve-devel
@ 2025-01-08 12:15 ` Lou Lecrivain via pve-devel
       [not found] ` <20250108121529.5813-3-lou.lecrivain@wdz.de>
  7 siblings, 0 replies; 18+ messages in thread
From: Lou Lecrivain via pve-devel @ 2025-01-08 12:15 UTC (permalink / raw)
  To: pve-devel; +Cc: Lou Lecrivain

[-- Attachment #1: Type: message/rfc822, Size: 8865 bytes --]

From: Lou Lecrivain <lou.lecrivain@wdz.de>
To: pve-devel@lists.proxmox.com
Subject: SPAM: [PATCH pve-network v2 7/7] ipam: nautobot: systematically use namespace
Date: Wed,  8 Jan 2025 13:15:29 +0100
Message-ID: <20250108121529.5813-8-lou.lecrivain@wdz.de>

this is needed in order to not accidentally use another
subnet or IP which might be in another namespace.

Signed-off-by: lou lecrivain <lou.lecrivain@wdz.de>
---
 src/PVE/Network/SDN/Ipams/NautobotPlugin.pm | 40 +++++++++++++--------
 1 file changed, 26 insertions(+), 14 deletions(-)

diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
index 3d60265..f69119e 100644
--- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
@@ -52,7 +52,7 @@ sub add_subnet {
     my $namespace = $plugin_config->{namespace};
     my $headers = default_headers($plugin_config);
 
-    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
+    my $internalid = get_prefix_id($plugin_config, $cidr, $noerr);
 
     #create subnet if it doesn't already exists
     if (!$internalid) {
@@ -74,7 +74,7 @@ sub del_subnet {
     my $url = $plugin_config->{url};
     my $headers = default_headers($plugin_config);
 
-    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
+    my $internalid = get_prefix_id($plugin_config, $cidr, $noerr);
     return if !$internalid;
 
     if (!subnet_is_deletable($class, $plugin_config, $subnetid, $subnet, $internalid, $noerr)) {
@@ -115,7 +115,7 @@ sub add_ip {
 
     if ($@) {
 	if($is_gateway) {
-	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !$noerr && !is_ip_gateway($url, $ip, $headers, $noerr);
+	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !$noerr && !is_ip_gateway($plugin_config, $ip, $noerr);
 	} else {
 	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !$noerr;
 	}
@@ -131,7 +131,7 @@ sub add_next_freeip {
     my $namespace = $plugin_config->{namespace};
     my $headers = default_headers($plugin_config);
 
-    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
+    my $internalid = get_prefix_id($plugin_config, $cidr, $noerr);
     die "cannot find prefix $cidr in Nautobot" if !$internalid;
 
     my $description = "mac:$mac" if $mac;
@@ -160,7 +160,7 @@ sub add_range_next_freeip {
 
     # ranges are not supported natively in nautobot, hence why we have to get a little hacky.
     my $minimal_size = NetAddr::IP->new($range->{'start-address'}) - NetAddr::IP->new($cidr);
-    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
+    my $internalid = get_prefix_id($plugin_config, $cidr, $noerr);
 
     my $ip = eval {
 	my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/$internalid/available-ips/?limit=$minimal_size", $headers);
@@ -201,7 +201,7 @@ sub update_ip {
 
     my $params = { address => "$ip/$mask", type => "dhcp", dns_name => $hostname, description => $description, namespace => $namespace, status => default_ip_status()};
 
-    my $ip_id = get_ip_id($url, $ip, $headers, $noerr);
+    my $ip_id = get_ip_id($plugin_config, $ip, $noerr);
     die "can't find ip $ip in ipam" if !$noerr && !$ip_id;
 
     eval {
@@ -221,7 +221,7 @@ sub del_ip {
     my $url = $plugin_config->{url};
     my $headers = default_headers($plugin_config);
 
-    my $ip_id = get_ip_id($url, $ip, $headers, $noerr);
+    my $ip_id = get_ip_id($plugin_config, $ip, $noerr);
     die "can't find ip $ip in ipam" if !$ip_id && !$noerr;
 
     eval {
@@ -274,7 +274,7 @@ sub subnet_is_deletable {
     } elsif (
 	!(all {$_ == 1} (
 	    map {
-		is_ip_gateway($url, $_->{host}, $headers, $noerr)
+		is_ip_gateway($plugin_config, $_->{host}, $noerr)
 	    } $response->{results}->@*
 	))) {
 	# some remaining IPs are not gateway, nok
@@ -342,10 +342,14 @@ sub get_ips_within_range {
 }
 
 sub get_ip_id {
-    my ($url, $ip, $headers, $noerr) = @_;
+    my ($plugin_config, $ip, $noerr) = @_;
+
+    my $url = $plugin_config->{url};
+    my $namespace = $plugin_config->{namespace};
+    my $headers = default_headers($plugin_config);
 
     my $result = eval {
-	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers);
+	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip&namespace=$namespace", $headers);
     };
     if ($@) {
 	die "error while querying for ip $ip id: $@" if !$noerr;
@@ -357,10 +361,14 @@ sub get_ip_id {
 }
 
 sub get_prefix_id {
-    my ($url, $cidr, $headers, $noerr) = @_;
+    my ($plugin_config, $cidr, $noerr) = @_;
+
+    my $url = $plugin_config->{url};
+    my $namespace = $plugin_config->{namespace};
+    my $headers = default_headers($plugin_config);
 
     my $result = eval {
-	return PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/?q=$cidr", $headers);
+	return PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/?q=$cidr&namespace=$namespace", $headers);
     };
     if ($@) {
 	die "error while querying for cidr $cidr prefix id: $@" if !$noerr;
@@ -402,10 +410,14 @@ sub get_status_id {
 }
 
 sub is_ip_gateway {
-    my ($url, $ip, $headers, $noerr) = @_;
+    my ($plugin_config, $ip, $noerr) = @_;
+
+    my $url = $plugin_config->{url};
+    my $namespace = $plugin_config->{namespace};
+    my $headers = default_headers($plugin_config);
 
     my $result = eval {
-	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers);
+	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip&namespace=$namespace", $headers);
     };
     if ($@) {
 	die "error while checking if $ip is a gateway" if !$noerr;
-- 
2.39.5



[-- Attachment #2: Type: text/plain, Size: 160 bytes --]

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

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [pve-devel] SPAM: [PATCH pve-network v2 1/7] ipam: nautobot support initial commit
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 1/7] ipam: nautobot support initial commit Lou Lecrivain via pve-devel
@ 2025-02-19 16:36   ` Hannes Dürr
  0 siblings, 0 replies; 18+ messages in thread
From: Hannes Dürr @ 2025-02-19 16:36 UTC (permalink / raw)
  To: Proxmox VE development discussion

On 1/8/25 13:15, Lou Lecrivain via pve-devel wrote:

> +use base('PVE::Network::SDN::Ipams::NetboxPlugin'); why do you base the Plugin in NetboxPlugin at the beginning?> +sub 
get_ips_from_mac { > + my ($class, $plugin_config, $mac, $zoneid) = @_; 
$zoneid is unused in the function, this looks like a copy paste mistake

On 1/8/25 13:15, Lou Lecrivain via pve-devel wrote:
> This is the initial Nautobot plugin, based on the Netbox plugin 
> implementation. Signed-off-by: lou lecrivain <lou.lecrivain@wdz.de>--- 
> src/PVE/API2/Network/SDN/Ipams.pm | 1 + src/PVE/Network/SDN/Ipams.pm | 
> 3 + src/PVE/Network/SDN/Ipams/Makefile | 2 +- 
> src/PVE/Network/SDN/Ipams/NautobotPlugin.pm | 195 ++++++++++++++++++++ 
> 4 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 
> src/PVE/Network/SDN/Ipams/NautobotPlugin.pm diff --git 
> a/src/PVE/API2/Network/SDN/Ipams.pm 
> b/src/PVE/API2/Network/SDN/Ipams.pm index 27ead02..8074512 100644 --- 
> a/src/PVE/API2/Network/SDN/Ipams.pm +++ 
> b/src/PVE/API2/Network/SDN/Ipams.pm @@ -12,6 +12,7 @@ use 
> PVE::Network::SDN::Ipams::Plugin; use 
> PVE::Network::SDN::Ipams::PVEPlugin; use 
> PVE::Network::SDN::Ipams::PhpIpamPlugin; use 
> PVE::Network::SDN::Ipams::NetboxPlugin; +use 
> PVE::Network::SDN::Ipams::NautobotPlugin; use PVE::Network::SDN::Dhcp; 
> use PVE::Network::SDN::Vnets; use PVE::Network::SDN::Zones; diff --git 
> a/src/PVE/Network/SDN/Ipams.pm b/src/PVE/Network/SDN/Ipams.pm index 
> c689b8f..2ecb75e 100644 --- a/src/PVE/Network/SDN/Ipams.pm +++ 
> b/src/PVE/Network/SDN/Ipams.pm @@ -12,11 +12,14 @@ use PVE::Network; 
> use PVE::Network::SDN::Ipams::PVEPlugin; use 
> PVE::Network::SDN::Ipams::NetboxPlugin; +use 
> PVE::Network::SDN::Ipams::NautobotPlugin; use 
> PVE::Network::SDN::Ipams::PhpIpamPlugin; use 
> PVE::Network::SDN::Ipams::Plugin; + 
> PVE::Network::SDN::Ipams::PVEPlugin->register(); 
> PVE::Network::SDN::Ipams::NetboxPlugin->register(); 
> +PVE::Network::SDN::Ipams::NautobotPlugin->register(); 
> PVE::Network::SDN::Ipams::PhpIpamPlugin->register(); 
> PVE::Network::SDN::Ipams::Plugin->init(); diff --git 
> a/src/PVE/Network/SDN/Ipams/Makefile 
> b/src/PVE/Network/SDN/Ipams/Makefile index 4e7d65f..75e5b9a 100644 --- 
> a/src/PVE/Network/SDN/Ipams/Makefile +++ 
> b/src/PVE/Network/SDN/Ipams/Makefile @@ -1,4 +1,4 @@ 
> -SOURCES=Plugin.pm PhpIpamPlugin.pm NetboxPlugin.pm PVEPlugin.pm 
> +SOURCES=Plugin.pm PhpIpamPlugin.pm NetboxPlugin.pm PVEPlugin.pm 
> NautobotPlugin.pm PERL5DIR=${DESTDIR}/usr/share/perl5 diff --git 
> a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm 
> b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm new file mode 100644 
> index 0000000..69e7897 --- /dev/null +++ 
> b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm @@ -0,0 +1,195 @@ 
> +package PVE::Network::SDN::Ipams::NautobotPlugin; + +use strict; +use 
> warnings; +use PVE::INotify; +use PVE::Cluster; +use PVE::Tools; + 
> +use base('PVE::Network::SDN::Ipams::NetboxPlugin');

why do you base the Plugin in NetboxPlugin at the beginning?

> + +sub type { + return 'nautobot'; +} + +sub properties { + return { + 
> namespace => { + type => 'string', + }, + }; +} + +sub options { + 
> return { + url => { optional => 0 }, + token => { optional => 0 }, + 
> namespace => { optional => 0 }, + }; +} + +sub default_ip_status { + 
> return 'Active'; +} + +sub default_headers { + my ($plugin_config) = 
> @_; + my $token = $plugin_config->{token}; + + return ['Content-Type' 
> => "application/json", 'Authorization' => "token $token", 'Accept' => 
> "application/json"]; +} + +# implem + +sub add_subnet { + my ($class, 
> $plugin_config, $subnetid, $subnet, $noerr) = @_;
$subnetid is unused
> + + my $cidr = $subnet->{cidr}; + my $gateway = $subnet->{gateway}; + 
> my $url = $plugin_config->{url}; + my $namespace = 
> $plugin_config->{namespace}; + my $headers = 
> default_headers($plugin_config); + + my $internalid = 
> PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, 
> $headers); + + #create subnet + if (!$internalid) { + my $params = { 
> prefix => $cidr, namespace => $namespace, status => 
> default_ip_status()}; + + eval { + my $result = 
> PVE::Network::SDN::api_request("POST", "$url/ipam/prefixes/", 
> $headers, $params); + }; + if ($@) { + die "error adding subnet to 
> ipam: $@" if !$noerr; + } + } +} + +sub add_ip { + my ($class, 
> $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, 
> $is_gateway, $noerr) = @_;
$subnetid and $vmid are unused
> + + my $mask = $subnet->{mask}; + my $url = $plugin_config->{url}; + 
> my $namespace = $plugin_config->{namespace}; + my $headers = 
> default_headers($plugin_config); + + my $description = undef; + if 
> ($is_gateway) { + $description = 'gateway' + } elsif ($mac) { + 
> $description = "mac:$mac"; + } + + my $params = { address => 
> "$ip/$mask", type => "dhcp", dns_name => $hostname, description => 
> $description, namespace => $namespace, status => default_ip_status()};
please break the line
> + + eval { + PVE::Network::SDN::api_request("POST", 
> "$url/ipam/ip-addresses/", $headers, $params); + }; + + if ($@) { + 
> if($is_gateway) { + die "error adding subnet ip to ipam: ip $ip 
> already exists: $@" if 
> !PVE::Network::SDN::Ipams::NetboxPlugin::is_ip_gateway($url, $ip, 
> $headers) && !$noerr; + } else { + die "error adding subnet ip to 
> ipam: ip $ip already exists: $@" if !$noerr; + } + } +} + + +sub 
> update_ip { + my ($class, $plugin_config, $subnetid, $subnet, $ip, 
> $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
$vmid is unused
> + + my $mask = $subnet->{mask}; + my $url = $plugin_config->{url}; + 
> my $namespace = $plugin_config->{namespace}; + my $headers = 
> default_headers($plugin_config); + + my $description = undef; + if 
> ($is_gateway) { + $description = 'gateway' + } elsif ($mac) { + 
> $description = "mac:$mac"; + } + + my $params = { address => 
> "$ip/$mask", type => "dhcp", dns_name => $hostname, description => 
> $description, namespace => $namespace, status => default_ip_status()}; 
> + + my $ip_id = 
> PVE::Network::SDN::Ipams::NetboxPlugin::get_ip_id($url, $ip, 
> $headers); + die "can't find ip $ip in ipam" if !$ip_id; + + eval { + 
> PVE::Network::SDN::api_request("PATCH", 
> "$url/ipam/ip-addresses/$ip_id/", $headers, $params); + }; + if ($@) { 
> + die "error updating ip $ip: $@" if !$noerr; + } +} + + +sub 
> verify_api { + my ($class, $plugin_config) = @_;
$class is unused
> + + my $url = $plugin_config->{url}; + my $namespace = 
> $plugin_config->{namespace}; + my $headers = 
> default_headers($plugin_config); + + # check that the namespace exists 
> AND that default IP active status + # exists AND that we have indeed 
> API access + eval { + get_namespace_id($url, $namespace, $headers) // 
> die "namespace $namespace does not exist"; + get_status_id($url, 
> default_ip_status(), $headers) // die "default IP status ". 
> default_ip_status() . " not found"; + }; + if ($@) { + die "Can't use 
> nautobot api: $@"; + } +} + +sub get_ips_from_mac { + my ($class, 
> $plugin_config, $mac, $zoneid) = @_;
$zoneid is unused in the function, this looks like a copy paste mistake
> + + my $url = $plugin_config->{url}; + my $namespace = 
> $plugin_config->{namespace}; + my $headers = 
> default_headers($plugin_config); + + my $ip4 = undef; + my $ip6 = 
> undef; + + my $data = PVE::Network::SDN::api_request("GET", 
> "$url/ipam/ip-addresses/?q=$mac", $headers); + for my $ip 
> (@{$data->{results}}) { + if ($ip->{ip_version} == 4 && !$ip4) { + 
> ($ip4, undef) = split(/\//, $ip->{address}); + } + + if 
> ($ip->{ip_version} == 6 && !$ip6) { + ($ip6, undef) = split(/\//, 
> $ip->{address}); + } + } + + return ($ip4, $ip6); +} + +sub 
> on_update_hook { + my ($class, $plugin_config) = @_; + + 
> PVE::Network::SDN::Ipams::NautobotPlugin::verify_api($class, 
> $plugin_config); +} + +# helpers +sub get_namespace_id { + my ($url, 
> $namespace, $headers) = @_; + + my $result = 
> PVE::Network::SDN::api_request("GET", 
> "$url/ipam/namespaces/?q=$namespace", $headers); + my $data = 
> @{$result->{results}}[0]; + my $internalid = $data->{id}; + return 
> $internalid; +} + +sub get_status_id { + my ($url, $status, $headers) 
> = @_; + + my $result = PVE::Network::SDN::api_request("GET", 
> "$url/extras/statuses/?q=$status", $headers); + my $data = 
> @{$result->{results}}[0]; + my $internalid = $data->{id}; + return 
> $internalid; +} + +1;
> -- 2.39.5 ______________________________________________
> pve-devel mailing list 
> pve-devel@lists.proxmox.comhttps://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


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


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [pve-devel] SPAM: [PATCH pve-network v2 2/7] ipam: nautobot: implement plain prefix allocation
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 2/7] ipam: nautobot: implement plain prefix allocation Lou Lecrivain via pve-devel
@ 2025-02-19 16:37   ` Hannes Dürr
  2025-02-20 13:24   ` Hannes Dürr
  1 sibling, 0 replies; 18+ messages in thread
From: Hannes Dürr @ 2025-02-19 16:37 UTC (permalink / raw)
  To: Proxmox VE development discussion


On 1/8/25 13:15, Lou Lecrivain via pve-devel wrote:
>
>
> add support for subnet allocation without ranges, where it was 
> previously not supported. Signed-off-by: lou lecrivain 
> <lou.lecrivain@wdz.de>--- src/PVE/Network/SDN/Ipams/NautobotPlugin.pm 
> | 68 +++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git 
> a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm 
> b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm index 69e7897..22867df 
> 100644 --- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm +++ 
> b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm @@ -5,6 +5,7 @@ use 
> warnings; use PVE::INotify; use PVE::Cluster; use PVE::Tools; +use 
> NetAddr::IP; use base('PVE::Network::SDN::Ipams::NetboxPlugin'); @@ 
> -95,6 +96,66 @@ sub add_ip { } } +sub add_next_freeip { + my ($class, 
> $plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid, $noerr) = @_;
$subnetid and $vmid are unused
> + + my $cidr = $subnet->{cidr}; + + my $url = $plugin_config->{url}; + 
> my $namespace = $plugin_config->{namespace}; + my $headers = 
> default_headers($plugin_config); + + my $internalid = 
> PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, 
> $headers); + + my $description = "mac:$mac" if $mac; + + my $params = 
> { type => "dhcp", dns_name => $hostname, description => $description, 
> namespace => $namespace, status => default_ip_status() };
please break the line
> + + my $ip = eval { + my $result = 
> PVE::Network::SDN::api_request("POST", 
> "$url/ipam/prefixes/$internalid/available-ips/", $headers, $params); + 
> my ($ip, undef) = split(/\//, $result->{address}); + return $ip; + }; 
> + + if ($@) { + die "can't find free ip in subnet $cidr: $@" if 
> !$noerr; + } + return $ip; +} + +sub add_range_next_freeip { + my 
> ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_; + + my 
> $url = $plugin_config->{url}; + my $namespace = 
> $plugin_config->{namespace}; + my $headers = 
> default_headers($plugin_config); + my $cidr = $subnet->{cidr}; + + # 
> ranges are not supported natively in nautobot, hence why we have to 
> get a little hacky. + my $minimal_size = 
> NetAddr::IP->new($range->{'start-address'}) - NetAddr::IP->new($cidr); 
> + my $internalid = 
> PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, 
> $headers); + + my $ip = eval { + my $result = 
> PVE::Network::SDN::api_request("GET", 
> "$url/ipam/prefixes/$internalid/available-ips/?limit=$minimal_size", 
> $headers); + # v important for NetAddr::IP comparison! + my @ips = 
> map((split(/\//,$_->{address}))[0], @{$result}); + # get 1st result + 
> my $ip = (get_ips_within_range($range->{'start-address'}, 
> $range->{'end-address'}, @ips))[0]; + + if ($ip) { + print "found free 
> ip $ip in range $range->{'start-address'}-$range->{'end-address'}\n"
prints are not shown anywhere in the GUI?
> + } else { die "prefix out of space in range"; } + + 
> $class->add_ip($plugin_config, undef, $subnet, $ip, $data->{hostname}, 
> $data->{mac}, undef, 0, 0); + return $ip; + }; + + if ($@) { + die 
> "can't find free ip in range 
> $range->{'start-address'}-$range->{'end-address'}: $@" if !$noerr; + } 
> + return $ip; +} + sub update_ip { my ($class, $plugin_config, 
> $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) 
> = @_; @@ -174,6 +235,13 @@ sub on_update_hook { } # helpers +sub 
> get_ips_within_range { + my ($start_address, $end_address, @list) = 
> @_; + $start_address = NetAddr::IP->new($start_address); + 
> $end_address = NetAddr::IP->new($end_address); + return 
> grep($start_address <= NetAddr::IP->new($_) <= $end_address, @list); 
> +} + sub get_namespace_id { my ($url, $namespace, $headers) = @_;
> -- 2.39.5
>
> _______________________________________________ pve-devel mailing list 
> pve-devel@lists.proxmox.comhttps://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


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


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [pve-devel] SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors Lou Lecrivain via pve-devel
@ 2025-02-19 16:37   ` Hannes Dürr
  2025-02-19 17:18   ` Hannes Dürr
  2025-02-20 13:27   ` Hannes Dürr
  2 siblings, 0 replies; 18+ messages in thread
From: Hannes Dürr @ 2025-02-19 16:37 UTC (permalink / raw)
  To: Proxmox VE development discussion


On 1/8/25 13:15, Lou Lecrivain via pve-devel wrote:
>
> added error handling in helpers Signed-off-by: lou lecrivain 
> <lou.lecrivain@wdz.de>--- src/PVE/Network/SDN/Ipams/NautobotPlugin.pm 
> | 126 ++++++++++++++++++-- 1 file changed, 113 insertions(+), 13 
> deletions(-) diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm 
> b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm index 22867df..79ac04d 
> 100644 --- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm +++ 
> b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm @@ -7,7 +7,7 @@ use 
> PVE::Cluster; use PVE::Tools; use NetAddr::IP; -use 
> base('PVE::Network::SDN::Ipams::NetboxPlugin'); +use 
> base('PVE::Network::SDN::Ipams::Plugin'); sub type { return 
> 'nautobot'; @@ -51,7 +51,7 @@ sub add_subnet { my $namespace = 
> $plugin_config->{namespace}; my $headers = 
> default_headers($plugin_config); - my $internalid = 
> PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, 
> $headers); + my $internalid = get_prefix_id($url, $cidr, $headers, 
> $noerr); #create subnet if (!$internalid) { @@ -66,6 +66,27 @@ sub 
> add_subnet { } } +sub del_subnet { + my ($class, $plugin_config, 
> $subnetid, $subnet, $noerr) = @_; + + my $cidr = $subnet->{cidr}; + my 
> $url = $plugin_config->{url}; + my $headers = 
> default_headers($plugin_config); + + my $internalid = 
> get_prefix_id($url, $cidr, $headers, $noerr); + return if 
> !$internalid; + + # TODO check that prefix is empty before deletion + 
> return; + + eval { + PVE::Network::SDN::api_request("DELETE", 
> "$url/ipam/prefixes/$internalid/", $headers); + }; + if ($@) { + die 
> "error deleting subnet in Nautobot: $@" if !$noerr; + } +} + sub 
> add_ip { my ($class, $plugin_config, $subnetid, $subnet, $ip, 
> $hostname, $mac, $vmid, $is_gateway, $noerr) = @_; @@ -89,7 +110,7 @@ 
> sub add_ip { if ($@) { if($is_gateway) { - die "error adding subnet ip 
> to ipam: ip $ip already exists: $@" if 
> !PVE::Network::SDN::Ipams::NetboxPlugin::is_ip_gateway($url, $ip, 
> $headers) && !$noerr; + die "error adding subnet ip to ipam: ip $ip 
> already exists: $@" if !$noerr && !is_ip_gateway($url, $ip, $headers, 
> $noerr); } else { die "error adding subnet ip to ipam: ip $ip already 
> exists: $@" if !$noerr; } @@ -105,7 +126,8 @@ sub add_next_freeip { my 
> $namespace = $plugin_config->{namespace}; my $headers = 
> default_headers($plugin_config); - my $internalid = 
> PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, 
> $headers); + my $internalid = get_prefix_id($url, $cidr, $headers, 
> $noerr); + die "cannot find prefix $cidr in Nautobot" if !$internalid; 
> my $description = "mac:$mac" if $mac; @@ -133,7 +155,7 @@ sub 
> add_range_next_freeip { # ranges are not supported natively in 
> nautobot, hence why we have to get a little hacky. my $minimal_size = 
> NetAddr::IP->new($range->{'start-address'}) - NetAddr::IP->new($cidr); 
> - my $internalid = 
> PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, 
> $headers); + my $internalid = get_prefix_id($url, $cidr, $headers, 
> $noerr); my $ip = eval { my $result = 
> PVE::Network::SDN::api_request("GET", 
> "$url/ipam/prefixes/$internalid/available-ips/?limit=$minimal_size", 
> $headers); @@ -174,8 +196,8 @@ sub update_ip { my $params = { address 
> => "$ip/$mask", type => "dhcp", dns_name => $hostname, description => 
> $description, namespace => $namespace, status => default_ip_status()};
please break this line, we accept max 100 chars per line
> - my $ip_id = PVE::Network::SDN::Ipams::NetboxPlugin::get_ip_id($url, 
> $ip, $headers); - die "can't find ip $ip in ipam" if !$ip_id; + my 
> $ip_id = get_ip_id($url, $ip, $headers, $noerr); + die "can't find ip 
> $ip in ipam" if !$noerr && !$ip_id; eval { 
> PVE::Network::SDN::api_request("PATCH", 
> "$url/ipam/ip-addresses/$ip_id/", $headers, $params); @@ -186,6 
> +208,26 @@ sub update_ip { } +sub del_ip { + my ($class, 
> $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_;
$subnetid and $subnet are both unused
> + + return if !$ip; + + my $url = $plugin_config->{url}; + my $headers 
> = default_headers($plugin_config); + + my $ip_id = get_ip_id($url, 
> $ip, $headers, $noerr); + die "can't find ip $ip in ipam" if !$ip_id 
> && !$noerr; + + eval { + PVE::Network::SDN::api_request("DELETE", 
> "$url/ipam/ip-addresses/$ip_id/", $headers); + }; + if ($@) { + die 
> "error deleting ip $ip : $@" if !$noerr; + } +} + + sub verify_api { 
> my ($class, $plugin_config) = @_; @@ -196,8 +238,8 @@ sub verify_api { 
> # check that the namespace exists AND that default IP active status # 
> exists AND that we have indeed API access eval { - 
> get_namespace_id($url, $namespace, $headers) // die "namespace 
> $namespace does not exist"; - get_status_id($url, default_ip_status(), 
> $headers) // die "default IP status ". default_ip_status() . " not 
> found"; + get_namespace_id($url, $namespace, $headers, 0) // die 
> "namespace $namespace does not exist"; + get_status_id($url, 
> default_ip_status(), $headers, 0) // die "default IP status ". 
> default_ip_status() . " not found"; }; if ($@) { die "Can't use 
> nautobot api: $@"; @@ -242,22 +284,80 @@ sub get_ips_within_range { 
> return grep($start_address <= NetAddr::IP->new($_) <= $end_address, 
> @list); } +sub get_ip_id { + my ($url, $ip, $headers, $noerr) = @_; + 
> + my $result = eval { + return PVE::Network::SDN::api_request("GET", 
> "$url/ipam/ip-addresses/?q=$ip", $headers); + }; + if ($@) { + die 
> "error while querying for ip $ip id: $@" if !$noerr; + } + + my $data 
> = @{$result->{results}}[0]; + my $ip_id = $data->{id}; + return 
> $ip_id; +} + +sub get_prefix_id { + my ($url, $cidr, $headers, $noerr) 
> = @_; + + my $result = eval { + return 
> PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/?q=$cidr", 
> $headers); + }; + if ($@) { + die "error while querying for cidr $cidr 
> prefix id: $@" if !$noerr; + } + + my $data = 
> @{$result->{results}}[0]; + my $internalid = $data->{id}; + return 
> $internalid; +} + sub get_namespace_id { - my ($url, $namespace, 
> $headers) = @_; + my ($url, $namespace, $headers, $noerr) = @_; + + my 
> $result = eval { + return PVE::Network::SDN::api_request("GET", 
> "$url/ipam/namespaces/?q=$namespace", $headers); + }; + if ($@) { + 
> die "error while querying for namespace $namespace id: $@" if !$noerr; 
> + } - my $result = PVE::Network::SDN::api_request("GET", 
> "$url/ipam/namespaces/?q=$namespace", $headers); my $data = 
> @{$result->{results}}[0]; my $internalid = $data->{id}; return 
> $internalid; } sub get_status_id { - my ($url, $status, $headers) = 
> @_; + my ($url, $status, $headers, $noerr) = @_; + + my $result = eval 
> { + return PVE::Network::SDN::api_request("GET", 
> "$url/extras/statuses/?q=$status", $headers); + }; + if ($@) { + die 
> "error while querying for status $status id: $@" if !$noerr; + } - my 
> $result = PVE::Network::SDN::api_request("GET", 
> "$url/extras/statuses/?q=$status", $headers); my $data = 
> @{$result->{results}}[0]; my $internalid = $data->{id}; return 
> $internalid; } +sub is_ip_gateway { + my ($url, $ip, $headers, $noerr) 
> = @_; + + my $result = eval { + return 
> PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", 
> $headers); + }; + if ($@) { + die "error while checking if $ip is a 
> gateway" if !$noerr; + } + + my $data = @{$result->{results}}[0]; + my 
> $description = $data->{description}; + my $is_gateway = 1 if 
> $description eq 'gateway'; + return $is_gateway; +} + 1;
> -- 2.39.5
>
> _______________________________________________ pve-devel mailing list 
> pve-devel@lists.proxmox.comhttps://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


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


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [pve-devel] SPAM: [PATCH pve-network v2 5/7] ipam: nautobot: add checks for prefix deletion
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 5/7] ipam: nautobot: add checks for prefix deletion Lou Lecrivain via pve-devel
@ 2025-02-19 16:37   ` Hannes Dürr
  2025-02-19 17:15   ` Hannes Dürr
  2025-02-20 13:29   ` Hannes Dürr
  2 siblings, 0 replies; 18+ messages in thread
From: Hannes Dürr @ 2025-02-19 16:37 UTC (permalink / raw)
  To: Proxmox VE development discussion


On 1/8/25 13:15, Lou Lecrivain via pve-devel wrote:
>
>
> check that prefix/subnet is empty (only gateway IPs should remain) 
> before deletion. Signed-off-by: lou lecrivain 
> <lou.lecrivain@wdz.de>--- src/PVE/Network/SDN/Ipams/NautobotPlugin.pm 
> | 60 ++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 
> deletions(-) diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm 
> b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm index 79ac04d..f736bad 
> 100644 --- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm +++ 
> b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm @@ -5,6 +5,7 @@ use 
> warnings; use PVE::INotify; use PVE::Cluster; use PVE::Tools; +use 
> List::Util qw(all); use NetAddr::IP; use 
> base('PVE::Network::SDN::Ipams::Plugin'); @@ -76,8 +77,11 @@ sub 
> del_subnet { my $internalid = get_prefix_id($url, $cidr, $headers, 
> $noerr); return if !$internalid; - # TODO check that prefix is empty 
> before deletion - return; + if (!subnet_is_deletable($class, 
> $plugin_config, $subnetid, $subnet, $internalid, $noerr)) { + die 
> "cannot delete prefix $cidr, not empty!"; + } + + empty_subnet($class, 
> $plugin_config, $subnetid, $subnet, $internalid, $noerr); eval { 
> PVE::Network::SDN::api_request("DELETE", 
> "$url/ipam/prefixes/$internalid/", $headers); @@ -227,6 +231,58 @@ sub 
> del_ip { } } +sub empty_subnet { + my ($class, $plugin_config, 
> $subnetid, $subnet, $subnetuuid, $noerr) = @_; +
$class is unused
> + my $url = $plugin_config->{url}; + my $namespace = 
> $plugin_config->{namespace}; + my $headers = 
> default_headers($plugin_config); + + my $response = eval { + return 
> PVE::Network::SDN::api_request("GET", 
> "$url/ipam/ip-addresses/?namespace=$namespace&parent=$subnetuuid", 
> $headers) + }; + if ($@) { + die "error querying prefix $subnet: $@" 
> if !$noerr; + } + + for my $ip (@{$response->{results}}) { + 
> del_ip($class, $plugin_config, $subnetid, $subnet, $ip->{host}, 
> $noerr); + } +} + +sub subnet_is_deletable { + my ($class, 
> $plugin_config, $subnetid, $subnet, $subnetuuid, $noerr) = @_;
$subnetid, $class and $subnet are unused
> + + my $url = $plugin_config->{url}; + my $namespace = 
> $plugin_config->{namespace}; + my $headers = 
> default_headers($plugin_config); + + + my $response = eval { + return 
> PVE::Network::SDN::api_request("GET", 
> "$url/ipam/ip-addresses/?namespace=$namespace&parent=$subnetuuid", 
> $headers) + }; + if ($@) { + die "error querying prefix $subnet: $@" 
> if !$noerr; + } + my $n_ips = scalar $response->{results}->@*; + + # 
> least costly check operation 1st + if ($n_ips == 0) { + # completely 
> empty, delete ok + return 1; + } elsif ( + !(all {$_ == 1} ( + map { + 
> is_ip_gateway($url, $_->{host}, $headers, $noerr) + } 
> $response->{results}->@* + ))) { + # some remaining IPs are not 
> gateway, nok + return 0; + } else { + # remaining IPs are all gateway, 
> delete ok + return 1; + } +} sub verify_api { my ($class, 
> $plugin_config) = @_;
> -- 2.39.5
>
> _______________________________________________ pve-devel mailing list 
> pve-devel@lists.proxmox.comhttps://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


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


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [pve-devel] SPAM: [PATCH pve-network v2 2/7] ipam: nautobot: implement plain prefix allocation
       [not found] ` <20250108121529.5813-3-lou.lecrivain@wdz.de>
@ 2025-02-19 17:08   ` Hannes Dürr
  0 siblings, 0 replies; 18+ messages in thread
From: Hannes Dürr @ 2025-02-19 17:08 UTC (permalink / raw)
  To: Lou Lecrivain, pve-devel


On 1/8/25 13:15, Lou Lecrivain wrote:
> add support for subnet allocation without ranges,
> where it was previously not supported.
>
> Signed-off-by: lou lecrivain <lou.lecrivain@wdz.de>
> ---
>   src/PVE/Network/SDN/Ipams/NautobotPlugin.pm | 68 +++++++++++++++++++++
>   1 file changed, 68 insertions(+)
>
> diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
> index 69e7897..22867df 100644
> --- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
> +++ b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
> @@ -5,6 +5,7 @@ use warnings;
>   use PVE::INotify;
>   use PVE::Cluster;
>   use PVE::Tools;
> +use NetAddr::IP;
>   
>   use base('PVE::Network::SDN::Ipams::NetboxPlugin');
>   
> @@ -95,6 +96,66 @@ sub add_ip {
>       }
>   }
>   
> +sub add_next_freeip {
> +    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid, $noerr) = @_;
$subnetid and $vmid are unused
> +
> +    my $cidr = $subnet->{cidr};
> +
> +    my $url = $plugin_config->{url};
> +    my $namespace = $plugin_config->{namespace};
> +    my $headers = default_headers($plugin_config);
> +
> +    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
> +
> +    my $description = "mac:$mac" if $mac;
> +
> +    my $params = { type => "dhcp", dns_name => $hostname, description => $description, namespace => $namespace, status => default_ip_status() };
please break the line
> +
> +    my $ip = eval {
> +	my $result = PVE::Network::SDN::api_request("POST", "$url/ipam/prefixes/$internalid/available-ips/", $headers, $params);
> +	my ($ip, undef) = split(/\//, $result->{address});
> +	return $ip;
> +    };
> +
> +    if ($@) {
> +	die "can't find free ip in subnet $cidr: $@" if !$noerr;
> +    }
> +    return $ip;
> +}
> +
> +sub add_range_next_freeip {
> +    my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
> +
> +    my $url = $plugin_config->{url};
> +    my $namespace = $plugin_config->{namespace};
> +    my $headers = default_headers($plugin_config);
> +    my $cidr = $subnet->{cidr};
> +
> +    # ranges are not supported natively in nautobot, hence why we have to get a little hacky.
> +    my $minimal_size = NetAddr::IP->new($range->{'start-address'}) - NetAddr::IP->new($cidr);
> +    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
> +
> +    my $ip = eval {
> +	my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/$internalid/available-ips/?limit=$minimal_size", $headers);
> +	# v important for NetAddr::IP comparison!
> +	my @ips = map((split(/\//,$_->{address}))[0], @{$result});
> +	# get 1st result
> +	my $ip = (get_ips_within_range($range->{'start-address'}, $range->{'end-address'}, @ips))[0];
> +
> +	if ($ip) {
> +	    print "found free ip $ip in range $range->{'start-address'}-$range->{'end-address'}\n"
prints are not shown anywhere in the GUI?
> +	} else { die "prefix out of space in range"; }
> +
> +	$class->add_ip($plugin_config, undef,  $subnet, $ip, $data->{hostname}, $data->{mac}, undef, 0, 0);
> +	return $ip;
> +    };
> +
> +    if ($@) {
> +	die "can't find free ip in range $range->{'start-address'}-$range->{'end-address'}: $@" if !$noerr;
> +    }
> +    return $ip;
> +}
> +
>   
>   sub update_ip {
>       my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
> @@ -174,6 +235,13 @@ sub on_update_hook {
>   }
>   
>   # helpers
> +sub get_ips_within_range {
> +    my ($start_address, $end_address, @list) = @_;
> +    $start_address = NetAddr::IP->new($start_address);
> +    $end_address = NetAddr::IP->new($end_address);
> +    return grep($start_address <= NetAddr::IP->new($_) <= $end_address, @list);
> +}
> +
>   sub get_namespace_id {
>       my ($url, $namespace, $headers) = @_;
>   


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


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [pve-devel] SPAM: [PATCH pve-network v2 5/7] ipam: nautobot: add checks for prefix deletion
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 5/7] ipam: nautobot: add checks for prefix deletion Lou Lecrivain via pve-devel
  2025-02-19 16:37   ` Hannes Dürr
@ 2025-02-19 17:15   ` Hannes Dürr
  2025-02-20 13:29   ` Hannes Dürr
  2 siblings, 0 replies; 18+ messages in thread
From: Hannes Dürr @ 2025-02-19 17:15 UTC (permalink / raw)
  To: Proxmox VE development discussion


On 1/8/25 13:15, Lou Lecrivain via pve-devel wrote:
> check that prefix/subnet is empty (only gateway IPs should remain) 
> before deletion. Signed-off-by: lou lecrivain 
> <lou.lecrivain@wdz.de>--- src/PVE/Network/SDN/Ipams/NautobotPlugin.pm 
> | 60 ++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 
> deletions(-) diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm 
> b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm index 79ac04d..f736bad 
> 100644 --- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm +++ 
> b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm @@ -5,6 +5,7 @@ use 
> warnings; use PVE::INotify; use PVE::Cluster; use PVE::Tools; +use 
> List::Util qw(all); use NetAddr::IP; use 
> base('PVE::Network::SDN::Ipams::Plugin'); @@ -76,8 +77,11 @@ sub 
> del_subnet { my $internalid = get_prefix_id($url, $cidr, $headers, 
> $noerr); return if !$internalid; - # TODO check that prefix is empty 
> before deletion - return; + if (!subnet_is_deletable($class, 
> $plugin_config, $subnetid, $subnet, $internalid, $noerr)) { + die 
> "cannot delete prefix $cidr, not empty!"; + } + + empty_subnet($class, 
> $plugin_config, $subnetid, $subnet, $internalid, $noerr); eval { 
> PVE::Network::SDN::api_request("DELETE", 
> "$url/ipam/prefixes/$internalid/", $headers); @@ -227,6 +231,58 @@ sub 
> del_ip { } } +sub empty_subnet { + my ($class, $plugin_config, 
> $subnetid, $subnet, $subnetuuid, $noerr) = @_;
$class is unused
> + + my $url = $plugin_config->{url}; + my $namespace = 
> $plugin_config->{namespace}; + my $headers = 
> default_headers($plugin_config); + + my $response = eval { + return 
> PVE::Network::SDN::api_request("GET", 
> "$url/ipam/ip-addresses/?namespace=$namespace&parent=$subnetuuid", 
> $headers) + }; + if ($@) { + die "error querying prefix $subnet: $@" 
> if !$noerr; + } + + for my $ip (@{$response->{results}}) { + 
> del_ip($class, $plugin_config, $subnetid, $subnet, $ip->{host}, 
> $noerr); + } +} + +sub subnet_is_deletable { + my ($class, 
> $plugin_config, $subnetid, $subnet, $subnetuuid, $noerr) = @_;
$subnetid, $class and $subnet are unused
> + + my $url = $plugin_config->{url}; + my $namespace = 
> $plugin_config->{namespace}; + my $headers = 
> default_headers($plugin_config); + + + my $response = eval { + return 
> PVE::Network::SDN::api_request("GET", 
> "$url/ipam/ip-addresses/?namespace=$namespace&parent=$subnetuuid", 
> $headers) + }; + if ($@) { + die "error querying prefix $subnet: $@" 
> if !$noerr; + } + my $n_ips = scalar $response->{results}->@*; + + # 
> least costly check operation 1st + if ($n_ips == 0) { + # completely 
> empty, delete ok + return 1; + } elsif ( + !(all {$_ == 1} ( + map { + 
> is_ip_gateway($url, $_->{host}, $headers, $noerr) + } 
> $response->{results}->@* + ))) { + # some remaining IPs are not 
> gateway, nok + return 0; + } else { + # remaining IPs are all gateway, 
> delete ok + return 1; + } +} sub verify_api { my ($class, 
> $plugin_config) = @_;
> -- 2.39.5
> ____________________________________
> pve-devel mailing list
> pve-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


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


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [pve-devel] SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors Lou Lecrivain via pve-devel
  2025-02-19 16:37   ` Hannes Dürr
@ 2025-02-19 17:18   ` Hannes Dürr
  2025-02-20 13:27   ` Hannes Dürr
  2 siblings, 0 replies; 18+ messages in thread
From: Hannes Dürr @ 2025-02-19 17:18 UTC (permalink / raw)
  To: Proxmox VE development discussion


On 1/8/25 13:15, Lou Lecrivain via pve-devel wrote:
>
> added error handling in helpers Signed-off-by: lou lecrivain 
> <lou.lecrivain@wdz.de>--- src/PVE/Network/SDN/Ipams/NautobotPlugin.pm 
> | 126 ++++++++++++++++++-- 1 file changed, 113 insertions(+), 13 
> deletions(-) diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm 
> b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm index 22867df..79ac04d 
> 100644 --- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm +++ 
> b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm @@ -7,7 +7,7 @@ use 
> PVE::Cluster; use PVE::Tools; use NetAddr::IP; -use 
> base('PVE::Network::SDN::Ipams::NetboxPlugin'); +use 
> base('PVE::Network::SDN::Ipams::Plugin'); sub type { return 
> 'nautobot'; @@ -51,7 +51,7 @@ sub add_subnet { my $namespace = 
> $plugin_config->{namespace}; my $headers = 
> default_headers($plugin_config); - my $internalid = 
> PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, 
> $headers); + my $internalid = get_prefix_id($url, $cidr, $headers, 
> $noerr); #create subnet if (!$internalid) { @@ -66,6 +66,27 @@ sub 
> add_subnet { } } +sub del_subnet { + my ($class, $plugin_config, 
> $subnetid, $subnet, $noerr) = @_; + + my $cidr = $subnet->{cidr}; + my 
> $url = $plugin_config->{url}; + my $headers = 
> default_headers($plugin_config); + + my $internalid = 
> get_prefix_id($url, $cidr, $headers, $noerr); + return if 
> !$internalid; + + # TODO check that prefix is empty before deletion + 
> return; + + eval { + PVE::Network::SDN::api_request("DELETE", 
> "$url/ipam/prefixes/$internalid/", $headers); + }; + if ($@) { + die 
> "error deleting subnet in Nautobot: $@" if !$noerr; + } +} + sub 
> add_ip { my ($class, $plugin_config, $subnetid, $subnet, $ip, 
> $hostname, $mac, $vmid, $is_gateway, $noerr) = @_; @@ -89,7 +110,7 @@ 
> sub add_ip { if ($@) { if($is_gateway) { - die "error adding subnet ip 
> to ipam: ip $ip already exists: $@" if 
> !PVE::Network::SDN::Ipams::NetboxPlugin::is_ip_gateway($url, $ip, 
> $headers) && !$noerr; + die "error adding subnet ip to ipam: ip $ip 
> already exists: $@" if !$noerr && !is_ip_gateway($url, $ip, $headers, 
> $noerr); } else { die "error adding subnet ip to ipam: ip $ip already 
> exists: $@" if !$noerr; } @@ -105,7 +126,8 @@ sub add_next_freeip { my 
> $namespace = $plugin_config->{namespace}; my $headers = 
> default_headers($plugin_config); - my $internalid = 
> PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, 
> $headers); + my $internalid = get_prefix_id($url, $cidr, $headers, 
> $noerr); + die "cannot find prefix $cidr in Nautobot" if !$internalid; 
> my $description = "mac:$mac" if $mac; @@ -133,7 +155,7 @@ sub 
> add_range_next_freeip { # ranges are not supported natively in 
> nautobot, hence why we have to get a little hacky. my $minimal_size = 
> NetAddr::IP->new($range->{'start-address'}) - NetAddr::IP->new($cidr); 
> - my $internalid = 
> PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, 
> $headers); + my $internalid = get_prefix_id($url, $cidr, $headers, 
> $noerr); my $ip = eval { my $result = 
> PVE::Network::SDN::api_request("GET", 
> "$url/ipam/prefixes/$internalid/available-ips/?limit=$minimal_size", 
> $headers); @@ -174,8 +196,8 @@ sub update_ip { my $params = { address 
> => "$ip/$mask", type => "dhcp", dns_name => $hostname, description => 
> $description, namespace => $namespace, status => default_ip_status()}; 
> - my $ip_id = PVE::Network::SDN::Ipams::NetboxPlugin::get_ip_id($url, 
> $ip, $headers); - die "can't find ip $ip in ipam" if !$ip_id; + my 
> $ip_id = get_ip_id($url, $ip, $headers, $noerr); + die "can't find ip 
> $ip in ipam" if !$noerr && !$ip_id; eval { 
> PVE::Network::SDN::api_request("PATCH", 
> "$url/ipam/ip-addresses/$ip_id/", $headers, $params); @@ -186,6 
> +208,26 @@ sub update_ip { } +sub del_ip { + my ($class, 
> $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_;
$subnetid and $subnet are both unused
> + + return if !$ip; + + my $url = $plugin_config->{url}; + my $headers 
> = default_headers($plugin_config); + + my $ip_id = get_ip_id($url, 
> $ip, $headers, $noerr); + die "can't find ip $ip in ipam" if !$ip_id 
> && !$noerr; + + eval { + PVE::Network::SDN::api_request("DELETE", 
> "$url/ipam/ip-addresses/$ip_id/", $headers); + }; + if ($@) { + die 
> "error deleting ip $ip : $@" if !$noerr; + } +} + + sub verify_api { 
> my ($class, $plugin_config) = @_; @@ -196,8 +238,8 @@ sub verify_api { 
> # check that the namespace exists AND that default IP active status # 
> exists AND that we have indeed API access eval { - 
> get_namespace_id($url, $namespace, $headers) // die "namespace 
> $namespace does not exist"; - get_status_id($url, default_ip_status(), 
> $headers) // die "default IP status ". default_ip_status() . " not 
> found"; + get_namespace_id($url, $namespace, $headers, 0) // die 
> "namespace $namespace does not exist"; + get_status_id($url, 
> default_ip_status(), $headers, 0) // die "default IP status ". 
> default_ip_status() . " not found"; }; if ($@) { die "Can't use 
> nautobot api: $@"; @@ -242,22 +284,80 @@ sub get_ips_within_range { 
> return grep($start_address <= NetAddr::IP->new($_) <= $end_address, 
> @list); } +sub get_ip_id { + my ($url, $ip, $headers, $noerr) = @_; + 
> + my $result = eval { + return PVE::Network::SDN::api_request("GET", 
> "$url/ipam/ip-addresses/?q=$ip", $headers); + }; + if ($@) { + die 
> "error while querying for ip $ip id: $@" if !$noerr; + } + + my $data 
> = @{$result->{results}}[0]; + my $ip_id = $data->{id}; + return 
> $ip_id; +} + +sub get_prefix_id { + my ($url, $cidr, $headers, $noerr) 
> = @_; + + my $result = eval { + return 
> PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/?q=$cidr", 
> $headers); + }; + if ($@) { + die "error while querying for cidr $cidr 
> prefix id: $@" if !$noerr; + } + + my $data = 
> @{$result->{results}}[0]; + my $internalid = $data->{id}; + return 
> $internalid; +} + sub get_namespace_id { - my ($url, $namespace, 
> $headers) = @_; + my ($url, $namespace, $headers, $noerr) = @_; + + my 
> $result = eval { + return PVE::Network::SDN::api_request("GET", 
> "$url/ipam/namespaces/?q=$namespace", $headers); + }; + if ($@) { + 
> die "error while querying for namespace $namespace id: $@" if !$noerr; 
> + } - my $result = PVE::Network::SDN::api_request("GET", 
> "$url/ipam/namespaces/?q=$namespace", $headers); my $data = 
> @{$result->{results}}[0]; my $internalid = $data->{id}; return 
> $internalid; } sub get_status_id { - my ($url, $status, $headers) = 
> @_; + my ($url, $status, $headers, $noerr) = @_; + + my $result = eval 
> { + return PVE::Network::SDN::api_request("GET", 
> "$url/extras/statuses/?q=$status", $headers); + }; + if ($@) { + die 
> "error while querying for status $status id: $@" if !$noerr; + } - my 
> $result = PVE::Network::SDN::api_request("GET", 
> "$url/extras/statuses/?q=$status", $headers); my $data = 
> @{$result->{results}}[0]; my $internalid = $data->{id}; return 
> $internalid; } +sub is_ip_gateway { + my ($url, $ip, $headers, $noerr) 
> = @_; + + my $result = eval { + return 
> PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", 
> $headers); + }; + if ($@) { + die "error while checking if $ip is a 
> gateway" if !$noerr; + } + + my $data = @{$result->{results}}[0]; + my 
> $description = $data->{description}; + my $is_gateway = 1 if 
> $description eq 'gateway'; + return $is_gateway; +} + 1;
> -- 2.39.5
> _______________________________________________
> pve-devel mailing list
> pve-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


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


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [pve-devel] SPAM: [PATCH pve-network v2 2/7] ipam: nautobot: implement plain prefix allocation
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 2/7] ipam: nautobot: implement plain prefix allocation Lou Lecrivain via pve-devel
  2025-02-19 16:37   ` Hannes Dürr
@ 2025-02-20 13:24   ` Hannes Dürr
  1 sibling, 0 replies; 18+ messages in thread
From: Hannes Dürr @ 2025-02-20 13:24 UTC (permalink / raw)
  To: Proxmox VE development discussion


On 1/8/25 13:15, Lou Lecrivain wrote:
> add support for subnet allocation without ranges,
> where it was previously not supported.
>
> Signed-off-by: lou lecrivain <lou.lecrivain@wdz.de>
> ---
>   src/PVE/Network/SDN/Ipams/NautobotPlugin.pm | 68 +++++++++++++++++++++
>   1 file changed, 68 insertions(+)
>
> diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
> index 69e7897..22867df 100644
> --- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
> +++ b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
> @@ -5,6 +5,7 @@ use warnings;
>   use PVE::INotify;
>   use PVE::Cluster;
>   use PVE::Tools;
> +use NetAddr::IP;
>   
>   use base('PVE::Network::SDN::Ipams::NetboxPlugin');
>   
> @@ -95,6 +96,66 @@ sub add_ip {
>       }
>   }
>   
> +sub add_next_freeip {
> +    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid, $noerr) = @_;
$subnetid and $vmid are unused
> +
> +    my $cidr = $subnet->{cidr};
> +
> +    my $url = $plugin_config->{url};
> +    my $namespace = $plugin_config->{namespace};
> +    my $headers = default_headers($plugin_config);
> +
> +    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
> +
> +    my $description = "mac:$mac" if $mac;
> +
> +    my $params = { type => "dhcp", dns_name => $hostname, description => $description, namespace => $namespace, status => default_ip_status() };
please break the line
> +
> +    my $ip = eval {
> +	my $result = PVE::Network::SDN::api_request("POST", "$url/ipam/prefixes/$internalid/available-ips/", $headers, $params);
> +	my ($ip, undef) = split(/\//, $result->{address});
> +	return $ip;
> +    };
> +
> +    if ($@) {
> +	die "can't find free ip in subnet $cidr: $@" if !$noerr;
> +    }
> +    return $ip;
> +}
> +
> +sub add_range_next_freeip {
> +    my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
> +
> +    my $url = $plugin_config->{url};
> +    my $namespace = $plugin_config->{namespace};
> +    my $headers = default_headers($plugin_config);
> +    my $cidr = $subnet->{cidr};
> +
> +    # ranges are not supported natively in nautobot, hence why we have to get a little hacky.
> +    my $minimal_size = NetAddr::IP->new($range->{'start-address'}) - NetAddr::IP->new($cidr);
> +    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
> +
> +    my $ip = eval {
> +	my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/$internalid/available-ips/?limit=$minimal_size", $headers);
> +	# v important for NetAddr::IP comparison!
> +	my @ips = map((split(/\//,$_->{address}))[0], @{$result});
> +	# get 1st result
> +	my $ip = (get_ips_within_range($range->{'start-address'}, $range->{'end-address'}, @ips))[0];
> +
> +	if ($ip) {
> +	    print "found free ip $ip in range $range->{'start-address'}-$range->{'end-address'}\n"
prints are not shown anywhere in the GUI?
> +	} else { die "prefix out of space in range"; }
> +
> +	$class->add_ip($plugin_config, undef,  $subnet, $ip, $data->{hostname}, $data->{mac}, undef, 0, 0);
> +	return $ip;
> +    };
> +
> +    if ($@) {
> +	die "can't find free ip in range $range->{'start-address'}-$range->{'end-address'}: $@" if !$noerr;
> +    }
> +    return $ip;
> +}
> +
>   
>   sub update_ip {
>       my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
> @@ -174,6 +235,13 @@ sub on_update_hook {
>   }
>   
>   # helpers
> +sub get_ips_within_range {
> +    my ($start_address, $end_address, @list) = @_;
> +    $start_address = NetAddr::IP->new($start_address);
> +    $end_address = NetAddr::IP->new($end_address);
> +    return grep($start_address <= NetAddr::IP->new($_) <= $end_address, @list);
> +}
> +
>   sub get_namespace_id {
>       my ($url, $namespace, $headers) = @_;
>   


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


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [pve-devel] SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors Lou Lecrivain via pve-devel
  2025-02-19 16:37   ` Hannes Dürr
  2025-02-19 17:18   ` Hannes Dürr
@ 2025-02-20 13:27   ` Hannes Dürr
  2 siblings, 0 replies; 18+ messages in thread
From: Hannes Dürr @ 2025-02-20 13:27 UTC (permalink / raw)
  To: Proxmox VE development discussion


On 1/8/25 13:15, Lou Lecrivain wrote:
> added error handling in helpers
>
> Signed-off-by: lou lecrivain <lou.lecrivain@wdz.de>
> ---
>   src/PVE/Network/SDN/Ipams/NautobotPlugin.pm | 126 ++++++++++++++++++--
>   1 file changed, 113 insertions(+), 13 deletions(-)
>
> diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
> index 22867df..79ac04d 100644
> --- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
> +++ b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
> @@ -7,7 +7,7 @@ use PVE::Cluster;
>   use PVE::Tools;
>   use NetAddr::IP;
>   
> -use base('PVE::Network::SDN::Ipams::NetboxPlugin');
> +use base('PVE::Network::SDN::Ipams::Plugin');
>   
>   sub type {
>       return 'nautobot';
> @@ -51,7 +51,7 @@ sub add_subnet {
>       my $namespace = $plugin_config->{namespace};
>       my $headers = default_headers($plugin_config);
>   
> -    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
> +    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
>   
>       #create subnet
>       if (!$internalid) {
> @@ -66,6 +66,27 @@ sub add_subnet {
>       }
>   }
>   
> +sub del_subnet {
> +    my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
> +
> +    my $cidr = $subnet->{cidr};
> +    my $url = $plugin_config->{url};
> +    my $headers = default_headers($plugin_config);
> +
> +    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
> +    return if !$internalid;
> +
> +    # TODO check that prefix is empty before deletion
> +    return;
> +
> +    eval {
> +	PVE::Network::SDN::api_request("DELETE", "$url/ipam/prefixes/$internalid/", $headers);
> +    };
> +    if ($@) {
> +	die "error deleting subnet in Nautobot: $@" if !$noerr;
> +    }
> +}
> +
>   sub add_ip {
>       my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
>   
> @@ -89,7 +110,7 @@ sub add_ip {
>   
>       if ($@) {
>   	if($is_gateway) {
> -	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !PVE::Network::SDN::Ipams::NetboxPlugin::is_ip_gateway($url, $ip, $headers) && !$noerr;
> +	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !$noerr && !is_ip_gateway($url, $ip, $headers, $noerr);
>   	} else {
>   	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !$noerr;
>   	}
> @@ -105,7 +126,8 @@ sub add_next_freeip {
>       my $namespace = $plugin_config->{namespace};
>       my $headers = default_headers($plugin_config);
>   
> -    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
> +    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
> +    die "cannot find prefix $cidr in Nautobot" if !$internalid;
>   
>       my $description = "mac:$mac" if $mac;
>   
> @@ -133,7 +155,7 @@ sub add_range_next_freeip {
>   
>       # ranges are not supported natively in nautobot, hence why we have to get a little hacky.
>       my $minimal_size = NetAddr::IP->new($range->{'start-address'}) - NetAddr::IP->new($cidr);
> -    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
> +    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
>   
>       my $ip = eval {
>   	my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/$internalid/available-ips/?limit=$minimal_size", $headers);
> @@ -174,8 +196,8 @@ sub update_ip {
>   
>       my $params = { address => "$ip/$mask", type => "dhcp", dns_name => $hostname, description => $description, namespace => $namespace, status => default_ip_status()};
please break this line, we accept max 100 chars per line
>   
> -    my $ip_id = PVE::Network::SDN::Ipams::NetboxPlugin::get_ip_id($url, $ip, $headers);
> -    die "can't find ip $ip in ipam" if !$ip_id;
> +    my $ip_id = get_ip_id($url, $ip, $headers, $noerr);
> +    die "can't find ip $ip in ipam" if !$noerr && !$ip_id;
>   
>       eval {
>   	PVE::Network::SDN::api_request("PATCH", "$url/ipam/ip-addresses/$ip_id/", $headers, $params);
> @@ -186,6 +208,26 @@ sub update_ip {
>   }
>   
>   
> +sub del_ip {
> +    my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_;
$subnetid and $subnet are both unused
> +
> +    return if !$ip;
> +
> +    my $url = $plugin_config->{url};
> +    my $headers = default_headers($plugin_config);
> +
> +    my $ip_id = get_ip_id($url, $ip, $headers, $noerr);
> +    die "can't find ip $ip in ipam" if !$ip_id && !$noerr;
> +
> +    eval {
> +	PVE::Network::SDN::api_request("DELETE", "$url/ipam/ip-addresses/$ip_id/", $headers);
> +    };
> +    if ($@) {
> +	die "error deleting ip $ip : $@" if !$noerr;
> +    }
> +}
> +
> +
>   sub verify_api {
>       my ($class, $plugin_config) = @_;
>   
> @@ -196,8 +238,8 @@ sub verify_api {
>       # check that the namespace exists AND that default IP active status
>       # exists AND that we have indeed API access
>       eval {
> -	get_namespace_id($url, $namespace, $headers) // die "namespace $namespace does not exist";
> -	get_status_id($url, default_ip_status(), $headers) // die "default IP status ". default_ip_status() . " not found";
> +	get_namespace_id($url, $namespace, $headers, 0) // die "namespace $namespace does not exist";
> +	get_status_id($url, default_ip_status(), $headers, 0) // die "default IP status ". default_ip_status() . " not found";
>       };
>       if ($@) {
>   	die "Can't use nautobot api: $@";
> @@ -242,22 +284,80 @@ sub get_ips_within_range {
>       return grep($start_address <= NetAddr::IP->new($_) <= $end_address, @list);
>   }
>   
> +sub get_ip_id {
> +    my ($url, $ip, $headers, $noerr) = @_;
> +
> +    my $result = eval {
> +	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers);
> +    };
> +    if ($@) {
> +	die "error while querying for ip $ip id: $@" if !$noerr;
> +    }
> +
> +    my $data = @{$result->{results}}[0];
> +    my $ip_id = $data->{id};
> +    return $ip_id;
> +}
> +
> +sub get_prefix_id {
> +    my ($url, $cidr, $headers, $noerr) = @_;
> +
> +    my $result = eval {
> +	return PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/?q=$cidr", $headers);
> +    };
> +    if ($@) {
> +	die "error while querying for cidr $cidr prefix id: $@" if !$noerr;
> +    }
> +
> +    my $data = @{$result->{results}}[0];
> +    my $internalid = $data->{id};
> +    return $internalid;
> +}
> +
>   sub get_namespace_id {
> -    my ($url, $namespace, $headers) = @_;
> +    my ($url, $namespace, $headers, $noerr) = @_;
> +
> +    my $result = eval {
> +	return PVE::Network::SDN::api_request("GET", "$url/ipam/namespaces/?q=$namespace", $headers);
> +    };
> +    if ($@) {
> +	die "error while querying for namespace $namespace id: $@" if !$noerr;
> +    }
>   
> -    my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/namespaces/?q=$namespace", $headers);
>       my $data = @{$result->{results}}[0];
>       my $internalid = $data->{id};
>       return $internalid;
>   }
>   
>   sub get_status_id {
> -    my ($url, $status, $headers) = @_;
> +    my ($url, $status, $headers, $noerr) = @_;
> +
> +    my $result = eval {
> +	return PVE::Network::SDN::api_request("GET", "$url/extras/statuses/?q=$status", $headers);
> +    };
> +    if ($@) {
> +	die "error while querying for status $status id: $@" if !$noerr;
> +    }
>   
> -    my $result = PVE::Network::SDN::api_request("GET", "$url/extras/statuses/?q=$status", $headers);
>       my $data = @{$result->{results}}[0];
>       my $internalid = $data->{id};
>       return $internalid;
>   }
>   
> +sub is_ip_gateway {
> +    my ($url, $ip, $headers, $noerr) = @_;
> +
> +    my $result = eval {
> +	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers);
> +    };
> +    if ($@) {
> +	die "error while checking if $ip is a gateway" if !$noerr;
> +    }
> +
> +    my $data = @{$result->{results}}[0];
> +    my $description = $data->{description};
> +    my $is_gateway = 1 if $description eq 'gateway';
> +    return $is_gateway;
> +}
> +
>   1;


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


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [pve-devel] SPAM: [PATCH pve-network v2 5/7] ipam: nautobot: add checks for prefix deletion
  2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 5/7] ipam: nautobot: add checks for prefix deletion Lou Lecrivain via pve-devel
  2025-02-19 16:37   ` Hannes Dürr
  2025-02-19 17:15   ` Hannes Dürr
@ 2025-02-20 13:29   ` Hannes Dürr
  2 siblings, 0 replies; 18+ messages in thread
From: Hannes Dürr @ 2025-02-20 13:29 UTC (permalink / raw)
  To: Proxmox VE development discussion


On 1/8/25 13:15, Lou Lecrivain wrote:
> check that prefix/subnet is empty (only gateway IPs should remain)
> before deletion.
>
> Signed-off-by: lou lecrivain <lou.lecrivain@wdz.de>
> ---
>   src/PVE/Network/SDN/Ipams/NautobotPlugin.pm | 60 ++++++++++++++++++++-
>   1 file changed, 58 insertions(+), 2 deletions(-)
>
> diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
> index 79ac04d..f736bad 100644
> --- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
> +++ b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
> @@ -5,6 +5,7 @@ use warnings;
>   use PVE::INotify;
>   use PVE::Cluster;
>   use PVE::Tools;
> +use List::Util qw(all);
>   use NetAddr::IP;
>   
>   use base('PVE::Network::SDN::Ipams::Plugin');
> @@ -76,8 +77,11 @@ sub del_subnet {
>       my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
>       return if !$internalid;
>   
> -    # TODO check that prefix is empty before deletion
> -    return;
> +    if (!subnet_is_deletable($class, $plugin_config, $subnetid, $subnet, $internalid, $noerr)) {
> +	die "cannot delete prefix $cidr, not empty!";
> +    }
> +
> +    empty_subnet($class, $plugin_config, $subnetid, $subnet, $internalid, $noerr);
>   
>       eval {
>   	PVE::Network::SDN::api_request("DELETE", "$url/ipam/prefixes/$internalid/", $headers);
> @@ -227,6 +231,58 @@ sub del_ip {
>       }
>   }
>   
> +sub empty_subnet {
> +    my ($class, $plugin_config, $subnetid, $subnet, $subnetuuid, $noerr) = @_;
$class is unused
> +
> +    my $url = $plugin_config->{url};
> +    my $namespace = $plugin_config->{namespace};
> +    my $headers = default_headers($plugin_config);
> +
> +    my $response = eval {
> +	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?namespace=$namespace&parent=$subnetuuid", $headers)
> +    };
> +    if ($@) {
> +	die "error querying prefix $subnet: $@" if !$noerr;
> +    }
> +
> +    for my $ip (@{$response->{results}}) {
> +	del_ip($class, $plugin_config, $subnetid, $subnet, $ip->{host}, $noerr);
> +    }
> +}
> +
> +sub subnet_is_deletable {
> +    my ($class, $plugin_config, $subnetid, $subnet, $subnetuuid, $noerr) = @_;
$subnetid, $class and $subnet are unused
> +
> +    my $url = $plugin_config->{url};
> +    my $namespace = $plugin_config->{namespace};
> +    my $headers = default_headers($plugin_config);
> +
> +
> +    my $response = eval {
> +	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?namespace=$namespace&parent=$subnetuuid", $headers)
> +    };
> +    if ($@) {
> +	die "error querying prefix $subnet: $@" if !$noerr;
> +    }
> +    my $n_ips = scalar $response->{results}->@*;
> +
> +    # least costly check operation 1st
> +    if ($n_ips == 0) {
> +	# completely empty, delete ok
> +	return 1;
> +    } elsif (
> +	!(all {$_ == 1} (
> +	    map {
> +		is_ip_gateway($url, $_->{host}, $headers, $noerr)
> +	    } $response->{results}->@*
> +	))) {
> +	# some remaining IPs are not gateway, nok
> +	return 0;
> +    } else {
> +	# remaining IPs are all gateway, delete ok
> +	return 1;
> +    }
> +}
>   
>   sub verify_api {
>       my ($class, $plugin_config) = @_;


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


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [pve-devel] SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors
       [not found] <20250108120903.5344-1-lou.lecrivain@wdz.de>
@ 2025-01-08 12:08 ` Lou Lecrivain via pve-devel
  0 siblings, 0 replies; 18+ messages in thread
From: Lou Lecrivain via pve-devel @ 2025-01-08 12:08 UTC (permalink / raw)
  To: pve-devel; +Cc: Lou Lecrivain

[-- Attachment #1: Type: message/rfc822, Size: 11013 bytes --]

From: Lou Lecrivain <lou.lecrivain@wdz.de>
To: pve-devel@lists.proxmox.com
Subject: SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors
Date: Wed,  8 Jan 2025 13:08:59 +0100
Message-ID: <20250108120903.5344-8-lou.lecrivain@wdz.de>

added error handling in helpers

Signed-off-by: lou lecrivain <lou.lecrivain@wdz.de>
---
 src/PVE/Network/SDN/Ipams/NautobotPlugin.pm | 126 ++++++++++++++++++--
 1 file changed, 113 insertions(+), 13 deletions(-)

diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
index 22867df..79ac04d 100644
--- a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
@@ -7,7 +7,7 @@ use PVE::Cluster;
 use PVE::Tools;
 use NetAddr::IP;
 
-use base('PVE::Network::SDN::Ipams::NetboxPlugin');
+use base('PVE::Network::SDN::Ipams::Plugin');
 
 sub type {
     return 'nautobot';
@@ -51,7 +51,7 @@ sub add_subnet {
     my $namespace = $plugin_config->{namespace};
     my $headers = default_headers($plugin_config);
 
-    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
+    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
 
     #create subnet
     if (!$internalid) {
@@ -66,6 +66,27 @@ sub add_subnet {
     }
 }
 
+sub del_subnet {
+    my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
+
+    my $cidr = $subnet->{cidr};
+    my $url = $plugin_config->{url};
+    my $headers = default_headers($plugin_config);
+
+    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
+    return if !$internalid;
+
+    # TODO check that prefix is empty before deletion
+    return;
+
+    eval {
+	PVE::Network::SDN::api_request("DELETE", "$url/ipam/prefixes/$internalid/", $headers);
+    };
+    if ($@) {
+	die "error deleting subnet in Nautobot: $@" if !$noerr;
+    }
+}
+
 sub add_ip {
     my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
 
@@ -89,7 +110,7 @@ sub add_ip {
 
     if ($@) {
 	if($is_gateway) {
-	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !PVE::Network::SDN::Ipams::NetboxPlugin::is_ip_gateway($url, $ip, $headers) && !$noerr;
+	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !$noerr && !is_ip_gateway($url, $ip, $headers, $noerr);
 	} else {
 	    die "error adding subnet ip to ipam: ip $ip already exists: $@" if !$noerr;
 	}
@@ -105,7 +126,8 @@ sub add_next_freeip {
     my $namespace = $plugin_config->{namespace};
     my $headers = default_headers($plugin_config);
 
-    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
+    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
+    die "cannot find prefix $cidr in Nautobot" if !$internalid;
 
     my $description = "mac:$mac" if $mac;
 
@@ -133,7 +155,7 @@ sub add_range_next_freeip {
 
     # ranges are not supported natively in nautobot, hence why we have to get a little hacky.
     my $minimal_size = NetAddr::IP->new($range->{'start-address'}) - NetAddr::IP->new($cidr);
-    my $internalid = PVE::Network::SDN::Ipams::NetboxPlugin::get_prefix_id($url, $cidr, $headers);
+    my $internalid = get_prefix_id($url, $cidr, $headers, $noerr);
 
     my $ip = eval {
 	my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/$internalid/available-ips/?limit=$minimal_size", $headers);
@@ -174,8 +196,8 @@ sub update_ip {
 
     my $params = { address => "$ip/$mask", type => "dhcp", dns_name => $hostname, description => $description, namespace => $namespace, status => default_ip_status()};
 
-    my $ip_id = PVE::Network::SDN::Ipams::NetboxPlugin::get_ip_id($url, $ip, $headers);
-    die "can't find ip $ip in ipam" if !$ip_id;
+    my $ip_id = get_ip_id($url, $ip, $headers, $noerr);
+    die "can't find ip $ip in ipam" if !$noerr && !$ip_id;
 
     eval {
 	PVE::Network::SDN::api_request("PATCH", "$url/ipam/ip-addresses/$ip_id/", $headers, $params);
@@ -186,6 +208,26 @@ sub update_ip {
 }
 
 
+sub del_ip {
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_;
+
+    return if !$ip;
+
+    my $url = $plugin_config->{url};
+    my $headers = default_headers($plugin_config);
+
+    my $ip_id = get_ip_id($url, $ip, $headers, $noerr);
+    die "can't find ip $ip in ipam" if !$ip_id && !$noerr;
+
+    eval {
+	PVE::Network::SDN::api_request("DELETE", "$url/ipam/ip-addresses/$ip_id/", $headers);
+    };
+    if ($@) {
+	die "error deleting ip $ip : $@" if !$noerr;
+    }
+}
+
+
 sub verify_api {
     my ($class, $plugin_config) = @_;
 
@@ -196,8 +238,8 @@ sub verify_api {
     # check that the namespace exists AND that default IP active status
     # exists AND that we have indeed API access
     eval {
-	get_namespace_id($url, $namespace, $headers) // die "namespace $namespace does not exist";
-	get_status_id($url, default_ip_status(), $headers) // die "default IP status ". default_ip_status() . " not found";
+	get_namespace_id($url, $namespace, $headers, 0) // die "namespace $namespace does not exist";
+	get_status_id($url, default_ip_status(), $headers, 0) // die "default IP status ". default_ip_status() . " not found";
     };
     if ($@) {
 	die "Can't use nautobot api: $@";
@@ -242,22 +284,80 @@ sub get_ips_within_range {
     return grep($start_address <= NetAddr::IP->new($_) <= $end_address, @list);
 }
 
+sub get_ip_id {
+    my ($url, $ip, $headers, $noerr) = @_;
+
+    my $result = eval {
+	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers);
+    };
+    if ($@) {
+	die "error while querying for ip $ip id: $@" if !$noerr;
+    }
+
+    my $data = @{$result->{results}}[0];
+    my $ip_id = $data->{id};
+    return $ip_id;
+}
+
+sub get_prefix_id {
+    my ($url, $cidr, $headers, $noerr) = @_;
+
+    my $result = eval {
+	return PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/?q=$cidr", $headers);
+    };
+    if ($@) {
+	die "error while querying for cidr $cidr prefix id: $@" if !$noerr;
+    }
+
+    my $data = @{$result->{results}}[0];
+    my $internalid = $data->{id};
+    return $internalid;
+}
+
 sub get_namespace_id {
-    my ($url, $namespace, $headers) = @_;
+    my ($url, $namespace, $headers, $noerr) = @_;
+
+    my $result = eval {
+	return PVE::Network::SDN::api_request("GET", "$url/ipam/namespaces/?q=$namespace", $headers);
+    };
+    if ($@) {
+	die "error while querying for namespace $namespace id: $@" if !$noerr;
+    }
 
-    my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/namespaces/?q=$namespace", $headers);
     my $data = @{$result->{results}}[0];
     my $internalid = $data->{id};
     return $internalid;
 }
 
 sub get_status_id {
-    my ($url, $status, $headers) = @_;
+    my ($url, $status, $headers, $noerr) = @_;
+
+    my $result = eval {
+	return PVE::Network::SDN::api_request("GET", "$url/extras/statuses/?q=$status", $headers);
+    };
+    if ($@) {
+	die "error while querying for status $status id: $@" if !$noerr;
+    }
 
-    my $result = PVE::Network::SDN::api_request("GET", "$url/extras/statuses/?q=$status", $headers);
     my $data = @{$result->{results}}[0];
     my $internalid = $data->{id};
     return $internalid;
 }
 
+sub is_ip_gateway {
+    my ($url, $ip, $headers, $noerr) = @_;
+
+    my $result = eval {
+	return PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers);
+    };
+    if ($@) {
+	die "error while checking if $ip is a gateway" if !$noerr;
+    }
+
+    my $data = @{$result->{results}}[0];
+    my $description = $data->{description};
+    my $is_gateway = 1 if $description eq 'gateway';
+    return $is_gateway;
+}
+
 1;
-- 
2.39.5



[-- Attachment #2: Type: text/plain, Size: 160 bytes --]

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

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2025-02-20 13:30 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <20250108121529.5813-1-lou.lecrivain@wdz.de>
2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 1/7] ipam: nautobot support initial commit Lou Lecrivain via pve-devel
2025-02-19 16:36   ` Hannes Dürr
2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 2/7] ipam: nautobot: implement plain prefix allocation Lou Lecrivain via pve-devel
2025-02-19 16:37   ` Hannes Dürr
2025-02-20 13:24   ` Hannes Dürr
2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 3/7] ipam: nautobot: add testing for nautobot plugin Lou Lecrivain via pve-devel
2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors Lou Lecrivain via pve-devel
2025-02-19 16:37   ` Hannes Dürr
2025-02-19 17:18   ` Hannes Dürr
2025-02-20 13:27   ` Hannes Dürr
2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 5/7] ipam: nautobot: add checks for prefix deletion Lou Lecrivain via pve-devel
2025-02-19 16:37   ` Hannes Dürr
2025-02-19 17:15   ` Hannes Dürr
2025-02-20 13:29   ` Hannes Dürr
2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 6/7] ipam: nautobot: add documentation Lou Lecrivain via pve-devel
2025-01-08 12:15 ` [pve-devel] SPAM: [PATCH pve-network v2 7/7] ipam: nautobot: systematically use namespace Lou Lecrivain via pve-devel
     [not found] ` <20250108121529.5813-3-lou.lecrivain@wdz.de>
2025-02-19 17:08   ` [pve-devel] SPAM: [PATCH pve-network v2 2/7] ipam: nautobot: implement plain prefix allocation Hannes Dürr
     [not found] <20250108120903.5344-1-lou.lecrivain@wdz.de>
2025-01-08 12:08 ` [pve-devel] SPAM: [PATCH pve-network v2 4/7] ipam: nautobot: base plugin + enhance errors Lou Lecrivain via pve-devel

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal