Add configurable Proxmox API timeout

This commit is contained in:
2026-07-10 11:14:29 +02:00
parent 847cc13a8f
commit c2276c2774
9 changed files with 75 additions and 11 deletions
+1
View File
@@ -12,3 +12,4 @@
| 0.1.7 | 4.4.0 | 4.6.x | | 0.1.7 | 4.4.0 | 4.6.x |
| 0.1.8 | 4.4.0 | 4.6.x | | 0.1.8 | 4.4.0 | 4.6.x |
| 0.2.0 | 4.4.0 | 4.6.x | | 0.2.0 | 4.4.0 | 4.6.x |
| 0.2.1 | 4.4.0 | 4.6.x |
+2
View File
@@ -52,6 +52,8 @@ sudo systemctl restart netbox netbox-rq
6. Optional Regex-Filter und Sync-Intervall setzen. 6. Optional Regex-Filter und Sync-Intervall setzen.
7. Auf der Detailseite `Sync jetzt starten` ausfuehren. 7. Auf der Detailseite `Sync jetzt starten` ausfuehren.
Bei selbstsignierten Proxmox-Zertifikaten `Validate SSL` deaktiviert lassen. Wenn Proxmox langsam antwortet, kann `API timeout` am Endpoint erhoeht werden.
Wenn `Sync interval minutes` gesetzt ist, prueft ein Systemjob alle fuenf Minuten, welche Endpoints faellig sind, und stellt die eigentlichen Sync-Jobs in die Queue. Wenn `Sync interval minutes` gesetzt ist, prueft ein Systemjob alle fuenf Minuten, welche Endpoints faellig sind, und stellt die eigentlichen Sync-Jobs in die Queue.
Ab Version `0.1.8` wird beim Speichern eines Endpoints zusaetzlich ein wiederkehrender NetBox-Job fuer genau diesen Endpoint geplant. Nach einem Update vorhandene Endpoints einmal speichern oder `netbox-rq` neu starten und bis zum naechsten Systemjob-Lauf warten. Ab Version `0.1.8` wird beim Speichern eines Endpoints zusaetzlich ein wiederkehrender NetBox-Job fuer genau diesen Endpoint geplant. Nach einem Update vorhandene Endpoints einmal speichern oder `netbox-rq` neu starten und bis zum naechsten Systemjob-Lauf warten.
+1 -1
View File
@@ -5,7 +5,7 @@ class VMwareImporterConfig(PluginConfig):
name = "netbox_vmware_importer" name = "netbox_vmware_importer"
verbose_name = "Virtualization Importer" verbose_name = "Virtualization Importer"
description = "Synchronize VMware vSphere and Proxmox VE virtual machines into NetBox." description = "Synchronize VMware vSphere and Proxmox VE virtual machines into NetBox."
version = "0.2.0" version = "0.2.1"
author = "Internal NetBox Team" author = "Internal NetBox Team"
base_url = "vmware-importer" base_url = "vmware-importer"
min_version = "4.4.0" min_version = "4.4.0"
+13 -1
View File
@@ -46,7 +46,18 @@ class VCenterEndpointForm(NetBoxModelForm):
fieldsets = ( fieldsets = (
FieldSet("name", "slug", "enabled", "comments", name=_("Endpoint")), FieldSet("name", "slug", "enabled", "comments", name=_("Endpoint")),
FieldSet("provider", "host", "port", "username", "auth_method", "token_name", "password", "validate_ssl", name=_("Connection")), FieldSet(
"provider",
"host",
"port",
"username",
"auth_method",
"token_name",
"password",
"validate_ssl",
"request_timeout_seconds",
name=_("Connection"),
),
FieldSet("tenant", "site", "cluster", name=_("NetBox target")), FieldSet("tenant", "site", "cluster", name=_("NetBox target")),
FieldSet( FieldSet(
"include_name_regex", "include_name_regex",
@@ -77,6 +88,7 @@ class VCenterEndpointForm(NetBoxModelForm):
"token_name", "token_name",
"password", "password",
"validate_ssl", "validate_ssl",
"request_timeout_seconds",
"tenant", "tenant",
"site", "site",
"cluster", "cluster",
@@ -0,0 +1,23 @@
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("netbox_vmware_importer", "0003_add_proxmox_endpoint_fields"),
]
operations = [
migrations.AddField(
model_name="vcenterendpoint",
name="request_timeout_seconds",
field=models.PositiveIntegerField(
default=120,
help_text="HTTP/API read timeout in seconds.",
validators=[
django.core.validators.MinValueValidator(5),
django.core.validators.MaxValueValidator(600),
],
),
),
]
+6
View File
@@ -60,6 +60,11 @@ class VCenterEndpoint(JobsMixin, NetBoxModel):
default=False, default=False,
help_text=_("Validate the endpoint TLS certificate."), help_text=_("Validate the endpoint TLS certificate."),
) )
request_timeout_seconds = models.PositiveIntegerField(
default=120,
validators=[MinValueValidator(5), MaxValueValidator(600)],
help_text=_("HTTP/API read timeout in seconds."),
)
tenant = models.ForeignKey( tenant = models.ForeignKey(
to="tenancy.Tenant", to="tenancy.Tenant",
on_delete=models.PROTECT, on_delete=models.PROTECT,
@@ -180,6 +185,7 @@ class VCenterEndpoint(JobsMixin, NetBoxModel):
"auth_method", "auth_method",
"token_name", "token_name",
"validate_ssl", "validate_ssl",
"request_timeout_seconds",
"tenant", "tenant",
"site", "site",
"cluster", "cluster",
+24 -8
View File
@@ -14,8 +14,10 @@ from .choices import EndpointAuthMethodChoices, EndpointProviderChoices
try: try:
import requests import requests
from urllib3.exceptions import InsecureRequestWarning
except ImportError: # pragma: no cover - handled at runtime inside NetBox except ImportError: # pragma: no cover - handled at runtime inside NetBox
requests = None requests = None
InsecureRequestWarning = None
try: try:
from pyVim.connect import Disconnect, SmartConnect from pyVim.connect import Disconnect, SmartConnect
@@ -230,6 +232,7 @@ class ProxmoxClient:
self.endpoint = endpoint self.endpoint = endpoint
self.session = None self.session = None
self.base_url = f"https://{endpoint.host}:{endpoint.port}/api2/json" self.base_url = f"https://{endpoint.host}:{endpoint.port}/api2/json"
self.timeout = (10, endpoint.request_timeout_seconds or 120)
def __enter__(self): def __enter__(self):
if requests is None: if requests is None:
@@ -237,6 +240,8 @@ class ProxmoxClient:
self.session = requests.Session() self.session = requests.Session()
self.session.verify = self.endpoint.validate_ssl self.session.verify = self.endpoint.validate_ssl
if not self.endpoint.validate_ssl and InsecureRequestWarning is not None:
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
if self.endpoint.auth_method == EndpointAuthMethodChoices.METHOD_API_TOKEN: if self.endpoint.auth_method == EndpointAuthMethodChoices.METHOD_API_TOKEN:
token_id = self.endpoint.token_name if "!" in self.endpoint.token_name else f"{self.endpoint.username}!{self.endpoint.token_name}" token_id = self.endpoint.token_name if "!" in self.endpoint.token_name else f"{self.endpoint.username}!{self.endpoint.token_name}"
@@ -251,15 +256,14 @@ class ProxmoxClient:
self.session.close() self.session.close()
def _authenticate_with_password(self): def _authenticate_with_password(self):
response = self.session.post( response = self._request(
self._url("access/ticket"), "POST",
"access/ticket",
data={ data={
"username": self.endpoint.username, "username": self.endpoint.username,
"password": self.endpoint.password, "password": self.endpoint.password,
}, },
timeout=30,
) )
response.raise_for_status()
data = response.json().get("data") or {} data = response.json().get("data") or {}
ticket = data.get("ticket") ticket = data.get("ticket")
csrf_token = data.get("CSRFPreventionToken") csrf_token = data.get("CSRFPreventionToken")
@@ -274,9 +278,21 @@ class ProxmoxClient:
def _url(self, path): def _url(self, path):
return f"{self.base_url}/{path.strip('/')}" return f"{self.base_url}/{path.strip('/')}"
def _get(self, path, params=None): def _request(self, method, path, **kwargs):
response = self.session.get(self._url(path), params=params, timeout=30) url = self._url(path)
try:
response = self.session.request(method, url, timeout=self.timeout, **kwargs)
response.raise_for_status() response.raise_for_status()
return response
except requests.Timeout as exc:
raise ProxmoxConnectionError(
f"Proxmox API {method} {path} timed out after {self.timeout[1]} seconds."
) from exc
except requests.RequestException as exc:
raise ProxmoxConnectionError(f"Proxmox API {method} {path} failed: {exc}") from exc
def _get(self, path, params=None):
response = self._request("GET", path, params=params)
return response.json().get("data") return response.json().get("data")
def iter_virtual_machines(self): def iter_virtual_machines(self):
@@ -448,7 +464,7 @@ class ProxmoxClient:
def _merge_qemu_agent_interfaces(self, node, vmid, interfaces): def _merge_qemu_agent_interfaces(self, node, vmid, interfaces):
try: try:
response = self._get(f"nodes/{node}/qemu/{vmid}/agent/network-get-interfaces") or {} response = self._get(f"nodes/{node}/qemu/{vmid}/agent/network-get-interfaces") or {}
except requests.RequestException: except (requests.RequestException, ProxmoxConnectionError):
return return
agent_interfaces = response.get("result") if isinstance(response, dict) else response agent_interfaces = response.get("result") if isinstance(response, dict) else response
@@ -488,7 +504,7 @@ class ProxmoxClient:
def _get_qemu_guest_os(self, node, vmid): def _get_qemu_guest_os(self, node, vmid):
try: try:
response = self._get(f"nodes/{node}/qemu/{vmid}/agent/get-osinfo") or {} response = self._get(f"nodes/{node}/qemu/{vmid}/agent/get-osinfo") or {}
except requests.RequestException: except (requests.RequestException, ProxmoxConnectionError):
return "" return ""
result = response.get("result") if isinstance(response, dict) else None result = response.get("result") if isinstance(response, dict) else None
@@ -40,6 +40,10 @@
<th scope="row">Validate SSL</th> <th scope="row">Validate SSL</th>
<td>{{ object.validate_ssl|yesno:"Yes,No" }}</td> <td>{{ object.validate_ssl|yesno:"Yes,No" }}</td>
</tr> </tr>
<tr>
<th scope="row">API timeout</th>
<td>{{ object.request_timeout_seconds }} seconds</td>
</tr>
</table> </table>
</div> </div>
</div> </div>
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "netbox-vmware-importer" name = "netbox-vmware-importer"
version = "0.2.0" version = "0.2.1"
description = "NetBox plugin to synchronize VMware vSphere and Proxmox VE virtual machines into NetBox." description = "NetBox plugin to synchronize VMware vSphere and Proxmox VE virtual machines into NetBox."
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"