|
11 | 11 | from unicon.plugins.iosxe.stack.utils import StackUtils |
12 | 12 | from unicon.plugins.generic.statements import custom_auth_statements |
13 | 13 | from unicon.plugins.iosxe.stack.service_statements import (switch_prompt, |
14 | | - stack_reload_stmt_list) |
| 14 | + stack_reload_stmt_list, |
| 15 | + stack_switchover_stmt_list) |
15 | 16 |
|
16 | 17 | utils = StackUtils() |
17 | 18 |
|
@@ -247,3 +248,110 @@ def boot(con): |
247 | 248 | if return_output: |
248 | 249 | Result = namedtuple('Result', ['result', 'output']) |
249 | 250 | self.result = Result(self.result, reload_cmd_output.match_output.replace(reload_cmd, '', 1)) |
| 251 | + |
| 252 | + |
| 253 | +class SVLStackSwitchover(BaseService): |
| 254 | + """ Get Rp state |
| 255 | +
|
| 256 | + Service to get the redundancy state of the device rp. |
| 257 | +
|
| 258 | + Arguments: |
| 259 | + target: Service target, by default active |
| 260 | +
|
| 261 | + Returns: |
| 262 | + Expected return values are ACTIVE, STANDBY, MEMBER |
| 263 | + raise SubCommandFailure on failure. |
| 264 | +
|
| 265 | + Example: |
| 266 | + .. code-block:: python |
| 267 | +
|
| 268 | + rtr.get_rp_state() |
| 269 | + rtr.get_rp_state(target='standby') |
| 270 | + """ |
| 271 | + |
| 272 | + def __init__(self, connection, context, **kwargs): |
| 273 | + super().__init__(connection, context, **kwargs) |
| 274 | + self.start_state = 'enable' |
| 275 | + self.end_state = 'enable' |
| 276 | + self.timeout = connection.settings.STACK_SWITCHOVER_TIMEOUT |
| 277 | + self.command = "redundancy force-switchover" |
| 278 | + self.dialog = Dialog(stack_switchover_stmt_list) |
| 279 | + self.__dict__.update(kwargs) |
| 280 | + |
| 281 | + def call_service(self, command=None, |
| 282 | + reply=Dialog([]), |
| 283 | + timeout=None, |
| 284 | + *args, **kwargs): |
| 285 | + |
| 286 | + switchover_cmd = command or self.command |
| 287 | + timeout = timeout or self.timeout |
| 288 | + conn = self.connection.active |
| 289 | + |
| 290 | + expected_active_sw = self.connection.standby.member_id |
| 291 | + dialog = self.dialog |
| 292 | + |
| 293 | + if reply: |
| 294 | + dialog = reply + self.dialog |
| 295 | + |
| 296 | + # added connection dialog in case switchover ask for username/password |
| 297 | + connect_dialog = self.connection.connection_provider.get_connection_dialog() |
| 298 | + dialog += connect_dialog |
| 299 | + |
| 300 | + conn.log.info('Processing on active rp %s-%s' % (conn.hostname, conn.alias)) |
| 301 | + conn.sendline(switchover_cmd) |
| 302 | + try: |
| 303 | + # A loop has been implemented to handle the |
| 304 | + # "Press RETURN to get started" prompt twice. Based on extensive |
| 305 | + # testing during SVL reloads on 9500x devices, it was observed |
| 306 | + # that the device is not fully ready after the first prompt. |
| 307 | + # As a result, the logic accounts for this behavior by waiting for |
| 308 | + # the second occurrence of the message, which is assumed to be the |
| 309 | + # default behavior for these devices. |
| 310 | + for _ in range(2): |
| 311 | + match_object = dialog.process(conn.spawn, timeout=timeout, |
| 312 | + prompt_recovery=self.prompt_recovery, |
| 313 | + context=conn.context) |
| 314 | + except Exception as e: |
| 315 | + raise SubCommandFailure('Error during switchover ', e) from e |
| 316 | + |
| 317 | + # try boot up original active rp with current active system |
| 318 | + # image, if it moved to rommon state. |
| 319 | + if 'state' in conn.context and conn.context.state == 'rommon': |
| 320 | + try: |
| 321 | + conn.state_machine.detect_state(conn.spawn, context=conn.context) |
| 322 | + conn.state_machine.go_to('enable', conn.spawn, timeout=timeout, |
| 323 | + prompt_recovery=self.prompt_recovery, |
| 324 | + context=conn.context, dialog=Dialog([switch_prompt])) |
| 325 | + except Exception as e: |
| 326 | + self.connection.log.warning('Fail to bring up original active rp from rommon state.', e) |
| 327 | + finally: |
| 328 | + conn.context.pop('state') |
| 329 | + |
| 330 | + # To ensure the stack is ready to accept the login |
| 331 | + self.connection.log.info('Sleeping for %s secs.' % \ |
| 332 | + self.connection.settings.POST_SWITCHOVER_SLEEP) |
| 333 | + sleep(self.connection.settings.POST_SWITCHOVER_SLEEP) |
| 334 | + |
| 335 | + # check all members are ready |
| 336 | + conn.state_machine.detect_state(conn.spawn, context=conn.context) |
| 337 | + |
| 338 | + interval = self.connection.settings.SWITCHOVER_POSTCHECK_INTERVAL |
| 339 | + if utils.is_all_member_ready(conn, timeout=timeout, interval=interval): |
| 340 | + self.connection.log.info('All members are ready.') |
| 341 | + else: |
| 342 | + self.connection.log.info('Timeout in %s secs. ' |
| 343 | + 'Not all members are in Ready state.' % timeout) |
| 344 | + self.result = False |
| 345 | + return |
| 346 | + |
| 347 | + self.connection.log.info('Disconnecting and reconnecting') |
| 348 | + self.connection.disconnect() |
| 349 | + self.connection.connect() |
| 350 | + |
| 351 | + self.connection.log.info('Verifying active and standby switch State.') |
| 352 | + if self.connection.active.member_id == expected_active_sw: |
| 353 | + self.connection.log.info('Switchover successful') |
| 354 | + self.result = True |
| 355 | + else: |
| 356 | + self.connection.log.info('Switchover failed') |
| 357 | + self.result = False |
0 commit comments