You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by je...@apache.org on 2010/12/24 20:16:49 UTC

svn commit: r1052557 - in /incubator/libcloud/trunk: libcloud/ libcloud/drivers/ test/ test/fixtures/brightbox/

Author: jerry
Date: Fri Dec 24 19:16:48 2010
New Revision: 1052557

URL: http://svn.apache.org/viewvc?rev=1052557&view=rev
Log:
Brightbox Cloud Driver; LIBCLOUD-63

Submitted By: Tim Fletcher <ma...@tfletcher.com>

Added:
    incubator/libcloud/trunk/libcloud/drivers/brightbox.py
    incubator/libcloud/trunk/test/fixtures/brightbox/
    incubator/libcloud/trunk/test/fixtures/brightbox/create_server.json
    incubator/libcloud/trunk/test/fixtures/brightbox/list_images.json
    incubator/libcloud/trunk/test/fixtures/brightbox/list_server_types.json
    incubator/libcloud/trunk/test/fixtures/brightbox/list_servers.json
    incubator/libcloud/trunk/test/fixtures/brightbox/list_zones.json
    incubator/libcloud/trunk/test/fixtures/brightbox/token.json
    incubator/libcloud/trunk/test/test_brightbox.py
Modified:
    incubator/libcloud/trunk/libcloud/drivers/__init__.py
    incubator/libcloud/trunk/libcloud/providers.py
    incubator/libcloud/trunk/libcloud/types.py
    incubator/libcloud/trunk/test/secrets.py-dist

