From a868ef960e3ea1ada799de8452af76c34d579c69 Mon Sep 17 00:00:00 2001
From: Markos Gogoulos <mgogoulos@unweb.me>
Date: Mon, 30 Sep 2013 14:00:58 +0300
Subject: [PATCH 1/8] nephoscale driver

---
 libcloud/compute/drivers/nephoscale.py | 460 +++++++++++++++++++++++++++++++++
 libcloud/compute/providers.py          |   4 +-
 libcloud/compute/types.py              |   2 +
 libcloud/data/pricing.json             |  16 ++
 4 files changed, 481 insertions(+), 1 deletion(-)
 create mode 100644 libcloud/compute/drivers/nephoscale.py

diff --git a/libcloud/compute/drivers/nephoscale.py b/libcloud/compute/drivers/nephoscale.py
new file mode 100644
index 0000000..05c8faf
--- /dev/null
+++ b/libcloud/compute/drivers/nephoscale.py
@@ -0,0 +1,460 @@
+# 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.
+
+"""
+NephoScale Cloud driver (http://www.nephoscale.com)
+API documentation: http://docs.nephoscale.com
+Created by Markos Gogoulos (https://mist.io)
+"""
+
+import base64
+import sys
+import string
+import random
+import time
+
+try:
+    import simplejson as json
+except:
+    import json
+
+from libcloud.utils.py3 import httplib
+from libcloud.utils.py3 import b
+from libcloud.utils.py3 import urlencode
+
+from libcloud.compute.providers import Provider
+from libcloud.common.base import JsonResponse, ConnectionUserAndKey
+from libcloud.compute.types import NodeState, InvalidCredsError
+from libcloud.compute.base import (Node, NodeDriver, NodeImage, NodeSize,
+                                    NodeLocation)
+
+API_HOST = "api.nephoscale.com"
+
+NODE_STATE_MAP = {
+    'on': NodeState.RUNNING,
+    'off': NodeState.UNKNOWN,
+    'unknown': NodeState.UNKNOWN,
+}
+
+VALID_RESPONSE_CODES = [httplib.OK, httplib.ACCEPTED, httplib.CREATED,
+                        httplib.NO_CONTENT]
+
+#used in create_node and specifies how many times to get the list of nodes and
+#check if the newly created node is there. This is because when a request is
+#sent to create a node, NephoScale replies with the job id, and not the node
+#itself thus we don't have the ip addresses, that are required in deploy_node
+CONNECT_ATTEMPTS = 10
+
+
+class NodeKey(object):
+    def __init__(self, id, name, public_key=None, key_group=None,
+                 password=None):
+        self.id = id
+        self.name = name
+        self.key_group = key_group
+        self.password = password
+        self.public_key = public_key
+
+    def __repr__(self):
+        return (('<NodeKey: id=%s, name=%s>') %
+                (self.id, self.name))
+
+
+class NephoscaleResponse(JsonResponse):
+    """
+    Nephoscale API Response
+    """
+
+    def parse_error(self):
+        if self.status == 401:
+            raise InvalidCredsError('Authorization Failed')
+        if self.status == 404:
+            raise Exception("The resource you are looking for is not found.")
+
+        return self.body
+
+    def success(self):
+        return self.status in VALID_RESPONSE_CODES
+
+
+class NephoscaleConnection(ConnectionUserAndKey):
+    """
+    Nephoscale connection class.
+    Authenticates to the API through Basic Authentication
+    with username/password
+    """
+    host = API_HOST
+    responseCls = NephoscaleResponse
+
+    def add_default_headers(self, headers):
+        """
+        Add parameters that are necessary for every request
+        """
+        user_b64 = base64.b64encode(b('%s:%s' % (self.user_id, self.key)))
+        headers['Authorization'] = 'Basic %s' % (user_b64.decode('utf-8'))
+        return headers
+
+
+class NephoscaleNodeDriver(NodeDriver):
+    """
+    Nephoscale node driver class.
+
+    >>> from libcloud.compute.types import Provider
+    >>> from libcloud.compute.providers import get_driver
+    >>> driver = get_driver('nephoscale')
+    >>> conn = driver('nepho_user','nepho_password')
+    >>> conn.list_nodes()
+    """
+
+    type = Provider.NEPHOSCALE
+    api_name = 'nephoscale'
+    name = 'NephoScale'
+    website = 'http://www.nephoscale.com'
+    connectionCls = NephoscaleConnection
+    features = {'create_node': ['ssh_key']}
+
+    def __init__(self, *args, **kwargs):
+        """Instantiate the driver with nephoscale's user and password
+        """
+        super(NephoscaleNodeDriver, self).__init__(*args, **kwargs)
+
+    def list_locations(self):
+        result = self.connection.request('/datacenter/zone/').object
+        locations = []
+        for value in result.get('data', []):
+            location = NodeLocation(id=value.get('id'),
+                                    name=value.get('name'),
+                                    country='US',
+                                    driver=self.connection.driver)
+            locations.append(location)
+        return locations
+
+    def list_images(self):
+        """
+        List available Images
+        """
+        result = self.connection.request('/image/server/').object
+        images = []
+        for value in result.get('data', []):
+            extra = {'architecture': value.get('architecture'),
+                     'disks': value.get('disks'),
+                     'billable_type': value.get('billable_type'),
+                     'pcpus': value.get('pcpus'),
+                     'cores': value.get('cores'),
+                     'uri': value.get('uri'),
+                     'storage': value.get('storage'),
+            }
+            image = NodeImage(id=value.get('id'),
+                              name=value.get('friendly_name'),
+                              driver=self.connection.driver,
+                              extra=extra)
+            images.append(image)
+        return images
+
+    def list_sizes(self):
+        """
+        List available Sizes
+        """
+        result = self.connection.request('/server/type/cloud/').object
+        sizes = []
+        for value in result.get('data', []):
+            value_id = value.get('id')
+            size = NodeSize(id=value_id,
+                            name=value.get('friendly_name'),
+                            ram=value.get('ram'),
+                            disk=value.get('storage'),
+                            bandwidth=None,
+                            price=self._get_size_price(size_id=str(value_id)),
+                            driver=self.connection.driver)
+            sizes.append(size)
+
+        return sorted(sizes, key=lambda k: k.price)
+
+    def list_nodes(self):
+        """
+        List available Nodes
+        """
+        result = self.connection.request('/server/cloud/').object
+        nodes = [self._to_node(value) for value in result.get('data', [])]
+        return nodes
+
+    def rename_node(self, node, name, hostname=None):
+        "rename a cloud server, optionally specify hostname too"
+        data = {'name': name}
+        if hostname:
+            data['hostname'] = hostname
+        params = urlencode(data)
+        result = self.connection.request('/server/cloud/%s/' % node.id,
+                                         data=params, method='PUT').object
+        return result.get('response') in VALID_RESPONSE_CODES
+
+    def reboot_node(self, node):
+        "reboot a running node"
+        result = self.connection.request('/server/cloud/%s/initiator/restart/'
+                                         % node.id, method='POST').object
+        return result.get('response') in VALID_RESPONSE_CODES
+
+    def ex_start_node(self, node):
+        "start a stopped node"
+        result = self.connection.request('/server/cloud/%s/initiator/start/'
+                                         % node.id, method='POST').object
+        return result.get('response') in VALID_RESPONSE_CODES
+
+    def ex_stop_node(self, node):
+        "stop a running node"
+        result = self.connection.request('/server/cloud/%s/initiator/stop/'
+                                         % node.id, method='POST').object
+        return result.get('response') in VALID_RESPONSE_CODES
+
+    def destroy_node(self, node):
+        "destroy a node"
+        result = self.connection.request('/server/cloud/%s/' % node.id,
+                                         method='DELETE').object
+        return result.get('response') in VALID_RESPONSE_CODES
+
+    def list_all_keys(self, key_group=None):
+        """list console and server keys
+           if key_group is specified, show keys with this key_group only
+           eg key_group=4 for console password keys
+        """
+        result = self.connection.request('/key/').object
+        keys = [self._to_ssh_key(value) for value in result.get('data', [])]
+        if key_group:
+            keys = [key for key in keys if
+                    key.key_group == key_group]
+        return keys
+
+    def list_ssh_keys(self):
+        "list ssh keys keys"
+        result = self.connection.request('/key/sshrsa/').object
+        keys = [self._to_ssh_key(value) for value in result.get('data', [])]
+        return keys
+
+    def list_password_keys(self):
+        "list password console and password server keys"
+        result = self.connection.request('/key/password/').object
+        keys = [self._to_ssh_key(value) for value in result.get('data', [])]
+        return keys
+
+    def add_ssh_key(self, name, public_key, key_group=1):
+        """Add an ssh key, given the public key and name
+           Returns the id of the created ssh key
+        """
+        data = {
+            'name': name,
+            'public_key': public_key,
+            'key_group': key_group
+            #key_group: The group for the key where Server=1 and Console=4
+        }
+        params = urlencode(data)
+        try:
+            result = self.connection.request('/key/sshrsa/', data=params,
+                                             method='POST').object
+        except Exception:
+            e = sys.exc_info()[1]
+            raise e
+        return result.get('data', {}).get('id', '')
+
+    def add_password_key(self, name, password=None, key_group=4):
+        """Add a password key, given the name and password
+           If password not specified, create a random password with
+           lowercase strings and numbers
+
+           Returns the id of the created ssh key
+        """
+        if not password:
+            password = self.random_password()
+        data = {
+            'name': name,
+            'password': password,
+            'key_group': key_group
+             #key_group: The group for the key, where Server=1 and Console=4
+        }
+        params = urlencode(data)
+        try:
+            result = self.connection.request('/key/password/', data=params,
+                                             method='POST').object
+        except Exception:
+            e = sys.exc_info()[1]
+            raise e
+        return result.get('data', {}).get('id', '')
+
+    def delete_ssh_key(self, key_id):
+        """Delete an ssh key, given it's id
+        """
+        try:
+            result = self.connection.request('/key/sshrsa/%s/' % key_id,
+                                             method='DELETE').object
+        except Exception:
+            e = sys.exc_info()[1]
+            raise e
+        return result.get('response') in VALID_RESPONSE_CODES
+
+    def delete_password_key(self, key_id):
+        """Delete a password, given it's id
+        """
+        try:
+            result = self.connection.request('/key/password/%s/' % key_id,
+                                             method='DELETE').object
+        except Exception:
+            e = sys.exc_info()[1]
+            raise e
+        return result.get('response') in VALID_RESPONSE_CODES
+
+    def create_node(self, **kwargs):
+        """Creates the node, and sets the ssh key, console key
+        NephoScale will respond with a 200-200 response after sending a valid
+        request. We then ask a few times until the server is created and
+        assigned a public IP address, so that deploy_node can be run
+
+        >>> from libcloud.compute.types import Provider
+        >>> from libcloud.compute.providers import get_driver
+        >>> driver = get_driver('nephoscale')
+        >>> conn = driver('nepho_user','nepho_password')
+        >>> conn.list_nodes()
+        >>> name = 'staging-server'
+        >>> size = conn.list_sizes()[0]
+        <NodeSize: id=27, ...name=CS025 - 0.25GB, 10GB, ...>
+        >>> image = conn.list_images()[9]
+        <NodeImage: id=49, name=Linux Ubuntu Server 10.04 LTS 64-bit, ...>
+        >>> server_keys = conn.list_all_keys(1)[0]
+        <NodeKey: id=71211, name=markos>
+        >>> server_key = conn.list_all_keys(1)[0].id
+        70867
+        >>> console_keys = conn.list_all_keys(4)[0]
+        <NodeKey: id=71213, name=mistio28434>
+        >>> console_key = conn.list_all_keys(4)[0].id
+        70907
+        >>> node = conn.create_node(name=name, size=size, image=image, \
+                console_key=console_key, server_key=server_key)
+
+        We can also create an ssh key, plus a console key and
+        deploy node with them
+        >>> server_key = conn.add_ssh_key(name, key)
+        71211
+        >>> console_key = conn.add_password_key(name)
+        71213
+
+        We can increase the number of connect attempts to wait until
+        the node is created, so that deploy_node has ip address to
+        deploy the script
+        We can also specify the location
+        >>> location = conn.list_locations()[0]
+        >>> node = conn.create_node(name=name,
+                                    size=size,
+                                    image=image,
+                                    console_key=console_key,
+                                    server_key=server_key,
+                                    connect_attempts=10,
+                                    location=location.id)
+        """
+        try:
+            name = kwargs.get('name')
+            if not name:
+                raise Exception("Name cannot be blank")
+            hostname = kwargs.get('hostname', name)
+            service_type = kwargs.get('size')
+            if not service_type:
+                raise Exception("Service type cannot be blank")
+            service_type = service_type.id
+            image = kwargs.get('image')
+            if not image:
+                raise Exception("Image cannot be blank")
+            image = image.id
+            server_key = kwargs.get('server_key', '')
+            console_key = kwargs.get('console_key', '')
+            zone = kwargs.get('location', '')
+            connect_attempts = int(kwargs.get('connect_attempts',
+                                              CONNECT_ATTEMPTS))
+        except Exception:
+            e = sys.exc_info()[1]
+            raise Exception("Error on create node: %s" % e)
+
+        data = {'name': name,
+                'hostname': hostname,
+                'service_type': service_type,
+                'image': image,
+                'server_key': server_key,
+                'console_key': console_key,
+                'zone': zone
+        }
+
+        params = urlencode(data)
+        try:
+            node = self.connection.request('/server/cloud/', data=params,
+                                           method='POST')
+        except Exception:
+            e = sys.exc_info()[1]
+            raise Exception("Failed to create node %s" % e)
+        node = Node(id='', name=name, state='', public_ips='', private_ips='',
+                    driver=self.connection.driver)
+        #try to get the created node public ips, for use in deploy_node
+        #At this point we don't have the id of the newly created Node,
+        #so search name in nodes
+
+        created_node = False
+        while connect_attempts > 0:
+            nodes = self.list_nodes()
+            created_node = [c_node for c_node in nodes if c_node.name == name]
+            if created_node:
+                return created_node[0]
+            else:
+                time.sleep(60)
+                connect_attempts = connect_attempts - 1
+        return node
+
+    def _to_node(self, data):
+        """Convert node in Node instances
+        """
+
+        state = NODE_STATE_MAP.get(data.get('power_status'), '4')
+        public_ips = []
+        private_ips = []
+        ip_addresses = data.get('ipaddresses', '')
+        #E.g. "ipaddresses": "198.120.14.6, 10.132.60.1"
+        if ip_addresses:
+            ip_addresses_list = ip_addresses.split(',')
+            for ip in ip_addresses_list:
+                ip = ip.replace(' ', '')
+                if ip.startswith('10.') or ip.startswith('192.168'):
+                    private_ips.append(ip)
+                else:
+                    public_ips.append(ip)
+        extra = {
+            'zone_data': data.get('zone'),
+            'zone': data.get('zone', {}).get('name'),
+            'image': data.get('image', {}).get('friendly_name'),
+            'create_time': data.get('create_time'),
+            'network_ports': data.get('network_ports'),
+            'is_console_enabled': data.get('is_console_enabled'),
+            'service_type': data.get('service_type', {}).get('friendly_name'),
+            'hostname': data.get('hostname')
+        }
+
+        node = Node(id=data.get('id'), name=data.get('name'), state=state,
+                    public_ips=public_ips, private_ips=private_ips,
+                    driver=self.connection.driver, extra=extra)
+        return node
+
+    def _to_ssh_key(self, data):
+        return NodeKey(id=data.get('id'),
+                      name=data.get('name'),
+                      password=data.get('password'),
+                      key_group=data.get('key_group'),
+                      public_key=data.get('public_key'))
+
+    def random_password(self, size=8,
+                        chars=string.ascii_lowercase + string.digits):
+        return ''.join(random.choice(chars) for x in range(size))
diff --git a/libcloud/compute/providers.py b/libcloud/compute/providers.py
index fbc9c63..32c60d6 100644
--- a/libcloud/compute/providers.py
+++ b/libcloud/compute/providers.py
@@ -126,7 +126,9 @@
     Provider.ABIQUO:
         ('libcloud.compute.drivers.abiquo', 'AbiquoNodeDriver'),
     Provider.DIGITAL_OCEAN:
