def fill_database(self): self.o = Orchestration(name='name', version=1, description='desc') self.at = ActionTemplate(name='action_name', version=1, action_type=ActionType.SHELL, code='') db.session.add_all([self.o, self.at])
def fill_database(self): v1 = Vault(user_id=ROOT, name='home', value='home_content') v2 = Vault(user_id=ROOT, name='command', value='command_content') v3 = Vault(user_id=ROOT, name='foo', value='foo_content') at = ActionTemplate('vault var in mapping', 1, action_type=ActionType.TEST, code='{{input.command}}', schema=dict( input=dict(command=dict(type='string')), required=['command']), id=self.ACTION_TEMPLATE1) at2 = ActionTemplate('vault var in action template code', 1, action_type=ActionType.TEST, code='{{vault.foo}}', schema=dict(required=['vault.foo']), id=self.ACTION_TEMPLATE2) o = Orchestration('vault', 1, id=self.ORCH) s1 = o.add_step(False, name='vault var in step code', action_type=ActionType.TEST, code="{{vault.home}}") s2 = o.add_step( False, action_template=at, schema=dict(mapping={'command': { 'from': 'vault.command' }}), parents=[s1]) s3 = o.add_step(False, action_template=at2, parents=[s2]) db.session.add_all([v1, v2, v3, o])
def test_init_on_load(self): o = Orchestration(id='aaaaaaaa-1234-5678-1234-aaaaaaaa0001', name='Test Orchestration', version=1, description='description') s1 = o.add_step(undo=False, action_template=self.at, parents=[], children=[], stop_on_error=False, id='bbbbbbbb-1234-5678-1234-bbbbbbbb0001') db.session.add(o) db.session.commit() del o o = Orchestration.query.get('aaaaaaaa-1234-5678-1234-aaaaaaaa0001') self.assertEqual({s1: []}, o._graph.succ) s2 = o.add_step(undo=False, action_template=self.at, parents=[], children=[], stop_on_error=False, id='bbbbbbbb-1234-5678-1234-bbbbbbbb0002') del o o = Orchestration.query.get('aaaaaaaa-1234-5678-1234-aaaaaaaa0001') self.assertEqual({s1: [], s2: []}, o._graph.succ)
def test_validate_input_chain_mapping_action_template(self): o = Orchestration("test", 1) at = ActionTemplate("action", 1, action_type=ActionType.SHELL, code='', schema={ 'input': { 'param1': {} }, 'required': ['param1'] }) s1 = o.add_step(undo=False, action_template=at, schema={'mapping': { 'param1': "value" }}) s2 = o.add_step(undo=False, action_type=ActionType.SHELL, schema={ 'input': { 'param2': {} }, 'required': ['param2'] }) validate_input_chain(o, params=dict(input={'param2'})) with self.assertRaises(errors.MissingParameters) as e: validate_input_chain(o, params=dict(input={'param1'})) self.assertEqual(['input.param2'], e.exception.parameters)
def fill_database(self): soft = Software(name="python", version="3.8.3", family="programming", filename=self.filename, size=self.size, checksum=self.checksum, id=self.SOFTWARE, last_modified_at=defaults.INITIAL_DATEMARK.strftime( defaults.DATEMARK_FORMAT)) ssa = SoftwareServerAssociation( software_id=self.SOFTWARE, server_id=self.SERVER1, path=self.source_folder, last_modified_at=defaults.INITIAL_DATEMARK.strftime( defaults.DATEMARK_FORMAT)) o = Orchestration(name="install python 3.8.3", version=1, description="installs python 3.8.3", id=self.ORCH, last_modified_at=defaults.INITIAL_DATEMARK.strftime( defaults.DATEMARK_FORMAT)) s1 = o.add_step(id=self.STEP1, action_template=ActionTemplate.query.get( ActionTemplate.SEND_SOFTWARE), undo=False, undo_on_error=False, schema={ "mapping": { "software": self.SOFTWARE, "server": { "from": "env.server_id" } } }, last_modified_at=defaults.INITIAL_DATEMARK.strftime( defaults.DATEMARK_FORMAT)) o.add_step(id=self.STEP2, undo=False, undo_on_error=False, parents=[s1], code="{{input.file}}", action_type="SHELL", expected_rc=0, last_modified_at=defaults.INITIAL_DATEMARK.strftime( defaults.DATEMARK_FORMAT)) db.session.add_all([soft, ssa, o])
def fill_database(self): self.at = ActionTemplate(name='action_name', version=1, action_type=ActionType.SHELL, code='') self.o = Orchestration(name='name', version=1) self.st1 = self.o.add_step(undo=False, action_template=self.at) self.st2 = self.o.add_step(undo=False, action_template=self.at, children=[self.st1]) self.st3 = self.o.add_step(undo=False, action_template=self.at, children=[self.st2]) db.session.add_all([self.at, self.o])
def test_validate_input_chain(self): o = Orchestration("test", 1) s1 = o.add_step(undo=False, schema={ 'input': { 'param1': {} }, 'required': ['param1'], 'output': { 'param2': {} } }, action_type=ActionType.SHELL, code='') s2 = o.add_step(undo=False, parents=[s1], schema={ 'input': { 'param3': {}, 'param4': {} }, 'required': ['param3', 'param4'], 'mapping': { 'param3': { 'from': 'param2' } } }, action_type=ActionType.SHELL, code='') s3 = o.add_step(undo=False, parents=[s2], schema={ 'input': { 'param2': {}, 'param5': {} }, 'required': ['param2'] }, action_type=ActionType.SHELL, code='') validate_input_chain(o, params=dict(input={'param1', 'param4'})) with self.assertRaises(errors.MissingParameters) as e: validate_input_chain(o, params=dict(input={'param1'})) self.assertEqual(['input.param4'], e.exception.parameters)
def test_orchestration_resource_list(self): resp = self.client.get(url_for('api_1_0.orchestrationlist'), headers=self.auth.header) self.assertEqual([], resp.get_json()) o = Orchestration(name='orchestration_name', version=1, description='desc') db.session.add(o) db.session.commit() resp = self.client.get(url_for('api_1_0.orchestrationlist'), headers=self.auth.header) self.assertEqual([o.to_json(add_params=True)], resp.get_json())
def test_create_cmd_from_orchestration_one_step(self): at = ActionTemplate(id='aaaaaaaa-1234-5678-1234-aaaaaaaa0001', name='create dir', version=1, action_type=ActionType.SHELL, code='mkdir {{dir}}', expected_stdout='', expected_rc=0, system_kwargs={}) o = Orchestration('Test Orchestration', 1, 'description', id='bbbbbbbb-1234-5678-1234-bbbbbbbb0001') me = Server('me', port=5000, me=True, id='cccccccc-1234-5678-1234-cccccccc0001') remote = Server('remote', port=5000, id='cccccccc-1234-5678-1234-cccccccc0002') db.session.add_all([me, remote, o]) s1 = o.add_step(id='eeeeeeee-1234-5678-1234-eeeeeeee0001', undo=False, action_template=at, parents=[], target=[]) cc = create_cmd_from_orchestration(o, Context({'dir': './test_folder'}), hosts={'all': [me.id]}, executor=None, register=mock.Mock()) c1, = cc._dag.get_nodes_at_level(1) self.assertTupleEqual(('cccccccc-1234-5678-1234-cccccccc0001', 'eeeeeeee-1234-5678-1234-eeeeeeee0001'), c1.id) self.assertIsInstance(c1, Command) self.assertTrue(c1.stop_on_error) self.assertTrue(c1.undo_on_error) self.assertIsNone(c1.stop_undo_on_error)
def test_orch_creation(self): o = Orchestration( id="aaaaaaaa-1234-5678-1234-56781234aaa1", name='Test Orchestration', version=1, description='description', ) db.session.add(o) db.session.commit() del o o = Orchestration.query.get("aaaaaaaa-1234-5678-1234-56781234aaa1")
def test_validate_input_chain_vault_container(self): o = Orchestration("test", 1) at1 = ActionTemplate("action1", 1, action_type=ActionType.SHELL, code='', schema={ 'input': { 'vault.var': {} }, 'required': ['vault.var'] }) s1 = o.add_step(undo=False, action_template=at1, schema={}) validate_input_chain(o, params=dict(vault={'var'})) with self.assertRaises(errors.MissingParameters) as e: validate_input_chain(o, params=dict()) self.assertEqual(['vault.var'], e.exception.parameters)
def test_validate_input_chain_mapping_default_value(self): o = Orchestration("test", 1) at1 = ActionTemplate("action1", 1, action_type=ActionType.SHELL, code='', schema={ 'input': { 'input1': {}, 'input2': { 'default': 1 } }, 'required': ['input1'], 'output': ['out1', 'out2'] }) at2 = ActionTemplate("action2", 1, action_type=ActionType.SHELL, code='', schema={ 'input': { 'out1': {}, 'out2': {}, 'input2': {} }, 'required': ['out1', 'out2', 'input2'], 'output': [] }) s1 = o.add_step(undo=False, action_template=at1, schema={}) s2 = o.add_step(undo=False, action_template=at2, schema={}, parents=[s1]) with self.assertRaises(errors.MissingParameters) as e: validate_input_chain(o, params=dict(input={'input1'})) self.assertEqual(['input.input2'], e.exception.parameters)
def fill_database(self): # create data for orchestration at = ActionTemplate(name='mkdir', version=1, action_type=ActionType.SHELL, code='mkdir -f {{input.folder}}', schema={ 'input': { 'folder': { 'type': 'string' } }, 'required': ['folder'] }, expected_rc=0) o = Orchestration(name='Create folder', version=1, description="creates a folder") s = o.add_step(undo=False, action_template=at) db.session.add_all([at, o, s]) o_wrapper = Orchestration(name="launch create folder", version=1) s21 = o_wrapper.add_step( undo=False, action_template=ActionTemplate.query.get( ActionTemplate.ORCHESTRATION), schema={'mapping': { 'orchestration': str(o.id), }}, parents=[]) db.session.add_all([o_wrapper, s21])
def test_parameters(self): o = Orchestration(id=1, name='Test Orchestration', version=1, description='description') s1 = o.add_step(undo=False, action_template=self.at, parents=[], children=[], stop_on_error=False, id=1) at = ActionTemplate(name='action', version=1, action_type=ActionType.SHELL, code='code to run', expected_stdout='expected output', expected_rc=0, system_kwargs={}) s1.action_template = at
def fill_database(self): self.o = Orchestration('create user', version=1) self.at1 = ActionTemplate(action_type=ActionType.SHELL, name="create folder", version=1, code="sudo mkdir -p {{folder}}") self.at2 = ActionTemplate(action_type=ActionType.SHELL, name="delete folder", version=1, code="sudo rm -fr {{folder}}") self.at3 = ActionTemplate(action_type=ActionType.SHELL, name="create user", version=1, code="sudo useradd -d {{home}} {{user}}") self.at4 = ActionTemplate(action_type=ActionType.SHELL, name="delete user", version=1, code="sudo userdel {{user}}") self.st1 = self.o.add_step(undo=False, action_template=self.at1) self.st2 = self.o.add_step(undo=True, action_template=self.at2, parents=[self.st1]) self.st3 = self.o.add_step(undo=False, action_template=self.at3, parents=[self.st1]) self.st4 = self.o.add_step(undo=True, action_template=self.at4, parents=[self.st3]) db.session.add(self.o) self.o2 = Orchestration('create user', version=2) self.st21 = self.o2.add_step(undo=False, action_template=self.at1) db.session.add(self.o2) db.session.commit()
def post(self): json_data = request.get_json() generated_version = False if 'version' not in json_data: generated_version = True json_data['version'] = Orchestration.query.filter_by( name=json_data['name']).count() + 1 o = Orchestration(**json_data) db.session.add(o) db.session.commit() resp_data = {'id': str(o.id)} if generated_version: resp_data.update(version=o.version) return resp_data, 201
def test_set_dependencies(self): s1 = Step(orchestration=None, undo=False, stop_on_error=False, action_template=self.at, id='11111111-2222-3333-4444-555555550001') s2 = Step(orchestration=None, undo=False, stop_on_error=False, action_template=self.at, id='11111111-2222-3333-4444-555555550002') s3 = Step(orchestration=None, undo=False, stop_on_error=False, action_template=self.at, id='11111111-2222-3333-4444-555555550003') o = Orchestration('test', 1, id='11111111-2222-3333-4444-555555550004', description='description test', steps=[s1, s2, s3], dependencies={ s1.id: [s2.id], s2.id: [s3.id], s3.id: [] }) self.assertDictEqual(o.dependencies, {s1: [s2], s2: [s3], s3: []}) self.assertListEqual(o.steps, [s1, s2, s3]) with self.assertRaises(ValueError): o.set_dependencies({s1: [s2], s2: [s3], s3: [4]}) o = Orchestration('test', 1, id='11111111-2222-3333-4444-555555550004', description='description test', steps=[s1, s2, s3]) self.assertListEqual([s1, s2, s3], o.steps) o.set_dependencies([(s1, s2), (s2, s3)]) self.assertDictEqual(o.dependencies, {s1: [s2], s2: [s3], s3: []})
def setUp(self): """Create and configure a new app instance for each test.""" # create the app with common test config self.app = create_app('test') self.app_context = self.app.app_context() self.app_context.push() self.client = self.app.test_client() self.headers = {"Authorization": f"Bearer {create_access_token('00000000-0000-0000-0000-000000000001')}"} db.create_all() self.at1 = ActionTemplate(id='11111111-2222-3333-4444-555555550001', name='action1', version=1, action_type=ActionType.SHELL, code='code to run', expected_stdout='expected output', expected_stderr='stderr', expected_rc=0, system_kwargs={}) self.at2 = ActionTemplate(id='11111111-2222-3333-4444-555555550002', name='action2', version=1, action_type=ActionType.SHELL, code='code to run', expected_stdout='expected output', expected_stderr='stderr', expected_rc=0, system_kwargs={}) self.o = Orchestration(id='11111111-2222-3333-4444-666666660001', name='orchestration_name', version=1, description='desc')
def test_validate_input_chain_mapping_with_orch(self): o = Orchestration('Schema Orch', 1, id='00000000-0000-0000-0000-000000000001') s1 = o.add_step(id=1, action_type=ActionType.SHELL, undo=False, schema={ 'input': { '1_a': {}, '1_b': {} }, 'required': ['1_b'], 'output': ['1_c'] }) s2 = o.add_step(undo=False, action_type=ActionType.SHELL, schema={ 'input': { '2_a': {} }, 'required': ['2_a'], 'output': ['2_b'] }) s3 = o.add_step(undo=False, action_type=ActionType.SHELL, parents=[s1], schema={ 'input': { '3_a': {}, '3_b': {} }, 'required': ['3_a'], 'mapping': { '3_a': { 'from': '2_b' } } }) db.session.add(o) o2 = Orchestration('Schema Orch', 1) at = ActionTemplate.query.filter_by(name='orchestration', version=1).one() s1 = o2.add_step(id=1, action_template=at, undo=False, schema={'mapping': { 'orchestration': o.id }}) s2 = o2.add_step(undo=False, action_type=1, parents=[s1], schema={ 'input': { "1": {}, "2": {} }, 'required': ["1"], 'mapping': { "1": { 'from': '1_c' } } }) validate_input_chain(o2, params=dict(input={'hosts', '1_b', '2_a'}))
def test_attributes_and_methods(self): o = Orchestration(id=1, name='Test Orchestration', version=1, description='description') s1 = o.add_step(undo=False, action_template=self.at, parents=[], children=[], stop_on_error=False, id=1) s2 = o.add_step(undo=True, action_template=self.at, parents=[s1], children=[], stop_on_error=False, id=2) with self.assertRaises(errors.ParentUndoError): o.add_step(undo=False, action_template=self.at, parents=[s2], children=[], stop_on_error=False) self.assertListEqual(o.steps, [s1, s2]) o2 = Orchestration('dto', 2, id=2) s21 = o2.add_step(undo=False, action_template=self.at, parents=[], children=[], stop_on_error=False, id=21) with self.assertRaises(ValueError): o.add_parents(s21, [s1]) o.delete_step(s2) self.assertListEqual(o.steps, [s1]) self.assertListEqual(o.children[s1], []) s2 = o.add_step(undo=False, action_template=self.at, parents=[s1], children=[], stop_on_error=False, id=2) s3 = o.add_step(undo=False, action_template=self.at, parents=[s2], children=[], stop_on_error=False, id=3) s4 = o.add_step(undo=False, action_template=self.at, parents=[s2], children=[], stop_on_error=False, id=4) s5 = o.add_step(undo=False, action_template=self.at, parents=[s4], children=[], stop_on_error=False, id=5) s6 = o.add_step(undo=False, action_template=self.at, parents=[s4], children=[], stop_on_error=False, id=6) s7 = o.add_step(undo=False, action_template=self.at, parents=[s1], children=[s2], stop_on_error=False, id=7) self.assertListEqual(o.steps, [s1, s2, s3, s4, s5, s6, s7]) self.assertDictEqual( o.children, { s1: [s2, s7], s2: [s3, s4], s3: [], s4: [s5, s6], s5: [], s6: [], s7: [s2] }) self.assertDictEqual(o.parents, { s1: [], s2: [s1, s7], s3: [s2], s4: [s2], s5: [s4], s6: [s4], s7: [s1] }) with self.assertRaises(errors.ChildDoError): o.add_step(undo=True, action_template=self.at, parents=[s1], children=[s2], stop_on_error=False) with self.assertRaises(errors.CycleError): o.add_step(undo=False, action_template=self.at, parents=[s6], children=[s1], stop_on_error=False) # Check parent functions o.add_parents(s6, [s2]) self.assertListEqual(o.children[s2], [s3, s4, s6]) o.set_parents(s6, [s3, s4]) self.assertListEqual(o.parents[s6], [s3, s4]) o.delete_parents(s6, [s3, s2]) self.assertListEqual(o.parents[s6], [s4]) # Check children functions o.add_children(s3, [s6]) self.assertListEqual(o.parents[s6], [s4, s3]) o.delete_children(s4, [s5, s6]) self.assertListEqual(o.parents[s6], [s3]) self.assertListEqual(o.parents[s5], []) o.set_children(s4, [s5, s6]).set_children(s3, []) self.assertListEqual([s4], o.parents[s6]) self.assertListEqual([s4], o.parents[s5]) self.assertListEqual([], o.children[s3]) # properties and default values s = o.add_step(undo=False, action_template=self.at) self.assertEqual(True, s.stop_on_error) self.assertEqual('code to run', s.code) self.assertEqual('expected output', s.expected_stdout) s.expected_output = 'changed' self.assertEqual('changed', s.expected_output) s.expected_output = '' self.assertEqual('', s.expected_output) s.expected_output = None self.assertEqual('expected output', s.expected_stdout) self.assertEqual(0, s.expected_rc) s.expected_rc = 2 self.assertEqual(2, s.expected_rc) s.expected_rc = 0 self.assertEqual(0, s.expected_rc) s.expected_rc = None self.assertEqual(0, s.expected_rc)
def fill_database(self): at1 = ActionTemplate(id='aaaaaaaa-1234-5678-1234-aaaaaaaa0001', name='create dir', version=1, action_type=ActionType.SHELL, code='useradd {{input.user}}; mkdir {{input.dir}}', expected_stdout='', expected_rc=0, system_kwargs={}) at2 = ActionTemplate(id='aaaaaaaa-1234-5678-1234-aaaaaaaa0002', name='rm dir', version=1, action_type=ActionType.SHELL, code='rmuser {{input.user}}', expected_stdout='', expected_rc=0, system_kwargs={}) at3 = ActionTemplate(id='aaaaaaaa-1234-5678-1234-aaaaaaaa0003', name='untar', version=1, action_type=ActionType.SHELL, code='tar -xf {{input.dir}}', expected_stdout='', expected_rc=0, system_kwargs={}) at4 = ActionTemplate(id='aaaaaaaa-1234-5678-1234-aaaaaaaa0004', name='install tibero', version=1, action_type=ActionType.SHELL, code='{{input.home}}/install_tibero.sh', expected_stdout='', expected_rc=0, system_kwargs={}) o = Orchestration('Test Orchestration', 1, 'description', id='bbbbbbbb-1234-5678-1234-bbbbbbbb0001') db.session.add(o) # Orch diagram # f f # 1-->3-->u4-->u5-->u6 # \/ \ \_____/ # /\ \ # 9-->u2 7-->u8 # b s1 = o.add_step(id='dddddddd-1234-5678-1234-dddddddd0001', undo=False, action_template=at1, parents=[], target=['frontend']) s2 = o.add_step(id='dddddddd-1234-5678-1234-dddddddd0002', undo=True, action_template=at2, parents=[s1], target=[]) s3 = o.add_step(id='dddddddd-1234-5678-1234-dddddddd0003', undo=False, action_template=at3, parents=[s1], target=['frontend']) s4 = o.add_step(id='dddddddd-1234-5678-1234-dddddddd0004', undo=True, action_template=at2, parents=[s3], target=[]) s5 = o.add_step(id='dddddddd-1234-5678-1234-dddddddd0005', undo=True, action_template=at2, parents=[s4], target=[]) s6 = o.add_step(id='dddddddd-1234-5678-1234-dddddddd0006', undo=True, action_template=at2, parents=[s4, s5], target=[]) s7 = o.add_step(id='dddddddd-1234-5678-1234-dddddddd0007', undo=False, action_template=at4, parents=[s3], target=[]) s8 = o.add_step(id='dddddddd-1234-5678-1234-dddddddd0008', undo=True, action_template=at2, parents=[s7], target=[]) s9 = o.add_step(id='dddddddd-1234-5678-1234-dddddddd0009', undo=False, action_template=at1, children=[s2, s3], target=['backend'])
def test_create_cmd_from_orchestration(self): at = ActionTemplate(id='aaaaaaaa-1234-5678-1234-aaaaaaaa0001', name='create dir', version=1, action_type=ActionType.SHELL, code='mkdir {{dir}}', expected_stdout='', expected_rc=0, system_kwargs={}) o = Orchestration('Test Orchestration', 1, 'description', id='bbbbbbbb-1234-5678-1234-bbbbbbbb0001') me = Server('me', port=5000, me=True, id='cccccccc-1234-5678-1234-cccccccc0001') remote = Server('remote', port=5000, id='cccccccc-1234-5678-1234-cccccccc0002') db.session.add_all([me, remote, o]) s1 = o.add_step(id='eeeeeeee-1234-5678-1234-eeeeeeee0001', undo=False, action_template=at, parents=[], target=['frontend']) s2 = o.add_step(id='eeeeeeee-1234-5678-1234-eeeeeeee0002', undo=True, action_template=at, parents=[s1], stop_on_error=False, target=[]) s3 = o.add_step(id='eeeeeeee-1234-5678-1234-eeeeeeee0003', undo=False, action_template=at, parents=[s1], stop_on_error=False, stop_undo_on_error=False, target=['frontend']) s4 = o.add_step(id='eeeeeeee-1234-5678-1234-eeeeeeee0004', undo=True, action_template=at, parents=[s3], target=[]) s5 = o.add_step(id='eeeeeeee-1234-5678-1234-eeeeeeee0005', undo=True, action_template=at, parents=[s4], stop_on_error=True, target=[]) s6 = o.add_step(id='eeeeeeee-1234-5678-1234-eeeeeeee0006', undo=True, action_template=at, parents=[s4, s5], target=[]) s7 = o.add_step(id='eeeeeeee-1234-5678-1234-eeeeeeee0007', undo=False, action_template=at, parents=[s3], undo_on_error=False, target=[]) s8 = o.add_step(id='eeeeeeee-1234-5678-1234-eeeeeeee0008', undo=True, action_template=at, parents=[s7], target=[]) s9 = o.add_step(id='eeeeeeee-1234-5678-1234-eeeeeeee0009', undo=False, action_template=at, children=[s2, s3], target=['backend']) cc = create_cmd_from_orchestration(o, Context({'dir': 'C:\\test_folder'}), hosts={ 'all': [me.id, remote.id], 'frontend': [me.id], 'backend': [remote.id] }, executor=None, register=mock.Mock()) c1, c9 = cc._dag.get_nodes_at_level(1) self.assertTupleEqual(('cccccccc-1234-5678-1234-cccccccc0001', 'eeeeeeee-1234-5678-1234-eeeeeeee0001'), c1.id) self.assertIsInstance(c1, Command) self.assertTrue(c1.stop_on_error) self.assertTrue(c1.undo_on_error) self.assertIsNone(c1.stop_undo_on_error) self.assertTupleEqual(('cccccccc-1234-5678-1234-cccccccc0002', 'eeeeeeee-1234-5678-1234-eeeeeeee0009'), c9.id) self.assertIsInstance(c9, ProxyCommand) self.assertTrue(c9.stop_on_error) self.assertTrue(c9.undo_on_error) self.assertIsNone(c9.stop_undo_on_error) c21 = c1.undo_command self.assertTupleEqual(('cccccccc-1234-5678-1234-cccccccc0001', 'eeeeeeee-1234-5678-1234-eeeeeeee0002'), c21.id) self.assertIsInstance(c21, UndoCommand) self.assertFalse(c21.stop_on_error) c22 = c9.undo_command self.assertTupleEqual(('cccccccc-1234-5678-1234-cccccccc0002', 'eeeeeeee-1234-5678-1234-eeeeeeee0002'), c22.id) self.assertIsInstance(c22, ProxyUndoCommand) self.assertFalse(c22.stop_on_error) c3, = cc._dag.get_nodes_at_level(2) self.assertTupleEqual(('cccccccc-1234-5678-1234-cccccccc0001', 'eeeeeeee-1234-5678-1234-eeeeeeee0003'), c3.id) self.assertIsInstance(c3, Command) self.assertFalse(c3.stop_on_error) self.assertTrue(c3.undo_on_error) self.assertFalse(c3.stop_undo_on_error) self.assertTupleEqual(('undo', 'eeeeeeee-1234-5678-1234-eeeeeeee0003'), c3.undo_command.id) self.assertIsInstance(c3.undo_command, CompositeCommand) self.assertFalse(c3.undo_command.stop_on_error) self.assertIsNone(c3.undo_command.stop_undo_on_error) c4, = c3.undo_command._dag.get_nodes_at_level(1) self.assertIsInstance(c4, UndoCommand) self.assertFalse(c4.stop_on_error) c5, = c3.undo_command._dag.get_nodes_at_level(2) self.assertIsInstance(c5, UndoCommand) self.assertTrue(c5.stop_on_error) c6, = c3.undo_command._dag.get_nodes_at_level(3) self.assertIsInstance(c6, UndoCommand) self.assertFalse(c6.stop_on_error) cc7, = cc._dag.get_nodes_at_level(3) self.assertEqual('eeeeeeee-1234-5678-1234-eeeeeeee0007', cc7.id) self.assertIsInstance(cc7, CompositeCommand) self.assertFalse(cc7.stop_on_error) self.assertFalse(cc7.stop_undo_on_error) c71, c72 = cc7._dag.root self.assertTupleEqual(('cccccccc-1234-5678-1234-cccccccc0001', 'eeeeeeee-1234-5678-1234-eeeeeeee0007'), c71.id) self.assertIsInstance(c71, Command) self.assertTrue(c71.stop_on_error) self.assertFalse(c71.undo_on_error) self.assertIsNone(c71.stop_undo_on_error) self.assertTupleEqual(('cccccccc-1234-5678-1234-cccccccc0001', 'eeeeeeee-1234-5678-1234-eeeeeeee0008'), c71.undo_command.id) self.assertIsInstance(c71.undo_command, UndoCommand) self.assertTrue(c71.undo_command.stop_on_error) self.assertTupleEqual(('cccccccc-1234-5678-1234-cccccccc0002', 'eeeeeeee-1234-5678-1234-eeeeeeee0007'), c72.id) self.assertIsInstance(c72, ProxyCommand) self.assertTrue(c72.stop_on_error) self.assertFalse(c72.undo_on_error) self.assertIsNone(c72.stop_undo_on_error) self.assertTupleEqual(('cccccccc-1234-5678-1234-cccccccc0002', 'eeeeeeee-1234-5678-1234-eeeeeeee0008'), c72.undo_command.id) self.assertIsInstance(c72.undo_command, ProxyUndoCommand) self.assertTrue(c72.undo_command.stop_on_error)
def test_eq_imp(self): o1 = Orchestration('dto', 1) s11 = o1.add_step(False, self.at) s12 = o1.add_step(False, self.at, parents=[s11]) o2 = Orchestration('dto', 2) s21 = o2.add_step( False, self.at, ) s22 = o2.add_step(False, self.at, parents=[s21]) self.assertTrue(o1.eq_imp(o2)) self.assertTrue(o2.eq_imp(o1)) s23 = o2.add_step(True, self.at, parents=[s22]) self.assertFalse(o1.eq_imp(o2)) self.assertFalse(o2.eq_imp(o1)) s13 = o1.add_step(True, self.at, parents=[s12]) self.assertTrue(o1.eq_imp(o2)) self.assertTrue(s13.eq_imp(s23)) self.assertTrue(o1.eq_imp(o2))
def test_schema(self): self.maxDiff = None o = Orchestration('Schema Orch', 1) s1 = o.add_step(id=1, action_type=ActionType.SHELL, undo=False, schema={ 'input': { '1_a': {}, '1_b': {} }, 'required': ['1_b'], 'output': ['1_c'] }) s2 = o.add_step(undo=False, action_type=ActionType.SHELL, parents=[s1], schema={ 'input': { '2_a': {} }, 'required': ['2_a'], 'mapping': { '2_a': { 'from': '1_c' } } }) self.assertDictEqual( { 'input': { '1_a': {}, '1_b': {} }, 'required': ['input.1_b'], 'output': ['1_c'] }, o.schema) o = Orchestration('Schema Orch', 1, id='00000000-0000-0000-0000-000000000001') s1 = o.add_step(id=1, action_type=ActionType.SHELL, undo=False, schema={ 'input': { '1_a': {}, '1_b': {} }, 'required': ['1_b'], 'output': ['1_c'] }) s2 = o.add_step(undo=False, action_type=ActionType.SHELL, schema={ 'input': { '2_a': {} }, 'required': ['2_a'], 'output': ['2_b'] }) s3 = o.add_step(undo=False, action_type=ActionType.SHELL, parents=[s1], schema={ 'input': { '3_a': {}, '3_b': {} }, 'required': ['3_a'], 'mapping': { '3_a': { 'from': '2_b' } } }) self.assertDictEqual( { 'input': { '1_a': {}, '1_b': {}, '2_a': {}, '3_b': {} }, 'required': ['input.1_b', 'input.2_a'], 'output': ['1_c', '2_b'] }, o.schema) db.session.add(o) o2 = Orchestration('Schema Orch', 1) at = ActionTemplate.query.filter_by(name='orchestration', version=1).one() s1 = o2.add_step(id=1, action_template=at, undo=False, schema={'mapping': { 'orchestration': o.id }}) s2 = o2.add_step(undo=False, action_type=1, parents=[s1], schema={ 'input': { '1': {}, '2': {} }, 'required': ['1', '2'], 'mapping': { '1': { 'from': '1_c' }, '2': { 'from': '5' } } }) with self.assertRaises(errors.MappingError): self.assertDictEqual( { 'input': { 'hosts': at.schema['input']['hosts'], '1_a': {}, '1_b': {}, '2_a': {}, '3_b': {} }, 'required': ['input.1_b', 'input.2_a', 'input.hosts'], 'output': ['1_c', '2_b'] }, o2.schema) s2.schema = { 'input': { '1': {}, '2': {} }, 'required': ['1'], 'mapping': { '1': { 'from': 'env.server_id' }, '2': { 'from': '5' } } } self.assertDictEqual( { 'input': { 'hosts': at.schema['input']['hosts'], 'version': { 'type': 'integer' }, # from ActionTemplate '1_a': {}, '1_b': {}, '2_a': {}, '3_b': {} }, 'required': ['input.1_b', 'input.2_a', 'input.hosts'], 'output': ['1_c', '2_b'] }, o2.schema) # test a container required in step o = Orchestration('Schema Orch', 1) s1 = o.add_step(id=1, action_type=ActionType.SHELL, undo=False, schema={ 'container': { '1_a': {} }, 'required': ['container.1_a'] }) s2 = o.add_step(id=1, action_type=ActionType.SHELL, undo=False, schema={ 'input': { '1_b': {} }, 'mapping': { '1_b': { 'from': 'container.foo' } }, 'required': ['1_b'] }, parents=[s1]) self.assertDictEqual( { 'container': { '1_a': {} }, 'required': ['container.1_a', 'container.foo'] }, o.schema)
def test_sqlalchemy_entity_events(self, mock_now): mock_now.return_value = dt.datetime(2019, 4, 2, tzinfo=dt.timezone.utc) o = Orchestration('orch', 1, last_modified_at=dt.datetime(2019, 4, 1, tzinfo=dt.timezone.utc)) self.assertEqual(dt.datetime(2019, 4, 1, tzinfo=dt.timezone.utc), o.last_modified_at) db.session.add(o) self.assertEqual(dt.datetime(2019, 4, 1, tzinfo=dt.timezone.utc), o.last_modified_at) db.session.commit() self.assertEqual(dt.datetime(2019, 4, 2, tzinfo=dt.timezone.utc), o.last_modified_at) c = Catalog.query.get('Orchestration') self.assertEqual(dt.datetime(2019, 4, 2, tzinfo=dt.timezone.utc), c.last_modified_at) mock_now.return_value = dt.datetime(2019, 4, 3, tzinfo=dt.timezone.utc) o.name = 'modified' self.assertEqual(dt.datetime(2019, 4, 2, tzinfo=dt.timezone.utc), o.last_modified_at) db.session.flush() self.assertEqual(dt.datetime(2019, 4, 3, tzinfo=dt.timezone.utc), o.last_modified_at) db.session.commit() self.assertEqual(dt.datetime(2019, 4, 3, tzinfo=dt.timezone.utc), o.last_modified_at) c = Catalog.query.get('Orchestration') self.assertEqual(dt.datetime(2019, 4, 3, tzinfo=dt.timezone.utc), c.last_modified_at) mock_now.return_value = dt.datetime(2019, 4, 2, tzinfo=dt.timezone.utc) o2 = Orchestration('orch', 2, last_modified_at=dt.datetime( 2019, 4, 1, tzinfo=dt.timezone.utc)) db.session.add(o2) self.assertEqual(dt.datetime(2019, 4, 1, tzinfo=dt.timezone.utc), o2.last_modified_at) with bypass_datamark_update(): self.assertEqual(dt.datetime(2019, 4, 2, tzinfo=dt.timezone.utc), o2.last_modified_at) o3 = Orchestration('orch', 3, last_modified_at=dt.datetime( 2019, 4, 4, tzinfo=dt.timezone.utc)) db.session.add(o3) o.name = 'orch' self.assertEqual(dt.datetime(2019, 4, 3, tzinfo=dt.timezone.utc), o.last_modified_at) self.assertEqual(dt.datetime(2019, 4, 4, tzinfo=dt.timezone.utc), o3.last_modified_at) self.assertEqual(dt.datetime(2019, 4, 3, tzinfo=dt.timezone.utc), o.last_modified_at) self.assertEqual(dt.datetime(2019, 4, 2, tzinfo=dt.timezone.utc), o2.last_modified_at) self.assertEqual(dt.datetime(2019, 4, 4, tzinfo=dt.timezone.utc), o3.last_modified_at) db.session.commit() self.assertEqual(dt.datetime(2019, 4, 3, tzinfo=dt.timezone.utc), o.last_modified_at) self.assertEqual(dt.datetime(2019, 4, 2, tzinfo=dt.timezone.utc), o2.last_modified_at) self.assertEqual(dt.datetime(2019, 4, 4, tzinfo=dt.timezone.utc), o3.last_modified_at) c = Catalog.query.get('Orchestration') self.assertEqual(dt.datetime(2019, 4, 4, tzinfo=dt.timezone.utc), c.last_modified_at)
def _execute(self, params: Kwargs, timeout=None, context: Context = None) -> CompletedProcess: from dimensigon.use_cases.deployment import deploy_orchestration input_params = params['input'] cp = CompletedProcess().set_start_time() # Validation orchestration = Orchestration.get( input_params.pop('orchestration', None), input_params.pop('version', None)) if not isinstance(orchestration, Orchestration): cp.stderr = orchestration cp.success = False return cp.set_end_time() hosts = input_params.pop('hosts', None) if hosts is None: cp.stderr = "No hosts specified" cp.success = False return cp.set_end_time() hosts = copy.deepcopy(hosts) if isinstance(hosts, list): hosts = {'all': hosts} elif isinstance(hosts, str): hosts = {'all': [hosts]} not_found = normalize_hosts(hosts) if not_found: cp.stderr = str(errors.ServerNormalizationError(not_found)) cp.success = False return cp.set_end_time() o_exe = OrchExecution( orchestration_id=orchestration.id, target=hosts, params=input_params, executor_id=context.env.get('executor_id'), server_id=context.env.get('server_id'), parent_step_execution_id=context.env.get('step_execution_id')) db.session.add(o_exe) se = StepExecution.query.get(context.env.get('step_execution_id')) if se: se.child_orch_execution_id = o_exe.id db.session.commit() cp.stdout = f"orch_execution_id={o_exe.id}" env = dict(context.env) env.update( root_orch_execution_id=context.env['root_orch_execution_id'], orch_execution_id=o_exe.id, executor_id=context.env['executor_id']) ctx = Context(input_params, env, vault=context.vault) try: deploy_orchestration(orchestration=orchestration, hosts=hosts, var_context=ctx, execution=o_exe, timeout=timeout) except Exception as e: cp.stderr = str(e) if str(e) else e.__class__.__name__ cp.success = False else: context.merge_ctx(ctx) db.session.refresh(o_exe) cp.success = o_exe.success return cp.set_end_time()
class TestLaunchOrchestration(TestDimensigonBase, ValidateResponseMixin): def fill_database(self): self.o = Orchestration('create user', version=1) self.at1 = ActionTemplate(action_type=ActionType.SHELL, name="create folder", version=1, code="sudo mkdir -p {{folder}}") self.at2 = ActionTemplate(action_type=ActionType.SHELL, name="delete folder", version=1, code="sudo rm -fr {{folder}}") self.at3 = ActionTemplate(action_type=ActionType.SHELL, name="create user", version=1, code="sudo useradd -d {{home}} {{user}}") self.at4 = ActionTemplate(action_type=ActionType.SHELL, name="delete user", version=1, code="sudo userdel {{user}}") self.st1 = self.o.add_step(undo=False, action_template=self.at1) self.st2 = self.o.add_step(undo=True, action_template=self.at2, parents=[self.st1]) self.st3 = self.o.add_step(undo=False, action_template=self.at3, parents=[self.st1]) self.st4 = self.o.add_step(undo=True, action_template=self.at4, parents=[self.st3]) db.session.add(self.o) self.o2 = Orchestration('create user', version=2) self.st21 = self.o2.add_step(undo=False, action_template=self.at1) db.session.add(self.o2) db.session.commit() def test_launch_orchestration_error_on_server(self): resp = self.client.post(url_for('api_1_0.launch_orchestration', orchestration_id=str(self.o.id)), json={'hosts': 'aaaaaaaa-1234-5678-1234-56781234aaa1'}, headers=self.auth.header) self.assertEqual(404, resp.status_code) self.assertDictEqual( errors.format_error_content(errors.ServerNormalizationError(['aaaaaaaa-1234-5678-1234-56781234aaa1'])), resp.get_json()) def test_launch_orchestration_error_on_target(self): resp = self.client.post(url_for('api_1_0.launch_orchestration', orchestration_id=str(self.o.id)), json={'hosts': {'all': ['aaaaaaaa-1234-5678-1234-56781234aaa1'], 'other': 'granule'}}, headers=self.auth.header) self.validate_error_response(resp, errors.TargetNotNeeded(['other'])) s = self.o.steps[0] s.target = ['new'] db.session.commit() resp = self.client.post(url_for('api_1_0.launch_orchestration', orchestration_id=str(self.o.id)), json={'hosts': {'all': ['aaaaaaaa-1234-5678-1234-56781234aaa1']}}, headers=self.auth.header) self.validate_error_response(resp, errors.TargetUnspecified(['new'])) @patch('dimensigon.web.api_1_0.urls.use_cases.uuid.uuid4') @patch('dimensigon.web.api_1_0.urls.use_cases.Context') @patch('dimensigon.web.api_1_0.urls.use_cases.deploy_orchestration') def test_launch_orchestration_ok_foreground(self, mock_deploy, mock_var_context, mock_uuid4): mock_uuid4.return_value = 'a7083c43-34cc-4b26-91f0-ea0928cf5945' data = {'hosts': self.s1.id, 'params': {'folder': '/home/{{user}}', 'home': '{{folder}}', 'user': '******'}, 'skip_validation': True, 'background': False} oe = OrchExecution(id=mock_uuid4.return_value, orchestration_id=self.o.id) db.session.add(oe) db.session.commit() def deploy_orchestration(*args, **kwargs): return mock_uuid4.return_value mock_deploy.side_effect = deploy_orchestration MockVarContext = mock.Mock(name='MockVarContext') mock_var_context.return_value = MockVarContext resp = self.client.post(url_for('api_1_0.launch_orchestration', orchestration_id=str(self.o.id)), json=data, headers=self.auth.header) self.assertEqual(200, resp.status_code) self.assertDictEqual(oe.to_json(add_step_exec=True, split_lines=True), resp.get_json()) mock_var_context.assert_called_once_with(data['params'], dict(execution_id=None, root_orch_execution_id=mock_uuid4.return_value, orch_execution_id=mock_uuid4.return_value, executor_id=ROOT), vault={}) mock_deploy.assert_called_once_with(orchestration=self.o, var_context=MockVarContext, hosts={'all': [self.s1.id]}, timeout=None) @patch('dimensigon.web.api_1_0.urls.use_cases.uuid.uuid4') @patch('dimensigon.web.api_1_0.urls.use_cases.Context') @patch('dimensigon.web.api_1_0.urls.use_cases.executor.submit') def test_launch_orchestration_ok_background(self, mock_submit, mock_var_context, mock_uuid4): with self.subTest("Test background resolved"): mock_uuid4.return_value = 'a7083c43-34cc-4b26-91f0-ea0928cf5945' data = {'hosts': self.s1.id, 'params': {'folder': '/home/{{user}}', 'home': '{{folder}}', 'user': '******'}, 'skip_validation': True, 'background': True} oe = OrchExecution(id=mock_uuid4.return_value, orchestration_id=self.o.id) db.session.add(oe) db.session.commit() mock_submit.return_value = mock.Mock() MockVarContext = mock.Mock(name='MockVarContext') mock_var_context.return_value = MockVarContext resp = self.client.post(url_for('api_1_0.launch_orchestration', orchestration_id=str(self.o.id)), json=data, headers=self.auth.header) mock_var_context.assert_called_once_with(data['params'], dict(execution_id=None, root_orch_execution_id=mock_uuid4.return_value, orch_execution_id=mock_uuid4.return_value, executor_id=ROOT), vault={}) mock_submit.assert_called_once_with(deploy_orchestration, orchestration=self.o.id, var_context=MockVarContext, hosts={'all': [self.s1.id]}, timeout=None) self.assertEqual(200, resp.status_code) self.assertDictEqual(oe.to_json(add_step_exec=True, split_lines=True), resp.get_json()) with self.subTest("Test background taking longer"): mock_submit.return_value = mock.Mock() mock_submit.return_value.result.side_effect = concurrent.futures.TimeoutError() resp = self.client.post(url_for('api_1_0.launch_orchestration', orchestration_id=str(self.o.id)), json=data, headers=self.auth.header) self.assertEqual(202, resp.status_code) self.assertDictEqual({'execution_id': 'a7083c43-34cc-4b26-91f0-ea0928cf5945'}, resp.get_json()) @patch('dimensigon.web.api_1_0.urls.use_cases.uuid.uuid4') @patch('dimensigon.web.api_1_0.urls.use_cases.Context') @patch('dimensigon.web.api_1_0.urls.use_cases.deploy_orchestration') def test_launch_orchestration_filter_by_name(self, mock_deploy, mock_var_context, mock_uuid4): mock_uuid4.return_value = 'a7083c43-34cc-4b26-91f0-ea0928cf5945' data = {'hosts': self.s1.id, 'orchestration': self.o.name, 'params': {'folder': '/home/{{user}}', 'home': '{{folder}}', 'user': '******'}, 'skip_validation': True, 'background': False} oe = OrchExecution(id=mock_uuid4.return_value, orchestration_id=self.o2.id) db.session.add(oe) db.session.commit() def deploy_orchestration(*args, **kwargs): return mock_uuid4.return_value mock_deploy.side_effect = deploy_orchestration MockVarContext = mock.Mock(name='MockVarContext') mock_var_context.return_value = MockVarContext resp = self.client.post(url_for('api_1_0.launch_orchestration'), json=data, headers=self.auth.header) self.assertEqual(200, resp.status_code) self.assertDictEqual(oe.to_json(add_step_exec=True, split_lines=True), resp.get_json()) mock_var_context.assert_called_once_with(data['params'], dict(execution_id=None, root_orch_execution_id=mock_uuid4.return_value, orch_execution_id=mock_uuid4.return_value, executor_id=ROOT), vault={}) mock_deploy.assert_called_once_with(orchestration=self.o2, var_context=MockVarContext, hosts={'all': [self.s1.id]}, timeout=None) @patch('dimensigon.web.api_1_0.urls.use_cases.uuid.uuid4') @patch('dimensigon.web.api_1_0.urls.use_cases.Context') @patch('dimensigon.web.api_1_0.urls.use_cases.deploy_orchestration') def test_launch_orchestration_filter_by_name_version(self, mock_deploy, mock_var_context, mock_uuid4): mock_uuid4.return_value = 'a7083c43-34cc-4b26-91f0-ea0928cf5945' data = {'hosts': self.s1.id, 'orchestration': self.o.name, 'version': 1, 'params': {'folder': '/home/{{user}}', 'home': '{{folder}}', 'user': '******'}, 'skip_validation': True, 'background': False} oe = OrchExecution(id=mock_uuid4.return_value, orchestration_id=self.o.id) db.session.add(oe) db.session.commit() def deploy_orchestration(*args, **kwargs): return mock_uuid4.return_value mock_deploy.side_effect = deploy_orchestration MockVarContext = mock.Mock(name='MockVarContext') mock_var_context.return_value = MockVarContext resp = self.client.post(url_for('api_1_0.launch_orchestration'), json=data, headers=self.auth.header) self.assertEqual(200, resp.status_code) self.assertDictEqual(oe.to_json(add_step_exec=True, split_lines=True), resp.get_json()) mock_var_context.assert_called_once_with(data['params'], dict(execution_id=None, root_orch_execution_id=mock_uuid4.return_value, orch_execution_id=mock_uuid4.return_value, executor_id=ROOT), vault={}) mock_deploy.assert_called_once_with(orchestration=self.o, var_context=MockVarContext, hosts={'all': [self.s1.id]}, timeout=None) def test_launch_orchestration_filter_by_orchestration_not_found(self): data = {'hosts': self.s1.id, 'orchestration': 'invented', 'params': {'folder': '/home/{{user}}', 'home': '{{folder}}', 'user': '******'}, 'skip_validation': True, 'background': False} resp = self.client.post(url_for('api_1_0.launch_orchestration'), json=data, headers=self.auth.header) self.validate_error_response(resp, errors.EntityNotFound('Orchestration', ['invented'], ('orchestration',))) def test_launch_orchestration_filter_by_version_not_found(self): data = {'hosts': self.s1.id, 'orchestration': self.o.name, 'version': 3, 'params': {'folder': '/home/{{user}}', 'home': '{{folder}}', 'user': '******'}, 'skip_validation': True, 'background': False} resp = self.client.post(url_for('api_1_0.launch_orchestration'), json=data, headers=self.auth.header) self.validate_error_response(resp, errors.EntityNotFound('Orchestration', [self.o.name, 3], ['orchestration', 'version']))
class Test(TestResourceBase): def fill_database(self): self.at = ActionTemplate(name='action_name', version=1, action_type=ActionType.SHELL, code='') self.o = Orchestration(name='name', version=1) self.st1 = self.o.add_step(undo=False, action_template=self.at) self.st2 = self.o.add_step(undo=False, action_template=self.at, children=[self.st1]) self.st3 = self.o.add_step(undo=False, action_template=self.at, children=[self.st2]) db.session.add_all([self.at, self.o]) def tearDown(self) -> None: db.session.remove() db.drop_all() self.app_context.pop() def test_get(self): resp = self.client.get(url_for('api_1_0.steprelationshipchildren', step_id=str(self.st1.id)), headers=self.auth.header) self.assertEqual(200, resp.status_code) self.assertDictEqual(dict(child_step_ids=[]), resp.get_json()) resp = self.client.get(url_for('api_1_0.steprelationshipchildren', step_id=str(self.st2.id)), headers=self.auth.header) self.assertEqual(200, resp.status_code) self.assertDictEqual(dict(child_step_ids=[str(self.st1.id)]), resp.get_json()) def test_patch(self): resp = self.client.patch(url_for('api_1_0.steprelationshipchildren', step_id=str(self.st3.id)), json={'child_step_ids': [str(self.st1.id)]}, headers=self.auth.header) self.assertEqual(200, resp.status_code) self.assertDictEqual(dict(child_step_ids=[str(self.st1.id)]), resp.get_json()) self.assertListEqual([self.st1], self.st3.children) def test_post(self): resp = self.client.post(url_for('api_1_0.steprelationshipchildren', step_id=str(self.st3.id)), json={'child_step_ids': [str(self.st1.id)]}, headers=self.auth.header) self.assertEqual(200, resp.status_code) expected = [str(self.st2.id), str(self.st1.id)] expected.sort() actual = resp.get_json()['child_step_ids'] actual.sort() self.assertListEqual(expected, actual) self.assertIn(self.st1, self.st3.children) self.assertIn(self.st2, self.st3.children) def test_delete(self): resp = self.client.delete(url_for('api_1_0.steprelationshipchildren', step_id=str(self.st3.id)), json={'child_step_ids': [str(self.st2.id)]}, headers=self.auth.header) self.assertEqual(200, resp.status_code) self.assertDictEqual(dict(child_step_ids=[]), resp.get_json()) self.assertEqual(0, len(self.st3.children))
def orchestrations_full(): json_data = request.get_json() json_steps = json_data.pop('steps') generated_version = False if 'version' not in json_data: generated_version = True json_data['version'] = Orchestration.query.filter_by( name=json_data['name']).count() + 1 o = Orchestration(**json_data) db.session.add(o) resp_data = {'id': o.id} if generated_version: resp_data.update(version=o.version) # reorder steps in order of dependency id2step = {s['id']: s for s in json_steps} dag = DAG() for s in json_steps: step_id = s['id'] if s['undo'] and len(s.get('parent_step_ids', [])) == 0: raise errors.UndoStepWithoutParent(step_id) dag.add_node(step_id) for p_s_id in s.get('parent_step_ids', []): dag.add_edge(p_s_id, step_id) if dag.is_cyclic(): raise errors.CycleError new_steps = [] for step_id in dag.ordered_nodes: step = id2step[step_id] new_steps.append(step) # end reorder steps in order of dependency rid2step = OrderedDict() dependencies = {} for json_step in new_steps: rid = json_step.pop('id', None) json_step['rid'] = rid if rid is not None and rid in rid2step.keys(): raise errors.DuplicatedId(rid) if 'action_template_id' in json_step: json_step['action_template'] = ActionTemplate.query.get_or_raise( json_step.pop('action_template_id')) elif 'action_type' in json_step: json_step['action_type'] = ActionType[json_step.pop('action_type')] dependencies[rid] = { 'parent_step_ids': [p_id for p_id in json_step.pop('parent_step_ids', [])] } s = o.add_step(**json_step) db.session.add(s) if rid: rid2step[rid] = s continue # process dependencies for rid, dep in dependencies.items(): step = rid2step[rid] parents = [] for p_s_id in dep['parent_step_ids']: if p_s_id in rid2step: parents.append(rid2step[p_s_id]) o.set_parents(step, parents) db.session.commit() # send new ids in order of appearance at beginning new_id_steps = [] for rid in rid2step.keys(): new_id_steps.append(rid2step[rid].id) resp_data.update({'step_ids': new_id_steps}) return resp_data, 201
def test_to_from_json(self): start = dt.datetime(2019, 4, 1, tzinfo=dt.timezone.utc) end = dt.datetime(2019, 4, 2, tzinfo=dt.timezone.utc) u = User('user', id='cccccccc-1234-5678-1234-56781234ccc1') db.session.add(u) o = Orchestration('run_orch', 1, id='eeeeeeee-1234-5678-1234-56781234eee1') s = o.add_step(undo=False, action_template=ActionTemplate('orchestration', 1, ActionType.ORCHESTRATION, code='')) co = Orchestration('child', 1, id='eeeeeeee-1234-5678-1234-56781234eee2') cs = o.add_step(undo=False, action_template=ActionTemplate('action', 1, ActionType.SHELL, code='')) db.session.add_all([o, s, co, cs]) oe = OrchExecution(id='bbbbbbbb-1234-5678-1234-56781234bbb1', start_time=start, end_time=end, target={'all': [str(self.me.id), str(self.remote.id)], 'backend': self.remote.name}, params={'params': 'content'}, executor=u, orchestration=o ) se = StepExecution(id='aaaaaaaa-1234-5678-1234-56781234aaa1', start_time=start, end_time=end, step=s, orch_execution_id=oe.id, params={'orchestration_id': 'eeeeeeee-1234-5678-1234-56781234eee2'}, rc=None, success=True, server=self.remote) coe = OrchExecution(id='bbbbbbbb-1234-5678-1234-56781234bbb2', start_time=start, end_time=end, target={'all': [str(self.me.id), str(self.remote.id)], 'backend': self.remote.name}, params={'params': 'content'}, executor=u, orchestration=o, parent_step_execution_id=se.id ) cse = StepExecution(id='aaaaaaaa-1234-5678-1234-56781234aaa2', start_time=start, end_time=end, step=s, orch_execution_id=coe.id, params={'param': 'data'}, rc=0, success=True, server=self.remote, ) se.child_orch_execution_id = coe.id db.session.add_all([oe, se, coe, cse]) db.session.commit() self.assertDictEqual(dict(id=str(oe.id), start_time=start.strftime(defaults.DATETIME_FORMAT), end_time=end.strftime(defaults.DATETIME_FORMAT), target={'all': [str(self.me.id), str(self.remote.id)], 'backend': self.remote.name}, params={'params': 'content'}, orchestration_id=str(o.id), success=None, undo_success=None, executor_id='cccccccc-1234-5678-1234-56781234ccc1', message=None), oe.to_json()) self.assertDictEqual(dict(id=str(oe.id), start_time=start.strftime(defaults.DATETIME_FORMAT), end_time=end.strftime(defaults.DATETIME_FORMAT), target={'all': [str(self.me), str(self.remote)], 'backend': self.remote.name}, params={'params': 'content'}, orchestration={'id': 'eeeeeeee-1234-5678-1234-56781234eee1', 'name': 'run_orch', 'version': 1}, success=None, undo_success=None, executor='user', message=None), oe.to_json(human=True)) dumped = oe.to_json(add_step_exec=True) self.assertEqual(1, len(dumped['steps'])) self.assertEqual('aaaaaaaa-1234-5678-1234-56781234aaa1', dumped['steps'][0]['id']) self.assertIn('orch_execution', dumped['steps'][0]) o_e_json = oe.to_json() db.session.commit() del oe # load existent object smashed = OrchExecution.from_json(o_e_json) self.assertEqual('bbbbbbbb-1234-5678-1234-56781234bbb1', smashed.id) self.assertEqual(start, smashed.start_time) self.assertEqual(end, smashed.end_time) self.assertEqual(o.id, smashed.orchestration.id) self.assertDictEqual({'all': [str(self.me.id), str(self.remote.id)], 'backend': self.remote.name}, smashed.target) self.assertDictEqual({'params': 'content'}, smashed.params) self.assertEqual(User.get_by_name('user'), smashed.executor) self.assertEqual(None, smashed.service) self.assertEqual(None, smashed.success) self.assertEqual(None, smashed.undo_success) # load new object and insert into database new_obj = OrchExecution.from_json(dict(id='bbbbbbbb-1234-5678-1234-56781234bbb3', start_time=start.strftime(defaults.DATETIME_FORMAT), end_time=end.strftime(defaults.DATETIME_FORMAT), target={'all': [str(self.me.id), str(self.remote.id)], 'backend': self.remote.name}, params={'params': 'content'}, orchestration_id=str(o.id), service_id=None, success=None, undo_success=None, executor_id='cccccccc-1234-5678-1234-56781234ccc1')) db.session.add(new_obj) db.session.commit()