Modified: incubator/libcloud/trunk/libcloud/drivers/__init__.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/drivers/__init__.py?rev=1052557&r1=1052556&r2=1052557&view=diff
==============================================================================
--- incubator/libcloud/trunk/libcloud/drivers/__init__.py (original)
+++ incubator/libcloud/trunk/libcloud/drivers/__init__.py Fri Dec 24 19:16:48 2010
@@ -18,6 +18,7 @@ Drivers for working with different provi
 """
 
 __all__ = [
+    'brightbox',
     'dummy',
     'ec2',
     'ecp',

Added: incubator/libcloud/trunk/libcloud/drivers/brightbox.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/drivers/brightbox.py?rev=1052557&view=auto
==============================================================================
--- incubator/libcloud/trunk/libcloud/drivers/brightbox.py (added)
+++ incubator/libcloud/trunk/libcloud/drivers/brightbox.py Fri Dec 24 19:16:48 2010
@@ -0,0 +1,216 @@
+# 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.
+"""
+Brightbox Driver
+"""
+from libcloud.types import Provider, NodeState, InvalidCredsError
+from libcloud.base import ConnectionUserAndKey, Response, NodeDriver
+from libcloud.base import Node, NodeImage, NodeSize, NodeLocation
+import httplib
+import base64
+import json
+
+
+API_VERSION = '1.0'
+
+
+class BrightboxResponse(Response):
+    def success(self):
+        return self.status >= 200 and self.status < 400
+
+    def parse_body(self):
+        if self.headers['content-type'].split('; ')[0] == 'application/json' and len(self.body) > 0:
+            return json.loads(self.body)
+        else:
+            return self.body
+
+    def parse_error(self):
+        return json.loads(self.body)['error']
+
+
+class BrightboxConnection(ConnectionUserAndKey):
+    """
+    Connection class for the Brightbox driver
+    """
+
+    host = 'api.gb1.brightbox.com'
+    responseCls = BrightboxResponse
+
+    def _fetch_oauth_token(self):
+        body = json.dumps({'client_id': self.user_id, 'grant_type': 'none'})
+
+        authorization = 'Basic ' + base64.encodestring('%s:%s' % (self.user_id, self.key)).rstrip()
+
+        self.connect()
+
+        response = self.connection.request(method='POST', url='/token', body=body, headers={
+            'Host': self.host,
+            'User-Agent': self._user_agent(),
+            'Authorization': authorization,
+            'Content-Type': 'application/json',
+            'Content-Length': str(len(body))
+        })
+
+        response = self.connection.getresponse()
+
+        if response.status == 200:
+            return json.loads(response.read())['access_token']
+        else:
+            message = '%s (%s)' % (json.loads(response.read())['error'], response.status)
+
+            raise InvalidCredsError, message
+
+    def add_default_headers(self, headers):
+        try:
+            headers['Authorization'] = 'OAuth ' + self.token
+        except AttributeError:
+            self.token = self._fetch_oauth_token()
+
+            headers['Authorization'] = 'OAuth ' + self.token
+
+        return headers
+
+    def encode_data(self, data):
+        return json.dumps(data)
+
+
+class BrightboxNodeDriver(NodeDriver):
+    """
+    Brightbox node driver
+    """
+
+    connectionCls = BrightboxConnection
+
+    type = Provider.BRIGHTBOX
+    name = 'Brightbox'
+
+    NODE_STATE_MAP = { 'creating': NodeState.PENDING,
+                       'active': NodeState.RUNNING,
+                       'inactive': NodeState.UNKNOWN,
+                       'deleting': NodeState.UNKNOWN,
+                       'deleted': NodeState.TERMINATED,
+                       'failed': NodeState.UNKNOWN }
+
+    def _to_node(self, data):
+        return Node(
+            id = data['id'],
+            name = data['name'],
+            state = self.NODE_STATE_MAP[data['status']],
+            public_ip = map(lambda cloud_ip: cloud_ip['public_ip'], data['cloud_ips']),
+            private_ip = map(lambda interface: interface['ipv4_address'], data['interfaces']),
+            driver = self.connection.driver,
+            extra = {
+                'status': data['status'],
+                'interfaces': data['interfaces']
+            }
+        )
+
+    def _to_image(self, data):
+        return NodeImage(
+            id = data['id'],
+            name = data['name'],
+            driver = self,
+            extra = {
+                'description': data['description'],
+                'arch': data['arch']
+            }
+        )
+
+    def _to_size(self, data):
+        return NodeSize(
+            id = data['id'],
+            name = data['name'],
+            ram = data['ram'],
+            disk = data['disk_size'],
+            bandwidth = 0,
+            price = '',
+            driver = self
+        )
+
+    def _to_location(self, data):
+        return NodeLocation(
+            id = data['id'],
+            name = data['handle'],
+            country = 'GB',
+            driver = self
+        )
+
+    def _post(self, path, data={}):
+        headers = {'Content-Type': 'application/json'}
+
+        return self.connection.request(path, data=data, headers=headers, method='POST')
+
+    def create_node(self, **kwargs):
+        data = {
+            'name': kwargs['name'],
+            'server_type': kwargs['size'].id,
+            'image': kwargs['image'].id,
+            'user_data': ''
+        }
+
+        if kwargs.has_key('location'):
+            data['zone'] = kwargs['location'].id
+        else:
+            data['zone'] = ''
+
+        data = self._post('/%s/servers' % API_VERSION, data).object
+
+        return self._to_node(data)
+
+    def destroy_node(self, node):
+        response = self.connection.request('/%s/servers/%s' % (API_VERSION, node.id), method='DELETE')
+
+        return response.status == httplib.ACCEPTED
+
+    def list_nodes(self):
+        data = self.connection.request('/%s/servers' % API_VERSION).object
+
+        return map(self._to_node, data)
+
+    def list_images(self):
+        data = self.connection.request('/%s/images' % API_VERSION).object
+
+        return map(self._to_image, data)
+
+    def list_sizes(self):
+        data = self.connection.request('/%s/server_types' % API_VERSION).object
+
+        return map(self._to_size, data)
+
+    def list_locations(self):
+        data = self.connection.request('/%s/zones' % API_VERSION).object
+
+        return map(self._to_location, data)
+
+    def ex_list_cloud_ips(self):
+        return self.connection.request('/%s/cloud_ips' % API_VERSION).object
+
+    def ex_create_cloud_ip(self):
+        return self._post('/%s/cloud_ips' % API_VERSION).object
+
+    def ex_map_cloud_ip(self, cloud_ip_id, interface_id):
+        response = self._post('/%s/cloud_ips/%s/map' % (API_VERSION, cloud_ip_id), {'interface': interface_id})
+
+        return response.status == httplib.ACCEPTED
+
+    def ex_unmap_cloud_ip(self, cloud_ip_id):
+        response = self._post('/%s/cloud_ips/%s/unmap' % (API_VERSION, cloud_ip_id))
+
+        return response.status == httplib.ACCEPTED
+
+    def ex_destroy_cloud_ip(self, cloud_ip_id):
+        response = self.connection.request('/%s/cloud_ips/%s' % (API_VERSION, cloud_ip_id), method='DELETE')
+
+        return response.status == httplib.OK

Modified: incubator/libcloud/trunk/libcloud/providers.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/providers.py?rev=1052557&r1=1052556&r2=1052557&view=diff
==============================================================================
--- incubator/libcloud/trunk/libcloud/providers.py (original)
+++ incubator/libcloud/trunk/libcloud/providers.py Fri Dec 24 19:16:48 2010
@@ -63,6 +63,8 @@ DRIVERS = {
         ('libcloud.drivers.opennebula', 'OpenNebulaNodeDriver'),
     Provider.DREAMHOST:
         ('libcloud.drivers.dreamhost', 'DreamhostNodeDriver'),
+    Provider.BRIGHTBOX:
+        ('libcloud.drivers.brightbox', 'BrightboxNodeDriver'),
 }
 
 def get_driver(provider):

Modified: incubator/libcloud/trunk/libcloud/types.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/types.py?rev=1052557&r1=1052556&r2=1052557&view=diff
==============================================================================
--- incubator/libcloud/trunk/libcloud/types.py (original)
+++ incubator/libcloud/trunk/libcloud/types.py Fri Dec 24 19:16:48 2010
@@ -63,6 +63,7 @@ class Provider(object):
     ELASTICHOSTS_US1 = 21
     EC2_AP_SOUTHEAST = 22
     RACKSPACE_UK = 23
+    BRIGHTBOX = 24
 
 class NodeState(object):
     """