-        ('libcloud.compute.drivers.digitalocean', 'DigitalOceanNodeDriver')
+        ('libcloud.compute.drivers.digitalocean', 'DigitalOceanNodeDriver'),
+    Provider.NEPHOSCALE:
+        ('libcloud.compute.drivers.nephoscale', 'NephoscaleNodeDriver')
 }
 
 
diff --git a/libcloud/compute/types.py b/libcloud/compute/types.py
index 926c750..aefbf5a 100644
--- a/libcloud/compute/types.py
+++ b/libcloud/compute/types.py
@@ -71,6 +71,7 @@ class Provider(object):
     :cvar KTUCLOUD: kt ucloud driver
     :cvar GRIDSPOT: Gridspot driver
     :cvar ABIQUO: Abiquo driver
+    @cvar NEPHOSCALE: NephoScale driver
     """
     DUMMY = 'dummy'
     EC2 = 'ec2_us_east'
@@ -117,6 +118,7 @@ class Provider(object):
     HOSTVIRTUAL = 'hostvirtual'
     ABIQUO = 'abiquo'
     DIGITAL_OCEAN = 'digitalocean'
+    NEPHOSCALE = 'nephoscale'
 
     # Deprecated constants which are still supported
     EC2_US_EAST = 'ec2_us_east'
diff --git a/libcloud/data/pricing.json b/libcloud/data/pricing.json
index 905d657..7a9b731 100644
--- a/libcloud/data/pricing.json
+++ b/libcloud/data/pricing.json
@@ -171,6 +171,22 @@
             "m3.2xlarge": 1.40
         },
 
+        "nephoscale" : {
+            "1": 0.60,
+            "3": 0.063,
+            "5": 0.031,
+            "7": 0.125,
+            "9": 0.188,
+            "11": 0.35,
+            "27": 0.0,
+            "46": 0.10,
+            "48": 0.15,
+            "50": 0.28,
+            "52": 0.48,
+            "54": 0.938,
+            "56": 0.75
+        },
+
         "nimbus" : {
             "m1.small": 0.0,
             "m1.large": 0.0,
-- 
1.8.4


From 727afb1731706b30c7667e9ffefd589253bba2d3 Mon Sep 17 00:00:00 2001
From: Markos Gogoulos <mgogoulos@unweb.me>
Date: Mon, 30 Sep 2013 16:56:50 +0300
Subject: [PATCH 2/8] fixes and improvements to the nephoscale driver

---
 libcloud/compute/drivers/nephoscale.py | 186 ++++++++++++++++++---------------
 1 file changed, 100 insertions(+), 86 deletions(-)

diff --git a/libcloud/compute/drivers/nephoscale.py b/libcloud/compute/drivers/nephoscale.py
index 05c8faf..3975a9c 100644
--- a/libcloud/compute/drivers/nephoscale.py
+++ b/libcloud/compute/drivers/nephoscale.py
@@ -35,12 +35,13 @@
 from libcloud.utils.py3 import urlencode
 
 from libcloud.compute.providers import Provider
+from libcloud.compute.base import is_private_subnet
 from libcloud.common.base import JsonResponse, ConnectionUserAndKey
 from libcloud.compute.types import NodeState, InvalidCredsError
 from libcloud.compute.base import (Node, NodeDriver, NodeImage, NodeSize,
                                     NodeLocation)
 
-API_HOST = "api.nephoscale.com"
+API_HOST = 'api.nephoscale.com'
 
 NODE_STATE_MAP = {
     'on': NodeState.RUNNING,
@@ -78,9 +79,9 @@ class NephoscaleResponse(JsonResponse):
     """
 
     def parse_error(self):
