You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2011/09/07 13:58:17 UTC

svn commit: r1166136 [1/2] - in /libcloud/trunk: libcloud/ libcloud/common/ libcloud/compute/drivers/ libcloud/loadbalancer/drivers/ libcloud/storage/drivers/ test/ test/compute/ test/compute/fixtures/openstack/ test/compute/fixtures/rackspace/

Author: tomaz
Date: Wed Sep  7 11:58:16 2011
New Revision: 1166136

URL: http://svn.apache.org/viewvc?rev=1166136&view=rev
Log:
Rackspace driver now inherits from the OpenStack one instead of doing it
vice-versa. The patch has been contributed by Mike Nerone <mike at nerone dot
org> and is part of LIBCLOUD-110.

Added:
    libcloud/trunk/libcloud/common/openstack.py
    libcloud/trunk/libcloud/compute/drivers/openstack.py
    libcloud/trunk/test/compute/fixtures/openstack/
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_flavors_detail.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_images_detail.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_images_post.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_limits.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_missing.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_pending.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_same_uuid.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_success.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_empty.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_metadata.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_ips.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_metadata.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_group.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_groups.xml
    libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_groups_detail.xml
    libcloud/trunk/test/compute/test_openstack.py
Modified:
    libcloud/trunk/libcloud/common/rackspace.py
    libcloud/trunk/libcloud/compute/drivers/rackspace.py
    libcloud/trunk/libcloud/loadbalancer/drivers/rackspace.py
    libcloud/trunk/libcloud/pricing.py
    libcloud/trunk/libcloud/storage/drivers/cloudfiles.py
    libcloud/trunk/test/__init__.py
    libcloud/trunk/test/compute/__init__.py
    libcloud/trunk/test/compute/fixtures/rackspace/
    libcloud/trunk/test/compute/test_deployment.py
    libcloud/trunk/test/compute/test_rackspace.py
    libcloud/trunk/test/test_pricing.py

Added: libcloud/trunk/libcloud/common/openstack.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/common/openstack.py?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/libcloud/common/openstack.py (added)
+++ libcloud/trunk/libcloud/common/openstack.py Wed Sep  7 11:58:16 2011
@@ -0,0 +1,133 @@
+# 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.
+
+"""
+Common utilities for OpenStack
+"""
+import httplib
+from urllib2 import urlparse
+from libcloud.common.base import ConnectionUserAndKey
+from libcloud.compute.types import InvalidCredsError, MalformedResponseError
+
+AUTH_API_VERSION = 'v1.0'
+
+__all__ = [
+    "OpenStackBaseConnection",
+    ]
+
+class OpenStackBaseConnection(ConnectionUserAndKey):
+
+    auth_host = None
+
+    def __init__(self, user_id, key, secure, host=None, port=None):
+        self.cdn_management_url = None
+        self.storage_url = None
+        self.auth_token = None
+        if host is not None:
+            self.auth_host = host
+        if port is not None:
+            self.port = (port, port)
+
+        super(OpenStackBaseConnection, self).__init__(
+            user_id, key, secure=secure)
+
+    def add_default_headers(self, headers):
+        headers['X-Auth-Token'] = self.auth_token
+        headers['Accept'] = self.accept_format
+        return headers
+
+    @property
+    def request_path(self):
+        return self._get_request_path(url_key=self._url_key)
+
+    @property
+    def host(self):
+        # Default to server_host
+        return self._get_host(url_key=self._url_key)
+
+    def _get_request_path(self, url_key):
+        value_key = '__request_path_%s' % (url_key)
+        value = getattr(self, value_key, None)
+
+        if not value:
+            self._populate_hosts_and_request_paths()
+            value = getattr(self, value_key, None)
+
+        return value
+
+    def _get_host(self, url_key):
+        value_key = '__%s' % (url_key)
+        value = getattr(self, value_key, None)
+
+        if not value:
+            self._populate_hosts_and_request_paths()
+            value = getattr(self, value_key, None)
+
+        return value
+
+    def _populate_hosts_and_request_paths(self):
+        """
+        OpenStack uses a separate host for API calls which is only provided
+        after an initial authentication request. If we haven't made that
+        request yet, do it here. Otherwise, just return the management host.
+        """
+        if not self.auth_token:
+            # Initial connection used for authentication
+            conn = self.conn_classes[self.secure](
+                self.auth_host, self.port[self.secure])
+            conn.request(
+                method='GET',
+                url='/%s' % (AUTH_API_VERSION),
+                headers={
+                    'X-Auth-User': self.user_id,
+                    'X-Auth-Key': self.key
+                }
+            )
+
+            resp = conn.getresponse()
+
+            if resp.status == httplib.NO_CONTENT:
+                # HTTP NO CONTENT (204): auth successful
+                headers = dict(resp.getheaders())
+
+                try:
+                    self.server_url = headers['x-server-management-url']
+                    self.storage_url = headers['x-storage-url']
+                    self.cdn_management_url = headers['x-cdn-management-url']
+                    self.lb_url = self.server_url.replace("servers", "ord.loadbalancers")
+                    self.auth_token = headers['x-auth-token']
+                except KeyError, e:
+                    # Returned 204 but has missing information in the header, something is wrong
+                    raise MalformedResponseError('Malformed response',
+                                                 body='Missing header: %s' % (str(e)),
+                                                 driver=self.driver)
+            elif resp.status == httplib.UNAUTHORIZED:
+                # HTTP UNAUTHORIZED (401): auth failed
+                raise InvalidCredsError()
+            else:
+                # Any response code != 401 or 204, something is wrong
+                raise MalformedResponseError('Malformed response',
+                        body='code: %s body:%s' % (resp.status, ''.join(resp.body.readlines())),
+                        driver=self.driver)
+
+            for key in ['server_url', 'storage_url', 'cdn_management_url',
+                        'lb_url']:
+                scheme, server, request_path, param, query, fragment = (
+                    urlparse.urlparse(getattr(self, key)))
+                # Set host to where we want to make further requests to
+                setattr(self, '__%s' % (key), server)
+                setattr(self, '__request_path_%s' % (key), request_path)
+
+            conn.close()

Modified: libcloud/trunk/libcloud/common/rackspace.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/common/rackspace.py?rev=1166136&r1=1166135&r2=1166136&view=diff
==============================================================================
--- libcloud/trunk/libcloud/common/rackspace.py (original)
+++ libcloud/trunk/libcloud/common/rackspace.py Wed Sep  7 11:58:16 2011
@@ -14,117 +14,13 @@
 # limitations under the License.
 
 """
-Common utilities for Rackspace Cloud Servers and Cloud Files
+Common settings for Rackspace Cloud Servers and Cloud Files
 """
-import httplib
-from urllib2 import urlparse
-from libcloud.common.base import ConnectionUserAndKey
-from libcloud.compute.types import InvalidCredsError, MalformedResponseError
 
-AUTH_HOST_US='auth.api.rackspacecloud.com'
-AUTH_HOST_UK='lon.auth.api.rackspacecloud.com'
-AUTH_API_VERSION = 'v1.0'
+AUTH_HOST_US = 'auth.api.rackspacecloud.com'
+AUTH_HOST_UK = 'lon.auth.api.rackspacecloud.com'
 
 __all__ = [
-    "RackspaceBaseConnection",
     "AUTH_HOST_US",
-    "AUTH_HOST_UK"
+    "AUTH_HOST_UK",
     ]
-
-class RackspaceBaseConnection(ConnectionUserAndKey):
-    def __init__(self, user_id, key, secure):
-        self.cdn_management_url = None
-        self.storage_url = None
-        self.auth_token = None
-        self.__host = None
-        super(RackspaceBaseConnection, self).__init__(
-            user_id, key, secure=secure)
-
-    def add_default_headers(self, headers):
-        headers['X-Auth-Token'] = self.auth_token
-        headers['Accept'] = self.accept_format
-        return headers
-
-    @property
-    def request_path(self):
-        return self._get_request_path(url_key=self._url_key)
-
-    @property
-    def host(self):
-        # Default to server_host
-        return self._get_host(url_key=self._url_key)
-
-    def _get_request_path(self, url_key):
-        value_key = '__request_path_%s' % (url_key)
-        value = getattr(self, value_key, None)
-
-        if not value:
-            self._populate_hosts_and_request_paths()
-            value = getattr(self, value_key, None)
-
-        return value
-
-    def _get_host(self, url_key):
-        value_key = '__%s' % (url_key)
-        value = getattr(self, value_key, None)
-
-        if not value:
-            self._populate_hosts_and_request_paths()
-            value = getattr(self, value_key, None)
-
-        return value
-
-    def _populate_hosts_and_request_paths(self):
-        """
-        Rackspace uses a separate host for API calls which is only provided
-        after an initial authentication request. If we haven't made that
-        request yet, do it here. Otherwise, just return the management host.
-        """
-        if not self.auth_token:
-            # Initial connection used for authentication
-            conn = self.conn_classes[self.secure](
-                self.auth_host, self.port[self.secure])
-            conn.request(
-                method='GET',
-                url='/%s' % (AUTH_API_VERSION),
-                headers={
-                    'X-Auth-User': self.user_id,
-                    'X-Auth-Key': self.key
-                }
-            )
-
-            resp = conn.getresponse()
-
-            if resp.status == httplib.NO_CONTENT:
-                # HTTP NO CONTENT (204): auth successful
-                headers = dict(resp.getheaders())
-
-                try:
-                    self.server_url = headers['x-server-management-url']
-                    self.storage_url = headers['x-storage-url']
-                    self.cdn_management_url = headers['x-cdn-management-url']
-                    self.lb_url = self.server_url.replace("servers", "ord.loadbalancers")
-                    self.auth_token = headers['x-auth-token']
-                except KeyError, e:
-                    # Returned 204 but has missing information in the header, something is wrong
-                    raise MalformedResponseError('Malformed response',
-                                                 body='Missing header: %s' % (str(e)),
-                                                 driver=self.driver)
-            elif resp.status == httplib.UNAUTHORIZED:
-                # HTTP UNAUTHORIZED (401): auth failed
-                raise InvalidCredsError()
-            else:
-                # Any response code != 401 or 204, something is wrong
-                raise MalformedResponseError('Malformed response',
-                        body='code: %s body:%s' % (resp.status, ''.join(resp.body.readlines())),
-                        driver=self.driver)
-
-            for key in ['server_url', 'storage_url', 'cdn_management_url',
-                        'lb_url']:
-                scheme, server, request_path, param, query, fragment = (
-                    urlparse.urlparse(getattr(self, key)))
-                # Set host to where we want to make further requests to
-                setattr(self, '__%s' % (key), server)
-                setattr(self, '__request_path_%s' % (key), request_path)
-
-            conn.close()

