def test_get_childnodes_order(self): """ The inspector should return the childnodes in the order, as they were nested inside the node definition. """ # Automatic class Root(Node): class A(Node): pass class B(Node): pass self.assertEqual(repr(Inspector(Root()).get_childnodes()), '[<Node Root.A>, <Node Root.B>]') self.assertEqual(repr(Inspector(Env(Root())).get_childnodes()), '[Env(Root.A), Env(Root.B)]') # Manually set creation order class Root(Node): class A(Node): pass class B(Node): pass A._node_creation_counter = 44 B._node_creation_counter = 45 self.assertEqual(repr(Inspector(Root()).get_childnodes()), '[<Node Root.A>, <Node Root.B>]') self.assertEqual(repr(Inspector(Env(Root())).get_childnodes()), '[Env(Root.A), Env(Root.B)]') # Manually revert the creation order class Root(Node): class A(Node): pass class B(Node): pass A._node_creation_counter = 45 B._node_creation_counter = 44 self.assertEqual(repr(Inspector(Root()).get_childnodes()), '[<Node Root.B>, <Node Root.A>]') self.assertEqual(repr(Inspector(Env(Root())).get_childnodes()), '[Env(Root.B), Env(Root.A)]')
def test_auto_mapping_from_node_to_simplenode_array(self): # When no role_mapping is defined between Node and SimpleNode.Array, # we will map *all* roles to 'host' class A(Node): class Hosts: role1 = {LocalHost1, LocalHost2} role2 = {LocalHost3} role3 = {LocalHost4} class B(SimpleNode.Array): pass env = Env(A()) self.assertEqual(len(list(env.B)), 4) # When we have a Hosts class, and no explicit # mapping between A and B, this hosts will be used. class A(Node): class Hosts: role1 = {LocalHost1, LocalHost2} role2 = {LocalHost3, LocalHost4} class B(SimpleNode.Array): class Hosts: host = LocalHost1 env = Env(A()) self.assertEqual(len(list(env.B)), 1) # In case of JustOne, it should work as well. class A(Node): class Hosts: role1 = LocalHost1 class B(SimpleNode.JustOne): pass env = Env(A()) self.assertEqual(len(list(env.B)), 1) # Exception: Invalid initialisation of SimpleNode.JustOne. 2 hosts given to <Node A.B>. class A(Node): class Hosts: role1 = {LocalHost1, LocalHost2} class B(SimpleNode.JustOne): pass env = Env(A()) self.assertRaises(Exception, lambda: env.B)
def test_simple_node_getitem(self): class N(SimpleNode): class Hosts: host = {LocalHost1, LocalHost2} def func(self): return 'result' n = N() self.assertIsInstance(n[0], SimpleNode) self.assertIsInstance(n[1], SimpleNode) self.assertIsInstance(n[LocalHost1], SimpleNode) self.assertIsInstance(n[LocalHost2], SimpleNode) self.assertEqual(n[0]._node_is_isolated, True) self.assertEqual(n[1]._node_is_isolated, True) self.assertEqual(n[LocalHost1]._node_is_isolated, True) self.assertEqual(n[LocalHost2]._node_is_isolated, True) self.assertRaises(KeyError, lambda: n[2]) self.assertRaises(KeyError, lambda: n[LocalHost3]) # Calling the isolated item should not return an array env = Env(N()) self.assertEqual(list(env.func()), ['result', 'result']) self.assertIsInstance(env.func(), ParallelActionResult) self.assertEqual(env[0].func(), 'result') self.assertEqual(env[1].func(), 'result') self.assertRaises(KeyError, lambda: env[2]) self.assertRaises(KeyError, lambda: env[LocalHost3])
def test_dont_isolate_yet(self): once = [0] for_each_host = [0] this = self class A(Node): class Hosts: my_role = {LocalHost1, LocalHost2} @map_roles('my_role') class B(SimpleNode.Array): def for_each_host(self): for_each_host[0] += 1 this.assertEqual(len(self.hosts), 1) @dont_isolate_yet def only_once(self): once[0] += 1 self.for_each_host() this.assertEqual(len(self.hosts), 2) return 'result' env = Env(A()) result = env.B.only_once() self.assertEqual(result, 'result') self.assertEqual(once, [1]) self.assertEqual(for_each_host, [2])
def test_simple_nodes_in_normal_node(self): class N(Node): class Hosts: role1 = {LocalHost1, LocalHost} role2 = LocalHost3 @map_roles('role1') class M(SimpleNode.Array): def func(self): return 'func-m' class X(SimpleNode): def func(self): return 'func-x' def func(self): return 'func-n' # `M` should behave as an array. env = Env(N()) self.assertEqual(env.func(), 'func-n') self.assertEqual(list(env.M.func()), ['func-m', 'func-m']) self.assertIsInstance(env.M.func(), ParallelActionResult) self.assertEqual(env.M[0].func(), 'func-m') self.assertEqual(list(env.M.X.func()), ['func-x', 'func-x']) self.assertIsInstance(env.M.X.func(), ParallelActionResult) self.assertEqual(env.M[0].X.func(), 'func-x') self.assertEqual(env.M.X[0].func(), 'func-x')
def test_additional_roles_in_simple_node(self): # We should be able to pass additional roles to a SimpleNode, but # isolation happens at the 'host' role. this = self counter = [0] class A(Node): class Hosts: role1 = {LocalHost1, LocalHost2, LocalHost3} role2 = {LocalHost2, LocalHost4, LocalHost5} @map_roles('role1', extra='role2') class B(SimpleNode.Array): def action(self): this.assertEqual(len(self.hosts.filter('host')), 1) this.assertEqual(len(self.hosts.filter('extra')), 3) this.assertEqual(set(self.hosts.roles), set(['host', 'extra'])) self.C.action2() counter[0] += 1 class C(SimpleNode): def action2(self): this.assertEqual(len(self.hosts.filter('host')), 1) this.assertEqual(len(self.hosts.filter('extra')), 3) this.assertEqual(set(self.hosts.roles), set(['host', 'extra'])) counter[0] += 1 env = Env(A()) env.B.action() self.assertEqual(counter[0], 6)
def test_env_object(self): class S(Node): class Hosts: role1 = LocalHost role2 = LocalHost2 def my_action(self): return 'result' def return_name_of_self(self): return self.__class__.__name__ def echo_on_all(self): return self.hosts.run('/bin/echo echo', interactive=False) def echo_on_role1(self): return self.hosts.filter('role1').run('/bin/echo echo', interactive=False) def echo_on_role2(self): return self.hosts.filter('role2')[0].run('/bin/echo echo', interactive=False) s = S() env = Env(s) self.assertEqual(env.my_action(), 'result') self.assertEqual(env.return_name_of_self(), 'Env') self.assertEqual(env.echo_on_all(), ['echo\r\n', 'echo\r\n']) self.assertEqual(env.echo_on_role1(), ['echo\r\n']) self.assertEqual(env.echo_on_role2(), 'echo\r\n') # Isinstance hooks self.assertIsInstance(s, S) self.assertIsInstance(env, S)
def test_simple_node(self): class N(SimpleNode): class Hosts: host = {LocalHost1, LocalHost2} def func(self): return 'result' # SimpleNode executes for each host separately. env = Env(N()) self.assertEqual(list(env.func()), ['result', 'result']) # Test return value of SimpleNode result = env.func() self.assertIsInstance(result, ParallelActionResult) # (The key is the Env, containing the isolation) self.assertIsInstance(result.keys()[0], Env) self.assertIsInstance(result.keys()[1], Env) self.assertIn(result.keys()[0]._node.Hosts.host, {LocalHost1, LocalHost2}) self.assertIn(result.keys()[1]._node.Hosts.host, {LocalHost1, LocalHost2}) self.assertEqual(result.values()[0], 'result') self.assertEqual(result.values()[1], 'result')
def setUp(self): class Root(Node): attr = 'value' query = Q.attr + Q.attr def my_action(self): pass self.env = Env(Root()) self.env_insp = Inspector(self.env) self.node_insp = Inspector(Root())
def test_q_navigation(self): this = self class DummyException(Exception): pass class MyNode(Node): class Hosts: host = LocalHost # 1. Normal query from node. attr = 'value' query = Q.attr query2 = Q.attr + Q.attr def my_action(self): return self.query # 2. Exception in query from node. @property def this_raises(self): raise DummyException query3 = Q.this_raises + Q.attr def my_action2(self): # Calling query3 raises a QueryException # The inner exception that one is the DummyException try: return self.query3 except Exception as e: this.assertIsInstance(e, ActionException) this.assertIsInstance(e.inner_exception, QueryException) this.assertIsInstance(e.inner_exception.node, MyNode) this.assertIsInstance(e.inner_exception.query, Query) this.assertIsInstance(e.inner_exception.inner_exception, DummyException) # Raising the exception again at this point, will turn it # into an action exception. raise s = MyNode() env = Env(s) # 1. self.assertEqual(env.my_action(), 'value') self.assertEqual(env.query2, 'valuevalue') # 2. self.assertRaises(ActionException, env.my_action2) try: env.my_action2() except Exception as e: self.assertIsInstance(e, ActionException)
def run_action(self, action_name, *a, **kw): """ Run a deployment command at the current shell state. """ env = Env(self.state._node, self.pty, self.logger_interface, is_sandbox=False) return getattr(env, action_name)(*a, **kw)
def test_nested_action(self): class MyNode(Node): class Node2(Node): class Node3(Node): def my_action(self): return 'result' env = Env(MyNode()) result = env.Node2.Node3.my_action() self.assertEqual(result, 'result')
def _get_env(self): """ Created a sandboxed environment for evaluation of attributes. (attributes shouldn't have side effects on servers, so sandboxing is fine.) Returns an ``Env`` object. """ env = Env(self.node, self.shell.pty, self.shell.logger_interface, is_sandbox=True) return Console(self.shell.pty).select_node_isolation(env)
def test_node_console(self): class Root(Node): def a(self): return 'result' # Test Console instance. p = Pty() env = Env(Root(), pty=p) self.assertIsInstance(env.console, Console) self.assertEqual(env.console.pty, p) self.assertEqual(env.console.is_interactive, p.interactive) #
def test_nested_simple_nodes(self): class N(SimpleNode): class Hosts: host = {LocalHost1, LocalHost2} class M(SimpleNode): def func(self): return 'result' # `M` gets both hosts as well. env = Env(N()) self.assertEqual(list(env.M.func()), ['result', 'result'])
def test_super_call(self): # Calling the superclass class A(Node): def action(self): return 'result' class B(A): def action(self): return 'The result was %s' % A.action(self) env = Env(B()) self.assertEqual(env.action(), 'The result was result')
def test_wrapping_middle_node_in_env(self): class S(Node): class Hosts: role1 = {LocalHost1, LocalHost2} class T(Node): def action(self): return len(self.hosts) s = S() env = Env(s.T) self.assertEqual(env.action(), 2)
def setUp(self): class Root(Node): def a(self): pass @suppress_action_result def b(self): pass self.env = Env(Root()) self.env_insp = Inspector(self.env) self.node_insp = Inspector(Root())
def test_action_with_params(self): class MyNode(Node): class Hosts: host = LocalHost def my_action(self, param1, *a, **kw): return (param1, a, kw) s = MyNode() env = Env(s) result = env.my_action('param1', 1, 2, k=3, v=4) self.assertEqual(result, ('param1', (1, 2), {'k': 3, 'v': 4}))
def test_action_aliases(self): # We can define multiple aliases for an action. class A(Node): @alias('my_alias2') @alias('my_alias') def action(self): return 'result' env = Env(A()) self.assertEqual(env.action(), 'result') self.assertEqual(env.my_alias(), 'result') self.assertEqual(env.my_alias2(), 'result')
def test_node_names(self): class Another(Node): pass class N(Node): class Hosts: role1 = {LocalHost1, LocalHost2} role2 = LocalHost3 class M(Node): class O(Node): pass @map_roles(host='role1') class P(SimpleNode.Array): pass class another_node(Another): pass another_node2 = Another # For the class definitions, the names certainly shoudn't change. self.assertEqual(N.__name__, 'N') self.assertEqual(N.M.__name__, 'M') self.assertEqual(N.M.O.__name__, 'O') self.assertEqual(N.P.__name__, 'P') self.assertEqual(N.another_node.__name__, 'another_node') self.assertEqual(N.another_node2.__name__, 'Another') # For instances (and mappings), they should be named according to the # full path. n = N() self.assertEqual(repr(n), '<Node N>') self.assertEqual(repr(n.M), '<Node N.M>') self.assertEqual(repr(n.M.O), '<Node N.M.O>') self.assertEqual(repr(n.P), '<Node N.P>') self.assertEqual(repr(n.P[0]), '<Node N.P[0]>') self.assertEqual(repr(n.P[1]), '<Node N.P[1]>') self.assertEqual(repr(n.another_node), '<Node N.another_node>') self.assertEqual(repr(n.another_node2), '<Node N.another_node2>') # Test Env.__repr__ env = Env(n) self.assertEqual(repr(env), 'Env(N)') self.assertEqual(repr(env.M.O), 'Env(N.M.O)') self.assertEqual(repr(env.P[0]), 'Env(N.P[0])') self.assertEqual(repr(env.P[1]), 'Env(N.P[1])')
def test_assignments_in_node(self): # It's not allowed to change attributes from a Node Environment. class MyNode(Node): def action(self): self.variable = 'value' # AttributeError wrapped in ActionException env = Env(MyNode()) self.assertRaises(ActionException, env.action) try: env.action() except ActionException as e: self.assertIsInstance(e.inner_exception, AttributeError)
def test_action_alias(self): """ By using the @alias decorator around an action, the action should be available through multiple names. """ class Root(Node): @alias('b.c.d') @alias('b') def a(self): return 'result' env = Env(Root()) self.assertEqual(env.a(), 'result') self.assertEqual(env.b(), 'result') self.assertEqual(getattr(env, 'b.c.d')(), 'result')
def test_property(self): class MyNode(Node): class Hosts: host = LocalHost @property def p(self): return 'value' def my_action(self): return self.p s = MyNode() env = Env(s) self.assertEqual(env.my_action(), 'value')
def test_required_property(self): class A(Node): p = required_property() def action(self): self.p() env = Env(A()) # NotImplementedError wrapped in ActionException self.assertRaises(ActionException, env.action) try: env.action() except ActionException as e: self.assertIsInstance(e.inner_exception, NotImplementedError)
def __call__(self): from deployer.contrib.nodes import connect class Connect(connect.Connect): class Hosts: host = self.node.hosts.get_hosts() env = Env(Connect(), self.shell.pty, self.shell.logger_interface) # Run as any other action. (Nice exception handling, e.g. in case of NoInput on host selection.) try: env.with_host() except ActionException as e: pass except Exception as e: self.shell.logger_interface.log_exception(e)
def test_action_names(self): # Test Action.__repr__ class N(Node): class M(Node): def my_action(self): pass n = N() self.assertEqual(repr(n.M.my_action), '<Action N.M.my_action>') self.assertEqual(repr(N.M.my_action), '<Unbound Action my_action>') self.assertEqual(n.M.my_action.name, 'my_action') # Env.Action.__repr__ env = Env(n) self.assertEqual(repr(env.M.my_action), '<Env.Action N.M.my_action>') self.assertEqual(env.M.my_action.name, 'my_action')
def test_bin_false(self): class S(Node): class Hosts: role1 = LocalHost def return_false(self): return self.hosts.run('/bin/false', interactive=False) s = S() env = Env(s) # This should raise an ExecCommandFailed, wrapped in an ActionException self.assertRaises(ActionException, env.return_false) try: env.return_false() except ActionException as e: self.assertIsInstance(e.inner_exception, ExecCommandFailed)
def test_simplenode_just_one(self): # In contrast with .Array, the .JustOne should # make sure that it doesn't behave as an array, # but instead asserts that it will only get one # host for this role. class A(Node): class Hosts: role1 = {LocalHost1, LocalHost2} role2 = LocalHost3 @map_roles('role2') class B(SimpleNode.JustOne): def action(self): return 'result' env = Env(A()) self.assertEqual(env.B._node_is_isolated, True) self.assertEqual(env.B.action(), 'result')
def __call__(self): # Execute logger_interface = self.shell.logger_interface try: # Create env env = Env(self.node, self.shell.pty, logger_interface, is_sandbox=self.sandbox) # Call action if self.attr_name is not None: result = getattr(env, self.attr_name)(*self.args) suppress_result = Inspector( self.node).suppress_result_for_action(self.attr_name) else: result = env(*self.args) suppress_result = False # When the result is a subnode, start a subshell. def handle_result(result): if isinstance(result, deployer.node.Env): print '' print 'Starting subshell ...' self.shell.state = ShellState( result._node, return_state=self.shell.state) # Otherwise, print result elif result is not None and not suppress_result: print result if isinstance(result, list): for r in result: handle_result(r) else: handle_result(result) except ActionException as e: # Already sent to logger_interface in the Action itself. pass except Exception as e: logger_interface.log_exception(e)