Skip to content

Commit

Permalink
Releasing v24.11
Browse files Browse the repository at this point in the history
  • Loading branch information
lsheikal committed Nov 25, 2024
1 parent 2f2eb30 commit c414b37
Show file tree
Hide file tree
Showing 289 changed files with 4,703 additions and 5,897 deletions.
42 changes: 42 additions & 0 deletions pkgs/clean-pkg/changelog/2024/november.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--------------------------------------------------------------------------------
New
--------------------------------------------------------------------------------

* clean-pkg
* iosxe
* Added default `LOAD_IMAGE` template
* iosxe
* image_handler
* check if smu image is passed instead of base image in the image list
* Skip `install_image` if smu only image passed.


--------------------------------------------------------------------------------
Fix
--------------------------------------------------------------------------------

* stages/iosxe
* install image
* Ensure startup config is verified if install image is skipped.
* install image
* Updated _check_for_member_config to handle install image stage.

* iosxe
* Modified clean stages
* Fixed the usage of steps in the clean stages to ensure correct result rollup
* Modified copy_to_device
* Skip verifying free space on the device if skip_deletion is set to True

* apic
* Modified copy_to_device
* Skip verifying free space on the device if skip_deletion is set to True

* generic
* Modified copy_to_device
* Skip verifying free space on the device if skip_deletion is set to True

* recovery
* Modified recovery_processor
* Removed unused params from docstring