Added: libcloud/trunk/libcloud/compute/drivers/openstack.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/drivers/openstack.py?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/libcloud/compute/drivers/openstack.py (added)
+++ libcloud/trunk/libcloud/compute/drivers/openstack.py Wed Sep  7 11:58:16 2011
@@ -0,0 +1,648 @@
+# 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.
+"""
+OpenStack driver
+"""
+import os
+
+import base64
+import warnings
+
+from xml.etree import ElementTree as ET
+from xml.parsers.expat import ExpatError
+
+from libcloud.pricing import get_size_price, PRICING_DATA
+from libcloud.common.base import Response
+from libcloud.common.types import MalformedResponseError
+from libcloud.compute.types import NodeState, Provider
+from libcloud.compute.base import NodeDriver, Node
+from libcloud.compute.base import NodeSize, NodeImage
+from libcloud.common.openstack import OpenStackBaseConnection
+
+__all__ = [
+    'OpenStackResponse',
+    'OpenStackConnection',
+    'OpenStackNodeDriver',
+    'OpenStackSharedIpGroup',
+    'OpenStackNodeIpAddresses',
+    ]
+
+NAMESPACE = 'http://docs.rackspacecloud.com/servers/api/v1.0'
+
+
+class OpenStackResponse(Response):
+
+    def success(self):
+        i = int(self.status)
+        return i >= 200 and i <= 299
+
+    def has_content_type(self, content_type):
+        content_type_value = self.headers.get('content-type') or ''
+        content_type_value = content_type_value.lower()
+        return content_type_value.find(content_type.lower()) > -1
+
+    def parse_body(self):
+        if self.has_content_type('application/xml'):
+            try:
+                return ET.XML(self.body)
+            except:
+                raise MalformedResponseError(
+                    'Failed to parse XML',
+                    body=self.body,
+                    driver=OpenStackNodeDriver)
+
+        else:
+            return self.body
+
+    def parse_error(self):
+        # TODO: fixup; only uses response codes really!
+        try:
+            body = ET.XML(self.body)
+        except:
+            raise MalformedResponseError(
+                "Failed to parse XML",
+                body=self.body, driver=OpenStackNodeDriver)
+        try:
+            text = "; ".join([err.text or ''
+                              for err in
+                              body.getiterator()
+                              if err.text])
+        except ExpatError:
+            text = self.body
+        return '%s %s %s' % (self.status, self.error, text)
+
+
+class OpenStackConnection(OpenStackBaseConnection):
+
+    responseCls = OpenStackResponse
+    _url_key = "server_url"
+
+    def __init__(self, user_id, key, secure, host=None, port=None):
+        super(OpenStackConnection, self).__init__(
+            user_id, key, secure=secure, host=host, port=port)
+        self.api_version = 'v1.0'
+        self.accept_format = 'application/xml'
+
+    def request(self, action, params=None, data='', headers=None,
+                method='GET'):
+        if not headers:
+            headers = {}
+        if not params:
+            params = {}
+        # Due to first-run authentication request, we may not have a path
+        if self.server_url:
+            action = self.server_url + action
+        if method in ("POST", "PUT"):
+            headers = {'Content-Type': 'application/xml; charset=UTF-8'}
+        if method == "GET":
+            params['cache-busting'] = os.urandom(8).encode('hex')
+        return super(OpenStackConnection, self).request(
+            action=action,
+            params=params, data=data,
+            method=method, headers=headers
+        )
+
+
+class OpenStackNodeDriver(NodeDriver):
+    """
+    OpenStack node driver.
+
+    Extra node attributes:
+        - password: root password, available after create.
+        - hostId: represents the host your cloud server runs on
+        - imageId: id of image
+        - flavorId: id of flavor
+    """
+    connectionCls = OpenStackConnection
+    type = Provider.OPENSTACK
+    api_name = 'openstack'
+    name = 'OpenStack'
+
+    features = {"create_node": ["generates_password"]}
+
+    NODE_STATE_MAP = {'BUILD': NodeState.PENDING,
+                      'REBUILD': NodeState.PENDING,
+                      'ACTIVE': NodeState.RUNNING,
+                      'SUSPENDED': NodeState.TERMINATED,
+                      'QUEUE_RESIZE': NodeState.PENDING,
+                      'PREP_RESIZE': NodeState.PENDING,
+                      'VERIFY_RESIZE': NodeState.RUNNING,
+                      'PASSWORD': NodeState.PENDING,
+                      'RESCUE': NodeState.PENDING,
+                      'REBUILD': NodeState.PENDING,
+                      'REBOOT': NodeState.REBOOTING,
+                      'HARD_REBOOT': NodeState.REBOOTING,
+                      'SHARE_IP': NodeState.PENDING,
+                      'SHARE_IP_NO_CONFIG': NodeState.PENDING,
+                      'DELETE_IP': NodeState.PENDING,
+                      'UNKNOWN': NodeState.UNKNOWN}
+
+    def list_nodes(self):
+        return self._to_nodes(self.connection.request('/servers/detail')
+                                             .object)
+
+    def list_sizes(self, location=None):
+        return self._to_sizes(self.connection.request('/flavors/detail')
+                                             .object)
+
+    def list_images(self, location=None):
+        return self._to_images(self.connection.request('/images/detail')
+                                              .object)
+    # TODO: def list_locations: Is there an OpenStack way to do this? Rackspace-specific docstring says no.
+
+    def _change_password_or_name(self, node, name=None, password=None):
+        uri = '/servers/%s' % (node.id)
+
+        if not name:
+            name = node.name
+
+        body = {'xmlns': NAMESPACE,
+                 'name': name}
+
+        if password != None:
+            body['adminPass'] = password
+
+        server_elm = ET.Element('server', body)
+
+        resp = self.connection.request(
+            uri, method='PUT', data=ET.tostring(server_elm))
+
+        if resp.status == 204 and password != None:
+            node.extra['password'] = password
+
+        return resp.status == 204
+
+    def ex_set_password(self, node, password):
+        """
+        Sets the Node's root password.
+
+        This will reboot the instance to complete the operation.
+
+        L{node.extra['password']} will be set to the new value if the
+        operation was successful.
+        """
+        return self._change_password_or_name(node, password=password)
+
+    def ex_set_server_name(self, node, name):
+        """
+        Sets the Node's name.
+
+        This will reboot the instance to complete the operation.
+        """
+        return self._change_password_or_name(node, name=name)
+
+    def create_node(self, **kwargs):
+        """Create a new node
+
+        See L{NodeDriver.create_node} for more keyword args.
+        @keyword    ex_metadata: Key/Value metadata to associate with a node
+        @type       ex_metadata: C{dict}
+
+        @keyword    ex_files:   File Path => File contents to create on
+                                the node
+        @type       ex_files:   C{dict}
+        """
+        name = kwargs['name']
+        image = kwargs['image']
+        size = kwargs['size']
+
+        attributes = {'xmlns': NAMESPACE,
+             'name': name,
+             'imageId': str(image.id),
+             'flavorId': str(size.id)
+        }
+
+        if 'ex_shared_ip_group' in kwargs:
+            # Deprecate this. Be explicit and call the variable
+            # ex_shared_ip_group_id since user needs to pass in the id, not the
+            # name.
+            warnings.warn('ex_shared_ip_group argument is deprecated. Please'
+                          + ' use ex_shared_ip_group_id')
+
+        if 'ex_shared_ip_group_id' in kwargs:
+            shared_ip_group_id = kwargs['ex_shared_ip_group_id']
+            attributes['sharedIpGroupId'] = shared_ip_group_id
+
+        server_elm = ET.Element('server', attributes)
+
+        metadata_elm = self._metadata_to_xml(kwargs.get("ex_metadata", {}))
+        if metadata_elm:
+            server_elm.append(metadata_elm)
+
+        files_elm = self._files_to_xml(kwargs.get("ex_files", {}))
+        if files_elm:
+            server_elm.append(files_elm)
+        resp = self.connection.request("/servers",
+                                       method='POST',
+                                       data=ET.tostring(server_elm))
+        return self._to_node(resp.object)
+
+    def ex_resize(self, node, size):
+        """
+        Change an existing server flavor / scale the server up or down.
+
+        @keyword    node: node to resize.
+        @param      node: C{Node}
+
+        @keyword    size: new size.
+        @param      size: C{NodeSize}
+        """
+        elm = ET.Element(
+            'resize',
+            {'xmlns': NAMESPACE,
+             'flavorId': str(size.id),
+            }
+        )
+
+        resp = self.connection.request("/servers/%s/action" % (node.id),
+                                       method='POST',
+                                       data=ET.tostring(elm))
+        return resp.status == 202
+
+    def ex_confirm_resize(self, node):
+        """
+        Confirm a resize request which is currently in progress. If a resize
+        request is not explicitly confirmed or reverted it's automatically
+        confirmed after 24 hours.
+
+        For more info refer to the API documentation: http://goo.gl/zjFI1
+
+        @keyword    node: node for which the resize request will be confirmed.
+        @param      node: C{Node}
+        """
+        elm = ET.Element(
+            'confirmResize',
+            {'xmlns': NAMESPACE}
+        )
+
+        resp = self.connection.request("/servers/%s/action" % (node.id),
+                                       method='POST',
+                                       data=ET.tostring(elm))
+        return resp.status == 204
+
+    def ex_revert_resize(self, node):
+        """
+        Revert a resize request which is currently in progress.
+        All resizes are automatically confirmed after 24 hours if they have
+        not already been confirmed explicitly or reverted.
+
+        For more info refer to the API documentation: http://goo.gl/AizBu
+
+        @keyword    node: node for which the resize request will be reverted.
+        @param      node: C{Node}
+        """
+        elm = ET.Element(
+            'revertResize',
+            {'xmlns': NAMESPACE}
+        )
+
+        resp = self.connection.request("/servers/%s/action" % (node.id),
+                                       method='POST',
+                                       data=ET.tostring(elm))
+        return resp.status == 204
+
+    def ex_rebuild(self, node_id, image_id):
+        # @TODO: Remove those ifs in 0.6
+        if isinstance(node_id, Node):
+            node_id = node_id.id
+
+        if isinstance(image_id, NodeImage):
+            image_id = image_id.id
+
+        elm = ET.Element(
+            'rebuild',
+            {'xmlns': NAMESPACE,
+             'imageId': image_id,
+            }
+        )
+        resp = self.connection.request("/servers/%s/action" % node_id,
+                                       method='POST',
+                                       data=ET.tostring(elm))
+        return resp.status == 202
+
+    def ex_create_ip_group(self, group_name, node_id=None):
+        # @TODO: Remove this if in 0.6
+        if isinstance(node_id, Node):
+            node_id = node_id.id
+
+        group_elm = ET.Element(
+            'sharedIpGroup',
+            {'xmlns': NAMESPACE,
+             'name': group_name,
+            }
+        )
+
+        if node_id:
+            ET.SubElement(group_elm,
+                'server',
+                {'id': node_id}
+            )
+
+        resp = self.connection.request('/shared_ip_groups',
+                                       method='POST',
+                                       data=ET.tostring(group_elm))
+        return self._to_shared_ip_group(resp.object)
+
+    def ex_list_ip_groups(self, details=False):
+        uri = '/shared_ip_groups/detail' if details else '/shared_ip_groups'
+        resp = self.connection.request(uri,
+                                       method='GET')
+        groups = self._findall(resp.object, 'sharedIpGroup')
+        return [self._to_shared_ip_group(el) for el in groups]
+
+    def ex_delete_ip_group(self, group_id):
+        uri = '/shared_ip_groups/%s' % group_id
+        resp = self.connection.request(uri, method='DELETE')
+        return resp.status == 204
+
+    def ex_share_ip(self, group_id, node_id, ip, configure_node=True):
+        # @TODO: Remove this if in 0.6
+        if isinstance(node_id, Node):
+            node_id = node_id.id
+
+        if configure_node:
+            str_configure = 'true'
+        else:
+            str_configure = 'false'
+
+        elm = ET.Element(
+            'shareIp',
+            {'xmlns': NAMESPACE,
+             'sharedIpGroupId': group_id,
+             'configureServer': str_configure}
+        )
+
+        uri = '/servers/%s/ips/public/%s' % (node_id, ip)
+
+        resp = self.connection.request(uri,
+                                       method='PUT',
+                                       data=ET.tostring(elm))
+        return resp.status == 202
+
+    def ex_unshare_ip(self, node_id, ip):
+        # @TODO: Remove this if in 0.6
+        if isinstance(node_id, Node):
+            node_id = node_id.id
+
+        uri = '/servers/%s/ips/public/%s' % (node_id, ip)
+
+        resp = self.connection.request(uri,
+                                       method='DELETE')
+        return resp.status == 202
+
+    def ex_list_ip_addresses(self, node_id):
+        # @TODO: Remove this if in 0.6
+        if isinstance(node_id, Node):
+            node_id = node_id.id
+
+        uri = '/servers/%s/ips' % node_id
+        resp = self.connection.request(uri,
+                                       method='GET')
+        return self._to_ip_addresses(resp.object)
+
+    def _metadata_to_xml(self, metadata):
+        if len(metadata) == 0:
+            return None
+
+        metadata_elm = ET.Element('metadata')
+        for k, v in metadata.items():
+            meta_elm = ET.SubElement(metadata_elm, 'meta', {'key': str(k)})
+            meta_elm.text = str(v)
+
+        return metadata_elm
+
+    def _files_to_xml(self, files):
+        if len(files) == 0:
+            return None
+
+        personality_elm = ET.Element('personality')
+        for k, v in files.items():
+            file_elm = ET.SubElement(personality_elm,
+                                     'file',
+                                     {'path': str(k)})
+            file_elm.text = base64.b64encode(v)
+
+        return personality_elm
+
+    def _reboot_node(self, node, reboot_type='SOFT'):
+        resp = self._node_action(node, ['reboot', ('type', reboot_type)])
+        return resp.status == 202
+
+    def ex_soft_reboot_node(self, node):
+        return self._reboot_node(node, reboot_type='SOFT')
+
+    def ex_hard_reboot_node(self, node):
+        return self._reboot_node(node, reboot_type='HARD')
+
+    def reboot_node(self, node):
+        return self._reboot_node(node, reboot_type='HARD')
+
+    def destroy_node(self, node):
+        uri = '/servers/%s' % (node.id)
+        resp = self.connection.request(uri, method='DELETE')
+        return resp.status == 202
+
+    def ex_get_node_details(self, node_id):
+        # @TODO: Remove this if in 0.6
+        if isinstance(node_id, Node):
+            node_id = node_id.id
+
+        uri = '/servers/%s' % (node_id)
+        resp = self.connection.request(uri, method='GET')
+        if resp.status == 404:
+            return None
+        return self._to_node(resp.object)
+
+    def _node_action(self, node, body):
+        if isinstance(body, list):
+            attr = ' '.join(['%s="%s"' % (item[0], item[1])
+                             for item in body[1:]])
+            body = '<%s xmlns="%s" %s/>' % (body[0], NAMESPACE, attr)
+        uri = '/servers/%s/action' % (node.id)
+        resp = self.connection.request(uri, method='POST', data=body)
+        return resp
+
+    def _to_nodes(self, object):
+        node_elements = self._findall(object, 'server')
+        return [self._to_node(el) for el in node_elements]
+
+    def _fixxpath(self, xpath):
+        # ElementTree wants namespaces in its xpaths, so here we add them.
+        return "/".join(["{%s}%s" % (NAMESPACE, e) for e in xpath.split("/")])
+
+    def _findall(self, element, xpath):
+        return element.findall(self._fixxpath(xpath))
+
+    def _to_node(self, el):
+        def get_ips(el):
+            return [ip.get('addr') for ip in el]
+
+        def get_meta_dict(el):
+            d = {}
+            for meta in el:
+                d[meta.get('key')] = meta.text
+            return d
+
+        public_ip = get_ips(self._findall(el,
+                                          'addresses/public/ip'))
+        private_ip = get_ips(self._findall(el,
+                                          'addresses/private/ip'))
+        metadata = get_meta_dict(self._findall(el, 'metadata/meta'))
+
+        n = Node(id=el.get('id'),
+                 name=el.get('name'),
+                 state=self.NODE_STATE_MAP.get(
+                     el.get('status'), NodeState.UNKNOWN),
+                 public_ip=public_ip,
+                 private_ip=private_ip,
+                 driver=self.connection.driver,
+                 extra={
+                    'password': el.get('adminPass'),
+                    'hostId': el.get('hostId'),
+                    'imageId': el.get('imageId'),
+                    'flavorId': el.get('flavorId'),
+                    'uri': "https://%s%s/servers/%s" % (
+                         self.connection.host,
+                         self.connection.request_path, el.get('id')),
+                    'metadata': metadata,
+                 })
+        return n
+
+    def _to_sizes(self, object):
+        elements = self._findall(object, 'flavor')
+        return [self._to_size(el) for el in elements]
+
+    def _to_size(self, el):
+        s = NodeSize(id=el.get('id'),
+                     name=el.get('name'),
+                     ram=int(el.get('ram')),
+                     disk=int(el.get('disk')),
+                     bandwidth=None, # XXX: needs hardcode
+                     price=self._get_size_price(el.get('id')), # Hardcoded,
+                     driver=self.connection.driver)
+        return s
+
+    def _to_images(self, object):
+        elements = self._findall(object, "image")
+        return [self._to_image(el)
+                for el in elements
+                if el.get('status') == 'ACTIVE']
+
+    def _to_image(self, el):
+        i = NodeImage(id=el.get('id'),
+                      name=el.get('name'),
+                      driver=self.connection.driver,
+                      extra={'updated': el.get('updated'),
+                             'created': el.get('created'),
+                             'status': el.get('status'),
+                             'serverId': el.get('serverId'),
+                             'progress': el.get('progress')})
+        return i
+
+    def ex_limits(self):
+        """
+        Extra call to get account's limits, such as
+        rates (for example amount of POST requests per day)
+        and absolute limits like total amount of available
+        RAM to be used by servers.
+
+        @return: C{dict} with keys 'rate' and 'absolute'
+        """
+
+        def _to_rate(el):
+            rate = {}
+            for item in el.items():
+                rate[item[0]] = item[1]
+
+            return rate
+
+        def _to_absolute(el):
+            return {el.get('name'): el.get('value')}
+
+        limits = self.connection.request("/limits").object
+        rate = [_to_rate(el) for el in self._findall(limits, 'rate/limit')]
+        absolute = {}
+        for item in self._findall(limits, 'absolute/limit'):
+            absolute.update(_to_absolute(item))
+
+        return {"rate": rate, "absolute": absolute}
+
+    def ex_save_image(self, node, name):
+        """Create an image for node.
+
+        @keyword    node: node to use as a base for image
+        @param      node: L{Node}
+        @keyword    name: name for new image
+        @param      name: C{string}
+        """
+
+        image_elm = ET.Element(
+                'image',
+                {'xmlns': NAMESPACE,
+                    'name': name,
+                    'serverId': node.id}
+        )
+
+        return self._to_image(self.connection.request("/images",
+                    method="POST",
+                    data=ET.tostring(image_elm)).object)
+
+    def _to_shared_ip_group(self, el):
+        servers_el = self._findall(el, 'servers')
+        if servers_el:
+            servers = [s.get('id')
+                       for s in self._findall(servers_el[0], 'server')]
+        else:
+            servers = None
+        return OpenStackSharedIpGroup(id=el.get('id'),
+                                      name=el.get('name'),
+                                      servers=servers)
+
+    def _to_ip_addresses(self, el):
+        return OpenStackNodeIpAddresses(
+            [ip.get('addr') for ip in
+             self._findall(self._findall(el, 'public')[0], 'ip')],
+            [ip.get('addr') for ip in
+             self._findall(self._findall(el, 'private')[0], 'ip')]
+        )
+
+    def _get_size_price(self, size_id):
+        if 'openstack' not in PRICING_DATA['compute']:
+            return 0.0
+
+        return get_size_price(driver_type='compute',
+                              driver_name='openstack',
+                              size_id=size_id)
+
+
+class OpenStackSharedIpGroup(object):
+    """
+    Shared IP group info.
+    """
+
+    def __init__(self, id, name, servers=None):
+        self.id = str(id)
+        self.name = name
+        self.servers = servers
+
+
+class OpenStackNodeIpAddresses(object):
+    """
+    List of public and private IP addresses of a Node.
+    """
+
+    def __init__(self, public_addresses, private_addresses):
+        self.public_addresses = public_addresses
+        self.private_addresses = private_addresses

