New compute driver for UpCloud Signed-off-by: Quentin Pradet <[email protected]>
Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/abbd9bb6 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/abbd9bb6 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/abbd9bb6 Branch: refs/heads/trunk Commit: abbd9bb63f8b1295bc67fbcf8f271f89ed79e4d6 Parents: 5892fa1 Author: Mika Lackman <[email protected]> Authored: Mon Aug 14 14:03:29 2017 +0300 Committer: Quentin Pradet <[email protected]> Committed: Wed Sep 27 07:04:33 2017 +0400 ---------------------------------------------------------------------- libcloud/common/upcloud.py | 183 +++++++++ libcloud/compute/drivers/upcloud.py | 277 +++++++++++++ libcloud/compute/types.py | 2 + libcloud/test/common/test_upcloud.py | 212 ++++++++++ .../compute/fixtures/upcloud/api_1_2_plan.json | 38 ++ .../fixtures/upcloud/api_1_2_server.json | 22 + ...er_00893c98-5d5a-4363-b177-88df518a2b60.json | 58 +++ ...er_00f8c525-7e62-4108-8115-3958df5b43dc.json | 57 +++ ...525-7e62-4108-8115-3958df5b43dc_restart.json | 57 +++ .../upcloud/api_1_2_server_from_cdrom.json | 65 +++ .../upcloud/api_1_2_server_from_template.json | 59 +++ .../fixtures/upcloud/api_1_2_storage_cdrom.json | 411 +++++++++++++++++++ .../upcloud/api_1_2_storage_template.json | 114 +++++ .../compute/fixtures/upcloud/api_1_2_zone.json | 30 ++ .../upcloud/api_1_2_zone_failed_auth.json | 6 + libcloud/test/compute/test_upcloud.py | 248 +++++++++++ libcloud/test/secrets.py-dist | 1 + 17 files changed, 1840 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/common/upcloud.py ---------------------------------------------------------------------- diff --git a/libcloud/common/upcloud.py b/libcloud/common/upcloud.py new file mode 100644 index 0000000..432606a --- /dev/null +++ b/libcloud/common/upcloud.py @@ -0,0 +1,183 @@ +# 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 json +import time + +from libcloud.common.exceptions import BaseHTTPError + + +class UpcloudTimeoutException(Exception): + pass + + +class UpcloudCreateNodeRequestBody(object): + """Body of the create_node request + + Takes the create_node arguments (**kwargs) and constructs the request body + """ + def __init__(self, user_id, name, size, image, location, auth=None): + self.body = { + 'server': { + 'title': name, + 'hostname': 'localhost', + 'plan': size.id, + 'zone': location.id, + 'login_user': _LoginUser(user_id, auth).to_dict(), + 'storage_devices': _StorageDevice(image, size).to_dict() + } + } + + def to_json(self): + """Serializes the body to json""" + return json.dumps(self.body) + + +class UpcloudNodeDestroyer(object): + """Destroyes the node. + Node must be first stopped and then it can be + destroyed""" + + WAIT_AMOUNT = 2 + SLEEP_COUNT_TO_TIMEOUT = 20 + + def __init__(self, upcloud_node_operations, sleep_func=None): + self._operations = upcloud_node_operations + self._sleep_func = sleep_func or time.sleep + self._sleep_count = 0 + + def destroy_node(self, node_id): + self._stop_called = False + self._sleep_count = 0 + return self._do_destroy_node(node_id) + + def _do_destroy_node(self, node_id): + state = self._operations.node_state(node_id) + if state == 'stopped': + self._operations.destroy_node(node_id) + return True + elif state == 'error': + return False + elif state == 'started': + if not self._stop_called: + self._operations.stop_node(node_id) + self._stop_called = True + else: + # Waiting for started state to change and + # not calling stop again + self._sleep() + return self._do_destroy_node(node_id) + elif state == 'maintenance': + # Lets wait maintenace state to go away and retry destroy + self._sleep() + return self._do_destroy_node(node_id) + elif state is None: # Server not found any more + return True + + def _sleep(self): + if self._sleep_count > self.SLEEP_COUNT_TO_TIMEOUT: + raise UpcloudTimeoutException("Timeout, could not destroy node") + self._sleep_count += 1 + self._sleep_func(self.WAIT_AMOUNT) + + +class UpcloudNodeOperations(object): + + def __init__(self, connection): + self.connection = connection + + def stop_node(self, node_id): + body = { + 'stop_server': { + 'stop_type': 'hard' + } + } + self.connection.request('1.2/server/{0}/stop'.format(node_id), + method='POST', + data=json.dumps(body)) + + def node_state(self, node_id): + action = '1.2/server/{0}'.format(node_id) + try: + response = self.connection.request(action) + return response.object['server']['state'] + except BaseHTTPError as e: + if e.code == 404: + return None + raise + + def destroy_node(self, node_id): + self.connection.request('1.2/server/{0}'.format(node_id), + method='DELETE') + + +class _LoginUser(object): + + def __init__(self, user_id, auth=None): + self.user_id = user_id + self.auth = auth + + def to_dict(self): + login_user = {'username': self.user_id} + if self.auth is not None: + login_user['ssh_keys'] = { + 'ssh_key': [self.auth.pubkey] + } + else: + login_user['create_password'] = 'yes' + + return login_user + + +class _StorageDevice(object): + + def __init__(self, image, size): + self.image = image + self.size = size + + def to_dict(self): + extra = self.image.extra + if extra['type'] == 'template': + return self._storage_device_for_template_image() + elif extra['type'] == 'cdrom': + return self._storage_device_for_cdrom_image() + + def _storage_device_for_template_image(self): + storage_devices = { + 'storage_device': [{ + 'action': 'clone', + 'title': self.image.name, + 'storage': self.image.id + }] + } + return storage_devices + + def _storage_device_for_cdrom_image(self): + storage_devices = { + 'storage_device': [ + { + 'action': 'create', + 'title': self.image.name, + 'size': self.size.disk, + 'tier': self.size.extra['storage_tier'] + + }, + { + 'action': 'attach', + 'storage': self.image.id, + 'type': 'cdrom' + } + ] + } + return storage_devices http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/compute/drivers/upcloud.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/upcloud.py b/libcloud/compute/drivers/upcloud.py new file mode 100644 index 0000000..b9daf84 --- /dev/null +++ b/libcloud/compute/drivers/upcloud.py @@ -0,0 +1,277 @@ +# 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. +""" +Upcloud node driver +""" +import base64 +import json + +from libcloud.utils.py3 import httplib, b +from libcloud.compute.base import NodeDriver, NodeLocation, NodeSize +from libcloud.compute.base import NodeImage, Node, NodeState +from libcloud.compute.types import Provider +from libcloud.common.base import ConnectionUserAndKey, JsonResponse +from libcloud.common.types import InvalidCredsError +from libcloud.common.upcloud import UpcloudCreateNodeRequestBody +from libcloud.common.upcloud import UpcloudNodeDestroyer +from libcloud.common.upcloud import UpcloudNodeOperations + +SERVER_STATE = { + 'started': NodeState.RUNNING, + 'stopped': NodeState.STOPPED, + 'maintenance': NodeState.RECONFIGURING, + 'error': NodeState.ERROR +} + + +class UpcloudResponse(JsonResponse): + """Response class for UpcloudDriver""" + + def success(self): + if self.status == httplib.NO_CONTENT: + return True + return super(UpcloudResponse, self).success() + + def parse_error(self): + data = self.parse_body() + if self.status == httplib.UNAUTHORIZED: + raise InvalidCredsError(value=data['error']['error_message']) + return data + + +class UpcloudConnection(ConnectionUserAndKey): + """Connection class for UpcloudDriver""" + host = 'api.upcloud.com' + responseCls = UpcloudResponse + + def add_default_headers(self, headers): + """Adds headers that are needed for all requests""" + headers['Authorization'] = self._basic_auth() + headers['Accept'] = 'application/json' + headers['Content-Type'] = 'application/json' + return headers + + def _basic_auth(self): + """Constructs basic auth header content string""" + credentials = b("{0}:{1}".format(self.user_id, self.key)) + credentials = base64.b64encode(credentials) + return 'Basic {0}'.format(credentials.decode('ascii')) + + +class UpcloudDriver(NodeDriver): + """Upcloud node driver + + :keyword username: Username required for authentication + :type username: ``str`` + + :keyword password: Password required for authentication + :type password: ``str`` + """ + type = Provider.UPCLOUD + name = 'Upcloud' + website = 'https://www.upcloud.com' + connectionCls = UpcloudConnection + features = {'create_node': ['ssh_key', 'generates_password']} + + def __init__(self, username, password, **kwargs): + super(UpcloudDriver, self).__init__(key=username, secret=password, + **kwargs) + + def list_locations(self): + """ + List available locations for deployment + + :rtype: ``list`` of :class:`NodeLocation` + """ + response = self.connection.request('1.2/zone') + return self._to_node_locations(response.object['zones']['zone']) + + def list_sizes(self): + """ + List available plans + + :rtype: ``list`` of :class:`NodeSize` + """ + response = self.connection.request('1.2/plan') + return self._to_node_sizes(response.object['plans']['plan']) + + def list_images(self): + """ + List available distributions. + + :rtype: ``list`` of :class:`NodeImage` + """ + response = self.connection.request('1.2/storage/template') + obj = response.object + response = self.connection.request('1.2/storage/cdrom') + storage = response.object['storages']['storage'] + obj['storages']['storage'].extend(storage) + return self._to_node_images(obj['storages']['storage']) + + def create_node(self, name, size, image, location, auth=None, **kwargs): + """ + Creates instance to upcloud. + + If auth is not given then password will be generated. + + :param name: String with a name for this new node (required) + :type name: ``str`` + + :param size: The size of resources allocated to this node. + (required) + :type size: :class:`.NodeSize` + + :param image: OS Image to boot on node. (required) + :type image: :class:`.NodeImage` + + :param location: Which data center to create a node in. If empty, + undefined behavior will be selected. (optional) + :type location: :class:`.NodeLocation` + + :param auth: Initial authentication information for the node + (optional) + :type auth: :class:`.NodeAuthSSHKey` + + :return: The newly created node. + :rtype: :class:`.Node` + """ + body = UpcloudCreateNodeRequestBody(user_id=self.connection.user_id, + name=name, size=size, image=image, + location=location, auth=auth) + response = self.connection.request('1.2/server', + method='POST', + data=body.to_json()) + server = response.object['server'] + # Upcloud server's are in maintenace state when goind + # from state to other, it is safe to assume STARTING state + return self._to_node(server, state=NodeState.STARTING) + + def list_nodes(self): + """ + List nodes + + :return: List of node objects + :rtype: ``list`` of :class:`Node` + """ + servers = [] + for nid in self._node_ids(): + response = self.connection.request('1.2/server/{0}'.format(nid)) + servers.append(response.object['server']) + return self._to_nodes(servers) + + def reboot_node(self, node): + """ + Reboot the given node + + :param node: the node to reboot + :type node: :class:`Node` + + :rtype: ``bool`` + """ + body = { + 'restart_server': { + 'stop_type': 'hard' + } + } + self.connection.request('1.2/server/{0}/restart'.format(node.id), + method='POST', + data=json.dumps(body)) + return True + + def destroy_node(self, node): + """ + Destroy the given node + + The disk resources, attached to node, will not be removed. + + :param node: the node to destroy + :type node: :class:`Node` + + :rtype: ``bool`` + """ + + operations = UpcloudNodeOperations(self.connection) + destroyer = UpcloudNodeDestroyer(operations) + return destroyer.destroy_node(node.id) + + def _node_ids(self): + """Returns list of server uids currently on upcloud""" + response = self.connection.request('1.2/server') + servers = response.object['servers']['server'] + return [server['uuid'] for server in servers] + + def _to_nodes(self, servers): + return [self._to_node(server) for server in servers] + + def _to_node(self, server, state=None): + ip_addresses = server['ip_addresses']['ip_address'] + public_ips = [ip['address'] for ip in ip_addresses + if ip['access'] == 'public'] + private_ips = [ip['address'] for ip in ip_addresses + if ip['access'] == 'private'] + + extra = {'vnc_password': server['vnc_password']} + if 'password' in server: + extra['password'] = server['password'] + return Node(id=server['uuid'], + name=server['title'], + state=state or SERVER_STATE[server['state']], + public_ips=public_ips, + private_ips=private_ips, + driver=self, + extra=extra) + + def _to_node_locations(self, zones): + return [self._construct_node_location(zone) for zone in zones] + + def _construct_node_location(self, zone): + return NodeLocation(id=zone['id'], + name=zone['description'], + country=self._parse_country(zone['id']), + driver=self) + + def _parse_country(self, zone_id): + """Parses the country information out of zone_id. + Zone_id format [country]_[city][number], like fi_hel1""" + return zone_id.split('-')[0].upper() + + def _to_node_sizes(self, plans): + return [self._construct_node_size(plan) for plan in plans] + + def _construct_node_size(self, plan): + extra = self._copy_dict(('core_number', 'storage_tier'), plan) + return NodeSize(id=plan['name'], name=plan['name'], + ram=plan['memory_amount'], + disk=plan['storage_size'], + bandwidth=plan['public_traffic_out'], + price=None, driver=self, + extra=extra) + + def _to_node_images(self, images): + return [self._construct_node_image(image) for image in images] + + def _construct_node_image(self, image): + extra = self._copy_dict(('access', 'license', + 'size', 'state', 'type'), image) + return NodeImage(id=image['uuid'], + name=image['title'], + driver=self, + extra=extra) + + def _copy_dict(self, keys, d): + extra = {} + for key in keys: + extra[key] = d[key] + return extra http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/compute/types.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/types.py b/libcloud/compute/types.py index deae506..b360c0e 100644 --- a/libcloud/compute/types.py +++ b/libcloud/compute/types.py @@ -98,6 +98,7 @@ class Provider(Type): :cvar RACKSPACE_FIRST_GEN: Rackspace First Gen Cloud Servers :cvar RIMUHOSTING: RimuHosting.com :cvar TERREMARK: Terremark + :cvar UPCLOUD: Upcloud :cvar VCL: VCL driver :cvar VCLOUD: vmware vCloud :cvar VPSNET: VPS.net @@ -161,6 +162,7 @@ class Provider(Type): SKALICLOUD = 'skalicloud' SOFTLAYER = 'softlayer' TERREMARK = 'terremark' + UPCLOUD = 'upcloud' VCL = 'vcl' VCLOUD = 'vcloud' VOXEL = 'voxel' http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/common/test_upcloud.py ---------------------------------------------------------------------- diff --git a/libcloud/test/common/test_upcloud.py b/libcloud/test/common/test_upcloud.py new file mode 100644 index 0000000..76dd9c1 --- /dev/null +++ b/libcloud/test/common/test_upcloud.py @@ -0,0 +1,212 @@ +# 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 json + +from mock import Mock, call + +from libcloud.common.upcloud import UpcloudCreateNodeRequestBody, UpcloudNodeDestroyer, UpcloudNodeOperations +from libcloud.common.upcloud import UpcloudTimeoutException +from libcloud.compute.base import NodeImage, NodeSize, NodeLocation, NodeAuthSSHKey +from libcloud.test import unittest + + +class TestUpcloudCreateNodeRequestBody(unittest.TestCase): + + def test_creating_node_from_template_image(self): + image = NodeImage(id='01000000-0000-4000-8000-000030060200', + name='Ubuntu Server 16.04 LTS (Xenial Xerus)', + driver='', + extra={'type': 'template'}) + location = NodeLocation(id='fi-hel1', name='Helsinki #1', country='FI', driver='') + size = NodeSize(id='1xCPU-1GB', name='1xCPU-1GB', ram=1024, disk=30, bandwidth=2048, + extra={'core_number': 1, 'storage_tier': 'maxiops'}, price=None, driver='') + + body = UpcloudCreateNodeRequestBody(user_id='somename', name='ts', image=image, location=location, size=size) + json_body = body.to_json() + dict_body = json.loads(json_body) + expected_body = { + 'server': { + 'title': 'ts', + 'hostname': 'localhost', + 'plan': '1xCPU-1GB', + 'zone': 'fi-hel1', + 'login_user': {'username': 'somename', + 'create_password': 'yes'}, + 'storage_devices': { + 'storage_device': [{ + 'action': 'clone', + 'title': 'Ubuntu Server 16.04 LTS (Xenial Xerus)', + 'storage': '01000000-0000-4000-8000-000030060200' + }] + }, + } + } + self.assertDictEqual(expected_body, dict_body) + + def test_creating_node_from_cdrom_image(self): + image = NodeImage(id='01000000-0000-4000-8000-000030060200', + name='Ubuntu Server 16.04 LTS (Xenial Xerus)', + driver='', + extra={'type': 'cdrom'}) + location = NodeLocation(id='fi-hel1', name='Helsinki #1', country='FI', driver='') + size = NodeSize(id='1xCPU-1GB', name='1xCPU-1GB', ram=1024, disk=30, bandwidth=2048, + extra={'core_number': 1, 'storage_tier': 'maxiops'}, price=None, driver='') + + body = UpcloudCreateNodeRequestBody(user_id='somename', name='ts', image=image, location=location, size=size) + json_body = body.to_json() + dict_body = json.loads(json_body) + expected_body = { + 'server': { + 'title': 'ts', + 'hostname': 'localhost', + 'plan': '1xCPU-1GB', + 'zone': 'fi-hel1', + 'login_user': {'username': 'somename', + 'create_password': 'yes'}, + 'storage_devices': { + 'storage_device': [ + { + 'action': 'create', + 'size': 30, + 'tier': 'maxiops', + 'title': 'Ubuntu Server 16.04 LTS (Xenial Xerus)', + }, + { + 'action': 'attach', + 'storage': '01000000-0000-4000-8000-000030060200', + 'type': 'cdrom' + } + ] + } + } + } + self.assertDictEqual(expected_body, dict_body) + + def test_creating_node_using_ssh_keys(self): + image = NodeImage(id='01000000-0000-4000-8000-000030060200', + name='Ubuntu Server 16.04 LTS (Xenial Xerus)', + driver='', + extra={'type': 'template'}) + location = NodeLocation(id='fi-hel1', name='Helsinki #1', country='FI', driver='') + size = NodeSize(id='1xCPU-1GB', name='1xCPU-1GB', ram=1024, disk=30, bandwidth=2048, + extra={'core_number': 1, 'storage_tier': 'maxiops'}, price=None, driver='') + auth = NodeAuthSSHKey('sshkey') + + body = UpcloudCreateNodeRequestBody(user_id='somename', name='ts', image=image, location=location, size=size, auth=auth) + json_body = body.to_json() + dict_body = json.loads(json_body) + expected_body = { + 'server': { + 'title': 'ts', + 'hostname': 'localhost', + 'plan': '1xCPU-1GB', + 'zone': 'fi-hel1', + 'login_user': { + 'username': 'somename', + 'ssh_keys': { + 'ssh_key': [ + 'sshkey' + ] + }, + }, + 'storage_devices': { + 'storage_device': [{ + 'action': 'clone', + 'title': 'Ubuntu Server 16.04 LTS (Xenial Xerus)', + 'storage': '01000000-0000-4000-8000-000030060200' + }] + }, + } + } + self.assertDictEqual(expected_body, dict_body) + + +class TestUpcloudNodeDestroyer(unittest.TestCase): + + def setUp(self): + self.mock_sleep = Mock() + self.mock_operations = Mock(spec=UpcloudNodeOperations) + self.destroyer = UpcloudNodeDestroyer(self.mock_operations, sleep_func=self.mock_sleep) + + def test_node_already_in_stopped_state(self): + self.mock_operations.node_state.side_effect = ['stopped'] + + self.assertTrue(self.destroyer.destroy_node(1)) + + self.assertTrue(self.mock_operations.stop_node.call_count == 0) + self.mock_operations.destroy_node.assert_called_once_with(1) + + def test_node_in_error_state(self): + self.mock_operations.node_state.side_effect = ['error'] + + self.assertFalse(self.destroyer.destroy_node(1)) + + self.assertTrue(self.mock_operations.stop_node.call_count == 0) + self.assertTrue(self.mock_operations.destroy_node.call_count == 0) + + def test_node_in_started_state(self): + self.mock_operations.node_state.side_effect = ['started', 'stopped'] + + self.assertTrue(self.destroyer.destroy_node(1)) + + self.mock_operations.stop_node.assert_called_once_with(1) + self.mock_operations.destroy_node.assert_called_once_with(1) + + def test_node_in_maintenace_state(self): + self.mock_operations.node_state.side_effect = ['maintenance', 'maintenance', None] + + self.assertTrue(self.destroyer.destroy_node(1)) + + self.mock_sleep.assert_has_calls([call(self.destroyer.WAIT_AMOUNT), call(self.destroyer.WAIT_AMOUNT)]) + + self.assertTrue(self.mock_operations.stop_node.call_count == 0) + self.assertTrue(self.mock_operations.destroy_node.call_count == 0) + + def test_node_statys_in_started_state_for_awhile(self): + self.mock_operations.node_state.side_effect = ['started', 'started', 'stopped'] + + self.assertTrue(self.destroyer.destroy_node(1)) + + # Only one all for stop should be done + self.mock_operations.stop_node.assert_called_once_with(1) + self.mock_sleep.assert_has_calls([call(self.destroyer.WAIT_AMOUNT)]) + self.mock_operations.destroy_node.assert_called_once_with(1) + + def test_reuse(self): + "Verify that internal flag self.destroyer._stop_node is handled properly" + self.mock_operations.node_state.side_effect = ['started', 'stopped', 'started', 'stopped'] + self.assertTrue(self.destroyer.destroy_node(1)) + self.assertTrue(self.destroyer.destroy_node(1)) + + self.assertEquals(self.mock_sleep.call_count, 0) + self.assertEquals(self.mock_operations.stop_node.call_count, 2) + + def test_timeout(self): + self.mock_operations.node_state.side_effect = ['maintenance'] * 50 + + self.assertRaises(UpcloudTimeoutException, self.destroyer.destroy_node, 1) + + def test_timeout_reuse(self): + "Verify sleep count is handled properly" + self.mock_operations.node_state.side_effect = ['maintenance'] * 50 + self.assertRaises(UpcloudTimeoutException, self.destroyer.destroy_node, 1) + + self.mock_operations.node_state.side_effect = ['maintenance', None] + self.assertTrue(self.destroyer.destroy_node(1)) + + +if __name__ == '__main__': + sys.exit(unittest.main()) http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/compute/fixtures/upcloud/api_1_2_plan.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/upcloud/api_1_2_plan.json b/libcloud/test/compute/fixtures/upcloud/api_1_2_plan.json new file mode 100644 index 0000000..c59dcf0 --- /dev/null +++ b/libcloud/test/compute/fixtures/upcloud/api_1_2_plan.json @@ -0,0 +1,38 @@ +{ + "plans" : { + "plan" : [ + { + "core_number" : 1, + "memory_amount" : 1024, + "name" : "1xCPU-1GB", + "public_traffic_out" : 2048, + "storage_size" : 30, + "storage_tier" : "maxiops" + }, + { + "core_number" : 2, + "memory_amount" : 2048, + "name" : "2xCPU-2GB", + "public_traffic_out" : 3072, + "storage_size" : 50, + "storage_tier" : "maxiops" + }, + { + "core_number" : 4, + "memory_amount" : 4096, + "name" : "4xCPU-4GB", + "public_traffic_out" : 4096, + "storage_size" : 100, + "storage_tier" : "maxiops" + }, + { + "core_number" : 6, + "memory_amount" : 8192, + "name" : "6xCPU-8GB", + "public_traffic_out" : 8192, + "storage_size" : 200, + "storage_tier" : "maxiops" + } + ] + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/compute/fixtures/upcloud/api_1_2_server.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/upcloud/api_1_2_server.json b/libcloud/test/compute/fixtures/upcloud/api_1_2_server.json new file mode 100644 index 0000000..e2a7d0a --- /dev/null +++ b/libcloud/test/compute/fixtures/upcloud/api_1_2_server.json @@ -0,0 +1,22 @@ +{ + "servers" : { + "server" : [ + { + "core_number" : "1", + "hostname" : "localhost", + "license" : 0, + "memory_amount" : "1024", + "plan" : "1xCPU-1GB", + "plan_ipv4_bytes" : "12267", + "plan_ipv6_bytes" : "4644", + "state" : "started", + "tags" : { + "tag" : [] + }, + "title" : "test_server", + "uuid" : "00f8c525-7e62-4108-8115-3958df5b43dc", + "zone" : "fi-hel1" + } + ] + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/compute/fixtures/upcloud/api_1_2_server_00893c98-5d5a-4363-b177-88df518a2b60.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/upcloud/api_1_2_server_00893c98-5d5a-4363-b177-88df518a2b60.json b/libcloud/test/compute/fixtures/upcloud/api_1_2_server_00893c98-5d5a-4363-b177-88df518a2b60.json new file mode 100644 index 0000000..c99fc05 --- /dev/null +++ b/libcloud/test/compute/fixtures/upcloud/api_1_2_server_00893c98-5d5a-4363-b177-88df518a2b60.json @@ -0,0 +1,58 @@ +{ + "server" : { + "boot_order" : "cdrom,disk", + "core_number" : "1", + "firewall" : "off", + "host" : 4297867907, + "hostname" : "localhost", + "ip_addresses" : { + "ip_address" : [ + { + "access" : "private", + "address" : "10.2.1.244", + "family" : "IPv4" + }, + { + "access" : "public", + "address" : "2a04:3541:1000:500:7cae:1dff:fead:5bde", + "family" : "IPv6" + }, + { + "access" : "public", + "address" : "83.136.254.34", + "family" : "IPv4", + "part_of_plan" : "yes" + } + ] + }, + "license" : 0, + "memory_amount" : "1024", + "nic_model" : "virtio", + "plan" : "1xCPU-1GB", + "plan_ipv4_bytes" : "0", + "plan_ipv6_bytes" : "0", + "state" : "stopped", + "storage_devices" : { + "storage_device" : [ + { + "address" : "virtio:0", + "part_of_plan" : "yes", + "storage" : "01839922-a675-4214-b10e-a4cf7953992f", + "storage_size" : 30, + "storage_title" : "localhost-disk0", + "type" : "disk" + } + ] + }, + "tags" : { + "tag" : [] + }, + "timezone" : "UTC", + "title" : "test", + "uuid" : "00893c98-5d5a-4363-b177-88df518a2b60", + "video_model" : "cirrus", + "vnc" : "off", + "vnc_password" : "Hf7qpJs8", + "zone" : "uk-lon1" + } +} http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/compute/fixtures/upcloud/api_1_2_server_00f8c525-7e62-4108-8115-3958df5b43dc.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/upcloud/api_1_2_server_00f8c525-7e62-4108-8115-3958df5b43dc.json b/libcloud/test/compute/fixtures/upcloud/api_1_2_server_00f8c525-7e62-4108-8115-3958df5b43dc.json new file mode 100644 index 0000000..1278e6e --- /dev/null +++ b/libcloud/test/compute/fixtures/upcloud/api_1_2_server_00f8c525-7e62-4108-8115-3958df5b43dc.json @@ -0,0 +1,57 @@ +{ + "server" : { + "boot_order" : "disk", + "core_number" : "1", + "firewall" : "off", + "host" : 4964762243, + "hostname" : "localhost", + "ip_addresses" : { + "ip_address" : [ + { + "access" : "private", + "address" : "10.1.7.68", + "family" : "IPv4" + }, + { + "access" : "public", + "address" : "2a04:3540:1000:310:7cae:1dff:fead:19dc", + "family" : "IPv6" + }, + { + "access" : "public", + "address" : "94.237.37.249", + "family" : "IPv4", + "part_of_plan" : "yes" + } + ] + }, + "license" : 0, + "memory_amount" : "1024", + "nic_model" : "virtio", + "plan" : "1xCPU-1GB", + "plan_ipv4_bytes" : "8242", + "plan_ipv6_bytes" : "3440", + "state" : "started", + "storage_devices" : { + "storage_device" : [ + { + "address" : "virtio:0", + "storage" : "01e5411f-37c9-4b8e-b0bb-4cad5119b3ea", + "storage_size" : 10, + "storage_title" : "Ubuntu Server 16.04 LTS (Xenial Xerus)", + "type" : "disk" + } + ] + }, + "tags" : { + "tag" : [] + }, + "timezone" : "UTC", + "title" : "test_server", + "uuid" : "00f8c525-7e62-4108-8115-3958df5b43dc", + "video_model" : "cirrus", + "vnc" : "off", + "vnc_password" : "r362XMmV", + "zone" : "fi-hel1" + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/compute/fixtures/upcloud/api_1_2_server_00f8c525-7e62-4108-8115-3958df5b43dc_restart.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/upcloud/api_1_2_server_00f8c525-7e62-4108-8115-3958df5b43dc_restart.json b/libcloud/test/compute/fixtures/upcloud/api_1_2_server_00f8c525-7e62-4108-8115-3958df5b43dc_restart.json new file mode 100644 index 0000000..af904e4 --- /dev/null +++ b/libcloud/test/compute/fixtures/upcloud/api_1_2_server_00f8c525-7e62-4108-8115-3958df5b43dc_restart.json @@ -0,0 +1,57 @@ +{ + "server" : { + "boot_order" : "disk", + "core_number" : "1", + "firewall" : "off", + "host" : 4964762243, + "hostname" : "localhost", + "ip_addresses" : { + "ip_address" : [ + { + "access" : "private", + "address" : "10.1.7.68", + "family" : "IPv4" + }, + { + "access" : "public", + "address" : "2a04:3540:1000:310:7cae:1dff:fead:19dc", + "family" : "IPv6" + }, + { + "access" : "public", + "address" : "94.237.37.249", + "family" : "IPv4", + "part_of_plan" : "yes" + } + ] + }, + "license" : 0, + "memory_amount" : "1024", + "nic_model" : "virtio", + "plan" : "1xCPU-1GB", + "plan_ipv4_bytes" : "218066", + "plan_ipv6_bytes" : "6450", + "state" : "started", + "storage_devices" : { + "storage_device" : [ + { + "address" : "virtio:0", + "storage" : "01e5411f-37c9-4b8e-b0bb-4cad5119b3ea", + "storage_size" : 10, + "storage_title" : "Ubuntu Server 16.04 LTS (Xenial Xerus)", + "type" : "disk" + } + ] + }, + "tags" : { + "tag" : [] + }, + "timezone" : "UTC", + "title" : "test_server", + "uuid" : "00f8c525-7e62-4108-8115-3958df5b43dc", + "video_model" : "cirrus", + "vnc" : "off", + "vnc_password" : "r362XMmV", + "zone" : "fi-hel1" + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/compute/fixtures/upcloud/api_1_2_server_from_cdrom.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/upcloud/api_1_2_server_from_cdrom.json b/libcloud/test/compute/fixtures/upcloud/api_1_2_server_from_cdrom.json new file mode 100644 index 0000000..b82f8c6 --- /dev/null +++ b/libcloud/test/compute/fixtures/upcloud/api_1_2_server_from_cdrom.json @@ -0,0 +1,65 @@ +{ + "server" : { + "boot_order" : "disk", + "core_number" : "1", + "firewall" : "off", + "hostname" : "localhost", + "ip_addresses" : { + "ip_address" : [ + { + "access" : "private", + "address" : "10.1.2.26", + "family" : "IPv4" + }, + { + "access" : "public", + "address" : "2a04:3540:1000:310:7cae:1dff:fead:5683", + "family" : "IPv6" + }, + { + "access" : "public", + "address" : "94.237.37.215", + "family" : "IPv4", + "part_of_plan" : "yes" + } + ] + }, + "license" : 0, + "memory_amount" : "1024", + "nic_model" : "virtio", + "plan" : "1xCPU-1GB", + "plan_ipv4_bytes" : "0", + "plan_ipv6_bytes" : "0", + "progress" : "0", + "state" : "maintenance", + "storage_devices" : { + "storage_device" : [ + { + "address" : "virtio:0", + "part_of_plan" : "yes", + "storage" : "01898fe2-9909-471e-b33c-59d7896b48f5", + "storage_size" : 30, + "storage_title" : "Ubuntu Server 16.04 LTS (Xenial Xerus), 64-bit", + "type" : "disk" + }, + { + "address" : "ide:0:0", + "storage" : "01000000-0000-4000-8000-000030040101", + "storage_size" : 1, + "storage_title" : "Ubuntu Server 14.04 LTS", + "type" : "cdrom" + } + ] + }, + "tags" : { + "tag" : [] + }, + "timezone" : "UTC", + "title" : "test_server", + "uuid" : "00c68ee6-e6e1-4d5f-a213-4a1e063a3cbd", + "video_model" : "cirrus", + "vnc" : "off", + "vnc_password" : "5C4TVPf8", + "zone" : "fi-hel1" + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/compute/fixtures/upcloud/api_1_2_server_from_template.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/upcloud/api_1_2_server_from_template.json b/libcloud/test/compute/fixtures/upcloud/api_1_2_server_from_template.json new file mode 100644 index 0000000..cde80cc --- /dev/null +++ b/libcloud/test/compute/fixtures/upcloud/api_1_2_server_from_template.json @@ -0,0 +1,59 @@ +{ + "server" : { + "boot_order" : "disk", + "core_number" : "1", + "firewall" : "off", + "hostname" : "localhost", + "ip_addresses" : { + "ip_address" : [ + { + "access" : "private", + "address" : "10.1.3.163", + "family" : "IPv4" + }, + { + "access" : "public", + "address" : "2a04:3540:1000:310:7cae:1dff:fead:3332", + "family" : "IPv6" + }, + { + "access" : "public", + "address" : "94.237.37.216", + "family" : "IPv4", + "part_of_plan" : "yes" + } + ] + }, + "license" : 0, + "memory_amount" : "1024", + "nic_model" : "virtio", + "password" : "777gznbm", + "plan" : "1xCPU-1GB", + "plan_ipv4_bytes" : "0", + "plan_ipv6_bytes" : "0", + "progress" : "0", + "state" : "maintenance", + "storage_devices" : { + "storage_device" : [ + { + "address" : "virtio:0", + "storage" : "018e2c82-1c16-46b9-8b7d-aeaf8d8309a9", + "storage_size" : 10, + "storage_title" : "Ubuntu Server 16.04 LTS (Xenial Xerus)", + "type" : "disk" + } + ] + }, + "tags" : { + "tag" : [] + }, + "timezone" : "UTC", + "title" : "test_server", + "username" : "mlackman", + "uuid" : "00814aac-240f-4f08-9139-9697c9ffc0b7", + "video_model" : "cirrus", + "vnc" : "off", + "vnc_password" : "G4FvMjvg", + "zone" : "fi-hel1" + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/compute/fixtures/upcloud/api_1_2_storage_cdrom.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/upcloud/api_1_2_storage_cdrom.json b/libcloud/test/compute/fixtures/upcloud/api_1_2_storage_cdrom.json new file mode 100644 index 0000000..5f45a2b --- /dev/null +++ b/libcloud/test/compute/fixtures/upcloud/api_1_2_storage_cdrom.json @@ -0,0 +1,411 @@ +{ + "storages" : { + "storage" : [ + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Windows Server 2003 R2 Standard (CD 1)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000010010101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Windows Server 2003 R2 Standard (CD 2)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000010010102" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Windows Server 2003 R2 Standard (CD 1)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000010010201" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Windows Server 2003 R2 Standard (CD 2)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000010010202" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Windows Server 2003 R2 Enterprise (CD 1)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000010020101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Windows Server 2003 R2 Enterprise (CD 2)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000010020102" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Windows Server 2003 R2 Enterprise (CD 1)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000010020201" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Windows Server 2003 R2 Enterprise (CD 2)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000010020202" + }, + { + "access" : "public", + "license" : 0, + "size" : 3, + "state" : "online", + "title" : "Windows Server 2008 R2 Datacenter", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000010030101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Windows Server 2003 R2 Datacenter", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000010040101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Windows Server 2003 R2 Datacenter", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000010040201" + }, + { + "access" : "public", + "license" : 0, + "size" : 4, + "state" : "online", + "title" : "Windows Server 2012", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000010050101" + }, + { + "access" : "public", + "license" : 0, + "size" : 5, + "state" : "online", + "title" : "Debian GNU/Linux 6.0.1 (Squeeze) (DVD)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000020010101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Debian GNU/Linux 6.0.1 (Squeeze) (netinst)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000020010201" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Debian GNU/Linux 6.0.1 (Squeeze) (netinst)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000020010301" + }, + { + "access" : "public", + "license" : 0, + "size" : 2, + "state" : "online", + "title" : "Debian GNU/Linux 6.0.1 (Squeeze) (live)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000020010401" + }, + { + "access" : "public", + "license" : 0, + "size" : 2, + "state" : "online", + "title" : "Debian GNU/Linux 6.0.1 (Squeeze) (live)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000020010501" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Debian GNU/Linux 7.8 (Wheezy)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000020020201" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Debian GNU/Linux 8.6.0 (Jessie)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000020030101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Debian GNU/Linux 9.0.0 (Stretch) Installation CD", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000020040101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Ubuntu Server 10.04", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000030010101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Ubuntu Server 10.04", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000030010201" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Ubuntu Server 11.04", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000030020101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Ubuntu Server 11.04", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000030020201" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Ubuntu Server 12.04", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000030030101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Ubuntu Server 14.04 LTS", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000030040101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Ubuntu Server 16.04 LTS (Xenial Xerus), 64-bit", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000030060101" + }, + { + "access" : "public", + "license" : 0, + "size" : 4, + "state" : "online", + "title" : "Fedora 16 (DVD)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000040010101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Fedora 16 (live)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000040010201" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Fedora 19", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000040020101" + }, + { + "access" : "public", + "license" : 0, + "size" : 4, + "state" : "online", + "title" : "CentOS 6.0 (DVD 1)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000050010101" + }, + { + "access" : "public", + "license" : 0, + "size" : 2, + "state" : "online", + "title" : "CentOS 6.0 (DVD 2)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000050010102" + }, + { + "access" : "public", + "license" : 0, + "size" : 4, + "state" : "online", + "title" : "CentOS 6.8 (DVD 1)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000050010103" + }, + { + "access" : "public", + "license" : 0, + "size" : 3, + "state" : "online", + "title" : "CentOS 6.8 (DVD 2)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000050010104" + }, + { + "access" : "public", + "license" : 0, + "size" : 4, + "state" : "online", + "title" : "CentOS 6.8 (DVD 1)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000050010105" + }, + { + "access" : "public", + "license" : 0, + "size" : 2, + "state" : "online", + "title" : "CentOS 6.8 (DVD 2)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000050010106" + }, + { + "access" : "public", + "license" : 0, + "size" : 5, + "state" : "online", + "title" : "CentOS 7.3-1611 DVD", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000050010301" + }, + { + "access" : "public", + "license" : 0, + "size" : 4, + "state" : "online", + "title" : "Knoppix 6.4.4", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000060010101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Arch Linux 2010.05", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000070010101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "CoreOS 607.0.0", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000080010101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "CoreOS 845.0.0", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000080010201" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "CoreOS Alpha (1032.1.0)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000080010501" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "CoreOS Stable (1068.8.0)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000080020101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "Gentoo Linux Minimal Installation CD (64bit)", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000090010101" + }, + { + "access" : "public", + "license" : 0, + "size" : 1, + "state" : "online", + "title" : "FreeBSD 11.0-RELEASE amd64 Installation CD", + "type" : "cdrom", + "uuid" : "01000000-0000-4000-8000-000100010101" + } + ] + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/compute/fixtures/upcloud/api_1_2_storage_template.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/upcloud/api_1_2_storage_template.json b/libcloud/test/compute/fixtures/upcloud/api_1_2_storage_template.json new file mode 100644 index 0000000..12f42c2 --- /dev/null +++ b/libcloud/test/compute/fixtures/upcloud/api_1_2_storage_template.json @@ -0,0 +1,114 @@ +{ + "storages" : { + "storage" : [ + { + "access" : "public", + "license" : 3.36, + "size" : 20, + "state" : "online", + "title" : "Windows Server 2012 R2 Datacenter", + "type" : "template", + "uuid" : "01000000-0000-4000-8000-000010050200" + }, + { + "access" : "public", + "license" : 0.694, + "size" : 20, + "state" : "online", + "title" : "Windows Server 2012 R2 Standard", + "type" : "template", + "uuid" : "01000000-0000-4000-8000-000010050300" + }, + { + "access" : "public", + "license" : 3.36, + "size" : 20, + "state" : "online", + "title" : "Windows Server 2016 Datacenter", + "type" : "template", + "uuid" : "01000000-0000-4000-8000-000010060200" + }, + { + "access" : "public", + "license" : 0.694, + "size" : 20, + "state" : "online", + "title" : "Windows Server 2016 Standard", + "type" : "template", + "uuid" : "01000000-0000-4000-8000-000010060300" + }, + { + "access" : "public", + "license" : 0, + "size" : 2, + "state" : "online", + "title" : "Debian GNU/Linux 7.8 (Wheezy)", + "type" : "template", + "uuid" : "01000000-0000-4000-8000-000020020100" + }, + { + "access" : "public", + "license" : 0, + "size" : 2, + "state" : "online", + "title" : "Debian GNU/Linux 8.7 (Jessie)", + "type" : "template", + "uuid" : "01000000-0000-4000-8000-000020030100" + }, + { + "access" : "public", + "license" : 0, + "size" : 2, + "state" : "online", + "title" : "Ubuntu Server 12.04 LTS (Precise Pangolin)", + "type" : "template", + "uuid" : "01000000-0000-4000-8000-000030030200" + }, + { + "access" : "public", + "license" : 0, + "size" : 2, + "state" : "online", + "title" : "Ubuntu Server 14.04 LTS (Trusty Tahr)", + "type" : "template", + "uuid" : "01000000-0000-4000-8000-000030040200" + }, + { + "access" : "public", + "license" : 0, + "size" : 2, + "state" : "online", + "title" : "Ubuntu Server 16.04 LTS (Xenial Xerus)", + "type" : "template", + "uuid" : "01000000-0000-4000-8000-000030060200" + }, + { + "access" : "public", + "license" : 0, + "size" : 2, + "state" : "online", + "title" : "CentOS 6.9", + "type" : "template", + "uuid" : "01000000-0000-4000-8000-000050010200" + }, + { + "access" : "public", + "license" : 0, + "size" : 2, + "state" : "online", + "title" : "CentOS 7.0", + "type" : "template", + "uuid" : "01000000-0000-4000-8000-000050010300" + }, + { + "access" : "public", + "license" : 0, + "size" : 5, + "state" : "online", + "title" : "CoreOS Stable 1068.8.0", + "type" : "template", + "uuid" : "01000000-0000-4000-8000-000080010200" + } + ] + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/compute/fixtures/upcloud/api_1_2_zone.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/upcloud/api_1_2_zone.json b/libcloud/test/compute/fixtures/upcloud/api_1_2_zone.json new file mode 100644 index 0000000..19deb8d --- /dev/null +++ b/libcloud/test/compute/fixtures/upcloud/api_1_2_zone.json @@ -0,0 +1,30 @@ +{ + "zones" : { + "zone" : [ + { + "description" : "Frankfurt #1", + "id" : "de-fra1" + }, + { + "description" : "Helsinki #1", + "id" : "fi-hel1" + }, + { + "description" : "Amsterdam #1", + "id" : "nl-ams1" + }, + { + "description" : "Singapore #1", + "id" : "sg-sin1" + }, + { + "description" : "London #1", + "id" : "uk-lon1" + }, + { + "description" : "Chicago #1", + "id" : "us-chi1" + } + ] + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/compute/fixtures/upcloud/api_1_2_zone_failed_auth.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/upcloud/api_1_2_zone_failed_auth.json b/libcloud/test/compute/fixtures/upcloud/api_1_2_zone_failed_auth.json new file mode 100644 index 0000000..356841c --- /dev/null +++ b/libcloud/test/compute/fixtures/upcloud/api_1_2_zone_failed_auth.json @@ -0,0 +1,6 @@ +{ + "error" : { + "error_code" : "AUTHENTICATION_FAILED", + "error_message" : "Authentication failed using the given username and password." + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/compute/test_upcloud.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_upcloud.py b/libcloud/test/compute/test_upcloud.py new file mode 100644 index 0000000..41d02a0 --- /dev/null +++ b/libcloud/test/compute/test_upcloud.py @@ -0,0 +1,248 @@ +# 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. + +from __future__ import with_statement +import sys +import re +import json +import base64 + +from libcloud.utils.py3 import httplib, ensure_string +from libcloud.compute.drivers.upcloud import UpcloudDriver +from libcloud.common.types import InvalidCredsError +from libcloud.compute.drivers.upcloud import UpcloudResponse +from libcloud.compute.types import NodeState +from libcloud.compute.base import NodeImage, NodeSize, NodeLocation, NodeAuthSSHKey, Node +from libcloud.test import LibcloudTestCase, unittest, MockHttp +from libcloud.test.file_fixtures import ComputeFileFixtures +from libcloud.test.secrets import UPCLOUD_PARAMS + + +class UpcloudPersistResponse(UpcloudResponse): + + def parse_body(self): + import os + path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.path.pardir, 'compute', 'fixtures', 'upcloud')) + filename = 'api' + self.request.path_url.replace('/', '_').replace('.', '_') + '.json' + filename = os.path.join(path, filename) + if not os.path.exists(filename): + with open(filename, 'w+') as f: + f.write(self.body) + return super(UpcloudPersistResponse, self).parse_body() + + +class UpcloudAuthenticationTests(LibcloudTestCase): + + def setUp(self): + UpcloudDriver.connectionCls.conn_class = UpcloudMockHttp + self.driver = UpcloudDriver("nosuchuser", "nopwd") + + def test_authentication_fails(self): + with self.assertRaises(InvalidCredsError): + self.driver.list_locations() + + +class UpcloudDriverTests(LibcloudTestCase): + + def setUp(self): + UpcloudDriver.connectionCls.conn_class = UpcloudMockHttp + # UpcloudDriver.connectionCls.responseCls = UpcloudPersistResponse + self.driver = UpcloudDriver(*UPCLOUD_PARAMS) + + def test_features(self): + features = self.driver.features['create_node'] + self.assertIn('ssh_key', features) + self.assertIn('generates_password', features) + + def test_list_locations(self): + locations = self.driver.list_locations() + self.assertTrue(len(locations) >= 1) + expected_node_location = NodeLocation(id='fi-hel1', + name='Helsinki #1', + country='FI', + driver=self.driver) + self.assert_object(expected_node_location, objects=locations) + + def test_list_sizes(self): + sizes = self.driver.list_sizes() + self.assertTrue(len(sizes) >= 1) + expected_node_size = NodeSize(id='1xCPU-1GB', + name='1xCPU-1GB', + ram=1024, + disk=30, + bandwidth=2048, + price=None, + driver=self.driver, + extra={'core_number': 1, + 'storage_tier': 'maxiops'}) + self.assert_object(expected_node_size, objects=sizes) + + def test_list_images(self): + images = self.driver.list_images() + self.assertTrue(len(images) >= 1) + expected_node_image = NodeImage(id='01000000-0000-4000-8000-000010010101', + name='Windows Server 2003 R2 Standard (CD 1)', + driver=self.driver, + extra={'access': 'public', + 'licence': 0, + 'size': 1, + 'state': 'online', + 'type': 'cdrom'}) + self.assert_object(expected_node_image, objects=images) + + def test_create_node_from_template(self): + image = NodeImage(id='01000000-0000-4000-8000-000030060200', + name='Ubuntu Server 16.04 LTS (Xenial Xerus)', + extra={'type': 'template'}, + driver=self.driver) + location = NodeLocation(id='fi-hel1', name='Helsinki #1', country='FI', driver=self.driver) + size = NodeSize(id='1xCPU-1GB', name='1xCPU-1GB', ram=1024, disk=30, bandwidth=2048, + extra={'core_number': 1, 'storage_tier': 'maxiops'}, price=None, driver=self.driver) + node = self.driver.create_node(name='test_server', size=size, image=image, location=location) + + self.assertTrue(re.match('^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$', node.id)) + self.assertEquals(node.name, 'test_server') + self.assertEquals(node.state, NodeState.STARTING) + self.assertTrue(len(node.public_ips) > 0) + self.assertTrue(len(node.private_ips) > 0) + self.assertEquals(node.driver, self.driver) + self.assertTrue(len(node.extra['password']) > 0) + self.assertTrue(len(node.extra['vnc_password']) > 0) + + def test_create_node_with_ssh_keys(self): + image = NodeImage(id='01000000-0000-4000-8000-000030060200', + name='Ubuntu Server 16.04 LTS (Xenial Xerus)', + extra={'type': 'template'}, + driver=self.driver) + location = NodeLocation(id='fi-hel1', name='Helsinki #1', country='FI', driver=self.driver) + size = NodeSize(id='1xCPU-1GB', name='1xCPU-1GB', ram=1024, disk=30, bandwidth=2048, + extra={'core_number': 1, 'storage_tier': 'maxiops'}, price=None, driver=self.driver) + + auth = NodeAuthSSHKey('ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCUUFfYA+T+BzoM7IIR' + + 'VXNndDjYvIROMjfyRBhhHf6RZd1IkAwcWSGISePh2tIiqu8gJalYYHg2w' + + 'i3ofMJfi6VYeyBFWrIDhMK0v+ziBbBUtlJNnP6MBOR/13avkk+76TVrcG' + + 'xu49RaptYNzZ21XluvIlaqqdjAhoh0J+o7OZTKD7N1UTPL7CIX+ITaA+g' + + '3FR5ITClk8KmIbp3vT6fUPD7pNUrGBZTpcPcHq8rodQ8igWIVdSkb9iky' + + 'ew4y6wvsubQ3Ykn26XeKxrk1vA6ZKMHt7ijCYmfL0LcDfctNymy/vc6hs' + + 'WxCRS5OqNQ6nxdXpv9A+TD0sJuf5jaoH7MSpU1 [email protected]') + node = self.driver.create_node(name='test_server', size=size, image=image, location=location, auth=auth) + self.assertTrue(re.match('^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$', node.id)) + self.assertEquals(node.name, 'test_server') + self.assertEquals(node.state, NodeState.STARTING) + self.assertTrue(len(node.public_ips) > 0) + self.assertTrue(len(node.private_ips) > 0) + self.assertEquals(node.driver, self.driver) + + def test_list_nodes(self): + nodes = self.driver.list_nodes() + + self.assertTrue(len(nodes) >= 1) + node = nodes[0] + self.assertEquals(node.name, 'test_server') + self.assertEquals(node.state, NodeState.RUNNING) + self.assertTrue(len(node.public_ips) > 0) + self.assertTrue(len(node.private_ips) > 0) + self.assertEquals(node.driver, self.driver) + + def test_reboot_node(self): + nodes = self.driver.list_nodes() + success = self.driver.reboot_node(nodes[0]) + self.assertTrue(success) + + def test_destroy_node(self): + if UpcloudDriver.connectionCls.conn_class == UpcloudMockHttp: + nodes = [Node(id='00893c98_5d5a_4363_b177_88df518a2b60', name='', state='', + public_ips=[], private_ips=[], driver=self.driver)] + else: + nodes = self.driver.list_nodes() + success = self.driver.destroy_node(nodes[0]) + self.assertTrue(success) + + def assert_object(self, expected_object, objects): + same_data = any([self.objects_equals(expected_object, obj) for obj in objects]) + self.assertTrue(same_data, "Objects does not match") + + def objects_equals(self, expected_obj, obj): + for name in vars(expected_obj): + expected_data = getattr(expected_obj, name) + actual_data = getattr(obj, name) + same_data = self.data_equals(expected_data, actual_data) + if not same_data: + break + return same_data + + def data_equals(self, expected_data, actual_data): + if isinstance(expected_data, dict): + return self.dicts_equals(expected_data, actual_data) + else: + return expected_data == actual_data + + def dicts_equals(self, d1, d2): + """Assumes dicts to contain only hashable types""" + return set(d1.values()) == set(d2.values()) + + +class UpcloudMockHttp(MockHttp): + fixtures = ComputeFileFixtures('upcloud') + + def _1_2_zone(self, method, url, body, headers): + auth = headers['Authorization'].split(' ')[1] + username, password = ensure_string(base64.b64decode(auth)).split(':') + if username == 'nosuchuser' and password == 'nopwd': + body = self.fixtures.load('api_1_2_zone_failed_auth.json') + status_code = httplib.UNAUTHORIZED + else: + body = self.fixtures.load('api_1_2_zone.json') + status_code = httplib.OK + return (status_code, body, {}, httplib.responses[httplib.OK]) + + def _1_2_plan(self, method, url, body, headers): + body = self.fixtures.load('api_1_2_plan.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _1_2_storage_cdrom(self, method, url, body, headers): + body = self.fixtures.load('api_1_2_storage_cdrom.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _1_2_storage_template(self, method, url, body, headers): + body = self.fixtures.load('api_1_2_storage_template.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _1_2_server(self, method, url, body, headers): + if method == 'POST': + dbody = json.loads(body) + storages = dbody['server']['storage_devices']['storage_device'] + if any(['type' in storage and storage['type'] == 'cdrom' for storage in storages]): + body = self.fixtures.load('api_1_2_server_from_cdrom.json') + else: + body = self.fixtures.load('api_1_2_server_from_template.json') + else: + body = self.fixtures.load('api_1_2_server.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _1_2_server_00f8c525_7e62_4108_8115_3958df5b43dc(self, method, url, body, headers): + body = self.fixtures.load('api_1_2_server_00f8c525-7e62-4108-8115-3958df5b43dc.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _1_2_server_00f8c525_7e62_4108_8115_3958df5b43dc_restart(self, method, url, body, headers): + body = self.fixtures.load('api_1_2_server_00f8c525-7e62-4108-8115-3958df5b43dc_restart.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _1_2_server_00893c98_5d5a_4363_b177_88df518a2b60(self, method, url, body, headers): + body = self.fixtures.load('api_1_2_server_00893c98-5d5a-4363-b177-88df518a2b60.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + +if __name__ == '__main__': + sys.exit(unittest.main()) http://git-wip-us.apache.org/repos/asf/libcloud/blob/abbd9bb6/libcloud/test/secrets.py-dist ---------------------------------------------------------------------- diff --git a/libcloud/test/secrets.py-dist b/libcloud/test/secrets.py-dist index 1b30bf2..8eaa0c6 100644 --- a/libcloud/test/secrets.py-dist +++ b/libcloud/test/secrets.py-dist @@ -57,6 +57,7 @@ VULTR_PARAMS = ('key') PACKET_PARAMS = ('api_key') ECS_PARAMS = ('access_key', 'access_secret') CLOUDSCALE_PARAMS = ('token',) +UPCLOUD_PARAMS = ('user', 'secret') # Storage STORAGE_S3_PARAMS = ('key', 'secret')