Added: incubator/libcloud/trunk/test/fixtures/brightbox/create_server.json
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/fixtures/brightbox/create_server.json?rev=1052557&view=auto
==============================================================================
--- incubator/libcloud/trunk/test/fixtures/brightbox/create_server.json (added)
+++ incubator/libcloud/trunk/test/fixtures/brightbox/create_server.json Fri Dec 24 19:16:48 2010
@@ -0,0 +1,62 @@
+{"id": "srv-3a97e",
+ "url": "servers/(server_id)",
+ "name": "My web server",
+ "status": "active",
+ "hostname": "srv-3a97e.gb1.brightbox.com",
+ "created_at": "",
+ "deleted_at": "",
+ "started_at": "",
+ "account":
+  {"id": "acc-3jd8s",
+   "url": "accounts/(account_id)",
+   "name": "Brightbox Systems Ltd.",
+   "status": "verified",
+   "ram_limit": 20480,
+   "ram_used": 2048,
+   "limits_cloudips": 5},
+ "image":
+  {"id": "img-9vxqi",
+   "url": "images/(image_id)",
+   "name": "Brightbox Lucid 32",
+   "status": "available",
+   "description": "Jeremy's debian ec2 image",
+   "source": "jeremy_debian-32_ec2",
+   "source_type": "upload",
+   "arch": "32-bit",
+   "created_at": "",
+   "owner": "acc-bright"},
+ "server_type":
+  {"id": "typ-a97e6",
+   "url": "server_types/(server_type_id)",
+   "handle": "nano",
+   "name": "Brightbox Nano",
+   "status": "",
+   "cores": 2,
+   "ram": 2048,
+   "disk_size": ""},
+ "zone":
+  {"id": "zon-8ja0a",
+   "url": "zones/(zone_id)",
+   "handle": "gb1-a"},
+ "snapshots":
+  [{"id": "img-9vxqi",
+    "url": "images/(image_id)",
+    "name": "Brightbox Lucid 32",
+    "status": "available",
+    "description": "Jeremy's debian ec2 image",
+    "source": "jeremy_debian-32_ec2",
+    "source_type": "upload",
+    "arch": "32-bit",
+    "created_at": "",
+    "owner": "acc-bright"}],
+ "cloud_ips":
+  [{"id": "cip-ja8ub",
+    "url": "cloud_ips/(cloud_ip_id)",
+    "public_ip": "109.107.42.129",
+    "status": "mapped",
+    "reverse_dns": "cip-109-107-42-129.gb1.brightbox.com"}],
+ "interfaces":
+  [{"id": "int-mc3a9",
+    "url": "interfaces/(interface_id)",
+    "mac_address": "02:24:19:6e:18:36",
+    "ipv4_address": "10.110.24.54"}]}
\ No newline at end of file

