You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by qu...@apache.org on 2017/09/27 03:12:44 UTC

[01/14] libcloud git commit: Add changes for #1102

Repository: libcloud
Updated Branches:
  refs/heads/trunk 5892fa1be -> d8be2d472


Add changes for #1102

Closes #1102


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/d8be2d47
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/d8be2d47
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/d8be2d47

Branch: refs/heads/trunk
Commit: d8be2d472b4fbd6c88d6e4e1d00f2aeda11a77a7
Parents: f37b7c0
Author: Quentin Pradet <qu...@apache.org>
Authored: Wed Sep 27 06:57:09 2017 +0400
Committer: Quentin Pradet <qu...@apache.org>
Committed: Wed Sep 27 07:04:33 2017 +0400

----------------------------------------------------------------------
 CHANGES.rst | 3 +++
 1 file changed, 3 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/d8be2d47/CHANGES.rst
----------------------------------------------------------------------
diff --git a/CHANGES.rst b/CHANGES.rst
index 527ecd4..ea472eb 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -7,6 +7,9 @@ Changes in Apach Libcloud in development
 Compute
 ~~~~~~~
 
+- New driver for UpCloud (GITHUB-1102)
+  [Mika Lackman, Ilari Mäkelä]
+
 - [EC2] Add new x1.16xlarge and x1e.32xlarge instance type. (GITHUB-1101)
   [@zulupro]
 


[09/14] libcloud git commit: Upcloud renamed to UpCloud

Posted by qu...@apache.org.
Upcloud renamed to UpCloud

Signed-off-by: Quentin Pradet <qu...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/0e0b6831
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/0e0b6831
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/0e0b6831

Branch: refs/heads/trunk
Commit: 0e0b68319ba8771122a1418bd8bb9ac351ba9ed3
Parents: 5326c9a
Author: Mika Lackman <mi...@upcloud.com>
Authored: Sun Sep 24 19:38:54 2017 +0300
Committer: Quentin Pradet <qu...@apache.org>
Committed: Wed Sep 27 07:04:33 2017 +0400

----------------------------------------------------------------------
 libcloud/compute/types.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/0e0b6831/libcloud/compute/types.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/types.py b/libcloud/compute/types.py
index b360c0e..0de0497 100644
--- a/libcloud/compute/types.py
+++ b/libcloud/compute/types.py
@@ -98,7 +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 UPCLOUD: UpCloud
     :cvar VCL: VCL driver
     :cvar VCLOUD: vmware vCloud
     :cvar VPSNET: VPS.net


[12/14] libcloud git commit: Changed data centres to data centers

Posted by qu...@apache.org.
Changed data centres to data centers

Signed-off-by: Quentin Pradet <qu...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/a2dc54a6
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/a2dc54a6
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/a2dc54a6

Branch: refs/heads/trunk
Commit: a2dc54a6f2abc070bfe167e1d6a1ec9686ba451e
Parents: d78edb8
Author: Ilari Mäkelä <il...@users.noreply.github.com>
Authored: Tue Sep 26 12:53:00 2017 +0300
Committer: Quentin Pradet <qu...@apache.org>
Committed: Wed Sep 27 07:04:33 2017 +0400

----------------------------------------------------------------------
 docs/compute/drivers/upcloud.rst | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/a2dc54a6/docs/compute/drivers/upcloud.rst
----------------------------------------------------------------------
diff --git a/docs/compute/drivers/upcloud.rst b/docs/compute/drivers/upcloud.rst
index b9a0bf3..09ae874 100644
--- a/docs/compute/drivers/upcloud.rst
+++ b/docs/compute/drivers/upcloud.rst
@@ -1,14 +1,14 @@
 UpCloud Driver Documentation
 ===============================
 `UpCloud`_ is a Finnish IaaS provider offering high performance servers 
-from data centres based in multiple countries.
+from data centers based in multiple countries.
 
 .. figure:: /_static/images/provider_logos/upcloud.png
     :align: center
     :width: 300
     :target: https://www.upcloud.com/
 
-UpCloud currently operates globally from six (6) data centres:
+UpCloud currently operates globally from six (6) data centers:
 
 * Amsterdam, Netherlands
 * Chicago, USA


[03/14] libcloud git commit: Updated upcloud.rst

Posted by qu...@apache.org.
Updated upcloud.rst

