Skip to content

Commit 5c70cd2

Browse files
authored
Merge pull request #27 from networktocode/gfm-default-interface
Handle mgmt-interface-not-found plus other fixes
2 parents e9c9256 + 9a15536 commit 5c70cd2

File tree

8 files changed

+91
-33
lines changed

8 files changed

+91
-33
lines changed

netbox_onboarding/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class OnboardingConfig(PluginConfig):
3333
"create_device_type_if_missing": True,
3434
"create_device_role_if_missing": True,
3535
"default_device_role": "network",
36+
"default_management_interface": "PLACEHOLDER",
37+
"default_management_prefix_length": 0,
3638
}
3739
caching_config = {}
3840

netbox_onboarding/admin.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Administrative capabilities for netbox_onboarding plugin.
2+
3+
(c) 2020 Network To Code
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
13+
"""
14+
from django.contrib import admin
15+
from .models import OnboardingTask
16+
17+
18+
@admin.register(OnboardingTask)
19+
class OnboardingTaskAdmin(admin.ModelAdmin):
20+
"""Administrative view for managing OnboardingTask instances."""
21+
22+
list_display = (
23+
"pk",
24+
"group_id",
25+
"owner",
26+
"created_device",
27+
"ip_address",
28+
"site",
29+
"role",
30+
"device_type",
31+
"platform",
32+
"status",
33+
"message",
34+
"failed_reason",
35+
"port",
36+
"timeout",
37+
"created_on",
38+
)

netbox_onboarding/onboard.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import re
1818
import socket
1919

20-
from first import first
2120
from napalm import get_network_driver
2221
from napalm.base.exceptions import ConnectionException, CommandErrorException
2322

@@ -196,7 +195,11 @@ def get_platform_object_from_netbox(platform_slug):
196195

197196
return platform
198197

199-
def get_required_info(self):
198+
def get_required_info(
199+
self,
200+
default_mgmt_if=PLUGIN_SETTINGS["default_management_interface"],
201+
default_mgmt_pfxlen=PLUGIN_SETTINGS["default_management_prefix_length"],
202+
):
200203
"""Gather information from the network device that is needed to onboard the device into the NetBox system.
201204
202205
Raises:
@@ -217,6 +220,9 @@ def get_required_info(self):
217220
try:
218221
platform_slug = self.get_platform_slug()
219222
platform_object = self.get_platform_object_from_netbox(platform_slug=platform_slug)
223+
if self.ot.platform != platform_object:
224+
self.ot.platform = platform_object
225+
self.ot.save()
220226

221227
driver_name = platform_object.napalm_driver
222228

@@ -248,17 +254,18 @@ def get_required_info(self):
248254
# locate the interface assigned with the mgmt_ipaddr value and retain
249255
# the interface name and IP prefix-length so that we can use it later
250256
# when creating the IPAM IP-Address instance.
257+
# Note that in some cases (e.g., NAT) the mgmt_ipaddr may differ than
258+
# the interface addresses present on the device. We need to handle this.
251259

252-
try:
253-
mgmt_ifname, mgmt_pflen = first(
254-
(if_name, if_addr_data["prefix_length"])
255-
for if_name, if_data in ip_ifs.items()
256-
for if_addr, if_addr_data in if_data["ipv4"].items()
257-
if if_addr == mgmt_ipaddr
258-
)
260+
def get_mgmt_info():
261+
"""Get the interface name and prefix length for the management interface."""
262+
for if_name, if_data in ip_ifs.items():
263+
for if_addr, if_addr_data in if_data["ipv4"].items():
264+
if if_addr == mgmt_ipaddr:
265+
return (if_name, if_addr_data["prefix_length"])
266+
return (default_mgmt_if, default_mgmt_pfxlen)
259267

260-
except Exception as exc:
261-
raise OnboardException(reason="fail-general", message=str(exc))
268+
mgmt_ifname, mgmt_pflen = get_mgmt_info()
262269

263270
# retain the attributes that will be later used by NetBox processing.
264271