Added: incubator/libcloud/trunk/test/fixtures/brightbox/list_images.json
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/fixtures/brightbox/list_images.json?rev=1052557&view=auto
==============================================================================
--- incubator/libcloud/trunk/test/fixtures/brightbox/list_images.json (added)
+++ incubator/libcloud/trunk/test/fixtures/brightbox/list_images.json Fri Dec 24 19:16:48 2010
@@ -0,0 +1,21 @@
+[{"id": "img-9vxqi",
+  "url": "images/(image_id)",
+  "name": "Brightbox Lucid 32",
+  "status": "available",
+  "description": "Jeremy's debian ec2 image",
+  "source": "jeremy_debian-32_ec2",
+  "source_type": "upload",
+  "arch": "32-bit",
+  "created_at": "",
+  "owner": "acc-bright",
+  "ancestor":
+   {"id": "img-9vxqi",
+    "url": "images/(image_id)",
+    "name": "Brightbox Lucid 32",
+    "status": "available",
+    "description": "Jeremy's debian ec2 image",
+    "source": "jeremy_debian-32_ec2",
+    "source_type": "upload",
+    "arch": "32-bit",
+    "created_at": "",
+    "owner": "acc-bright"}}]

Added: incubator/libcloud/trunk/test/fixtures/brightbox/list_server_types.json
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/fixtures/brightbox/list_server_types.json?rev=1052557&view=auto
==============================================================================
--- incubator/libcloud/trunk/test/fixtures/brightbox/list_server_types.json (added)
+++ incubator/libcloud/trunk/test/fixtures/brightbox/list_server_types.json Fri Dec 24 19:16:48 2010
@@ -0,0 +1,8 @@
+[{"id": "typ-4nssg",
+  "url": "server_types/typ-4nssg",
+  "handle": "nano",
+  "name": "Brightbox Nano Instance",
+  "status": "",
+  "cores": 1,
+  "ram": 512,
+  "disk_size": 10240}]

Added: incubator/libcloud/trunk/test/fixtures/brightbox/list_servers.json
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/fixtures/brightbox/list_servers.json?rev=1052557&view=auto
==============================================================================
--- incubator/libcloud/trunk/test/fixtures/brightbox/list_servers.json (added)
+++ incubator/libcloud/trunk/test/fixtures/brightbox/list_servers.json Fri Dec 24 19:16:48 2010
@@ -0,0 +1,62 @@
+[{"id": "srv-3a97e",
+  "url": "servers/(server_id)",
+  "name": "My web server",
+  "status": "active",
+  "hostname": "srv-3a97e.gb1.brightbox.com",
+  "created_at": "",
+  "deleted_at": "",
+  "started_at": "",
+  "account":
+   {"id": "acc-3jd8s",
+    "url": "accounts/(account_id)",
+    "name": "Brightbox Systems Ltd.",
+    "status": "verified",
+    "ram_limit": 20480,
+    "ram_used": 2048,
+    "limits_cloudips": 5},
+  "image":
+   {"id": "img-9vxqi",
+    "url": "images/(image_id)",
+    "name": "Brightbox Lucid 32",
+    "status": "available",
+    "description": "Jeremy's debian ec2 image",
+    "source": "jeremy_debian-32_ec2",
+    "source_type": "upload",
+    "arch": "32-bit",
+    "created_at": "",
+    "owner": "acc-bright"},
+  "server_type":
+   {"id": "typ-a97e6",
+    "url": "server_types/(server_type_id)",
+    "handle": "nano",
+    "name": "Brightbox Nano",
+    "status": "",
+    "cores": 2,
+    "ram": 2048,
+    "disk_size": ""},
+  "zone":
+   {"id": "zon-8ja0a",
+    "url": "zones/(zone_id)",
+    "handle": "gb1-a"},
+  "snapshots":
+   [{"id": "img-9vxqi",
+     "url": "images/(image_id)",
+     "name": "Brightbox Lucid 32",
+     "status": "available",
+     "description": "Jeremy's debian ec2 image",
+     "source": "jeremy_debian-32_ec2",
+     "source_type": "upload",
+     "arch": "32-bit",
+     "created_at": "",
+     "owner": "acc-bright"}],
+  "cloud_ips":
+   [{"id": "cip-ja8ub",
+     "url": "cloud_ips/(cloud_ip_id)",
+     "public_ip": "109.107.42.129",
+     "status": "mapped",
+     "reverse_dns": "cip-109-107-42-129.gb1.brightbox.com"}],
+  "interfaces":
+   [{"id": "int-mc3a9",
+     "url": "interfaces/(interface_id)",
+     "mac_address": "02:24:19:6e:18:36",
+     "ipv4_address": "10.110.24.54"}]}]