52 changes: 26 additions & 26 deletions pkgs/clean-pkg/sdk_generator/output/github_clean.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkgs/clean-pkg/src/genie/libs/clean/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
'''

# metadata
__version__ = '24.10'
__version__ = '24.11'
__author__ = 'Cisco Systems Inc.'
__contact__ = ['asg-genie-support@cisco.com', 'pyats-support-ext@cisco.com']
__copyright__ = 'Copyright (c) 2019, Cisco Systems Inc.'
Expand Down
2 changes: 0 additions & 2 deletions pkgs/clean-pkg/src/genie/libs/clean/recovery/recovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,6 @@ def recovery_processor(
grub_breakboot_char: <Character to send when grub_activity_pattern is matched, 'str'>
timeout: <Timeout in seconds to recover the device, 'int'>
recovery_password: <Device password after coming up, 'str'>
recovery_username: <Device username after coming up, 'str'>
recovery_en_password: <Device enable password after coming up, 'str'>
powercycler: <Should powercycler execute, 'bool'> (Default: True)
powercycler_delay: <Powercycler delay between on/off>, 'int'> (Default: 30)
reconnect_delay: <Once device recovered, delay before final reconnect>, 'int'> (Default: 60)
Expand Down
5 changes: 3 additions & 2 deletions pkgs/clean-pkg/src/genie/libs/clean/stages/apic/stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,9 @@ def copy_to_device(self, steps, device, origin, destination, protocol,
step.failed("Error while creating free space for "
"image on device {} {}".
format(device.name, dest))
else:
step.skipped(f"Skip verifying free space on the device '{device.name}'"
" because skip_deletion is set to True")

# Copy the file to the devices
for file, file_data in files_to_copy.items():
Expand Down Expand Up @@ -1030,8 +1033,6 @@ def copy_to_device(self, steps, device, origin, destination, protocol,
"Image file has been copied to device {} correctly"
" but cannot verify file size".format(device.name))

self.passed("Copy to device completed")


class ApplyConfiguration(BaseStage):
"""This stage executes the REST API against the device.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import unittest

from unittest.mock import Mock, call, ANY

from genie.libs.clean.stages.apic.stages import CopyToDevice
from genie.libs.clean.stages.tests.utils import create_test_device

from pyats.aetest.steps import Steps
from pyats.results import Passed
from pyats.topology import Testbed
from pyats.datastructures import AttrDict


class VerifyCopyToDevice(unittest.TestCase):

def setUp(self):
# Instantiate class object
self.cls = CopyToDevice()
self.cls.history = {'CopyToDevice': AttrDict({'parameters': {}})}

# Instantiate device object. This also sets up commonly needed
# attributes and Mock objects associated with the device.
self.device = create_test_device('PE1', os='apic', via='cli')

# And we want the connect method to be mocked.
# This simulates the pass case.
self.device.connect = Mock()


def test_copy_to_device_skip_deletion_true(self):
class MockExecute:

def __init__(self, *args, **kwargs):
self.data = {
'dir bootflash:': iter([
'''
Directory of bootflash:/
11 drwx 16384 Nov 25 2016 19:32:53 -07:00 lost+found
12 -rw- 0 Dec 13 2016 11:36:36 -07:00 ds_stats.txt
104417 drwx 4096 Apr 10 2017 09:09:11 -07:00 .prst_sync
80321 drwx 4096 Nov 25 2016 19:40:38 -07:00 .rollback_timer
64257 drwx 4096 Nov 25 2016 19:41:02 -07:00 .installer
48193 drwx 4096 Nov 25 2016 19:41:14 -07:00 virtual-instance-stby-sync
1940303872 bytes total (1036210176 bytes free)
''',
f'''
Directory of bootflash:/
11 drwx 16384 Nov 25 2016 19:32:53 -07:00 lost+found
12 -rw- 0 Dec 13 2016 11:36:36 -07:00 ds_stats.txt
104417 drwx 4096 Apr 10 2017 09:09:11 -07:00 .prst_sync
80321 drwx 4096 Nov 25 2016 19:40:38 -07:00 .rollback_timer
64257 drwx 4096 Nov 25 2016 19:41:02 -07:00 .installer
48193 drwx 4096 Nov 25 2016 19:41:14 -07:00 virtual-instance-stby-sync
8033 drwx 4096 Nov 25 2016 18:42:07 -07:00 test.bin
1940303872 bytes total (1036210176 bytes free)
'''
]),
'scp None@127.0.0.1:/path/test.bin bootflash:/test.bin': iter(['Copied file']),
'show version': iter([
'''
Role Pod Node Name Version
---------- ---------- ---------- ------------------------ --------------------
controller 1 1 msl-ifav205-ifc1 5.1(2e)
leaf 1 101 msl-ifav205-leaf1 n9000-15.1(2e)
spine 1 201 msl-ifav205-spine1 n9000-15.1(2e)
spine 1 202 msl-ifav205-spine2 n9000-14.2(2e)
'''
]),
'ls -l bootflash:': iter([
'''
total 6894908
lrwxrwxrwx 1 root root 12 Mar 23 23:36 aci -> /.aci/viewfs
-rw-r--r-- 1 admin admin 7060381696 Apr 7 01:41 aci-apic-dk9.5.1.2e.iso
lrwxrwxrwx 1 root root 13 Mar 23 23:36 debug -> /.aci/debugfs
lrwxrwxrwx 1 root root 11 Mar 23 23:36 mit -> /.aci/mitfs
lrwxrwxrwx 1 root root 11 Mar 23 2009 nonaci
'''
])
}

def __call__(self, cmd, *args, **kwargs):
output = next(self.data[cmd])
return output

mock_execute = MockExecute()

# And we want the execute method to be mocked with device console output.
self.device.execute = Mock(side_effect=mock_execute)

steps = Steps()

testbed = Testbed('mytb', servers={
'server1': {
'address': '127.0.0.1',
'protocol': 'scp'
}
})

self.device.testbed = testbed

# Call the method to be tested (clean step inside class)
with self.assertLogs(level='INFO') as log:
self.cls.copy_to_device(
steps=steps, device=self.device,
origin=dict(
files=[f'/path/test.bin'],
hostname='server1'
),
destination=dict(
directory='bootflash:'
),
protocol='scp',
verify_running_image=False,
skip_deletion=True
)
self.assertIn(f"INFO:pyats.aetest.steps.implementation:Skipped reason: Skip verifying free space on the device '{self.device.name}' because skip_deletion is set to True",
log.output)

# Check that the result is expected
self.assertEqual(Passed, steps.details[0].result)
self.device.execute.assert_has_calls([
call('show version'),
call('ls -l bootflash:'),
call(f'scp None@127.0.0.1:/path/test.bin bootflash:/test.bin',
prompt_recovery=True, timeout=300, reply=ANY,
error_pattern=ANY),
call('dir bootflash:')
])
39 changes: 15 additions & 24 deletions pkgs/clean-pkg/src/genie/libs/clean/stages/iosxe/cat9k/stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,7 @@ def _check_for_member_config(spawn, session):
if not session.get('member_config'):
raise StackMemberConfigException

dialog = Dialog([
install_add_one_shot_dialog = Dialog([
Statement(pattern=r"Do you want to proceed\? \[y\/n\]",
action='sendline(y)',
loop_continue=True,
Expand All @@ -943,31 +943,22 @@ def _check_for_member_config(spawn, session):
loop_continue=True,
continue_timer=False)
])
if issu:
device.sendline('install add file {} activate issu commit'.format(images[0]))
else:
device.sendline('install add file {} activate commit'.format(images[0]))
try:
dialog.process(device.spawn,
timeout = install_timeout,
context=device.context
reload_args.update(
{"timeout": install_timeout, "reply": install_add_one_shot_dialog}
)
if issu:
device.reload("install add file {} activate issu commit".format(
images[0]
),
**reload_args,
)
else:
device.reload("install add file {} activate commit".format(
images[0]
),
**reload_args,
)
except StackMemberConfigException as e:
log.debug("Expected exception continue with the stage")
log.info('Waiting for buffer to settle down')
post_reload_wait_time = reload_args.get('post_reload_wait', 15)
post_reload_timeout = reload_args.get('post_reload_timeout', 60)
start_time = current_time = datetime.now()
timeout_time = timedelta(seconds=post_reload_timeout)
settle_time = current_time = datetime.now()
while (current_time - settle_time) < timeout_time:
if buffer_settled(device.spawn, post_reload_wait_time):
log.info('Buffer settled, accessing device..')
break
current_time = datetime.now()
if (current_time - start_time) > timeout_time:
log.info('Time out, trying to acces device..')
break
except Exception as e:
step.failed("Failed to install the image", from_exception=e)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def test_iosxe_install_image_pass(self):

device = Mock()
device.chassis_type = 'stack'
device.sendline = Mock()
device.reload = Mock()
# device.api.get_running_image.return_value = 'sftp://server/image.bin'

with patch(
Expand All @@ -35,7 +35,7 @@ def test_iosxe_install_image_pass(self):
"genie.libs.clean.stages.iosxe.cat9k.stages.Dialog") as dialog_mock:
cls.install_image(steps=steps, device=device, images=['sftp://server/image.bin'])

device.sendline.assert_has_calls([
device.reload([
call('install add file sftp://server/image.bin activate commit')])
self.assertEqual(Passed, steps.details[0].result)

Expand All @@ -46,6 +46,7 @@ def test_iosxe_install_image_skip(self):
device = Mock()
device.chassis_type = 'stack'
device.api.get_running_image.return_value = 'sftp://server/image.bin'
device.reload = Mock(side_effect=Exception)
with self.assertRaises(TerminateStepSignal):
with patch(
"genie.libs.clean.stages.iosxe.cat9k.stages.StackUtils") as stack_mock:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import logging
import unittest

from unittest.mock import Mock, MagicMock, call, patch

from genie.libs.clean.stages.iosxe.cat9k.stages import InstallImage

from pyats.aetest.steps import Steps
from pyats.results import Passed, Failed
from pyats.aetest.signals import TerminateStepSignal

logging.disable(logging.CRITICAL)

class TestInstallImage(unittest.TestCase):

def test_iosxe_install_image_reload_pass(self):
steps = Steps()
cls = InstallImage()
cls.history = MagicMock()
cls.new_boot_var = 'image.bin'

device = Mock()
device.chassis_type = 'stack'
device.reload = Mock()

with patch(
"genie.libs.clean.stages.iosxe.cat9k.stages.StackUtils") as stack_mock:
with patch(
"genie.libs.clean.stages.iosxe.cat9k.stages.Dialog") as dialog_mock:
cls.install_image(steps=steps, device=device, images=['sftp://server/image.bin'])

device.reload([
call('install add file sftp://server/image.bin activate commit')])
self.assertEqual(Passed, steps.details[0].result)

def test_iosxe_install_image_reload_fail(self):
steps = Steps()
cls = InstallImage()
cls.history = {}

device = Mock()
device.chassis_type = 'stack'
device.reload = Mock(side_effect=Exception)

with self.assertRaises(TerminateStepSignal):
with patch(
"genie.libs.clean.stages.iosxe.cat9k.stages.StackUtils") as stack_mock:
with patch(
"genie.libs.clean.stages.iosxe.cat9k.stages.Dialog.process") as dialog_mock:
dialog_mock.side_effect = Exception
cls.install_image(steps=steps, device=device, images=['sftp://server/image.bin'])

self.assertEqual(Failed, steps.details[0].result)
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ def valid_structure_1(self, images):
return False

if len(images) == 1:
setattr(self, "image", images)
if "smu" in images[0]:
setattr(self, "smu", images)
else:
setattr(self, "image", images)
return True
if len(images) >= 2:
setattr(self, "image", images[:1])
Expand Down Expand Up @@ -162,6 +165,7 @@ class ImageHandler(BaseImageHandler, ImageLoader):
def __init__(self, device, images, *args, **kwargs):

# Set default
self.image = []
self.smu = []
self.packages = []
self.other = []
Expand All @@ -171,7 +175,7 @@ def __init__(self, device, images, *args, **kwargs):
ImageLoader.load(self, images)

# Temp workaround for XPRESSO
self.image = [self.image[0].replace("file://", "")]
self.image = [self.image[0].replace("file://", "")] if self.image else []

if hasattr(self, "smu"):
self.smu = [x.replace("file://", "") for x in self.smu]
Expand All @@ -182,7 +186,7 @@ def __init__(self, device, images, *args, **kwargs):
if hasattr(self, "other"):
self.other = [x.replace("file://", "") for x in self.other]

self.original_image = [self.image[0].replace("file://", "")]
self.original_image = [self.image[0].replace("file://", "")] if self.image else []

super().__init__(device, *args, **kwargs)

Expand Down
Loading

0 comments on commit c414b37

Please sign in to comment.