@@ -322,14 +329,14 @@ def ensure_device_type(
322329
# instance.
323330

324331
try:
325-
self.manufacturer = Manufacturer.objects.get(slug=self.netdev.vendor)
332+
self.manufacturer = Manufacturer.objects.get(slug=self.netdev.vendor.lower())
326333
except Manufacturer.DoesNotExist:
327334
if not create_manufacturer:
328335
raise OnboardException(
329336
reason="fail-config", message=f"ERROR manufacturer not found: {self.netdev.vendor}"
330337
)
331338

332-
self.manufacturer = Manufacturer.objects.create(name=self.netdev.vendor, slug=self.netdev.vendor)
339+
self.manufacturer = Manufacturer.objects.create(name=self.netdev.vendor, slug=self.netdev.vendor.lower())
333340
self.manufacturer.save()
334341

335342
# Now see if the device type (slug) already exists,
@@ -343,7 +350,9 @@ def ensure_device_type(
343350
logging.warning("device model is now: %s", self.netdev.model)
344351

345352
try:
346-
self.device_type = DeviceType.objects.get(slug=self.netdev.model)
353+
self.device_type = DeviceType.objects.get(slug=self.netdev.model.lower())
354+
self.netdev.ot.device_type = self.device_type.slug
355+
self.netdev.ot.save()
347356
except DeviceType.DoesNotExist:
348357
if not create_device_type:
349358
raise OnboardException(
@@ -352,9 +361,11 @@ def ensure_device_type(
352361

353362
logging.info("CREATE: device-type: %s", self.netdev.model)
354363
self.device_type = DeviceType.objects.create(
355-
slug=self.netdev.model, model=self.netdev.model.upper(), manufacturer=self.manufacturer
364+
slug=self.netdev.model.lower(), model=self.netdev.model.upper(), manufacturer=self.manufacturer
356365
)
357366
self.device_type.save()
367+
self.netdev.ot.device_type = self.device_type.slug
368+
self.netdev.ot.save()
358369
return
359370

360371
if self.device_type.manufacturer.id != self.manufacturer.id:
@@ -398,6 +409,7 @@ def ensure_device_role(
398409

399410
def ensure_device_instance(self):
400411
"""Ensure that the device instance exists in NetBox and is assigned the provided device role or DEFAULT_ROLE."""
412+
# TODO: this can create duplicate entries in NetBox...
401413
device, _ = Device.objects.get_or_create(
402414
name=self.netdev.hostname,
403415
device_type=self.device_type,
@@ -409,7 +421,7 @@ def ensure_device_instance(self):
409421
device.serial = self.netdev.serial_number
410422
device.save()
411423

412-
self.netdev.ot.device = device
424+
self.netdev.ot.created_device = device
413425
self.netdev.ot.save()
414426

415427
self.device = device

netbox_onboarding/tables.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,22 @@ class OnboardingTaskTable(BaseTable):
2020
"""Table for displaying OnboardingTask instances."""
2121

2222
site = tables.LinkColumn()
23+
platform = tables.LinkColumn()
24+
created_device = tables.LinkColumn()
2325

2426
class Meta(BaseTable.Meta): # noqa: D106 "Missing docstring in public nested class"
2527
model = OnboardingTask
26-
fields = ("pk", "created_on", "ip_address", "site", "platform", "device", "status", "failed_reason", "message")
28+
fields = (
29+
"pk",
30+
"created_on",
31+
"ip_address",
32+
"site",
33+
"platform",
34+
"created_device",
35+
"status",
36+
"failed_reason",
37+
"message",
38+
)
2739

2840

2941
class OnboardingTaskFeedBulkTable(BaseTable):

netbox_onboarding/tests/test_onboard.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def test_ensure_device_instance_not_exist(self):
117117

118118
nbk.ensure_device_instance()
119119
self.assertIsInstance(nbk.device, Device)
120-
self.assertEqual(nbk.device, nbk.netdev.ot.device)
120+
self.assertEqual(nbk.device, nbk.netdev.ot.created_device)
121121
self.assertEqual(nbk.device.serial, "123456")
122122

123123
def test_ensure_interface_not_exist(self):

netbox_onboarding/worker.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
limitations under the License.
1313
"""
1414
import logging
15+
import time
1516

1617
from django_rq import job
1718

@@ -29,7 +30,12 @@ def onboard_device(task_id, credentials):
2930
username = credentials.username
3031
password = credentials.password
3132

32-
ot = OnboardingTask.objects.get(id=task_id)
33+
try:
34+
ot = OnboardingTask.objects.get(id=task_id)
35+
except OnboardingTask.DoesNotExist:
36+
# TODO: maybe we started before the DB was done writing it, or maybe it was deleted out from under us?
37+
time.sleep(1)
38+
ot = OnboardingTask.objects.get(id=task_id)
3339

3440
logging.info("START: onboard device")
3541

@@ -54,6 +60,7 @@ def onboard_device(task_id, credentials):
5460
except Exception as exc:
5561
ot.status = OnboardingStatusChoices.STATUS_FAILED
5662
ot.failed_reason = OnboardingFailChoices.FAIL_GENERAL
63+
ot.message = str(exc)
5764
ot.save()
5865
raise
5966

poetry.lock

Lines changed: 1 addition & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ packages = [
1010
[tool.poetry.dependencies]
1111
python = "^3.6 || ^3.7 || ^3.8"
1212
invoke = "^1.4.1"
13-
first = "^2.0.2"
1413
napalm = "^2.5.0"
1514

1615
[tool.poetry.dev-dependencies]

0 commit comments

Comments
 (0)