Skip to content

Commit 65cd407

Browse files
authored
Merge pull request #143 from networktocode/develop
2 parents 16fe780 + 6e61b76 commit 65cd407

File tree

6 files changed

+67
-7
lines changed

6 files changed

+67
-7
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/)
1313
### Security
1414

1515

16+
## [0.0.11]
17+
18+
### Added
19+
- AIREOSDevice property, ``peer_redundancy_state`` for standby device status
20+
21+
### Changed
22+
- AIREOSDevice ``os_install`` method verifies standby device is in same state as before install
23+
1624
## [0.0.10]
1725
### Added
1826
- Cisco WLC/AireOS Driver

pyntc/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
except ImportError:
1313
from ConfigParser import SafeConfigParser
1414

15-
__version__ = "0.0.10"
15+
__version__ = "0.0.11"
1616

1717
LIB_PATH_ENV_VAR = "PYNTC_CONF"
1818
LIB_PATH_DEFAULT = "~/.ntc.conf"

pyntc/devices/aireos_device.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,11 +417,14 @@ def install_os(self, image_name, controller="both", save_config=True, **vendor_s
417417
"""
418418
timeout = vendor_specifics.get("timeout", 3600)
419419
if not self._image_booted(image_name):
420+
peer_redundancy = self.peer_redundancy_state
420421
self.set_boot_options(image_name, **vendor_specifics)
421422
self.reboot(confirm=True, controller=controller, save_config=save_config)
422423
self._wait_for_device_reboot(timeout=timeout)
423424
if not self._image_booted(image_name):
424425
raise OSInstallError(hostname=self.host, desired_boot=image_name)
426+
if not self.peer_redundancy_state == peer_redundancy:
427+
raise OSInstallError(hostname=f"{self.host}-standby", desired_boot=image_name)
425428

426429
return True
427430

@@ -456,6 +459,24 @@ def open(self):
456459
if not self.redundancy_state:
457460
self.close()
458461

462+
@property
463+
def peer_redundancy_state(self):
464+
"""
465+
Determine the state of the peer device.
466+
467+
Returns:
468+
str: The Peer State from the local device's perspective.
469+
470+
Example:
471+
>>> device = AIREOSDevice(**connection_args)
472+
>>> device.peer_redundancy_state
473+
'standby hot'
474+
>>>
475+
"""
476+
ha = self.show("show redundancy summary")
477+
ha_peer_state = re.search(r"^\s*Peer\s+State\s*=\s*(.+?)\s*$", ha, re.M)
478+
return ha_peer_state.group(1).lower()
479+
459480
def reboot(self, timer=0, confirm=False, controller="self", save_config=True):
460481
"""
461482
Reload the controller or controller pair.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api"
44

55
[tool.poetry]
66
name = "pyntc"
7-
version = "0.0.10"
7+
version = "0.0.11"
88
description = "SDK to simplify common workflows for Network Devices."
99
authors = ["NTC <[email protected]>"]
1010
readme = "README.md"
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
Redundancy Mode = SSO DISABLED
22
Local State = ACTIVE
3+
Peer State = N/A
34
Unit = Primary
45
Unit ID = AB:12:CD:34:EF:56
5-
Redundancy State = SSO DISABLED
6+
Redundancy State = N/A
67
Mobility MAC = AB:12:CD:34:EF:56
8+
Redundancy Port = DOWN
9+
Link Encryption = Enabled

test/unit/test_devices/test_aireos_device.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,14 @@ def test_file_copy_error(mock_cftv, aireos_device_path, aireos_send_command_timi
264264
@mock.patch.object(AIREOSDevice, "set_boot_options")
265265
@mock.patch.object(AIREOSDevice, "reboot")
266266
@mock.patch.object(AIREOSDevice, "_wait_for_device_reboot")
267-
def test_install_os(mock_wait, mock_reboot, mock_sbo, aireos_image_booted, aireos_boot_image):
267+
@mock.patch.object(AIREOSDevice, "peer_redundancy_state", new_callable=mock.PropertyMock)
268+
def test_install_os(mock_prs, mock_wait, mock_reboot, mock_sbo, aireos_image_booted, aireos_boot_image):
268269
device = aireos_image_booted([False, True])
269270
assert device.install_os(aireos_boot_image) is True
270271
device._image_booted.assert_has_calls([mock.call(aireos_boot_image)] * 2)
271272
mock_sbo.assert_has_calls([mock.call(aireos_boot_image)])
272273
mock_reboot.assert_called_with(confirm=True, controller="both", save_config=True)
274+
mock_prs.assert_called()
273275

274276

275277
def test_install_os_no_install(aireos_image_booted, aireos_boot_image):
@@ -281,17 +283,33 @@ def test_install_os_no_install(aireos_image_booted, aireos_boot_image):
281283
@mock.patch.object(AIREOSDevice, "set_boot_options")
282284
@mock.patch.object(AIREOSDevice, "reboot")
283285
@mock.patch.object(AIREOSDevice, "_wait_for_device_reboot")
284-
def test_install_os_error(mock_wait, mock_reboot, mock_sbo, aireos_image_booted, aireos_boot_image):
286+
@mock.patch.object(AIREOSDevice, "peer_redundancy_state", new_callable=mock.PropertyMock)
287+
def test_install_os_error(mock_prs, mock_wait, mock_reboot, mock_sbo, aireos_image_booted, aireos_boot_image):
285288
device = aireos_image_booted([False, False])
286-
with pytest.raises(OSInstallError):
289+
with pytest.raises(OSInstallError) as boot_error:
287290
device.install_os(aireos_boot_image)
291+
assert boot_error.value.message == f"{device.host} was unable to boot into {aireos_boot_image}"
288292
device._image_booted.assert_has_calls([mock.call(aireos_boot_image)] * 2)
289293

290294

291295
@mock.patch.object(AIREOSDevice, "set_boot_options")
292296
@mock.patch.object(AIREOSDevice, "reboot")
293297
@mock.patch.object(AIREOSDevice, "_wait_for_device_reboot")
294-
def test_install_os_pass_args(mock_wait, mock_reboot, mock_sbo, aireos_image_booted, aireos_boot_image):
298+
@mock.patch.object(AIREOSDevice, "peer_redundancy_state", new_callable=mock.PropertyMock)
299+
def test_install_os_error_peer(mock_prs, mock_wait, mock_reboot, mock_sbo, aireos_image_booted, aireos_boot_image):
300+
mock_prs.side_effect = ["standby hot", "unknown"]
301+
device = aireos_image_booted([False, True])
302+
with pytest.raises(OSInstallError) as boot_error:
303+
device.install_os(aireos_boot_image)
304+
assert boot_error.value.message == f"{device.host}-standby was unable to boot into {aireos_boot_image}"
305+
device._image_booted.assert_has_calls([mock.call(aireos_boot_image)] * 2)
306+
307+
308+
@mock.patch.object(AIREOSDevice, "set_boot_options")
309+
@mock.patch.object(AIREOSDevice, "reboot")
310+
@mock.patch.object(AIREOSDevice, "_wait_for_device_reboot")
311+
@mock.patch.object(AIREOSDevice, "peer_redundancy_state", new_callable=mock.PropertyMock)
312+
def test_install_os_pass_args(mock_prs, mock_wait, mock_reboot, mock_sbo, aireos_image_booted, aireos_boot_image):
295313
device = aireos_image_booted([False, True])
296314
assert device.install_os(aireos_boot_image, controller="self", save_config=False) is True
297315
mock_reboot.assert_called_with(confirm=True, controller="self", save_config=False)
@@ -340,6 +358,16 @@ def test_open_standby(mock_ch, aireos_device, aireos_redundancy_state_path):
340358
assert aireos_device.connected is False
341359

342360

361+
def test_peer_redundancy_state_standby_hot(aireos_show):
362+
device = aireos_show(["show_redundancy_summary_sso_enabled.txt"])
363+
assert device.peer_redundancy_state == "standby hot"
364+
365+
366+
def test_peer_redundancy_state_standalone(aireos_show):
367+
device = aireos_show(["show_redundancy_summary_standalone.txt"])
368+
assert device.peer_redundancy_state == "n/a"
369+
370+
343371
@mock.patch("pyntc.devices.aireos_device.RebootSignal")
344372
@mock.patch.object(AIREOSDevice, "save")
345373
def test_reboot_confirm(mock_save, mock_reboot, aireos_send_command_timing, aireos_redundancy_mode_path):

0 commit comments

Comments
 (0)