def config_transition(statemachine, spawn, context): # Config may be locked, retry until max attempts or config state reached wait_time = spawn.settings.CONFIG_LOCK_RETRY_SLEEP max_attempts = spawn.settings.CONFIG_LOCK_RETRIES dialog = Dialog([ Statement(pattern=patterns.config_locked, action=update_context, args={'config_locked': True}, loop_continue=False, trim_buffer=True), Statement(pattern=statemachine.get_state('enable').pattern, action=update_context, args={'config_locked': False}, loop_continue=False, trim_buffer=False), Statement(pattern=statemachine.get_state('config').pattern, action=update_context, args={'config_locked': False}, loop_continue=False, trim_buffer=False) ]) for attempts in range(max_attempts + 1): spawn.sendline(statemachine.config_command) try: dialog.process(spawn, timeout=spawn.settings.CONFIG_TIMEOUT, context=context) except UniconTimeoutError: pass if context.get('config_locked'): if attempts < max_attempts: spawn.log.warning( '*** Config lock detected, waiting {} seconds. Retry attempt {}/{} ***' .format(wait_time, attempts + 1, max_attempts)) sleep(wait_time) else: statemachine.detect_state(spawn) if statemachine.current_state == 'config': return else: spawn.log.warning( "Could not enter config mode, sending clear line command and trying again.." ) spawn.send(spawn.settings.CLEAR_LINE_CMD) if context.get('config_locked'): raise StateMachineError('Config locked, unable to configure device') else: raise StateMachineError('Unable to transition to config mode')
def config_transition(statemachine, spawn, context): # Config may be locked, retry until max attempts or config state reached wait_time = spawn.settings.CONFIG_LOCK_RETRY_SLEEP max_attempts = spawn.settings.CONFIG_LOCK_RETRIES dialog = Dialog([Statement(pattern=statemachine.get_state('enable').pattern, loop_continue=False, trim_buffer=True), Statement(pattern=statemachine.get_state('config').pattern, loop_continue=False, trim_buffer=False), ]) if hasattr(statemachine, 'config_transition_statement_list'): dialog += Dialog(statemachine.config_transition_statement_list) for attempt in range(max_attempts + 1): spawn.sendline(statemachine.config_command) dialog.process(spawn, timeout=spawn.settings.CONFIG_TIMEOUT, context=context) statemachine.detect_state(spawn) if statemachine.current_state == 'config': return if attempt < max_attempts: spawn.log.warning('*** Could not enter config mode, waiting {} seconds. Retry attempt {}/{} ***'.format( wait_time, attempt + 1, max_attempts)) sleep(wait_time) raise StateMachineError('Unable to transition to config mode')
def call_service(self, style, *args, **kwargs): # Get current state of the state machine and determine end state sm = self.get_sm() con = self.connection con.log.debug('+++ cli_style current state %s +++' % sm.current_state) current_state = sm.current_state self.start_state = current_state if sm.current_cli_style == 'cisco': if style[0].lower() == 'j': self.start_state = "juniper_" + sm.current_cli_mode elif sm.current_cli_style == 'juniper': if style[0].lower() == 'c': self.start_state = "cisco_" + sm.current_cli_mode else: raise StateMachineError('Invalid state when calling cli_style') self.end_state = self.start_state spawn = self.get_spawn() try: sm.go_to(self.start_state, spawn, context=self.context) except Exception as err: raise SubCommandFailure( "Failed to bring device to requested CLI state", err) con.log.debug('+++ cli_style new state %s +++' % sm.current_state) self.result = True
def test_stage_apply_configuration2(self): self.device.configure = Mock(side_effect=StateMachineError('negative test')) self.device.destroy = Mock(return_value="") self.device.connect = Mock(return_value=self.raw_output.connect) self.device.execute = Mock(return_value="") # Execute stage: apply_configuration with self.assertRaises(TerminateStepSignal): apply_configuration(self.section, self.steps, self.device, **self.device.clean.apply_configuration)
def test_stage_apply_configuration(self): self.device.configure = Mock(side_effect=StateMachineError('device hostname changed')) self.device.destroy = Mock(return_value="") self.device.connect = Mock(return_value=self.raw_output.connect) self.device.execute = Mock(side_effect=pos_execute) # Execute stage: apply_configuration with self.assertRaises(AEtestPassedSignal): apply_configuration(self.section, self.steps, self.device, **self.device.clean.apply_configuration)
def switch_console(statemachine, spawn, context): sm = statemachine # switch between XR and BMC console if sm.current_state == 'enable': target_state = 'bmc' elif sm.current_state == 'bmc': target_state = 'enable' else: raise StateMachineError('Unsupported state transition from {}'.format( sm.current_state)) # Try ctrl-o (\x0f) and then ctrl-w (\x17) for cmd in ['\x0f', '\x17']: spawn.send(cmd) sm.go_to('any', spawn, timeout=spawn.timeout) if sm.current_state == target_state: spawn.sendline() return raise StateMachineError('Unable to switch console state')
def glean_state(self, spawn, possible_states): """ Try to figure out the state by sending commands and verifying the matches against known output. """ # Create list of commands to execute glean_command_map = {} state_patterns = [] for state in possible_states: state_patterns.append(state.pattern) glean_data = self.STATE_GLEAN.get(state.name, None) if glean_data: if glean_data.command in glean_command_map: glean_command_map[glean_data.command][ glean_data.pattern] = state else: glean_command_map[glean_data.command] = {} glean_command_map[glean_data.command][ glean_data.pattern] = state if not glean_command_map: raise StateMachineError( 'Unable to detect state, multiple states possible and no glean data available' ) # Execute each glean commnd and check for pattern match for glean_cmd in glean_command_map: glean_pattern_map = glean_command_map[glean_cmd] dialog = Dialog(default_statement_list + [Statement(p) for p in state_patterns]) spawn.sendline(glean_cmd) result = dialog.process(spawn) if result: output = result.match_output for glean_pattern in glean_pattern_map: if re.search(glean_pattern, output): self.update_cur_state(glean_pattern_map[glean_pattern]) return
def ftd_to_module_transition(statemachine, spawn, context): if context.get('console'): spawn.sendline('exit') else: raise StateMachineError('Not on console, cannot transition')
def raise_ftd_not_running(): raise StateMachineError('FTD is not running')
def call_service(self, command=[], reply=Dialog([]), timeout=None, error_pattern=None, *args, **kwargs): # Get current state of the state machine and determine end state sm = self.get_sm() con = self.connection con.log.debug('+++ configure state %s +++' % sm.current_state) if sm.current_cli_style == 'cisco': self.start_state = 'cisco_config' self.end_state = 'cisco_exec' PROMPT_PREFIX = None elif sm.current_cli_style == 'juniper': self.start_state = 'juniper_config' self.end_state = 'juniper_exec' PROMPT_PREFIX = con.settings.JUNIPER_PROMPT_PREFIX else: raise StateMachineError( 'Invalid state (%s) when calling configure' % sm.current_cli_style()) spawn = self.get_spawn() sm.go_to(self.start_state, spawn, context=self.context) timeout = timeout or self.timeout if isinstance(command, str): command = command.splitlines() self.command_list_is_empty = False if not isinstance(reply, Dialog): raise SubCommandFailure( "dialog passed via 'reply' must be an instance of Dialog") # No command passed, just move to config mode if len(command) == 0: self.result = None self.command_list_is_empty = True return if con.settings.IGNORE_CHATTY_TERM_OUTPUT: # clear buffer of 'System message at ...' messages chatty_term_wait(con.spawn, trim_buffer=True) command_output = {} # if commands is a list if not isinstance(command, collections.abc.Sequence): raise SubCommandFailure('Invalid command passed %s' % repr(command)) if 'commit' not in command: command.append('commit') try: for cmd in command: self.result = con.command(cmd, reply=reply, error_pattern=error_pattern, timeout=timeout) if self.result: output = utils.truncate_trailing_prompt( sm.get_state(sm.current_state), self.result, self.connection.hostname) output = output.replace(cmd, "", 1) output = re.sub(r"^\r\n", "", output, 1) if PROMPT_PREFIX: output = re.sub(PROMPT_PREFIX, "", output) command_output[cmd] = output.rstrip() except SubCommandFailure as e: # Go to exec state after command failure, # do not commit changes (handled by state transition) sm.go_to(self.end_state, spawn, context=self.context) raise if len(command_output) == 1: self.result = list(command_output.values())[0] else: self.result = command_output
def go_to(self, to_state, spawn, context=AttributeDict(), dialog=None, timeout=None, hop_wise=False, prompt_recovery=False): # get the composed full (slot_<slot>_<app>) state name to_state = self.get_full_state_name(to_state) # when a connection is made to the device and the device # is left in a random state we need to try to detect the # state or signal failure because going to 'any' state # can lead to circular transitions which go on in a loop # indefinetely if self.current_state == 'generic' and to_state is 'any': # try to detect in which state we are right now or fail # send a newline to initialize the prompt spawn.sendline('') # wait 10 seconds for the prompt to populate time.sleep(10) # match everything in the output buffer output = spawn.expect('.*', timeout=30).match_output for state_name, state_data in self.states_dict.items(): pattern = state_data.pattern if isinstance(pattern, str): if re.match(pattern, output.split('\r\n')[-1]): self.update_cur_state(state_name) return output if isinstance(pattern, list): for pat in pattern: if re.match(pat, output.split('\r\n')[-1]): self.update_cur_state(state_name) return output raise RuntimeError('Could not detect current state. Please ' + 'connect to the chassis and bring it to ' + 'the mio state prompt. Output is:' + output) elif (self.current_state != 'generic' and to_state is 'any') or \ isinstance(to_state, list): expected_state = to_state transition = AnyStateTransition(state_machine=self, to_state=to_state, spawn=spawn, dialog=dialog, timeout=timeout, context=context, prompt_recovery=prompt_recovery) else: if not isinstance(to_state, State): to_state = self.get_state(to_state) expected_state = to_state.name # Get the current state from SM current_state = self.get_state(self.current_state) # If the current and to_state state are same # we are already there so just return if to_state == current_state: return # If hop_wise is enabled then do step by step state transition if hop_wise: transition = HopWiseStateTransition(state_machine=self, to_state=to_state, spawn=spawn, dialog=dialog, timeout=timeout, context=context, prompt_recovery=prompt_recovery) else: transition = StateTransition(state_machine=self, to_state=to_state, spawn=spawn, dialog=dialog, timeout=timeout, context=context, prompt_recovery=prompt_recovery) # Start the state transition try: output = transition.do_transitions() except Exception as err: raise StateMachineError('Failed while bringing device to ' + '"%s" state' % \ str(expected_state)) from err finally: if transition.current_state is not 'generic': self.update_cur_state(transition.current_state) # If the current_state and to_state are not matching # the probably whe landed somewhere wrong, so raise exception if expected_state is not 'any' \ and self.current_state not in expected_state: raise StateMachineError( 'Changing state to %s failed\n' 'current_state: %s\n' 'last command: %s\n' 'buffer: %s\n' 'last match: %s' % ( expected_state, self.current_state, repr(spawn.last_sent), repr(spawn.buffer), repr(spawn.match.match_output) ) ) return output
def current_cli_mode(self): self.current_state_tokens = self.current_state.split('_') if len(self.current_state_tokens) < 1: raise StateMachineError('Invalid state') mode = self.current_state_tokens[1] return mode
def current_cli_style(self): self.current_state_tokens = self.current_state.split('_') if len(self.current_state_tokens) < 1: raise StateMachineError('Invalid state') style = self.current_state_tokens[0] return style