# -*- coding: utf-8 -*- # Licensed to the Apache Software Foundation (ASF) under one or more§ # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys import time import unittest from libcloud.py3 import httplib from libcloud.py3 import u from libcloud.compute.deployment import MultiStepDeployment, Deployment from libcloud.compute.deployment import SSHKeyDeployment, ScriptDeployment from libcloud.compute.base import Node from libcloud.compute.types import NodeState, DeploymentError, LibcloudError from libcloud.compute.ssh import BaseSSHClient from libcloud.compute.drivers.rackspace import RackspaceNodeDriver as Rackspace from test import MockHttp, XML_HEADERS from test.file_fixtures import ComputeFileFixtures, OpenStackFixtures from mock import Mock, patch from test.secrets import RACKSPACE_PARAMS class MockDeployment(Deployment): def run(self, node, client): return node class MockClient(BaseSSHClient): def __init__(self, *args, **kwargs): self.stdout = '' self.stderr = '' self.exit_status = 0 def put(self, path, contents, chmod=755): return contents def run(self, name): return self.stdout, self.stderr, self.exit_status def delete(self, name): return True class DeploymentTests(unittest.TestCase): def setUp(self): Rackspace.connectionCls.conn_classes = (None, RackspaceMockHttp) RackspaceMockHttp.type = None self.driver = Rackspace(*RACKSPACE_PARAMS) 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) 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_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_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 = self.driver._wait_until_running(node=self.node, wait_period=1, timeout=10) self.assertEqual(self.node.uuid, node2.uuid) def test_wait_until_running_running_after_1_second(self): RackspaceMockHttp.type = '1_SECOND_DELAY' node2 = self.driver._wait_until_running(node=self.node, wait_period=1, timeout=10) self.assertEqual(self.node.uuid, node2.uuid) def test_wait_until_running_timeout(self): RackspaceMockHttp.type = 'TIMEOUT' try: self.driver._wait_until_running(node=self.node, wait_period=0.5, timeout=1) 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(node=self.node, wait_period=0.5, timeout=1) except LibcloudError: e = sys.exc_info()[1] self.assertTrue(e.value.find('is missing from list_nodes') != -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(node=self.node, wait_period=0.5, timeout=1) except LibcloudError: e = sys.exc_info()[1] self.assertTrue(e.value.find('multiple nodes have same UUID') != -1) else: self.fail('Exception was not thrown') 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=10) 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=1) 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 RackspaceMockHttp(MockHttp): fixtures = ComputeFileFixtures('openstack') auth_fixtures = OpenStackFixtures() def _v1_1__auth(self, method, url, body, headers): body = self.auth_fixtures.load('_v1_1__auth.json') return (httplib.OK, body, {'content-type': 'application/json; charset=UTF-8'}, httplib.responses[httplib.OK]) # fake auth token response def _v1_0(self, method, url, body, headers): headers = {'x-server-management-url': 'https://servers.api.rackspacecloud.com/v1.0/slug', 'x-auth-token': 'FE011C19-CF86-4F87-BE5D-9229145D7A06', 'x-cdn-management-url': 'https://cdn.clouddrive.com/v1/MossoCloudFS_FE011C19-CF86-4F87-BE5D-9229145D7A06', 'x-storage-token': 'FE011C19-CF86-4F87-BE5D-9229145D7A06', 'x-storage-url': 'https://storage4.clouddrive.com/v1/MossoCloudFS_FE011C19-CF86-4F87-BE5D-9229145D7A06'} return (httplib.NO_CONTENT, "", headers, httplib.responses[httplib.NO_CONTENT]) def _v1_0_slug_servers_detail(self, method, url, body, headers): body = self.fixtures.load('v1_slug_servers_detail_deployment_success.xml') return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK]) def _v1_0_slug_servers_detail_1_SECOND_DELAY(self, method, url, body, headers): time.sleep(1) body = self.fixtures.load('v1_slug_servers_detail_deployment_success.xml') return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK]) def _v1_0_slug_servers_detail_TIMEOUT(self, method, url, body, headers): body = self.fixtures.load('v1_slug_servers_detail_deployment_pending.xml') return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK]) def _v1_0_slug_servers_detail_MISSING(self, method, url, body, headers): body = self.fixtures.load('v1_slug_servers_detail_deployment_missing.xml') return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK]) def _v1_0_slug_servers_detail_SAME_UUID(self, method, url, body, headers): body = self.fixtures.load('v1_slug_servers_detail_deployment_same_uuid.xml') return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK]) if __name__ == '__main__': sys.exit(unittest.main())