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 2014/09/14 13:25:12 UTC

[2/3] git commit: [Libcloud-589] Add a new compute driver for ProfitBricks provider.

[Libcloud-589] Add a new compute driver for ProfitBricks provider.

Closes #352


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

Branch: refs/heads/trunk
Commit: 34cff3012c097b1f37b821bea60eb2c761d4fe15
Parents: 6869ebc
Author: Matt Baldwin <ba...@gmail.com>
Authored: Tue Aug 26 13:59:37 2014 -0700
Committer: Tomaz Muraus <to...@apache.org>
Committed: Sun Sep 14 13:20:13 2014 +0200

----------------------------------------------------------------------
 CHANGES.rst                                     |    4 +
 libcloud/compute/drivers/profitbricks.py        | 1485 ++++++++++++++++++
 libcloud/compute/providers.py                   |    2 +
 libcloud/compute/types.py                       |    2 +
 .../fixtures/profitbricks/attach_volume.xml     |   12 +
 .../fixtures/profitbricks/create_node.xml       |   13 +
 .../fixtures/profitbricks/create_volume.xml     |   13 +
 .../fixtures/profitbricks/destroy_node.xml      |   12 +
 .../fixtures/profitbricks/destroy_volume.xml    |   12 +
 .../fixtures/profitbricks/detach_volume.xml     |   12 +
 .../profitbricks/ex_clear_datacenter.xml        |   12 +
 .../profitbricks/ex_create_datacenter.xml       |   13 +
 .../ex_create_network_interface.xml             |   13 +
 .../profitbricks/ex_describe_datacenter.xml     |   15 +
 .../ex_describe_network_interface.xml           |   26 +
 .../fixtures/profitbricks/ex_describe_node.xml  |   77 +
 .../profitbricks/ex_describe_volume.xml         |   22 +
 .../profitbricks/ex_destroy_datacenter.xml      |   10 +
 .../ex_destroy_network_interface.xml            |   12 +
 .../profitbricks/ex_list_datacenters.xml        |   19 +
 .../profitbricks/ex_list_network_interfaces.xml |   75 +
 .../fixtures/profitbricks/ex_start_node.xml     |   10 +
 .../fixtures/profitbricks/ex_stop_node.xml      |   10 +
 .../profitbricks/ex_update_datacenter.xml       |   12 +
 .../ex_update_network_interface.xml             |   12 +
 .../fixtures/profitbricks/ex_update_node.xml    |   12 +
 .../fixtures/profitbricks/ex_update_volume.xml  |   12 +
 .../fixtures/profitbricks/list_images.xml       |   43 +
 .../fixtures/profitbricks/list_nodes.xml        |  172 ++
 .../fixtures/profitbricks/list_volumes.xml      |   66 +
 .../fixtures/profitbricks/reboot_node.xml       |   10 +
 libcloud/test/compute/test_profitbricks.py      |  509 ++++++
 libcloud/test/secrets.py-dist                   |    1 +
 33 files changed, 2730 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/CHANGES.rst
----------------------------------------------------------------------
diff --git a/CHANGES.rst b/CHANGES.rst
index 1495ea7..e9ac0fd 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -98,6 +98,10 @@ Compute
   Reported by Xavier Barbosa.
   [Tomaz Muraus, Xavier Barbosa]
 