- Revised the wording
- Added 'Enabling API access' section

Signed-off-by: Quentin Pradet <qu...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/fdbd943b
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/fdbd943b
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/fdbd943b

Branch: refs/heads/trunk
Commit: fdbd943b103c1bd2873b5d9d2b67140fa6002cb7
Parents: 9a97e3b
Author: Ilari Mäkelä <il...@users.noreply.github.com>
Authored: Tue Sep 26 10:47:18 2017 +0300
Committer: Quentin Pradet <qu...@apache.org>
Committed: Wed Sep 27 07:04:33 2017 +0400

----------------------------------------------------------------------
 docs/compute/drivers/upcloud.rst | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/fdbd943b/docs/compute/drivers/upcloud.rst
----------------------------------------------------------------------
diff --git a/docs/compute/drivers/upcloud.rst b/docs/compute/drivers/upcloud.rst
index 4c6a400..b9a0bf3 100644
--- a/docs/compute/drivers/upcloud.rst
+++ b/docs/compute/drivers/upcloud.rst
@@ -1,7 +1,7 @@
 UpCloud Driver Documentation
 ===============================
-`UpCloud`_ is a Finnish IaaS provider offering the most high performance 
-servers with extremely redundant infrastructure to the most demanding customers.
+`UpCloud`_ is a Finnish IaaS provider offering high performance servers 
+from data centres based in multiple countries.
 
 .. figure:: /_static/images/provider_logos/upcloud.png
     :align: center
@@ -26,6 +26,20 @@ driver constructor:
 * ``username`` - Your API access enabled users username
 * ``password`` - Your API access enabled users password
 
-You can setup your API access enabled user account from https://my.upcloud.com/account
+Enabling API access
+-------------------
+
+To allow API access to your UpCloud account, you first need to enable the API 
+permissions by visiting `My Account -> User accounts`_ in your UpCloud Control 
+Panel. We recommend you to set up a sub-account specifically for the API usage 
+with its own username and password, as it allows you to assign specific permissions 
+for increased security.
+
+Click **Add user** and fill in the required details, and check the 
+“**Allow API connections**” checkbox to enable API for the user. You can also 
+limit the API connections to a specific IP address or address range for additional 
+security. Once you are done entering the user information, hit the **Save** button 
+at the bottom of the page to create the new username.
 
 .. _`UpCloud`: https://www.upcloud.com/
+.. _`My Account -> User accounts`: https://my.upcloud.com/account


[14/14] libcloud git commit: Renamed SERVER_STATE to NODE_STATE_MAP and moved it to UpcloudDriver for better consistency with other drivers

Posted by qu...@apache.org.
Renamed SERVER_STATE to NODE_STATE_MAP and moved it to UpcloudDriver for better consistency with other drivers

Signed-off-by: Quentin Pradet <qu...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/e7df9487
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/e7df9487
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/e7df9487

Branch: refs/heads/trunk
Commit: e7df94873dc0e2f9c5bfdd358fe99da6eda82c04
Parents: a2dc54a
Author: Mika Lackman <mi...@upcloud.com>
Authored: Tue Sep 26 15:33:23 2017 +0300
Committer: Quentin Pradet <qu...@apache.org>
Committed: Wed Sep 27 07:04:33 2017 +0400

----------------------------------------------------------------------
 libcloud/compute/drivers/upcloud.py | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/e7df9487/libcloud/compute/drivers/upcloud.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/upcloud.py b/libcloud/compute/drivers/upcloud.py
