Skip to content

Commit e0a1344

Browse files
authored
Merge pull request #199 from CiscoTestAutomation/release_25.11
Release 25.11
2 parents 9226cb1 + 3d5ee6a commit e0a1344

File tree

155 files changed

+10880
-7742
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

155 files changed

+10880
-7742
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ RELATED_PKGS += genie.libs.filetransferutils
4848
# Adding pyasyncore pkg to fix pysnmp scripts for python 3.12
4949
DEPENDENCIES = restview psutil Sphinx wheel asynctest 'pysnmp>=6.1.4,<6.2' pyasn1==0.6.0
5050
DEPENDENCIES += sphinx-rtd-theme==1.1.0 pyftpdlib tftpy\<0.8.1 robotframework
51-
DEPENDENCIES += Cython requests ruamel.yaml grpcio protobuf jinja2 pyVmomi
51+
DEPENDENCIES += Cython requests "ruamel.yaml.clib<0.2.15" ruamel.yaml grpcio protobuf jinja2 pyVmomi asyncssh
5252
# Internal variables.
5353
# (note - build examples & templates last because it will fail uploading to pypi
5454
# due to duplicates, and we'll for now accept that error)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--------------------------------------------------------------------------------
2+
Fix
3+
--------------------------------------------------------------------------------
4+
5+
* clean-pkg
6+
* Added support to block copy operations if the target file size does not match.
7+
8+
* rommonboot stage
9+
* iosxe
10+
* Removed duplicate task function.
11+
* cat9k
12+
* Removed device.destroy() call and added device.sendline() in the rommon boot stage so that the device reaches the rommon prompt.
13+
14+
* clean-pkg/stages
15+
* Added the reset of the rollup flag when recovery is enabled
16+
* Updated the api configure_management to able to skip for missing attribute instead of failing complete stages.
17+
18+
* iosxe
19+
* clean-pkg/utils
20+
* Fixed issue with updating protected file for image
21+
22+
23+
--------------------------------------------------------------------------------
24+
New
25+
--------------------------------------------------------------------------------
26+
27+
* clean/recover
28+
* Added power cycle retry mechanism to enhance reliability during device recovery.
29+
* Updated the console speed configuration in case of failiure connection to device
30+
31+

pkgs/clean-pkg/sdk_generator/github/clean_datafile.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ root_directories:
33
root: 'genie.libs.clean'
44
mod_name: 'clean'
55
url:
6-
link: 'https://github.com/CiscoTestAutomation/genielibs/tree/{branch}/'
7-
branch: 'master'
6+
link: 'https://github.com/CiscoTestAutomation/genielibs/tree/{branch}/pkgs/clean-pkg/'
7+
branch: 'main'
88
style: 'github'

pkgs/clean-pkg/sdk_generator/output/github_clean.json

Lines changed: 93 additions & 93 deletions
Large diffs are not rendered by default.

