Exemple #1
0
class DeploymentTests(unittest.TestCase):
    def setUp(self):
        Rackspace.connectionCls.conn_class = RackspaceMockHttp
        RackspaceMockHttp.type = None
        self.driver = Rackspace(*RACKSPACE_PARAMS)
        # normally authentication happens lazily, but we force it here
        self.driver.connection._populate_hosts_and_request_paths()
        self.driver.features = {'create_node': ['generates_password']}
        self.node = Node(id=12345,
                         name='test',
                         state=NodeState.RUNNING,
                         public_ips=['1.2.3.4'],
                         private_ips=['1.2.3.5'],
                         driver=Rackspace)
        self.node2 = Node(id=123456,
                          name='test',
                          state=NodeState.RUNNING,
                          public_ips=['1.2.3.4'],
                          private_ips=['1.2.3.5'],
                          driver=Rackspace)

    def test_multi_step_deployment(self):
        msd = MultiStepDeployment()
        self.assertEqual(len(msd.steps), 0)

        msd.add(MockDeployment())
        self.assertEqual(len(msd.steps), 1)

        self.assertEqual(self.node, msd.run(node=self.node, client=None))

    def test_ssh_key_deployment(self):
        sshd = SSHKeyDeployment(key='1234')

        self.assertEqual(
            self.node,
            sshd.run(node=self.node, client=MockClient(hostname='localhost')))

    def test_file_deployment(self):
        # use this file (__file__) for obtaining permissions
        target = os.path.join('/tmp', os.path.basename(__file__))
        fd = FileDeployment(__file__, target)
        self.assertEqual(target, fd.target)
        self.assertEqual(__file__, fd.source)
        self.assertEqual(
            self.node,
            fd.run(node=self.node, client=MockClient(hostname='localhost')))

    def test_script_deployment(self):
        sd1 = ScriptDeployment(script='foobar', delete=True)
        sd2 = ScriptDeployment(script='foobar', delete=False)
        sd3 = ScriptDeployment(script='foobar',
                               delete=False,
                               name='foobarname')

        self.assertTrue(sd1.name.find('deployment') != '1')
        self.assertEqual(sd3.name, 'foobarname')

        self.assertEqual(
            self.node,
            sd1.run(node=self.node, client=MockClient(hostname='localhost')))
        self.assertEqual(
            self.node,
            sd2.run(node=self.node, client=MockClient(hostname='localhost')))

    def test_script_file_deployment(self):
        file_path = os.path.abspath(__file__)
        with open(file_path, 'rb') as fp:
            content = fp.read()

        if PY3:
            content = content.decode('utf-8')

        sfd1 = ScriptFileDeployment(script_file=file_path)
        self.assertEqual(sfd1.script, content)

    def test_script_deployment_relative_path(self):
        client = Mock()
        client.put.return_value = '/home/ubuntu/relative.sh'
        client.run.return_value = ('', '', 0)

        sd = ScriptDeployment(script='echo "foo"', name='relative.sh')
        sd.run(self.node, client)

        client.run.assert_called_once_with('/home/ubuntu/relative.sh')

    def test_script_deployment_absolute_path(self):
        client = Mock()
        client.put.return_value = '/home/ubuntu/relative.sh'
        client.run.return_value = ('', '', 0)

        sd = ScriptDeployment(script='echo "foo"', name='/root/relative.sh')
        sd.run(self.node, client)

        client.run.assert_called_once_with('/root/relative.sh')

    def test_script_deployment_with_arguments(self):
        client = Mock()
        client.put.return_value = '/home/ubuntu/relative.sh'
        client.run.return_value = ('', '', 0)

        args = ['arg1', 'arg2', '--option1=test']
        sd = ScriptDeployment(script='echo "foo"',
                              args=args,
                              name='/root/relative.sh')
        sd.run(self.node, client)

        expected = '/root/relative.sh arg1 arg2 --option1=test'
        client.run.assert_called_once_with(expected)

        client.reset_mock()

        args = []
        sd = ScriptDeployment(script='echo "foo"',
                              args=args,
                              name='/root/relative.sh')
        sd.run(self.node, client)

        expected = '/root/relative.sh'
        client.run.assert_called_once_with(expected)

    def test_script_file_deployment_with_arguments(self):
        file_path = os.path.abspath(__file__)
        client = Mock()
        client.put.return_value = '/home/ubuntu/relative.sh'
        client.run.return_value = ('', '', 0)

        args = ['arg1', 'arg2', '--option1=test', 'option2']
        sfd = ScriptFileDeployment(script_file=file_path,
                                   args=args,
                                   name='/root/relative.sh')

        sfd.run(self.node, client)

        expected = '/root/relative.sh arg1 arg2 --option1=test option2'
        client.run.assert_called_once_with(expected)

    def test_script_deployment_and_sshkey_deployment_argument_types(self):
        class FileObject(object):
            def __init__(self, name):
                self.name = name

            def read(self):
                return 'bar'

        ScriptDeployment(script='foobar')
        ScriptDeployment(script=u('foobar'))
        ScriptDeployment(script=FileObject('test'))

        SSHKeyDeployment(key='foobar')
        SSHKeyDeployment(key=u('foobar'))
        SSHKeyDeployment(key=FileObject('test'))

        try:
            ScriptDeployment(script=[])
        except TypeError:
            pass
        else:
            self.fail('TypeError was not thrown')

        try:
            SSHKeyDeployment(key={})
        except TypeError:
            pass
        else:
            self.fail('TypeError was not thrown')

    def test_wait_until_running_running_instantly(self):
        node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                    wait_period=0.1,
                                                    timeout=0.5)[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(['67.23.21.33'], ips)

    def test_wait_until_running_without_ip(self):
        RackspaceMockHttp.type = 'NO_IP'

        try:
            node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                        wait_period=0.1,
                                                        timeout=0.2)[0]
        except LibcloudError as e:
            self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_with_only_ipv6(self):
        RackspaceMockHttp.type = 'IPV6'

        try:
            node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                        wait_period=0.1,
                                                        timeout=0.2)[0]
        except LibcloudError as e:
            self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_with_ipv6_ok(self):
        RackspaceMockHttp.type = 'IPV6'
        node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                    wait_period=1,
                                                    force_ipv4=False,
                                                    timeout=0.5)[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(['2001:DB8::1'], ips)

    def test_wait_until_running_running_after_0_2_second(self):
        RackspaceMockHttp.type = '05_SECOND_DELAY'
        node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                    wait_period=0.2,
                                                    timeout=0.1)[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(['67.23.21.33'], ips)

    def test_wait_until_running_running_after_0_2_second_private_ips(self):
        RackspaceMockHttp.type = '05_SECOND_DELAY'
        node2, ips = self.driver.wait_until_running(
            nodes=[self.node],
            wait_period=0.2,
            timeout=0.1,
            ssh_interface='private_ips')[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(['10.176.168.218'], ips)

    def test_wait_until_running_invalid_ssh_interface_argument(self):
        try:
            self.driver.wait_until_running(nodes=[self.node],
                                           wait_period=1,
                                           ssh_interface='invalid')
        except ValueError:
            pass
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_timeout(self):
        RackspaceMockHttp.type = 'TIMEOUT'

        try:
            self.driver.wait_until_running(nodes=[self.node],
                                           wait_period=0.1,
                                           timeout=0.2)
        except LibcloudError as e:
            self.assertTrue(e.value.find('Timed out') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_running_node_missing_from_list_nodes(self):
        RackspaceMockHttp.type = 'MISSING'

        try:
            self.driver.wait_until_running(nodes=[self.node],
                                           wait_period=0.1,
                                           timeout=0.2)
        except LibcloudError as e:
            self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_running_multiple_nodes_have_same_uuid(self):
        RackspaceMockHttp.type = 'SAME_UUID'

        try:
            self.driver.wait_until_running(nodes=[self.node],
                                           wait_period=0.1,
                                           timeout=0.2)
        except LibcloudError as e:
            self.assertTrue(
                e.value.find('Unable to match specified uuids') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_running_wait_for_multiple_nodes(self):
        RackspaceMockHttp.type = 'MULTIPLE_NODES'

        nodes = self.driver.wait_until_running(nodes=[self.node, self.node2],
                                               wait_period=0.1,
                                               timeout=0.5)
        self.assertEqual(self.node.uuid, nodes[0][0].uuid)
        self.assertEqual(self.node2.uuid, nodes[1][0].uuid)
        self.assertEqual(['67.23.21.33'], nodes[0][1])
        self.assertEqual(['67.23.21.34'], nodes[1][1])

    def test_ssh_client_connect_success(self):
        mock_ssh_client = Mock()
        mock_ssh_client.return_value = None

        ssh_client = self.driver._ssh_client_connect(
            ssh_client=mock_ssh_client, timeout=0.5)
        self.assertEqual(mock_ssh_client, ssh_client)

    def test_ssh_client_connect_timeout(self):
        mock_ssh_client = Mock()
        mock_ssh_client.connect = Mock()
        mock_ssh_client.connect.side_effect = IOError('bam')

        try:
            self.driver._ssh_client_connect(ssh_client=mock_ssh_client,
                                            wait_period=0.1,
                                            timeout=0.2)
        except LibcloudError as e:
            self.assertTrue(e.value.find('Giving up') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_run_deployment_script_success(self):
        task = Mock()
        ssh_client = Mock()

        ssh_client2 = self.driver._run_deployment_script(task=task,
                                                         node=self.node,
                                                         ssh_client=ssh_client,
                                                         max_tries=2)
        self.assertTrue(isinstance(ssh_client2, Mock))

    def test_run_deployment_script_exception(self):
        task = Mock()
        task.run = Mock()
        task.run.side_effect = Exception('bar')
        ssh_client = Mock()

        try:
            self.driver._run_deployment_script(task=task,
                                               node=self.node,
                                               ssh_client=ssh_client,
                                               max_tries=2)
        except LibcloudError as e:
            self.assertTrue(e.value.find('Failed after 2 tries') != -1)
        else:
            self.fail('Exception was not thrown')

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_success(self, mock_ssh_module, _):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        mock_ssh_module.have_paramiko = True

        deploy = Mock()

        node = self.driver.deploy_node(deploy=deploy)
        self.assertEqual(self.node.id, node.id)

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_deploy_node_kwargs_except_auth_are_not_propagated_on(
            self, mock_ssh_module, _):
        # Verify that keyword arguments which are specific to deploy_node()
        # are not propagated to create_node()
        mock_ssh_module.have_paramiko = True
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        self.driver._connect_and_run_deployment_script = Mock()
        self.driver._wait_until_running = Mock()

        kwargs = {}
        for key in DEPLOY_NODE_KWARGS:
            kwargs[key] = key

        kwargs['ssh_interface'] = 'public_ips'
        kwargs['ssh_alternate_usernames'] = ['foo', 'bar']
        kwargs['timeout'] = 10

        auth = NodeAuthPassword('P@$$w0rd')

        node = self.driver.deploy_node(name='name',
                                       image='image',
                                       size='size',
                                       auth=auth,
                                       ex_foo='ex_foo',
                                       **kwargs)
        self.assertEqual(self.node.id, node.id)
        self.assertEqual(self.driver.create_node.call_count, 1)

        call_kwargs = self.driver.create_node.call_args_list[0][1]
        expected_call_kwargs = {
            'name': 'name',
            'image': 'image',
            'size': 'size',
            'auth': auth,
            'ex_foo': 'ex_foo'
        }
        self.assertEqual(expected_call_kwargs, call_kwargs)

        # If driver throws an exception it should fall back to passing in all
        # the arguments
        global call_count
        call_count = 0

        def create_node(name,
                        image,
                        size,
                        ex_custom_arg_1,
                        ex_custom_arg_2,
                        ex_foo=None,
                        auth=None,
                        **kwargs):
            global call_count

            call_count += 1
            if call_count == 1:
                msg = 'create_node() takes at least 5 arguments (7 given)'
                raise TypeError(msg)
            return self.node

        self.driver.create_node = create_node

        node = self.driver.deploy_node(name='name',
                                       image='image',
                                       size='size',
                                       auth=auth,
                                       ex_foo='ex_foo',
                                       ex_custom_arg_1='a',
                                       ex_custom_arg_2='b',
                                       **kwargs)
        self.assertEqual(self.node.id, node.id)
        self.assertEqual(call_count, 2)

        call_count = 0

        def create_node(name,
                        image,
                        size,
                        ex_custom_arg_1,
                        ex_custom_arg_2,
                        ex_foo=None,
                        auth=None,
                        **kwargs):
            global call_count

            call_count += 1
            if call_count == 1:
                msg = 'create_node() missing 3 required positional arguments'
                raise TypeError(msg)
            return self.node

        self.driver.create_node = create_node

        node = self.driver.deploy_node(name='name',
                                       image='image',
                                       size='size',
                                       auth=auth,
                                       ex_foo='ex_foo',
                                       ex_custom_arg_1='a',
                                       ex_custom_arg_2='b',
                                       **kwargs)
        self.assertEqual(self.node.id, node.id)
        self.assertEqual(call_count, 2)

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_exception_run_deployment_script(
            self, mock_ssh_module, _):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        mock_ssh_module.have_paramiko = True

        deploy = Mock()
        deploy.run = Mock()
        deploy.run.side_effect = Exception('foo')

        try:
            self.driver.deploy_node(deploy=deploy)
        except DeploymentError as e:
            self.assertTrue(e.node.id, self.node.id)
        else:
            self.fail('Exception was not thrown')

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_exception_ssh_client_connect(self, mock_ssh_module,
                                                      ssh_client):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        mock_ssh_module.have_paramiko = True

        deploy = Mock()
        ssh_client.side_effect = IOError('bar')

        try:
            self.driver.deploy_node(deploy=deploy)
        except DeploymentError as e:
            self.assertTrue(e.node.id, self.node.id)
        else:
            self.fail('Exception was not thrown')

    @patch('libcloud.compute.ssh')
    def test_deploy_node_depoy_node_not_implemented(self, mock_ssh_module):
        self.driver.features = {'create_node': []}
        mock_ssh_module.have_paramiko = True

        try:
            self.driver.deploy_node(deploy=Mock())
        except NotImplementedError:
            pass
        else:
            self.fail('Exception was not thrown')

        self.driver.features = {}

        try:
            self.driver.deploy_node(deploy=Mock())
        except NotImplementedError:
            pass
        else:
            self.fail('Exception was not thrown')

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_password_auth(self, mock_ssh_module, _):
        self.driver.features = {'create_node': ['password']}
        mock_ssh_module.have_paramiko = True

        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        node = self.driver.deploy_node(deploy=Mock())
        self.assertEqual(self.node.id, node.id)

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_exception_is_thrown_is_paramiko_is_not_available(
            self, mock_ssh_module, _):
        self.driver.features = {'create_node': ['password']}
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        mock_ssh_module.have_paramiko = False

        try:
            self.driver.deploy_node(deploy=Mock())
        except RuntimeError as e:
            self.assertTrue(str(e).find('paramiko is not installed') != -1)
        else:
            self.fail('Exception was not thrown')

        mock_ssh_module.have_paramiko = True
        node = self.driver.deploy_node(deploy=Mock())
        self.assertEqual(self.node.id, node.id)
Exemple #2
0
class DeploymentTests(unittest.TestCase):
    def setUp(self):
        Rackspace.connectionCls.conn_class = RackspaceMockHttp
        RackspaceMockHttp.type = None
        self.driver = Rackspace(*RACKSPACE_PARAMS)
        # normally authentication happens lazily, but we force it here
        self.driver.connection._populate_hosts_and_request_paths()
        self.driver.features = {'create_node': ['generates_password']}
        self.node = Node(id=12345,
                         name='test',
                         state=NodeState.RUNNING,
                         public_ips=['1.2.3.4'],
                         private_ips=['1.2.3.5'],
                         driver=Rackspace)
        self.node2 = Node(id=123456,
                          name='test',
                          state=NodeState.RUNNING,
                          public_ips=['1.2.3.4'],
                          private_ips=['1.2.3.5'],
                          driver=Rackspace)

    def test_multi_step_deployment(self):
        msd = MultiStepDeployment()
        self.assertEqual(len(msd.steps), 0)

        msd.add(MockDeployment())
        self.assertEqual(len(msd.steps), 1)

        self.assertEqual(self.node, msd.run(node=self.node, client=None))

    def test_ssh_key_deployment(self):
        sshd = SSHKeyDeployment(key='1234')

        self.assertEqual(
            self.node,
            sshd.run(node=self.node, client=MockClient(hostname='localhost')))

    def test_file_deployment(self):
        # use this file (__file__) for obtaining permissions
        target = os.path.join('/tmp', os.path.basename(__file__))
        fd = FileDeployment(__file__, target)
        self.assertEqual(target, fd.target)
        self.assertEqual(__file__, fd.source)
        self.assertEqual(
            self.node,
            fd.run(node=self.node, client=MockClient(hostname='localhost')))

    def test_script_deployment(self):
        sd1 = ScriptDeployment(script='foobar', delete=True)
        sd2 = ScriptDeployment(script='foobar', delete=False)
        sd3 = ScriptDeployment(script='foobar',
                               delete=False,
                               name='foobarname')
        sd4 = ScriptDeployment(script='foobar',
                               delete=False,
                               name='foobarname',
                               timeout=10)

        self.assertTrue(sd1.name.find('deployment') != '1')
        self.assertEqual(sd3.name, 'foobarname')
        self.assertEqual(sd3.timeout, None)
        self.assertEqual(sd4.timeout, 10)

        self.assertEqual(
            self.node,
            sd1.run(node=self.node, client=MockClient(hostname='localhost')))
        self.assertEqual(
            self.node,
            sd2.run(node=self.node, client=MockClient(hostname='localhost')))
        self.assertEqual(
            self.node,
            sd3.run(node=self.node, client=MockClient(hostname='localhost')))

        assertRaisesRegex(self,
                          ValueError,
                          'timeout',
                          sd4.run,
                          node=self.node,
                          client=MockClient(hostname='localhost',
                                            throw_on_timeout=True))

    def test_script_file_deployment(self):
        file_path = os.path.abspath(__file__)
        with open(file_path, 'rb') as fp:
            content = fp.read()

        if PY3:
            content = content.decode('utf-8')

        sfd1 = ScriptFileDeployment(script_file=file_path)
        self.assertEqual(sfd1.script, content)
        self.assertEqual(sfd1.timeout, None)

        sfd2 = ScriptFileDeployment(script_file=file_path, timeout=20)
        self.assertEqual(sfd2.timeout, 20)

    def test_script_deployment_relative_path(self):
        client = Mock()
        client.put.return_value = FILE_PATH
        client.run.return_value = ('', '', 0)

        sd = ScriptDeployment(script='echo "foo"', name='relative.sh')
        sd.run(self.node, client)

        client.run.assert_called_once_with(FILE_PATH, timeout=None)

    def test_script_deployment_absolute_path(self):
        file_path = '{0}root{0}relative.sh'.format(os.path.sep)

        client = Mock()
        client.put.return_value = file_path
        client.run.return_value = ('', '', 0)

        sd = ScriptDeployment(script='echo "foo"', name=file_path)
        sd.run(self.node, client)

        client.run.assert_called_once_with(file_path, timeout=None)

    def test_script_deployment_with_arguments(self):
        file_path = '{0}root{0}relative.sh'.format(os.path.sep)

        client = Mock()
        client.put.return_value = file_path
        client.run.return_value = ('', '', 0)

        args = ['arg1', 'arg2', '--option1=test']
        sd = ScriptDeployment(script='echo "foo"', args=args, name=file_path)
        sd.run(self.node, client)

        expected = '%s arg1 arg2 --option1=test' % (file_path)
        client.run.assert_called_once_with(expected, timeout=None)

        client.reset_mock()

        args = []
        sd = ScriptDeployment(script='echo "foo"', args=args, name=file_path)
        sd.run(self.node, client)

        expected = file_path
        client.run.assert_called_once_with(expected, timeout=None)

    def test_script_file_deployment_with_arguments(self):
        file_path = os.path.abspath(__file__)
        client = Mock()
        client.put.return_value = FILE_PATH
        client.run.return_value = ('', '', 0)

        args = ['arg1', 'arg2', '--option1=test', 'option2']
        sfd = ScriptFileDeployment(script_file=file_path,
                                   args=args,
                                   name=file_path)

        sfd.run(self.node, client)

        expected = '%s arg1 arg2 --option1=test option2' % (file_path)
        client.run.assert_called_once_with(expected, timeout=None)

    def test_script_deployment_and_sshkey_deployment_argument_types(self):
        class FileObject(object):
            def __init__(self, name):
                self.name = name

            def read(self):
                return 'bar'

        ScriptDeployment(script='foobar')
        ScriptDeployment(script=u('foobar'))
        ScriptDeployment(script=FileObject('test'))

        SSHKeyDeployment(key='foobar')
        SSHKeyDeployment(key=u('foobar'))
        SSHKeyDeployment(key=FileObject('test'))

        try:
            ScriptDeployment(script=[])
        except TypeError:
            pass
        else:
            self.fail('TypeError was not thrown')

        try:
            SSHKeyDeployment(key={})
        except TypeError:
            pass
        else:
            self.fail('TypeError was not thrown')

    def test_wait_until_running_running_instantly(self):
        node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                    wait_period=0.1,
                                                    timeout=0.5)[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(['67.23.21.33'], ips)

    def test_wait_until_running_without_ip(self):
        RackspaceMockHttp.type = 'NO_IP'

        try:
            node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                        wait_period=0.1,
                                                        timeout=0.2)[0]
        except LibcloudError as e:
            self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_with_only_ipv6(self):
        RackspaceMockHttp.type = 'IPV6'

        try:
            node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                        wait_period=0.1,
                                                        timeout=0.2)[0]
        except LibcloudError as e:
            self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_with_ipv6_ok(self):
        RackspaceMockHttp.type = 'IPV6'
        node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                    wait_period=1,
                                                    force_ipv4=False,
                                                    timeout=0.5)[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(['2001:DB8::1'], ips)

    def test_wait_until_running_running_after_0_2_second(self):
        RackspaceMockHttp.type = '05_SECOND_DELAY'
        node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                    wait_period=0.2,
                                                    timeout=0.1)[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(['67.23.21.33'], ips)

    def test_wait_until_running_running_after_0_2_second_private_ips(self):
        RackspaceMockHttp.type = '05_SECOND_DELAY'
        node2, ips = self.driver.wait_until_running(
            nodes=[self.node],
            wait_period=0.2,
            timeout=0.1,
            ssh_interface='private_ips')[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(['10.176.168.218'], ips)

    def test_wait_until_running_invalid_ssh_interface_argument(self):
        try:
            self.driver.wait_until_running(nodes=[self.node],
                                           wait_period=1,
                                           ssh_interface='invalid')
        except ValueError:
            pass
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_timeout(self):
        RackspaceMockHttp.type = 'TIMEOUT'

        try:
            self.driver.wait_until_running(nodes=[self.node],
                                           wait_period=0.1,
                                           timeout=0.2)
        except LibcloudError as e:
            self.assertTrue(e.value.find('Timed out') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_running_node_missing_from_list_nodes(self):
        RackspaceMockHttp.type = 'MISSING'

        try:
            self.driver.wait_until_running(nodes=[self.node],
                                           wait_period=0.1,
                                           timeout=0.2)
        except LibcloudError as e:
            self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_running_multiple_nodes_have_same_uuid(self):
        RackspaceMockHttp.type = 'SAME_UUID'

        try:
            self.driver.wait_until_running(nodes=[self.node],
                                           wait_period=0.1,
                                           timeout=0.2)
        except LibcloudError as e:
            self.assertTrue(
                e.value.find('Unable to match specified uuids') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_running_wait_for_multiple_nodes(self):
        RackspaceMockHttp.type = 'MULTIPLE_NODES'

        nodes = self.driver.wait_until_running(nodes=[self.node, self.node2],
                                               wait_period=0.1,
                                               timeout=0.5)
        self.assertEqual(self.node.uuid, nodes[0][0].uuid)
        self.assertEqual(self.node2.uuid, nodes[1][0].uuid)
        self.assertEqual(['67.23.21.33'], nodes[0][1])
        self.assertEqual(['67.23.21.34'], nodes[1][1])

    def test_ssh_client_connect_success(self):
        mock_ssh_client = Mock()
        mock_ssh_client.return_value = None

        ssh_client = self.driver._ssh_client_connect(
            ssh_client=mock_ssh_client, timeout=0.5)
        self.assertEqual(mock_ssh_client, ssh_client)

    def test_ssh_client_connect_timeout(self):
        mock_ssh_client = Mock()
        mock_ssh_client.connect = Mock()
        mock_ssh_client.connect.side_effect = IOError('bam')

        try:
            self.driver._ssh_client_connect(ssh_client=mock_ssh_client,
                                            wait_period=0.1,
                                            timeout=0.2)
        except LibcloudError as e:
            self.assertTrue(e.value.find('Giving up') != -1)
        else:
            self.fail('Exception was not thrown')

    @unittest.skipIf(not have_paramiko,
                     'Skipping because paramiko is not available')
    def test_ssh_client_connect_immediately_throws_on_fatal_execption(self):
        # Verify that fatal exceptions are immediately propagated and ensure
        # we don't try to retry on them
        from paramiko.ssh_exception import SSHException
        from paramiko.ssh_exception import PasswordRequiredException

        mock_ssh_client = Mock()
        mock_ssh_client.connect = Mock()
        mock_ssh_client.connect.side_effect = IOError('bam')

        mock_exceptions = [
            SSHException('Invalid or unsupported key type'),
            PasswordRequiredException('private key file is encrypted'),
            SSHException('OpenSSH private key file checkints do not match')
        ]

        for mock_exception in mock_exceptions:
            mock_ssh_client.connect = Mock(side_effect=mock_exception)
            assertRaisesRegex(self,
                              mock_exception.__class__,
                              str(mock_exception),
                              self.driver._ssh_client_connect,
                              ssh_client=mock_ssh_client,
                              wait_period=0.1,
                              timeout=0.2)

    def test_run_deployment_script_success(self):
        task = Mock()
        ssh_client = Mock()

        ssh_client2 = self.driver._run_deployment_script(task=task,
                                                         node=self.node,
                                                         ssh_client=ssh_client,
                                                         max_tries=2)
        self.assertTrue(isinstance(ssh_client2, Mock))

    def test_run_deployment_script_exception(self):
        task = Mock()
        task.run = Mock()
        task.run.side_effect = Exception('bar')
        ssh_client = Mock()

        try:
            self.driver._run_deployment_script(task=task,
                                               node=self.node,
                                               ssh_client=ssh_client,
                                               max_tries=2)
        except LibcloudError as e:
            self.assertTrue(e.value.find('Failed after 2 tries') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_run_deployment_script_ssh_command_timeout_fatal_exception(self):
        # We shouldn't retry on SSHCommandTimeoutError error since it's fatal
        task = Mock()
        task.run = Mock()
        task.run.side_effect = SSHCommandTimeoutError('ls -la', 10)
        ssh_client = Mock()

        try:
            self.driver._run_deployment_script(task=task,
                                               node=self.node,
                                               ssh_client=ssh_client,
                                               max_tries=5)
        except SSHCommandTimeoutError as e:
            self.assertTrue(e.message.find('Command didn\'t finish') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_run_deployment_script_reconnect_on_ssh_session_not_active(self):
        # Verify that we try to reconnect if task.run() throws exception with
        # "SSH client not active" message
        global exception_counter
        exception_counter = 0

        def mock_run(*args, **kwargs):
            # Mock run() method which throws "SSH session not active" exception
            # during first two calls and on third one it returns None.
            global exception_counter

            exception_counter += 1

            if exception_counter > 2:
                return None

            raise Exception("SSH session not active")

        task = Mock()
        task.run = Mock()
        task.run = mock_run
        ssh_client = Mock()
        ssh_client.timeout = 20

        self.assertEqual(ssh_client.connect.call_count, 0)
        self.assertEqual(ssh_client.close.call_count, 0)

        self.driver._run_deployment_script(task=task,
                                           node=self.node,
                                           ssh_client=ssh_client,
                                           max_tries=5)

        self.assertEqual(ssh_client.connect.call_count, 2)
        self.assertEqual(ssh_client.close.call_count, 2 + 1)

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_success(self, mock_ssh_module, _):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        mock_ssh_module.have_paramiko = True

        deploy = Mock()

        node = self.driver.deploy_node(deploy=deploy)
        self.assertEqual(self.node.id, node.id)

    @patch('libcloud.compute.base.atexit')
    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_at_exit_func_functionality(self, mock_ssh_module, _,
                                                    mock_at_exit):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        mock_ssh_module.have_paramiko = True

        deploy = Mock()

        def mock_at_exit_func(driver, node):
            pass

        # On success, at exit handler should be unregistered
        self.assertEqual(mock_at_exit.register.call_count, 0)
        self.assertEqual(mock_at_exit.unregister.call_count, 0)

        node = self.driver.deploy_node(deploy=deploy,
                                       at_exit_func=mock_at_exit_func)
        self.assertEqual(mock_at_exit.register.call_count, 1)
        self.assertEqual(mock_at_exit.unregister.call_count, 1)
        self.assertEqual(self.node.id, node.id)

        # On deploy failure, at exit handler should also be unregistered
        mock_at_exit.reset_mock()

        deploy.run.side_effect = Exception('foo')

        self.assertEqual(mock_at_exit.register.call_count, 0)
        self.assertEqual(mock_at_exit.unregister.call_count, 0)

        try:
            self.driver.deploy_node(deploy=deploy,
                                    at_exit_func=mock_at_exit_func)
        except DeploymentError as e:
            self.assertTrue(e.node.id, self.node.id)
        else:
            self.fail('Exception was not thrown')

        self.assertEqual(mock_at_exit.register.call_count, 1)
        self.assertEqual(mock_at_exit.unregister.call_count, 1)

        # But it should not be registered on create_node exception
        mock_at_exit.reset_mock()

        self.driver.create_node = Mock(side_effect=Exception('Failure'))

        self.assertEqual(mock_at_exit.register.call_count, 0)
        self.assertEqual(mock_at_exit.unregister.call_count, 0)

        try:
            self.driver.deploy_node(deploy=deploy,
                                    at_exit_func=mock_at_exit_func)
        except Exception as e:
            self.assertTrue('Failure' in str(e))
        else:
            self.fail('Exception was not thrown')

        self.assertEqual(mock_at_exit.register.call_count, 0)
        self.assertEqual(mock_at_exit.unregister.call_count, 0)

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_deploy_node_kwargs_except_auth_are_not_propagated_on(
            self, mock_ssh_module, _):
        # Verify that keyword arguments which are specific to deploy_node()
        # are not propagated to create_node()
        mock_ssh_module.have_paramiko = True
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        self.driver._connect_and_run_deployment_script = Mock()
        self.driver._wait_until_running = Mock()

        kwargs = {}
        for key in DEPLOY_NODE_KWARGS:
            kwargs[key] = key

        kwargs['ssh_interface'] = 'public_ips'
        kwargs['ssh_alternate_usernames'] = ['foo', 'bar']
        kwargs['timeout'] = 10

        auth = NodeAuthPassword('P@$$w0rd')

        node = self.driver.deploy_node(name='name',
                                       image='image',
                                       size='size',
                                       auth=auth,
                                       ex_foo='ex_foo',
                                       **kwargs)
        self.assertEqual(self.node.id, node.id)
        self.assertEqual(self.driver.create_node.call_count, 1)

        call_kwargs = self.driver.create_node.call_args_list[0][1]
        expected_call_kwargs = {
            'name': 'name',
            'image': 'image',
            'size': 'size',
            'auth': auth,
            'ex_foo': 'ex_foo'
        }
        self.assertEqual(expected_call_kwargs, call_kwargs)

        # If driver throws an exception it should fall back to passing in all
        # the arguments
        global call_count
        call_count = 0

        def create_node(name,
                        image,
                        size,
                        ex_custom_arg_1,
                        ex_custom_arg_2,
                        ex_foo=None,
                        auth=None,
                        **kwargs):
            global call_count

            call_count += 1
            if call_count == 1:
                msg = 'create_node() takes at least 5 arguments (7 given)'
                raise TypeError(msg)
            return self.node

        self.driver.create_node = create_node

        node = self.driver.deploy_node(name='name',
                                       image='image',
                                       size='size',
                                       auth=auth,
                                       ex_foo='ex_foo',
                                       ex_custom_arg_1='a',
                                       ex_custom_arg_2='b',
                                       **kwargs)
        self.assertEqual(self.node.id, node.id)
        self.assertEqual(call_count, 2)

        call_count = 0

        def create_node(name,
                        image,
                        size,
                        ex_custom_arg_1,
                        ex_custom_arg_2,
                        ex_foo=None,
                        auth=None,
                        **kwargs):
            global call_count

            call_count += 1
            if call_count == 1:
                msg = 'create_node() missing 3 required positional arguments'
                raise TypeError(msg)
            return self.node

        self.driver.create_node = create_node

        node = self.driver.deploy_node(name='name',
                                       image='image',
                                       size='size',
                                       auth=auth,
                                       ex_foo='ex_foo',
                                       ex_custom_arg_1='a',
                                       ex_custom_arg_2='b',
                                       **kwargs)
        self.assertEqual(self.node.id, node.id)
        self.assertEqual(call_count, 2)

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_exception_run_deployment_script(
            self, mock_ssh_module, _):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        mock_ssh_module.have_paramiko = True

        deploy = Mock()
        deploy.run = Mock()
        deploy.run.side_effect = Exception('foo')

        try:
            self.driver.deploy_node(deploy=deploy)
        except DeploymentError as e:
            self.assertTrue(e.node.id, self.node.id)
        else:
            self.fail('Exception was not thrown')

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_exception_ssh_client_connect(self, mock_ssh_module,
                                                      ssh_client):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        mock_ssh_module.have_paramiko = True

        deploy = Mock()
        ssh_client.side_effect = IOError('bar')

        try:
            self.driver.deploy_node(deploy=deploy)
        except DeploymentError as e:
            self.assertTrue(e.node.id, self.node.id)
        else:
            self.fail('Exception was not thrown')

    @patch('libcloud.compute.ssh')
    def test_deploy_node_depoy_node_not_implemented(self, mock_ssh_module):
        self.driver.features = {'create_node': []}
        mock_ssh_module.have_paramiko = True

        try:
            self.driver.deploy_node(deploy=Mock())
        except NotImplementedError:
            pass
        else:
            self.fail('Exception was not thrown')

        self.driver.features = {}

        try:
            self.driver.deploy_node(deploy=Mock())
        except NotImplementedError:
            pass
        else:
            self.fail('Exception was not thrown')

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_password_auth(self, mock_ssh_module, _):
        self.driver.features = {'create_node': ['password']}
        mock_ssh_module.have_paramiko = True

        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        node = self.driver.deploy_node(deploy=Mock())
        self.assertEqual(self.node.id, node.id)

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_exception_is_thrown_is_paramiko_is_not_available(
            self, mock_ssh_module, _):
        self.driver.features = {'create_node': ['password']}
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        mock_ssh_module.have_paramiko = False

        try:
            self.driver.deploy_node(deploy=Mock())
        except RuntimeError as e:
            self.assertTrue(str(e).find('paramiko is not installed') != -1)
        else:
            self.fail('Exception was not thrown')

        mock_ssh_module.have_paramiko = True
        node = self.driver.deploy_node(deploy=Mock())
        self.assertEqual(self.node.id, node.id)
class DeploymentTests(unittest.TestCase):
    def setUp(self):
        Rackspace.connectionCls.conn_classes = (None, RackspaceMockHttp)
        RackspaceMockHttp.type = None
        self.driver = Rackspace(*RACKSPACE_PARAMS)
        # normally authentication happens lazily, but we force it here
        self.driver.connection._populate_hosts_and_request_paths()
        self.driver.features = {"create_node": ["generates_password"]}
        self.node = Node(
            id=12345,
            name="test",
            state=NodeState.RUNNING,
            public_ips=["1.2.3.4"],
            private_ips=["1.2.3.5"],
            driver=Rackspace,
        )
        self.node2 = Node(
            id=123456,
            name="test",
            state=NodeState.RUNNING,
            public_ips=["1.2.3.4"],
            private_ips=["1.2.3.5"],
            driver=Rackspace,
        )

    def test_multi_step_deployment(self):
        msd = MultiStepDeployment()
        self.assertEqual(len(msd.steps), 0)

        msd.add(MockDeployment())
        self.assertEqual(len(msd.steps), 1)

        self.assertEqual(self.node, msd.run(node=self.node, client=None))

    def test_ssh_key_deployment(self):
        sshd = SSHKeyDeployment(key="1234")

        self.assertEqual(self.node, sshd.run(node=self.node, client=MockClient(hostname="localhost")))

    def test_file_deployment(self):
        # use this file (__file__) for obtaining permissions
        target = os.path.join("/tmp", os.path.basename(__file__))
        fd = FileDeployment(__file__, target)
        self.assertEqual(target, fd.target)
        self.assertEqual(__file__, fd.source)
        self.assertEqual(self.node, fd.run(node=self.node, client=MockClient(hostname="localhost")))

    def test_script_deployment(self):
        sd1 = ScriptDeployment(script="foobar", delete=True)
        sd2 = ScriptDeployment(script="foobar", delete=False)
        sd3 = ScriptDeployment(script="foobar", delete=False, name="foobarname")

        self.assertTrue(sd1.name.find("deployment") != "1")
        self.assertEqual(sd3.name, "foobarname")

        self.assertEqual(self.node, sd1.run(node=self.node, client=MockClient(hostname="localhost")))
        self.assertEqual(self.node, sd2.run(node=self.node, client=MockClient(hostname="localhost")))

    def test_script_file_deployment(self):
        file_path = os.path.abspath(__file__)
        with open(file_path, "rb") as fp:
            content = fp.read()

        if PY3:
            content = content.decode("utf-8")

        sfd1 = ScriptFileDeployment(script_file=file_path)
        self.assertEqual(sfd1.script, content)

    def test_script_deployment_relative_path(self):
        client = Mock()
        client.put.return_value = "/home/ubuntu/relative.sh"
        client.run.return_value = ("", "", 0)

        sd = ScriptDeployment(script='echo "foo"', name="relative.sh")
        sd.run(self.node, client)

        client.run.assert_called_once_with("/home/ubuntu/relative.sh")

    def test_script_deployment_absolute_path(self):
        client = Mock()
        client.put.return_value = "/home/ubuntu/relative.sh"
        client.run.return_value = ("", "", 0)

        sd = ScriptDeployment(script='echo "foo"', name="/root/relative.sh")
        sd.run(self.node, client)

        client.run.assert_called_once_with("/root/relative.sh")

    def test_script_deployment_with_arguments(self):
        client = Mock()
        client.put.return_value = "/home/ubuntu/relative.sh"
        client.run.return_value = ("", "", 0)

        args = ["arg1", "arg2", "--option1=test"]
        sd = ScriptDeployment(script='echo "foo"', args=args, name="/root/relative.sh")
        sd.run(self.node, client)

        expected = "/root/relative.sh arg1 arg2 --option1=test"
        client.run.assert_called_once_with(expected)

        client.reset_mock()

        args = []
        sd = ScriptDeployment(script='echo "foo"', args=args, name="/root/relative.sh")
        sd.run(self.node, client)

        expected = "/root/relative.sh"
        client.run.assert_called_once_with(expected)

    def test_script_file_deployment_with_arguments(self):
        file_path = os.path.abspath(__file__)
        client = Mock()
        client.put.return_value = "/home/ubuntu/relative.sh"
        client.run.return_value = ("", "", 0)

        args = ["arg1", "arg2", "--option1=test", "option2"]
        sfd = ScriptFileDeployment(script_file=file_path, args=args, name="/root/relative.sh")

        sfd.run(self.node, client)

        expected = "/root/relative.sh arg1 arg2 --option1=test option2"
        client.run.assert_called_once_with(expected)

    def test_script_deployment_and_sshkey_deployment_argument_types(self):
        class FileObject(object):
            def __init__(self, name):
                self.name = name

            def read(self):
                return "bar"

        ScriptDeployment(script="foobar")
        ScriptDeployment(script=u("foobar"))
        ScriptDeployment(script=FileObject("test"))

        SSHKeyDeployment(key="foobar")
        SSHKeyDeployment(key=u("foobar"))
        SSHKeyDeployment(key=FileObject("test"))

        try:
            ScriptDeployment(script=[])
        except TypeError:
            pass
        else:
            self.fail("TypeError was not thrown")

        try:
            SSHKeyDeployment(key={})
        except TypeError:
            pass
        else:
            self.fail("TypeError was not thrown")

    def test_wait_until_running_running_instantly(self):
        node2, ips = self.driver.wait_until_running(nodes=[self.node], wait_period=1, timeout=0.5)[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(["67.23.21.33"], ips)

    def test_wait_until_running_running_after_1_second(self):
        RackspaceMockHttp.type = "05_SECOND_DELAY"
        node2, ips = self.driver.wait_until_running(nodes=[self.node], wait_period=1, timeout=0.5)[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(["67.23.21.33"], ips)

    def test_wait_until_running_running_after_1_second_private_ips(self):
        RackspaceMockHttp.type = "05_SECOND_DELAY"
        node2, ips = self.driver.wait_until_running(
            nodes=[self.node], wait_period=1, timeout=0.5, ssh_interface="private_ips"
        )[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(["10.176.168.218"], ips)

    def test_wait_until_running_invalid_ssh_interface_argument(self):
        try:
            self.driver.wait_until_running(nodes=[self.node], wait_period=1, ssh_interface="invalid")
        except ValueError:
            pass
        else:
            self.fail("Exception was not thrown")

    def test_wait_until_running_timeout(self):
        RackspaceMockHttp.type = "TIMEOUT"

        try:
            self.driver.wait_until_running(nodes=[self.node], wait_period=0.1, timeout=0.5)
        except LibcloudError:
            e = sys.exc_info()[1]
            self.assertTrue(e.value.find("Timed out") != -1)
        else:
            self.fail("Exception was not thrown")

    def test_wait_until_running_running_node_missing_from_list_nodes(self):
        RackspaceMockHttp.type = "MISSING"

        try:
            self.driver.wait_until_running(nodes=[self.node], wait_period=0.1, timeout=0.5)
        except LibcloudError:
            e = sys.exc_info()[1]
            self.assertTrue(e.value.find("Timed out after 0.5 second") != -1)
        else:
            self.fail("Exception was not thrown")

    def test_wait_until_running_running_multiple_nodes_have_same_uuid(self):
        RackspaceMockHttp.type = "SAME_UUID"

        try:
            self.driver.wait_until_running(nodes=[self.node], wait_period=0.1, timeout=0.5)
        except LibcloudError:
            e = sys.exc_info()[1]
            self.assertTrue(e.value.find("Unable to match specified uuids") != -1)
        else:
            self.fail("Exception was not thrown")

    def test_wait_until_running_running_wait_for_multiple_nodes(self):
        RackspaceMockHttp.type = "MULTIPLE_NODES"

        nodes = self.driver.wait_until_running(nodes=[self.node, self.node2], wait_period=0.1, timeout=0.5)
        self.assertEqual(self.node.uuid, nodes[0][0].uuid)
        self.assertEqual(self.node2.uuid, nodes[1][0].uuid)
        self.assertEqual(["67.23.21.33"], nodes[0][1])
        self.assertEqual(["67.23.21.34"], nodes[1][1])

    def test_ssh_client_connect_success(self):
        mock_ssh_client = Mock()
        mock_ssh_client.return_value = None

        ssh_client = self.driver._ssh_client_connect(ssh_client=mock_ssh_client, timeout=0.5)
        self.assertEqual(mock_ssh_client, ssh_client)

    def test_ssh_client_connect_timeout(self):
        mock_ssh_client = Mock()
        mock_ssh_client.connect = Mock()
        mock_ssh_client.connect.side_effect = IOError("bam")

        try:
            self.driver._ssh_client_connect(ssh_client=mock_ssh_client, timeout=0.5)
        except LibcloudError:
            e = sys.exc_info()[1]
            self.assertTrue(e.value.find("Giving up") != -1)
        else:
            self.fail("Exception was not thrown")

    def test_run_deployment_script_success(self):
        task = Mock()
        ssh_client = Mock()

        ssh_client2 = self.driver._run_deployment_script(task=task, node=self.node, ssh_client=ssh_client, max_tries=2)
        self.assertTrue(isinstance(ssh_client2, Mock))

    def test_run_deployment_script_exception(self):
        task = Mock()
        task.run = Mock()
        task.run.side_effect = Exception("bar")
        ssh_client = Mock()

        try:
            self.driver._run_deployment_script(task=task, node=self.node, ssh_client=ssh_client, max_tries=2)
        except LibcloudError:
            e = sys.exc_info()[1]
            self.assertTrue(e.value.find("Failed after 2 tries") != -1)
        else:
            self.fail("Exception was not thrown")

    @patch("libcloud.compute.base.SSHClient")
    @patch("libcloud.compute.ssh")
    def test_deploy_node_success(self, mock_ssh_module, _):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        mock_ssh_module.have_paramiko = True

        deploy = Mock()

        node = self.driver.deploy_node(deploy=deploy)
        self.assertEqual(self.node.id, node.id)

    @patch("libcloud.compute.base.SSHClient")
    @patch("libcloud.compute.ssh")
    def test_deploy_node_exception_run_deployment_script(self, mock_ssh_module, _):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        mock_ssh_module.have_paramiko = True

        deploy = Mock()
        deploy.run = Mock()
        deploy.run.side_effect = Exception("foo")

        try:
            self.driver.deploy_node(deploy=deploy)
        except DeploymentError:
            e = sys.exc_info()[1]
            self.assertTrue(e.node.id, self.node.id)
        else:
            self.fail("Exception was not thrown")

    @patch("libcloud.compute.base.SSHClient")
    @patch("libcloud.compute.ssh")
    def test_deploy_node_exception_ssh_client_connect(self, mock_ssh_module, ssh_client):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        mock_ssh_module.have_paramiko = True

        deploy = Mock()
        ssh_client.side_effect = IOError("bar")

        try:
            self.driver.deploy_node(deploy=deploy)
        except DeploymentError:
            e = sys.exc_info()[1]
            self.assertTrue(e.node.id, self.node.id)
        else:
            self.fail("Exception was not thrown")

    @patch("libcloud.compute.ssh")
    def test_deploy_node_depoy_node_not_implemented(self, mock_ssh_module):
        self.driver.features = {"create_node": []}
        mock_ssh_module.have_paramiko = True

        try:
            self.driver.deploy_node(deploy=Mock())
        except NotImplementedError:
            pass
        else:
            self.fail("Exception was not thrown")

        self.driver.features = {}

        try:
            self.driver.deploy_node(deploy=Mock())
        except NotImplementedError:
            pass
        else:
            self.fail("Exception was not thrown")

    @patch("libcloud.compute.base.SSHClient")
    @patch("libcloud.compute.ssh")
    def test_deploy_node_password_auth(self, mock_ssh_module, _):
        self.driver.features = {"create_node": ["password"]}
        mock_ssh_module.have_paramiko = True

        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        node = self.driver.deploy_node(deploy=Mock())
        self.assertEqual(self.node.id, node.id)

    @patch("libcloud.compute.base.SSHClient")
    @patch("libcloud.compute.ssh")
    def test_exception_is_thrown_is_paramiko_is_not_available(self, mock_ssh_module, _):
        self.driver.features = {"create_node": ["password"]}
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        mock_ssh_module.have_paramiko = False

        try:
            self.driver.deploy_node(deploy=Mock())
        except RuntimeError:
            e = sys.exc_info()[1]
            self.assertTrue(str(e).find("paramiko is not installed") != -1)
        else:
            self.fail("Exception was not thrown")

        mock_ssh_module.have_paramiko = True
        node = self.driver.deploy_node(deploy=Mock())
        self.assertEqual(self.node.id, node.id)
class DeploymentTests(unittest.TestCase):

    def setUp(self):
        Rackspace.connectionCls.conn_classes = (None, RackspaceMockHttp)
        RackspaceMockHttp.type = None
        self.driver = Rackspace(*RACKSPACE_PARAMS)
        # normally authentication happens lazily, but we force it here
        self.driver.connection._populate_hosts_and_request_paths()
        self.driver.features = {'create_node': ['generates_password']}
        self.node = Node(id=12345, name='test', state=NodeState.RUNNING,
                         public_ips=['1.2.3.4'], private_ips=['1.2.3.5'],
                         driver=Rackspace)
        self.node2 = Node(id=123456, name='test', state=NodeState.RUNNING,
                          public_ips=['1.2.3.4'], private_ips=['1.2.3.5'],
                          driver=Rackspace)

    def test_multi_step_deployment(self):
        msd = MultiStepDeployment()
        self.assertEqual(len(msd.steps), 0)

        msd.add(MockDeployment())
        self.assertEqual(len(msd.steps), 1)

        self.assertEqual(self.node, msd.run(node=self.node, client=None))

    def test_ssh_key_deployment(self):
        sshd = SSHKeyDeployment(key='1234')

        self.assertEqual(self.node, sshd.run(node=self.node,
                                             client=MockClient(hostname='localhost')))

    def test_file_deployment(self):
        # use this file (__file__) for obtaining permissions
        target = os.path.join('/tmp', os.path.basename(__file__))
        fd = FileDeployment(__file__, target)
        self.assertEqual(target, fd.target)
        self.assertEqual(__file__, fd.source)
        self.assertEqual(self.node, fd.run(
            node=self.node, client=MockClient(hostname='localhost')))

    def test_script_deployment(self):
        sd1 = ScriptDeployment(script='foobar', delete=True)
        sd2 = ScriptDeployment(script='foobar', delete=False)
        sd3 = ScriptDeployment(
            script='foobar', delete=False, name='foobarname')

        self.assertTrue(sd1.name.find('deployment') != '1')
        self.assertEqual(sd3.name, 'foobarname')

        self.assertEqual(self.node, sd1.run(node=self.node,
                                            client=MockClient(hostname='localhost')))
        self.assertEqual(self.node, sd2.run(node=self.node,
                                            client=MockClient(hostname='localhost')))

    def test_script_file_deployment(self):
        file_path = os.path.abspath(__file__)
        with open(file_path, 'rb') as fp:
            content = fp.read()

        if PY3:
            content = content.decode('utf-8')

        sfd1 = ScriptFileDeployment(script_file=file_path)
        self.assertEqual(sfd1.script, content)

    def test_script_deployment_relative_path(self):
        client = Mock()
        client.put.return_value = '/home/ubuntu/relative.sh'
        client.run.return_value = ('', '', 0)

        sd = ScriptDeployment(script='echo "foo"', name='relative.sh')
        sd.run(self.node, client)

        client.run.assert_called_once_with('/home/ubuntu/relative.sh')

    def test_script_deployment_absolute_path(self):
        client = Mock()
        client.put.return_value = '/home/ubuntu/relative.sh'
        client.run.return_value = ('', '', 0)

        sd = ScriptDeployment(script='echo "foo"', name='/root/relative.sh')
        sd.run(self.node, client)

        client.run.assert_called_once_with('/root/relative.sh')

    def test_script_deployment_with_arguments(self):
        client = Mock()
        client.put.return_value = '/home/ubuntu/relative.sh'
        client.run.return_value = ('', '', 0)

        args = ['arg1', 'arg2', '--option1=test']
        sd = ScriptDeployment(script='echo "foo"', args=args,
                              name='/root/relative.sh')
        sd.run(self.node, client)

        expected = '/root/relative.sh arg1 arg2 --option1=test'
        client.run.assert_called_once_with(expected)

        client.reset_mock()

        args = []
        sd = ScriptDeployment(script='echo "foo"', args=args,
                              name='/root/relative.sh')
        sd.run(self.node, client)

        expected = '/root/relative.sh'
        client.run.assert_called_once_with(expected)

    def test_script_file_deployment_with_arguments(self):
        file_path = os.path.abspath(__file__)
        client = Mock()
        client.put.return_value = '/home/ubuntu/relative.sh'
        client.run.return_value = ('', '', 0)

        args = ['arg1', 'arg2', '--option1=test', 'option2']
        sfd = ScriptFileDeployment(script_file=file_path, args=args,
                                   name='/root/relative.sh')

        sfd.run(self.node, client)

        expected = '/root/relative.sh arg1 arg2 --option1=test option2'
        client.run.assert_called_once_with(expected)

    def test_script_deployment_and_sshkey_deployment_argument_types(self):
        class FileObject(object):

            def __init__(self, name):
                self.name = name

            def read(self):
                return 'bar'

        ScriptDeployment(script='foobar')
        ScriptDeployment(script=u('foobar'))
        ScriptDeployment(script=FileObject('test'))

        SSHKeyDeployment(key='foobar')
        SSHKeyDeployment(key=u('foobar'))
        SSHKeyDeployment(key=FileObject('test'))

        try:
            ScriptDeployment(script=[])
        except TypeError:
            pass
        else:
            self.fail('TypeError was not thrown')

        try:
            SSHKeyDeployment(key={})
        except TypeError:
            pass
        else:
            self.fail('TypeError was not thrown')

    def test_wait_until_running_running_instantly(self):
        node2, ips = self.driver.wait_until_running(
            nodes=[self.node], wait_period=1,
            timeout=0.5)[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(['67.23.21.33'], ips)

    def test_wait_until_running_running_after_1_second(self):
        RackspaceMockHttp.type = '05_SECOND_DELAY'
        node2, ips = self.driver.wait_until_running(
            nodes=[self.node], wait_period=1,
            timeout=0.5)[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(['67.23.21.33'], ips)

    def test_wait_until_running_running_after_1_second_private_ips(self):
        RackspaceMockHttp.type = '05_SECOND_DELAY'
        node2, ips = self.driver.wait_until_running(
            nodes=[self.node], wait_period=1,
            timeout=0.5, ssh_interface='private_ips')[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(['10.176.168.218'], ips)

    def test_wait_until_running_invalid_ssh_interface_argument(self):
        try:
            self.driver.wait_until_running(nodes=[self.node], wait_period=1,
                                           ssh_interface='invalid')
        except ValueError:
            pass
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_timeout(self):
        RackspaceMockHttp.type = 'TIMEOUT'

        try:
            self.driver.wait_until_running(nodes=[self.node], wait_period=0.1,
                                           timeout=0.5)
        except LibcloudError:
            e = sys.exc_info()[1]
            self.assertTrue(e.value.find('Timed out') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_running_node_missing_from_list_nodes(self):
        RackspaceMockHttp.type = 'MISSING'

        try:
            self.driver.wait_until_running(nodes=[self.node], wait_period=0.1,
                                           timeout=0.5)
        except LibcloudError:
            e = sys.exc_info()[1]
            self.assertTrue(e.value.find('Timed out after 0.5 second') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_running_multiple_nodes_have_same_uuid(self):
        RackspaceMockHttp.type = 'SAME_UUID'

        try:
            self.driver.wait_until_running(nodes=[self.node], wait_period=0.1,
                                           timeout=0.5)
        except LibcloudError:
            e = sys.exc_info()[1]
            self.assertTrue(
                e.value.find('Unable to match specified uuids') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_wait_until_running_running_wait_for_multiple_nodes(self):
        RackspaceMockHttp.type = 'MULTIPLE_NODES'

        nodes = self.driver.wait_until_running(
            nodes=[self.node, self.node2], wait_period=0.1,
            timeout=0.5)
        self.assertEqual(self.node.uuid, nodes[0][0].uuid)
        self.assertEqual(self.node2.uuid, nodes[1][0].uuid)
        self.assertEqual(['67.23.21.33'], nodes[0][1])
        self.assertEqual(['67.23.21.34'], nodes[1][1])

    def test_ssh_client_connect_success(self):
        mock_ssh_client = Mock()
        mock_ssh_client.return_value = None

        ssh_client = self.driver._ssh_client_connect(
            ssh_client=mock_ssh_client,
            timeout=0.5)
        self.assertEqual(mock_ssh_client, ssh_client)

    def test_ssh_client_connect_timeout(self):
        mock_ssh_client = Mock()
        mock_ssh_client.connect = Mock()
        mock_ssh_client.connect.side_effect = IOError('bam')

        try:
            self.driver._ssh_client_connect(ssh_client=mock_ssh_client,
                                            timeout=0.5)
        except LibcloudError:
            e = sys.exc_info()[1]
            self.assertTrue(e.value.find('Giving up') != -1)
        else:
            self.fail('Exception was not thrown')

    def test_run_deployment_script_success(self):
        task = Mock()
        ssh_client = Mock()

        ssh_client2 = self.driver._run_deployment_script(task=task,
                                                         node=self.node,
                                                         ssh_client=ssh_client,
                                                         max_tries=2)
        self.assertTrue(isinstance(ssh_client2, Mock))

    def test_run_deployment_script_exception(self):
        task = Mock()
        task.run = Mock()
        task.run.side_effect = Exception('bar')
        ssh_client = Mock()

        try:
            self.driver._run_deployment_script(task=task,
                                               node=self.node,
                                               ssh_client=ssh_client,
                                               max_tries=2)
        except LibcloudError:
            e = sys.exc_info()[1]
            self.assertTrue(e.value.find('Failed after 2 tries') != -1)
        else:
            self.fail('Exception was not thrown')

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_success(self, mock_ssh_module, _):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        mock_ssh_module.have_paramiko = True

        deploy = Mock()

        node = self.driver.deploy_node(deploy=deploy)
        self.assertEqual(self.node.id, node.id)

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_exception_run_deployment_script(self, mock_ssh_module,
                                                         _):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        mock_ssh_module.have_paramiko = True

        deploy = Mock()
        deploy.run = Mock()
        deploy.run.side_effect = Exception('foo')

        try:
            self.driver.deploy_node(deploy=deploy)
        except DeploymentError:
            e = sys.exc_info()[1]
            self.assertTrue(e.node.id, self.node.id)
        else:
            self.fail('Exception was not thrown')

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_exception_ssh_client_connect(self, mock_ssh_module,
                                                      ssh_client):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        mock_ssh_module.have_paramiko = True

        deploy = Mock()
        ssh_client.side_effect = IOError('bar')

        try:
            self.driver.deploy_node(deploy=deploy)
        except DeploymentError:
            e = sys.exc_info()[1]
            self.assertTrue(e.node.id, self.node.id)
        else:
            self.fail('Exception was not thrown')

    @patch('libcloud.compute.ssh')
    def test_deploy_node_depoy_node_not_implemented(self, mock_ssh_module):
        self.driver.features = {'create_node': []}
        mock_ssh_module.have_paramiko = True

        try:
            self.driver.deploy_node(deploy=Mock())
        except NotImplementedError:
            pass
        else:
            self.fail('Exception was not thrown')

        self.driver.features = {}

        try:
            self.driver.deploy_node(deploy=Mock())
        except NotImplementedError:
            pass
        else:
            self.fail('Exception was not thrown')

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_deploy_node_password_auth(self, mock_ssh_module, _):
        self.driver.features = {'create_node': ['password']}
        mock_ssh_module.have_paramiko = True

        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        node = self.driver.deploy_node(deploy=Mock())
        self.assertEqual(self.node.id, node.id)

    @patch('libcloud.compute.base.SSHClient')
    @patch('libcloud.compute.ssh')
    def test_exception_is_thrown_is_paramiko_is_not_available(self,
                                                              mock_ssh_module,
                                                              _):
        self.driver.features = {'create_node': ['password']}
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        mock_ssh_module.have_paramiko = False

        try:
            self.driver.deploy_node(deploy=Mock())
        except RuntimeError:
            e = sys.exc_info()[1]
            self.assertTrue(str(e).find('paramiko is not installed') != -1)
        else:
            self.fail('Exception was not thrown')

        mock_ssh_module.have_paramiko = True
        node = self.driver.deploy_node(deploy=Mock())
        self.assertEqual(self.node.id, node.id)
Exemple #5
0
class DeploymentTests(unittest.TestCase):
    def setUp(self):
        Rackspace.connectionCls.conn_class = RackspaceMockHttp
        RackspaceMockHttp.type = None
        self.driver = Rackspace(*RACKSPACE_PARAMS)
        # normally authentication happens lazily, but we force it here
        self.driver.connection._populate_hosts_and_request_paths()
        self.driver.features = {"create_node": ["generates_password"]}
        self.node = Node(
            id=12345,
            name="test",
            state=NodeState.RUNNING,
            public_ips=["1.2.3.4"],
            private_ips=["1.2.3.5"],
            driver=Rackspace,
        )
        self.node2 = Node(
            id=123456,
            name="test",
            state=NodeState.RUNNING,
            public_ips=["1.2.3.4"],
            private_ips=["1.2.3.5"],
            driver=Rackspace,
        )

    def test_multi_step_deployment(self):
        msd = MultiStepDeployment()
        self.assertEqual(len(msd.steps), 0)

        msd.add(MockDeployment())
        self.assertEqual(len(msd.steps), 1)

        self.assertEqual(self.node, msd.run(node=self.node, client=None))

    def test_ssh_key_deployment(self):
        sshd = SSHKeyDeployment(key="1234")

        self.assertEqual(
            self.node,
            sshd.run(node=self.node, client=MockClient(hostname="localhost")))

    def test_file_deployment(self):
        # use this file (__file__) for obtaining permissions
        target = os.path.join("/tmp", os.path.basename(__file__))
        fd = FileDeployment(__file__, target)
        self.assertEqual(target, fd.target)
        self.assertEqual(__file__, fd.source)
        self.assertEqual(
            self.node,
            fd.run(node=self.node, client=MockClient(hostname="localhost")))

    def test_script_deployment(self):
        sd1 = ScriptDeployment(script="foobar", delete=True)
        sd2 = ScriptDeployment(script="foobar", delete=False)
        sd3 = ScriptDeployment(script="foobar",
                               delete=False,
                               name="foobarname")
        sd4 = ScriptDeployment(script="foobar",
                               delete=False,
                               name="foobarname",
                               timeout=10)

        self.assertTrue(sd1.name.find("deployment") != "1")
        self.assertEqual(sd3.name, "foobarname")
        self.assertEqual(sd3.timeout, None)
        self.assertEqual(sd4.timeout, 10)

        self.assertEqual(
            self.node,
            sd1.run(node=self.node, client=MockClient(hostname="localhost")))
        self.assertEqual(
            self.node,
            sd2.run(node=self.node, client=MockClient(hostname="localhost")))
        self.assertEqual(
            self.node,
            sd3.run(node=self.node, client=MockClient(hostname="localhost")))

        assertRaisesRegex(
            self,
            ValueError,
            "timeout",
            sd4.run,
            node=self.node,
            client=MockClient(hostname="localhost", throw_on_timeout=True),
        )

    def test_script_file_deployment(self):
        file_path = os.path.abspath(__file__)
        with open(file_path, "rb") as fp:
            content = fp.read()

        if PY3:
            content = content.decode("utf-8")

        sfd1 = ScriptFileDeployment(script_file=file_path)
        self.assertEqual(sfd1.script, content)
        self.assertEqual(sfd1.timeout, None)

        sfd2 = ScriptFileDeployment(script_file=file_path, timeout=20)
        self.assertEqual(sfd2.timeout, 20)

    def test_script_deployment_relative_path(self):
        client = Mock()
        client.put.return_value = FILE_PATH
        client.run.return_value = ("", "", 0)

        sd = ScriptDeployment(script='echo "foo"', name="relative.sh")
        sd.run(self.node, client)

        client.run.assert_called_once_with(FILE_PATH, timeout=None)

    def test_script_deployment_absolute_path(self):
        file_path = "{0}root{0}relative.sh".format(os.path.sep)

        client = Mock()
        client.put.return_value = file_path
        client.run.return_value = ("", "", 0)

        sd = ScriptDeployment(script='echo "foo"', name=file_path)
        sd.run(self.node, client)

        client.run.assert_called_once_with(file_path, timeout=None)

    def test_script_deployment_with_arguments(self):
        file_path = "{0}root{0}relative.sh".format(os.path.sep)

        client = Mock()
        client.put.return_value = file_path
        client.run.return_value = ("", "", 0)

        args = ["arg1", "arg2", "--option1=test"]
        sd = ScriptDeployment(script='echo "foo"', args=args, name=file_path)
        sd.run(self.node, client)

        expected = "%s arg1 arg2 --option1=test" % (file_path)
        client.run.assert_called_once_with(expected, timeout=None)

        client.reset_mock()

        args = []
        sd = ScriptDeployment(script='echo "foo"', args=args, name=file_path)
        sd.run(self.node, client)

        expected = file_path
        client.run.assert_called_once_with(expected, timeout=None)

    def test_script_file_deployment_with_arguments(self):
        file_path = os.path.abspath(__file__)
        client = Mock()
        client.put.return_value = FILE_PATH
        client.run.return_value = ("", "", 0)

        args = ["arg1", "arg2", "--option1=test", "option2"]
        sfd = ScriptFileDeployment(script_file=file_path,
                                   args=args,
                                   name=file_path)

        sfd.run(self.node, client)

        expected = "%s arg1 arg2 --option1=test option2" % (file_path)
        client.run.assert_called_once_with(expected, timeout=None)

    def test_script_deployment_and_sshkey_deployment_argument_types(self):
        class FileObject(object):
            def __init__(self, name):
                self.name = name

            def read(self):
                return "bar"

        ScriptDeployment(script="foobar")
        ScriptDeployment(script=u("foobar"))
        ScriptDeployment(script=FileObject("test"))

        SSHKeyDeployment(key="foobar")
        SSHKeyDeployment(key=u("foobar"))
        SSHKeyDeployment(key=FileObject("test"))

        try:
            ScriptDeployment(script=[])
        except TypeError:
            pass
        else:
            self.fail("TypeError was not thrown")

        try:
            SSHKeyDeployment(key={})
        except TypeError:
            pass
        else:
            self.fail("TypeError was not thrown")

    def test_wait_until_running_running_instantly(self):
        node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                    wait_period=0.1,
                                                    timeout=0.5)[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(["67.23.21.33"], ips)

    def test_wait_until_running_without_ip(self):
        RackspaceMockHttp.type = "NO_IP"

        try:
            node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                        wait_period=0.1,
                                                        timeout=0.2)[0]
        except LibcloudError as e:
            self.assertTrue(e.value.find("Timed out after 0.2 second") != -1)
        else:
            self.fail("Exception was not thrown")

    def test_wait_until_running_with_only_ipv6(self):
        RackspaceMockHttp.type = "IPV6"

        try:
            node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                        wait_period=0.1,
                                                        timeout=0.2)[0]
        except LibcloudError as e:
            self.assertTrue(e.value.find("Timed out after 0.2 second") != -1)
        else:
            self.fail("Exception was not thrown")

    def test_wait_until_running_with_ipv6_ok(self):
        RackspaceMockHttp.type = "IPV6"
        node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                    wait_period=1,
                                                    force_ipv4=False,
                                                    timeout=0.5)[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(["2001:DB8::1"], ips)

    def test_wait_until_running_running_after_0_2_second(self):
        RackspaceMockHttp.type = "05_SECOND_DELAY"
        node2, ips = self.driver.wait_until_running(nodes=[self.node],
                                                    wait_period=0.2,
                                                    timeout=0.1)[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(["67.23.21.33"], ips)

    def test_wait_until_running_running_after_0_2_second_private_ips(self):
        RackspaceMockHttp.type = "05_SECOND_DELAY"
        node2, ips = self.driver.wait_until_running(
            nodes=[self.node],
            wait_period=0.2,
            timeout=0.1,
            ssh_interface="private_ips")[0]
        self.assertEqual(self.node.uuid, node2.uuid)
        self.assertEqual(["10.176.168.218"], ips)

    def test_wait_until_running_invalid_ssh_interface_argument(self):
        try:
            self.driver.wait_until_running(nodes=[self.node],
                                           wait_period=1,
                                           ssh_interface="invalid")
        except ValueError:
            pass
        else:
            self.fail("Exception was not thrown")

    def test_wait_until_running_timeout(self):
        RackspaceMockHttp.type = "TIMEOUT"

        try:
            self.driver.wait_until_running(nodes=[self.node],
                                           wait_period=0.1,
                                           timeout=0.2)
        except LibcloudError as e:
            self.assertTrue(e.value.find("Timed out") != -1)
        else:
            self.fail("Exception was not thrown")

    def test_wait_until_running_running_node_missing_from_list_nodes(self):
        RackspaceMockHttp.type = "MISSING"

        try:
            self.driver.wait_until_running(nodes=[self.node],
                                           wait_period=0.1,
                                           timeout=0.2)
        except LibcloudError as e:
            self.assertTrue(e.value.find("Timed out after 0.2 second") != -1)
        else:
            self.fail("Exception was not thrown")

    def test_wait_until_running_running_multiple_nodes_have_same_uuid(self):
        RackspaceMockHttp.type = "SAME_UUID"

        try:
            self.driver.wait_until_running(nodes=[self.node],
                                           wait_period=0.1,
                                           timeout=0.2)
        except LibcloudError as e:
            self.assertTrue(
                e.value.find("Unable to match specified uuids") != -1)
        else:
            self.fail("Exception was not thrown")

    def test_wait_until_running_running_wait_for_multiple_nodes(self):
        RackspaceMockHttp.type = "MULTIPLE_NODES"

        nodes = self.driver.wait_until_running(nodes=[self.node, self.node2],
                                               wait_period=0.1,
                                               timeout=0.5)
        self.assertEqual(self.node.uuid, nodes[0][0].uuid)
        self.assertEqual(self.node2.uuid, nodes[1][0].uuid)
        self.assertEqual(["67.23.21.33"], nodes[0][1])
        self.assertEqual(["67.23.21.34"], nodes[1][1])

    def test_ssh_client_connect_success(self):
        mock_ssh_client = Mock()
        mock_ssh_client.return_value = None

        ssh_client = self.driver._ssh_client_connect(
            ssh_client=mock_ssh_client, timeout=0.5)
        self.assertEqual(mock_ssh_client, ssh_client)

    def test_ssh_client_connect_timeout(self):
        mock_ssh_client = Mock()
        mock_ssh_client.connect = Mock()
        mock_ssh_client.connect.side_effect = IOError("bam")

        try:
            self.driver._ssh_client_connect(ssh_client=mock_ssh_client,
                                            wait_period=0.1,
                                            timeout=0.2)
        except LibcloudError as e:
            self.assertTrue(e.value.find("Giving up") != -1)
        else:
            self.fail("Exception was not thrown")

    @unittest.skipIf(not have_paramiko,
                     "Skipping because paramiko is not available")
    def test_ssh_client_connect_immediately_throws_on_fatal_execption(self):
        # Verify that fatal exceptions are immediately propagated and ensure
        # we don't try to retry on them
        from paramiko.ssh_exception import SSHException
        from paramiko.ssh_exception import PasswordRequiredException

        mock_ssh_client = Mock()
        mock_ssh_client.connect = Mock()
        mock_ssh_client.connect.side_effect = IOError("bam")

        mock_exceptions = [
            SSHException("Invalid or unsupported key type"),
            PasswordRequiredException("private key file is encrypted"),
            SSHException("OpenSSH private key file checkints do not match"),
        ]

        for mock_exception in mock_exceptions:
            mock_ssh_client.connect = Mock(side_effect=mock_exception)
            assertRaisesRegex(
                self,
                mock_exception.__class__,
                str(mock_exception),
                self.driver._ssh_client_connect,
                ssh_client=mock_ssh_client,
                wait_period=0.1,
                timeout=0.2,
            )

    def test_run_deployment_script_success(self):
        task = Mock()
        ssh_client = Mock()

        ssh_client2 = self.driver._run_deployment_script(task=task,
                                                         node=self.node,
                                                         ssh_client=ssh_client,
                                                         max_tries=2)
        self.assertTrue(isinstance(ssh_client2, Mock))

    def test_run_deployment_script_exception(self):
        task = Mock()
        task.run = Mock()
        task.run.side_effect = Exception("bar")
        ssh_client = Mock()

        try:
            self.driver._run_deployment_script(task=task,
                                               node=self.node,
                                               ssh_client=ssh_client,
                                               max_tries=2)
        except LibcloudError as e:
            self.assertTrue(e.value.find("Failed after 2 tries") != -1)
        else:
            self.fail("Exception was not thrown")

    def test_run_deployment_script_ssh_command_timeout_fatal_exception(self):
        # We shouldn't retry on SSHCommandTimeoutError error since it's fatal
        task = Mock()
        task.run = Mock()
        task.run.side_effect = SSHCommandTimeoutError("ls -la", 10)
        ssh_client = Mock()

        try:
            self.driver._run_deployment_script(task=task,
                                               node=self.node,
                                               ssh_client=ssh_client,
                                               max_tries=5)
        except SSHCommandTimeoutError as e:
            self.assertTrue(e.message.find("Command didn't finish") != -1)
        else:
            self.fail("Exception was not thrown")

    def test_run_deployment_script_reconnect_on_ssh_session_not_active(self):
        # Verify that we try to reconnect if task.run() throws exception with
        # "SSH client not active" message
        global exception_counter
        exception_counter = 0

        def mock_run(*args, **kwargs):
            # Mock run() method which throws "SSH session not active" exception
            # during first two calls and on third one it returns None.
            global exception_counter

            exception_counter += 1

            if exception_counter > 2:
                return None

            raise Exception("SSH session not active")

        task = Mock()
        task.run = Mock()
        task.run = mock_run
        ssh_client = Mock()
        ssh_client.timeout = 20

        self.assertEqual(ssh_client.connect.call_count, 0)
        self.assertEqual(ssh_client.close.call_count, 0)

        self.driver._run_deployment_script(task=task,
                                           node=self.node,
                                           ssh_client=ssh_client,
                                           max_tries=5)

        self.assertEqual(ssh_client.connect.call_count, 2)
        self.assertEqual(ssh_client.close.call_count, 2 + 1)

    @patch("libcloud.compute.base.SSHClient")
    @patch("libcloud.compute.ssh")
    def test_deploy_node_success(self, mock_ssh_module, _):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        mock_ssh_module.have_paramiko = True

        deploy = Mock()

        node = self.driver.deploy_node(deploy=deploy)
        self.assertEqual(self.node.id, node.id)

    @patch("libcloud.compute.base.atexit")
    @patch("libcloud.compute.base.SSHClient")
    @patch("libcloud.compute.ssh")
    def test_deploy_node_at_exit_func_functionality(self, mock_ssh_module, _,
                                                    mock_at_exit):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        mock_ssh_module.have_paramiko = True

        deploy = Mock()

        def mock_at_exit_func(driver, node):
            pass

        # On success, at exit handler should be unregistered
        self.assertEqual(mock_at_exit.register.call_count, 0)
        self.assertEqual(mock_at_exit.unregister.call_count, 0)

        node = self.driver.deploy_node(deploy=deploy,
                                       at_exit_func=mock_at_exit_func)
        self.assertEqual(mock_at_exit.register.call_count, 1)
        self.assertEqual(mock_at_exit.unregister.call_count, 1)
        self.assertEqual(self.node.id, node.id)

        # On deploy failure, at exit handler should also be unregistered
        mock_at_exit.reset_mock()

        deploy.run.side_effect = Exception("foo")

        self.assertEqual(mock_at_exit.register.call_count, 0)
        self.assertEqual(mock_at_exit.unregister.call_count, 0)

        try:
            self.driver.deploy_node(deploy=deploy,
                                    at_exit_func=mock_at_exit_func)
        except DeploymentError as e:
            self.assertTrue(e.node.id, self.node.id)
        else:
            self.fail("Exception was not thrown")

        self.assertEqual(mock_at_exit.register.call_count, 1)
        self.assertEqual(mock_at_exit.unregister.call_count, 1)

        # But it should not be registered on create_node exception
        mock_at_exit.reset_mock()

        self.driver.create_node = Mock(side_effect=Exception("Failure"))

        self.assertEqual(mock_at_exit.register.call_count, 0)
        self.assertEqual(mock_at_exit.unregister.call_count, 0)

        try:
            self.driver.deploy_node(deploy=deploy,
                                    at_exit_func=mock_at_exit_func)
        except Exception as e:
            self.assertTrue("Failure" in str(e))
        else:
            self.fail("Exception was not thrown")

        self.assertEqual(mock_at_exit.register.call_count, 0)
        self.assertEqual(mock_at_exit.unregister.call_count, 0)

    @patch("libcloud.compute.base.SSHClient")
    @patch("libcloud.compute.ssh")
    def test_deploy_node_deploy_node_kwargs_except_auth_are_not_propagated_on(
            self, mock_ssh_module, _):
        # Verify that keyword arguments which are specific to deploy_node()
        # are not propagated to create_node()
        mock_ssh_module.have_paramiko = True
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        self.driver._connect_and_run_deployment_script = Mock()
        self.driver._wait_until_running = Mock()

        kwargs = {}
        for key in DEPLOY_NODE_KWARGS:
            kwargs[key] = key

        kwargs["ssh_interface"] = "public_ips"
        kwargs["ssh_alternate_usernames"] = ["foo", "bar"]
        kwargs["timeout"] = 10

        auth = NodeAuthPassword("P@$$w0rd")

        node = self.driver.deploy_node(
            name="name",
            image="image",
            size="size",
            auth=auth,
            ex_foo="ex_foo",
            **kwargs,
        )
        self.assertEqual(self.node.id, node.id)
        self.assertEqual(self.driver.create_node.call_count, 1)

        call_kwargs = self.driver.create_node.call_args_list[0][1]
        expected_call_kwargs = {
            "name": "name",
            "image": "image",
            "size": "size",
            "auth": auth,
            "ex_foo": "ex_foo",
        }
        self.assertEqual(expected_call_kwargs, call_kwargs)

        # If driver throws an exception it should fall back to passing in all
        # the arguments
        global call_count
        call_count = 0

        def create_node(
            name,
            image,
            size,
            ex_custom_arg_1,
            ex_custom_arg_2,
            ex_foo=None,
            auth=None,
            **kwargs,
        ):
            global call_count

            call_count += 1
            if call_count == 1:
                msg = "create_node() takes at least 5 arguments (7 given)"
                raise TypeError(msg)
            return self.node

        self.driver.create_node = create_node

        node = self.driver.deploy_node(
            name="name",
            image="image",
            size="size",
            auth=auth,
            ex_foo="ex_foo",
            ex_custom_arg_1="a",
            ex_custom_arg_2="b",
            **kwargs,
        )
        self.assertEqual(self.node.id, node.id)
        self.assertEqual(call_count, 2)

        call_count = 0

        def create_node(
            name,
            image,
            size,
            ex_custom_arg_1,
            ex_custom_arg_2,
            ex_foo=None,
            auth=None,
            **kwargs,
        ):
            global call_count

            call_count += 1
            if call_count == 1:
                msg = "create_node() missing 3 required positional arguments"
                raise TypeError(msg)
            return self.node

        self.driver.create_node = create_node

        node = self.driver.deploy_node(
            name="name",
            image="image",
            size="size",
            auth=auth,
            ex_foo="ex_foo",
            ex_custom_arg_1="a",
            ex_custom_arg_2="b",
            **kwargs,
        )
        self.assertEqual(self.node.id, node.id)
        self.assertEqual(call_count, 2)

    @patch("libcloud.compute.base.SSHClient")
    @patch("libcloud.compute.ssh")
    def test_deploy_node_exception_run_deployment_script(
            self, mock_ssh_module, _):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node
        mock_ssh_module.have_paramiko = True

        deploy = Mock()
        deploy.run = Mock()
        deploy.run.side_effect = Exception("foo")

        try:
            self.driver.deploy_node(deploy=deploy)
        except DeploymentError as e:
            self.assertTrue(e.node.id, self.node.id)
        else:
            self.fail("Exception was not thrown")

    @patch("libcloud.compute.base.SSHClient")
    @patch("libcloud.compute.ssh")
    def test_deploy_node_exception_ssh_client_connect(self, mock_ssh_module,
                                                      ssh_client):
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        mock_ssh_module.have_paramiko = True

        deploy = Mock()
        ssh_client.side_effect = IOError("bar")

        try:
            self.driver.deploy_node(deploy=deploy)
        except DeploymentError as e:
            self.assertTrue(e.node.id, self.node.id)
        else:
            self.fail("Exception was not thrown")

    @patch("libcloud.compute.ssh")
    def test_deploy_node_depoy_node_not_implemented(self, mock_ssh_module):
        self.driver.features = {"create_node": []}
        mock_ssh_module.have_paramiko = True

        try:
            self.driver.deploy_node(deploy=Mock())
        except NotImplementedError:
            pass
        else:
            self.fail("Exception was not thrown")

        self.driver.features = {}

        try:
            self.driver.deploy_node(deploy=Mock())
        except NotImplementedError:
            pass
        else:
            self.fail("Exception was not thrown")

    @patch("libcloud.compute.base.SSHClient")
    @patch("libcloud.compute.ssh")
    def test_deploy_node_password_auth(self, mock_ssh_module, _):
        self.driver.features = {"create_node": ["password"]}
        mock_ssh_module.have_paramiko = True

        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        node = self.driver.deploy_node(deploy=Mock())
        self.assertEqual(self.node.id, node.id)

    @patch("libcloud.compute.base.SSHClient")
    @patch("libcloud.compute.ssh")
    def test_exception_is_thrown_is_paramiko_is_not_available(
            self, mock_ssh_module, _):
        self.driver.features = {"create_node": ["password"]}
        self.driver.create_node = Mock()
        self.driver.create_node.return_value = self.node

        mock_ssh_module.have_paramiko = False

        try:
            self.driver.deploy_node(deploy=Mock())
        except RuntimeError as e:
            self.assertTrue(str(e).find("paramiko is not installed") != -1)
        else:
            self.fail("Exception was not thrown")

        mock_ssh_module.have_paramiko = True
        node = self.driver.deploy_node(deploy=Mock())
        self.assertEqual(self.node.id, node.id)