index d2e0314..c9c762e 100644
--- a/libcloud/compute/drivers/upcloud.py
+++ b/libcloud/compute/drivers/upcloud.py
@@ -28,13 +28,6 @@ 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):
     """
@@ -92,6 +85,13 @@ class UpcloudDriver(NodeDriver):
     connectionCls = UpcloudConnection
     features = {'create_node': ['ssh_key', 'generates_password']}
 
+    NODE_STATE_MAP = {
+        'started': NodeState.RUNNING,
+        'stopped': NodeState.STOPPED,
+        'maintenance': NodeState.RECONFIGURING,
+        'error': NodeState.ERROR
+    }
+
     def __init__(self, username, password, **kwargs):
         super(UpcloudDriver, self).__init__(key=username, secret=password,
                                             **kwargs)
@@ -236,7 +236,7 @@ class UpcloudDriver(NodeDriver):
             extra['password'] = server['password']
         return Node(id=server['uuid'],
                     name=server['title'],
-                    state=state or SERVER_STATE[server['state']],
+                    state=state or self.NODE_STATE_MAP[server['state']],
                     public_ips=public_ips,
                     private_ips=private_ips,
                     driver=self,


[10/14] libcloud git commit: Added upcloud.png to provider logos

Posted by qu...@apache.org.
Added upcloud.png to provider logos

Signed-off-by: Quentin Pradet <qu...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/d78edb8f
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/d78edb8f
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/d78edb8f

Branch: refs/heads/trunk
Commit: d78edb8fb1e6404f49576fb670b046071c5f8fa5
Parents: fdbd943
Author: Ilari Mäkelä <il...@i28.fi>
Authored: Tue Sep 26 10:52:33 2017 +0300
Committer: Quentin Pradet <qu...@apache.org>
Committed: Wed Sep 27 07:04:33 2017 +0400

----------------------------------------------------------------------
 docs/_static/images/provider_logos/upcloud.png | Bin 0 -> 20107 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/d78edb8f/docs/_static/images/provider_logos/upcloud.png
----------------------------------------------------------------------
diff --git a/docs/_static/images/provider_logos/upcloud.png b/docs/_static/images/provider_logos/upcloud.png
new file mode 100644
index 0000000..5b5f205
Binary files /dev/null and b/docs/_static/images/provider_logos/upcloud.png differ


[04/14] libcloud git commit: New compute driver for UpCloud

Posted by qu...@apache.org.
New compute driver for UpCloud

Signed-off-by: Quentin Pradet <qu...@apache.org>


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 <mi...@upcloud.com>
Authored: Mon Aug 14 14:03:29 2017 +0300
Committer: Quentin Pradet <qu...@apache.org>
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 mika.lackman@gmail.com')
+        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')


[07/14] libcloud git commit: Docstrings added for public classes

Posted by qu...@apache.org.
Docstrings added for public classes

Signed-off-by: Quentin Pradet <qu...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/82b5c3cc
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/82b5c3cc
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/82b5c3cc

Branch: refs/heads/trunk
Commit: 82b5c3cc63e28d7a540974f7bfeb832e4c68d16c
Parents: d238e93
Author: Mika Lackman <mi...@upcloud.com>
Authored: Sun Sep 24 16:08:31 2017 +0300
Committer: Quentin Pradet <qu...@apache.org>
Committed: Wed Sep 27 07:04:33 2017 +0400

----------------------------------------------------------------------
 libcloud/common/upcloud.py | 67 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 66 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/82b5c3cc/libcloud/common/upcloud.py
----------------------------------------------------------------------
diff --git a/libcloud/common/upcloud.py b/libcloud/common/upcloud.py
index cbb2904..a63be08 100644
--- a/libcloud/common/upcloud.py
+++ b/libcloud/common/upcloud.py
@@ -27,6 +27,27 @@ class UpcloudCreateNodeRequestBody(object):
     Body of the create_node request
 
     Takes the create_node arguments (**kwargs) and constructs the request body
+
+    :param      user_id: required for authentication (required)
+    :type       user_id: ``str``
+
+    :param      name: Name of the created server (required)
+    :type       name: ``str``
+
+    :param      size: The size of resources allocated to this node.
+    :type       size: :class:`.NodeSize`
+
+    :param      image: OS Image to boot on node.
+    :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`
+
     """
 
     def __init__(self, user_id, name, size, image, location, auth=None):
@@ -44,15 +65,26 @@ class UpcloudCreateNodeRequestBody(object):
     def to_json(self):
         """
         Serializes the body to json
+
+        :return: JSON string
+        :rtype: ``str``
         """
         return json.dumps(self.body)
 
 
 class UpcloudNodeDestroyer(object):
     """
-    Destroyes the node.
+    Helper class for destroying node.
     Node must be first stopped and then it can be
     destroyed
+
+    :param  upcloud_node_operations: UpcloudNodeOperations instance
+    :type   upcloud_node_operations: :class:`.UpcloudNodeOperations`
+
+    :param  sleep_func: Callable function, which sleeps.
+        Takes int argument to sleep in seconds (optional)
+    :type   sleep_func: ``function``
+
     """
 
     WAIT_AMOUNT = 2
