public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling
@ 2025-03-07 17:43 Stefan Hanreich
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 2/8] ipam: netbox: implement deleting subnets Stefan Hanreich
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Stefan Hanreich @ 2025-03-07 17:43 UTC (permalink / raw)
  To: pve-devel

Create a helper method that abstracts the common code used in making
netbox requests. Move all api_request incovations over to using the
helper method. This saves us from writing lots of repeated code.

This also updates the helpers and introduces error checking there.
Helpers didn't catch any errors and the invoking methods didn't as
well. This meant that functions with $noerr set to 1 would still error
out. We now pass $noerr to the helper functions and they behave the
same as the parent methods. This requires some additional checks in
the call sites of the helpers.

Also canonicalize all URLs, since Netbox does that and it saves us a
redirect.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/Ipams/NetboxPlugin.pm | 241 +++++++++++-----------
 1 file changed, 126 insertions(+), 115 deletions(-)

diff --git a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
index 18089b7..9fef3dc 100644
--- a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
@@ -25,6 +25,21 @@ sub options {
     };
 }
 
+sub netbox_api_request {
+    my ($config, $method, $path, $params) = @_;
+
+    return PVE::Network::SDN::api_request(
+	$method,
+	"$config->{url}${path}",
+	[
+	    'Content-Type' => 'application/json; charset=UTF-8',
+	    'Authorization' => "token $config->{token}"
+	],
+	$params,
+	$config->{fingerprint},
+    );
+}
+
 # Plugin implementation
 
 sub add_subnet {
@@ -32,62 +47,42 @@ sub add_subnet {
 
     my $cidr = $subnet->{cidr};
     my $gateway = $subnet->{gateway};
-    my $url = $plugin_config->{url};
-    my $token = $plugin_config->{token};
-    my $fingerprint = $plugin_config->{fingerprint};
-    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
-
-    my $internalid = get_prefix_id($url, $cidr, $headers, $fingerprint);
 
-    #create subnet
-    if (!$internalid) {
-
-	my $params = { prefix => $cidr };
+    if (get_prefix_id($plugin_config, $cidr, $noerr)) {
+	return if $noerr;
+	die "prefix $cidr already exists in netbox";
+    }
 
-	eval {
-	    my $result = PVE::Network::SDN::api_request(
-		"POST", "$url/ipam/prefixes/", $headers, $params, $fingerprint );
-	};
-	if ($@) {
-	    die "error add subnet to ipam: $@" if !$noerr;
-	}
+    eval {
+	netbox_api_request($plugin_config, "POST", "/ipam/prefixes/", {
+	    prefix => $cidr
+	});
+    };
+    if ($@) {
+	return if $noerr;
+	die "error adding subnet to ipam: $@";
     }
-   
 }
 
 sub del_subnet {
     my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
 
     my $cidr = $subnet->{cidr};
-    my $url = $plugin_config->{url};
-    my $token = $plugin_config->{token};
-    my $gateway = $subnet->{gateway};
-    my $fingerprint = $plugin_config->{fingerprint};
-    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
 
-    my $internalid = get_prefix_id($url, $cidr, $headers, $fingerprint);
-    return if !$internalid;
+    my $internalid = get_prefix_id($plugin_config, $cidr, $noerr);
 
     return; #fixme: check that prefix is empty exluding gateway, before delete
 
     eval {
-	PVE::Network::SDN::api_request(
-	    "DELETE", "$url/ipam/prefixes/$internalid/", $headers, undef, $fingerprint);
+	netbox_api_request($plugin_config, "DELETE", "/ipam/prefixes/$internalid/");
     };
-    if ($@) {
-	die "error deleting subnet from ipam: $@" if !$noerr;
-    }
-
+    die "error deleting subnet from 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 $token = $plugin_config->{token};
-    my $fingerprint = $plugin_config->{fingerprint};
-    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
 
     my $description = undef;
     if ($is_gateway) {
@@ -96,18 +91,18 @@ sub add_ip {
 	$description = "mac:$mac";
     }
 
-    my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description };
-
     eval {
-	PVE::Network::SDN::api_request(
-	    "POST", "$url/ipam/ip-addresses/", $headers, $params, $fingerprint);
+	netbox_api_request($plugin_config, "POST", "/ipam/ip-addresses/", {
+	    address => "$ip/$mask",
+	    dns_name => $hostname,
+	    description => $description,
+	});
     };
 
     if ($@) {
 	if ($is_gateway) {
-	    if (!is_ip_gateway($url, $ip, $headers, $fingerprint) && !$noerr) {
-		die "error add subnet ip to ipam: ip $ip already exist: $@";
-	    }
+	    die "error add subnet ip to ipam: ip $ip already exist: $@"
+		if !is_ip_gateway($plugin_config, $ip, $noerr);
 	} elsif (!$noerr) {
 	    die "error add subnet ip to ipam: ip already exist: $@";
 	}
@@ -118,11 +113,6 @@ 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 $token = $plugin_config->{token};
-    my $section = $plugin_config->{section};
-    my $fingerprint = $plugin_config->{fingerprint};
-    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
 
     my $description = undef;
     if ($is_gateway) {
@@ -131,14 +121,20 @@ sub update_ip {
 	$description = "mac:$mac";
     }
 
-    my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description };
+    my $ip_id = get_ip_id($plugin_config, $ip, $noerr);
 
-    my $ip_id = get_ip_id($url, $ip, $headers, $fingerprint);
-    die "can't find ip $ip in ipam" if !$ip_id;
+    # definedness check, because ID could be 0
+    if (!defined($ip_id)) {
+	return if $noerr;
+	die "could not find id for ip address $ip";
+    }
 
     eval {
-	PVE::Network::SDN::api_request(
-	    "PATCH", "$url/ipam/ip-addresses/$ip_id/", $headers, $params, $fingerprint);
+	netbox_api_request($plugin_config, "PATCH", "/ipam/ip-addresses/$ip_id/", {
+	    address => "$ip/$mask",
+	    dns_name => $hostname,
+	    description => $description,
+	});
     };
     if ($@) {
 	die "error update ip $ip : $@" if !$noerr;
@@ -150,20 +146,22 @@ sub add_next_freeip {
 
     my $cidr = $subnet->{cidr};
 
-    my $url = $plugin_config->{url};
-    my $token = $plugin_config->{token};
-    my $fingerprint = $plugin_config->{fingerprint};
-    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
+    my $internalid = get_prefix_id($plugin_config, $cidr, $noerr);
 
-    my $internalid = get_prefix_id($url, $cidr, $headers, $fingerprint);
+    # definedness check, because ID could be 0
+    if (!defined($internalid)) {
+	return if $noerr;
+	die "could not find id for prefix $cidr";
+    }
 
     my $description = "mac:$mac" if $mac;
 
-    my $params = { dns_name => $hostname, description => $description };
-
     eval {
-	my $result = PVE::Network::SDN::api_request(
-	    "POST", "$url/ipam/prefixes/$internalid/available-ips/", $headers, $params, $fingerprint);
+	my $result = netbox_api_request($plugin_config, "POST", "/ipam/prefixes/$internalid/available-ips/", {
+	    dns_name => $hostname,
+	    description => $description,
+	});
+
 	my ($ip, undef) = split(/\//, $result->{address});
 	return $ip;
     };
@@ -176,19 +174,22 @@ sub add_next_freeip {
 sub add_range_next_freeip {
     my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
 
-    my $url = $plugin_config->{url};
-    my $token = $plugin_config->{token};
-    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
-    my $fingerprint = $plugin_config->{fingerprint};
+    my $internalid = get_iprange_id($plugin_config, $range, $noerr);
 
-    my $internalid = get_iprange_id($url, $range, $headers, $fingerprint);
-    my $description = "mac:$data->{mac}" if $data->{mac};
+    # definedness check, because ID could be 0
+    if (!defined($internalid)) {
+	return if $noerr;
+	die "could not find id for ip range $range->{'start-address'}:$range->{'end-address'}";
+    }
 
-    my $params = { dns_name => $data->{hostname}, description => $description };
+    my $description = "mac:$data->{mac}" if $data->{mac};
 
     eval {
-	my $result = PVE::Network::SDN::api_request(
-	    "POST", "$url/ipam/ip-ranges/$internalid/available-ips/", $headers, $params, $fingerprint);
+	my $result = netbox_api_request($plugin_config, "POST", "/ipam/ip-ranges/$internalid/available-ips/", {
+	    dns_name => $data->{hostname},
+	    description => $description,
+	});
+
 	my ($ip, undef) = split(/\//, $result->{address});
 	print "found ip free $ip in range $range->{'start-address'}-$range->{'end-address'}\n" if $ip;
 	return $ip;
@@ -204,16 +205,14 @@ sub del_ip {
 
     return if !$ip;
 
-    my $url = $plugin_config->{url};
-    my $token = $plugin_config->{token};
-    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
-    my $fingerprint = $plugin_config->{fingerprint};
-
-    my $ip_id = get_ip_id($url, $ip, $headers, $fingerprint);
-    die "can't find ip $ip in ipam" if !$ip_id;
+    my $ip_id = get_ip_id($plugin_config, $ip, $noerr);
+    if (!defined($ip_id)) {
+	warn "could not find id for ip $ip";
+	return;
+    }
 
     eval {
-	PVE::Network::SDN::api_request("DELETE", "$url/ipam/ip-addresses/$ip_id/", $headers, undef, $fingerprint);
+	netbox_api_request($plugin_config, "DELETE", "/ipam/ip-addresses/$ip_id/");
     };
     if ($@) {
 	die "error delete ip $ip : $@" if !$noerr;
@@ -221,18 +220,14 @@ sub del_ip {
 }
 
 sub get_ips_from_mac {
-    my ($class, $plugin_config, $mac, $zoneid) = @_;
-
-    my $url = $plugin_config->{url};
-    my $token = $plugin_config->{token};
-    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
-    my $fingerprint = $plugin_config->{fingerprint};
+    my ($class, $plugin_config, $mac, $zoneid, $noerr) = @_;
 
     my $ip4 = undef;
     my $ip6 = undef;
 
-    my $data = PVE::Network::SDN::api_request(
-	"GET", "$url/ipam/ip-addresses/?description__ic=$mac", $headers, undef, $fingerprint);
+    my $data = eval {
+	netbox_api_request($plugin_config, "GET", "/ipam/ip-addresses/?description__ic=$mac/");
+    };
 
     for my $ip (@{$data->{results}}) {
 	if ($ip->{family}->{value} == 4 && !$ip4) {
@@ -247,18 +242,10 @@ sub get_ips_from_mac {
     return ($ip4, $ip6);
 }
 
-
 sub verify_api {
     my ($class, $plugin_config) = @_;
 
-    my $url = $plugin_config->{url};
-    my $token = $plugin_config->{token};
-    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
-    my $fingerprint = $plugin_config->{fingerprint};
-
-    eval {
-	PVE::Network::SDN::api_request("GET", "$url/ipam/aggregates/", $headers, undef, $fingerprint);
-    };
+    eval { netbox_api_request($plugin_config, "GET", "/ipam/aggregates/"); };
     if ($@) {
 	die "Can't connect to netbox api: $@";
     }
@@ -266,47 +253,71 @@ sub verify_api {
 
 sub on_update_hook {
     my ($class, $plugin_config) = @_;
-
-    PVE::Network::SDN::Ipams::NetboxPlugin::verify_api($class, $plugin_config);
+    verify_api($class, $plugin_config);
 }
 
-#helpers
-
+# helpers
 sub get_prefix_id {
-    my ($url, $cidr, $headers, $fingerprint) = @_;
-    my $result = PVE::Network::SDN::api_request(
-	"GET", "$url/ipam/prefixes/?q=$cidr", $headers, undef, $fingerprint);
+    my ($config, $cidr, $noerr) = @_;
+
+    # we need to supply any IP inside the prefix, without supplying the mask, so
+    # just take the one from the cidr
+    my ($ip, undef) = split(/\//, $cidr);
+
+    my $result = eval { netbox_api_request($config, "GET", "/ipam/prefixes/?q=$ip/") };
+    if ($@) {
+	return if $noerr;
+	die "could not obtain ID for prefix $cidr: $@";
+    }
+
     my $data = @{$result->{results}}[0];
     my $internalid = $data->{id};
     return $internalid;
 }
 
 sub get_iprange_id {
-    my ($url, $range, $headers, $fingerprint) = @_;
-    my $result = PVE::Network::SDN::api_request(
-	"GET",
-	"$url/ipam/ip-ranges/?start_address=$range->{'start-address'}&end_address=$range->{'end-address'}",
-	$headers,
-	undef,
-	$fingerprint
-    );
+    my ($config, $range, $noerr) = @_;
+
+    my $result = eval {
+	netbox_api_request(
+	    $config,
+	    "GET",
+	    "/ipam/ip-ranges/?start_address=$range->{'start-address'}&end_address=$range->{'end-address'}",
+	);
+    };
+    if ($@) {
+	return if $noerr;
+	die "could not obtain ID for IP range $range->{'start-address'}:$range->{'end-address'}: $@";
+    }
+
     my $data = @{$result->{results}}[0];
     my $internalid = $data->{id};
     return $internalid;
 }
 
 sub get_ip_id {
-    my ($url, $ip, $headers, $fingerprint) = @_;
-    my $result = PVE::Network::SDN::api_request(
-	"GET", "$url/ipam/ip-addresses/?q=$ip", $headers, undef, $fingerprint);
+    my ($config, $ip, $noerr) = @_;
+
+    my $result = eval { netbox_api_request($config, "GET", "/ipam/ip-addresses/?q=$ip/") };
+    if ($@) {
+	return if $noerr;
+	die "could not obtain ID for IP $ip: $@";
+    }
+
     my $data = @{$result->{results}}[0];
     my $ip_id = $data->{id};
     return $ip_id;
 }
 
 sub is_ip_gateway {
-    my ($url, $ip, $headers, $fingerprint) = @_;
-    my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers, undef, $fingerprint);
+    my ($config, $ip, $noerr) = @_;
+
+    my $result = eval { netbox_api_request($config, "GET", "/ipam/ip-addresses/?q=$ip/") };
+    if ($@) {
+	return if $noerr;
+	die "could not obtain ipam entry for address $ip: $@";
+    }
+
     my $data = @{$result->{data}}[0];
     my $description = $data->{description};
     my $is_gateway = 1 if $description eq 'gateway';
-- 
2.39.5


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


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

* [pve-devel] [PATCH pve-network 2/8] ipam: netbox: implement deleting subnets
  2025-03-07 17:43 [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling Stefan Hanreich
@ 2025-03-07 17:43 ` Stefan Hanreich
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 3/8] ipam: netbox: simplify helpers Stefan Hanreich
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Hanreich @ 2025-03-07 17:43 UTC (permalink / raw)
  To: pve-devel

Deleting a subnet did not delete any created entities in Netbox.
Implement deletion of a subnet by deleting all entities that are
created in Netbox upon creation of a subnet.

We are checking for any leftover IP assignments before deleting the
prefix, so we do not accidentally delete any manually created IP
assignments.

This method tries to check for any possible errors before editing the
entities. There is still a small window where external changes can
occur that lead to errors. We are touching multiple entities here, so
in case of errors users have to fix their Netbox instance manually.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/Ipams/NetboxPlugin.pm | 34 ++++++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
index 9fef3dc..ea0fd0c 100644
--- a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
@@ -71,7 +71,22 @@ sub del_subnet {
 
     my $internalid = get_prefix_id($plugin_config, $cidr, $noerr);
 
-    return; #fixme: check that prefix is empty exluding gateway, before delete
+    # definedness check, because ID could be 0
+    if (!defined($internalid)) {
+	warn "could not find id for ip prefix $cidr";
+	return;
+    }
+
+    if (!is_prefix_empty($plugin_config, $cidr, $noerr)) {
+	return if $noerr;
+	die "not deleting prefix $cidr because it still contains entries";
+    }
+
+    # last IP is assumed to be the gateway, delete it
+    if (!$class->del_ip($plugin_config, $subnetid, $subnet, $subnet->{gateway}, $noerr)) {
+	return if $noerr;
+	die "could not delete gateway ip from subnet $subnetid";
+    }
 
     eval {
 	netbox_api_request($plugin_config, "DELETE", "/ipam/prefixes/$internalid/");
@@ -217,6 +232,8 @@ sub del_ip {
     if ($@) {
 	die "error delete ip $ip : $@" if !$noerr;
     }
+
+    return 1;
 }
 
 sub get_ips_from_mac {
@@ -324,6 +341,21 @@ sub is_ip_gateway {
     return $is_gateway;
 }
 
+sub is_prefix_empty {
+    my ($config, $cidr, $noerr) = @_;
+
+    # everywhere else we canonicalize to trailing slashes, but this endpoint
+    # DOES NOT WORK with trailing slashes...
+    my $result = eval { netbox_api_request($config, "GET", "/ipam/ip-addresses/?parent=$cidr") };
+    if ($@) {
+	return if $noerr;
+	die "could not query children for prefix $cidr: $@";
+    }
+
+    # checking against 1, because we do not count the gateway
+    return scalar(@{$result->{results}}) <= 1;
+}
+
 1;
 
 
-- 
2.39.5


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


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

* [pve-devel] [PATCH pve-network 3/8] ipam: netbox: simplify helpers
  2025-03-07 17:43 [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling Stefan Hanreich
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 2/8] ipam: netbox: implement deleting subnets Stefan Hanreich
@ 2025-03-07 17:43 ` Stefan Hanreich
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 4/8] ipam: netbox: no conditional assignments for descriptions Stefan Hanreich
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Hanreich @ 2025-03-07 17:43 UTC (permalink / raw)
  To: pve-devel

The helpers had lots of unnecessary intermediate assignments, which we
can just simplify.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/Ipams/NetboxPlugin.pm | 13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)

diff --git a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
index ea0fd0c..99ff3d9 100644
--- a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
@@ -288,8 +288,7 @@ sub get_prefix_id {
     }
 
     my $data = @{$result->{results}}[0];
-    my $internalid = $data->{id};
-    return $internalid;
+    return $data->{id};
 }
 
 sub get_iprange_id {
@@ -308,8 +307,7 @@ sub get_iprange_id {
     }
 
     my $data = @{$result->{results}}[0];
-    my $internalid = $data->{id};
-    return $internalid;
+    return $data->{id};
 }
 
 sub get_ip_id {
@@ -322,8 +320,7 @@ sub get_ip_id {
     }
 
     my $data = @{$result->{results}}[0];
-    my $ip_id = $data->{id};
-    return $ip_id;
+    return $data->{id};
 }
 
 sub is_ip_gateway {
@@ -336,9 +333,7 @@ sub is_ip_gateway {
     }
 
     my $data = @{$result->{data}}[0];
-    my $description = $data->{description};
-    my $is_gateway = 1 if $description eq 'gateway';
-    return $is_gateway;
+    return $data->{description} eq 'gateway';
 }
 
 sub is_prefix_empty {
-- 
2.39.5


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


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

* [pve-devel] [PATCH pve-network 4/8] ipam: netbox: no conditional assignments for descriptions
  2025-03-07 17:43 [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling Stefan Hanreich
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 2/8] ipam: netbox: implement deleting subnets Stefan Hanreich
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 3/8] ipam: netbox: simplify helpers Stefan Hanreich
@ 2025-03-07 17:43 ` Stefan Hanreich
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 5/8] ipam: netbox: add error handling to get_ips_from_mac Stefan Hanreich
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Hanreich @ 2025-03-07 17:43 UTC (permalink / raw)
  To: pve-devel

While it should make practically no difference, it opens up potential
errors in the future, so just remove the conditional assignments and
explicitly define the variable as undef, so the intention is more
clear.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/Ipams/NetboxPlugin.pm | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
index 99ff3d9..6e1f78b 100644
--- a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
@@ -169,7 +169,8 @@ sub add_next_freeip {
 	die "could not find id for prefix $cidr";
     }
 
-    my $description = "mac:$mac" if $mac;
+    my $description = undef;
+    $description = "mac:$mac" if $mac;
 
     eval {
 	my $result = netbox_api_request($plugin_config, "POST", "/ipam/prefixes/$internalid/available-ips/", {
@@ -197,7 +198,8 @@ sub add_range_next_freeip {
 	die "could not find id for ip range $range->{'start-address'}:$range->{'end-address'}";
     }
 
-    my $description = "mac:$data->{mac}" if $data->{mac};
+    my $description = undef;
+    $description = "mac:$data->{mac}" if $data->{mac};
 
     eval {
 	my $result = netbox_api_request($plugin_config, "POST", "/ipam/ip-ranges/$internalid/available-ips/", {
-- 
2.39.5


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


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

* [pve-devel] [PATCH pve-network 5/8] ipam: netbox: add error handling to get_ips_from_mac
  2025-03-07 17:43 [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling Stefan Hanreich
                   ` (2 preceding siblings ...)
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 4/8] ipam: netbox: no conditional assignments for descriptions Stefan Hanreich
@ 2025-03-07 17:43 ` Stefan Hanreich
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 6/8] partial fix #5496: ipam: netbox: properly return allocated ip Stefan Hanreich
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Hanreich @ 2025-03-07 17:43 UTC (permalink / raw)
  To: pve-devel

This function did not catch any possible errors, nor respect the
$noerr parameter.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/Ipams/NetboxPlugin.pm | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
index 6e1f78b..0d9d5a3 100644
--- a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
@@ -247,6 +247,10 @@ sub get_ips_from_mac {
     my $data = eval {
 	netbox_api_request($plugin_config, "GET", "/ipam/ip-addresses/?description__ic=$mac/");
     };
+    if ($@) {
+	return if $noerr;
+	die "could not query ip address entry for mac $mac: $@";
+    }
 
     for my $ip (@{$data->{results}}) {
 	if ($ip->{family}->{value} == 4 && !$ip4) {
-- 
2.39.5


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


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

* [pve-devel] [PATCH pve-network 6/8] partial fix #5496: ipam: netbox: properly return allocated ip
  2025-03-07 17:43 [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling Stefan Hanreich
                   ` (3 preceding siblings ...)
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 5/8] ipam: netbox: add error handling to get_ips_from_mac Stefan Hanreich
@ 2025-03-07 17:43 ` Stefan Hanreich
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 7/8] partial fix #5496: ipam: netbox: create / delete ip ranges for dhcp Stefan Hanreich
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Hanreich @ 2025-03-07 17:43 UTC (permalink / raw)
  To: pve-devel

The netbox integration did not properly return the IP when creating
the entries in netbox. This lead to errors on starting the guest,
stating that an IP could not be allocated.

Originally-by: lou lecrivain <lou.lecrivain@wdz.de>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/Ipams/NetboxPlugin.pm | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
index 0d9d5a3..6d462d2 100644
--- a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
@@ -172,7 +172,7 @@ sub add_next_freeip {
     my $description = undef;
     $description = "mac:$mac" if $mac;
 
-    eval {
+    my $ip = eval {
 	my $result = netbox_api_request($plugin_config, "POST", "/ipam/prefixes/$internalid/available-ips/", {
 	    dns_name => $hostname,
 	    description => $description,
@@ -185,6 +185,8 @@ sub add_next_freeip {
     if ($@) {
 	die "can't find free ip in subnet $cidr: $@" if !$noerr;
     }
+
+    return $ip;
 }
 
 sub add_range_next_freeip {
@@ -201,7 +203,7 @@ sub add_range_next_freeip {
     my $description = undef;
     $description = "mac:$data->{mac}" if $data->{mac};
 
-    eval {
+    my $ip = eval {
 	my $result = netbox_api_request($plugin_config, "POST", "/ipam/ip-ranges/$internalid/available-ips/", {
 	    dns_name => $data->{hostname},
 	    description => $description,
@@ -215,6 +217,8 @@ sub add_range_next_freeip {
     if ($@) {
 	die "can't find free ip in range $range->{'start-address'}-$range->{'end-address'}: $@" if !$noerr;
     }
+
+    return $ip;
 }
 
 sub del_ip {
-- 
2.39.5


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


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

* [pve-devel] [PATCH pve-network 7/8] partial fix #5496: ipam: netbox: create / delete ip ranges for dhcp
  2025-03-07 17:43 [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling Stefan Hanreich
                   ` (4 preceding siblings ...)
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 6/8] partial fix #5496: ipam: netbox: properly return allocated ip Stefan Hanreich
@ 2025-03-07 17:43 ` Stefan Hanreich
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 8/8] partial fix #5496: subnet: ipam: add update_subnet hook Stefan Hanreich
  2025-03-10  8:52 ` [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling Stefan Hanreich
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Hanreich @ 2025-03-07 17:43 UTC (permalink / raw)
  To: pve-devel

We use the IP ranges of netbox to represent the dhcp ranges. We were
already querying the IP ranges for a IP when starting a guest, but we
never created the IP ranges in the first place. Additionally implement
deleting the IP ranges when the subnet gets deleted.

These methods try to check for any possible errors before editing the
entities. There is still a small window where external changes can
occur that lead to errors. We are touching multiple entities here, so
in case of errors users have to fix their Netbox instance manually.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/Ipams/NetboxPlugin.pm | 45 +++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
index 6d462d2..b696dd4 100644
--- a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
@@ -40,6 +40,33 @@ sub netbox_api_request {
     );
 }
 
+sub add_dhcp_range {
+    my ($config, $dhcp_range, $noerr) = @_;
+
+    my $result = eval {
+	netbox_api_request($config, "POST", "/ipam/ip-ranges/", {
+	    start_address => $dhcp_range->{'start-address'},
+	    end_address => $dhcp_range->{'end-address'},
+	});
+    };
+    if ($@) {
+	return if $noerr;
+	die "could not create ip range $dhcp_range->{'start-address'}:$dhcp_range->{'end-address'}: $@";
+    }
+
+    return $result->{id};
+}
+
+sub del_dhcp_range {
+    my ($config, $id, $noerr) = @_;
+
+    eval {
+	netbox_api_request($config, "DELETE", "/ipam/ip-ranges/$id/");
+    };
+
+    die "could not create dhcp range: $@" if $@ && !$noerr;
+}
+
 # Plugin implementation
 
 sub add_subnet {
@@ -62,6 +89,11 @@ sub add_subnet {
 	return if $noerr;
 	die "error adding subnet to ipam: $@";
     }
+
+    my $dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet);
+    for my $dhcp_range (@$dhcp_ranges) {
+	add_dhcp_range($plugin_config, $dhcp_range, $noerr);
+    }
 }
 
 sub del_subnet {
@@ -88,6 +120,19 @@ sub del_subnet {
 	die "could not delete gateway ip from subnet $subnetid";
     }
 
+    my $dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet);
+    for my $dhcp_range (@$dhcp_ranges) {
+	my $internalid = get_iprange_id($plugin_config, $dhcp_range, $noerr);
+
+	# definedness check, because ID could be 0
+	if (!defined($internalid)) {
+	    warn "could not find id for ip range $dhcp_range->{'start-address'}:$dhcp_range->{'end-address'}";
+	    next;
+	}
+
+	del_dhcp_range($plugin_config, $internalid, $noerr);
+    }
+
     eval {
 	netbox_api_request($plugin_config, "DELETE", "/ipam/prefixes/$internalid/");
     };
-- 
2.39.5


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


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

* [pve-devel] [PATCH pve-network 8/8] partial fix #5496: subnet: ipam: add update_subnet hook
  2025-03-07 17:43 [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling Stefan Hanreich
                   ` (5 preceding siblings ...)
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 7/8] partial fix #5496: ipam: netbox: create / delete ip ranges for dhcp Stefan Hanreich
@ 2025-03-07 17:43 ` Stefan Hanreich
  2025-03-10  8:52 ` [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling Stefan Hanreich
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Hanreich @ 2025-03-07 17:43 UTC (permalink / raw)
  To: pve-devel

Because of how the Netbox IPAM plugin works (utilizing IP ranges to
represent DHCP ranges), we need a hook in the IPAM plugin that runs on
updates to the subnet because DHCP ranges can be edited. The update
hook in Netbox checks which DHCP ranges got added and which got
deleted and then performs the respective changes in the Netbox IPAM.
This operates under the assumption that DHCP ranges do not overlap
(which is not supported by Netbox anyway).

Only Netbox needs to do work on update, so we can leave this as noop
in phpIPAM and the PVE IPAM, because they have no notion of IP ranges
or similar entities. phpIPAM doesn't support DHCP ranges at all and
PVE IPAM simply uses DHCP ranges as a constraint when allocating an
IP.

I decided on this approach over just creating IP ranges on demand when
assigning IPs, because this keeps Netbox clean and in sync with the
PVE state. It doesn't leave remnants of IP ranges in the Netbox
database, which can lead to errors when trying to create IP ranges
that overlap with IP ranges that already existed in an SDN subnet.

This method tries to check for any possible errors before editing the
entities. There is still a small window where external changes can
occur that lead to errors. We are touching multiple entities here, so
in case of errors users have to fix their Netbox instance manually.

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/Ipams/NetboxPlugin.pm  | 54 ++++++++++++++++++++++
 src/PVE/Network/SDN/Ipams/PVEPlugin.pm     |  5 ++
 src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm |  5 ++
 src/PVE/Network/SDN/Ipams/Plugin.pm        |  6 +++
 src/PVE/Network/SDN/SubnetPlugin.pm        |  6 ++-
 src/PVE/Network/SDN/Subnets.pm             | 12 +++++
 6 files changed, 87 insertions(+), 1 deletion(-)

diff --git a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
index b696dd4..4984e5a 100644
--- a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
@@ -96,6 +96,60 @@ sub add_subnet {
     }
 }
 
+sub update_subnet {
+    my ($class, $plugin_config, $subnetid, $subnet, $old_subnet, $noerr) = @_;
+
+    # old subnet in SubnetPlugin hook has already parsed dhcp-ranges
+    # new subnet doesn't
+    my $old_dhcp_ranges = $old_subnet->{'dhcp-range'};
+    my $new_dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet);
+
+    my $hash_range = sub {
+	my ($dhcp_range) = @_;
+	"$dhcp_range->{'start-address'} - $dhcp_range->{'end-address'}"
+    };
+
+    my $old_lookup = {};
+    for my $dhcp_range (@$old_dhcp_ranges) {
+	my $hash = $hash_range->($dhcp_range);
+	$old_lookup->{$hash} = undef;
+    }
+
+    my $new_lookup = {};
+    for my $dhcp_range (@$new_dhcp_ranges) {
+	my $hash = $hash_range->($dhcp_range);
+	$new_lookup->{$hash} = undef;
+    }
+
+    my $to_delete_ids = ();
+
+    # delete first so we don't get errors with overlapping ranges
+    for my $dhcp_range (@$old_dhcp_ranges) {
+	my $hash = $hash_range->($dhcp_range);
+
+	if (exists($new_lookup->{$hash})) {
+	    next;
+	}
+
+	my $internalid = get_iprange_id($plugin_config, $dhcp_range, $noerr);
+
+	# definedness check, because ID could be 0
+	if (!defined($internalid)) {
+	    warn "could not find id for ip range $dhcp_range->{'start-address'}:$dhcp_range->{'end-address'}";
+	    next;
+	}
+
+	del_dhcp_range($plugin_config, $internalid, $noerr);
+    }
+
+    for my $dhcp_range (@$new_dhcp_ranges) {
+	my $hash = $hash_range->($dhcp_range);
+
+	add_dhcp_range($plugin_config, $dhcp_range, $noerr)
+	    if !exists($old_lookup->{$hash});
+    }
+}
+
 sub del_subnet {
     my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
 
diff --git a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm
index 742f1b1..59ad4ea 100644
--- a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm
@@ -82,6 +82,11 @@ sub add_subnet {
     die "$@" if $@;
 }
 
+sub update_subnet {
+    my ($class, $plugin_config, $subnetid, $subnet, $old_subnet, $noerr) = @_;
+    # we don't need to do anything on update
+}
+
 sub only_gateway_remains {
     my ($ips) = @_;
 
diff --git a/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm b/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm
index df5048d..8ee430a 100644
--- a/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm
@@ -67,6 +67,11 @@ sub add_subnet {
     }
 }
 
+sub update_subnet {
+    my ($class, $plugin_config, $subnetid, $subnet, $old_subnet, $noerr) = @_;
+    # we don't need to do anything on update
+}
+
 sub del_subnet {
     my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
 
diff --git a/src/PVE/Network/SDN/Ipams/Plugin.pm b/src/PVE/Network/SDN/Ipams/Plugin.pm
index ab4cae8..6190c24 100644
--- a/src/PVE/Network/SDN/Ipams/Plugin.pm
+++ b/src/PVE/Network/SDN/Ipams/Plugin.pm
@@ -75,6 +75,12 @@ sub add_subnet {
     die "please implement inside plugin";
 }
 
+sub update_subnet {
+    my ($class, $plugin_config, $subnetid, $subnet, $old_subnet, $noerr) = @_;
+
+    die "please implement inside plugin";
+}
+
 sub del_subnet {
     my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
 
diff --git a/src/PVE/Network/SDN/SubnetPlugin.pm b/src/PVE/Network/SDN/SubnetPlugin.pm
index b911d69..8a79eae 100644
--- a/src/PVE/Network/SDN/SubnetPlugin.pm
+++ b/src/PVE/Network/SDN/SubnetPlugin.pm
@@ -201,7 +201,11 @@ sub on_update_hook {
     validate_dhcp_ranges($subnet);
 
     if ($ipam) {
-	PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet);
+	if ($old_subnet) {
+	    PVE::Network::SDN::Subnets::update_subnet($zone, $subnetid, $subnet, $old_subnet);
+	} else {
+	    PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet);
+	}
 
 	#don't register gateway for pointopoint
 	return if $pointopoint;
diff --git a/src/PVE/Network/SDN/Subnets.pm b/src/PVE/Network/SDN/Subnets.pm
index e2c8c9c..18847c2 100644
--- a/src/PVE/Network/SDN/Subnets.pm
+++ b/src/PVE/Network/SDN/Subnets.pm
@@ -194,6 +194,18 @@ sub add_subnet {
     $plugin->add_subnet($plugin_config, $subnetid, $subnet);
 }
 
+sub update_subnet {
+    my ($zone, $subnetid, $subnet, $old_subnet) = @_;
+
+    my $ipam = $zone->{ipam};
+    return if !$ipam;
+
+    my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+    my $plugin_config = $ipam_cfg->{ids}->{$ipam};
+    my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
+    $plugin->update_subnet($plugin_config, $subnetid, $subnet, $old_subnet);
+}
+
 sub del_subnet {
     my ($zone, $subnetid, $subnet) = @_;
 
-- 
2.39.5


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


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

* Re: [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling
  2025-03-07 17:43 [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling Stefan Hanreich
                   ` (6 preceding siblings ...)
  2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 8/8] partial fix #5496: subnet: ipam: add update_subnet hook Stefan Hanreich
@ 2025-03-10  8:52 ` Stefan Hanreich
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Hanreich @ 2025-03-10  8:52 UTC (permalink / raw)
  To: pve-devel

Superseded-by:
https://lore.proxmox.com/pve-devel/20250310085103.30549-1-s.hanreich@proxmox.com/T/

On 3/7/25 18:43, Stefan Hanreich wrote:
> Create a helper method that abstracts the common code used in making
> netbox requests. Move all api_request incovations over to using the
> helper method. This saves us from writing lots of repeated code.
> 
> This also updates the helpers and introduces error checking there.
> Helpers didn't catch any errors and the invoking methods didn't as
> well. This meant that functions with $noerr set to 1 would still error
> out. We now pass $noerr to the helper functions and they behave the
> same as the parent methods. This requires some additional checks in
> the call sites of the helpers.
> 
> Also canonicalize all URLs, since Netbox does that and it saves us a
> redirect.
> 
> Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
> ---
>  src/PVE/Network/SDN/Ipams/NetboxPlugin.pm | 241 +++++++++++-----------
>  1 file changed, 126 insertions(+), 115 deletions(-)
> 
> diff --git a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
> index 18089b7..9fef3dc 100644
> --- a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
> +++ b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
> @@ -25,6 +25,21 @@ sub options {
>      };
>  }
>  
> +sub netbox_api_request {
> +    my ($config, $method, $path, $params) = @_;
> +
> +    return PVE::Network::SDN::api_request(
> +	$method,
> +	"$config->{url}${path}",
> +	[
> +	    'Content-Type' => 'application/json; charset=UTF-8',
> +	    'Authorization' => "token $config->{token}"
> +	],
> +	$params,
> +	$config->{fingerprint},
> +    );
> +}
> +
>  # Plugin implementation
>  
>  sub add_subnet {
> @@ -32,62 +47,42 @@ sub add_subnet {
>  
>      my $cidr = $subnet->{cidr};
>      my $gateway = $subnet->{gateway};
> -    my $url = $plugin_config->{url};
> -    my $token = $plugin_config->{token};
> -    my $fingerprint = $plugin_config->{fingerprint};
> -    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
> -
> -    my $internalid = get_prefix_id($url, $cidr, $headers, $fingerprint);
>  
> -    #create subnet
> -    if (!$internalid) {
> -
> -	my $params = { prefix => $cidr };
> +    if (get_prefix_id($plugin_config, $cidr, $noerr)) {
> +	return if $noerr;
> +	die "prefix $cidr already exists in netbox";
> +    }
>  
> -	eval {
> -	    my $result = PVE::Network::SDN::api_request(
> -		"POST", "$url/ipam/prefixes/", $headers, $params, $fingerprint );
> -	};
> -	if ($@) {
> -	    die "error add subnet to ipam: $@" if !$noerr;
> -	}
> +    eval {
> +	netbox_api_request($plugin_config, "POST", "/ipam/prefixes/", {
> +	    prefix => $cidr
> +	});
> +    };
> +    if ($@) {
> +	return if $noerr;
> +	die "error adding subnet to ipam: $@";
>      }
> -   
>  }
>  
>  sub del_subnet {
>      my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
>  
>      my $cidr = $subnet->{cidr};
> -    my $url = $plugin_config->{url};
> -    my $token = $plugin_config->{token};
> -    my $gateway = $subnet->{gateway};
> -    my $fingerprint = $plugin_config->{fingerprint};
> -    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
>  
> -    my $internalid = get_prefix_id($url, $cidr, $headers, $fingerprint);
> -    return if !$internalid;
> +    my $internalid = get_prefix_id($plugin_config, $cidr, $noerr);
>  
>      return; #fixme: check that prefix is empty exluding gateway, before delete
>  
>      eval {
> -	PVE::Network::SDN::api_request(
> -	    "DELETE", "$url/ipam/prefixes/$internalid/", $headers, undef, $fingerprint);
> +	netbox_api_request($plugin_config, "DELETE", "/ipam/prefixes/$internalid/");
>      };
> -    if ($@) {
> -	die "error deleting subnet from ipam: $@" if !$noerr;
> -    }
> -
> +    die "error deleting subnet from 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 $token = $plugin_config->{token};
> -    my $fingerprint = $plugin_config->{fingerprint};
> -    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
>  
>      my $description = undef;
>      if ($is_gateway) {
> @@ -96,18 +91,18 @@ sub add_ip {
>  	$description = "mac:$mac";
>      }
>  
> -    my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description };
> -
>      eval {
> -	PVE::Network::SDN::api_request(
> -	    "POST", "$url/ipam/ip-addresses/", $headers, $params, $fingerprint);
> +	netbox_api_request($plugin_config, "POST", "/ipam/ip-addresses/", {
> +	    address => "$ip/$mask",
> +	    dns_name => $hostname,
> +	    description => $description,
> +	});
>      };
>  
>      if ($@) {
>  	if ($is_gateway) {
> -	    if (!is_ip_gateway($url, $ip, $headers, $fingerprint) && !$noerr) {
> -		die "error add subnet ip to ipam: ip $ip already exist: $@";
> -	    }
> +	    die "error add subnet ip to ipam: ip $ip already exist: $@"
> +		if !is_ip_gateway($plugin_config, $ip, $noerr);
>  	} elsif (!$noerr) {
>  	    die "error add subnet ip to ipam: ip already exist: $@";
>  	}
> @@ -118,11 +113,6 @@ 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 $token = $plugin_config->{token};
> -    my $section = $plugin_config->{section};
> -    my $fingerprint = $plugin_config->{fingerprint};
> -    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
>  
>      my $description = undef;
>      if ($is_gateway) {
> @@ -131,14 +121,20 @@ sub update_ip {
>  	$description = "mac:$mac";
>      }
>  
> -    my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description };
> +    my $ip_id = get_ip_id($plugin_config, $ip, $noerr);
>  
> -    my $ip_id = get_ip_id($url, $ip, $headers, $fingerprint);
> -    die "can't find ip $ip in ipam" if !$ip_id;
> +    # definedness check, because ID could be 0
> +    if (!defined($ip_id)) {
> +	return if $noerr;
> +	die "could not find id for ip address $ip";
> +    }
>  
>      eval {
> -	PVE::Network::SDN::api_request(
> -	    "PATCH", "$url/ipam/ip-addresses/$ip_id/", $headers, $params, $fingerprint);
> +	netbox_api_request($plugin_config, "PATCH", "/ipam/ip-addresses/$ip_id/", {
> +	    address => "$ip/$mask",
> +	    dns_name => $hostname,
> +	    description => $description,
> +	});
>      };
>      if ($@) {
>  	die "error update ip $ip : $@" if !$noerr;
> @@ -150,20 +146,22 @@ sub add_next_freeip {
>  
>      my $cidr = $subnet->{cidr};
>  
> -    my $url = $plugin_config->{url};
> -    my $token = $plugin_config->{token};
> -    my $fingerprint = $plugin_config->{fingerprint};
> -    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
> +    my $internalid = get_prefix_id($plugin_config, $cidr, $noerr);
>  
> -    my $internalid = get_prefix_id($url, $cidr, $headers, $fingerprint);
> +    # definedness check, because ID could be 0
> +    if (!defined($internalid)) {
> +	return if $noerr;
> +	die "could not find id for prefix $cidr";
> +    }
>  
>      my $description = "mac:$mac" if $mac;
>  
> -    my $params = { dns_name => $hostname, description => $description };
> -
>      eval {
> -	my $result = PVE::Network::SDN::api_request(
> -	    "POST", "$url/ipam/prefixes/$internalid/available-ips/", $headers, $params, $fingerprint);
> +	my $result = netbox_api_request($plugin_config, "POST", "/ipam/prefixes/$internalid/available-ips/", {
> +	    dns_name => $hostname,
> +	    description => $description,
> +	});
> +
>  	my ($ip, undef) = split(/\//, $result->{address});
>  	return $ip;
>      };
> @@ -176,19 +174,22 @@ sub add_next_freeip {
>  sub add_range_next_freeip {
>      my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
>  
> -    my $url = $plugin_config->{url};
> -    my $token = $plugin_config->{token};
> -    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
> -    my $fingerprint = $plugin_config->{fingerprint};
> +    my $internalid = get_iprange_id($plugin_config, $range, $noerr);
>  
> -    my $internalid = get_iprange_id($url, $range, $headers, $fingerprint);
> -    my $description = "mac:$data->{mac}" if $data->{mac};
> +    # definedness check, because ID could be 0
> +    if (!defined($internalid)) {
> +	return if $noerr;
> +	die "could not find id for ip range $range->{'start-address'}:$range->{'end-address'}";
> +    }
>  
> -    my $params = { dns_name => $data->{hostname}, description => $description };
> +    my $description = "mac:$data->{mac}" if $data->{mac};
>  
>      eval {
> -	my $result = PVE::Network::SDN::api_request(
> -	    "POST", "$url/ipam/ip-ranges/$internalid/available-ips/", $headers, $params, $fingerprint);
> +	my $result = netbox_api_request($plugin_config, "POST", "/ipam/ip-ranges/$internalid/available-ips/", {
> +	    dns_name => $data->{hostname},
> +	    description => $description,
> +	});
> +
>  	my ($ip, undef) = split(/\//, $result->{address});
>  	print "found ip free $ip in range $range->{'start-address'}-$range->{'end-address'}\n" if $ip;
>  	return $ip;
> @@ -204,16 +205,14 @@ sub del_ip {
>  
>      return if !$ip;
>  
> -    my $url = $plugin_config->{url};
> -    my $token = $plugin_config->{token};
> -    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
> -    my $fingerprint = $plugin_config->{fingerprint};
> -
> -    my $ip_id = get_ip_id($url, $ip, $headers, $fingerprint);
> -    die "can't find ip $ip in ipam" if !$ip_id;
> +    my $ip_id = get_ip_id($plugin_config, $ip, $noerr);
> +    if (!defined($ip_id)) {
> +	warn "could not find id for ip $ip";
> +	return;
> +    }
>  
>      eval {
> -	PVE::Network::SDN::api_request("DELETE", "$url/ipam/ip-addresses/$ip_id/", $headers, undef, $fingerprint);
> +	netbox_api_request($plugin_config, "DELETE", "/ipam/ip-addresses/$ip_id/");
>      };
>      if ($@) {
>  	die "error delete ip $ip : $@" if !$noerr;
> @@ -221,18 +220,14 @@ sub del_ip {
>  }
>  
>  sub get_ips_from_mac {
> -    my ($class, $plugin_config, $mac, $zoneid) = @_;
> -
> -    my $url = $plugin_config->{url};
> -    my $token = $plugin_config->{token};
> -    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
> -    my $fingerprint = $plugin_config->{fingerprint};
> +    my ($class, $plugin_config, $mac, $zoneid, $noerr) = @_;
>  
>      my $ip4 = undef;
>      my $ip6 = undef;
>  
> -    my $data = PVE::Network::SDN::api_request(
> -	"GET", "$url/ipam/ip-addresses/?description__ic=$mac", $headers, undef, $fingerprint);
> +    my $data = eval {
> +	netbox_api_request($plugin_config, "GET", "/ipam/ip-addresses/?description__ic=$mac/");
> +    };
>  
>      for my $ip (@{$data->{results}}) {
>  	if ($ip->{family}->{value} == 4 && !$ip4) {
> @@ -247,18 +242,10 @@ sub get_ips_from_mac {
>      return ($ip4, $ip6);
>  }
>  
> -
>  sub verify_api {
>      my ($class, $plugin_config) = @_;
>  
> -    my $url = $plugin_config->{url};
> -    my $token = $plugin_config->{token};
> -    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
> -    my $fingerprint = $plugin_config->{fingerprint};
> -
> -    eval {
> -	PVE::Network::SDN::api_request("GET", "$url/ipam/aggregates/", $headers, undef, $fingerprint);
> -    };
> +    eval { netbox_api_request($plugin_config, "GET", "/ipam/aggregates/"); };
>      if ($@) {
>  	die "Can't connect to netbox api: $@";
>      }
> @@ -266,47 +253,71 @@ sub verify_api {
>  
>  sub on_update_hook {
>      my ($class, $plugin_config) = @_;
> -
> -    PVE::Network::SDN::Ipams::NetboxPlugin::verify_api($class, $plugin_config);
> +    verify_api($class, $plugin_config);
>  }
>  
> -#helpers
> -
> +# helpers
>  sub get_prefix_id {
> -    my ($url, $cidr, $headers, $fingerprint) = @_;
> -    my $result = PVE::Network::SDN::api_request(
> -	"GET", "$url/ipam/prefixes/?q=$cidr", $headers, undef, $fingerprint);
> +    my ($config, $cidr, $noerr) = @_;
> +
> +    # we need to supply any IP inside the prefix, without supplying the mask, so
> +    # just take the one from the cidr
> +    my ($ip, undef) = split(/\//, $cidr);
> +
> +    my $result = eval { netbox_api_request($config, "GET", "/ipam/prefixes/?q=$ip/") };
> +    if ($@) {
> +	return if $noerr;
> +	die "could not obtain ID for prefix $cidr: $@";
> +    }
> +
>      my $data = @{$result->{results}}[0];
>      my $internalid = $data->{id};
>      return $internalid;
>  }
>  
>  sub get_iprange_id {
> -    my ($url, $range, $headers, $fingerprint) = @_;
> -    my $result = PVE::Network::SDN::api_request(
> -	"GET",
> -	"$url/ipam/ip-ranges/?start_address=$range->{'start-address'}&end_address=$range->{'end-address'}",
> -	$headers,
> -	undef,
> -	$fingerprint
> -    );
> +    my ($config, $range, $noerr) = @_;
> +
> +    my $result = eval {
> +	netbox_api_request(
> +	    $config,
> +	    "GET",
> +	    "/ipam/ip-ranges/?start_address=$range->{'start-address'}&end_address=$range->{'end-address'}",
> +	);
> +    };
> +    if ($@) {
> +	return if $noerr;
> +	die "could not obtain ID for IP range $range->{'start-address'}:$range->{'end-address'}: $@";
> +    }
> +
>      my $data = @{$result->{results}}[0];
>      my $internalid = $data->{id};
>      return $internalid;
>  }
>  
>  sub get_ip_id {
> -    my ($url, $ip, $headers, $fingerprint) = @_;
> -    my $result = PVE::Network::SDN::api_request(
> -	"GET", "$url/ipam/ip-addresses/?q=$ip", $headers, undef, $fingerprint);
> +    my ($config, $ip, $noerr) = @_;
> +
> +    my $result = eval { netbox_api_request($config, "GET", "/ipam/ip-addresses/?q=$ip/") };
> +    if ($@) {
> +	return if $noerr;
> +	die "could not obtain ID for IP $ip: $@";
> +    }
> +
>      my $data = @{$result->{results}}[0];
>      my $ip_id = $data->{id};
>      return $ip_id;
>  }
>  
>  sub is_ip_gateway {
> -    my ($url, $ip, $headers, $fingerprint) = @_;
> -    my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers, undef, $fingerprint);
> +    my ($config, $ip, $noerr) = @_;
> +
> +    my $result = eval { netbox_api_request($config, "GET", "/ipam/ip-addresses/?q=$ip/") };
> +    if ($@) {
> +	return if $noerr;
> +	die "could not obtain ipam entry for address $ip: $@";
> +    }
> +
>      my $data = @{$result->{data}}[0];
>      my $description = $data->{description};
>      my $is_gateway = 1 if $description eq 'gateway';



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


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

end of thread, other threads:[~2025-03-10  8:53 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-03-07 17:43 [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling Stefan Hanreich
2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 2/8] ipam: netbox: implement deleting subnets Stefan Hanreich
2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 3/8] ipam: netbox: simplify helpers Stefan Hanreich
2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 4/8] ipam: netbox: no conditional assignments for descriptions Stefan Hanreich
2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 5/8] ipam: netbox: add error handling to get_ips_from_mac Stefan Hanreich
2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 6/8] partial fix #5496: ipam: netbox: properly return allocated ip Stefan Hanreich
2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 7/8] partial fix #5496: ipam: netbox: create / delete ip ranges for dhcp Stefan Hanreich
2025-03-07 17:43 ` [pve-devel] [PATCH pve-network 8/8] partial fix #5496: subnet: ipam: add update_subnet hook Stefan Hanreich
2025-03-10  8:52 ` [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling Stefan Hanreich

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