Added: incubator/libcloud/trunk/test/fixtures/brightbox/list_zones.json
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/fixtures/brightbox/list_zones.json?rev=1052557&view=auto
==============================================================================
--- incubator/libcloud/trunk/test/fixtures/brightbox/list_zones.json (added)
+++ incubator/libcloud/trunk/test/fixtures/brightbox/list_zones.json Fri Dec 24 19:16:48 2010
@@ -0,0 +1,3 @@
+[{"id": "zon-8ja0a",
+  "url": "zones/(zone_id)",
+  "handle": "gb1-a"}]

Added: incubator/libcloud/trunk/test/fixtures/brightbox/token.json
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/fixtures/brightbox/token.json?rev=1052557&view=auto
==============================================================================
--- incubator/libcloud/trunk/test/fixtures/brightbox/token.json (added)
+++ incubator/libcloud/trunk/test/fixtures/brightbox/token.json Fri Dec 24 19:16:48 2010
@@ -0,0 +1 @@
+{"access_token":"k1bjflpsaj8wnrbrwzad0eqo36nxiha", "expires_in": 3600}

Modified: incubator/libcloud/trunk/test/secrets.py-dist
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/secrets.py-dist?rev=1052557&r1=1052556&r2=1052557&view=diff
==============================================================================
--- incubator/libcloud/trunk/test/secrets.py-dist (original)
+++ incubator/libcloud/trunk/test/secrets.py-dist Fri Dec 24 19:16:48 2010
@@ -20,6 +20,9 @@
 EC2_ACCESS_ID='YoUR K3Y'
 EC2_SECRET='secr3t'
 
+BRIGHTBOX_CLIENT_ID=''
+BRIGHTBOX_CLIENT_SECRET=''
+
 RACKSPACE_USER=''
 RACKSPACE_KEY=''
 