@@ -64,6 +96,12 @@ class UpcloudNodeDestroyer(object):
         self._sleep_count = 0
 
     def destroy_node(self, node_id):
+        """
+        Destroys the given node.
+
+        :param  node_id: Id of the Node.
+        :type   node_id: ``int``
+        """
         self._stop_called = False
         self._sleep_count = 0
         return self._do_destroy_node(node_id)
@@ -99,11 +137,23 @@ class UpcloudNodeDestroyer(object):
 
 
 class UpcloudNodeOperations(object):
+    """
+    Helper class to start and stop node.
+
+    :param  conneciton: Connection instance
+    :type   connection: :class:`.UpcloudConnection`
+    """
 
     def __init__(self, connection):
         self.connection = connection
 
     def stop_node(self, node_id):
+        """
+        Stops the node
+
+        :param  node_id: Id of the Node
+        :type   node_id: ``int``
+        """
         body = {
             'stop_server': {
                 'stop_type': 'hard'
@@ -114,6 +164,15 @@ class UpcloudNodeOperations(object):
                                 data=json.dumps(body))
 
     def node_state(self, node_id):
+        """
+        Get the state of the node.
+
+        :param  node_id: Id of the Node
+        :type   node_id: ``int``
+
+        :rtype: ``str``
+        """
+
         action = '1.2/server/{0}'.format(node_id)
         try:
             response = self.connection.request(action)
@@ -124,6 +183,12 @@ class UpcloudNodeOperations(object):
             raise
 
     def destroy_node(self, node_id):
+        """
+        Destroys the node.
+
+        :param  node_id: Id of the Node
+        :type   node_id: ``int``
+        """
         self.connection.request('1.2/server/{0}'.format(node_id),
                                 method='DELETE')
 


[05/14] libcloud git commit: New compute driver for UpCloud

Posted by qu...@apache.org.
New compute driver for UpCloud

Signed-off-by: Quentin Pradet <qu...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/33c1e6fd
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/33c1e6fd
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/33c1e6fd

Branch: refs/heads/trunk
Commit: 33c1e6fdbbb6ceefc9be933ebfe555930a0a02d2
Parents: abbd9bb
Author: Mika Lackman <mi...@upcloud.com>
Authored: Mon Aug 14 14:03:29 2017 +0300
Committer: Quentin Pradet <qu...@apache.org>
Committed: Wed Sep 27 07:04:33 2017 +0400

----------------------------------------------------------------------
 libcloud/test/compute/test_upcloud.py | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/33c1e6fd/libcloud/test/compute/test_upcloud.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_upcloud.py b/libcloud/test/compute/test_upcloud.py
index 41d02a0..98e0105 100644
--- a/libcloud/test/compute/test_upcloud.py
+++ b/libcloud/test/compute/test_upcloud.py
@@ -130,13 +130,7 @@ class UpcloudDriverTests(LibcloudTestCase):
         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 mika.lackman@gmail.com')
+        auth = NodeAuthSSHKey('publikey')
         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')


[08/14] libcloud git commit: inherit exception from LibcloudError

Posted by qu...@apache.org.
inherit exception from LibcloudError

Signed-off-by: Quentin Pradet <qu...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/5326c9aa
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/5326c9aa
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/5326c9aa

Branch: refs/heads/trunk
Commit: 5326c9aa938c35fd773d27d2d53c9e5e21d3658f
Parents: 82b5c3c
Author: Mika Lackman <mi...@upcloud.com>
Authored: Sun Sep 24 19:38:38 2017 +0300
Committer: Quentin Pradet <qu...@apache.org>
Committed: Wed Sep 27 07:04:33 2017 +0400

----------------------------------------------------------------------
 libcloud/common/upcloud.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/5326c9aa/libcloud/common/upcloud.py
----------------------------------------------------------------------
diff --git a/libcloud/common/upcloud.py b/libcloud/common/upcloud.py
index a63be08..02413fc 100644
--- a/libcloud/common/upcloud.py
+++ b/libcloud/common/upcloud.py
@@ -16,9 +16,10 @@ import json
 import time
 
 from libcloud.common.exceptions import BaseHTTPError
+from libcloud.common.types import LibcloudError
 
 
-class UpcloudTimeoutException(Exception):
+class UpcloudTimeoutException(LibcloudError):
     pass
 
 


[02/14] libcloud git commit: renamed method from node_state to get_node_state

Posted by qu...@apache.org.
renamed method from node_state to get_node_state

Signed-off-by: Quentin Pradet <qu...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/513b9110
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/513b9110
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/513b9110

Branch: refs/heads/trunk
Commit: 513b9110bf62946695f0a0a8b817c32e4ba12cbc
Parents: e7df948
Author: Mika Lackman <mi...@upcloud.com>
Authored: Tue Sep 26 16:34:20 2017 +0300
Committer: Quentin Pradet <qu...@apache.org>
Committed: Wed Sep 27 07:04:33 2017 +0400

----------------------------------------------------------------------
 libcloud/common/upcloud.py           |  4 ++--
 libcloud/test/common/test_upcloud.py | 18 +++++++++---------
 2 files changed, 11 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/513b9110/libcloud/common/upcloud.py
----------------------------------------------------------------------
diff --git a/libcloud/common/upcloud.py b/libcloud/common/upcloud.py
index 02413fc..b824664 100644
--- a/libcloud/common/upcloud.py
+++ b/libcloud/common/upcloud.py
@@ -108,7 +108,7 @@ class UpcloudNodeDestroyer(object):
         return self._do_destroy_node(node_id)
 
     def _do_destroy_node(self, node_id):
-        state = self._operations.node_state(node_id)
+        state = self._operations.get_node_state(node_id)
         if state == 'stopped':
             self._operations.destroy_node(node_id)
             return True
@@ -164,7 +164,7 @@ class UpcloudNodeOperations(object):
                                 method='POST',
                                 data=json.dumps(body))
 