Modified: libcloud/trunk/libcloud/compute/drivers/rackspace.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/drivers/rackspace.py?rev=1166136&r1=1166135&r2=1166136&view=diff
==============================================================================
--- libcloud/trunk/libcloud/compute/drivers/rackspace.py (original)
+++ libcloud/trunk/libcloud/compute/drivers/rackspace.py Wed Sep  7 11:58:16 2011
@@ -15,164 +15,28 @@
 """
 Rackspace driver
 """
-import os
-
-import base64
-import warnings
-
-from xml.etree import ElementTree as ET
-from xml.parsers.expat import ExpatError
-
-from libcloud.pricing import get_pricing, get_size_price, PRICING_DATA
-from libcloud.common.base import Response
-from libcloud.common.types import MalformedResponseError
-from libcloud.compute.types import NodeState, Provider
-from libcloud.compute.base import NodeDriver, Node
-from libcloud.compute.base import NodeSize, NodeImage, NodeLocation
+from libcloud.compute.types import Provider
+from libcloud.compute.base import NodeLocation
+from libcloud.compute.drivers.openstack import OpenStackConnection, OpenStackNodeDriver, OpenStackResponse
 
 from libcloud.common.rackspace import (
-    AUTH_HOST_US, AUTH_HOST_UK, RackspaceBaseConnection)
-
-
-NAMESPACE = 'http://docs.rackspacecloud.com/servers/api/v1.0'
-
+    AUTH_HOST_US, AUTH_HOST_UK)
 
