Skip to content

Expand tests to cover ssh logic #315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,6 @@ jobs:
- python-version: "3.11"
db-backend: "postgresql"
nautobot-version: "2.2.3"
# - python-version: "3.12"
# db-backend: "mysql"
# nautobot-version: "stable"
runs-on: "ubuntu-22.04"
env:
INVOKE_NAUTOBOT_DEVICE_ONBOARDING_PYTHON_VER: "${{ matrix.python-version }}"
Expand Down
1 change: 1 addition & 0 deletions changes/315.housekeeping
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added fake SSH devices to tests to increase coverage.
8 changes: 7 additions & 1 deletion nautobot_device_onboarding/nornir_plays/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import logging
from json.decoder import JSONDecodeError

import jinja2
from django.template import engines
from django.utils.module_loading import import_string
from jdiff import extract_data_from_json
Expand Down Expand Up @@ -106,7 +107,12 @@ def extract_and_post_process(parsed_command_output, yaml_command_element, j2_dat
# j2 context data changes obj(hostname) -> extracted_value for post_processor
j2_data_context["obj"] = extracted_value
template = j2_env.from_string(yaml_command_element["post_processor"])
extracted_processed = template.render(**j2_data_context)
try:
extracted_processed = template.render(**j2_data_context)
except jinja2.exceptions.UndefinedError:
raise ValueError(
f"Failure Jinja parsing, context: {j2_data_context}. processor: {yaml_command_element['post_processor']}"
)
else:
extracted_processed = extracted_value
post_processed_data = normalize_processed_data(extracted_processed, iter_type)
Expand Down
69 changes: 69 additions & 0 deletions nautobot_device_onboarding/tests/fakenos/custom_ios.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
name: "tweaked_cisco_ios"
initial_prompt: "{base_prompt}>"
enable_prompt: "{base_prompt}#"
config_prompt: "{base_prompt}(config)#"
commands:
enable:
output: "null"
new_prompt: "{base_prompt}#"
help: "enter enable mode"
prompt: "{base_prompt}>"
show interfaces:
output:
"TenGigabitEthernet1/1/15 is up, line protocol is down (disabled)\n Hardware\
\ is Ten Gigabit Ethernet Port, address is 6c41.6aba.b44e (bia 6c41.6aba.b44e)\n\
\ Internet address is 127.0.0.1/32\n MTU 1500 bytes, BW 10000000 Kbit/sec,\
\ DLY 10 usec,\n reliability 255/255, txload 1/255, rxload 1/255\n Encapsulation\
\ ARPA, loopback not set\n Keepalive set (10 sec)\n Full-duplex, Auto-speed,\
\ link type is auto, media type is No XCVR\n input flow-control is off, output\
\ flow-control is off\n ARP type: ARPA, ARP Timeout 04:00:00\n Last input\
\ never, output never, output hang never\n Last clearing of \"show interface\"\
\ counters never\n Input queue: 0/2000/0/0 (size/max/drops/flushes); Total\
\ output drops: 0\n Queueing strategy: fifo\n Output queue: 0/40 (size/max)\n\
\ 5 minute input rate 0 bits/sec, 0 packets/sec\n 5 minute output rate 0\
\ bits/sec, 0 packets/sec\n 0 packets input, 0 bytes, 0 no buffer\n \
\ Received 0 broadcasts (0 multicasts)\n 0 runts, 0 giants, 0 throttles\n\
\ 0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored\n 0 input packets\
\ with dribble condition detected\n 0 packets output, 0 bytes, 0 underruns\n\
\ 0 output errors, 0 collisions, 1 interface resets\n 0 unknown protocol\
\ drops\n 0 babbles, 0 late collision, 0 deferred\n 0 lost carrier,\
\ 0 no carrier\n 0 output buffer failures, 0 output buffers swapped out"
help: "execute the command 'show interfaces'"
prompt:
- "{base_prompt}>"
- "{base_prompt}#"
show version:
output:
"Cisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.8(3)M2,\
\ RELEASE SOFTWARE (fc2)\nTechnical Support: http://www.cisco.com/techsupport\n\
\ Copyright (c) 1986-2019 by Cisco Systems, Inc.\nCompiled Thu 28-Mar-19 14:06\
\ by prod_rel_team\n\n\nROM: Bootstrap program is IOSv\n\nfake-ios-01 uptime is 1\
\ week, 3 days, 16 hours, 11 minutes\nSystem returned to ROM by reload\nSystem\
\ image file is \"flash0:/vios-adventerprisek9-m\"\nLast reload reason: Unknown\
\ reason\n \n\n\nThis product contains cryptographic features and is subject\
\ to United\n States and local country laws governing import, export, transfer\
\ and\nuse. Delivery of Cisco cryptographic products does not imply\nthird-party\
\ authority to import, export, distribute or use encryption.\nImporters, exporters,\
\ distributors and users are responsible for\ncompliance with U.S. and local\
\ country laws. By using this product you\nagree to comply with applicable laws\
\ and regulations. If you are unable\nto comply with U.S. and local laws, return\
\ this product immediately.\n \nA summary of U.S. laws governing Cisco cryptographic\
\ products may be found at:\nhttp://www.cisco.com/wwl/export/crypto/tool/stqrg.html\n\
\nIf you require further assistance please contact us by sending email to\n\
export@cisco.com.\n \nCisco IOSv (revision 1.0) with with 460137K/62464K bytes\
\ of memory.\nProcessor board ID 991UCMIHG4UAJ1J010CQG\n4 Gigabit Ethernet interfaces\n\
DRAM configuration is 72 bits wide with parity disabled.\n256K bytes of non-volatile\
\ configuration memory.\n2097152K bytes of ATA System CompactFlash 0 (Read/Write)\n\
0K bytes of ATA CompactFlash 1 (Read/Write)\n11217K bytes of ATA CompactFlash\
\ 2 (Read/Write)\n 0K bytes of ATA CompactFlash 3 (Read/Write)\n\n\n\nConfiguration\
\ register is 0x0"
help: "execute the command 'show version'"
prompt:
- "{base_prompt}>"
- "{base_prompt}#"
_default_:
output: "% Invalid input detected at '^' marker."
help: "Output to print for unknown commands"
terminal width 511: {"output":"", "help":"Set terminal width to 511"}
terminal length 0: {"output":"", "help":"Set terminal length to 0"}
59 changes: 59 additions & 0 deletions nautobot_device_onboarding/tests/test_jobs.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
"""Test Jobs."""

import os
from unittest.mock import ANY, patch

from django.core.files.base import ContentFile
from django.test import override_settings
from fakenos import FakeNOS
from fakenos.core.host import Host
from nautobot.apps.testing import create_job_result_and_run_job
from nautobot.core.testing import TransactionTestCase
from nautobot.dcim.models import Device, Interface, Manufacturer, Platform
from nautobot.extras.choices import JobResultStatusChoices
from nautobot.extras.models import FileProxy
from nautobot.ipam.models import IPAddress

from nautobot_device_onboarding import jobs
from nautobot_device_onboarding.tests import utils
Expand Down Expand Up @@ -174,6 +179,10 @@ class SSOTSyncNetworkDataTestCase(TransactionTestCase):

databases = ("default", "job_logs")

@classmethod
def setUpClass(cls):
super().setUpClass()

def setUp(self): # pylint: disable=invalid-name
"""Initialize test case."""
# Setup Nautobot Objects
Expand Down Expand Up @@ -236,3 +245,53 @@ def test_sync_network_data__success(self, device_data):

if interface_data["vrf"]:
self.assertEqual(interface.vrf.name, interface_data["vrf"]["name"])

@override_settings(CELERY_TASK_ALWAYS_EAGER=True)
@patch.dict("os.environ", {"DEVICE_USER": "admin", "DEVICE_PASS": "admin"})
def test_sync_network_devices_with_full_ssh(self):
"""Use the fakeNOS library to expand test coverage to cover SSH connectivity."""
job_form_inputs = {
"debug": False,
"connectivity_test": False,
"dryrun": False,
"csv_file": None,
"location": self.testing_objects["location"].pk,
"namespace": self.testing_objects["namespace"].pk,
"ip_addresses": "127.0.0.1",
"port": 6222,
"timeout": 30,
"set_mgmt_only": True,
"update_devices_without_primary_ip": True,
"device_role": self.testing_objects["device_role"].pk,
"device_status": self.testing_objects["status"].pk,
"interface_status": self.testing_objects["status"].pk,
"ip_address_status": self.testing_objects["status"].pk,
"default_prefix_status": self.testing_objects["status"].pk,
"secrets_group": self.testing_objects["secrets_group"].pk,
"platform": self.testing_objects["platform_1"].pk,
"memory_profiling": False,
}
current_file_path = os.path.dirname(os.path.abspath(__file__))
fake_ios_inventory = {
"hosts": {
"dev1": {
"username": "admin",
"password": "admin",
"platform": "tweaked_cisco_ios",
"port": 6222,
}
}
}
# This is hacky, theres clearly a bug in the fakenos library
# https://github.com/fakenos/fakenos/issues/19
with patch.object(Host, "_check_if_platform_is_supported"):
with FakeNOS(
inventory=fake_ios_inventory, plugins=[os.path.join(current_file_path, "fakenos/custom_ios.yaml")]
):
create_job_result_and_run_job(
module="nautobot_device_onboarding.jobs", name="SSOTSyncDevices", **job_form_inputs
)

newly_imported_device = Device.objects.get(name="fake-ios-01")
self.assertEqual(str(IPAddress.objects.get(id=newly_imported_device.primary_ip4_id)), "127.0.0.1/32")
self.assertEqual(newly_imported_device.serial, "991UCMIHG4UAJ1J010CQG")
37 changes: 35 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ mkdocstrings = "0.25.2"
mkdocstrings-python = "1.10.8"
griffe = "1.1.1"
towncrier = "~23.6.0"
# Used in integration tests
fakenos = { git = "https://github.com/fakenos/fakenos", branch = "master" }

[tool.poetry.extras]
all = [
Expand Down