-        if self.status == 401:
+        if self.status == httplib.UNAUTHORIZED:
             raise InvalidCredsError('Authorization Failed')
-        if self.status == 404:
+        if self.status == httplib.NOT_FOUND:
             raise Exception("The resource you are looking for is not found.")
 
         return self.body
@@ -126,11 +127,25 @@ class NephoscaleNodeDriver(NodeDriver):
     features = {'create_node': ['ssh_key']}
 
     def __init__(self, *args, **kwargs):
-        """Instantiate the driver with nephoscale's user and password
+        """
+        Instantiate the driver with nephoscale's user and password
+
+        :keyword   key: user name (required)
+        :type    key: ``str``
+
+        :keyword   secret: password (required)
+        :type    secret: ``str``
+
+        :rtype: ``None``
         """
         super(NephoscaleNodeDriver, self).__init__(*args, **kwargs)
 
     def list_locations(self):
+        """
+        List available zones for deployment
+
+        :rtype: ``list`` of :class:`NodeLocation`
+        """
         result = self.connection.request('/datacenter/zone/').object
         locations = []
         for value in result.get('data', []):
@@ -143,7 +158,9 @@ def list_locations(self):
 
     def list_images(self):
         """
-        List available Images
+        List available images for deployment
+
+        :rtype: ``list`` of :class:`NodeImage`
         """
         result = self.connection.request('/image/server/').object
         images = []
@@ -165,7 +182,9 @@ def list_images(self):
 
     def list_sizes(self):
         """
-        List available Sizes
+        List available sizes containing prices
+
+        :rtype: ``list`` of :class:`NodeSize`
         """
         result = self.connection.request('/server/type/cloud/').object
         sizes = []
@@ -184,14 +203,16 @@ def list_sizes(self):
 
     def list_nodes(self):
         """
-        List available Nodes
+        List available nodes
+
+        :rtype: ``list`` of :class:`Node`
         """
         result = self.connection.request('/server/cloud/').object
         nodes = [self._to_node(value) for value in result.get('data', [])]
         return nodes
 
     def rename_node(self, node, name, hostname=None):
-        "rename a cloud server, optionally specify hostname too"
+        """rename a cloud server, optionally specify hostname too"""
         data = {'name': name}
         if hostname:
             data['hostname'] = hostname
@@ -201,54 +222,59 @@ def rename_node(self, node, name, hostname=None):
         return result.get('response') in VALID_RESPONSE_CODES
 
     def reboot_node(self, node):
-        "reboot a running node"
+        """reboot a running node"""
         result = self.connection.request('/server/cloud/%s/initiator/restart/'
                                          % node.id, method='POST').object
         return result.get('response') in VALID_RESPONSE_CODES
 
     def ex_start_node(self, node):
-        "start a stopped node"
+        """start a stopped node"""
         result = self.connection.request('/server/cloud/%s/initiator/start/'
                                          % node.id, method='POST').object
         return result.get('response') in VALID_RESPONSE_CODES
 
     def ex_stop_node(self, node):
-        "stop a running node"
+        """stop a running node"""
         result = self.connection.request('/server/cloud/%s/initiator/stop/'
                                          % node.id, method='POST').object
         return result.get('response') in VALID_RESPONSE_CODES
 
     def destroy_node(self, node):
-        "destroy a node"
+        """destroy a node"""
         result = self.connection.request('/server/cloud/%s/' % node.id,
                                          method='DELETE').object
         return result.get('response') in VALID_RESPONSE_CODES
 
-    def list_all_keys(self, key_group=None):
-        """list console and server keys
-           if key_group is specified, show keys with this key_group only
-           eg key_group=4 for console password keys
+    def list_keypairs(self, ssh=False, password=False, key_group=None):
         """
-        result = self.connection.request('/key/').object
-        keys = [self._to_ssh_key(value) for value in result.get('data', [])]
+        List available console and server keys
+
+        :keyword ssh: if specified, show ssh keys only
+        :type    ssh: ``bool``
+
+        :keyword password: if specified, show password keys only
+        :type    password: ``bool``
+
+        :keyword key_group: if specified, show keys with this key_group only
+                            eg key_group=4 for console password keys
+        :type    key_group: ``int``
+
+        :rtype: ``list`` of :class:`NodeKey`
+        """
+        if ssh:
+            result = self.connection.request('/key/sshrsa/').object
+        if password:
+            result = self.connection.request('/key/password/').object
+        if not (ssh or password):
+            result = self.connection.request('/key/').object
+        keys = [self._to_key(value) for value in result.get('data', [])]
+
         if key_group:
             keys = [key for key in keys if
                     key.key_group == key_group]
         return keys
 
-    def list_ssh_keys(self):
-        "list ssh keys keys"
-        result = self.connection.request('/key/sshrsa/').object
-        keys = [self._to_ssh_key(value) for value in result.get('data', [])]
-        return keys
-
-    def list_password_keys(self):
-        "list password console and password server keys"
-        result = self.connection.request('/key/password/').object
-        keys = [self._to_ssh_key(value) for value in result.get('data', [])]
-        return keys
-
-    def add_ssh_key(self, name, public_key, key_group=1):
+    def create_ssh_key(self, name, public_key, key_group=1):
         """Add an ssh key, given the public key and name
            Returns the id of the created ssh key
         """
@@ -259,15 +285,12 @@ def add_ssh_key(self, name, public_key, key_group=1):
             #key_group: The group for the key where Server=1 and Console=4
         }
         params = urlencode(data)
-        try:
-            result = self.connection.request('/key/sshrsa/', data=params,
-                                             method='POST').object
-        except Exception:
-            e = sys.exc_info()[1]
-            raise e
+
+        result = self.connection.request('/key/sshrsa/', data=params,
+                                         method='POST').object
         return result.get('data', {}).get('id', '')
 
-    def add_password_key(self, name, password=None, key_group=4):
+    def create_password_key(self, name, password=None, key_group=4):
         """Add a password key, given the name and password
            If password not specified, create a random password with
            lowercase strings and numbers
@@ -283,41 +306,27 @@ def add_password_key(self, name, password=None, key_group=4):
              #key_group: The group for the key, where Server=1 and Console=4
         }
         params = urlencode(data)
-        try:
-            result = self.connection.request('/key/password/', data=params,
-                                             method='POST').object
-        except Exception:
-            e = sys.exc_info()[1]
-            raise e
+        result = self.connection.request('/key/password/', data=params,
+                                         method='POST').object
         return result.get('data', {}).get('id', '')
 
-    def delete_ssh_key(self, key_id):
-        """Delete an ssh key, given it's id
+    def delete_keypair(self, key_id, ssh=False):
+        """Delete an ssh key or password given it's id
         """
-        try:
+        if ssh:
             result = self.connection.request('/key/sshrsa/%s/' % key_id,
                                              method='DELETE').object
-        except Exception:
-            e = sys.exc_info()[1]
-            raise e
-        return result.get('response') in VALID_RESPONSE_CODES
-
-    def delete_password_key(self, key_id):
-        """Delete a password, given it's id
-        """
-        try:
+        else:
             result = self.connection.request('/key/password/%s/' % key_id,
-                                             method='DELETE').object
-        except Exception:
-            e = sys.exc_info()[1]
-            raise e
+                                         method='DELETE').object
         return result.get('response') in VALID_RESPONSE_CODES
 
     def create_node(self, **kwargs):
         """Creates the node, and sets the ssh key, console key
         NephoScale will respond with a 200-200 response after sending a valid
-        request. We then ask a few times until the server is created and
-        assigned a public IP address, so that deploy_node can be run
+        request. If nowait=True is specified in the args, we then ask a few
+        times until the server is created and assigned a public IP address,
+        so that deploy_node can be run
 
         >>> from libcloud.compute.types import Provider
         >>> from libcloud.compute.providers import get_driver
@@ -329,22 +338,22 @@ def create_node(self, **kwargs):
         <NodeSize: id=27, ...name=CS025 - 0.25GB, 10GB, ...>
         >>> image = conn.list_images()[9]
         <NodeImage: id=49, name=Linux Ubuntu Server 10.04 LTS 64-bit, ...>
-        >>> server_keys = conn.list_all_keys(1)[0]
+        >>> server_keys = conn.list_keypairs(key_group=1)[0]
         <NodeKey: id=71211, name=markos>
-        >>> server_key = conn.list_all_keys(1)[0].id
+        >>> server_key = conn.list_keypairs(key_group=1)[0].id
         70867
-        >>> console_keys = conn.list_all_keys(4)[0]
+        >>> console_keys = conn.list_keypairs(key_group=4)[0]
         <NodeKey: id=71213, name=mistio28434>
-        >>> console_key = conn.list_all_keys(4)[0].id
+        >>> console_key = conn.list_keypairs(key_group=4)[0].id
         70907
         >>> node = conn.create_node(name=name, size=size, image=image, \
                 console_key=console_key, server_key=server_key)
 
         We can also create an ssh key, plus a console key and
         deploy node with them
-        >>> server_key = conn.add_ssh_key(name, key)
+        >>> server_key = conn.create_ssh_key(name, key)
         71211
-        >>> console_key = conn.add_password_key(name)
+        >>> console_key = conn.create_password_key(name)
         71213
 
         We can increase the number of connect attempts to wait until
@@ -358,6 +367,7 @@ def create_node(self, **kwargs):
                                     console_key=console_key,
                                     server_key=server_key,
                                     connect_attempts=10,
+                                    nowait=True,
                                     location=location.id)
         """
         try:
@@ -400,20 +410,25 @@ def create_node(self, **kwargs):
             raise Exception("Failed to create node %s" % e)
         node = Node(id='', name=name, state='', public_ips='', private_ips='',
                     driver=self.connection.driver)
-        #try to get the created node public ips, for use in deploy_node
-        #At this point we don't have the id of the newly created Node,
-        #so search name in nodes
-
-        created_node = False
-        while connect_attempts > 0:
-            nodes = self.list_nodes()
-            created_node = [c_node for c_node in nodes if c_node.name == name]
-            if created_node:
-                return created_node[0]
-            else:
-                time.sleep(60)
-                connect_attempts = connect_attempts - 1
-        return node
+
+        nowait = kwargs.get('ex_wait', False)
+        if not nowait:
+            return node
+        else:
+            #try to get the created node public ips, for use in deploy_node
+            #At this point we don't have the id of the newly created Node,
+            #so search name in nodes
+            created_node = False
+            while connect_attempts > 0:
+                nodes = self.list_nodes()
+                created_node = [c_node for c_node in nodes if
+                                         c_node.name == name]
+                if created_node:
+                    return created_node[0]
+                else:
+                    time.sleep(60)
+                    connect_attempts = connect_attempts - 1
+            return node
 
     def _to_node(self, data):
         """Convert node in Node instances
@@ -425,10 +440,9 @@ def _to_node(self, data):
         ip_addresses = data.get('ipaddresses', '')
         #E.g. "ipaddresses": "198.120.14.6, 10.132.60.1"
         if ip_addresses:
-            ip_addresses_list = ip_addresses.split(',')
-            for ip in ip_addresses_list:
+            for ip in ip_addresses.split(','):
                 ip = ip.replace(' ', '')
-                if ip.startswith('10.') or ip.startswith('192.168'):
+                if is_private_subnet(ip):
                     private_ips.append(ip)
                 else:
                     public_ips.append(ip)
@@ -448,7 +462,7 @@ def _to_node(self, data):
                     driver=self.connection.driver, extra=extra)
         return node
 
-    def _to_ssh_key(self, data):
+    def _to_key(self, data):
         return NodeKey(id=data.get('id'),
                       name=data.get('name'),
                       password=data.get('password'),
-- 
1.8.4


From 15e23794337b925e8d171258775b0e1a512d02b2 Mon Sep 17 00:00:00 2001
From: Markos Gogoulos <mgogoulos@unweb.me>
Date: Mon, 30 Sep 2013 17:11:32 +0300
Subject: [PATCH 3/8] replace self.driver.connection with self

---
 libcloud/compute/drivers/nephoscale.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/libcloud/compute/drivers/nephoscale.py b/libcloud/compute/drivers/nephoscale.py
index 3975a9c..176f6ea 100644
--- a/libcloud/compute/drivers/nephoscale.py
+++ b/libcloud/compute/drivers/nephoscale.py
@@ -152,7 +152,7 @@ def list_locations(self):
             location = NodeLocation(id=value.get('id'),
                                     name=value.get('name'),
                                     country='US',
-                                    driver=self.connection.driver)
+                                    driver=self)
             locations.append(location)
         return locations
 
@@ -175,7 +175,7 @@ def list_images(self):
             }
             image = NodeImage(id=value.get('id'),
                               name=value.get('friendly_name'),
-                              driver=self.connection.driver,
+                              driver=self,
                               extra=extra)
             images.append(image)
         return images
@@ -196,7 +196,7 @@ def list_sizes(self):
                             disk=value.get('storage'),
                             bandwidth=None,
                             price=self._get_size_price(size_id=str(value_id)),
-                            driver=self.connection.driver)
+                            driver=self)
             sizes.append(size)
 
         return sorted(sizes, key=lambda k: k.price)
@@ -409,7 +409,7 @@ def create_node(self, **kwargs):
             e = sys.exc_info()[1]
             raise Exception("Failed to create node %s" % e)
         node = Node(id='', name=name, state='', public_ips='', private_ips='',
-                    driver=self.connection.driver)
+                    driver=self)
 
         nowait = kwargs.get('ex_wait', False)
         if not nowait:
@@ -459,7 +459,7 @@ def _to_node(self, data):
 
         node = Node(id=data.get('id'), name=data.get('name'), state=state,
                     public_ips=public_ips, private_ips=private_ips,
-                    driver=self.connection.driver, extra=extra)
+                    driver=self, extra=extra)
         return node
 
     def _to_key(self, data):
-- 
1.8.4


From 5df107af36d1f0b47fec784eb29634848bcca153 Mon Sep 17 00:00:00 2001
From: Markos Gogoulos <mgogoulos@unweb.me>
Date: Mon, 30 Sep 2013 18:32:07 +0300
Subject: [PATCH 4/8] nephoscale driver refactoring

---
 libcloud/compute/drivers/nephoscale.py | 87 ++++++++++++++--------------------
 1 file changed, 35 insertions(+), 52 deletions(-)

diff --git a/libcloud/compute/drivers/nephoscale.py b/libcloud/compute/drivers/nephoscale.py
index 176f6ea..516c62e 100644
--- a/libcloud/compute/drivers/nephoscale.py
+++ b/libcloud/compute/drivers/nephoscale.py
@@ -126,20 +126,6 @@ class NephoscaleNodeDriver(NodeDriver):
     connectionCls = NephoscaleConnection
     features = {'create_node': ['ssh_key']}
 
-    def __init__(self, *args, **kwargs):
-        """
-        Instantiate the driver with nephoscale's user and password
-
-        :keyword   key: user name (required)
-        :type    key: ``str``
-
-        :keyword   secret: password (required)
-        :type    secret: ``str``
-
-        :rtype: ``None``
-        """
-        super(NephoscaleNodeDriver, self).__init__(*args, **kwargs)
-
     def list_locations(self):
         """
         List available zones for deployment
@@ -245,7 +231,7 @@ def destroy_node(self, node):
                                          method='DELETE').object
         return result.get('response') in VALID_RESPONSE_CODES
 
-    def list_keypairs(self, ssh=False, password=False, key_group=None):
+    def ex_list_keypairs(self, ssh=False, password=False, key_group=None):
         """
         List available console and server keys
 
@@ -274,43 +260,40 @@ def list_keypairs(self, ssh=False, password=False, key_group=None):
                     key.key_group == key_group]
         return keys
 
-    def create_ssh_key(self, name, public_key, key_group=1):
-        """Add an ssh key, given the public key and name
-           Returns the id of the created ssh key
+    def ex_create_keypair(self, name, public_key=None, password=None,
+                       key_group=None):
+        """Creates a key, ssh or password, for server or console
+           The group for the key (key_group) is 1 for Server and 4 for Console
+           Returns the id of the created key
         """