-    def node_state(self, node_id):
+    def get_node_state(self, node_id):
         """
         Get the state of the node.
 

http://git-wip-us.apache.org/repos/asf/libcloud/blob/513b9110/libcloud/test/common/test_upcloud.py
----------------------------------------------------------------------
diff --git a/libcloud/test/common/test_upcloud.py b/libcloud/test/common/test_upcloud.py
index 76dd9c1..2df8213 100644
--- a/libcloud/test/common/test_upcloud.py
+++ b/libcloud/test/common/test_upcloud.py
@@ -142,7 +142,7 @@ class TestUpcloudNodeDestroyer(unittest.TestCase):
         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.mock_operations.get_node_state.side_effect = ['stopped']
 
         self.assertTrue(self.destroyer.destroy_node(1))
 
@@ -150,7 +150,7 @@ class TestUpcloudNodeDestroyer(unittest.TestCase):
         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.mock_operations.get_node_state.side_effect = ['error']
 
         self.assertFalse(self.destroyer.destroy_node(1))
 
@@ -158,7 +158,7 @@ class TestUpcloudNodeDestroyer(unittest.TestCase):
         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.mock_operations.get_node_state.side_effect = ['started', 'stopped']
 
         self.assertTrue(self.destroyer.destroy_node(1))
 
@@ -166,7 +166,7 @@ class TestUpcloudNodeDestroyer(unittest.TestCase):
         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.mock_operations.get_node_state.side_effect = ['maintenance', 'maintenance', None]
 
         self.assertTrue(self.destroyer.destroy_node(1))
 
@@ -176,7 +176,7 @@ class TestUpcloudNodeDestroyer(unittest.TestCase):
         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.mock_operations.get_node_state.side_effect = ['started', 'started', 'stopped']
 
         self.assertTrue(self.destroyer.destroy_node(1))
 
@@ -187,7 +187,7 @@ class TestUpcloudNodeDestroyer(unittest.TestCase):
 
     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.mock_operations.get_node_state.side_effect = ['started', 'stopped', 'started', 'stopped']
         self.assertTrue(self.destroyer.destroy_node(1))
         self.assertTrue(self.destroyer.destroy_node(1))
 
@@ -195,16 +195,16 @@ class TestUpcloudNodeDestroyer(unittest.TestCase):
         self.assertEquals(self.mock_operations.stop_node.call_count, 2)
 
     def test_timeout(self):
-        self.mock_operations.node_state.side_effect = ['maintenance'] * 50
+        self.mock_operations.get_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.mock_operations.get_node_state.side_effect = ['maintenance'] * 50
         self.assertRaises(UpcloudTimeoutException, self.destroyer.destroy_node, 1)
 
-        self.mock_operations.node_state.side_effect = ['maintenance', None]
+        self.mock_operations.get_node_state.side_effect = ['maintenance', None]
         self.assertTrue(self.destroyer.destroy_node(1))
 
 


[06/14] libcloud git commit: fixing docstring line handling

Posted by qu...@apache.org.
fixing docstring line handling

Signed-off-by: Quentin Pradet <qu...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/d238e939
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/d238e939
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/d238e939

Branch: refs/heads/trunk
Commit: d238e9391cad3d94e50d6e35b9d4efe06f598cda
Parents: 33c1e6f
Author: Mika Lackman <mi...@upcloud.com>
Authored: Sun Sep 24 08:45:06 2017 +0300
Committer: Quentin Pradet <qu...@apache.org>
Committed: Wed Sep 27 07:04:33 2017 +0400

----------------------------------------------------------------------
 libcloud/common/upcloud.py          | 14 ++++++++++----
 libcloud/compute/drivers/upcloud.py | 17 +++++++++++++----
 2 files changed, 23 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/d238e939/libcloud/common/upcloud.py
----------------------------------------------------------------------
diff --git a/libcloud/common/upcloud.py b/libcloud/common/upcloud.py
index 432606a..cbb2904 100644
--- a/libcloud/common/upcloud.py
+++ b/libcloud/common/upcloud.py
@@ -23,10 +23,12 @@ class UpcloudTimeoutException(Exception):
 
 
 class UpcloudCreateNodeRequestBody(object):
-    """Body of the create_node request
+    """
+    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': {
@@ -40,14 +42,18 @@ class UpcloudCreateNodeRequestBody(object):
         }
 
     def to_json(self):
-        """Serializes the body to json"""
+        """
+        Serializes the body to json
+        """
         return json.dumps(self.body)
 
 
 class UpcloudNodeDestroyer(object):
-    """Destroyes the node.
+    """
+    Destroyes the node.
     Node must be first stopped and then it can be
-    destroyed"""
+    destroyed
+    """
 
     WAIT_AMOUNT = 2
     SLEEP_COUNT_TO_TIMEOUT = 20

http://git-wip-us.apache.org/repos/asf/libcloud/blob/d238e939/libcloud/compute/drivers/upcloud.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/upcloud.py b/libcloud/compute/drivers/upcloud.py
index b9daf84..d2e0314 100644
--- a/libcloud/compute/drivers/upcloud.py
+++ b/libcloud/compute/drivers/upcloud.py
@@ -37,7 +37,9 @@ SERVER_STATE = {
 
 
 class UpcloudResponse(JsonResponse):
-    """Response class for UpcloudDriver"""
+    """
+    Response class for UpcloudDriver
+    """
 
     def success(self):
         if self.status == httplib.NO_CONTENT:
@@ -52,7 +54,10 @@ class UpcloudResponse(JsonResponse):
 
 
 class UpcloudConnection(ConnectionUserAndKey):
-    """Connection class for UpcloudDriver"""
+    """
+    Connection class for UpcloudDriver
+    """
+
     host = 'api.upcloud.com'
     responseCls = UpcloudResponse
 
@@ -71,7 +76,8 @@ class UpcloudConnection(ConnectionUserAndKey):
 
 
 class UpcloudDriver(NodeDriver):
-    """Upcloud node driver
+    """
+    Upcloud node driver
 
     :keyword    username: Username required for authentication
     :type       username: ``str``
@@ -79,6 +85,7 @@ class UpcloudDriver(NodeDriver):
     :keyword    password: Password required for authentication
     :type       password: ``str``
     """
