def test_stack_requires_when_locked(self): definition = generate_definition( base_name="vpc", stack_id=1, variables={ "Var1": "${noop fakeStack3::FakeOutput}", "Var2": ("some.template.value:${output fakeStack2::FakeOutput}:" "${output fakeStack::FakeOutput}"), "Var3": "${output fakeStack::FakeOutput}," "${output fakeStack2::FakeOutput}", }, requires=["fakeStack"], ) stack = Stack(definition=definition, context=self.context) stack.locked = True self.assertEqual(len(stack.requires), 0) # TODO(ejholmes): When the stack is in `--force`, it's not really # locked. Maybe it would be better if `stack.locked` were false when # the stack is in `--force`. stack.force = True self.assertEqual(len(stack.requires), 2)
def test_execute_plan_exception(self): vpc = Stack(definition=generate_definition('vpc', 1), context=self.context) bastion = Stack(definition=generate_definition('bastion', 1, requires=[vpc.name]), context=self.context) calls = [] def fn(stack, status=None): calls.append(stack.fqn) if stack.name == vpc_step.name: raise ValueError('Boom') return COMPLETE vpc_step = Step(vpc, fn) bastion_step = Step(bastion, fn) plan = build_plan(description="Test", steps=[vpc_step, bastion_step]) with self.assertRaises(PlanFailed): plan.execute(walk) self.assertEquals(calls, ['namespace-vpc.1']) self.assertEquals(vpc_step.status, FAILED)
def test_execute_plan_filtered(self): vpc = Stack(definition=generate_definition('vpc', 1), context=self.context) db = Stack(definition=generate_definition('db', 1, requires=[vpc.name]), context=self.context) app = Stack(definition=generate_definition('app', 1, requires=[db.name]), context=self.context) calls = [] def fn(stack, status=None): calls.append(stack.fqn) return COMPLETE plan = build_plan(description="Test", steps=[Step(vpc, fn), Step(db, fn), Step(app, fn)], targets=['db.1']) plan.execute(walk) self.assertEquals(calls, ['namespace-vpc.1', 'namespace-db.1'])
def test_execute_plan_failed(self): vpc = Stack(definition=generate_definition('vpc', 1), context=self.context) bastion = Stack(definition=generate_definition('bastion', 1, requires=[vpc.name]), context=self.context) db = Stack(definition=generate_definition('db', 1), context=self.context) calls = [] def fn(stack, status=None): calls.append(stack.fqn) if stack.name == vpc_step.name: return FAILED return COMPLETE vpc_step = Step(vpc, fn) bastion_step = Step(bastion, fn) db_step = Step(db, fn) plan = build_plan(description="Test", steps=[vpc_step, bastion_step, db_step]) with self.assertRaises(PlanFailed): plan.execute(walk) calls.sort() self.assertEquals(calls, ['namespace-db.1', 'namespace-vpc.1'])
def test_stack_requires_when_locked(self): definition = generate_definition( base_name="vpc", stack_id=1, variables={ "Var1": "${noop fakeStack3::FakeOutput}", "Var2": ( "some.template.value:${output fakeStack2::FakeOutput}:" "${output fakeStack::FakeOutput}" ), "Var3": "${output fakeStack::FakeOutput}," "${output fakeStack2::FakeOutput}", }, requires=["fakeStack"], ) stack = Stack(definition=definition, context=self.context) stack.locked = True self.assertEqual(len(stack.requires), 0) # TODO(ejholmes): When the stack is in `--force`, it's not really # locked. Maybe it would be better if `stack.locked` were false when # the stack is in `--force`. stack.force = True self.assertEqual(len(stack.requires), 2)
def test_variable_resolve_nested_lookup(self): stack = Stack(definition=generate_definition("vpc", 1), context=self.context) stack.set_outputs({ "FakeOutput": "resolved", "FakeOutput2": "resolved2", }) def mock_handler(value, context, provider, **kwargs): return "looked up: {}".format(value) register_lookup_handler("lookup", mock_handler) self.context.get_stack.return_value = stack var = Variable( "Param1", "${lookup ${lookup ${output fakeStack::FakeOutput}}}", ) self.assertEqual( len(var.lookups), 1, "should only parse out the first complete lookup first", ) var.resolve(self.context, self.provider) self.assertTrue(var.resolved) self.assertEqual(var.value, "looked up: looked up: resolved")
def test_stack_should_submit(self): for enabled in (True, False): definition = generate_definition( base_name="vpc", stack_id=1, enabled=enabled) stack = Stack(definition=definition, context=self.context) self.assertEqual(stack.should_submit(), enabled)
def test_output_handler(self): stack = Stack(definition=generate_definition("vpc", 1), context=self.context) stack.set_outputs({"SomeOutput": "Test Output"}) self.context.get_stack.return_value = stack value = handler("stack-name::SomeOutput", context=self.context) self.assertEqual(value, "Test Output") self.assertEqual(self.context.get_stack.call_count, 1) args = self.context.get_stack.call_args self.assertEqual(args[0][0], "stack-name")
def test_execute_plan_ensure_parallel_builds(self): # key: stack_name, value: current iteration work_states = {} submitted_state = 0 # It takes 4 iterations for each task to finish finished_state = 3 def _run_func(stack, *args, **kwargs): if stack.name not in work_states: work_states[stack.name] = submitted_state return SUBMITTED if work_states[stack.name] == finished_state: return COMPLETE work_states[stack.name] += 1 return SUBMITTED vpc_stack = Stack(definition=generate_definition("vpc", 1), context=self.context) web_stack = Stack( definition=generate_definition("web", 2, requires=[vpc_stack.fqn]), context=self.context, ) db_stack = Stack( definition=generate_definition("db", 3, requires=[vpc_stack.fqn]), context=self.context, ) plan = Plan(description="Test", sleep_time=0) for stack in [vpc_stack, web_stack, db_stack]: plan.add( stack=stack, run_func=_run_func, requires=stack.requires, ) parallel_success = False while not plan._single_run(): vpc_step = plan[vpc_stack.fqn] web_step = plan[web_stack.fqn] db_step = plan[db_stack.fqn] if not vpc_step.completed: self.assertFalse(web_step.submitted) self.assertFalse(db_step.submitted) else: # If the vpc step is complete, and we see both the web & db # steps submitted during the same run, then parallel running # works if web_step.status == SUBMITTED and \ db_step.status == SUBMITTED: parallel_success = True self.assertTrue(parallel_success)
def test_plan(self): vpc = Stack( definition=generate_definition('vpc', 1), context=self.context) bastion = Stack( definition=generate_definition('bastion', 1, requires=[vpc.fqn]), context=self.context) plan = build_plan(description="Test", steps=[ Step(vpc, fn=None), Step(bastion, fn=None)]) self.assertEqual(plan.graph.to_dict(), { 'namespace-bastion.1': set(['namespace-vpc.1']), 'namespace-vpc.1': set([])})
def test_variable_resolve_simple_lookup(self): stack = Stack( definition=generate_definition("vpc", 1), context=self.context) stack.set_outputs({ "FakeOutput": "resolved", "FakeOutput2": "resolved2", }) self.context.get_stack.return_value = stack var = Variable("Param1", "${output fakeStack::FakeOutput}") var.resolve(self.context, self.provider) self.assertTrue(var.resolved) self.assertEqual(var.value, "resolved")
def test_build_graph_cyclic_dependencies(self): vpc = Stack(definition=generate_definition('vpc', 1), context=self.context) db = Stack(definition=generate_definition('db', 1, requires=['app.1']), context=self.context) app = Stack(definition=generate_definition('app', 1, requires=['db.1']), context=self.context) with self.assertRaises(GraphError) as expected: build_graph([Step(vpc, None), Step(db, None), Step(app, None)]) message = ("Error detected when adding 'db.1' " "as a dependency of 'app.1': graph is " "not acyclic") self.assertEqual(str(expected.exception), message)
def test_plan(self): vpc = Stack( definition=generate_definition('vpc', 1), context=self.context) bastion = Stack( definition=generate_definition('bastion', 1, requires=[vpc.name]), context=self.context) graph = build_graph([ Step(vpc, fn=None), Step(bastion, fn=None)]) plan = build_plan(description="Test", graph=graph) self.assertEqual(plan.graph.to_dict(), { 'bastion.1': set(['vpc.1']), 'vpc.1': set([])})
def test_execute_plan_with_watchers(self, patched_multiprocessing): watch_func = mock.MagicMock() plan = Plan(description="Test", sleep_time=0, watch_func=watch_func) previous_stack = None for i in range(5): overrides = {} if previous_stack: overrides["requires"] = [previous_stack.fqn] stack = Stack( definition=generate_definition("vpc", i, **overrides), context=self.context, ) previous_stack = stack plan.add( stack=stack, run_func=self._run_func, requires=stack.requires, ) plan.execute() self.assertEqual(self.count, 9) self.assertEqual(len(plan.list_skipped()), 1) self.assertEqual(patched_multiprocessing.Process().start.call_count, 5) # verify we terminate the process when the stack is finished and also # redundantly terminate the process after execution self.assertEqual( patched_multiprocessing.Process().terminate.call_count, 10)
def test_dump_no_provider_lookups(self, *args): plan = Plan(description="Test", sleep_time=0) previous_stack = None for i in range(5): overrides = { "variables": { "Var1": "${output fakeStack::FakeOutput}", "Var2": "${xref fakeStack::FakeOutput2}", }, } if previous_stack: overrides["requires"] = [previous_stack.fqn] stack = Stack( definition=generate_definition("vpc", i, **overrides), context=self.context, ) previous_stack = stack plan.add( stack=stack, run_func=self._run_func, requires=stack.requires, ) with self.assertRaises(FailedVariableLookup): plan.dump("test", context=self.context)
def test_stack_cfn_parameters(self): definition = generate_definition( base_name="vpc", stack_id=1, variables={ "Param1": "${output fakeStack::FakeOutput}", }, ) stack = Stack(definition=definition, context=self.context) stack._blueprint = MagicMock() stack._blueprint.get_parameter_values.return_value = { "Param2": "Some Resolved Value", } self.assertEqual(len(stack.parameter_values), 1) param = stack.parameter_values["Param2"] self.assertEqual(param, "Some Resolved Value")
def test_dump(self, *args): requires = None steps = [] for i in range(5): overrides = { "variables": { "PublicSubnets": "1", "SshKeyName": "1", "PrivateSubnets": "1", "Random": "${noop something}", }, "requires": requires, } stack = Stack(definition=generate_definition( 'vpc', i, **overrides), context=self.context) requires = [stack.name] steps += [Step(stack, None)] plan = build_plan(description="Test", steps=steps) tmp_dir = tempfile.mkdtemp() try: plan.dump(tmp_dir, context=self.context) for step in plan.steps: template_path = os.path.join( tmp_dir, stack_template_key_name(step.stack.blueprint)) self.assertTrue(os.path.isfile(template_path)) finally: shutil.rmtree(tmp_dir)
def test_stack_requires(self): definition = generate_definition( base_name="vpc", stack_id=1, variables={ "Var1": "${noop fakeStack3::FakeOutput}", "Var2": ("some.template.value:${output fakeStack2::FakeOutput}:" "${output fakeStack::FakeOutput}"), "Var3": "${output fakeStack::FakeOutput}," "${output fakeStack2::FakeOutput}", }, requires=["fakeStack"], ) stack = Stack(definition=definition, context=self.context) self.assertEqual(len(stack.requires), 2) self.assertIn( "fakeStack", stack.requires, ) self.assertIn( "fakeStack2", stack.requires, )
def setUp(self): self.sd = {"name": "test"} self.context = Context({'namespace': 'namespace'}) self.stack = Stack( definition=generate_definition('vpc', 1), context=self.context, )
def setUp(self): self.sd = {"name": "test"} self.context = Context({"namespace": "namespace"}) self.stack = Stack( definition=generate_definition("vpc", 1), context=self.context, )
def test_stack_cfn_parameters(self): definition = generate_definition( base_name="vpc", stack_id=1, variables={ "Param1": "${output fakeStack::FakeOutput}", }, ) stack = Stack(definition=definition, context=self.context) stack._blueprint = MagicMock() stack._blueprint.get_parameter_values.return_value = { "Param2": "Some Resolved Value", } self.assertEqual(len(stack.parameter_values), 1) param = stack.parameter_values["Param2"] self.assertEqual(param, "Some Resolved Value")
def test_stack_should_update(self): test_scenarios = [ dict(locked=False, force=False, result=True), dict(locked=False, force=True, result=True), dict(locked=True, force=False, result=False), dict(locked=True, force=True, result=True) ] for t in test_scenarios: definition = generate_definition( base_name="vpc", stack_id=1, locked=t['locked']) stack = Stack(definition=definition, context=self.context, force=t['force']) self.assertEqual(stack.should_update(), t['result'])
def test_stack_tags_extra(self): self.config.tags = {"environment": "prod"} definition = generate_definition(base_name="vpc", stack_id=1, tags={"app": "graph"}) stack = Stack(definition=definition, context=self.context) self.assertEquals(stack.tags, {"environment": "prod", "app": "graph"})
def test_reset_after_dump(self, *args): plan = Plan(description="Test", sleep_time=0) previous_stack = None for i in range(5): overrides = { "variables": { "PublicSubnets": "1", "SshKeyName": "1", "PrivateSubnets": "1", "Random": "${noop something}", }, } if previous_stack: overrides["requires"] = [previous_stack.fqn] stack = Stack( definition=generate_definition("vpc", i, **overrides), context=self.context, ) previous_stack = stack plan.add( stack=stack, run_func=self._run_func, requires=stack.requires, ) plan.dump("test", context=self.context) self.assertEqual(len(plan.list_pending()), len(plan))
def test_plan_steps_listed_with_fqn(self): plan = Plan(description="Test", sleep_time=0) stack = Stack(definition=generate_definition("vpc", 1), context=self.context) plan.add(stack=stack, run_func=lambda x, y: (x, y)) steps = plan.list_pending() self.assertEqual(steps[0][0], stack.fqn)
def setUp(self): self.sd = {"name": "test"} self.config = Config({"namespace": "namespace"}) self.context = Context(config=self.config) self.stack = Stack( definition=generate_definition("vpc", 1), context=self.context, ) register_lookup_handler("noop", lambda **kwargs: "test")
def test_execute_plan(self): vpc = Stack( definition=generate_definition('vpc', 1), context=self.context) bastion = Stack( definition=generate_definition('bastion', 1, requires=[vpc.fqn]), context=self.context) calls = [] def fn(stack, status=None): calls.append(stack.fqn) return COMPLETE plan = build_plan( description="Test", steps=[Step(vpc, fn), Step(bastion, fn)]) plan.execute() self.assertEquals(calls, ['namespace-vpc.1', 'namespace-bastion.1'])
def test_step_must_return_status(self): plan = Plan(description="Test", sleep_time=0) stack = Stack(definition=generate_definition("vpc", 1), context=mock.MagicMock()) plan.add( stack=stack, run_func=lambda x, **kwargs: (x), ) with self.assertRaises(ValueError): plan.execute()
def test_variable_resolve_multiple_lookups_string(self): var = Variable( "Param1", "url://${output fakeStack::FakeOutput}@" "${output fakeStack::FakeOutput2}", ) stack = Stack( definition=generate_definition("vpc", 1), context=self.context) stack.set_outputs({ "FakeOutput": "resolved", "FakeOutput2": "resolved2", }) self.context.get_stack.return_value = stack var.resolve(self.context, self.provider) self.assertTrue(var.resolved) self.assertEqual(var.value, "url://resolved@resolved2")
def setUp(self): self.context = Context({"namespace": "namespace"}) stack = Stack( definition=generate_definition("vpc", 1), context=self.context, ) self.step = Step( stack=stack, run_func=lambda x, y: (x, y), )
def test_build_plan_missing_dependency(self): bastion = Stack(definition=generate_definition( 'bastion', 1, requires=['namespace-vpc.1']), context=self.context) with self.assertRaises(GraphError) as expected: build_plan(description="Test", steps=[Step(bastion, None)]) message = ("Error detected when adding 'namespace-vpc.1' " "as a dependency of 'namespace-bastion.1': dependent node " "namespace-vpc.1 does not exist") self.assertEqual(expected.exception.message, message)
def test_stack_requires_circular_ref(self): definition = generate_definition( base_name="vpc", stack_id=1, variables={ "Var1": "${output vpc.1::FakeOutput}", }, ) stack = Stack(definition=definition, context=self.context) with self.assertRaises(ValueError): stack.requires
def test_variable_resolve_nested_lookup(self): stack = Stack( definition=generate_definition("vpc", 1), context=self.context) stack.set_outputs({ "FakeOutput": "resolved", "FakeOutput2": "resolved2", }) def mock_handler(value, context, provider, **kwargs): return "looked up: {}".format(value) register_lookup_handler("lookup", mock_handler) self.context.get_stack.return_value = stack var = Variable( "Param1", "${lookup ${lookup ${output fakeStack::FakeOutput}}}", ) var.resolve(self.context, self.provider) self.assertTrue(var.resolved) self.assertEqual(var.value, "looked up: looked up: resolved")