-        data = {
-            'name': name,
-            'public_key': public_key,
-            'key_group': key_group
-            #key_group: The group for the key where Server=1 and Console=4
-        }
-        params = urlencode(data)
+        if public_key:
+            if not key_group:
+                key_group = 1
+            data = {
+                'name': name,
+                'public_key': public_key,
+                'key_group': key_group
 
-        result = self.connection.request('/key/sshrsa/', data=params,
+            }
+            params = urlencode(data)
+            result = self.connection.request('/key/sshrsa/', data=params,
                                          method='POST').object
-        return result.get('data', {}).get('id', '')
-
-    def create_password_key(self, name, password=None, key_group=4):
-        """Add a password key, given the name and password
-           If password not specified, create a random password with
-           lowercase strings and numbers
-
-           Returns the id of the created ssh key
-        """
-        if not password:
-            password = self.random_password()
-        data = {
-            'name': name,
-            'password': password,
-            'key_group': key_group
-             #key_group: The group for the key, where Server=1 and Console=4
-        }
-        params = urlencode(data)
-        result = self.connection.request('/key/password/', data=params,
+        else:
+            if not key_group:
+                key_group = 4
+            if not password:
+                password = self.random_password()
+                data = {
+                    'name': name,
+                    'password': password,
+                    'key_group': key_group
+                }
+            params = urlencode(data)
+            result = self.connection.request('/key/password/', data=params,
                                          method='POST').object
         return result.get('data', {}).get('id', '')
 
-    def delete_keypair(self, key_id, ssh=False):
+    def ex_delete_keypair(self, key_id, ssh=False):
         """Delete an ssh key or password given it's id
         """
         if ssh:
@@ -338,22 +321,22 @@ def create_node(self, **kwargs):
         <NodeSize: id=27, ...name=CS025 - 0.25GB, 10GB, ...>
         >>> image = conn.list_images()[9]
         <NodeImage: id=49, name=Linux Ubuntu Server 10.04 LTS 64-bit, ...>
-        >>> server_keys = conn.list_keypairs(key_group=1)[0]
+        >>> server_keys = conn.ex_list_keypairs(key_group=1)[0]
         <NodeKey: id=71211, name=markos>
-        >>> server_key = conn.list_keypairs(key_group=1)[0].id
+        >>> server_key = conn.ex_list_keypairs(key_group=1)[0].id
         70867
-        >>> console_keys = conn.list_keypairs(key_group=4)[0]
+        >>> console_keys = conn.ex_list_keypairs(key_group=4)[0]
         <NodeKey: id=71213, name=mistio28434>
-        >>> console_key = conn.list_keypairs(key_group=4)[0].id
+        >>> console_key = conn.ex_list_keypairs(key_group=4)[0].id
         70907
         >>> node = conn.create_node(name=name, size=size, image=image, \
                 console_key=console_key, server_key=server_key)
 
         We can also create an ssh key, plus a console key and
         deploy node with them
-        >>> server_key = conn.create_ssh_key(name, key)
+        >>> server_key = conn.ex_create_keypair(name, public_key=key)
         71211
-        >>> console_key = conn.create_password_key(name)
+        >>> console_key = conn.ex_create_keypair(name, key_group=4)
         71213
 
         We can increase the number of connect attempts to wait until
-- 
1.8.4


From 51ab856b6ec26949b1a99bcfed05738a4cc20e78 Mon Sep 17 00:00:00 2001
From: Markos Gogoulos <mgogoulos@unweb.me>
Date: Mon, 30 Sep 2013 18:44:46 +0300
Subject: [PATCH 5/8] use random password generate as in libcloud

---
 libcloud/compute/drivers/nephoscale.py | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/libcloud/compute/drivers/nephoscale.py b/libcloud/compute/drivers/nephoscale.py
index 516c62e..29877be 100644
--- a/libcloud/compute/drivers/nephoscale.py
+++ b/libcloud/compute/drivers/nephoscale.py
@@ -24,6 +24,8 @@
 import string
 import random
 import time
+import os
+import binascii
 
 try:
     import simplejson as json
@@ -452,6 +454,8 @@ def _to_key(self, data):
                       key_group=data.get('key_group'),
                       public_key=data.get('public_key'))
 
-    def random_password(self, size=8,
-                        chars=string.ascii_lowercase + string.digits):
-        return ''.join(random.choice(chars) for x in range(size))
+    def random_password(self, size=8):
+        value = os.urandom(size)
+        password = binascii.hexlify(value).decode('ascii')
+        return password[:size]
+        
-- 
1.8.4


From d4db1664a5d8ddb3d06a9ece467973a32e59c75d Mon Sep 17 00:00:00 2001
From: Markos Gogoulos <mgogoulos@unweb.me>
Date: Wed, 2 Oct 2013 19:01:43 +0300
Subject: [PATCH 6/8] add testing for nephoscale, refactor create_node

---
 libcloud/compute/drivers/nephoscale.py             |  23 +-
 .../compute/fixtures/nephoscale/list_images.json   | 243 +++++++++++++++++++++
 .../compute/fixtures/nephoscale/list_keys.json     |  25 +++
 .../fixtures/nephoscale/list_locations.json        |  31 +++
 .../compute/fixtures/nephoscale/list_nodes.json    | 161 ++++++++++++++
 .../fixtures/nephoscale/list_password_keys.json    |  18 ++
 .../compute/fixtures/nephoscale/list_sizes.json    | 178 +++++++++++++++
 .../compute/fixtures/nephoscale/list_ssh_keys.json |  18 ++
 .../fixtures/nephoscale/success_action.json        |  11 +
 libcloud/test/compute/test_nephoscale.py           | 189 ++++++++++++++++
 10 files changed, 880 insertions(+), 17 deletions(-)
 create mode 100644 libcloud/test/compute/fixtures/nephoscale/list_images.json
 create mode 100644 libcloud/test/compute/fixtures/nephoscale/list_keys.json
 create mode 100644 libcloud/test/compute/fixtures/nephoscale/list_locations.json
 create mode 100644 libcloud/test/compute/fixtures/nephoscale/list_nodes.json
 create mode 100644 libcloud/test/compute/fixtures/nephoscale/list_password_keys.json
 create mode 100644 libcloud/test/compute/fixtures/nephoscale/list_sizes.json
 create mode 100644 libcloud/test/compute/fixtures/nephoscale/list_ssh_keys.json
 create mode 100644 libcloud/test/compute/fixtures/nephoscale/success_action.json
 create mode 100644 libcloud/test/compute/test_nephoscale.py

diff --git a/libcloud/compute/drivers/nephoscale.py b/libcloud/compute/drivers/nephoscale.py
index 29877be..f2f9cd9 100644
--- a/libcloud/compute/drivers/nephoscale.py
+++ b/libcloud/compute/drivers/nephoscale.py
@@ -306,7 +306,9 @@ def ex_delete_keypair(self, key_id, ssh=False):
                                          method='DELETE').object
         return result.get('response') in VALID_RESPONSE_CODES
 
-    def create_node(self, **kwargs):
+
+    def create_node(self, name, size, image, server_key=None,
+                    console_key=None, zone=None, **kwargs):
         """Creates the node, and sets the ssh key, console key
         NephoScale will respond with a 200-200 response after sending a valid
         request. If nowait=True is specified in the args, we then ask a few
@@ -353,26 +355,14 @@ def create_node(self, **kwargs):
                                     server_key=server_key,
                                     connect_attempts=10,
                                     nowait=True,
-                                    location=location.id)
+                                    zone=location.id)
         """
         try:
-            name = kwargs.get('name')
-            if not name:
-                raise Exception("Name cannot be blank")
             hostname = kwargs.get('hostname', name)
-            service_type = kwargs.get('size')
-            if not service_type:
-                raise Exception("Service type cannot be blank")
-            service_type = service_type.id
-            image = kwargs.get('image')
-            if not image:
-                raise Exception("Image cannot be blank")
+            service_type = size.id
             image = image.id
-            server_key = kwargs.get('server_key', '')
-            console_key = kwargs.get('console_key', '')
-            zone = kwargs.get('location', '')
             connect_attempts = int(kwargs.get('connect_attempts',
-                                              CONNECT_ATTEMPTS))
+                                   CONNECT_ATTEMPTS))
         except Exception:
             e = sys.exc_info()[1]
             raise Exception("Error on create node: %s" % e)
@@ -458,4 +448,3 @@ def random_password(self, size=8):
         value = os.urandom(size)
         password = binascii.hexlify(value).decode('ascii')
         return password[:size]