+
     type = Provider.UPCLOUD
     name = 'Upcloud'
     website = 'https://www.upcloud.com'
@@ -207,7 +214,9 @@ class UpcloudDriver(NodeDriver):
         return destroyer.destroy_node(node.id)
 
     def _node_ids(self):
-        """Returns list of server uids currently on upcloud"""
+        """
+        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]


[11/14] libcloud git commit: Created upcloud.rst

Posted by qu...@apache.org.
Created upcloud.rst

Signed-off-by: Quentin Pradet <qu...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/9a97e3b9
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/9a97e3b9
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/9a97e3b9

Branch: refs/heads/trunk
Commit: 9a97e3b9e22bef0852aaed2061328db3c9797913
Parents: 0e0b683
Author: Ilari Mäkelä <il...@users.noreply.github.com>
Authored: Mon Sep 25 16:44:34 2017 +0300
Committer: Quentin Pradet <qu...@apache.org>
Committed: Wed Sep 27 07:04:33 2017 +0400

----------------------------------------------------------------------
 docs/compute/drivers/upcloud.rst | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/9a97e3b9/docs/compute/drivers/upcloud.rst
----------------------------------------------------------------------
diff --git a/docs/compute/drivers/upcloud.rst b/docs/compute/drivers/upcloud.rst
new file mode 100644
index 0000000..4c6a400
--- /dev/null
+++ b/docs/compute/drivers/upcloud.rst
@@ -0,0 +1,31 @@
+UpCloud Driver Documentation
+===============================
+`UpCloud`_ is a Finnish IaaS provider offering the most high performance 
+servers with extremely redundant infrastructure to the most demanding customers.
+
+.. figure:: /_static/images/provider_logos/upcloud.png
+    :align: center
+    :width: 300
+    :target: https://www.upcloud.com/
+
+UpCloud currently operates globally from six (6) data centres:
+
+* Amsterdam, Netherlands
+* Chicago, USA
+* Frankfurt, Germany
+* Helsinki, Finland
+* London, UK
+* Singapore, Singapore
+
+Instantiating a driver
+----------------------
+
+When you instantiate a driver you need to pass the following arguments to the
+driver constructor:
+
+* ``username`` - Your API access enabled users username
+* ``password`` - Your API access enabled users password
+
+You can setup your API access enabled user account from https://my.upcloud.com/account
+
+.. _`UpCloud`: https://www.upcloud.com/


[13/14] libcloud git commit: docs: Fix upcloud.rst whitespace

Posted by qu...@apache.org.
docs: Fix upcloud.rst whitespace


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/f37b7c01
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/f37b7c01
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/f37b7c01

Branch: refs/heads/trunk
Commit: f37b7c01a6476dc5427e0a484cb5d427608455e5
Parents: 513b911
Author: Quentin Pradet <qu...@apache.org>
Authored: Wed Sep 27 06:50:54 2017 +0400
Committer: Quentin Pradet <qu...@apache.org>
Committed: Wed Sep 27 07:04:33 2017 +0400

----------------------------------------------------------------------
 docs/compute/drivers/upcloud.rst | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/f37b7c01/docs/compute/drivers/upcloud.rst
----------------------------------------------------------------------
diff --git a/docs/compute/drivers/upcloud.rst b/docs/compute/drivers/upcloud.rst
index 09ae874..e24b46c 100644
--- a/docs/compute/drivers/upcloud.rst
+++ b/docs/compute/drivers/upcloud.rst
@@ -1,6 +1,6 @@
 UpCloud Driver Documentation
 ===============================
-`UpCloud`_ is a Finnish IaaS provider offering high performance servers 
+`UpCloud`_ is a Finnish IaaS provider offering high performance servers
 from data centers based in multiple countries.
 
 .. figure:: /_static/images/provider_logos/upcloud.png
@@ -29,16 +29,16 @@ driver constructor:
 Enabling API access
 -------------------
 
-To allow API access to your UpCloud account, you first need to enable the API 
-permissions by visiting `My Account -> User accounts`_ in your UpCloud Control 
-Panel. We recommend you to set up a sub-account specifically for the API usage 
-with its own username and password, as it allows you to assign specific permissions 
+To allow API access to your UpCloud account, you first need to enable the API
+permissions by visiting `My Account -> User accounts`_ in your UpCloud Control
+Panel. We recommend you to set up a sub-account specifically for the API usage
+with its own username and password, as it allows you to assign specific permissions
 for increased security.
 
-Click **Add user** and fill in the required details, and check the 
-“**Allow API connections**” checkbox to enable API for the user. You can also 
-limit the API connections to a specific IP address or address range for additional 
-security. Once you are done entering the user information, hit the **Save** button 
+Click **Add user** and fill in the required details, and check the
+“**Allow API connections**” checkbox to enable API for the user. You can also
+limit the API connections to a specific IP address or address range for additional
+security. Once you are done entering the user information, hit the **Save** button
 at the bottom of the page to create the new username.
 
 .. _`UpCloud`: https://www.upcloud.com/