Added: incubator/libcloud/trunk/test/test_brightbox.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/test_brightbox.py?rev=1052557&view=auto
==============================================================================
--- incubator/libcloud/trunk/test/test_brightbox.py (added)
+++ incubator/libcloud/trunk/test/test_brightbox.py Fri Dec 24 19:16:48 2010
@@ -0,0 +1,123 @@
+# 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, unittest, httplib, json
+
+from libcloud.drivers.brightbox import BrightboxNodeDriver
+from libcloud.types import NodeState, InvalidCredsError
+
+from test import MockHttp, TestCaseMixin
+from test.file_fixtures import FileFixtures
+from secrets import BRIGHTBOX_CLIENT_ID, BRIGHTBOX_CLIENT_SECRET
+
+
+class BrightboxTest(unittest.TestCase, TestCaseMixin):
+    def setUp(self):
+        BrightboxNodeDriver.connectionCls.conn_classes = (None, BrightboxMockHttp)
+        BrightboxMockHttp.type = None
+        self.driver = BrightboxNodeDriver(BRIGHTBOX_CLIENT_ID, BRIGHTBOX_CLIENT_SECRET)
+
+    def test_authentication(self):
+        BrightboxMockHttp.type = 'INVALID_CLIENT'
+        self.assertRaises(InvalidCredsError, self.driver.list_nodes)
+
+        BrightboxMockHttp.type = 'UNAUTHORIZED_CLIENT'
+        self.assertRaises(InvalidCredsError, self.driver.list_nodes)
+
+    def test_list_nodes(self):
+        nodes = self.driver.list_nodes()
+        self.assertEqual(len(nodes), 1)
+        self.assertTrue('109.107.42.129' in nodes[0].public_ip)
+        self.assertTrue('10.110.24.54' in nodes[0].private_ip)
+        self.assertEqual(nodes[0].state, NodeState.RUNNING)
+
+    def test_list_sizes(self):
+        sizes = self.driver.list_sizes()
+        self.assertEqual(len(sizes), 1)
+        self.assertEqual(sizes[0].id, 'typ-4nssg')
+        self.assertEqual(sizes[0].name, 'Brightbox Nano Instance')
+        self.assertEqual(sizes[0].ram, 512)
+
+    def test_list_images(self):
+        images = self.driver.list_images()
+        self.assertEqual(len(images), 1)
+        self.assertEqual(images[0].id, 'img-9vxqi')
+        self.assertEqual(images[0].name, 'Brightbox Lucid 32')
+        self.assertEqual(images[0].extra['arch'], '32-bit')
+
+    def test_reboot_node_response(self):
+        node = self.driver.list_nodes()[0]
+        self.assertRaises(NotImplementedError, self.driver.reboot_node, [node])
+
+    def test_destroy_node(self):
+        node = self.driver.list_nodes()[0]
+        self.assertTrue(self.driver.destroy_node(node))
+
+    def test_create_node(self):
+        size = self.driver.list_sizes()[0]
+        image = self.driver.list_images()[0]
+        node = self.driver.create_node(name='Test Node', image=image, size=size)
+        self.assertEqual('srv-3a97e', node.id)
+        self.assertEqual('Test Node', node.name)
+
+
+class BrightboxMockHttp(MockHttp):
+    fixtures = FileFixtures('brightbox')
+
+    def _token(self, method, url, body, headers):
+        if method == 'POST':
+            return self.response(httplib.OK, self.fixtures.load('token.json'))
+
+    def _token_INVALID_CLIENT(self, method, url, body, headers):
+        if method == 'POST':
+            return self.response(httplib.BAD_REQUEST, '{"error":"invalid_client"}')
+
+    def _token_UNAUTHORIZED_CLIENT(self, method, url, body, headers):
+        if method == 'POST':
+            return self.response(httplib.UNAUTHORIZED, '{"error":"unauthorized_client"}')
+
+    def _1_0_images(self, method, url, body, headers):
+        if method == 'GET':
+            return self.response(httplib.OK, self.fixtures.load('list_images.json'))
+
+    def _1_0_servers(self, method, url, body, headers):
+        if method == 'GET':
+            return self.response(httplib.OK, self.fixtures.load('list_servers.json'))
+        elif method == 'POST':
+            body = json.loads(body)
+
+            node = json.loads(self.fixtures.load('create_server.json'))
+
+            node['name'] = body['name']
+
+            return self.response(httplib.ACCEPTED, json.dumps(node))
+
+    def _1_0_servers_srv_3a97e(self, method, url, body, headers):
+        if method == 'DELETE':
+            return self.response(httplib.ACCEPTED, '')
+
+    def _1_0_server_types(self, method, url, body, headers):
+        if method == 'GET':
+            return self.response(httplib.OK, self.fixtures.load('list_server_types.json'))
+
+    def _1_0_zones(self, method, url, body, headers):
+        if method == 'GET':
+            return self.response(httplib.OK, self.fixtures.load('list_zones.json'))
+
+    def response(self, status, body):
+        return (status, body, {'content-type': 'application/json'}, httplib.responses[status])
+
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())