-        
diff --git a/libcloud/test/compute/fixtures/nephoscale/list_images.json b/libcloud/test/compute/fixtures/nephoscale/list_images.json
new file mode 100644
index 0000000..1ede35d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/nephoscale/list_images.json
@@ -0,0 +1,243 @@
+{
+    "success": true,
+    "total_count": 18,
+    "subcode": 0,
+    "message": "Your request was processed successfully.",
+    "data": [
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux CentOS 5.5 32-bit",
+            "uri": "https://api.nephoscale.com/image/server/3/",
+            "max_memory": 128,
+            "id": 3,
+            "is_default": true,
+            "create_time": "2010-12-20 14:25:36",
+            "architecture": "x86",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux CentOS 5.5 64-bit",
+            "uri": "https://api.nephoscale.com/image/server/5/",
+            "max_memory": 128,
+            "id": 5,
+            "is_default": true,
+            "create_time": "2010-12-20 14:25:36",
+            "architecture": "x86_64",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Windows Server 2008 64-bit",
+            "uri": "https://api.nephoscale.com/image/server/21/",
+            "max_memory": 128,
+            "id": 21,
+            "is_default": true,
+            "create_time": "2010-12-20 14:25:36",
+            "architecture": "x86_64",
+            "base_type": "windows"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux Debian Server 5.05 32-bit",
+            "uri": "https://api.nephoscale.com/image/server/23/",
+            "max_memory": 128,
+            "id": 23,
+            "is_default": true,
+            "create_time": "2010-12-20 16:51:20",
+            "architecture": "x86",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux Debian Server 5.05 64-bit",
+            "uri": "https://api.nephoscale.com/image/server/25/",
+            "max_memory": 128,
+            "id": 25,
+            "is_default": true,
+            "create_time": "2010-12-20 16:55:42",
+            "architecture": "x86_64",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Windows Server 2003 Enterprise 64-bit",
+            "uri": "https://api.nephoscale.com/image/server/33/",
+            "max_memory": 128,
+            "id": 33,
+            "is_default": true,
+            "create_time": "2011-03-02 14:20:49",
+            "architecture": "x86_64",
+            "base_type": "windows"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux CentOS 5.7 64-bit",
+            "uri": "https://api.nephoscale.com/image/server/41/",
+            "max_memory": 128,
+            "id": 41,
+            "is_default": true,
+            "create_time": "2011-09-19 17:30:04",
+            "architecture": "x86_64",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux Ubuntu Server 10.04 LTS 32-bit",
+            "uri": "https://api.nephoscale.com/image/server/43/",
+            "max_memory": 128,
+            "id": 43,
+            "is_default": true,
+            "create_time": "2011-10-01 02:26:17",
+            "architecture": "x86",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux CentOS 5.7 32-bit",
+            "uri": "https://api.nephoscale.com/image/server/45/",
+            "max_memory": 128,
+            "id": 45,
+            "is_default": true,
+            "create_time": "2011-10-05 19:41:30",
+            "architecture": "x86",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux Ubuntu Server 10.04 LTS 64-bit",
+            "uri": "https://api.nephoscale.com/image/server/49/",
+            "max_memory": 128,
+            "id": 49,
+            "is_default": true,
+            "create_time": "2011-10-08 05:01:10",
+            "architecture": "x86_64",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux Debian Server 6.0.3 64-bit",
+            "uri": "https://api.nephoscale.com/image/server/51/",
+            "max_memory": 128,
+            "id": 51,
+            "is_default": true,
+            "create_time": "2011-10-08 19:54:41",
+            "architecture": "x86_64",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux Debian 5.0.9 64-bit",
+            "uri": "https://api.nephoscale.com/image/server/55/",
+            "max_memory": 128,
+            "id": 55,
+            "is_default": false,
+            "create_time": "2011-10-13 12:53:36",
+            "architecture": "x86_64",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux Debian 5.0.9 32-bit",
+            "uri": "https://api.nephoscale.com/image/server/57/",
+            "max_memory": 128,
+            "id": 57,
+            "is_default": false,
+            "create_time": "2011-10-13 12:55:09",
+            "architecture": "x86",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux CentOS 6.2 64-bit",
+            "uri": "https://api.nephoscale.com/image/server/59/",
+            "max_memory": 128,
+            "id": 59,
+            "is_default": true,
+            "create_time": "2011-10-15 17:11:34",
+            "architecture": "x86_64",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux CentOS 5.8 64-bit",
+            "uri": "https://api.nephoscale.com/image/server/64/",
+            "max_memory": 128,
+            "id": 64,
+            "is_default": true,
+            "create_time": "2012-03-28 19:54:10",
+            "architecture": "x86_64",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Linux Ubuntu Server 12.04 LTS 64-bit",
+            "uri": "https://api.nephoscale.com/image/server/75/",
+            "max_memory": 128,
+            "id": 75,
+            "is_default": true,
+            "create_time": "2012-05-18 08:41:03",
+            "architecture": "x86_64",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "cloud",
+            "is_active": true,
+            "friendly_name": "VOD Cloud Storage Proxy (FTP:HTTP)",
+            "uri": "https://api.nephoscale.com/image/server/101/",
+            "max_memory": 128,
+            "id": 101,
+            "is_default": false,
+            "create_time": "2012-08-30 08:49:55",
+            "architecture": "x86_64",
+            "base_type": "linux"
+        },
+        {
+            "max_cpu": 64,
+            "deployable_type": "both",
+            "is_active": true,
+            "friendly_name": "Debian 7.1 64-bit",
+            "uri": "https://api.nephoscale.com/image/server/177/",
+            "max_memory": 128,
+            "id": 177,
+            "is_default": true,
+            "create_time": "2013-09-10 16:12:10",
+            "architecture": "x86_64",
+            "base_type": "linux"
+        }
+    ],
+    "response": 200
+}
diff --git a/libcloud/test/compute/fixtures/nephoscale/list_keys.json b/libcloud/test/compute/fixtures/nephoscale/list_keys.json
new file mode 100644
index 0000000..f6f9205
--- /dev/null
+++ b/libcloud/test/compute/fixtures/nephoscale/list_keys.json
@@ -0,0 +1,25 @@
+{
+    "success": true,
+    "total_count": 2,
+    "subcode": 0,
+    "message": "Your request was processed successfully.",
+    "data": [
+        {
+            "name": "mistio-ssh",
+            "key_group": 1,
+            "uri": "https://api.nephoscale.com/key/sshrsa/72209/",
+            "key_type": 2,
+            "create_time": "2013-10-02 07:24:37",
+            "id": 72209
+        },
+        {
+            "name": "mistio-testing",
+            "key_group": 4,
+            "uri": "https://api.nephoscale.com/key/password/72211/",
+            "key_type": 1,
+            "create_time": "2013-10-02 07:27:10",
+            "id": 72211
+        }
+    ],
+    "response": 200
+}
diff --git a/libcloud/test/compute/fixtures/nephoscale/list_locations.json b/libcloud/test/compute/fixtures/nephoscale/list_locations.json
new file mode 100644
index 0000000..952fac4
--- /dev/null
+++ b/libcloud/test/compute/fixtures/nephoscale/list_locations.json
@@ -0,0 +1,31 @@
+{
+    "success": true,
+    "total_count": 2,
+    "subcode": 0,
+    "message": "Your request was processed successfully.",
+    "data": [
+        {
+            "datacenter": {
+                "airport_code": "SJC",
+                "name": "SJC-1",
+                "uri": "https://api.nephoscale.com/datacenter/1/",
+                "id": 1
+            },
+            "uri": "https://api.nephoscale.com/datacenter/zone/86945/",
+            "id": 86945,
+            "name": "SJC-1"
+        },
+        {
+            "datacenter": {
+                "airport_code": "RIC",
+                "name": "RIC-1",
+                "uri": "https://api.nephoscale.com/datacenter/3/",
+                "id": 3
+            },
+            "uri": "https://api.nephoscale.com/datacenter/zone/87729/",
+            "id": 87729,
+            "name": "RIC-1"
+        }
+    ],
+    "response": 200
+}
diff --git a/libcloud/test/compute/fixtures/nephoscale/list_nodes.json b/libcloud/test/compute/fixtures/nephoscale/list_nodes.json
new file mode 100644
index 0000000..7fdff6c
--- /dev/null
+++ b/libcloud/test/compute/fixtures/nephoscale/list_nodes.json
@@ -0,0 +1,161 @@
+{
+    "success": true,
+    "total_count": 2,
+    "subcode": 0,
+    "message": "Your request was processed successfully.",
+    "data": [
+        {
+            "server_keys": [
+                {
+                    "key_type": 2,
+                    "key_group": 1,
+                    "id": 71757,
+                    "uri": "https://api.nephoscale.com/key/sshrsa/71157/"
+                }
+            ],
+            "name": "mongodb-staging",
+            "zone": {
+                "uri": "https://api.nephoscale.com/datacenter/zone/88211/",
+                "id": 87729,
+                "name": "RIC-1"
+            },
+            "image": {
+                "max_cpu": 64,
+                "deployable_type": "both",
+                "is_active": true,
+                "friendly_name": "Linux Ubuntu Server 10.04 LTS 64-bit",
+                "uri": "https://api.nephoscale.com/image/server/49/",
+                "max_memory": 128,
+                "id": 49,
+                "is_default": true,
+                "create_time": "2011-10-08 05:01:10",
+                "architecture": "x86_64",
+                "has_agent": true,
+                "base_type": "linux"
+            },
+            "hostname": "mongodb-staging",
+            "podzone": "P1A2",
+            "uri": "https://api.nephoscale.com/server/cloud/87241/",
+            "ipaddresses": "198.89.117.16",
+            "power_status": "on",
+            "create_time": "2013-09-25 07:38:53",
+            "postinit_state": 1,
+            "console_keys": [
+                {
+                    "key_type": 1,
+                    "key_group": 4,
+                    "id": 71761,
+                    "uri": "https://api.nephoscale.com/key/password/71761/"
+                }
+            ],
+            "memory": 512,
+            "service_type": {
+                "sku": {
+                    "name": "CS05",
+                    "description": "Cloud Server 0.5 GB RAM, 1 Core"
+                },
+                "uri": "https://api.nephoscale.com/server/type/cloud/5/",
+                "friendly_name": "CS05 - 0.5GB, 1Core, 25GB",
+                "id": 5,
+                "billable_type": 1
+            },
+            "network_ports": [
+                {
+                    "macaddress": "00:16:3e:06:dc:41",
+                    "devname": "eth0",
+                    "network_domain": {
+                        "domain_type": 0,
+                        "name": "default_public_network_RIC"
+                    }
+                },
+                {
+                    "macaddress": "00:16:3e:06:dc:45",
+                    "devname": "eth1",
+                    "network_domain": {
+                        "domain_type": 1,
+                        "name": "default_private_network_RIC"
+                    }
+                }
+            ],
+            "id": 88241,
+            "is_console_enabled": true
+        },
+        {
+            "server_keys": [
+                {
+                    "key_type": 2,
+                    "key_group": 1,
+                    "id": 72049,
+                    "uri": "https://api.nephoscale.com/key/sshrsa/72049/"
+                }
+            ],
+            "name": "backup-server2",
+            "zone": {
+                "uri": "https://api.nephoscale.com/datacenter/zone/88751/",
+                "id": 87729,
+                "name": "RIC-1"
+            },
+            "image": {
+                "max_cpu": 64,
+                "deployable_type": "both",
+                "is_active": true,
+                "friendly_name": "Linux Debian Server 6.0.3 64-bit",
+                "uri": "https://api.nephoscale.com/image/server/51/",
+                "max_memory": 128,
+                "id": 51,
+                "is_default": true,
+                "create_time": "2011-10-08 19:54:41",
+                "architecture": "x86_64",
+                "has_agent": true,
+                "base_type": "linux"
+            },
+            "hostname": "backup-server2",
+            "podzone": "P1A2",
+            "uri": "https://api.nephoscale.com/server/cloud/88751/",
+            "ipaddresses": "198.89.112.115",
+            "power_status": "on",
+            "create_time": "2013-10-02 05:02:50",
+            "postinit_state": 1,
+            "console_keys": [
+                {
+                    "key_type": 1,
+                    "key_group": 4,
+                    "id": 72165,
+                    "uri": "https://api.nephoscale.com/key/password/72165/"
+                }
+            ],
+            "memory": 512,
+            "service_type": {
+                "sku": {
+                    "name": "CS05",
+                    "description": "Cloud Server 0.5 GB RAM, 1 Core"
+                },
+                "uri": "https://api.nephoscale.com/server/type/cloud/5/",
+                "friendly_name": "CS05 - 0.5GB, 1Core, 25GB",
+                "id": 5,
+                "billable_type": 1
+            },
+            "network_ports": [
+                {
+                    "macaddress": "00:16:3e:06:f5:2f",
+                    "devname": "eth0",
+                    "network_domain": {
+                        "domain_type": 0,
+                        "name": "default_public_network_RIC"
+                    }
+                },
+                {
+                    "macaddress": "00:16:3e:06:f5:33",
+                    "devname": "eth1",
+                    "network_domain": {
+                        "domain_type": 1,
+                        "name": "default_private_network_RIC"
+                    }
+                }
+            ],
+            "id": 88751,
+            "is_console_enabled": true
+        }
+    ],
+    "response": 200
+}
diff --git a/libcloud/test/compute/fixtures/nephoscale/list_password_keys.json b/libcloud/test/compute/fixtures/nephoscale/list_password_keys.json
new file mode 100644
index 0000000..ca3c629
--- /dev/null
+++ b/libcloud/test/compute/fixtures/nephoscale/list_password_keys.json
@@ -0,0 +1,18 @@
+{
+    "success": true,
+    "total_count": 1,
+    "subcode": 0,
+    "message": "Your request was processed successfully.",
+    "data": [
+        {
+            "name": "mistio-testing",
+            "key_group": 4,
+            "uri": "https://api.nephoscale.com/key/password/72211/",
+            "key_type": 1,
+            "create_time": "2013-10-02 07:27:10",
+            "password": "23d493j5",
+            "id": 72211
+        }
+    ],
+    "response": 200
+}
diff --git a/libcloud/test/compute/fixtures/nephoscale/list_sizes.json b/libcloud/test/compute/fixtures/nephoscale/list_sizes.json
new file mode 100644
index 0000000..c6d89f3
--- /dev/null
+++ b/libcloud/test/compute/fixtures/nephoscale/list_sizes.json
@@ -0,0 +1,178 @@
+{
+    "success": true,
+    "total_count": 13,
+    "subcode": 0,
+    "message": "Your request was processed successfully.",
+    "data": [
+        {
+            "sku": {
+                "name": "CS16.16",
+                "description": "Cloud Server 16 GB RAM, 16 Cores"
+            },
+            "storage": 800,
+            "ram": 16384,
+            "friendly_name": "CS16.16 - 16GB, 16Core, 800GB",
+            "uri": "https://api.nephoscale.com/server/type/cloud/1/",
+            "vcpus": 16,
+            "id": 1,
+            "billable_type": 1
+        },
+        {
+            "sku": {
+                "name": "CS1",
+                "description": "Cloud Server 1 GB RAM, 1 Core"
+            },
+            "storage": 50,
+            "ram": 1024,
+            "friendly_name": "CS1 - 1GB, 1Core, 50GB",
+            "uri": "https://api.nephoscale.com/server/type/cloud/3/",
+            "vcpus": 1,
+            "id": 3,
+            "billable_type": 1
+        },
+        {
+            "sku": {
+                "name": "CS05",
+                "description": "Cloud Server 0.5 GB RAM, 1 Core"
+            },
+            "storage": 25,
+            "ram": 512,
+            "friendly_name": "CS05 - 0.5GB, 1Core, 25GB",
+            "uri": "https://api.nephoscale.com/server/type/cloud/5/",
+            "vcpus": 1,
+            "id": 5,
+            "billable_type": 1
+        },
+        {
+            "sku": {
+                "name": "CS2.2",
+                "description": "Cloud Server 2 GB RAM, 2 Cores"
+            },
+            "storage": 100,
+            "ram": 2048,
+            "friendly_name": "CS2.2 - 2GB, 2Core, 100GB",
+            "uri": "https://api.nephoscale.com/server/type/cloud/7/",
+            "vcpus": 2,
+            "id": 7,
+            "billable_type": 1
+        },
+        {
+            "sku": {
+                "name": "CS4.4",
+                "description": "Cloud Server 4 GB RAM, 4 Cores"
+            },
+            "storage": 200,
+            "ram": 4096,
+            "friendly_name": "CS4.4 - 4GB, 4Core, 200GB",
+            "uri": "https://api.nephoscale.com/server/type/cloud/9/",
+            "vcpus": 4,
+            "id": 9,
+            "billable_type": 1
+        },
+        {
+            "sku": {
+                "name": "CS8.8",
+                "description": "Cloud Server 8 GB RAM, 8 Cores"
+            },
+            "storage": 400,
+            "ram": 8192,
+            "friendly_name": "CS8.8 - 8GB, 8Core, 400GB",
+            "uri": "https://api.nephoscale.com/server/type/cloud/11/",
+            "vcpus": 8,
+            "id": 11,
+            "billable_type": 1
+        },
+        {
+            "sku": {
+                "name": "CS025",
+                "description": "Cloud Server 0.25 GB RAM"
+            },
+            "storage": 15,
+            "ram": 256,
+            "friendly_name": "CS025 - 0.25GB, 10GB",
+            "uri": "https://api.nephoscale.com/server/type/cloud/27/",
+            "vcpus": 1,
+            "id": 27,
+            "billable_type": 1
+        },
+        {
+            "sku": {
+                "name": "CS2.1",
+                "description": "Cloud Server 2 GB RAM, 1 Core"
+            },
+            "storage": 75,
+            "ram": 2048,
+            "friendly_name": "CS2.1 - 2GB, 1Core, 75GB",
+            "uri": "https://api.nephoscale.com/server/type/cloud/46/",
+            "vcpus": 1,
+            "id": 46,
+            "billable_type": 1
+        },
+        {
+            "sku": {
+                "name": "CS4.2",
+                "description": "Cloud Server 4 GB RAM, 2 Cores"
+            },
+            "storage": 150,
+            "ram": 4096,
+            "friendly_name": "CS4.2 - 4GB, 2Core, 150GB",
+            "uri": "https://api.nephoscale.com/server/type/cloud/48/",
+            "vcpus": 2,
+            "id": 48,
+            "billable_type": 1
+        },
+        {
+            "sku": {
+                "name": "CS8.4",
+                "description": "Cloud Server 8 GB RAM, 4 Cores"
+            },
+            "storage": 300,
+            "ram": 8192,
+            "friendly_name": "CS8.4 - 8GB, 4Core, 300GB",
+            "uri": "https://api.nephoscale.com/server/type/cloud/50/",
+            "vcpus": 4,
+            "id": 50,
+            "billable_type": 1
+        },
+        {
+            "sku": {
+                "name": "CS16.8",
+                "description": "Cloud Server 16 GB RAM, 8 Cores"
+            },
+            "storage": 600,
+            "ram": 16384,
+            "friendly_name": "CS16.8 - 16GB, 8Core, 600GB",
+            "uri": "https://api.nephoscale.com/server/type/cloud/52/",
+            "vcpus": 8,
+            "id": 52,
+            "billable_type": 1
+        },
+        {
+            "sku": {
+                "name": "CS32.16",
+                "description": "Cloud Server 32 GB RAM, 16 Cores"
+            },
+            "storage": 1200,
+            "ram": 32768,
+            "friendly_name": "CS32.16 - 32GB, 16Core, 1200GB",
+            "uri": "https://api.nephoscale.com/server/type/cloud/54/",
+            "vcpus": 16,
+            "id": 54,
+            "billable_type": 1
+        },
+        {
+            "sku": {
+                "name": "CS32.8",
+                "description": "Cloud Server 32 GB RAM, 8 Cores"
+            },
+            "storage": 1000,
+            "ram": 32768,
+            "friendly_name": "CS32.8 - 32GB, 8Core, 1000GB",
+            "uri": "https://api.nephoscale.com/server/type/cloud/56/",
+            "vcpus": 8,
+            "id": 56,
+            "billable_type": 1
+        }
+    ],
+    "response": 200
+}
diff --git a/libcloud/test/compute/fixtures/nephoscale/list_ssh_keys.json b/libcloud/test/compute/fixtures/nephoscale/list_ssh_keys.json
new file mode 100644
index 0000000..dc83a8f
--- /dev/null
+++ b/libcloud/test/compute/fixtures/nephoscale/list_ssh_keys.json
@@ -0,0 +1,18 @@
+{
+    "success": true,
+    "total_count": 1,
+    "subcode": 0,
+    "message": "Your request was processed successfully.",
+    "data": [
+        {
+            "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBs+gQwoeFNa+4pYz2AKz5Op7EqrzeP3YsyTKxx7P9gt4aSt5w8Z+lRn3p3CVG+th5i6lZqOxWgCZ1kp2KEKNbSsA2HWl3OwkY8IqHGSEeMrF+3A2Ncz88kUIAWzCswxPY4uqb/yA4EzEQDk7PJj7Q1DruObhOm7qyHT40n2KJ3TqHJQlV9XE3RcXSaQcwUt0YFXFMx8wkgy0NKqqSiQuH8RofyfnOABEzKAARGbcQjZWxh2ITzUmwMxUCBa0X5wvblgcE6/pRZN5Xq6NQr2XEU5Z48+mLy6asdasdwrM0v10Y7ojDL/TosK/8T5+d5yaRsvtBlBstDZhNWY31n5iCLxx user@mistio",
+            "name": "mistio-ssh",
+            "key_group": 1,
+            "uri": "https://api.nephoscale.com/key/sshrsa/72209/",
+            "key_type": 2,
+            "create_time": "2013-10-02 07:24:37",
+            "id": 72209
+        }
+    ],
+    "response": 200
+}
diff --git a/libcloud/test/compute/fixtures/nephoscale/success_action.json b/libcloud/test/compute/fixtures/nephoscale/success_action.json
new file mode 100644
index 0000000..62db155
--- /dev/null
+++ b/libcloud/test/compute/fixtures/nephoscale/success_action.json
@@ -0,0 +1,11 @@
+{
+    "subcode": 0,
+    "message": "Your request was processed successfully.",
+    "data": {
+        "id": 141229,
+        "resource_type": "/job",
+        "uri": "https://api.nephoscale.com/job/141229/"
+    },
+    "response": 202,
+    "success": true
+}
diff --git a/libcloud/test/compute/test_nephoscale.py b/libcloud/test/compute/test_nephoscale.py
new file mode 100644
index 0000000..256aa99
--- /dev/null
+++ b/libcloud/test/compute/test_nephoscale.py
@@ -0,0 +1,189 @@
+# 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.
+#
+#Created by Markos Gogoulos (https://mist.io)
+#
+
+import sys
+import unittest
+from libcloud.utils.py3 import httplib
+
+from libcloud.compute.drivers.nephoscale import NephoscaleNodeDriver
+from libcloud.compute.base import Node
+
+from libcloud.test import MockHttp
+from libcloud.test.compute import TestCaseMixin
+from libcloud.test.file_fixtures import ComputeFileFixtures
+from libcloud.common.types import InvalidCredsError, LibcloudError
+
+
+class NephoScaleTest(unittest.TestCase, TestCaseMixin):
+    def setUp(self):
+        NephoscaleNodeDriver.connectionCls.conn_classes = (
+                                  None, NephoscaleMockHttp)
+        self.driver = NephoscaleNodeDriver('user', 'password')
+
+    def test_list_sizes(self):
+        sizes = self.driver.list_sizes()
+        self.assertEqual(len(sizes), 13)
+        for size in sizes:
+            self.assertEqual(type(size.disk), int)
+            self.assertEqual(type(size.ram), int)
+
+    def test_list_images(self):
+        images = self.driver.list_images()
+        self.assertEqual(len(images), 18)
+        for image in images:
+            arch = image.extra.get('architecture')
+            self.assertTrue(arch.startswith('x86'))
+
+    def test_list_locations(self):
+        locations = self.driver.list_locations()
+        self.assertEqual(len(locations), 2)
+        self.assertEqual(locations[0].name, "SJC-1")
+
+    def test_list_nodes(self):
+        nodes = self.driver.list_nodes()
+        self.assertEqual(len(nodes), 2)
+        self.assertEqual(nodes[0].extra.get('zone'), 'RIC-1')
+        self.assertEqual(nodes[0].name, 'mongodb-staging')
+        self.assertEqual(nodes[0].extra.get('service_type'),
+                         u'CS05 - 0.5GB, 1Core, 25GB')
+
+    def test_list_keys(self):
+        keys = self.driver.ex_list_keypairs()
+        self.assertEqual(len(keys), 2)
+        self.assertEqual(keys[0].name, 'mistio-ssh')
+
+    def test_list_ssh_keys(self):
+        ssh_keys = self.driver.ex_list_keypairs(ssh=True)
+        self.assertEqual(len(ssh_keys), 1)
+        self.assertTrue(ssh_keys[0].public_key.startswith('ssh-rsa'))
+
+    def test_list_password_keys(self):
+        password_keys = self.driver.ex_list_keypairs(password=True)
+        self.assertEqual(len(password_keys), 1)
+        self.assertEquals(password_keys[0].password, '23d493j5')
+
+    def test_reboot_node(self):
+        node = self.driver.list_nodes()[0]
+        result = self.driver.reboot_node(node)
+        self.assertTrue(result)
+
+    def test_destroy_node(self):
+        node = self.driver.list_nodes()[0]
+        result = self.driver.destroy_node(node)
+        self.assertTrue(result)
+
+    def test_stop_node(self):
+        node = self.driver.list_nodes()[0]
+        result = self.driver.ex_stop_node(node)
+        self.assertTrue(result)
+
+    def test_start_node(self):
+        node = self.driver.list_nodes()[0]
+        result = self.driver.ex_start_node(node)
+        self.assertTrue(result)
+
+    def test_rename_node(self):
+        node = self.driver.list_nodes()[0]
+        result = self.driver.rename_node(node, 'new-name')
+        self.assertTrue(result)
+
+    def test_create_node(self):
+        name = 'mongodb-staging'
+        size = self.driver.list_sizes()[0]
+        image = self.driver.list_images()[3]
+        node = self.driver.create_node(name=name,
+                                       size=size,
+                                       nowait=True,
+                                       image=image)
+        self.assertEqual(node.name, 'mongodb-staging')
+
+    def test_create_node_no_name(self):
+        size = self.driver.list_sizes()[0]
+        image = self.driver.list_images()[3]
+        self.assertRaises(TypeError, self.driver.create_node, size=size,
+                          image=image)
+
+    def test_delete_ssh_keys(self):
+        key = self.driver.ex_delete_keypair(key_id=72209, ssh=True)
+
+    def test_delete_password_keys(self):
+        key = self.driver.ex_delete_keypair(key_id=72211)
+
+
+class NephoscaleMockHttp(MockHttp):
+    fixtures = ComputeFileFixtures('nephoscale')
+
+    def _server_type_cloud(self, method, url, body, headers):
+        body = self.fixtures.load('list_sizes.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _server_cloud(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load('success_action.json')
+        else:
+            body = self.fixtures.load('list_nodes.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _image_server(self, method, url, body, headers):
+        body = self.fixtures.load('list_images.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _datacenter_zone(self, method, url, body, headers):
+        body = self.fixtures.load('list_locations.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _key(self, method, url, body, headers):
+        body = self.fixtures.load('list_keys.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _key_sshrsa(self, method, url, body, headers):
+        body = self.fixtures.load('list_ssh_keys.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _key_password(self, method, url, body, headers):
+        body = self.fixtures.load('list_password_keys.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _server_cloud_88241(self, method, url, body, headers):
+        body = self.fixtures.load('success_action.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _server_cloud_88241_initiator_restart(self, method, url, body,
+                                              headers):
+        body = self.fixtures.load('success_action.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _server_cloud_88241_initiator_start(self, method, url, body, headers):
+        body = self.fixtures.load('success_action.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _server_cloud_88241_initiator_stop(self, method, url, body, headers):
+        body = self.fixtures.load('success_action.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _key_password_72211(self, method, url, body, headers):
+        body = self.fixtures.load('success_action.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _key_sshrsa_72209(self, method, url, body, headers):
+        body = self.fixtures.load('success_action.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())
-- 
1.8.4


From 6059ddc9688588bb5337b2b54d7101848e6b0dfd Mon Sep 17 00:00:00 2001
From: Markos Gogoulos <mgogoulos@unweb.me>
Date: Wed, 2 Oct 2013 19:25:18 +0300
Subject: [PATCH 7/8] python3 compatibility

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

diff --git a/libcloud/test/compute/test_nephoscale.py b/libcloud/test/compute/test_nephoscale.py
index 256aa99..7a83808 100644
--- a/libcloud/test/compute/test_nephoscale.py
+++ b/libcloud/test/compute/test_nephoscale.py
@@ -60,7 +60,7 @@ def test_list_nodes(self):
         self.assertEqual(nodes[0].extra.get('zone'), 'RIC-1')
         self.assertEqual(nodes[0].name, 'mongodb-staging')
         self.assertEqual(nodes[0].extra.get('service_type'),
-                         u'CS05 - 0.5GB, 1Core, 25GB')
+                         'CS05 - 0.5GB, 1Core, 25GB')
 
     def test_list_keys(self):
         keys = self.driver.ex_list_keypairs()
-- 
1.8.4


From 342f3ee3f64a8982ed599ea1162998ead1aa9eee Mon Sep 17 00:00:00 2001
From: Markos Gogoulos <mgogoulos@unweb.me>
Date: Fri, 4 Oct 2013 16:08:19 +0300
Subject: [PATCH 8/8] small addition on documentation and pep8 fix

---
 libcloud/compute/drivers/nephoscale.py | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/libcloud/compute/drivers/nephoscale.py b/libcloud/compute/drivers/nephoscale.py
index f2f9cd9..9cd1574 100644
--- a/libcloud/compute/drivers/nephoscale.py
+++ b/libcloud/compute/drivers/nephoscale.py
@@ -236,15 +236,21 @@ def destroy_node(self, node):
     def ex_list_keypairs(self, ssh=False, password=False, key_group=None):
         """
         List available console and server keys
-
-        :keyword ssh: if specified, show ssh keys only
+        There are two types of keys for NephoScale, ssh and password keys.
+        If run without arguments, lists all keys. Otherwise list only
+        ssh keys, or only password keys.
+        Password keys with key_group 4 are console keys. When a server
+        is created, it has two keys, one password or ssh key, and
+        one password console key.
+
+        :keyword ssh: if specified, show ssh keys only (optional)
         :type    ssh: ``bool``
 
-        :keyword password: if specified, show password keys only
+        :keyword password: if specified, show password keys only (optional)
         :type    password: ``bool``
 
         :keyword key_group: if specified, show keys with this key_group only
-                            eg key_group=4 for console password keys
+                            eg key_group=4 for console password keys (optional)
         :type    key_group: ``int``
 
         :rtype: ``list`` of :class:`NodeKey`
@@ -306,7 +312,6 @@ def ex_delete_keypair(self, key_id, ssh=False):
                                          method='DELETE').object
         return result.get('response') in VALID_RESPONSE_CODES
 
-
     def create_node(self, name, size, image, server_key=None,
                     console_key=None, zone=None, **kwargs):
         """Creates the node, and sets the ssh key, console key
-- 
1.8.4