pkgs/clean-pkg/src/genie/libs/clean/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
'''
99

1010
# metadata
11-
__version__ = "25.10"
11+
__version__ = "25.11"
1212
__author__ = 'Cisco Systems Inc.'
1313
1414
__copyright__ = 'Copyright (c) 2019, Cisco Systems Inc.'

pkgs/clean-pkg/src/genie/libs/clean/recovery/recovery.py

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ def _disconnect_reconnect(device):
5959

6060

6161
def _recovery_steps(device, clear_line=True, powercycler=True,
62-
powercycler_delay=30, reconnect_delay=60, **kwargs):
62+
powercycler_delay=30, reconnect_delay=60, configure_console_speed=False,
63+
**kwargs):
6364

6465
'''Steps to recover device
6566
1. First step clear line
@@ -99,29 +100,40 @@ def _recovery_steps(device, clear_line=True, powercycler=True,
99100
else:
100101
log.info('Clear line is not provided!')
101102

102-
# Step-3: Powercycle device
103+
# Step-3&4: Powercycle device
103104
if powercycler:
104-
log.info(banner("Powercycling device '{}'".format(device.name)))
105-
106-
try:
105+
power_cycle_retry = 1
106+
if configure_console_speed:
107+
power_cycle_retry = 2
108+
for powercycler_attempt in range(power_cycle_retry):
109+
log.info(banner(f" Powercycling device '{device.name}'"))
107110
device.api.execute_power_cycle_device(delay=powercycler_delay)
111+
log.info(f"Successfully powercycled device '{device.name}' during recovery")
112+
try:
113+
device.api.device_recovery_boot()
114+
except Exception as e:
115+
log.error(str(e))
116+
if configure_console_speed and powercycler_attempt + 1 < power_cycle_retry:
117+
log.info(f"Device '{device.name}' failed to boot. Updating the console speed.")
118+
log.info(banner(f"Attempting to configure management console speed on device '{device.name}'"))
119+
device.destroy()
120+
device.api.configure_management_console()
121+
log.info(f"Successfully configured management console speed on device '{device.name}'")
122+
device.disconnect()
123+
continue
124+
else:
125+
raise Exception(f"Failed to boot device '{device.name}' after powercycle")
126+
else:
127+
log.info(f"Successfully booted device '{device.name}' after powercycle")
128+
break
129+
else:
130+
# Powercycler not provided do only step-4:
131+
log.info('Powercycler is not provided!')
132+
try:
133+
device.api.device_recovery_boot()
108134
except Exception as e:
109135
log.error(str(e))
110-
raise Exception("Failed to powercycle device '{}'".format(device.name))
111-
else:
112-
log.info("Successfully powercycled device '{}' during recovery".\
113-
format(device.name))
114-
else:
115-
log.info("powercycle is not provided!")
116-
117-
# Step-4: Boot device with given golden image or by tftp boot
118-
try:
119-
device.api.device_recovery_boot()
120-
except Exception as e:
121-
log.error(e)
122-
raise Exception(f"Failed to boot the device {device.name}")
123-
else:
124-
log.info(f"successfully booted the device {device.name}")
136+
raise Exception(f"Failed to boot device '{device.name}' after powercycle")
125137

126138
log.info('Sleeping for {r} before reconnection'.format(r=reconnect_delay))
127139
time.sleep(reconnect_delay)
@@ -158,6 +170,7 @@ def _recovery_steps(device, clear_line=True, powercycler=True,
158170
},
159171
Optional('recovery_password'): str,
160172
Optional('clear_line'): bool,
173+
Optional('configure_console_speed'): bool,
161174
Optional('powercycler'): bool,
162175
Optional('powercycler_delay'): int,
163176
Optional('reconnect_delay'): int,
@@ -181,7 +194,8 @@ def recovery_processor(
181194
powercycler_delay=30,
182195
reconnect_delay=60,
183196
post_recovery_configuration=None,
184-
connection_timeout=45
197+
connection_timeout=45,
198+
configure_console_speed=True,
185199
):
186200

187201
'''
@@ -195,6 +209,7 @@ def recovery_processor(
195209
console_breakboot_char: <Character to send when console_activity_pattern is matched, 'str'>
196210
console_breakboot_telnet_break: Use telnet `send break` to interrupt device boot
197211
grub_activity_pattern: <Break pattern on the device for grub boot mode, 'str'>
212+
configure_console_speed: <Should configure console speed during recovery, 'bool'> (Default: False)
198213
grub_breakboot_char: <Character to send when grub_activity_pattern is matched, 'str'>
199214
timeout: <Timeout in seconds to recover the device, 'int'>
200215
recovery_password: <Device password after coming up, 'str'>
@@ -364,7 +379,7 @@ def recovery_processor(
364379

365380
try:
366381
_recovery_steps(device, clear_line, powercycler,
367-
powercycler_delay, reconnect_delay)
382+
powercycler_delay, reconnect_delay, configure_console_speed)
368383
except Exception as e:
369384
# Could not recover the device!
370385
log.error(banner("*** Terminating Genie Clean ***"))

pkgs/clean-pkg/src/genie/libs/clean/stages/iosxe/cat9k/stages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ def reload_check(device, target):
385385
device.expect(['(.*Initializing Hardware.*|^(.*)((rommon(.*))+>|switch *:).*$)'], timeout=60)
386386

387387
log.info("Device is reloading")
388-
device.destroy_all()
388+
device.sendline()
389389

390390
def rommon_boot(self, steps, device, image, tftp=None, timeout=TIMEOUT, recovery_password=RECOVERY_PASSWORD,
391391
recovery_username=RECOVERY_USERNAME, recovery_enable_password=RECOVERY_ENABLE_PASSWORD, ether_port=ETHER_PORT):

pkgs/clean-pkg/src/genie/libs/clean/stages/iosxe/cat9k/tests/test_stages.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ def test_go_to_rommon_pass(self, dialog, statement):
118118
self.device.is_ha = False
119119
self.device.subconnections = mock.MagicMock()
120120
self.device.expect = mock.Mock()
121-
self.device.destroy_all = mock.Mock()
122121

123122
reload_dialog = mock.Mock()
124123
dialog.return_value = reload_dialog
@@ -140,13 +139,15 @@ def test_go_to_rommon_pass(self, dialog, statement):
140139
continue_timer=False)
141140
])
142141

143-
self.device.sendline.assert_called_with("reload")
142+
# Check both sendline calls
143+
self.device.sendline.assert_has_calls([
144+
mock.call("reload"),
145+
mock.call()
146+
])
144147
reload_dialog.process.assert_called_with(self.device.spawn)
145148
self.device.expect.assert_called_with(
146149
['(.*Initializing Hardware.*|^(.*)((rommon(.*))+>|switch *:).*$)'], timeout=60)
147150

148-
self.device.destroy_all.assert_called_once()
149-
150151
# step_context comes from the following snippet
151152
# with steps.start('...') as step_context:
152153
step_context = steps.start.return_value.__enter__.return_value

pkgs/clean-pkg/src/genie/libs/clean/stages/iosxe/stages.py

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2591,61 +2591,6 @@ def _grub_boot_device(spawn, session, context):
25912591
con.context['grub_boot_image'] = image_to_boot
25922592
con.context['image_to_boot'] = image_to_boot
25932593

2594-
def task(con, timeout, image, grub_activity_pattern):
2595-
"""
2596-
A method for processing a dialog that loads a local image onto a device
2597-
2598-
Args:
2599-
con (obj): connection object
2600-
timeout (int, optional): Recovery process timeout. Defaults to 600.
2601-
image (dict): Information to load golden image on the device
2602-
grub_activity_pattern (str): Grub activity pattern
2603-
Returns:
2604-
None
2605-
"""
2606-
# if we have grup activity pattern then we need to update the context with grub boot image
2607-
2608-
if grub_activity_pattern:
2609-
image_to_boot = con.context.get('grub_boot_image')
2610-
con.context['grub_boot_image'] = image[0]
2611-
2612-
# check for image to boot if we have a value store it and replace it with the golden image
2613-
image_to_boot = con.context.get('image_to_boot')
2614-
con.context['image_to_boot'] = image[0]
2615-
2616-
# these statments needed for booting from grub menu
2617-
def _grub_boot_device(spawn, session, context):
2618-
# '\033' == <ESC>
2619-
spawn.send('\033')
2620-
time.sleep(0.8)
2621-
2622-
grub_prompt_stmt = \
2623-
Statement(pattern=r'.*grub *>.*',
2624-
action=_grub_boot_device,
2625-
args=None,
2626-
loop_continue=True,
2627-
continue_timer=False)
2628-
2629-
grub_boot_stmt = \
2630-
Statement(pattern=r'.*Use the UP and DOWN arrow keys to select.*',
2631-
action=grub_prompt_handler,
2632-
args=None,
2633-
loop_continue=True,
2634-
continue_timer=False)
2635-
2636-
dialog = Dialog([grub_boot_stmt, grub_prompt_stmt])
2637-
2638-
con.state_machine.go_to('enable',
2639-
con.spawn,
2640-
timeout=timeout,
2641-
context=con.context,
2642-
dialog=dialog,
2643-
prompt_recovery=True)
2644-
# we need to set the value of grub_boot_image and image_to_boot to original value
2645-
if grub_activity_pattern:
2646-
con.context['grub_boot_image'] = image_to_boot
2647-
con.context['image_to_boot'] = image_to_boot
2648-
26492594
def reconnect(self, steps, device, reconnect_timeout=RECONNECT_TIMEOUT):
26502595
with steps.start("Reconnect to device") as step:
26512596

pkgs/clean-pkg/src/genie/libs/clean/stages/iosxe/tests/test_install_image.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -540,11 +540,11 @@ def test_iosxe_install_image_pass_retries_not_enough_space(self, dialog):
540540
device.api.get_running_image = Mock()
541541
device.api.collect_install_log = Mock()
542542
device.api.free_up_disk_space = Mock(return_value=True)
543-
cls.install_image(steps=steps, device=device, images=['sftp://server/image.bin'])
543+
cls.install_image(steps=steps, device=device, images=['bootflash:/image.bin'])
544544

545-
expected_execute_call = [call('install add file sftp://server/image.bin activate commit prompt-level none', reply=reload_dialog, error_pattern=['FAILED:'], timeout=500),
545+
expected_execute_call = [call('install add file bootflash:/image.bin activate commit prompt-level none', reply=reload_dialog, error_pattern=['FAILED:'], timeout=500),
546546
call('more bootflash:packages.conf'),
547-
call('install add file sftp://server/image.bin activate commit prompt-level none', reply=reload_dialog, error_pattern=['FAILED:'], timeout=500),
547+
call('install add file bootflash:/image.bin activate commit prompt-level none', reply=reload_dialog, error_pattern=['FAILED:'], timeout=500),
548548
call('install commit')]
549549

550550
device.execute.assert_has_calls(expected_execute_call)
@@ -559,7 +559,7 @@ def test_iosxe_install_image_pass_retries_not_enough_space(self, dialog):
559559
)
560560
device.reload.assert_has_calls([expected_reload_call])
561561
device.api.free_up_disk_space.assert_called_with(destination='', required_size=5000,
562-
protected_files=['//server/image.bin'], allow_deletion_failure=True, skip_deletion=False)
562+
protected_files=['image.bin'], allow_deletion_failure=True, skip_deletion=False)
563563
device.api.get_running_image.assert_called_once()
564564
self.assertEqual(Passed, steps.details[0].result)
565565

0 commit comments

Comments
 (0)