From: Stefan Hanreich <s.hanreich@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH pve-network 1/8] ipam: netbox: factor out common api methods and unify error handling
Date: Fri, 7 Mar 2025 18:43:45 +0100 [thread overview]
Message-ID: <20250307174352.337597-1-s.hanreich@proxmox.com> (raw)
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
next reply other threads:[~2025-03-07 17:44 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-07 17:43 Stefan Hanreich [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250307174352.337597-1-s.hanreich@proxmox.com \
--to=s.hanreich@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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