-class RackspaceResponse(Response):
 
-    def success(self):
-        i = int(self.status)
-        return i >= 200 and i <= 299
-
-    def parse_body(self):
-        if not self.body:
-            return None
-        try:
-            body = ET.XML(self.body)
-        except:
-            raise MalformedResponseError(
-                "Failed to parse XML",
-                body=self.body,
-                driver=RackspaceNodeDriver)
-        return body
-
-    def parse_error(self):
-        # TODO: fixup, Rackspace only uses response codes really!
-        try:
-            body = ET.XML(self.body)
-        except:
-            raise MalformedResponseError(
-                "Failed to parse XML",
-                body=self.body, driver=RackspaceNodeDriver)
-        try:
-            text = "; ".join([err.text or ''
-                              for err in
-                              body.getiterator()
-                              if err.text])
-        except ExpatError:
-            text = self.body
-        return '%s %s %s' % (self.status, self.error, text)
-
-
-class RackspaceConnection(RackspaceBaseConnection):
+class RackspaceConnection(OpenStackConnection):
     """
     Connection class for the Rackspace driver
     """
 
-    responseCls = RackspaceResponse
+    responseCls = OpenStackResponse
     auth_host = AUTH_HOST_US
-    _url_key = "server_url"
 
-    def __init__(self, user_id, key, secure=True):
-        super(RackspaceConnection, self).__init__(user_id, key, secure)
-        self.api_version = 'v1.0'
-        self.accept_format = 'application/xml'
-
-    def request(self, action, params=None, data='', headers=None,
-                method='GET'):
-        if not headers:
-            headers = {}
-        if not params:
-            params = {}
-        # Due to first-run authentication request, we may not have a path
-        if self.server_url:
-            action = self.server_url + action
-        if method in ("POST", "PUT"):
-            headers = {'Content-Type': 'application/xml; charset=UTF-8'}
-        if method == "GET":
-            params['cache-busting'] = os.urandom(8).encode('hex')
-        return super(RackspaceConnection, self).request(
-            action=action,
-            params=params, data=data,
-            method=method, headers=headers
-        )
 
-
-class RackspaceSharedIpGroup(object):
-    """
-    Shared IP group info.
-    """
-
-    def __init__(self, id, name, servers=None):
-        self.id = str(id)
-        self.name = name
-        self.servers = servers
-
-
-class RackspaceNodeIpAddresses(object):
-    """
-    List of public and private IP addresses of a Node.
-    """
-
-    def __init__(self, public_addresses, private_addresses):
-        self.public_addresses = public_addresses
-        self.private_addresses = private_addresses
-
-
-class RackspaceNodeDriver(NodeDriver):
-    """
-    Rackspace node driver.
-
-    Extra node attributes:
-        - password: root password, available after create.
-        - hostId: represents the host your cloud server runs on
-        - imageId: id of image
-        - flavorId: id of flavor
-    """
+class RackspaceNodeDriver(OpenStackNodeDriver):
+    name = 'Rackspace'
     connectionCls = RackspaceConnection
     type = Provider.RACKSPACE
     api_name = 'rackspace'
-    name = 'Rackspace'
-
-    features = {"create_node": ["generates_password"]}
-
-    NODE_STATE_MAP = {'BUILD': NodeState.PENDING,
-                      'REBUILD': NodeState.PENDING,
-                      'ACTIVE': NodeState.RUNNING,
-                      'SUSPENDED': NodeState.TERMINATED,
-                      'QUEUE_RESIZE': NodeState.PENDING,
-                      'PREP_RESIZE': NodeState.PENDING,
-                      'VERIFY_RESIZE': NodeState.RUNNING,
-                      'PASSWORD': NodeState.PENDING,
-                      'RESCUE': NodeState.PENDING,
-                      'REBUILD': NodeState.PENDING,
-                      'REBOOT': NodeState.REBOOTING,
-                      'HARD_REBOOT': NodeState.REBOOTING,
-                      'SHARE_IP': NodeState.PENDING,
-                      'SHARE_IP_NO_CONFIG': NodeState.PENDING,
-                      'DELETE_IP': NodeState.PENDING,
-                      'UNKNOWN': NodeState.UNKNOWN}
-
-    def list_nodes(self):
-        return self._to_nodes(self.connection.request('/servers/detail')
-                                             .object)
-
-    def list_sizes(self, location=None):
-        return self._to_sizes(self.connection.request('/flavors/detail')
-                                             .object)
-
-    def list_images(self, location=None):
-        return self._to_images(self.connection.request('/images/detail')
-                                              .object)
 
     def list_locations(self):
         """Lists available locations
@@ -182,462 +46,6 @@ class RackspaceNodeDriver(NodeDriver):
         """
         return [NodeLocation(0, "Rackspace DFW1/ORD1", 'US', self)]
 
