def test_update_sequences(self): """ Test the sequencing of the update_sequences method. """ from supvisors.application import ApplicationStatus from supvisors.process import ProcessStatus application = ApplicationStatus('ApplicationTest', self.supvisors.logger) # add processes to the application for info in database_copy(): process = ProcessStatus(info['group'], info['name'], self.supvisors) process.add_info('10.0.0.1', info) # set random sequence to process process.rules.start_sequence = random.randint(0, 2) process.rules.stop_sequence = random.randint(0, 2) application.add_process(process) # call the sequencer application.update_sequences() # check the sequencing of the starting sequences = sorted({process.rules.start_sequence for process in application.processes.values()}) # as key is an integer, the sequence dictionary should be sorted but pypy doesn't... for sequence, processes in sorted(application.start_sequence.items()): self.assertEqual(sequence, sequences.pop(0)) self.assertListEqual(sorted(processes, key=lambda x: x.process_name), sorted([proc for proc in application.processes.values() if sequence == proc.rules.start_sequence], key=lambda x: x.process_name)) # check the sequencing of the stopping sequences = sorted({process.rules.stop_sequence for process in application.processes.values()}) # as key is an integer, the sequence dictionary should be sorted but pypy doesn't... for sequence, processes in sorted(application.stop_sequence.items()): self.assertEqual(sequence, sequences.pop(0)) self.assertListEqual(sorted(processes, key=lambda x: x.process_name), sorted([proc for proc in application.processes.values() if sequence == proc.rules.stop_sequence], key=lambda x: x.process_name))
def test_add_process(self): """ Test the add_process method. """ from supvisors.application import ApplicationStatus from supvisors.process import ProcessStatus application = ApplicationStatus('ApplicationTest', self.supvisors.logger) # add a process to the application info = any_process_info() process = ProcessStatus(info['group'], info['name'], self.supvisors) process.add_info('10.0.0.1', info) application.add_process(process) # check that process is stored self.assertIn(process.process_name, application.processes.keys()) self.assertIs(process, application.processes[process.process_name])
def load_processes(self, address, all_info): """ Load application dictionary from process info got from Supervisor on address. """ # get all processes and sort them by group (application) # first store applications in a set application_list = {x['group'] for x in all_info} self.logger.debug('applicationList={} from {}'.format( application_list, address)) # add unknown applications for application_name in application_list: if application_name not in self.applications.keys(): application = ApplicationStatus(application_name, self.logger) if self.supvisors.parser: self.supvisors.parser.load_application_rules(application) self.applications[application_name] = application # get AddressStatus corresponding to address status = self.addresses[address] # store processes into their application entry for info in all_info: try: process = self.process_from_info(info) except KeyError: # not found. add new ProcessStatus instance to dictionary and application process = ProcessStatus(info['group'], info['name'], self.supvisors) if self.supvisors.parser: self.supvisors.parser.load_process_rules(process) self.processes[process.namespec()] = process self.applications[process.application_name].add_process( process) # update the current entry process.add_info(address, info) # share the instance to the Supervisor instance that holds it status.add_process(process)
def test_serialization(self): """ Test the serial method used to get a serializable form of Application. """ import pickle from supvisors.application import ApplicationStatus from supvisors.ttypes import ApplicationStates # create address status instance application = ApplicationStatus('ApplicationTest', self.supvisors.logger) application._state = ApplicationStates.RUNNING application.major_failure = False application.minor_failure = True # test to_json method serialized = application.serial() self.assertDictEqual(serialized, {'application_name': 'ApplicationTest', 'statecode': 2, 'statename': 'RUNNING', 'major_failure': False, 'minor_failure':True}) # test that returned structure is serializable using pickle dumped = pickle.dumps(serialized) loaded = pickle.loads(dumped) self.assertDictEqual(serialized, loaded)
def test_serialization(self): """ Test the serial method used to get a serializable form of Application. """ import pickle from supvisors.application import ApplicationStatus from supvisors.ttypes import ApplicationStates # create address status instance application = ApplicationStatus('ApplicationTest', self.supvisors.logger) application._state = ApplicationStates.RUNNING application.major_failure = False application.minor_failure = True # test to_json method serialized = application.serial() self.assertDictEqual( serialized, { 'application_name': 'ApplicationTest', 'statecode': 2, 'statename': 'RUNNING', 'major_failure': False, 'minor_failure': True }) # test that returned structure is serializable using pickle dumped = pickle.dumps(serialized) loaded = pickle.loads(dumped) self.assertDictEqual(serialized, loaded)
def test_update_sequences(self): """ Test the sequencing of the deployment method. """ from supvisors.application import ApplicationStatus from supvisors.process import ProcessStatus application = ApplicationStatus('ApplicationTest', self.supvisors.logger) # add processes to the application for info in ProcessInfoDatabase: process = ProcessStatus(info['group'], info['name'], self.supvisors) process.add_info('10.0.0.1', info.copy()) # set random sequence to process process.rules.start_sequence = random.randint(0, 2) process.rules.stop_sequence = random.randint(0, 2) application.add_process(process) # call the sequencer application.update_sequences() # check the sequencing of the starting sequences = sorted({process.rules.start_sequence for process in application.processes.values()}) # as key is an integer, the sequence dictionary should be sorted but pypy doesn't... for sequence, processes in sorted(application.start_sequence.items()): self.assertEqual(sequence, sequences.pop(0)) self.assertListEqual(sorted(processes, key=lambda x: x.process_name), sorted([proc for proc in application.processes.values() if sequence == proc.rules.start_sequence], key=lambda x: x.process_name)) # check the sequencing of the stopping sequences = sorted({process.rules.stop_sequence for process in application.processes.values()}) # as key is an integer, the sequence dictionary should be sorted but pypy doesn't... for sequence, processes in sorted(application.stop_sequence.items()): self.assertEqual(sequence, sequences.pop(0)) self.assertListEqual(sorted(processes, key=lambda x: x.process_name), sorted([proc for proc in application.processes.values() if sequence == proc.rules.stop_sequence], key=lambda x: x.process_name))
def setdefault_application(self, application_name): """ Return the application corresponding to application_name if found. Otherwise return a new application for application_name. Related application rules are loaded from the rules file. """ try: # find existing application application = self.applications[application_name] except KeyError: # create new instance application = ApplicationStatus(application_name, self.logger) # load rules from rules file self.supvisors.parser.load_application_rules(application) # add new application to context self.applications[application_name] = application return application
def test_create(self): """ Test the values set at construction. """ from supvisors.application import ApplicationStatus from supvisors.ttypes import ApplicationStates, StartingFailureStrategies, RunningFailureStrategies application = ApplicationStatus('ApplicationTest', self.supvisors.logger) # check application default attributes self.assertEqual('ApplicationTest', application.application_name) self.assertEqual(ApplicationStates.STOPPED, application.state) self.assertFalse(application.major_failure) self.assertFalse(application.minor_failure) self.assertFalse(application.processes) self.assertFalse(application.start_sequence) self.assertFalse(application.stop_sequence) # check application default rules self.assertEqual(0, application.rules.start_sequence) self.assertEqual(0, application.rules.stop_sequence) self.assertEqual(StartingFailureStrategies.ABORT, application.rules.starting_failure_strategy) self.assertEqual(RunningFailureStrategies.CONTINUE, application.rules.running_failure_strategy)
def check_valid(self, parser): """ Test the parsing of a valid XML. """ from supvisors.application import ApplicationStatus from supvisors.process import ProcessStatus from supvisors.ttypes import RunningFailureStrategies, StartingFailureStrategies # test models & patterns self.assertItemsEqual([ 'dummy_model_01', 'dummy_model_02', 'dummy_model_03', 'dummy_model_04' ], parser.models.keys()) self.assertItemsEqual(['dummies_', 'dummies_01_', 'dummies_02_'], parser.patterns.keys()) # check unknown application application = ApplicationStatus('dummy_application_X', self.supvisors.logger) parser.load_application_rules(application) self.assert_default_application_rules(application.rules) # check first application application = ApplicationStatus('dummy_application_A', self.supvisors.logger) parser.load_application_rules(application) self.assert_default_application_rules(application.rules) # check second application application = ApplicationStatus('dummy_application_B', self.supvisors.logger) parser.load_application_rules(application) self.assert_application_rules(application.rules, 1, 4, StartingFailureStrategies.STOP, RunningFailureStrategies.RESTART_PROCESS) # check third application application = ApplicationStatus('dummy_application_C', self.supvisors.logger) parser.load_application_rules(application) self.assert_application_rules( application.rules, 20, 0, StartingFailureStrategies.ABORT, RunningFailureStrategies.STOP_APPLICATION) # check fourth application application = ApplicationStatus('dummy_application_D', self.supvisors.logger) parser.load_application_rules(application) self.assert_application_rules( application.rules, 0, 100, StartingFailureStrategies.CONTINUE, RunningFailureStrategies.RESTART_APPLICATION) # check program from unknown application: all default process = ProcessStatus('dummy_application_X', 'dummy_program_X0', self.supvisors) parser.load_process_rules(process) self.assert_default_process_rules(process.rules) # check unknown program from known application: all default process = ProcessStatus('dummy_application_A', 'dummy_program_A0', self.supvisors) parser.load_process_rules(process) self.assert_default_process_rules(process.rules) # check known program from known but not related application: all default process = ProcessStatus('dummy_application_A', 'dummy_program_B1', self.supvisors) parser.load_process_rules(process) self.assert_default_process_rules(process.rules) # check known empty program process = ProcessStatus('dummy_application_B', 'dummy_program_B0', self.supvisors) parser.load_process_rules(process) self.assert_default_process_rules(process.rules) # check dash addresses and valid other values process = ProcessStatus('dummy_application_B', 'dummy_program_B1', self.supvisors) parser.load_process_rules(process) self.assert_process_rules(process.rules, ['#'], 3, 50, True, False, 5, RunningFailureStrategies.CONTINUE) # check single address with required not applicable and out of range loading process = ProcessStatus('dummy_application_B', 'dummy_program_B2', self.supvisors) parser.load_process_rules(process) self.assert_process_rules(process.rules, ['10.0.0.3'], 0, 0, False, False, 1, RunningFailureStrategies.RESTART_PROCESS) # check wildcard address, optional and max loading process = ProcessStatus('dummy_application_B', 'dummy_program_B3', self.supvisors) parser.load_process_rules(process) self.assert_process_rules(process.rules, ['*'], 0, 0, False, False, 100, RunningFailureStrategies.STOP_APPLICATION) # check multiple addresses, all other incorrect values process = ProcessStatus('dummy_application_B', 'dummy_program_B4', self.supvisors) parser.load_process_rules(process) self.assert_process_rules(process.rules, ['10.0.0.3', '10.0.0.1', '10.0.0.5'], 0, 0, False, False, 1, RunningFailureStrategies.RESTART_APPLICATION) # check empty reference process = ProcessStatus('dummy_application_C', 'dummy_program_C0', self.supvisors) parser.load_process_rules(process) self.assert_default_process_rules(process.rules) # check unknown reference process = ProcessStatus('dummy_application_C', 'dummy_program_C1', self.supvisors) parser.load_process_rules(process) self.assert_default_process_rules(process.rules) # check known reference process = ProcessStatus('dummy_application_C', 'dummy_program_C2', self.supvisors) parser.load_process_rules(process) self.assert_process_rules(process.rules, ['*'], 0, 0, False, False, 25, RunningFailureStrategies.STOP_APPLICATION) # check other known reference process = ProcessStatus('dummy_application_C', 'dummy_program_C3', self.supvisors) parser.load_process_rules(process) self.assert_process_rules(process.rules, ['#'], 1, 0, True, True, 1, RunningFailureStrategies.CONTINUE) # check pattern with single matching and reference process = ProcessStatus('dummy_application_D', 'dummies_any', self.supvisors) parser.load_process_rules(process) self.assert_process_rules(process.rules, ['10.0.0.4', '10.0.0.2'], 0, 100, False, False, 10, RunningFailureStrategies.CONTINUE) # check pattern with multiple matching and configuration process = ProcessStatus('dummy_application_D', 'dummies_01_any', self.supvisors) parser.load_process_rules(process) self.assert_process_rules(process.rules, ['#'], 1, 1, False, True, 75, RunningFailureStrategies.CONTINUE) # check pattern with multiple matching and incorrect reference process = ProcessStatus('dummy_application_D', 'any_dummies_02_', self.supvisors) parser.load_process_rules(process) self.assert_default_process_rules(process.rules)
def test_running(self): """ Test the running method. """ from supvisors.application import ApplicationStatus from supvisors.process import ProcessStatus application = ApplicationStatus('ApplicationTest', self.supvisors.logger) self.assertFalse(application.running()) # add a stopped process info = any_stopped_process_info() process = ProcessStatus(info['group'], info['name'], self.supvisors) process.add_info('10.0.0.1', info) application.add_process(process) application.update_status() self.assertFalse(application.running()) # add a running process info = any_running_process_info() process = ProcessStatus(info['group'], info['name'], self.supvisors) process.add_info('10.0.0.1', info) application.add_process(process) application.update_status() self.assertTrue(application.running())
def test_update_status(self): """ Test the rules to update the status of the application method. """ from supervisor.states import ProcessStates from supvisors.application import ApplicationStatus from supvisors.process import ProcessStatus from supvisors.ttypes import ApplicationStates application = ApplicationStatus('ApplicationTest', self.supvisors.logger) # add processes to the application for info in database_copy(): process = ProcessStatus(info['group'], info['name'], self.supvisors) process.add_info('10.0.0.1', info) application.add_process(process) # init status # there are lots of states but the 'strongest' is STARTING # STARTING is a 'running' state so major/minor failures are applicable application.update_status() self.assertEqual(ApplicationStates.STARTING, application.state) # there is a FATAL state in the process database # no rule is set for processes, so there are only minor failures self.assertFalse(application.major_failure) self.assertTrue(application.minor_failure) # set FATAL process to major fatal_process = next((process for process in application.processes.values() if process.state == ProcessStates.FATAL), None) fatal_process.rules.required = True # update status. major failure is now expected application.update_status() self.assertEqual(ApplicationStates.STARTING, application.state) self.assertTrue(application.major_failure) self.assertFalse(application.minor_failure) # set STARTING process to RUNNING starting_process = next((process for process in application.processes.values() if process.state == ProcessStates.STARTING), None) starting_process.state = ProcessStates.RUNNING # update status. there is still one BACKOFF process leading to STARTING application application.update_status() self.assertEqual(ApplicationStates.STARTING, application.state) self.assertTrue(application.major_failure) self.assertFalse(application.minor_failure) # set BACKOFF process to EXITED backoff_process = next((process for process in application.processes.values() if process.state == ProcessStates.BACKOFF), None) backoff_process.state = ProcessStates.EXITED # update status. the 'strongest' state is now STOPPING # as STOPPING is not a 'running' state, failures are not applicable application.update_status() self.assertEqual(ApplicationStates.STOPPING, application.state) self.assertFalse(application.major_failure) self.assertFalse(application.minor_failure) # set STOPPING process to STOPPED stopping_process = next((process for process in application.processes.values() if process.state == ProcessStates.STOPPING), None) stopping_process.state = ProcessStates.STOPPED # update status. the 'strongest' state is now RUNNING # failures are applicable again application.update_status() self.assertEqual(ApplicationStates.RUNNING, application.state) self.assertTrue(application.major_failure) self.assertFalse(application.minor_failure) # set RUNNING processes to STOPPED for process in application.processes.values(): if process.state == ProcessStates.RUNNING: process.state = ProcessStates.STOPPED # update status. the 'strongest' state is now RUNNING # failures are not applicable anymore application.update_status() self.assertEqual(ApplicationStates.STOPPED, application.state) self.assertFalse(application.major_failure) self.assertFalse(application.minor_failure)
def test_deployment_state(self): """ Test the Deployment state of the FSM. """ from supvisors.application import ApplicationStatus from supvisors.process import ProcessStatus from supvisors.statemachine import AbstractState, DeploymentState from supvisors.ttypes import ApplicationStates, SupvisorsStates state = DeploymentState(self.supvisors) self.assertIsInstance(state, AbstractState) # test enter method # test that start_applications is called only when local address is the master address with patch.object(self.supvisors.starter, 'start_applications') as mocked_starter: self.supvisors.context.master = False state.enter() self.assertEqual(0, mocked_starter.call_count) # now master address is local self.supvisors.context.master = True state.enter() self.assertEqual(1, mocked_starter.call_count) # create application context application = ApplicationStatus('sample_test_2', self.supvisors.logger) self.supvisors.context.applications['sample_test_2'] = application for info in database_copy(): if info['group'] == 'sample_test_2': process = ProcessStatus(info['group'], info['name'], self.supvisors) process.rules.start_sequence = len(process.namespec()) % 3 process.rules.stop_sequence = len(process.namespec()) % 3 + 1 process.add_info('10.0.0.1', info) application.add_process(process) # test application updates self.supvisors.context.master = False self.assertEqual(ApplicationStates.STOPPED, application.state) self.assertFalse(application.minor_failure) self.assertFalse(application.major_failure) self.assertDictEqual({}, application.start_sequence) self.assertDictEqual({}, application.stop_sequence) state.enter() application = self.supvisors.context.applications['sample_test_2'] self.assertEqual(ApplicationStates.RUNNING, application.state) self.assertTrue(application.minor_failure) self.assertFalse(application.major_failure) # list order may differ, so break down self.assertItemsEqual([0, 1], application.start_sequence.keys()) self.assertItemsEqual([ application.processes['yeux_01'], application.processes['yeux_00'] ], application.start_sequence[0]) self.assertItemsEqual([application.processes['sleep']], application.start_sequence[1]) self.assertItemsEqual([1, 2], application.stop_sequence.keys()) self.assertItemsEqual([ application.processes['yeux_01'], application.processes['yeux_00'] ], application.stop_sequence[1]) self.assertItemsEqual([application.processes['sleep']], application.stop_sequence[2]) # test next method # stay in DEPLOYMENT if local is master and a deployment is in progress, whatever the conflict status with patch.object(self.supvisors.starter, 'check_starting', return_value=False): for conflict in [True, False]: with patch.object(self.supvisors.context, 'conflicting', return_value=conflict): self.supvisors.context.master = True result = state.next() self.assertEqual(SupvisorsStates.DEPLOYMENT, result) # return OPERATION if local is not master and no conflict, whatever the starting status self.supvisors.context.master = False with patch.object(self.supvisors.context, 'conflicting', return_value=False): for starting in [True, False]: with patch.object(self.supvisors.starter, 'check_starting', return_value=starting): result = state.next() self.assertEqual(SupvisorsStates.OPERATION, result) # return OPERATION if a deployment is in progress and no conflict, whatever the master status with patch.object(self.supvisors.starter, 'check_starting', return_value=True): with patch.object(self.supvisors.context, 'conflicting', return_value=False): for master in [True, False]: self.supvisors.context.master = master result = state.next() self.assertEqual(SupvisorsStates.OPERATION, result) # return CONCILIATION if local is not master and conflict detected, whatever the starting status self.supvisors.context.master = False with patch.object(self.supvisors.context, 'conflicting', return_value=True): for starting in [True, False]: with patch.object(self.supvisors.starter, 'check_starting', return_value=starting): result = state.next() self.assertEqual(SupvisorsStates.CONCILIATION, result) # return CONCILIATION if a deployment is in progress and conflict detected, whatever the master status with patch.object(self.supvisors.starter, 'check_starting', return_value=True): with patch.object(self.supvisors.context, 'conflicting', return_value=True): for master in [True, False]: self.supvisors.context.master = master result = state.next() self.assertEqual(SupvisorsStates.CONCILIATION, result) # no exit implementation. just call it without test state.exit()
def test_deployment_state(self): """ Test the Deployment state of the FSM. """ from supvisors.application import ApplicationStatus from supvisors.process import ProcessStatus from supvisors.statemachine import AbstractState, DeploymentState from supvisors.ttypes import ApplicationStates, SupvisorsStates state = DeploymentState(self.supvisors) self.assertIsInstance(state, AbstractState) # test enter method # test that start_applications is called only when local address is the master address with patch.object(self.supvisors.starter, 'start_applications') as mocked_starter: self.supvisors.context.master = False state.enter() self.assertEqual(0, mocked_starter.call_count) # now master address is local self.supvisors.context.master = True state.enter() self.assertEqual(1, mocked_starter.call_count) # create application context application = ApplicationStatus('sample_test_2', self.supvisors.logger) self.supvisors.context.applications['sample_test_2'] = application for info in database_copy(): if info['group'] == 'sample_test_2': process = ProcessStatus(info['group'], info['name'], self.supvisors) process.rules.start_sequence = len(process.namespec()) % 3 process.rules.stop_sequence = len(process.namespec()) % 3 + 1 process.add_info('10.0.0.1', info) application.add_process(process) # test application updates self.supvisors.context.master = False self.assertEqual(ApplicationStates.STOPPED, application.state) self.assertFalse(application.minor_failure) self.assertFalse(application.major_failure) self.assertDictEqual({}, application.start_sequence) self.assertDictEqual({}, application.stop_sequence) state.enter() application = self.supvisors.context.applications['sample_test_2'] self.assertEqual(ApplicationStates.RUNNING, application.state) self.assertTrue(application.minor_failure) self.assertFalse(application.major_failure) # list order may differ, so break down self.assertItemsEqual([0, 1], application.start_sequence.keys()) self.assertItemsEqual([application.processes['yeux_01'], application.processes['yeux_00']], application.start_sequence[0]) self.assertItemsEqual([application.processes['sleep']], application.start_sequence[1]) self.assertItemsEqual([1, 2], application.stop_sequence.keys()) self.assertItemsEqual([application.processes['yeux_01'], application.processes['yeux_00']], application.stop_sequence[1]) self.assertItemsEqual([application.processes['sleep']], application.stop_sequence[2]) # test next method # stay in DEPLOYMENT if local is master and a starting is in progress, whatever the conflict status with patch.object(self.supvisors.starter, 'check_starting', return_value=False): for conflict in [True, False]: with patch.object(self.supvisors.context, 'conflicting', return_value=conflict): self.supvisors.context.master = True result = state.next() self.assertEqual(SupvisorsStates.DEPLOYMENT, result) # return OPERATION if local is not master and no conflict, whatever the starting status self.supvisors.context.master = False with patch.object(self.supvisors.context, 'conflicting', return_value=False): for starting in [True, False]: with patch.object(self.supvisors.starter, 'check_starting', return_value=starting): result = state.next() self.assertEqual(SupvisorsStates.OPERATION, result) # return OPERATION if a starting is in progress and no conflict, whatever the master status with patch.object(self.supvisors.starter, 'check_starting', return_value=True): with patch.object(self.supvisors.context, 'conflicting', return_value=False): for master in [True, False]: self.supvisors.context.master = master result = state.next() self.assertEqual(SupvisorsStates.OPERATION, result) # return CONCILIATION if local is not master and conflict detected, whatever the starting status self.supvisors.context.master = False with patch.object(self.supvisors.context, 'conflicting', return_value=True): for starting in [True, False]: with patch.object(self.supvisors.starter, 'check_starting', return_value=starting): result = state.next() self.assertEqual(SupvisorsStates.CONCILIATION, result) # return CONCILIATION if a starting is in progress and conflict detected, whatever the master status with patch.object(self.supvisors.starter, 'check_starting', return_value=True): with patch.object(self.supvisors.context, 'conflicting', return_value=True): for master in [True, False]: self.supvisors.context.master = master result = state.next() self.assertEqual(SupvisorsStates.CONCILIATION, result) # no exit implementation. just call it without test state.exit()
def test_stopped(self): """ Test the stopped method. """ from supvisors.application import ApplicationStatus from supvisors.process import ProcessStatus application = ApplicationStatus('ApplicationTest', self.supvisors.logger) self.assertTrue(application.stopped()) # add a stopped process info = any_stopped_process_info() process = ProcessStatus(info['group'], info['name'], self.supvisors) process.add_info('10.0.0.1', info) application.add_process(process) application.update_status() self.assertTrue(application.stopped()) # add a running process info = any_running_process_info() process = ProcessStatus(info['group'], info['name'], self.supvisors) process.add_info('10.0.0.1', info) application.add_process(process) application.update_status() self.assertFalse(application.stopped())
def test_update_status(self): """ Test the rules to update the status of the application method. """ from supervisor.states import ProcessStates from supvisors.application import ApplicationStatus from supvisors.process import ProcessStatus from supvisors.ttypes import ApplicationStates application = ApplicationStatus('ApplicationTest', self.supvisors.logger) # add processes to the application for info in ProcessInfoDatabase: process = ProcessStatus(info['group'], info['name'], self.supvisors) process.add_info('10.0.0.1', info.copy()) application.add_process(process) # init status # there are lots of states but the 'strongest' is STARTING # STARTING is a 'running' state so major/minor failures are applicable application.update_status() self.assertEqual(ApplicationStates.STARTING, application.state) # there is a FATAL state in the process database # no rule is set for processes, so there are only minor failures self.assertFalse(application.major_failure) self.assertTrue(application.minor_failure) # set FATAL process to major fatal_process = next((process for process in application.processes.values() if process.state == ProcessStates.FATAL), None) fatal_process.rules.required = True # update status. major failure is now expected application.update_status() self.assertEqual(ApplicationStates.STARTING, application.state) self.assertTrue(application.major_failure) self.assertFalse(application.minor_failure) # set STARTING process to RUNNING starting_process = next( (process for process in application.processes.values() if process.state == ProcessStates.STARTING), None) starting_process.state = ProcessStates.RUNNING # update status. there is still one BACKOFF process leading to STARTING application application.update_status() self.assertEqual(ApplicationStates.STARTING, application.state) self.assertTrue(application.major_failure) self.assertFalse(application.minor_failure) # set BACKOFF process to EXITED backoff_process = next( (process for process in application.processes.values() if process.state == ProcessStates.BACKOFF), None) backoff_process.state = ProcessStates.EXITED # update status. the 'strongest' state is now STOPPING # as STOPPING is not a 'running' state, failures are not applicable application.update_status() self.assertEqual(ApplicationStates.STOPPING, application.state) self.assertFalse(application.major_failure) self.assertFalse(application.minor_failure) # set STOPPING process to STOPPED stopping_process = next( (process for process in application.processes.values() if process.state == ProcessStates.STOPPING), None) stopping_process.state = ProcessStates.STOPPED # update status. the 'strongest' state is now RUNNING # failures are applicable again application.update_status() self.assertEqual(ApplicationStates.RUNNING, application.state) self.assertTrue(application.major_failure) self.assertFalse(application.minor_failure) # set RUNNING processes to STOPPED for process in application.processes.values(): if process.state == ProcessStates.RUNNING: process.state = ProcessStates.STOPPED # update status. the 'strongest' state is now RUNNING # failures are not applicable anymore application.update_status() self.assertEqual(ApplicationStates.STOPPED, application.state) self.assertFalse(application.major_failure) self.assertFalse(application.minor_failure)