+- Add new driver for ProfitBricks provider.
+  (LIBCLOUD-589, GITHUB-352)
+  [Matt Baldwin]
+
 Storage
 ~~~~~~~
 

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/compute/drivers/profitbricks.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/profitbricks.py b/libcloud/compute/drivers/profitbricks.py
new file mode 100644
index 0000000..0d4a5ea
--- /dev/null
+++ b/libcloud/compute/drivers/profitbricks.py
@@ -0,0 +1,1485 @@
+# 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.
+"""ProfitBricks Compute driver
+"""
+import base64
+
+import copy
+import time
+
+try:
+    from lxml import etree as ET
+except ImportError:
+    from xml.etree import ElementTree as ET
+
+from libcloud.utils.networking import is_private_subnet
+from libcloud.utils.py3 import b
+from libcloud.compute.providers import Provider
+from libcloud.common.base import ConnectionUserAndKey, XmlResponse
+from libcloud.compute.base import Node, NodeDriver, NodeLocation, NodeSize
+from libcloud.compute.base import NodeImage, StorageVolume
+from libcloud.compute.base import UuidMixin
+from libcloud.compute.types import NodeState
+from libcloud.common.types import LibcloudError, MalformedResponseError
+
+__all__ = [
+    'API_VERSION',
+    'API_HOST',
+    'ProfitBricksNodeDriver',
+    'Datacenter',
+    'ProfitBricksNetworkInterface',
+    'ProfitBricksAvailabilityZone'
+]
+
+API_HOST = 'api.profitbricks.com'
+API_VERSION = '/1.3/'
+
+
+class ProfitBricksResponse(XmlResponse):
+    """
+    ProfitBricks response parsing.
+    """
+    def parse_error(self):
+        try:
+            body = ET.XML(self.body)
+        except:
+            raise MalformedResponseError('Failed to parse XML',
+                                         body=self.body,
+                                         driver=ProfitBricksNodeDriver)
+
+        for e in body.findall('.//detail'):
+            if ET.iselement(e[0].find('httpCode')):
+                http_code = e[0].find('httpCode').text
+            else:
+                http_code = None
+            if ET.iselement(e[0].find('faultCode')):
+                fault_code = e[0].find('faultCode').text
+            else:
+                fault_code = None
+            if ET.iselement(e[0].find('message')):
+                message = e[0].find('message').text
+            else:
+                message = None
+
+        return LibcloudError('HTTP Code: %s, Fault Code: %s, Message: %s' %
+                             (http_code, fault_code, message), driver=self)
+
+
+class ProfitBricksConnection(ConnectionUserAndKey):
+    """
+    Represents a single connection to the ProfitBricks endpoint.
+    """
+    host = API_HOST
+    api_prefix = API_VERSION
+    responseCls = ProfitBricksResponse
+
+    def add_default_headers(self, headers):
+        headers['Content-Type'] = 'text/xml'
+        headers['Authorization'] = 'Basic %s' % (base64.b64encode(
+            b('%s:%s' % (self.user_id, self.key))).decode('utf-8'))
+
+        return headers
+
+    def encode_data(self, data):
+        soap_env = ET.Element('soapenv:Envelope', {
+            'xmlns:soapenv': 'http://schemas.xmlsoap.org/soap/envelope/',
+            'xmlns:ws': 'http://ws.api.profitbricks.com/'
+            })
+        ET.SubElement(soap_env, 'soapenv:Header')
+        soap_body = ET.SubElement(soap_env, 'soapenv:Body')
+        soap_req_body = ET.SubElement(soap_body, 'ws:%s' % (data['action']))
+
+        if 'request' in data.keys():
+            soap_req_body = ET.SubElement(soap_req_body, 'request')
+            for key, value in data.items():
+                if key not in ['action', 'request']:
+                    child = ET.SubElement(soap_req_body, key)
+                    child.text = value
+        else:
+            for key, value in data.items():
+                if key != 'action':
+                    child = ET.SubElement(soap_req_body, key)
+                    child.text = value
+
+        soap_post = ET.tostring(soap_env)
+
+        return soap_post
+
+    def request(self, action, params=None, data=None, headers=None,
+                method='POST', raw=False):
+        action = self.api_prefix + action
+
+        return super(ProfitBricksConnection, self).request(action=action,
+                                                           params=params,
+                                                           data=data,
+                                                           headers=headers,
+                                                           method=method,
+                                                           raw=raw)
+
+
+class Datacenter(UuidMixin):
+    """
+    Class which stores information about ProfitBricks datacenter
+    instances.
+
+    :param      id: The datacenter ID.
+    :type       id: ``str``
+
+    :param      name: The datacenter name.
+    :type       name: ``str``
+
+    :param version: Datacenter version.
+    :type version: ``str``
+
+
+    Note: This class is ProfitBricks specific.
+    """
+    def __init__(self, id, name, version, driver, extra=None):
+        self.id = str(id)
+        self.name = name
+        self.version = version
+        self.driver = driver
+        self.extra = extra or {}
+        UuidMixin.__init__(self)
+
+    def __repr__(self):
+        return ((
+            '<Datacenter: id=%s, name=%s, version=%s, driver=%s> ...>')
+            % (self.id, self.name, self.version,
+                self.driver.name))
+
+
+class ProfitBricksNetworkInterface(object):
+    """
+    Class which stores information about ProfitBricks network
+    interfaces.
+
+    :param      id: The network interface ID.
+    :type       id: ``str``
+
+    :param      name: The network interface name.
+    :type       name: ``str``
+
+    :param      state: The network interface name.
+    :type       state: ``int``
+
+    Note: This class is ProfitBricks specific.
+    """
+    def __init__(self, id, name, state, extra=None):
+        self.id = id
+        self.name = name
+        self.state = state
+        self.extra = extra or {}
+
+    def __repr__(self):
+        return (('<ProfitBricksNetworkInterface: id=%s, name=%s>')
+                % (self.id, self.name))
+
+
+class ProfitBricksAvailabilityZone(object):
+    """
+    Extension class which stores information about a ProfitBricks
+    availability zone.
+
+    Note: This class is ProfitBricks specific.
+    """
+
+    def __init__(self, name):
+        self.name = name
+
+    def __repr__(self):
+        return (('<ProfitBricksAvailabilityZone: name=%s>')
+                % (self.name))
+
+
+class ProfitBricksNodeDriver(NodeDriver):
+    """
+    Base ProfitBricks node driver.
+    """
+    connectionCls = ProfitBricksConnection
+    name = 'ProfitBricks'
+    website = 'http://www.profitbricks.com'
+    type = Provider.PROFIT_BRICKS
+
+    PROVISIONING_STATE = {
+        'INACTIVE': NodeState.PENDING,
+        'INPROCESS': NodeState.PENDING,
+        'AVAILABLE': NodeState.RUNNING,
+        'DELETED': NodeState.TERMINATED,
+    }
+
+    NODE_STATE_MAP = {
+        'NOSTATE': NodeState.UNKNOWN,
+        'RUNNING': NodeState.RUNNING,
+        'BLOCKED': NodeState.STOPPED,
+        'PAUSE': NodeState.STOPPED,
+        'SHUTDOWN': NodeState.PENDING,
+        'SHUTOFF': NodeState.STOPPED,
+        'CRASHED': NodeState.STOPPED,
+    }
+
+    REGIONS = {
+        '1': {'region': 'us/las', 'country': 'USA'},
+        '2': {'region': 'de/fra', 'country': 'DEU'},
+        '3': {'region': 'de/fkb', 'country': 'DEU'},
+    }
+
+    AVAILABILITY_ZONE = {
+        '1': {'name': 'AUTO'},
+        '2': {'name': 'ZONE_1'},
+        '3': {'name': 'ZONE_2'},
+    }
+
+    """
+    ProfitBricks is unique in that they allow the user to define all aspects
+    of the instance size, i.e. disk size, core size, and memory size.
+
+    These are instance types that match up with what other providers support.
+
+    You can configure disk size, core size, and memory size using the ex_
+    parameters on the create_node method.
+    """
+
+    PROFIT_BRICKS_GENERIC_SIZES = {
+        '1': {
+            'id': '1',
+            'name': 'Micro',
+            'ram': 1024,
+            'disk': 50,
+            'cores': 1
+        },
+        '2': {
+            'id': '2',
+            'name': 'Small Instance',
+            'ram': 2048,
+            'disk': 50,
+            'cores': 1
+        },
+        '3': {
+            'id': '3',
+            'name': 'Medium Instance',
+            'ram': 4096,
+            'disk': 50,
+            'cores': 2
+        },
+        '4': {
+            'id': '4',
+            'name': 'Large Instance',
+            'ram': 7168,
+            'disk': 50,
+            'cores': 4
+        },
+        '5': {
+            'id': '5',
+            'name': 'ExtraLarge Instance',
+            'ram': 14336,
+            'disk': 50,
+            'cores': 8
+        },
+        '6': {
+            'id': '6',
+            'name': 'Memory Intensive Instance Medium',
+            'ram': 28672,
+            'disk': 50,
+            'cores': 4
+        },
+        '7': {
+            'id': '7',
+            'name': 'Memory Intensive Instance Large',
+            'ram': 57344,
+            'disk': 50,
+            'cores': 8
+        }
+    }
+
+    """
+    Core Functions
+    """
+
+    def list_sizes(self):
+        """
+        Lists all sizes
+
+        :rtype: ``list`` of :class:`NodeSize`
+        """
+        sizes = []
+
+        for key, values in self.PROFIT_BRICKS_GENERIC_SIZES.items():
+            node_size = self._to_node_size(values)
+            sizes.append(node_size)
+
+        return sizes
+
+    def list_images(self):
+        """
+        List all images.
+
+        :rtype: ``list`` of :class:`NodeImage`
+        """
+
+        action = 'getAllImages'
+        body = {'action': action}
+
+        return self._to_images(self.connection.request(action=action,
+                               data=body, method='POST').object)
+
+    def list_locations(self):
+        """
+        List all locations.
+        """
+        locations = []
+
+        for key, values in self.REGIONS.items():
+            location = self._to_location(values)
+            locations.append(location)
+
+        return locations
+
+    def list_nodes(self):
+        """
+        List all nodes.
+
+        :rtype: ``list`` of :class:`Node`
+        """
+        action = 'getAllServers'
+        body = {'action': action}
+
+        return self._to_nodes(self.connection.request(action=action,
+                              data=body, method='POST').object)
+
+    def reboot_node(self, node):
+        """
+        Reboots the node.
+
+        :rtype: ``bool``
+        """
+        action = 'resetServer'
+        body = {'action': action,
+                'serverId': node.id
+                }
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+
+        return True
+
+    def create_node(self, name, image, size=None, volume=None,
+                    ex_datacenter=None, ex_internet_access=True,
+                    ex_availability_zone=None, ex_ram=None,
+                    ex_cores=None, ex_disk=None, **kwargs):
+        """
+        Creates a node.
+
+        image is optional as long as you pass ram, cores, and disk
+        to the method. ProfitBricks allows you to adjust compute
+        resources at a much more granular level.
+
+        :param volume: If the volume already exists then pass this in.
+        :type volume: :class:`StorageVolume`
+
+        :param ex_datacenter: If you've already created the DC then pass
+                           it in.
+        :type ex_datacenter: :class:`Datacenter`
+
+        :param ex_internet_access: Configure public Internet access.
+        :type ex_internet_access: : ``bool``
+
+        :param ex_availability_zone: The availability zone.
+        :type ex_availability_zone: class: `ProfitBricksAvailabilityZone`
+
+        :param ex_ram: The amount of ram required.
+        :type ex_ram: : ``int``
+
+        :param ex_cores: The number of cores required.
+        :type ex_cores: : ``int``
+
+        :param ex_disk: The amount of disk required.
+        :type ex_disk: : ``int``
+
+        :return:    Instance of class ``Node``
+        :rtype:     :class:`Node`
+        """
+        if not ex_datacenter:
+            '''
+            We generate a name from the server name passed into the function.
+            '''
+
+            'Creating a Datacenter for the node since one was not provided.'
+            new_datacenter = self._create_new_datacenter_for_node(name=name)
+            datacenter_id = new_datacenter.id
+
+            'Waiting for the Datacenter create operation to finish.'
+            self._wait_for_datacenter_state(datacenter=new_datacenter)
+        else:
+            datacenter_id = ex_datacenter.id
+            new_datacenter = None
+
+        if not size:
+            if not ex_ram:
+                raise ValueError('You need to either pass a '
+                                 'NodeSize or specify ex_ram as '
+                                 'an extra parameter.')
+            if not ex_cores:
+                raise ValueError('You need to either pass a '
+                                 'NodeSize or specify ex_cores as '
+                                 'an extra parameter.')
+
+        if not volume:
+            if not size:
+                if not ex_disk:
+                    raise ValueError('You need to either pass a '
+                                     'StorageVolume, a NodeSize, or specify '
+                                     'ex_disk as an extra parameter.')
+
+        '''
+        You can override the suggested sizes by passing in unique
+        values for ram, cores, and disk allowing you to size it
+        for your specific use.
+        '''
+
+        if not ex_disk:
+            ex_disk = size.disk
+
+        if not ex_ram:
+            ex_ram = size.ram
+
+        if not ex_cores:
+            ex_cores = size.extra['cores']
+
+        '''
+        A pasword is automatically generated if it is
+        not provided. This is then sent via email to
+        the admin contact on record.
+        '''
+
+        if 'auth' in kwargs:
+            auth = self._get_and_check_auth(kwargs["auth"])
+            password = auth.password
+        else:
+            password = None
+
+        '''
+        Create a StorageVolume that can be attached to the
+        server when it is created.
+        '''
+        if not volume:
+            volume = self._create_node_volume(ex_disk=ex_disk,
+                                              image=image,
+                                              password=password,
+                                              name=name,
+                                              ex_datacenter=ex_datacenter,
+                                              new_datacenter=new_datacenter)
+
+            storage_id = volume.id
+
+            'Waiting on the storage volume to be created before provisioning '
+            'the instance.'
+            self._wait_for_storage_volume_state(volume)
+        else:
+            if ex_datacenter:
+                datacenter_id = ex_datacenter.id
+            else:
+                datacenter_id = volume.extra['datacenter_id']
+
+            storage_id = volume.id
+
+        action = 'createServer'
+        body = {'action': action,
+                'request': 'true',
+                'serverName': name,
+                'cores': str(ex_cores),
+                'ram': str(ex_ram),
+                'bootFromStorageId': storage_id,
+                'internetAccess': str(ex_internet_access).lower(),
+                'dataCenterId': datacenter_id
+                }
+
+        if ex_availability_zone:
+            body['availabilityZone'] = ex_availability_zone.name
+
+        data = self.connection.request(action=action,
+                                       data=body,
+                                       method='POST').object
+        nodes = self._to_nodes(data)
+        return nodes[0]
+
+    def destroy_node(self, node, ex_remove_attached_disks=False):
+        """
+        Destroys a node.
+
+        :param node: The node you wish to destroy.
+        :type volume: :class:`Node`
+
+        :param ex_remove_attached_disks: True to destory all attached volumes.
+        :type ex_remove_attached_disks: : ``bool``
+
+        :rtype:     : ``bool``
+        """
+        action = 'deleteServer'
+        body = {'action': action,
+                'serverId': node.id
+                }
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+
+        return True
+
+    """
+    Volume Functions
+    """
+
+    def list_volumes(self):
+        """
+        Lists all voumes.
+        """
+        action = 'getAllStorages'
+        body = {'action': action}
+
+        return self._to_volumes(self.connection.request(action=action,
+                                                        data=body,
+                                                        method='POST').object)
+
+    def attach_volume(self, node, volume, device=None, ex_bus_type=None):
+        """
+        Attaches a volume.
+
+        :param volume: The volume you're attaching.
+        :type volume: :class:`StorageVolume`
+
+        :param node: The node to which you're attaching the volume.
+        :type node: :class:`Node`
+
+        :param device: The device number order.
+        :type device: : ``int``
+
+        :param ex_bus_type: Bus type. Either IDE or VIRTIO (default).
+        :type ex_bus_type: ``str``
+
+        :return:    Instance of class ``StorageVolume``
+        :rtype:     :class:`StorageVolume`
+        """
+        action = 'connectStorageToServer'
+        body = {'action': action,
+                'request': 'true',
+                'storageId': volume.id,
+                'serverId': node.id,
+                'busType': ex_bus_type,
+                'deviceNumber': str(device)
+                }
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+        return volume
+
+    def create_volume(self, size, name=None,
+                      ex_datacenter=None, ex_image=None, ex_password=None):
+        """
+        Creates a volume.
+
+        :param ex_datacenter: The datacenter you're placing
+                              the storage in. (req)
+        :type ex_datacenter: :class:`Datacenter`
+
+        :param ex_image: The OS image for the volume.
+        :type ex_image: :class:`NodeImage`
+
+        :param ex_password: Optional password for root.
+        :type ex_password: : ``str``
+
+        :return:    Instance of class ``StorageVolume``
+        :rtype:     :class:`StorageVolume`
+        """
+        action = 'createStorage'
+        body = {'action': action,
+                'request': 'true',
+                'size': str(size),
+                'storageName': name,
+                'mountImageId': ex_image.id
+                }
+
+        if ex_datacenter:
+            body['dataCenterId'] = ex_datacenter.id
+
+        if ex_password:
+            body['profitBricksImagePassword'] = ex_password
+
+        data = self.connection.request(action=action,
+                                       data=body,
+                                       method='POST').object
+        volumes = self._to_volumes(data)
+        return volumes[0]
+
+    def detach_volume(self, volume):
+        """
+        Detaches a volume.
+
+        :param volume: The volume you're detaching.
+        :type volume: :class:`StorageVolume`
+
+        :rtype:     :``bool``
+        """
+        node_id = volume.extra['server_id']
+
+        action = 'disconnectStorageFromServer'
+        body = {'action': action,
+                'storageId': volume.id,
+                'serverId': node_id
+                }
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+
+        return True
+
+    def destroy_volume(self, volume):
+        """
+        Destroys a volume.
+
+        :param volume: The volume you're attaching.
+        :type volume: :class:`StorageVolume`
+
+        :rtype:     : ``bool``
+        """
+        action = 'deleteStorage'
+        body = {'action': action,
+                'storageId': volume.id}
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+
+        return True
+
+    def ex_update_volume(self, volume, storage_name=None, size=None):
+        """
+        Updates a volume.
+
+        :param volume: The volume you're attaching..
+        :type volume: :class:`StorageVolume`
+
+        :param storage_name: The name of the volume.
+        :type storage_name: : ``str``
+
+        :param size: The desired size.
+        :type size: ``int``
+
+        :rtype:     : ``bool``
+        """
+        action = 'updateStorage'
+        body = {'action': action,
+                'request': 'true',
+                'storageId': volume.id
+                }
+
+        if storage_name:
+            body['storageName'] = storage_name
+        if size:
+            body['size'] = str(size)
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+
+        return True
+
+    def ex_describe_volume(self, volume_id):
+        """
+        Describes a volume.
+
+        :param volume_id: The ID of the volume you're describing.
+        :type volume_id: :class:`StorageVolume`
+
+        :return:    Instance of class ``StorageVolume``
+        :rtype:     :class:`StorageVolume`
+        """
+        action = 'getStorage'
+        body = {'action': action,
+                'storageId': volume_id
+                }
+
+        data = self.connection.request(action=action,
+                                       data=body,
+                                       method='POST').object
+        volumes = self._to_volumes(data)
+        return volumes[0]
+
+    """
+    Extension Functions
+    """
+
+    ''' Server Extension Functions
+    '''
+    def ex_stop_node(self, node):
+        """
+        Stops a node.
+
+        This also dealloctes the public IP space.
+
+        :param node: The node you wish to halt.
+        :type node: :class:`Node`
+
+        :rtype:     : ``bool``
+        """
+        action = 'stopServer'
+        body = {'action': action,
+                'serverId': node.id
+                }
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+
+        return True
+
+    def ex_start_node(self, node):
+        """
+        Starts a volume.
+
+        :param node: The node you wish to start.
+        :type node: :class:`Node`
+
+        :rtype:     : ``bool``
+        """
+        action = 'startServer'
+        body = {'action': action,
+                'serverId': node.id
+                }
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+
+        return True
+
+    def ex_list_availability_zones(self):
+        """
+        Returns a list of availability zones.
+        """
+
+        availability_zones = []
+
+        for key, values in self.AVAILABILITY_ZONE.items():
+            name = copy.deepcopy(values)["name"]
+
+            availability_zone = ProfitBricksAvailabilityZone(
+                name=name
+            )
+            availability_zones.append(availability_zone)
+
+        return availability_zones
+
+    def ex_describe_node(self, node):
+        """
+        Describes a node.
+
+        :param node: The node you wish to describe.
+        :type node: :class:`Node`
+
+        :return:    Instance of class ``Node``
+        :rtype:     :class:`Node`
+        """
+        action = 'getServer'
+        body = {'action': action,
+                'serverId': node.id
+                }
+
+        data = self.connection.request(action=action,
+                                       data=body,
+                                       method='POST').object
+        nodes = self._to_nodes(data)
+        return nodes[0]
+
+    def ex_update_node(self, node, name=None, cores=None,
+                       ram=None, availability_zone=None):
+        """
+        Updates a node.
+
+        :param cores: The number of CPUs the node should have.
+        :type device: : ``int``
+
+        :param ram: The amount of ram the machine should have.
+        :type ram: : ``int``
+
+        :param ex_availability_zone: Update the availability zone.
+        :type ex_availability_zone: :class:`ProfitBricksAvailabilityZone`
+
+        :rtype:     : ``bool``
+        """
+        action = 'updateServer'
+
+        body = {'action': action,
+                'request': 'true',
+                'serverId': node.id
+                }
+
+        if name:
+            body['serverName'] = name
+
+        if cores:
+            body['cores'] = str(cores)
+
+        if ram:
+            body['ram'] = str(ram)
+
+        if availability_zone:
+            body['availabilityZone'] = availability_zone.name
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+
+        return True
+
+    '''
+    Datacenter Extension Functions
+    '''
+
+    def ex_create_datacenter(self, name, location):
+        """
+        Creates a datacenter.
+
+        ProfitBricks has a concept of datacenters.
+        These represent buckets into which you
+        can place various compute resources.
+
+        :param name: The DC name.
+        :type name: : ``str``
+
+        :param location: The DC region.
+        :type location: : ``str``
+
+        :return:    Instance of class ``Datacenter``
+        :rtype:     :class:`Datacenter`
+        """
+        action = 'createDataCenter'
+
+        body = {'action': action,
+                'request': 'true',
+                'dataCenterName': name,
+                'location': location.lower()
+                }
+        data = self.connection.request(action=action,
+                                       data=body,
+                                       method='POST').object
+        datacenters = self._to_datacenters(data)
+        return datacenters[0]
+
+    def ex_destroy_datacenter(self, datacenter):
+        """
+        Destroys a datacenter.
+
+        :param datacenter: The DC you're destroying.
+        :type datacenter: :class:`Datacenter`
+
+        :rtype:     : ``bool``
+        """
+        action = 'deleteDataCenter'
+        body = {'action': action,
+                'dataCenterId': datacenter.id
+                }
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+
+        return True
+
+    def ex_describe_datacenter(self, datacenter_id):
+        """
+        Describes a datacenter.
+
+        :param datacenter_id: The DC you are describing.
+        :type datacenter_id: ``str``
+
+        :return:    Instance of class ``Datacenter``
+        :rtype:     :class:`Datacenter`
+        """
+
+        action = 'getDataCenter'
+        body = {'action': action,
+                'dataCenterId': datacenter_id
+                }
+
+        data = self.connection.request(action=action,
+                                       data=body,
+                                       method='POST').object
+        datacenters = self._to_datacenters(data)
+        return datacenters[0]
+
+    def ex_list_datacenters(self):
+        """
+        Lists all datacenters.
+
+        :return:    ``list`` of class ``Datacenter``
+        :rtype:     :class:`Datacenter`
+        """
+        action = 'getAllDataCenters'
+        body = {'action': action}
+
+        return self._to_datacenters(self.connection.request(
+                                    action=action,
+                                    data=body,
+                                    method='POST').object)
+
+    def ex_rename_datacenter(self, datacenter, name):
+        """
+        Update a datacenter.
+
+        :param datacenter: The DC you are renaming.
+        :type datacenter: :class:`Datacenter`
+
+        :param name: The DC name.
+        :type name: : ``str``
+
+        :rtype:     : ``bool``
+        """
+        action = 'updateDataCenter'
+        body = {'action': action,
+                'request': 'true',
+                'dataCenterId': datacenter.id,
+                'dataCenterName': name
+                }
+
+        self.connection.request(action=action,
+                                data=body,
+                                method='POST').object
+
+        return True
+
+    def ex_clear_datacenter(self, datacenter):
+        """
+        Clear a datacenter.
+
+        This removes all objects in a DC.
+
+        :param datacenter: The DC you're clearing.
+        :type datacenter: :class:`Datacenter`
+
+        :rtype:     : ``bool``
+        """
+        action = 'clearDataCenter'
+        body = {'action': action,
+                'dataCenterId': datacenter.id
+                }
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+
+        return True
+
+    '''
+    Network Interface Extension Functions
+    '''
+
+    def ex_list_network_interfaces(self):
+        """
+        Lists all network interfaces.
+
+        :return:    ``list`` of class ``ProfitBricksNetworkInterface``
+        :rtype:     :class:`ProfitBricksNetworkInterface`
+        """
+        action = 'getAllNic'
+        body = {'action': action}
+
+        return self._to_interfaces(
+            self.connection.request(action=action,
+                                    data=body,
+                                    method='POST').object)
+
+    def ex_describe_network_interface(self, network_interface):
+        """
+        Describes a network interface.
+
+        :param network_interface: The NIC you wish to describe.
+        :type network_interface: :class:`ProfitBricksNetworkInterface`
+
+        :return:    Instance of class ``ProfitBricksNetworkInterface``
+        :rtype:     :class:`ProfitBricksNetworkInterface`
+        """
+        action = 'getNic'
+        body = {'action': action,
+                'nicId': network_interface.id
+                }
+
+        return self._to_interface(
+            self.connection.request(
+                action=action,
+                data=body,
+                method='POST').object.findall('.//return')[0])
+
+    def ex_create_network_interface(self, node,
+                                    lan_id=None, ip=None, nic_name=None,
+                                    dhcp_active=True):
+        """
+        Creates a network interface.
+
+        :param lan_id: The ID for the LAN.
+        :type lan_id: : ``int``
+
+        :param ip: The IP address for the NIC.
+        :type ip: ``str``
+
+        :param nic_name: The name of the NIC, e.g. PUBLIC.
+        :type nic_name: ``str``
+
+        :param dhcp_active: Set to false to disable.
+        :type dhcp_active: ``bool``
+
+        :return:    Instance of class ``ProfitBricksNetworkInterface``
+        :rtype:     :class:`ProfitBricksNetworkInterface`
+        """
+        action = 'createNic'
+        body = {'action': action,
+                'request': 'true',
+                'serverId': node.id,
+                'dhcpActive': str(dhcp_active)
+                }
+
+        if lan_id:
+            body['lanId'] = str(lan_id)
+        else:
+            body['lanId'] = str(1)
+
+        if ip:
+            body['ip'] = ip
+
+        if nic_name:
+            body['nicName'] = nic_name
+
+        data = self.connection.request(action=action,
+                                       data=body,
+                                       method='POST').object
+        interfaces = self._to_interfaces(data)
+        return interfaces[0]
+
+    def ex_update_network_interface(self, network_interface, name=None,
+                                    lan_id=None, ip=None,
+                                    dhcp_active=None):
+        """
+        Updates a network interface.
+
+        :param lan_id: The ID for the LAN.
+        :type lan_id: : ``int``
+
+        :param ip: The IP address for the NIC.
+        :type ip: ``str``
+
+        :param name: The name of the NIC, e.g. PUBLIC.
+        :type name: ``str``
+
+        :param dhcp_active: Set to false to disable.
+        :type dhcp_active: ``bool``
+
+        :rtype:     : ``bool``
+        """
+        action = 'updateNic'
+        body = {'action': action,
+                'request': 'true',
+                'nicId': network_interface.id
+                }
+
+        if name:
+            body['nicName'] = name
+
+        if lan_id:
+            body['lanId'] = str(lan_id)
+
+        if ip:
+            body['ip'] = ip
+
+        if dhcp_active is not None:
+            body['dhcpActive'] = str(dhcp_active).lower()
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+
+        return True
+
+    def ex_destroy_network_interface(self, network_interface):
+        """
+        Destroy a network interface.
+
+        :param network_interface: The NIC you wish to describe.
+        :type network_interface: :class:`ProfitBricksNetworkInterface`
+
+        :rtype:     : ``bool``
+        """
+
+        action = 'deleteNic'
+        body = {'action': action,
+                'nicId': network_interface.id}
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+
+        return True
+
+    def ex_set_inet_access(self, datacenter,
+                           network_interface, internet_access=True):
+
+        action = 'setInternetAccess'
+
+        body = {'action': action,
+                'dataCenterId': datacenter.id,
+                'lanId': network_interface.extra['lan_id'],
+                'internetAccess': str(internet_access).lower()
+                }
+
+        self.connection.request(action=action,
+                                data=body, method='POST').object
+
+        return True
+
+    """
+    Private Functions
+    """
+
+    def _to_datacenters(self, object):
+        return [self._to_datacenter(
+            datacenter) for datacenter in object.findall('.//return')]
+
+    def _to_datacenter(self, datacenter):
+        datacenter_id = datacenter.find('dataCenterId').text
+        if ET.iselement(datacenter.find('dataCenterName')):
+            datacenter_name = datacenter.find('dataCenterName').text
+        else:
+            datacenter_name = None
+        version = datacenter.find('dataCenterVersion').text
+        if ET.iselement(datacenter.find('provisioningState')):
+            provisioning_state = datacenter.find('provisioningState').text
+        else:
+            provisioning_state = None
+        if ET.iselement(datacenter.find('location')):
+            location = datacenter.find('location').text
+        else:
+            location = None
+
+        provisioning_state = self.PROVISIONING_STATE.get(provisioning_state,
+                                                         NodeState.UNKNOWN)
+
+        return Datacenter(id=datacenter_id,
+                          name=datacenter_name,
+                          version=version,
+                          driver=self.connection.driver,
+                          extra={'provisioning_state': provisioning_state,
+                                 'location': location})
+
+    def _to_images(self, object):
+        return [self._to_image(image) for image in object.findall('.//return')]
+
+    def _to_image(self, image):
+        image_id = image.find('imageId').text
+        image_name = image.find('imageName').text
+        image_size = image.find('imageSize').text
+        image_type = image.find('imageType').text
+        os_type = image.find('osType').text
+        public = image.find('public').text
+        writeable = image.find('writeable').text
+
+        if ET.iselement(image.find('cpuHotpluggable')):
+            cpu_hotpluggable = image.find('cpuHotpluggable').text
+        else:
+            cpu_hotpluggable = None
+
+        if ET.iselement(image.find('memoryHotpluggable')):
+            memory_hotpluggable = image.find('memoryHotpluggable').text
+        else:
+            memory_hotpluggable = None
+
+        if ET.iselement(image.find('location')):
+            if image.find('region'):
+                image_region = image.find('region').text
+            else:
+                image_region = None
+        else:
+            image_region = None
+
+        return NodeImage(id=image_id,
+                         name=image_name,
+                         driver=self.connection.driver,
+                         extra={'image_size': image_size,
+                                'image_type': image_type,
+                                'cpu_hotpluggable': cpu_hotpluggable,
+                                'memory_hotpluggable': memory_hotpluggable,
+                                'os_type': os_type,
+                                'public': public,
+                                'location': image_region,
+                                'writeable': writeable})
+
+    def _to_nodes(self, object):
+        return [self._to_node(n) for n in object.findall('.//return')]
+
+    def _to_node(self, node):
+        """
+        Convert the request into a node Node
+        """
+        ATTRIBUTE_NAME_MAP = {
+            'dataCenterId': 'datacenter_id',
+            'dataCenterVersion': 'datacenter_version',
+            'serverId': 'node_id',
+            'serverName': 'node_name',
+            'cores': 'cores',
+            'ram': 'ram',
+            'internetAccess': 'internet_access',
+            'provisioningState': 'provisioning_state',
+            'virtualMachineState': 'virtual_machine_state',
+            'creationTime': 'creation_time',
+            'lastModificationTime': 'last_modification_time',
+            'osType': 'os_type',
+            'availabilityZone': 'availability_zone',
+            'cpuHotPlug': 'cpu_hotpluggable',
+            'ramHotPlug': 'memory_hotpluggable',
+            'nicHotPlug': 'nic_hotpluggable',
+            'discVirtioHotPlug': 'disc_virtio_hotplug',
+            'discVirtioHotUnPlug': 'disc_virtio_hotunplug'
+        }
+
+        extra = {}
+        for attribute_name, extra_name in ATTRIBUTE_NAME_MAP.items():
+            elem = node.find(attribute_name)
+
+            if ET.iselement(elem):
+                value = elem.text
+            else:
+                value = None
+
+            extra[extra_name] = value
+
+        public_ips = []
+        private_ips = []
+
+        if ET.iselement(node.find('nics')):
+            for nic in node.findall('.//nics'):
+                n_elements = list(nic.findall('.//ips'))
+                if len(n_elements) > 0:
+                    ip = n_elements[0].text
+                    if is_private_subnet(ip):
+                        private_ips.append(ip)
+                    else:
+                        public_ips.append(ip)
+
+        extra['provisioning_state'] = self.PROVISIONING_STATE.get(
+            extra['provisioning_state'], NodeState.UNKNOWN)
+
+        node_id = extra['node_id']
+        node_name = extra['node_name']
+        state = self.NODE_STATE_MAP.get(extra['virtual_machine_state'],
+                                        NodeState.UNKNOWN)
+
+        return Node(
+            id=node_id,
+            name=node_name,
+            state=state,
+            public_ips=public_ips,
+            private_ips=private_ips,
+            driver=self.connection.driver,
+            extra=extra)
+
+    def _to_volumes(self, object):
+        return [self._to_volume(
+            volume) for volume in object.findall('.//return')]
+
+    def _to_volume(self, volume, node=None):
+        ATTRIBUTE_NAME_MAP = {
+            'dataCenterId': 'datacenter_id',
+            'storageId': 'storage_id',
+            'storageName': 'storage_name',
+            'serverIds': 'server_id',
+            'creationTime': 'creation_time',
+            'lastModificationTime': 'last_modification_time',
+            'provisioningState': 'provisioning_state',
+            'size': 'size',
+        }
+
+        extra = {}
+        for attribute_name, extra_name in ATTRIBUTE_NAME_MAP.items():
+            elem = volume.find(attribute_name)
+
+            if ET.iselement(elem):
+                value = elem.text
+            else:
+                value = None
+
+            extra[extra_name] = value
+
+        if ET.iselement(volume.find('mountImage')):
+            image_id = volume.find('mountImage')[0].text
+            image_name = volume.find('mountImage')[1].text
+        else:
+            image_id = None
+            image_name = None
+
+        extra['image_id'] = image_id
+        extra['image_name'] = image_name
+        extra['size'] = int(extra['size']) if extra['size'] else 0
+        extra['provisioning_state'] = \
+            self.PROVISIONING_STATE.get(extra['provisioning_state'],
+                                        NodeState.UNKNOWN)
+
+        storage_id = extra['storage_id']
+        storage_name = extra['storage_name']
+        size = extra['size']
+
+        return StorageVolume(
+            id=storage_id,
+            name=storage_name,
+            size=size,
+            driver=self.connection.driver,
+            extra=extra)
+
+    def _to_interfaces(self, object):
+        return [self._to_interface(
+            interface) for interface in object.findall('.//return')]
+
+    def _to_interface(self, interface):
+        ATTRIBUTE_NAME_MAP = {
+            'nicId': 'nic_id',
+            'nicName': 'nic_name',
+            'serverId': 'server_id',
+            'lanId': 'lan_id',
+            'internetAccess': 'internet_access',
+            'macAddress': 'mac_address',
+            'dhcpActive': 'dhcp_active',
+            'gatewayIp': 'gateway_ip',
+            'provisioningState': 'provisioning_state',
+            'dataCenterId': 'datacenter_id',
+            'dataCenterVersion': 'datacenter_version'
+        }
+
+        extra = {}
+        for attribute_name, extra_name in ATTRIBUTE_NAME_MAP.items():
+            elem = interface.find(attribute_name)
+
+            if ET.iselement(elem):
+                value = elem.text
+            else:
+                value = None
+
+            extra[extra_name] = value
+
+        ips = []
+
+        if ET.iselement(interface.find('ips')):
+            for ip in interface.findall('.//ips'):
+                ips.append(ip.text)
+
+        extra['ips'] = ips
+
+        nic_id = extra['nic_id']
+        nic_name = extra['nic_name']
+        state = self.PROVISIONING_STATE.get(extra['provisioning_state'],
+                                            NodeState.UNKNOWN)
+
+        return ProfitBricksNetworkInterface(
+            id=nic_id,
+            name=nic_name,
+            state=state,
+            extra=extra)
+
+    def _to_location(self, data):
+
+        return NodeLocation(id=data["region"],
+                            name=data["region"],
+                            country=data["country"],
+                            driver=self.connection.driver)
+
+    def _to_node_size(self, data):
+        """
+        Convert the PROFIT_BRICKS_GENERIC_SIZES into NodeSize
+        """
+        return NodeSize(id=data["id"],
+                        name=data["name"],
+                        ram=data["ram"],
+                        disk=data["disk"],
+                        bandwidth=None,
+                        price=None,
+                        driver=self.connection.driver,
+                        extra={
+                            'cores': data["cores"]})
+
+    def _wait_for_datacenter_state(self, datacenter, state=NodeState.RUNNING,
+                                   timeout=300, interval=5):
+        """
+        Private function that waits the datacenter to transition into the
+        specified state.
+
+        :return: Datacenter object on success.
+        :rtype: :class:`.Datacenter`
+        """
+        wait_time = 0
+        datacenter = self.ex_describe_datacenter(datacenter_id=datacenter.id)
+
+        while (datacenter.extra['provisioning_state'] != state):
+            datacenter = \
+                self.ex_describe_datacenter(datacenter_id=datacenter.id)
+            if datacenter.extra['provisioning_state'] == state:
+                break
+
+            if wait_time >= timeout:
+                raise Exception('Datacenter didn\'t transition to %s state '
+                                'in %s seconds' % (state, timeout))
+
+            wait_time += interval
+            time.sleep(interval)
+
+        return datacenter
+
+    def _create_new_datacenter_for_node(self, name):
+        """
+        Creates a Datacenter for a node.
+        """
+        dc_name = name + '-DC'
+
+        return self.ex_create_datacenter(name=dc_name, location='us/las')
+
+    def _wait_for_storage_volume_state(self, volume, state=NodeState.RUNNING,
+                                       timeout=300, interval=5):
+        """
+        Wait for volume to transition into the specified state.
+
+        :return: Volume object on success.
+        :rtype: :class:`Volume`
+        """
+        wait_time = 0
+        volume = self.ex_describe_volume(volume_id=volume.id)
+
+        while (volume.extra['provisioning_state'] != state):
+            volume = self.ex_describe_volume(volume_id=volume.id)
+            if volume.extra['provisioning_state'] == state:
+                break
+
+            if wait_time >= timeout:
+                raise Exception('Volume didn\'t transition to %s state '
+                                'in %s seconds' % (state, timeout))
+
+            wait_time += interval
+            time.sleep(interval)
+
+        return volume
+
+    def _create_node_volume(self, ex_disk, image, password,
+                            name, ex_datacenter=None, new_datacenter=None):
+
+        volume_name = name + '-volume'
+
+        if ex_datacenter:
+            volume = self.create_volume(size=ex_disk,
+                                        ex_datacenter=ex_datacenter,
+                                        ex_image=image,
+                                        ex_password=password,
+                                        name=volume_name)
+        else:
+            volume = self.create_volume(size=ex_disk,
+                                        ex_datacenter=new_datacenter,
+                                        ex_image=image,
+                                        ex_password=password,
+                                        name=volume_name)
+
+        return volume

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/compute/providers.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/providers.py b/libcloud/compute/providers.py
index 5c19f61..b449e78 100644
--- a/libcloud/compute/providers.py
+++ b/libcloud/compute/providers.py
@@ -149,6 +149,8 @@ DRIVERS = {
     ('libcloud.compute.drivers.ec2', 'OutscaleINCNodeDriver'),
     Provider.VSPHERE:
     ('libcloud.compute.drivers.vsphere', 'VSphereNodeDriver'),
+    Provider.PROFIT_BRICKS:
+    ('libcloud.compute.drivers.profitbricks', 'ProfitBricksNodeDriver'),
 
     # Deprecated
     Provider.CLOUDSIGMA_US:

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/compute/types.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/types.py b/libcloud/compute/types.py
index c7c7616..f07bc48 100644
--- a/libcloud/compute/types.py
+++ b/libcloud/compute/types.py
@@ -76,6 +76,7 @@ class Provider(object):
     :cvar IKOULA: Ikoula driver.
     :cvar OUTSCALE_SAS: Outscale SAS driver.
     :cvar OUTSCALE_INC: Outscale INC driver.
+    :cvar PROFIT_BRICKS: ProfitBricks driver.
     """
     DUMMY = 'dummy'
     EC2 = 'ec2_us_east'
@@ -122,6 +123,7 @@ class Provider(object):
     OUTSCALE_SAS = 'outscale_sas'
     OUTSCALE_INC = 'outscale_inc'
     VSPHERE = 'vsphere'
+    PROFIT_BRICKS = 'profitbricks'
 
     # OpenStack based providers
     HPCLOUD = 'hpcloud'

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/attach_volume.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/attach_volume.xml b/libcloud/test/compute/fixtures/profitbricks/attach_volume.xml
new file mode 100644
index 0000000..8b2b1f5
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/attach_volume.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:connectStorageToServerResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3613039</requestId>
+            <dataCenterId>c2df1871-6aac-458e-ad1a-ef3f530cb7aa</dataCenterId>
+            <dataCenterVersion>4</dataCenterVersion>
+         </return>
+      </ns2:connectStorageToServerResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/create_node.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/create_node.xml b/libcloud/test/compute/fixtures/profitbricks/create_node.xml
new file mode 100644
index 0000000..ad515ba
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/create_node.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:createServerReturn xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3768523</requestId>
+            <dataCenterId>3aefc31b-57e9-4af6-8348-af961ac00f74</dataCenterId>
+            <dataCenterVersion>3</dataCenterVersion>
+            <serverId>7b18b85f-cc93-4c2d-abcc-5ce732d35750</serverId>
+         </return>
+      </ns2:createServerReturn>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/create_volume.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/create_volume.xml b/libcloud/test/compute/fixtures/profitbricks/create_volume.xml
new file mode 100644
index 0000000..326879a
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/create_volume.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:createStorageReturn xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3532463</requestId>
+            <dataCenterId>06eac419-c2b3-4761-aeb9-10efdd2cf292</dataCenterId>
+            <dataCenterVersion>3</dataCenterVersion>
+            <storageId>f54aeea3-667a-4460-8cf0-80909509df0c</storageId>
+         </return>
+      </ns2:createStorageReturn>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/destroy_node.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/destroy_node.xml b/libcloud/test/compute/fixtures/profitbricks/destroy_node.xml
new file mode 100644
index 0000000..1dacfaf
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/destroy_node.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:deleteServerResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3498434</requestId>
+            <dataCenterId>782247bf-f12d-4f08-8050-302c02c4b2e0</dataCenterId>
+            <dataCenterVersion>2</dataCenterVersion>
+         </return>
+      </ns2:deleteServerResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/destroy_volume.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/destroy_volume.xml b/libcloud/test/compute/fixtures/profitbricks/destroy_volume.xml
new file mode 100644
index 0000000..0591e1c
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/destroy_volume.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:deleteStorageResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3616447</requestId>
+            <dataCenterId>c2df1871-6aac-458e-ad1a-ef3f530cb7aa</dataCenterId>
+            <dataCenterVersion>13</dataCenterVersion>
+         </return>
+      </ns2:deleteStorageResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/detach_volume.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/detach_volume.xml b/libcloud/test/compute/fixtures/profitbricks/detach_volume.xml
new file mode 100644
index 0000000..fafc327
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/detach_volume.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:disconnectStorageFromServerResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3614242</requestId>
+            <dataCenterId>c2df1871-6aac-458e-ad1a-ef3f530cb7aa</dataCenterId>
+            <dataCenterVersion>6</dataCenterVersion>
+         </return>
+      </ns2:disconnectStorageFromServerResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_clear_datacenter.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_clear_datacenter.xml b/libcloud/test/compute/fixtures/profitbricks/ex_clear_datacenter.xml
new file mode 100644
index 0000000..5259d45
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_clear_datacenter.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:clearDataCenterResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3339052</requestId>
+            <dataCenterId>8669a69f-2274-4520-b51e-dbdf3986a476</dataCenterId>
+            <dataCenterVersion>2</dataCenterVersion>
+         </return>
+      </ns2:clearDataCenterResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_create_datacenter.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_create_datacenter.xml b/libcloud/test/compute/fixtures/profitbricks/ex_create_datacenter.xml
new file mode 100644
index 0000000..f4238d8
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_create_datacenter.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:createDataCenterResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3711001</requestId>
+            <dataCenterId>0c793dd1-d4cd-4141-86f3-8b1a24b2d604</dataCenterId>
+            <dataCenterVersion>1</dataCenterVersion>
+            <location>us/las</location>
+         </return>
+      </ns2:createDataCenterResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_create_network_interface.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_create_network_interface.xml b/libcloud/test/compute/fixtures/profitbricks/ex_create_network_interface.xml
new file mode 100644
index 0000000..830d41d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_create_network_interface.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:createNicReturn xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3633314</requestId>
+            <dataCenterId>c2df1871-6aac-458e-ad1a-ef3f530cb7aa</dataCenterId>
+            <dataCenterVersion>27</dataCenterVersion>
+            <nicId>951e1b49-5f1b-4b2b-b7d9-263dba6e2ddd</nicId>
+         </return>
+      </ns2:createNicReturn>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_describe_datacenter.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_describe_datacenter.xml b/libcloud/test/compute/fixtures/profitbricks/ex_describe_datacenter.xml
new file mode 100644
index 0000000..cb4a1a0
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_describe_datacenter.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:getDataCenterResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3719240</requestId>
+            <dataCenterId>a3e6f83a-8982-4d6a-aebc-60baf5755ede</dataCenterId>
+            <dataCenterVersion>1</dataCenterVersion>
+            <dataCenterName>StackPointCloud</dataCenterName>
+            <provisioningState>AVAILABLE</provisioningState>
+            <location>us/las</location>
+         </return>
+      </ns2:getDataCenterResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_describe_network_interface.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_describe_network_interface.xml b/libcloud/test/compute/fixtures/profitbricks/ex_describe_network_interface.xml
new file mode 100644
index 0000000..e2235b9
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_describe_network_interface.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:getNicResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3707226</requestId>
+            <dataCenterId>a3a2e730-0dc3-47e6-bac6-4c056d5e2aee</dataCenterId>
+            <dataCenterVersion>6</dataCenterVersion>
+            <nicId>f1c7a244-2fa6-44ee-8fb6-871f337683a3</nicId>
+            <lanId>1</lanId>
+            <internetAccess>false</internetAccess>
+            <serverId>c09f4f31-336c-4ad2-9ec7-591778513408</serverId>
+            <ips>10.10.38.12</ips>
+            <macAddress>02:01:96:d7:60:e0</macAddress>
+            <firewall>
+               <active>false</active>
+               <firewallId>01490a19-2b20-43cc-86a4-ff0b0460f076</firewallId>
+               <nicId>f1c7a244-2fa6-44ee-8fb6-871f337683a3</nicId>
+               <provisioningState>AVAILABLE</provisioningState>
+            </firewall>
+            <dhcpActive>true</dhcpActive>
+            <provisioningState>AVAILABLE</provisioningState>
+         </return>
+      </ns2:getNicResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_describe_node.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_describe_node.xml b/libcloud/test/compute/fixtures/profitbricks/ex_describe_node.xml
new file mode 100644
index 0000000..5567e8f
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_describe_node.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:getServerResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3706813</requestId>
+            <dataCenterId>a3a2e730-0dc3-47e6-bac6-4c056d5e2aee</dataCenterId>
+            <dataCenterVersion>6</dataCenterVersion>
+            <serverId>c09f4f31-336c-4ad2-9ec7-591778513408</serverId>
+            <serverName>server001</serverName>
+            <cores>1</cores>
+            <ram>1024</ram>
+            <internetAccess>true</internetAccess>
+            <ips>10.10.38.12</ips>
+            <ips>162.254.26.14</ips>
+            <connectedStorages>
+               <bootDevice>true</bootDevice>
+               <busType>VIRTIO</busType>
+               <deviceNumber>1</deviceNumber>
+               <size>50</size>
+               <storageId>addb19d8-e664-43c1-bd2d-ad9210edc610</storageId>
+               <storageName>storage001</storageName>
+            </connectedStorages>
+            <nics>
+               <dataCenterId>a3a2e730-0dc3-47e6-bac6-4c056d5e2aee</dataCenterId>
+               <dataCenterVersion>6</dataCenterVersion>
+               <nicId>f1c7a244-2fa6-44ee-8fb6-871f337683a3</nicId>
+               <lanId>1</lanId>
+               <internetAccess>false</internetAccess>
+               <serverId>c09f4f31-336c-4ad2-9ec7-591778513408</serverId>
+               <ips>10.10.38.12</ips>
+               <macAddress>02:01:96:d7:60:e0</macAddress>
+               <firewall>
+                  <active>false</active>
+                  <firewallId>01490a19-2b20-43cc-86a4-ff0b0460f076</firewallId>
+                  <nicId>f1c7a244-2fa6-44ee-8fb6-871f337683a3</nicId>
+                  <provisioningState>AVAILABLE</provisioningState>
+               </firewall>
+               <dhcpActive>true</dhcpActive>
+               <provisioningState>AVAILABLE</provisioningState>
+            </nics>
+            <nics>
+               <dataCenterId>a3a2e730-0dc3-47e6-bac6-4c056d5e2aee</dataCenterId>
+               <dataCenterVersion>6</dataCenterVersion>
+               <nicId>e6263870-cd70-42e4-956a-00f3bbec70e3</nicId>
+               <nicName>PUBLIC</nicName>
+               <lanId>3</lanId>
+               <internetAccess>true</internetAccess>
+               <serverId>c09f4f31-336c-4ad2-9ec7-591778513408</serverId>
+               <ips>162.254.26.14</ips>
+               <macAddress>02:01:9c:53:c3:50</macAddress>
+               <firewall>
+                  <active>false</active>
+                  <firewallId>c0fa291e-38c2-48a6-bd15-b66ba54ac18a</firewallId>
+                  <nicId>e6263870-cd70-42e4-956a-00f3bbec70e3</nicId>
+                  <provisioningState>AVAILABLE</provisioningState>
+               </firewall>
+               <dhcpActive>false</dhcpActive>
+               <gatewayIp>162.254.26.1</gatewayIp>
+               <provisioningState>AVAILABLE</provisioningState>
+            </nics>
+            <provisioningState>AVAILABLE</provisioningState>
+            <virtualMachineState>RUNNING</virtualMachineState>
+            <creationTime>2014-07-16T18:53:05.109Z</creationTime>
+            <lastModificationTime>2014-07-16T19:57:51.577Z</lastModificationTime>
+            <osType>LINUX</osType>
+            <availabilityZone>AUTO</availabilityZone>
+            <cpuHotPlug>true</cpuHotPlug>
+            <ramHotPlug>true</ramHotPlug>
+            <nicHotPlug>true</nicHotPlug>
+            <nicHotUnPlug>true</nicHotUnPlug>
+            <discVirtioHotPlug>true</discVirtioHotPlug>
+            <discVirtioHotUnPlug>true</discVirtioHotUnPlug>
+         </return>
+      </ns2:getServerResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_describe_volume.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_describe_volume.xml b/libcloud/test/compute/fixtures/profitbricks/ex_describe_volume.xml
new file mode 100644
index 0000000..050b4b5
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_describe_volume.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:getStorageResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3767716</requestId>
+            <dataCenterId>905f1346-d199-425d-a035-7dc28f6819cd</dataCenterId>
+            <dataCenterVersion>2</dataCenterVersion>
+            <storageId>00d0b9e7-e016-456f-85a0-517aa9a34bf5</storageId>
+            <size>50</size>
+            <storageName>StackPointCloud-Volume</storageName>
+            <mountImage>
+               <imageId>cd59b162-0289-11e4-9f63-52540066fee9</imageId>
+               <imageName>Debian-7-server-2014-07-01</imageName>
+            </mountImage>
+            <provisioningState>AVAILABLE</provisioningState>
+            <creationTime>2014-07-21T17:37:45.958Z</creationTime>
+            <lastModificationTime>2014-07-21T17:37:45.958Z</lastModificationTime>
+         </return>
+      </ns2:getStorageResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_destroy_datacenter.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_destroy_datacenter.xml b/libcloud/test/compute/fixtures/profitbricks/ex_destroy_datacenter.xml
new file mode 100644
index 0000000..4d36bdb
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_destroy_datacenter.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:deleteDataCenterResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3339313</requestId>
+         </return>
+      </ns2:deleteDataCenterResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_destroy_network_interface.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_destroy_network_interface.xml b/libcloud/test/compute/fixtures/profitbricks/ex_destroy_network_interface.xml
new file mode 100644
index 0000000..6584f06
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_destroy_network_interface.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:deleteNicResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3634902</requestId>
+            <dataCenterId>c2df1871-6aac-458e-ad1a-ef3f530cb7aa</dataCenterId>
+            <dataCenterVersion>31</dataCenterVersion>
+         </return>
+      </ns2:deleteNicResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_list_datacenters.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_list_datacenters.xml b/libcloud/test/compute/fixtures/profitbricks/ex_list_datacenters.xml
new file mode 100644
index 0000000..dfb9245
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_list_datacenters.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:getAllDataCentersResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <dataCenterId>a3e6f83a-8982-4d6a-aebc-60baf5755ede</dataCenterId>
+            <dataCenterName>StackPointCloud</dataCenterName>
+            <dataCenterVersion>1</dataCenterVersion>
+            <provisioningState>AVAILABLE</provisioningState>
+         </return>
+         <return>
+            <dataCenterId>c68f77b8-7ecb-40e9-8b41-79415dffc0f1</dataCenterId>
+            <dataCenterName>XYZ</dataCenterName>
+            <dataCenterVersion>2</dataCenterVersion>
+            <provisioningState>AVAILABLE</provisioningState>
+         </return>
+      </ns2:getAllDataCentersResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_list_network_interfaces.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_list_network_interfaces.xml b/libcloud/test/compute/fixtures/profitbricks/ex_list_network_interfaces.xml
new file mode 100644
index 0000000..7164f22
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_list_network_interfaces.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:getAllNicResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <dataCenterId>c2df1871-6aac-458e-ad1a-ef3f530cb7aa</dataCenterId>
+            <dataCenterVersion>26</dataCenterVersion>
+            <nicId>6b38a4f3-b851-4614-9e3a-5ddff4727727</nicId>
+            <nicName>StackPointCloud</nicName>
+            <lanId>3</lanId>
+            <internetAccess>false</internetAccess>
+            <serverId>234f0cf9-1efc-4ade-b829-036456584116</serverId>
+            <ips>10.14.96.11</ips>
+            <ips>162.254.26.14</ips>
+            <ips>162.254.26.15</ips>
+            <macAddress>02:01:40:47:90:04</macAddress>
+            <firewall>
+               <active>false</active>
+               <firewallId>e93f74b2-d969-4b7d-8fad-3931b85dbc4d</firewallId>
+               <firewallRules>
+                  <firewallRuleId>d6f7e726-c13d-464c-b454-cae726dac75d</firewallRuleId>
+                  <protocol>ANY</protocol>
+                  <sourceIp>1.2.3.4</sourceIp>
+                  <targetIp>1.2.3.4</targetIp>
+               </firewallRules>
+               <firewallRules>
+                  <firewallRuleId>87773a01-b7e2-481e-8a44-d5ffac830292</firewallRuleId>
+                  <protocol>ICMP</protocol>
+               </firewallRules>
+               <nicId>6b38a4f3-b851-4614-9e3a-5ddff4727727</nicId>
+               <provisioningState>AVAILABLE</provisioningState>
+            </firewall>
+            <dhcpActive>true</dhcpActive>
+            <provisioningState>AVAILABLE</provisioningState>
+         </return>
+         <return>
+            <dataCenterId>c2df1871-6aac-458e-ad1a-ef3f530cb7aa</dataCenterId>
+            <dataCenterVersion>26</dataCenterVersion>
+            <nicId>47e3b2ce-7846-41cc-8404-1475190e89a3</nicId>
+            <lanId>3</lanId>
+            <internetAccess>false</internetAccess>
+            <serverId>7fb5d34c-77c2-4452-b7b2-274fa0f46327</serverId>
+            <ips>10.14.96.12</ips>
+            <macAddress>02:01:fe:1c:81:73</macAddress>
+            <firewall>
+               <active>false</active>
+               <firewallId>d3d1d8b9-0dd5-4866-8429-a1817be7b6e9</firewallId>
+               <nicId>47e3b2ce-7846-41cc-8404-1475190e89a3</nicId>
+               <provisioningState>AVAILABLE</provisioningState>
+            </firewall>
+            <dhcpActive>true</dhcpActive>
+            <provisioningState>AVAILABLE</provisioningState>
+         </return>
+         <return>
+            <dataCenterId>c2df1871-6aac-458e-ad1a-ef3f530cb7aa</dataCenterId>
+            <dataCenterVersion>26</dataCenterVersion>
+            <nicId>cbd00ace-d210-43e8-8cb7-097ad6b33e82</nicId>
+            <lanId>1</lanId>
+            <internetAccess>true</internetAccess>
+            <serverId>234f0cf9-1efc-4ade-b829-036456584116</serverId>
+            <ips>208.94.38.110</ips>
+            <macAddress>02:01:e9:20:cd:81</macAddress>
+            <firewall>
+               <active>false</active>
+               <firewallId>a1a2e1da-7672-4a5c-af62-6c37edaffd26</firewallId>
+               <nicId>cbd00ace-d210-43e8-8cb7-097ad6b33e82</nicId>
+               <provisioningState>INPROCESS</provisioningState>
+            </firewall>
+            <dhcpActive>true</dhcpActive>
+            <gatewayIp>208.94.38.1</gatewayIp>
+            <provisioningState>AVAILABLE</provisioningState>
+         </return>
+      </ns2:getAllNicResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_start_node.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_start_node.xml b/libcloud/test/compute/fixtures/profitbricks/ex_start_node.xml
new file mode 100644
index 0000000..f83d6a4
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_start_node.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:stopServerResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3494585</requestId>
+         </return>
+      </ns2:stopServerResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_stop_node.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_stop_node.xml b/libcloud/test/compute/fixtures/profitbricks/ex_stop_node.xml
new file mode 100644
index 0000000..f83d6a4
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_stop_node.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:stopServerResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3494585</requestId>
+         </return>
+      </ns2:stopServerResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_update_datacenter.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_update_datacenter.xml b/libcloud/test/compute/fixtures/profitbricks/ex_update_datacenter.xml
new file mode 100644
index 0000000..270aa41
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_update_datacenter.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+  <S:Body>
+    <ns2:updateDataCenterResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+      <return>
+        <requestId>3325148</requestId>
+        <dataCenterId>d96dfafc-9a8c-4c0e-8a0c-857a15db572d</dataCenterId>
+        <dataCenterVersion>3</dataCenterVersion>
+      </return>
+    </ns2:updateDataCenterResponse>
+  </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_update_network_interface.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_update_network_interface.xml b/libcloud/test/compute/fixtures/profitbricks/ex_update_network_interface.xml
new file mode 100644
index 0000000..f9f8e0d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_update_network_interface.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:updateNicResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3665310</requestId>
+            <dataCenterId>aab9454d-c442-4d06-9dd7-7c6121ae5ca2</dataCenterId>
+            <dataCenterVersion>3</dataCenterVersion>
+         </return>
+      </ns2:updateNicResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_update_node.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_update_node.xml b/libcloud/test/compute/fixtures/profitbricks/ex_update_node.xml
new file mode 100644
index 0000000..03693a7
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_update_node.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:updateServerResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3623299</requestId>
+            <dataCenterId>c2df1871-6aac-458e-ad1a-ef3f530cb7aa</dataCenterId>
+            <dataCenterVersion>18</dataCenterVersion>
+         </return>
+      </ns2:updateServerResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/ex_update_volume.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/ex_update_volume.xml b/libcloud/test/compute/fixtures/profitbricks/ex_update_volume.xml
new file mode 100644
index 0000000..03693a7
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/ex_update_volume.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:updateServerResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <requestId>3623299</requestId>
+            <dataCenterId>c2df1871-6aac-458e-ad1a-ef3f530cb7aa</dataCenterId>
+            <dataCenterVersion>18</dataCenterVersion>
+         </return>
+      </ns2:updateServerResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/34cff301/libcloud/test/compute/fixtures/profitbricks/list_images.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/profitbricks/list_images.xml b/libcloud/test/compute/fixtures/profitbricks/list_images.xml
new file mode 100644
index 0000000..3c8ac7b
--- /dev/null
+++ b/libcloud/test/compute/fixtures/profitbricks/list_images.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
+   <S:Body>
+      <ns2:getAllImagesResponse xmlns:ns2="http://ws.api.profitbricks.com/">
+         <return>
+            <cpuHotpluggable>false</cpuHotpluggable>
+            <imageId>03b6c3e7-f2ad-11e3-a036-52540066fee9</imageId>
+            <imageName>windows-2012-r2-server-2014-06</imageName>
+            <imageSize>11264</imageSize>
+            <imageType>HDD</imageType>
+            <memoryHotpluggable>false</memoryHotpluggable>
+            <osType>WINDOWS</osType>
+            <public>true</public>
+            <region>NORTH_AMERICA</region>
+            <writeable>true</writeable>
+         </return>
+         <return>
+            <cpuHotpluggable>true</cpuHotpluggable>
+            <imageId>cd59b162-0289-11e4-9f63-52540066fee9</imageId>
+            <imageName>Debian-7-server-2014-07-01</imageName>
+            <imageSize>2048</imageSize>
+            <imageType>HDD</imageType>
+            <memoryHotpluggable>true</memoryHotpluggable>
+            <osType>LINUX</osType>
+            <public>true</public>
+            <region>NORTH_AMERICA</region>
+            <writeable>true</writeable>
+         </return>
+         <return>
+            <cpuHotpluggable>true</cpuHotpluggable>
+            <imageId>d2f627c4-0289-11e4-9f63-52540066fee9</imageId>
+            <imageName>CentOS-6-server-2014-07-01</imageName>
+            <imageSize>2048</imageSize>
+            <imageType>HDD</imageType>
+            <memoryHotpluggable>true</memoryHotpluggable>
+            <osType>LINUX</osType>
+            <public>true</public>
+            <region>NORTH_AMERICA</region>
+            <writeable>true</writeable>
+         </return>
+      </ns2:getAllImagesResponse>
+   </S:Body>
+</S:Envelope>
\ No newline at end of file