-    def _change_password_or_name(self, node, name=None, password=None):
-        uri = '/servers/%s' % (node.id)
-
-        if not name:
-            name = node.name
-
-        body = {'xmlns': NAMESPACE,
-                 'name': name}
-
-        if password != None:
-            body['adminPass'] = password
-
-        server_elm = ET.Element('server', body)
-
-        resp = self.connection.request(
-            uri, method='PUT', data=ET.tostring(server_elm))
-
-        if resp.status == 204 and password != None:
-            node.extra['password'] = password
-
-        return resp.status == 204
-
-    def ex_set_password(self, node, password):
-        """
-        Sets the Node's root password.
-
-        This will reboot the instance to complete the operation.
-
-        L{node.extra['password']} will be set to the new value if the
-        operation was successful.
-        """
-        return self._change_password_or_name(node, password=password)
-
-    def ex_set_server_name(self, node, name):
-        """
-        Sets the Node's name.
-
-        This will reboot the instance to complete the operation.
-        """
-        return self._change_password_or_name(node, name=name)
-
-    def create_node(self, **kwargs):
-        """Create a new rackspace node
-
-        See L{NodeDriver.create_node} for more keyword args.
-        @keyword    ex_metadata: Key/Value metadata to associate with a node
-        @type       ex_metadata: C{dict}
-
-        @keyword    ex_files:   File Path => File contents to create on
-                                the node
-        @type       ex_files:   C{dict}
-        """
-        name = kwargs['name']
-        image = kwargs['image']
-        size = kwargs['size']
-
-        attributes = {'xmlns': NAMESPACE,
-             'name': name,
-             'imageId': str(image.id),
-             'flavorId': str(size.id)
-        }
-
-        if 'ex_shared_ip_group' in kwargs:
-            # Deprecate this. Be explicit and call the variable
-            # ex_shared_ip_group_id since user needs to pass in the id, not the
-            # name.
-            warnings.warn('ex_shared_ip_group argument is deprecated. Please'
-                          + ' use ex_shared_ip_group_id')
-
-        if 'ex_shared_ip_group_id' in kwargs:
-            shared_ip_group_id = kwargs['ex_shared_ip_group_id']
-            attributes['sharedIpGroupId'] = shared_ip_group_id
-
-        server_elm = ET.Element('server', attributes)
-
-        metadata_elm = self._metadata_to_xml(kwargs.get("ex_metadata", {}))
-        if metadata_elm:
-            server_elm.append(metadata_elm)
-
-        files_elm = self._files_to_xml(kwargs.get("ex_files", {}))
-        if files_elm:
-            server_elm.append(files_elm)
-        resp = self.connection.request("/servers",
-                                       method='POST',
-                                       data=ET.tostring(server_elm))
-        return self._to_node(resp.object)
-
-    def ex_resize(self, node, size):
-        """
-        Change an existing server flavor / scale the server up or down.
-
-        @keyword    node: node to resize.
-        @param      node: C{Node}
-
-        @keyword    size: new size.
-        @param      size: C{NodeSize}
-        """
-        elm = ET.Element(
-            'resize',
-            {'xmlns': NAMESPACE,
-             'flavorId': str(size.id),
-            }
-        )
-
-        resp = self.connection.request("/servers/%s/action" % (node.id),
-                                       method='POST',
-                                       data=ET.tostring(elm))
-        return resp.status == 202
-
-    def ex_confirm_resize(self, node):
-        """
-        Confirm a resize request which is currently in progress. If a resize
-        request is not explicitly confirmed or reverted it's automatically
-        confirmed after 24 hours.
-
-        For more info refer to the API documentation: http://goo.gl/zjFI1
-
-        @keyword    node: node for which the resize request will be confirmed.
-        @param      node: C{Node}
-        """
-        elm = ET.Element(
-            'confirmResize',
-            {'xmlns': NAMESPACE}
-        )
-
-        resp = self.connection.request("/servers/%s/action" % (node.id),
-                                       method='POST',
-                                       data=ET.tostring(elm))
-        return resp.status == 204
-
-    def ex_revert_resize(self, node):
-        """
-        Revert a resize request which is currently in progress.
-        All resizes are automatically confirmed after 24 hours if they have
-        not already been confirmed explicitly or reverted.
-
-        For more info refer to the API documentation: http://goo.gl/AizBu
-
-        @keyword    node: node for which the resize request will be reverted.
-        @param      node: C{Node}
-        """
-        elm = ET.Element(
-            'revertResize',
-            {'xmlns': NAMESPACE}
-        )
-
-        resp = self.connection.request("/servers/%s/action" % (node.id),
-                                       method='POST',
-                                       data=ET.tostring(elm))
-        return resp.status == 204
-
-    def ex_rebuild(self, node_id, image_id):
-        # @TODO: Remove those ifs in 0.6
-        if isinstance(node_id, Node):
-            node_id = node_id.id
-
-        if isinstance(image_id, NodeImage):
-            image_id = image_id.id
-
-        elm = ET.Element(
-            'rebuild',
-            {'xmlns': NAMESPACE,
-             'imageId': image_id,
-            }
-        )
-        resp = self.connection.request("/servers/%s/action" % node_id,
-                                       method='POST',
-                                       data=ET.tostring(elm))
-        return resp.status == 202
-
-    def ex_create_ip_group(self, group_name, node_id=None):
-        # @TODO: Remove this if in 0.6
-        if isinstance(node_id, Node):
-            node_id = node_id.id
-
-        group_elm = ET.Element(
-            'sharedIpGroup',
-            {'xmlns': NAMESPACE,
-             'name': group_name,
-            }
-        )
-
-        if node_id:
-            ET.SubElement(group_elm,
-                'server',
-                {'id': node_id}
-            )
-
-        resp = self.connection.request('/shared_ip_groups',
-                                       method='POST',
-                                       data=ET.tostring(group_elm))
-        return self._to_shared_ip_group(resp.object)
-
-    def ex_list_ip_groups(self, details=False):
-        uri = '/shared_ip_groups/detail' if details else '/shared_ip_groups'
-        resp = self.connection.request(uri,
-                                       method='GET')
-        groups = self._findall(resp.object, 'sharedIpGroup')
-        return [self._to_shared_ip_group(el) for el in groups]
-
-    def ex_delete_ip_group(self, group_id):
-        uri = '/shared_ip_groups/%s' % group_id
-        resp = self.connection.request(uri, method='DELETE')
-        return resp.status == 204
-
-    def ex_share_ip(self, group_id, node_id, ip, configure_node=True):
-        # @TODO: Remove this if in 0.6
-        if isinstance(node_id, Node):
-            node_id = node_id.id
-
-        if configure_node:
-            str_configure = 'true'
-        else:
-            str_configure = 'false'
-
-        elm = ET.Element(
-            'shareIp',
-            {'xmlns': NAMESPACE,
-             'sharedIpGroupId': group_id,
-             'configureServer': str_configure}
-        )
-
-        uri = '/servers/%s/ips/public/%s' % (node_id, ip)
-
-        resp = self.connection.request(uri,
-                                       method='PUT',
-                                       data=ET.tostring(elm))
-        return resp.status == 202
-
-    def ex_unshare_ip(self, node_id, ip):
-        # @TODO: Remove this if in 0.6
-        if isinstance(node_id, Node):
-            node_id = node_id.id
-
-        uri = '/servers/%s/ips/public/%s' % (node_id, ip)
-
-        resp = self.connection.request(uri,
-                                       method='DELETE')
-        return resp.status == 202
-
-    def ex_list_ip_addresses(self, node_id):
-        # @TODO: Remove this if in 0.6
-        if isinstance(node_id, Node):
-            node_id = node_id.id
-
-        uri = '/servers/%s/ips' % node_id
-        resp = self.connection.request(uri,
-                                       method='GET')
-        return self._to_ip_addresses(resp.object)
-
-    def _metadata_to_xml(self, metadata):
-        if len(metadata) == 0:
-            return None
-
-        metadata_elm = ET.Element('metadata')
-        for k, v in metadata.items():
-            meta_elm = ET.SubElement(metadata_elm, 'meta', {'key': str(k)})
-            meta_elm.text = str(v)
-
-        return metadata_elm
-
-    def _files_to_xml(self, files):
-        if len(files) == 0:
-            return None
-
-        personality_elm = ET.Element('personality')
-        for k, v in files.items():
-            file_elm = ET.SubElement(personality_elm,
-                                     'file',
-                                     {'path': str(k)})
-            file_elm.text = base64.b64encode(v)
-
-        return personality_elm
-
-    def _reboot_node(self, node, reboot_type='SOFT'):
-        resp = self._node_action(node, ['reboot', ('type', reboot_type)])
-        return resp.status == 202
-
-    def ex_soft_reboot_node(self, node):
-        return self._reboot_node(node, reboot_type='SOFT')
-
-    def ex_hard_reboot_node(self, node):
-        return self._reboot_node(node, reboot_type='HARD')
-
-    def reboot_node(self, node):
-        return self._reboot_node(node, reboot_type='HARD')
-
-    def destroy_node(self, node):
-        uri = '/servers/%s' % (node.id)
-        resp = self.connection.request(uri, method='DELETE')
-        return resp.status == 202
-
-    def ex_get_node_details(self, node_id):
-        # @TODO: Remove this if in 0.6
-        if isinstance(node_id, Node):
-            node_id = node_id.id
-
-        uri = '/servers/%s' % (node_id)
-        resp = self.connection.request(uri, method='GET')
-        if resp.status == 404:
-            return None
-        return self._to_node(resp.object)
-
-    def _node_action(self, node, body):
-        if isinstance(body, list):
-            attr = ' '.join(['%s="%s"' % (item[0], item[1])
-                             for item in body[1:]])
-            body = '<%s xmlns="%s" %s/>' % (body[0], NAMESPACE, attr)
-        uri = '/servers/%s/action' % (node.id)
-        resp = self.connection.request(uri, method='POST', data=body)
-        return resp
-
-    def _to_nodes(self, object):
-        node_elements = self._findall(object, 'server')
-        return [self._to_node(el) for el in node_elements]
-
-    def _fixxpath(self, xpath):
-        # ElementTree wants namespaces in its xpaths, so here we add them.
-        return "/".join(["{%s}%s" % (NAMESPACE, e) for e in xpath.split("/")])
-
-    def _findall(self, element, xpath):
-        return element.findall(self._fixxpath(xpath))
-
-    def _to_node(self, el):
-        def get_ips(el):
-            return [ip.get('addr') for ip in el]
-
-        def get_meta_dict(el):
-            d = {}
-            for meta in el:
-                d[meta.get('key')] = meta.text
-            return d
-
-        public_ip = get_ips(self._findall(el,
-                                          'addresses/public/ip'))
-        private_ip = get_ips(self._findall(el,
-                                          'addresses/private/ip'))
-        metadata = get_meta_dict(self._findall(el, 'metadata/meta'))
-
-        n = Node(id=el.get('id'),
-                 name=el.get('name'),
-                 state=self.NODE_STATE_MAP.get(
-                     el.get('status'), NodeState.UNKNOWN),
-                 public_ip=public_ip,
-                 private_ip=private_ip,
-                 driver=self.connection.driver,
-                 extra={
-                    'password': el.get('adminPass'),
-                    'hostId': el.get('hostId'),
-                    'imageId': el.get('imageId'),
-                    'flavorId': el.get('flavorId'),
-                    'uri': "https://%s%s/servers/%s" % (
-                         self.connection.host,
-                         self.connection.request_path, el.get('id')),
-                    'metadata': metadata,
-                 })
-        return n
-
-    def _to_sizes(self, object):
-        elements = self._findall(object, 'flavor')
-        return [self._to_size(el) for el in elements]
-
-    def _to_size(self, el):
-        s = NodeSize(id=el.get('id'),
-                     name=el.get('name'),
-                     ram=int(el.get('ram')),
-                     disk=int(el.get('disk')),
-                     bandwidth=None,  # XXX: needs hardcode
-                     price=self._get_size_price(el.get('id')),  # Hardcoded,
-                     driver=self.connection.driver)
-        return s
-
-    def _to_images(self, object):
-        elements = self._findall(object, "image")
-        return [self._to_image(el)
-                for el in elements
-                if el.get('status') == 'ACTIVE']
-
-    def _to_image(self, el):
-        i = NodeImage(id=el.get('id'),
-                      name=el.get('name'),
-                      driver=self.connection.driver,
-                      extra={'updated': el.get('updated'),
-                             'created': el.get('created'),
-                             'status': el.get('status'),
-                             'serverId': el.get('serverId'),
-                             'progress': el.get('progress')})
-        return i
-
-    def ex_limits(self):
-        """
-        Extra call to get account's limits, such as
-        rates (for example amount of POST requests per day)
-        and absolute limits like total amount of available
-        RAM to be used by servers.
-
-        @return: C{dict} with keys 'rate' and 'absolute'
-        """
-
-        def _to_rate(el):
-            rate = {}
-            for item in el.items():
-                rate[item[0]] = item[1]
-
-            return rate
-
-        def _to_absolute(el):
-            return {el.get('name'): el.get('value')}
-
-        limits = self.connection.request("/limits").object
-        rate = [_to_rate(el) for el in self._findall(limits, 'rate/limit')]
-        absolute = {}
-        for item in self._findall(limits, 'absolute/limit'):
-            absolute.update(_to_absolute(item))
-
-        return {"rate": rate, "absolute": absolute}
-
-    def ex_save_image(self, node, name):
-        """Create an image for node.
-
-        @keyword    node: node to use as a base for image
-        @param      node: L{Node}
-        @keyword    name: name for new image
-        @param      name: C{string}
-        """
-
-        image_elm = ET.Element(
-                'image',
-                {'xmlns': NAMESPACE,
-                    'name': name,
-                    'serverId': node.id}
-        )
-
-        return self._to_image(self.connection.request("/images",
-                    method="POST",
-                    data=ET.tostring(image_elm)).object)
-
-    def _to_shared_ip_group(self, el):
-        servers_el = self._findall(el, 'servers')
-        if servers_el:
-            servers = [s.get('id')
-                       for s in self._findall(servers_el[0], 'server')]
-        else:
-            servers = None
-        return RackspaceSharedIpGroup(id=el.get('id'),
-                                      name=el.get('name'),
-                                      servers=servers)
-
-    def _to_ip_addresses(self, el):
-        return RackspaceNodeIpAddresses(
-            [ip.get('addr') for ip in
-             self._findall(self._findall(el, 'public')[0], 'ip')],
-            [ip.get('addr') for ip in
-             self._findall(self._findall(el, 'private')[0], 'ip')]
-        )
-
 
 class RackspaceUKConnection(RackspaceConnection):
     """
@@ -655,52 +63,3 @@ class RackspaceUKNodeDriver(RackspaceNod
 
     def list_locations(self):
         return [NodeLocation(0, 'Rackspace UK London', 'UK', self)]
-
-
-class OpenStackResponse(RackspaceResponse):
-
-    def has_content_type(self, content_type):
-        content_type_header = dict([(key, value) for key, value in
-                                    self.headers.items()
-                                    if key.lower() == 'content-type'])
-        if not content_type_header:
-            return False
-
-        content_type_value = content_type_header['content-type'].lower()
-
-        return content_type_value.find(content_type.lower()) > -1
-
-    def parse_body(self):
-        if not self.has_content_type('application/xml') or not self.body:
-            return self.body
-
-        try:
-            return ET.XML(self.body)
-        except:
-            raise MalformedResponseError(
-                'Failed to parse XML',
-                body=self.body,
-                driver=RackspaceNodeDriver)
-
-
-class OpenStackConnection(RackspaceConnection):
-
-    responseCls = OpenStackResponse
-
-    def __init__(self, user_id, key, secure, host, port):
-        super(OpenStackConnection, self).__init__(user_id, key, secure=secure)
-        self.auth_host = host
-        self.port = (port, port)
-
-
-class OpenStackNodeDriver(RackspaceNodeDriver):
-    name = 'OpenStack'
-    connectionCls = OpenStackConnection
-
-    def _get_size_price(self, size_id):
-        if 'openstack' not in PRICING_DATA['compute']:
-            return 0.0
-
-        return get_size_price(driver_type='compute',
-                              driver_name='openstack',
-                              size_id=size_id)

Modified: libcloud/trunk/libcloud/loadbalancer/drivers/rackspace.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/loadbalancer/drivers/rackspace.py?rev=1166136&r1=1166135&r2=1166136&view=diff
==============================================================================
--- libcloud/trunk/libcloud/loadbalancer/drivers/rackspace.py (original)
+++ libcloud/trunk/libcloud/loadbalancer/drivers/rackspace.py Wed Sep  7 11:58:16 2011
@@ -25,9 +25,9 @@ from libcloud.common.base import Respons
 from libcloud.loadbalancer.base import LoadBalancer, Member, Driver, Algorithm
 from libcloud.loadbalancer.base import DEFAULT_ALGORITHM
 from libcloud.loadbalancer.types import State
-from libcloud.common.rackspace import (AUTH_HOST_US, AUTH_HOST_UK,
-        RackspaceBaseConnection)
-
+from libcloud.common.openstack import OpenStackBaseConnection
+from libcloud.common.rackspace import (
+        AUTH_HOST_US, AUTH_HOST_UK)
 
 class RackspaceResponse(Response):
 
@@ -41,7 +41,7 @@ class RackspaceResponse(Response):
             return json.loads(self.body)
 
 
-class RackspaceConnection(RackspaceBaseConnection):
+class RackspaceConnection(OpenStackBaseConnection):
     responseCls = RackspaceResponse
     auth_host = AUTH_HOST_US
     _url_key = "lb_url"

Modified: libcloud/trunk/libcloud/pricing.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/pricing.py?rev=1166136&r1=1166135&r2=1166136&view=diff
==============================================================================
--- libcloud/trunk/libcloud/pricing.py (original)
+++ libcloud/trunk/libcloud/pricing.py Wed Sep  7 11:58:16 2011
@@ -27,13 +27,18 @@ from os.path import join as pjoin
 
 PRICING_FILE_PATH = 'data/pricing.json'
 
-PRICING_DATA = {
-    'compute': {},
-    'storage': {}
-}
+PRICING_DATA = {}
 
 VALID_PRICING_DRIVER_TYPES = [ 'compute', 'storage' ]
 
+def clear_pricing_data():
+    PRICING_DATA.clear()
+    PRICING_DATA.update({
+        'compute': {},
+        'storage': {},
+    })
+clear_pricing_data()
+
 def get_pricing_file_path(file_path=None):
     pricing_directory = os.path.dirname(os.path.abspath(__file__))
     pricing_file_path = pjoin(pricing_directory, PRICING_FILE_PATH)

Modified: libcloud/trunk/libcloud/storage/drivers/cloudfiles.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/storage/drivers/cloudfiles.py?rev=1166136&r1=1166135&r2=1166136&view=diff
==============================================================================
--- libcloud/trunk/libcloud/storage/drivers/cloudfiles.py (original)
+++ libcloud/trunk/libcloud/storage/drivers/cloudfiles.py Wed Sep  7 11:58:16 2011
@@ -34,9 +34,10 @@ from libcloud.storage.types import Objec
 from libcloud.storage.types import ObjectHashMismatchError
 from libcloud.storage.types import InvalidContainerNameError
 from libcloud.common.types import LazyList
+from libcloud.common.openstack import OpenStackBaseConnection
 
 from libcloud.common.rackspace import (
-    AUTH_HOST_US, AUTH_HOST_UK, RackspaceBaseConnection)
+    AUTH_HOST_US, AUTH_HOST_UK)
 
 CDN_HOST = 'cdn.clouddrive.com'
 API_VERSION = 'v1.0'
@@ -82,7 +83,7 @@ class CloudFilesResponse(Response):
 class CloudFilesRawResponse(CloudFilesResponse, RawResponse):
     pass
 
-class CloudFilesConnection(RackspaceBaseConnection):
+class CloudFilesConnection(OpenStackBaseConnection):
     """
     Base connection class for the Cloudfiles driver.
     """

Modified: libcloud/trunk/test/__init__.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/__init__.py?rev=1166136&r1=1166135&r2=1166136&view=diff
==============================================================================
--- libcloud/trunk/test/__init__.py (original)
+++ libcloud/trunk/test/__init__.py Wed Sep  7 11:58:16 2011
@@ -21,6 +21,9 @@ from cStringIO import StringIO
 from urllib2 import urlparse
 from cgi import parse_qs
 
+XML_HEADERS = {'content-type': 'application/xml'}
+
+
 class LibcloudTestCase(unittest.TestCase):
     def __init__(self, *args, **kwargs):
         self._visited_urls = []
@@ -90,11 +93,11 @@ class MockResponse(object):
 
 class BaseMockHttpObject(object):
     def _get_method_name(self, type, use_param, qs, path):
-        meth_name = path.replace('/','_').replace('.', '_').replace('-','_')
+        meth_name = path.replace('/', '_').replace('.', '_').replace('-', '_')
         if type:
             meth_name = '%s_%s' % (meth_name, self.type)
         if use_param:
-            param = qs[self.use_param][0].replace('.', '_').replace('-','_')
+            param = qs[self.use_param][0].replace('.', '_').replace('-', '_')
             meth_name = '%s_%s' % (meth_name, param)
         return meth_name
 

Modified: libcloud/trunk/test/compute/__init__.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/__init__.py?rev=1166136&r1=1166135&r2=1166136&view=diff
==============================================================================
--- libcloud/trunk/test/compute/__init__.py (original)
+++ libcloud/trunk/test/compute/__init__.py Wed Sep  7 11:58:16 2011
@@ -14,8 +14,11 @@
 # limitations under the License.
 
 from libcloud.compute.base import Node, NodeImage, NodeLocation
+from libcloud.pricing import get_pricing
 
 class TestCaseMixin(object):
+    should_list_locations = True
+    should_have_pricing = False
 
     def test_list_nodes_response(self):
         nodes = self.driver.list_nodes()
@@ -39,8 +42,10 @@ class TestCaseMixin(object):
         for image in images:
             self.assertTrue(isinstance(image, NodeImage))
 
-
     def test_list_locations_response(self):
+        if not self.should_list_locations:
+            return None
+
         locations = self.driver.list_locations()
         self.assertTrue(isinstance(locations, list))
         for dc in locations:
@@ -67,6 +72,19 @@ class TestCaseMixin(object):
         ret = self.driver.reboot_node(node)
         self.assertTrue(isinstance(ret, bool))
 
+    def test_get_pricing_success(self):
+        if not self.should_have_pricing:
+            return None
+
+        driver_type = 'compute'
+        try:
+            get_pricing(driver_type=driver_type, driver_name=self.driver.api_name)
+        except KeyError:
+            self.fail("No {driver_type!r} pricing info for {driver}.".format(
+                driver=self.driver.__class__.__name__,
+                driver_type=driver_type,
+            ))
+
 if __name__ == "__main__":
     import doctest
     doctest.testmod()

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_flavors_detail.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_flavors_detail.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_flavors_detail.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_flavors_detail.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<flavors xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+  <flavor disk="10" ram="256" name="256 slice" id="1"/>
+  <flavor disk="20" ram="512" name="512 slice" id="2"/>
+  <flavor disk="40" ram="1024" name="1GB slice" id="3"/>
+  <flavor disk="80" ram="2048" name="2GB slice" id="4"/>
+  <flavor disk="160" ram="4096" name="4GB slice" id="5"/>
+  <flavor disk="320" ram="8192" name="8GB slice" id="6"/>
+  <flavor disk="620" ram="15872" name="15.5GB slice" id="7"/>
+</flavors>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_images_detail.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_images_detail.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_images_detail.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_images_detail.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<images xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+  <image status="ACTIVE" created="2009-07-20T09:14:37-05:00" updated="2009-07-20T09:14:37-05:00" name="CentOS 5.2" id="2"/>
+  <image status="ACTIVE" created="2009-07-20T09:14:37-05:00" updated="2009-07-20T09:14:37-05:00" name="Gentoo 2008.0" id="3"/>
+  <image status="ACTIVE" created="2009-07-20T09:14:37-05:00" updated="2009-07-20T09:14:37-05:00" name="Debian 5.0 (lenny)" id="4"/>
+  <image status="ACTIVE" created="2009-07-20T09:14:37-05:00" updated="2009-07-20T09:14:37-05:00" name="Fedora 10 (Cambridge)" id="5"/>
+  <image status="ACTIVE" created="2009-07-20T09:14:37-05:00" updated="2009-07-20T09:14:37-05:00" name="CentOS 5.3" id="7"/>
+  <image status="ACTIVE" created="2009-07-20T09:14:37-05:00" updated="2009-07-20T09:14:37-05:00" name="Ubuntu 9.04 (jaunty)" id="8"/>
+  <image status="ACTIVE" created="2009-07-20T09:14:37-05:00" updated="2009-07-20T09:14:37-05:00" name="Arch 2009.02" id="9"/>
+  <image status="ACTIVE" created="2009-07-20T09:14:37-05:00" updated="2009-07-20T09:14:37-05:00" name="Ubuntu 8.04.2 LTS (hardy)" id="10"/>
+  <image status="ACTIVE" created="2009-07-20T09:14:37-05:00" updated="2009-07-20T09:14:37-05:00" name="Ubuntu 8.10 (intrepid)" id="11"/>
+  <image status="ACTIVE" created="2009-07-20T09:14:37-05:00" updated="2009-07-20T09:14:37-05:00" name="Red Hat EL 5.3" id="12"/>
+  <image status="ACTIVE" created="2009-07-20T09:14:37-05:00" updated="2009-07-20T09:14:37-05:00" name="Fedora 11 (Leonidas)" id="13"/>
+  <image status="ACTIVE" progress="100" created="2009-11-29T20:22:09-06:00" updated="2009-11-29T20:24:08-06:00" serverId="91221" name="daily" id="191234"/>
+</images>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_images_post.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_images_post.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_images_post.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_images_post.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<image xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" status="QUEUED" updated="2010-11-19T23:36:58-06:00" serverId="444222" name="imgtest" id="12345"/>
+

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_limits.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_limits.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_limits.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_limits.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,15 @@
+<?xml version="1.0" ?>
+<limits xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+	<rate>
+		<limit URI="*changes-since*" regex="changes-since" remaining="3" resetTime="1288636970" unit="MINUTE" value="3" verb="GET"/>
+		<limit URI="*" regex=".*" remaining="10" resetTime="1288636970" unit="MINUTE" value="10" verb="PUT"/>
+		<limit URI="*" regex=".*" remaining="10" resetTime="1288636970" unit="MINUTE" value="10" verb="POST"/>
+		<limit URI="*" regex=".*" remaining="600" resetTime="1288636970" unit="MINUTE" value="600" verb="DELETE"/>
+		<limit URI="/servers*" regex="^/servers" remaining="500" resetTime="1288636970" unit="DAY" value="500" verb="POST"/>
+	</rate>
+	<absolute>
+		<limit name="maxIPGroupMembers" value="25"/>
+		<limit name="maxIPGroups" value="25"/>
+		<limit name="maxTotalRAMSize" value="921600"/>
+	</absolute>
+</limits>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<server xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" status="BUILD" progress="0" hostId="9dd380940fcbe39cb30255ed4664f1f3" flavorId="1" imageId="11" adminPass="racktestvJq7d3" id="72258" name="racktest">
+  <metadata/>
+  <addresses>
+    <public>
+      <ip addr="67.23.21.33"/>
+    </public>
+    <private>
+      <ip addr="10.176.168.218"/>
+    </private>
+  </addresses>
+</server>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<servers xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+  <server status="ACTIVE" progress="100" hostId="9dd380940fcbe39cb30255ed4664f1f3" flavorId="1" imageId="11" id="72258" name="racktest">
+    <metadata/>
+    <addresses>
+      <public>
+	<ip addr="67.23.21.33"/>
+      </public>
+      <private>
+	<ip addr="10.176.168.218"/>
+      </private>
+    </addresses>
+  </server>
+</servers>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_missing.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_missing.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_missing.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_missing.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<servers xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+  <server status="ACTIVE" progress="100" hostId="9dd380940fcbe39cb30255ed4664f1f3" flavorId="1" imageId="11" id="11111" name="racktest">
+    <metadata/>
+    <addresses>
+      <public>
+    <ip addr="67.23.21.33"/>
+      </public>
+      <private>
+    <ip addr="10.176.168.218"/>
+      </private>
+    </addresses>
+  </server>
+</servers>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_pending.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_pending.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_pending.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_pending.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<servers xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+  <server status="BUILD" progress="100" hostId="9dd380940fcbe39cb30255ed4664f1f3" flavorId="1" imageId="11" id="12345" name="racktest">
+    <metadata/>
+    <addresses>
+      <public>
+    <ip addr="67.23.21.33"/>
+      </public>
+      <private>
+    <ip addr="10.176.168.218"/>
+      </private>
+    </addresses>
+  </server>
+</servers>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_same_uuid.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_same_uuid.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_same_uuid.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_same_uuid.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<servers xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+  <server status="ACTIVE" progress="100" hostId="9dd380940fcbe39cb30255ed4664f1f3" flavorId="1" imageId="11" id="12345" name="racktest">
+    <metadata/>
+    <addresses>
+      <public>
+    <ip addr="67.23.21.33"/>
+      </public>
+      <private>
+    <ip addr="10.176.168.218"/>
+      </private>
+    </addresses>
+ </server>
+ <server status="ACTIVE" progress="100" hostId="9dd380940fcbe39cb30255ed4664f1f3" flavorId="1" imageId="11" id="12345" name="racktest">
+    <metadata/>
+    <addresses>
+      <public>
+    <ip addr="67.23.21.33"/>
+      </public>
+      <private>
+    <ip addr="10.176.168.218"/>
+      </private>
+    </addresses>
+  </server>
+
+</servers>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_success.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_success.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_success.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_deployment_success.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<servers xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+  <server status="ACTIVE" progress="100" hostId="9dd380940fcbe39cb30255ed4664f1f3" flavorId="1" imageId="11" id="12345" name="racktest">
+    <metadata/>
+    <addresses>
+      <public>
+    <ip addr="67.23.21.33"/>
+      </public>
+      <private>
+    <ip addr="10.176.168.218"/>
+      </private>
+    </addresses>
+  </server>
+</servers>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_empty.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_empty.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_empty.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_empty.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<servers xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"/>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_metadata.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_metadata.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_metadata.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_detail_metadata.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<servers xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+  <server status="ACTIVE" progress="100" hostId="9dd380940fcbe39cb30255ed4664f1f3" flavorId="1" imageId="11" id="72258" name="racktest">
+    <metadata>
+      <meta key="somekey">somevalue</meta>
+    </metadata>
+    <addresses>
+      <public>
+	<ip addr="67.23.21.33"/>
+      </public>
+      <private>
+	<ip addr="10.176.168.218"/>
+      </private>
+    </addresses>
+  </server>
+</servers>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_ips.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_ips.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_ips.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_ips.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<addresses xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+    <public>
+        <ip addr="67.23.10.132"/>
+        <ip addr="67.23.10.131"/>
+    </public>
+    <private>
+        <ip addr="10.176.42.16"/>
+    </private>
+</addresses>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_metadata.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_metadata.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_metadata.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_servers_metadata.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<server xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" status="BUILD" progress="0" hostId="9dd380940fcbe39cb30255ed4664f1f3" flavorId="1" imageId="11" adminPass="racktestvJq7d3" id="72258" name="racktest">
+  <metadata>
+    <meta key="a">b</meta>
+    <meta key="c">d</meta>
+  </metadata>
+  <addresses>
+    <public>
+      <ip addr="67.23.21.33"/>
+    </public>
+    <private>
+      <ip addr="10.176.168.218"/>
+    </private>
+  </addresses>
+</server>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_group.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_group.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_group.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_group.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<sharedIpGroup xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" id="1234" name="Shared IP Group 1">
+    <servers>
+        <server id="422"/>
+    </servers>
+</sharedIpGroup>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_groups.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_groups.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_groups.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_groups.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<sharedIpGroups xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+    <sharedIpGroup id="1234" name="Shared IP Group 1"/>
+    <sharedIpGroup id="5678" name="Shared IP Group 2"/>
+</sharedIpGroups>

Added: libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_groups_detail.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_groups_detail.xml?rev=1166136&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_groups_detail.xml (added)
+++ libcloud/trunk/test/compute/fixtures/openstack/v1_slug_shared_ip_groups_detail.xml Wed Sep  7 11:58:16 2011
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<sharedIpGroups xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+    <sharedIpGroup id="1234" name="Shared IP Group 1">
+        <servers>
+            <server id="422" />
+            <server id="3445" />
+        </servers>
+    </sharedIpGroup>
+    <sharedIpGroup id="5678" name="Shared IP Group 2">
+        <servers>
+            <server id="23203"/>
+            <server id="2456" />
+            <server id="9891" />
+        </servers>
+    </sharedIpGroup>
+</sharedIpGroups>

Modified: libcloud/trunk/test/compute/test_deployment.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/test_deployment.py?rev=1166136&r1=1166135&r2=1166136&view=diff
==============================================================================
--- libcloud/trunk/test/compute/test_deployment.py (original)
+++ libcloud/trunk/test/compute/test_deployment.py Wed Sep  7 11:58:16 2011
@@ -24,10 +24,9 @@ from libcloud.compute.deployment import 
 from libcloud.compute.base import Node
 from libcloud.compute.types import NodeState, DeploymentError, LibcloudError
 from libcloud.compute.ssh import BaseSSHClient
-from libcloud.compute.drivers.ec2 import EC2NodeDriver
 from libcloud.compute.drivers.rackspace import RackspaceNodeDriver as Rackspace
 
-from test import MockHttp
+from test import MockHttp, XML_HEADERS
 from test.file_fixtures import ComputeFileFixtures
 from mock import Mock, patch
 
@@ -43,7 +42,7 @@ class MockClient(BaseSSHClient):
         self.stderr = ''
         self.exit_status = 0
 
-    def put(self,  path, contents, chmod=755):
+    def put(self, path, contents, chmod=755):
         return contents
 
     def run(self, name):
@@ -321,7 +320,7 @@ class DeploymentTests(unittest.TestCase)
 
 class RackspaceMockHttp(MockHttp):
 
-    fixtures = ComputeFileFixtures('rackspace')
+    fixtures = ComputeFileFixtures('openstack')
 
     # fake auth token response
     def _v1_0(self, method, url, body, headers):
@@ -334,24 +333,24 @@ class RackspaceMockHttp(MockHttp):
 
     def _v1_0_slug_servers_detail(self, method, url, body, headers):
         body = self.fixtures.load('v1_slug_servers_detail_deployment_success.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+        return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
 
     def _v1_0_slug_servers_detail_1_SECOND_DELAY(self, method, url, body, headers):
         time.sleep(1)
         body = self.fixtures.load('v1_slug_servers_detail_deployment_success.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+        return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
 
     def _v1_0_slug_servers_detail_TIMEOUT(self, method, url, body, headers):
         body = self.fixtures.load('v1_slug_servers_detail_deployment_pending.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+        return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
 
     def _v1_0_slug_servers_detail_MISSING(self, method, url, body, headers):
         body = self.fixtures.load('v1_slug_servers_detail_deployment_missing.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+        return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
 
     def _v1_0_slug_servers_detail_SAME_UUID(self, method, url, body, headers):
         body = self.fixtures.load('v1_slug_servers_detail_deployment_same_uuid.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+        return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
 
 
 if __name__ == '__main__':