* [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 inboxService provided by Proxmox Server Solutions GmbH | Privacy | Legal