all lists on 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal