Repository: libcloud
Updated Branches:
  refs/heads/trunk 0789cb58d -> b9a5586e9


Adding tagging feature to DimensionDataNodeDriver
Closes #773


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

Branch: refs/heads/trunk
Commit: b9a5586e9672a64fcc618f087d8675c902a4b265
Parents: 0789cb5
Author: Jeffrey Dunham <jeffrey.a.dun...@gmail.com>
Authored: Fri Apr 22 19:51:27 2016 -0400
Committer: anthony-shaw <anthonys...@apache.org>
Committed: Sat Apr 23 13:20:05 2016 +1000

----------------------------------------------------------------------
 libcloud/common/dimensiondata.py                |  84 +++-
 libcloud/compute/drivers/dimensiondata.py       | 373 ++++++++++++++++++
 .../fixtures/dimensiondata/tag_applyTags.xml    |   6 +
 .../dimensiondata/tag_applyTags_BADREQUEST.xml  |   6 +
 .../fixtures/dimensiondata/tag_createTagKey.xml |   7 +
 .../tag_createTagKey_BADREQUEST.xml             |   6 +
 .../fixtures/dimensiondata/tag_deleteTagKey.xml |   6 +
 .../tag_deleteTagKey_BADREQUEST.xml             |   6 +
 .../fixtures/dimensiondata/tag_editTagKey.xml   |   6 +
 .../dimensiondata/tag_editTagKey_BADREQUEST.xml |   6 +
 .../fixtures/dimensiondata/tag_removeTag.xml    |   6 +
 .../dimensiondata/tag_removeTag_BADREQUEST.xml  |   6 +
 ...Key_5ab77f5f_5aa9_426f_8459_4eab34e03d54.xml |   6 +
 ...f_5aa9_426f_8459_4eab34e03d54_BADREQUEST.xml |   6 +
 .../fixtures/dimensiondata/tag_tagKey_list.xml  |  19 +
 .../dimensiondata/tag_tagKey_list_SINGLE.xml    |   8 +
 .../fixtures/dimensiondata/tag_tag_list.xml     |  36 ++
 libcloud/test/compute/test_dimensiondata.py     | 388 +++++++++++++++++++
 18 files changed, 980 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/common/dimensiondata.py
----------------------------------------------------------------------
diff --git a/libcloud/common/dimensiondata.py b/libcloud/common/dimensiondata.py
index 50870e7..8410106 100644
--- a/libcloud/common/dimensiondata.py
+++ b/libcloud/common/dimensiondata.py
@@ -472,7 +472,8 @@ class DimensionDataConnection(ConnectionUserAndKey):
         yield paged_resp
         paged_resp = paged_resp or {}
 
-        while paged_resp.get('pageCount') >= paged_resp.get('pageSize'):
+        while int(paged_resp.get('pageCount')) >= \
+                int(paged_resp.get('pageSize')):
             params['pageNumber'] = int(paged_resp.get('pageNumber')) + 1
             paged_resp = self.request_with_orgId_api_2(action, params,
                                                        data, headers,
@@ -1408,3 +1409,84 @@ class DimensionDataBackupSchedulePolicy(object):
     def __repr__(self):
         return (('<DimensionDataBackupSchedulePolicy: name=%s>')
                 % (self.name))
+
+
+class DimensionDataTag(object):
+    """
+    A representation of a Tag in Dimension Data
+    A Tag first must have a Tag Key, then an asset is tag with
+    a key and an option value.  Tags can be queried later to filter assets
+    and also show up on usage report if so desired.
+    """
+    def __init__(self, asset_type, asset_id, asset_name,
+                 datacenter, key, value):
+        """
+        Initialize an instance of :class:`DimensionDataTag`
+
+        :param asset_type: The type of asset.  Current asset types:
+                           SERVER, VLAN, NETWORK_DOMAIN, CUSTOMER_IMAGE,
+                           PUBLIC_IP_BLOCK, ACCOUNT
+        :type  asset_type: ``str``
+
+        :param asset_id: The GUID of the asset that is tagged
+        :type  asset_id: ``str``
+
+        :param asset_name: The name of the asset that is tagged
+        :type  asset_name: ``str``
+
+        :param datacenter: The short datacenter name of the tagged asset
+        :type  datacenter: ``str``
+
+        :param key: The tagged key
+        :type  key: :class:`DimensionDataTagKey`
+
+        :param value: The tagged value
+        :type  value: ``None`` or ``str``
+        """
+        self.asset_type = asset_type
+        self.asset_id = asset_id
+        self.asset_name = asset_name
+        self.datacenter = datacenter
+        self.key = key
+        self.value = value
+
+    def __repr__(self):
+        return (('<DimensionDataTag: asset_name=%s, tag_name=%s, value=%s>')
+                % (self.asset_name, self.key.name, self.value))
+
+
+class DimensionDataTagKey(object):
+    """
+    A representation of a Tag Key in Dimension Data
+    A tag key is required to tag an asset
+    """
+    def __init__(self, id, name, description,
+                 value_required, display_on_report):
+        """
+        Initialize an instance of :class:`DimensionDataTagKey`
+
+        :param id: GUID of the tag key
+        :type  id: ``str``
+
+        :param name: Name of the tag key
+        :type  name: ``str``
+
+        :param description: Description of the tag key
+        :type  description: ``str``
+
+        :param value_required: If a value is required for this tag key
+        :type  value_required: ``bool``
+
+        :param display_on_report: If this tag key should be displayed on
+                                  usage reports
+        :type  display_on_report: ``bool``
+        """
+        self.id = id
+        self.name = name
+        self.description = description
+        self.value_required = value_required
+        self.display_on_report = display_on_report
+
+    def __repr__(self):
+        return (('<DimensionDataTagKey: name=%s>')
+                % (self.name))

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/compute/drivers/dimensiondata.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/dimensiondata.py 
b/libcloud/compute/drivers/dimensiondata.py
index 7a969e8..a3881f6 100644
--- a/libcloud/compute/drivers/dimensiondata.py
+++ b/libcloud/compute/drivers/dimensiondata.py
@@ -39,6 +39,8 @@ from libcloud.common.dimensiondata import 
DimensionDataFirewallAddress
 from libcloud.common.dimensiondata import DimensionDataNatRule
 from libcloud.common.dimensiondata import DimensionDataAntiAffinityRule
 from libcloud.common.dimensiondata import NetworkDomainServicePlan
+from libcloud.common.dimensiondata import DimensionDataTagKey
+from libcloud.common.dimensiondata import DimensionDataTag
 from libcloud.common.dimensiondata import API_ENDPOINTS, DEFAULT_REGION
 from libcloud.common.dimensiondata import TYPES_URN
 from libcloud.common.dimensiondata import SERVER_NS, NETWORK_NS, GENERAL_NS
@@ -73,6 +75,14 @@ NODE_STATE_MAP = {
         NodeState.RECONFIGURING,
 }
 
+OBJECT_TO_TAGGING_ASSET_TYPE_MAP = {
+    'Node': 'SERVER',
+    'NodeImage': 'CUSTOMER_IMAGE',
+    'DimensionDataNetworkDomain': 'NETWORK_DOMAIN',
+    'DimensionDataVlan': 'VLAN',
+    'DimensionDataPublicIpBlock': 'PUBLIC_IP_BLOCK'
+}
+
 
 class DimensionDataNodeDriver(NodeDriver):
     """
@@ -1959,10 +1969,361 @@ class DimensionDataNodeDriver(NodeDriver):
                 raise e
         return self.ex_get_customer_image_by_id(id)
 
+    def ex_create_tag_key(self, name, description=None,
+                          value_required=True, display_on_report=True):
+        """
+        Creates a tag key in the Dimension Data Cloud
+
+        :param name: The name of the tag key (required)
+        :type  name: ``str``
+
+        :param description: The description of the tag key
+        :type  description: ``str``
+
+        :param value_required: If a value is required for the tag
+                               Tags themselves can be just a tag,
+                               or be a key/value pair
+        :type  value_required: ``bool``
+
+        :param display_on_report: Should this key show up on the usage reports
+        :type  display_on_report: ``bool``
+
+        :rtype: ``bool``
+        """
+        create_tag_key = ET.Element('createTagKey', {'xmlns': TYPES_URN})
+        ET.SubElement(create_tag_key, 'name').text = name
+        if description is not None:
+            ET.SubElement(create_tag_key, 'description').text = description
+        ET.SubElement(create_tag_key, 'valueRequired').text = \
+            str(value_required).lower()
+        ET.SubElement(create_tag_key, 'displayOnReport').text = \
+            str(display_on_report).lower()
+        response = self.connection.request_with_orgId_api_2(
+            'tag/createTagKey',
+            method='POST',
+            data=ET.tostring(create_tag_key)).object
+        response_code = findtext(response, 'responseCode', TYPES_URN)
+        return response_code in ['IN_PROGRESS', 'OK']
+
+    def ex_list_tag_keys(self, id=None, name=None,
+                         value_required=None, display_on_report=None):
+        """
+        List tag keys in the Dimension Data Cloud
+
+        :param id: Filter the list to the id of the tag key
+        :type  id: ``str``
+
+        :param name: Filter the list to the name of the tag key
+        :type  name: ``str``
+
+        :param value_required: Filter the list to if a value is required
+                               for a tag key
+        :type  value_required: ``bool``
+
+        :param display_on_report: Filter the list to if the tag key should
+                                  show up on usage reports
+        :type  display_on_report: ``bool``
+
+        :rtype: ``list`` of :class:`DimensionDataTagKey`
+        """
+        params = {}
+        if id is not None:
+            params['id'] = id
+        if name is not None:
+            params['name'] = name
+        if value_required is not None:
+            params['valueRequired'] = str(value_required).lower()
+        if display_on_report is not None:
+            params['displayOnReport'] = str(display_on_report).lower()
+
+        paged_result = self.connection.paginated_request_with_orgId_api_2(
+            'tag/tagKey',
+            method='GET',
+            params=params
+        )
+
+        tag_keys = []
+        for result in paged_result:
+            tag_keys.extend(self._to_tag_keys(result))
+        return tag_keys
+
+    def ex_get_tag_key_by_id(self, id):
+        """
+        Get a specific tag key by ID
+
+        :param id: ID of the tag key you want (required)
+        :type  id: ``str``
+
+        :rtype: :class:`DimensionDataTagKey`
+        """
+        tag_key = self.connection.request_with_orgId_api_2(
+            'tag/tagKey/%s' % id).object
+        return self._to_tag_key(tag_key)
+
+    def ex_get_tag_key_by_name(self, name):
+        """
+        Get a specific tag key by Name
+
+        :param name: Name of the tag key you want (required)
+        :type  name: ``str``
+
+        :rtype: :class:`DimensionDataTagKey`
+        """
+        tag_keys = self.ex_list_tag_keys(name=name)
+        if len(tag_keys) != 1:
+            raise ValueError("No tags found with name %s" % name)
+        return tag_keys[0]
+
+    def ex_modify_tag_key(self, tag_key, name=None, description=None,
+                          value_required=None, display_on_report=None):
+
+        """
+        Modify a specific tag key
+
+        :param tag_key: The tag key you want to modify (required)
+        :type  tag_key: :class:`DimensionDataTagKey` or ``str``
+
+        :param name: Set to modifiy the name of the tag key
+        :type  name: ``str``
+
+        :param description: Set to modify the description of the tag key
+        :type  description: ``str``
+
+        :param value_required: Set to modify if a value is required for
+                               the tag key
+        :type  value_required: ``bool``
+
+        :param display_on_report: Set to modify if this tag key should display
+                                  on the usage reports
+        :type  display_on_report: ``bool``
+
+        :rtype: ``bool``
+        """
+        tag_key_id = self._tag_key_to_tag_key_id(tag_key)
+        modify_tag_key = ET.Element('editTagKey',
+                                    {'xmlns': TYPES_URN, 'id': tag_key_id})
+        if name is not None:
+            ET.SubElement(modify_tag_key, 'name').text = name
+        if description is not None:
+            ET.SubElement(modify_tag_key, 'description').text = description
+        if value_required is not None:
+            ET.SubElement(modify_tag_key, 'valueRequired').text = \
+                str(value_required).lower()
+        if display_on_report is not None:
+            ET.SubElement(modify_tag_key, 'displayOnReport').text = \
+                str(display_on_report).lower()
+
+        response = self.connection.request_with_orgId_api_2(
+            'tag/editTagKey',
+            method='POST',
+            data=ET.tostring(modify_tag_key)).object
+        response_code = findtext(response, 'responseCode', TYPES_URN)
+        return response_code in ['IN_PROGRESS', 'OK']
+
+    def ex_remove_tag_key(self, tag_key):
+        """
+        Modify a specific tag key
+
+        :param tag_key: The tag key you want to remove (required)
+        :type  tag_key: :class:`DimensionDataTagKey` or ``str``
+
+        :rtype: ``bool``
+        """
+        tag_key_id = self._tag_key_to_tag_key_id(tag_key)
+        remove_tag_key = ET.Element('deleteTagKey',
+                                    {'xmlns': TYPES_URN, 'id': tag_key_id})
+        response = self.connection.request_with_orgId_api_2(
+            'tag/deleteTagKey',
+            method='POST',
+            data=ET.tostring(remove_tag_key)).object
+        response_code = findtext(response, 'responseCode', TYPES_URN)
+        return response_code in ['IN_PROGRESS', 'OK']
+
+    def ex_apply_tag_to_asset(self, asset, tag_key, value=None):
+        """
+        Apply a tag to a Dimension Data Asset
+
+        :param asset: The asset to apply a tag to. (required)
+        :type  asset: :class:`Node` or :class:`NodeImage` or
+                      :class:`DimensionDataNewtorkDomain` or
+                      :class:`DimensionDataVlan` or
+                      :class:`DimensionDataPublicIpBlock`
+
+        :param tag_key: The tag_key to apply to the asset. (required)
+        :type  tag_key: :class:`DimensionDataTagKey` or ``str``
+
+        :param value: The value to be assigned to the tag key
+                      This is only required if the :class:`DimensionDataTagKey`
+                      requires it
+        :type  value: ``str``
+
+        :rtype: ``bool``
+        """
+        asset_type = self._get_tagging_asset_type(asset)
+        tag_key_name = self._tag_key_to_tag_key_name(tag_key)
+
+        apply_tags = ET.Element('applyTags', {'xmlns': TYPES_URN})
+        ET.SubElement(apply_tags, 'assetType').text = asset_type
+        ET.SubElement(apply_tags, 'assetId').text = asset.id
+
+        tag_ele = ET.SubElement(apply_tags, 'tag')
+        ET.SubElement(tag_ele, 'tagKeyName').text = tag_key_name
+        if value is not None:
+            ET.SubElement(tag_ele, 'value').text = value
+
+        response = self.connection.request_with_orgId_api_2(
+            'tag/applyTags',
+            method='POST',
+            data=ET.tostring(apply_tags)).object
+        response_code = findtext(response, 'responseCode', TYPES_URN)
+        return response_code in ['IN_PROGRESS', 'OK']
+
+    def ex_remove_tag_from_asset(self, asset, tag_key):
+        """
+        Remove a tag from an asset
+
+        :param asset: The asset to remove a tag from. (required)
+        :type  asset: :class:`Node` or :class:`NodeImage` or
+                      :class:`DimensionDataNewtorkDomain` or
+                      :class:`DimensionDataVlan` or
+                      :class:`DimensionDataPublicIpBlock`
+
+        :param tag_key: The tag key you want to remove (required)
+        :type  tag_key: :class:`DimensionDataTagKey` or ``str``
+
+        :rtype: ``bool``
+        """
+        asset_type = self._get_tagging_asset_type(asset)
+        tag_key_name = self._tag_key_to_tag_key_name(tag_key)
+
+        apply_tags = ET.Element('removeTags', {'xmlns': TYPES_URN})
+        ET.SubElement(apply_tags, 'assetType').text = asset_type
+        ET.SubElement(apply_tags, 'assetId').text = asset.id
+        ET.SubElement(apply_tags, 'tagKeyName').text = tag_key_name
+        response = self.connection.request_with_orgId_api_2(
+            'tag/removeTags',
+            method='POST',
+            data=ET.tostring(apply_tags)).object
+        response_code = findtext(response, 'responseCode', TYPES_URN)
+        return response_code in ['IN_PROGRESS', 'OK']
+
+    def ex_list_tags(self, asset_id=None, asset_type=None, location=None,
+                     tag_key_name=None, tag_key_id=None, value=None,
+                     value_required=None, display_on_report=None):
+        """
+        List tags in the Dimension Data Cloud
+
+        :param asset_id: Filter the list by asset id
+        :type  asset_id: ``str``
+
+        :param asset_type: Filter the list by asset type
+        :type  asset_type: ``str``
+
+        :param location: Filter the list by the assets location
+        :type  location: :class:``NodeLocation`` or ``str``
+
+        :param tag_key_name: Filter the list by a tag key name
+        :type  tag_key_name: ``str``
+
+        :param tag_key_id: Filter the list by a tag key id
+        :type  tag_key_id: ``str``
+
+        :param value: Filter the list by a tag value
+        :type  value: ``str``
+
+        :param value_required: Filter the list to if a value is required
+                               for a tag
+        :type  value_required: ``bool``
+
+        :param display_on_report: Filter the list to if the tag should
+                                  show up on usage reports
+        :type  display_on_report: ``bool``
+
+        :rtype: ``list`` of :class:`DimensionDataTag`
+        """
+        params = {}
+        if asset_id is not None:
+            params['assetId'] = asset_id
+        if asset_type is not None:
+            params['assetType'] = asset_type
+        if location is not None:
+            params['datacenterId'] = self._location_to_location_id(location)
+        if tag_key_name is not None:
+            params['tagKeyName'] = tag_key_name
+        if tag_key_id is not None:
+            params['tagKeyId'] = tag_key_id
+        if value is not None:
+            params['value'] = value
+        if value_required is not None:
+            params['valueRequired'] = str(value_required).lower()
+        if display_on_report is not None:
+            params['displayOnReport'] = str(display_on_report).lower()
+
+        paged_result = self.connection.paginated_request_with_orgId_api_2(
+            'tag/tag',
+            method='GET',
+            params=params
+        )
+
+        tags = []
+        for result in paged_result:
+            tags.extend(self._to_tags(result))
+        return tags
+
+    @staticmethod
+    def _get_tagging_asset_type(asset):
+        objecttype = type(asset)
+        if objecttype.__name__ in OBJECT_TO_TAGGING_ASSET_TYPE_MAP:
+            return OBJECT_TO_TAGGING_ASSET_TYPE_MAP[objecttype.__name__]
+        raise TypeError("Asset type %s cannot be tagged" % objecttype.__name__)
+
     def _list_nodes_single_page(self, params={}):
         return self.connection.request_with_orgId_api_2(
             'server/server', params=params).object
 
+    def _to_tags(self, object):
+        tags = []
+        for element in object.findall(fixxpath('tag', TYPES_URN)):
+            tags.append(self._to_tag(element))
+        return tags
+
+    def _to_tag(self, element):
+        tag_key = self._to_tag_key(element, from_tag_api=True)
+        return DimensionDataTag(
+            asset_type=findtext(element, 'assetType', TYPES_URN),
+            asset_id=findtext(element, 'assetId', TYPES_URN),
+            asset_name=findtext(element, 'assetId', TYPES_URN),
+            datacenter=findtext(element, 'datacenterId', TYPES_URN),
+            key=tag_key,
+            value=findtext(element, 'value', TYPES_URN)
+        )
+
+    def _to_tag_keys(self, object):
+        keys = []
+        for element in object.findall(fixxpath('tagKey', TYPES_URN)):
+            keys.append(self._to_tag_key(element))
+        return keys
+
+    def _to_tag_key(self, element, from_tag_api=False):
+        if from_tag_api:
+            id = findtext(element, 'tagKeyId', TYPES_URN)
+            name = findtext(element, 'tagKeyName', TYPES_URN)
+        else:
+            id = element.get('id')
+            name = findtext(element, 'name', TYPES_URN)
+
+        return DimensionDataTagKey(
+            id=id,
+            name=name,
+            description=findtext(element, 'description', TYPES_URN),
+            value_required=self._str2bool(
+                findtext(element, 'valueRequired', TYPES_URN)
+            ),
+            display_on_report=self._str2bool(
+                findtext(element, 'displayOnReport', TYPES_URN)
+            )
+        )
+
     def _to_images(self, object, el_name='osImage'):
         images = []
         locations = self.list_locations()
@@ -2382,3 +2743,15 @@ class DimensionDataNodeDriver(NodeDriver):
     @staticmethod
     def _network_domain_to_network_domain_id(network_domain):
         return dd_object_to_id(network_domain, DimensionDataNetworkDomain)
+
+    @staticmethod
+    def _tag_key_to_tag_key_id(tag_key):
+        return dd_object_to_id(tag_key, DimensionDataTagKey)
+
+    @staticmethod
+    def _tag_key_to_tag_key_name(tag_key):
+        return dd_object_to_id(tag_key, DimensionDataTagKey, id_value='name')
+
+    @staticmethod
+    def _str2bool(string):
+        return string.lower() in ("true")

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_applyTags.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/dimensiondata/tag_applyTags.xml 
b/libcloud/test/compute/fixtures/dimensiondata/tag_applyTags.xml
new file mode 100644
index 0000000..91114bb
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/tag_applyTags.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<response xmlns="urn:didata.com:api:cloud:types" 
requestId="na_20160421T194729995-0400_3409863f-ad9c-4381-b841-762e311aaebc">
+  <operation>APPLY_TAGS</operation>
+  <responseCode>OK</responseCode>
+  <message>Tag(s) successfully applied.</message>
+</response>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_applyTags_BADREQUEST.xml
----------------------------------------------------------------------
diff --git 
a/libcloud/test/compute/fixtures/dimensiondata/tag_applyTags_BADREQUEST.xml 
b/libcloud/test/compute/fixtures/dimensiondata/tag_applyTags_BADREQUEST.xml
new file mode 100644
index 0000000..dec66ad
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/tag_applyTags_BADREQUEST.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<response xmlns="urn:didata.com:api:cloud:types" 
requestId="na_20160421T194841441-0400_567f2bdf-88a5-4460-87be-1cc972eeb34c">
+  <operation>APPLY_TAGS</operation>
+  <responseCode>RESOURCE_NOT_FOUND</responseCode>
+  <message>Tag Key(s) (ChangeNameTes) not found.</message>
+</response>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_createTagKey.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/dimensiondata/tag_createTagKey.xml 
b/libcloud/test/compute/fixtures/dimensiondata/tag_createTagKey.xml
new file mode 100644
index 0000000..927812d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/tag_createTagKey.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<response xmlns="urn:didata.com:api:cloud:types" 
requestId="na_20160421T171444589-0400_7abb497e-403a-4b4a-a073-671212e77a14">
+  <operation>CREATE_TAG_KEY</operation>
+  <responseCode>OK</responseCode>
+  <message>Tag Key 'MyTestKey' has been created.</message>
+  <info name="tagKeyId" value="4f921962-402d-438d-aa37-6f6a0392a1a9"/>
+</response>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_createTagKey_BADREQUEST.xml
----------------------------------------------------------------------
diff --git 
a/libcloud/test/compute/fixtures/dimensiondata/tag_createTagKey_BADREQUEST.xml 
b/libcloud/test/compute/fixtures/dimensiondata/tag_createTagKey_BADREQUEST.xml
new file mode 100644
index 0000000..eaa73ab
--- /dev/null
+++ 
b/libcloud/test/compute/fixtures/dimensiondata/tag_createTagKey_BADREQUEST.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<response xmlns="urn:didata.com:api:cloud:types" 
requestId="na_20160421T171806338-0400_7606f82d-900e-41a7-864d-de4412c38eaf">
+  <operation>CREATE_TAG_KEY</operation>
+  <responseCode>NAME_NOT_UNIQUE</responseCode>
+  <message>Another Tag Key named 'MyTestKey' already exists.</message>
+</response>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_deleteTagKey.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/dimensiondata/tag_deleteTagKey.xml 
b/libcloud/test/compute/fixtures/dimensiondata/tag_deleteTagKey.xml
new file mode 100644
index 0000000..9fa8b59
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/tag_deleteTagKey.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<response xmlns="urn:didata.com:api:cloud:types" 
requestId="na_20160421T193459513-0400_d0f68e46-acd1-4af0-a41c-21b6e90695cb">
+  <operation>DELETE_TAG_KEY</operation>
+  <responseCode>OK</responseCode>
+  <message>Tag Key (Id:4f921962-402d-438d-aa37-6f6a0392a1a9) has been 
deleted.</message>
+</response>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_deleteTagKey_BADREQUEST.xml
----------------------------------------------------------------------
diff --git 
a/libcloud/test/compute/fixtures/dimensiondata/tag_deleteTagKey_BADREQUEST.xml 
b/libcloud/test/compute/fixtures/dimensiondata/tag_deleteTagKey_BADREQUEST.xml
new file mode 100644
index 0000000..357f2d8
--- /dev/null
+++ 
b/libcloud/test/compute/fixtures/dimensiondata/tag_deleteTagKey_BADREQUEST.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<response xmlns="urn:didata.com:api:cloud:types" 
requestId="na_20160421T193649125-0400_88027795-0516-4f8d-be8d-15e7505adcef">
+  <operation>DELETE_TAG_KEY</operation>
+  <responseCode>RESOURCE_NOT_FOUND</responseCode>
+  <message>Tag Key fdafdsa not found.</message>
+</response>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_editTagKey.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/dimensiondata/tag_editTagKey.xml 
b/libcloud/test/compute/fixtures/dimensiondata/tag_editTagKey.xml
new file mode 100644
index 0000000..f5e4d77
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/tag_editTagKey.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<response xmlns="urn:didata.com:api:cloud:types" 
requestId="na_20160421T192343920-0400_d0381407-8141-4ddb-a08e-dae4ddde5cda">
+  <operation>EDIT_TAG_KEY</operation>
+  <responseCode>OK</responseCode>
+  <message>Tag Key 'ChangeNameTest' has been edited.</message>
+</response>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_editTagKey_BADREQUEST.xml
----------------------------------------------------------------------
diff --git 
a/libcloud/test/compute/fixtures/dimensiondata/tag_editTagKey_BADREQUEST.xml 
b/libcloud/test/compute/fixtures/dimensiondata/tag_editTagKey_BADREQUEST.xml
new file mode 100644
index 0000000..cb0c706
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/tag_editTagKey_BADREQUEST.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<response xmlns="urn:didata.com:api:cloud:types" 
requestId="na_20160421T192212756-0400_1439e70b-a681-4d5b-939d-dc4264f0f66d">
+  <operation>EDIT_TAG_KEY</operation>
+  <responseCode>NO_CHANGE</responseCode>
+  <message>At least one of name, description, valueRequired or displayOnReport 
must be changed from its current value.</message>
+</response>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_removeTag.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/dimensiondata/tag_removeTag.xml 
b/libcloud/test/compute/fixtures/dimensiondata/tag_removeTag.xml
new file mode 100644
index 0000000..1d3c9a3
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/tag_removeTag.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<response xmlns="urn:didata.com:api:cloud:types" 
requestId="na_20160422T150018531-0400_f9fe6110-6156-4f8b-8c6e-97927e87f744">
+  <operation>REMOVE_TAGS</operation>
+  <responseCode>OK</responseCode>
+  <message>Tag(s) successfully removed.</message>
+</response>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_removeTag_BADREQUEST.xml
----------------------------------------------------------------------
diff --git 
a/libcloud/test/compute/fixtures/dimensiondata/tag_removeTag_BADREQUEST.xml 
b/libcloud/test/compute/fixtures/dimensiondata/tag_removeTag_BADREQUEST.xml
new file mode 100644
index 0000000..c686a20
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/tag_removeTag_BADREQUEST.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<response xmlns="urn:didata.com:api:cloud:types" 
requestId="na_20160422T181236250-0400_0aca7bf9-2a5c-4650-b648-d9356cb3309b">
+  <operation>REMOVE_TAGS</operation>
+  <responseCode>RESOURCE_NOT_FOUND</responseCode>
+  <message>Tag Key(s) (AaronTestModified) not applied to Server 
eb222a4a-fffd-4e4a-8346-1279ef621ab0.</message>
+</response>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_5ab77f5f_5aa9_426f_8459_4eab34e03d54.xml
----------------------------------------------------------------------
diff --git 
a/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_5ab77f5f_5aa9_426f_8459_4eab34e03d54.xml
 
b/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_5ab77f5f_5aa9_426f_8459_4eab34e03d54.xml
new file mode 100644
index 0000000..6d1aa13
--- /dev/null
+++ 
b/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_5ab77f5f_5aa9_426f_8459_4eab34e03d54.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tagKey xmlns="urn:didata.com:api:cloud:types" 
id="5ab77f5f-5aa9-426f-8459-4eab34e03d54">
+  <name>LibcloudTest</name>
+  <valueRequired>true</valueRequired>
+  <displayOnReport>true</displayOnReport>
+</tagKey>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_5ab77f5f_5aa9_426f_8459_4eab34e03d54_BADREQUEST.xml
----------------------------------------------------------------------
diff --git 
a/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_5ab77f5f_5aa9_426f_8459_4eab34e03d54_BADREQUEST.xml
 
b/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_5ab77f5f_5aa9_426f_8459_4eab34e03d54_BADREQUEST.xml
new file mode 100644
index 0000000..d585d5a
--- /dev/null
+++ 
b/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_5ab77f5f_5aa9_426f_8459_4eab34e03d54_BADREQUEST.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<response xmlns="urn:didata.com:api:cloud:types" 
requestId="na_20160421T190934127-0400_6504c4a8-afa1-476f-b9d4-a01d8e52d599">
+  <operation>GET_TAG_KEY</operation>
+  <responseCode>RESOURCE_NOT_FOUND</responseCode>
+  <message>Tag Key 5ab77f5f-5aa9-426f-8459-4eab34e03d5 not found.</message>
+</response>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_list.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_list.xml 
b/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_list.xml
new file mode 100644
index 0000000..a75b8c5
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_list.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tagKeys xmlns="urn:didata.com:api:cloud:types" pageNumber="1" pageCount="3" 
totalCount="3" pageSize="250">
+  <tagKey id="d047c609-93d7-4bc5-8fc9-732c85840075">
+    <name>AaronTestModified</name>
+    <description>Testing for VMWare</description>
+    <valueRequired>true</valueRequired>
+    <displayOnReport>true</displayOnReport>
+  </tagKey>
+  <tagKey id="5ab77f5f-5aa9-426f-8459-4eab34e03d54">
+    <name>LibcloudTest</name>
+    <valueRequired>true</valueRequired>
+    <displayOnReport>true</displayOnReport>
+  </tagKey>
+  <tagKey id="4f921962-402d-438d-aa37-6f6a0392a1a9">
+    <name>MyTestKey</name>
+    <valueRequired>true</valueRequired>
+    <displayOnReport>true</displayOnReport>
+  </tagKey>
+</tagKeys>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_list_SINGLE.xml
----------------------------------------------------------------------
diff --git 
a/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_list_SINGLE.xml 
b/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_list_SINGLE.xml
new file mode 100644
index 0000000..8deaa96
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_list_SINGLE.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tagKeys xmlns="urn:didata.com:api:cloud:types" pageNumber="1" pageCount="3" 
totalCount="3" pageSize="250">
+  <tagKey id="5ab77f5f-5aa9-426f-8459-4eab34e03d54">
+    <name>LibcloudTest</name>
+    <valueRequired>true</valueRequired>
+    <displayOnReport>true</displayOnReport>
+  </tagKey>
+</tagKeys>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/fixtures/dimensiondata/tag_tag_list.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/dimensiondata/tag_tag_list.xml 
b/libcloud/test/compute/fixtures/dimensiondata/tag_tag_list.xml
new file mode 100644
index 0000000..d79b648
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/tag_tag_list.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tags xmlns="urn:didata.com:api:cloud:types" pageNumber="1" pageCount="3" 
totalCount="3" pageSize="1000">
+  <tag>
+    <assetType>SERVER</assetType>
+    <assetId>09242b55-3bc8-4cb7-b30c-4158267f58e6</assetId>
+    <assetName>App server</assetName>
+    <datacenterId>NA9</datacenterId>
+    <tagKeyId>5ab77f5f-5aa9-426f-8459-4eab34e03d54</tagKeyId>
+    <tagKeyName>ChangeNameTest</tagKeyName>
+    <value>No way!</value>
+    <displayOnReport>true</displayOnReport>
+    <valueRequired>true</valueRequired>
+  </tag>
+  <tag>
+    <assetType>NETWORK_DOMAIN</assetType>
+    <assetId>1a16bf5e-583b-42c9-af94-a92d9ee1607f</assetId>
+    <assetName>An Ho1a Demo</assetName>
+    <datacenterId>NA9</datacenterId>
+    <tagKeyId>d047c609-93d7-4bc5-8fc9-732c85840075</tagKeyId>
+    <tagKeyName>AaronTestModified</tagKeyName>
+    <value>Success</value>
+    <displayOnReport>true</displayOnReport>
+    <valueRequired>true</valueRequired>
+  </tag>
+  <tag>
+    <assetType>SERVER</assetType>
+    <assetId>77da591d-b58e-43ef-8bc2-ddde3f732893</assetId>
+    <assetName>Test 1</assetName>
+    <datacenterId>NA9</datacenterId>
+    <tagKeyId>d047c609-93d7-4bc5-8fc9-732c85840075</tagKeyId>
+    <tagKeyName>AaronTestModified</tagKeyName>
+    <value>Test VMware</value>
+    <displayOnReport>true</displayOnReport>
+    <valueRequired>true</valueRequired>
+  </tag>
+</tags>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b9a5586e/libcloud/test/compute/test_dimensiondata.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_dimensiondata.py 
b/libcloud/test/compute/test_dimensiondata.py
index 1e1fa2a..f4f03bc 100644
--- a/libcloud/test/compute/test_dimensiondata.py
+++ b/libcloud/test/compute/test_dimensiondata.py
@@ -25,6 +25,7 @@ from libcloud.utils.py3 import httplib
 from libcloud.common.types import InvalidCredsError
 from libcloud.common.dimensiondata import DimensionDataAPIException, 
NetworkDomainServicePlan
 from libcloud.common.dimensiondata import DimensionDataServerCpuSpecification, 
DimensionDataServerDisk, DimensionDataServerVMWareTools
+from libcloud.common.dimensiondata import DimensionDataTag, DimensionDataTagKey
 from libcloud.common.dimensiondata import TYPES_URN
 from libcloud.compute.drivers.dimensiondata import DimensionDataNodeDriver as 
DimensionData
 from libcloud.compute.base import Node, NodeAuthPassword, NodeLocation
@@ -964,6 +965,133 @@ class DimensionDataTests(unittest.TestCase, 
TestCaseMixin):
         with self.assertRaises(ValueError):
             self.driver.ex_list_anti_affinity_rules(network='fake_network', 
network_domain='fake_network_domain')
 
+    def test_ex_create_tag_key(self):
+        success = self.driver.ex_create_tag_key('MyTestKey')
+        self.assertTrue(success)
+
+    def test_ex_create_tag_key_ALLPARAMS(self):
+        self.driver.connection._get_orgId()
+        DimensionDataMockHttp.type = 'ALLPARAMS'
+        success = self.driver.ex_create_tag_key('MyTestKey', description="Test 
Key Desc.", value_required=False, display_on_report=False)
+        self.assertTrue(success)
+
+    def test_ex_create_tag_key_BADREQUEST(self):
+        self.driver.connection._get_orgId()
+        DimensionDataMockHttp.type = 'BADREQUEST'
+        with self.assertRaises(DimensionDataAPIException):
+            self.driver.ex_create_tag_key('MyTestKey')
+
+    def test_ex_list_tag_keys(self):
+        tag_keys = self.driver.ex_list_tag_keys()
+        self.assertTrue(isinstance(tag_keys, list))
+        self.assertTrue(isinstance(tag_keys[0], DimensionDataTagKey))
+        self.assertTrue(isinstance(tag_keys[0].id, str))
+
+    def test_ex_list_tag_keys_ALLFILTERS(self):
+        self.driver.connection._get_orgId()
+        DimensionDataMockHttp.type = 'ALLFILTERS'
+        self.driver.ex_list_tag_keys(id='fake_id', name='fake_name', 
value_required=False, display_on_report=False)
+
+    def test_ex_get_tag_by_id(self):
+        tag = 
self.driver.ex_get_tag_key_by_id('d047c609-93d7-4bc5-8fc9-732c85840075')
+        self.assertTrue(isinstance(tag, DimensionDataTagKey))
+
+    def test_ex_get_tag_by_id_NOEXIST(self):
+        self.driver.connection._get_orgId()
+        DimensionDataMockHttp.type = 'NOEXIST'
+        with self.assertRaises(DimensionDataAPIException):
+            
self.driver.ex_get_tag_key_by_id('d047c609-93d7-4bc5-8fc9-732c85840075')
+
+    def test_ex_get_tag_by_name(self):
+        self.driver.connection._get_orgId()
+        DimensionDataMockHttp.type = 'SINGLE'
+        tag = self.driver.ex_get_tag_key_by_name('LibcloudTest')
+        self.assertTrue(isinstance(tag, DimensionDataTagKey))
+
+    def test_ex_get_tag_by_name_NOEXIST(self):
+        with self.assertRaises(ValueError):
+            self.driver.ex_get_tag_key_by_name('LibcloudTest')
+
+    def test_ex_modify_tag_key_NAME(self):
+        tag_key = self.driver.ex_list_tag_keys()[0]
+        DimensionDataMockHttp.type = 'NAME'
+        success = self.driver.ex_modify_tag_key(tag_key, name='NewName')
+        self.assertTrue(success)
+
+    def test_ex_modify_tag_key_NOTNAME(self):
+        tag_key = self.driver.ex_list_tag_keys()[0]
+        DimensionDataMockHttp.type = 'NOTNAME'
+        success = self.driver.ex_modify_tag_key(tag_key, 
description='NewDesc', value_required=False, display_on_report=True)
+        self.assertTrue(success)
+
+    def test_ex_modify_tag_key_NOCHANGE(self):
+        tag_key = self.driver.ex_list_tag_keys()[0]
+        DimensionDataMockHttp.type = 'NOCHANGE'
+        with self.assertRaises(DimensionDataAPIException):
+            self.driver.ex_modify_tag_key(tag_key)
+
+    def test_ex_remove_tag_key(self):
+        tag_key = self.driver.ex_list_tag_keys()[0]
+        success = self.driver.ex_remove_tag_key(tag_key)
+        self.assertTrue(success)
+
+    def test_ex_remove_tag_key_NOEXIST(self):
+        tag_key = self.driver.ex_list_tag_keys()[0]
+        DimensionDataMockHttp.type = 'NOEXIST'
+        with self.assertRaises(DimensionDataAPIException):
+            self.driver.ex_remove_tag_key(tag_key)
+
+    def test_ex_apply_tag_to_asset(self):
+        node = self.driver.list_nodes()[0]
+        success = self.driver.ex_apply_tag_to_asset(node, 'TagKeyName', 
'FakeValue')
+        self.assertTrue(success)
+
+    def test_ex_apply_tag_to_asset_NOVALUE(self):
+        node = self.driver.list_nodes()[0]
+        DimensionDataMockHttp.type = 'NOVALUE'
+        success = self.driver.ex_apply_tag_to_asset(node, 'TagKeyName')
+        self.assertTrue(success)
+
+    def test_ex_apply_tag_to_asset_NOTAGKEY(self):
+        node = self.driver.list_nodes()[0]
+        DimensionDataMockHttp.type = 'NOTAGKEY'
+        with self.assertRaises(DimensionDataAPIException):
+            self.driver.ex_apply_tag_to_asset(node, 'TagKeyNam')
+
+    def test_ex_apply_tag_to_asset_BADASSETTYPE(self):
+        network = self.driver.list_networks()[0]
+        DimensionDataMockHttp.type = 'NOTAGKEY'
+        with self.assertRaises(TypeError):
+            self.driver.ex_apply_tag_to_asset(network, 'TagKeyNam')
+
+    def test_ex_remove_tag_from_asset(self):
+        node = self.driver.list_nodes()[0]
+        success = self.driver.ex_remove_tag_from_asset(node, 'TagKeyName')
+        self.assertTrue(success)
+
+    def test_ex_remove_tag_from_asset_NOTAG(self):
+        node = self.driver.list_nodes()[0]
+        DimensionDataMockHttp.type = 'NOTAG'
+        with self.assertRaises(DimensionDataAPIException):
+            self.driver.ex_remove_tag_from_asset(node, 'TagKeyNam')
+
+    def test_ex_list_tags(self):
+        tags = self.driver.ex_list_tags()
+        self.assertTrue(isinstance(tags, list))
+        self.assertTrue(isinstance(tags[0], DimensionDataTag))
+        self.assertTrue(len(tags) == 3)
+
+    def test_ex_list_tags_ALLPARAMS(self):
+        self.driver.connection._get_orgId()
+        DimensionDataMockHttp.type = 'ALLPARAMS'
+        tags = self.driver.ex_list_tags(asset_id='fake_asset_id', 
asset_type='fake_asset_type',
+                                        location='fake_location', 
tag_key_name='fake_tag_key_name',
+                                        tag_key_id='fake_tag_key_id', 
value='fake_value',
+                                        value_required=False, 
display_on_report=False)
+        self.assertTrue(isinstance(tags, list))
+        self.assertTrue(isinstance(tags[0], DimensionDataTag))
+        self.assertTrue(len(tags) == 3)
+
     def test_priv_location_to_location_id(self):
         location = self.driver.ex_get_location_by_id('NA9')
         self.assertEqual(
@@ -1757,6 +1885,266 @@ class DimensionDataMockHttp(MockHttp):
             'server_removeDisk.xml')
         return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
+    def _caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_createTagKey(self, 
method, url, body, headers):
+        request = ET.fromstring(body)
+        if request.tag != "{urn:didata.com:api:cloud:types}createTagKey":
+            raise InvalidRequestError(request.tag)
+        name = findtext(request, 'name', TYPES_URN)
+        description = findtext(request, 'description', TYPES_URN)
+        value_required = findtext(request, 'valueRequired', TYPES_URN)
+        display_on_report = findtext(request, 'displayOnReport', TYPES_URN)
+        if name is None:
+            raise ValueError("Name must have a value in the request")
+        if description is not None:
+            raise ValueError("Default description for a tag should be blank")
+        if value_required is None or value_required != 'true':
+            raise ValueError("Default valueRequired should be true")
+        if display_on_report is None or display_on_report != 'true':
+            raise ValueError("Default displayOnReport should be true")
+
+        body = self.fixtures.load(
+            'tag_createTagKey.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def 
_caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_createTagKey_ALLPARAMS(self, 
method, url, body, headers):
+        request = ET.fromstring(body)
+        if request.tag != "{urn:didata.com:api:cloud:types}createTagKey":
+            raise InvalidRequestError(request.tag)
+        name = findtext(request, 'name', TYPES_URN)
+        description = findtext(request, 'description', TYPES_URN)
+        value_required = findtext(request, 'valueRequired', TYPES_URN)
+        display_on_report = findtext(request, 'displayOnReport', TYPES_URN)
+        if name is None:
+            raise ValueError("Name must have a value in the request")
+        if description is None:
+            raise ValueError("Description should have a value")
+        if value_required is None or value_required != 'false':
+            raise ValueError("valueRequired should be false")
+        if display_on_report is None or display_on_report != 'false':
+            raise ValueError("displayOnReport should be false")
+
+        body = self.fixtures.load(
+            'tag_createTagKey.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def 
_caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_createTagKey_BADREQUEST(self,
 method, url, body, headers):
+        body = self.fixtures.load(
+            'tag_createTagKey_BADREQUEST.xml')
+        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_tagKey(self, 
method, url, body, headers):
+        body = self.fixtures.load(
+            'tag_tagKey_list.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_tagKey_SINGLE(self, 
method, url, body, headers):
+        body = self.fixtures.load(
+            'tag_tagKey_list_SINGLE.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def 
_caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_tagKey_ALLFILTERS(self, 
method, url, body, headers):
+        (_, params) = url.split('?')
+        parameters = params.split('&')
+        for parameter in parameters:
+            (key, value) = parameter.split('=')
+            if key == 'id':
+                assert value == 'fake_id'
+            elif key == 'name':
+                assert value == 'fake_name'
+            elif key == 'valueRequired':
+                assert value == 'false'
+            elif key == 'displayOnReport':
+                assert value == 'false'
+            elif key == 'pageSize':
+                assert value == '250'
+            else:
+                raise ValueError("Could not find in url parameters 
{0}:{1}".format(key, value))
+        body = self.fixtures.load(
+            'tag_tagKey_list.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def 
_caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_tagKey_d047c609_93d7_4bc5_8fc9_732c85840075(self,
 method, url, body, headers):
+        body = self.fixtures.load(
+            'tag_tagKey_5ab77f5f_5aa9_426f_8459_4eab34e03d54.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def 
_caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_tagKey_d047c609_93d7_4bc5_8fc9_732c85840075_NOEXIST(self,
 method, url, body, headers):
+        body = self.fixtures.load(
+            'tag_tagKey_5ab77f5f_5aa9_426f_8459_4eab34e03d54_BADREQUEST.xml'
+        )
+        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
+
+    def 
_caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_editTagKey_NAME(self, 
method, url, body, headers):
+        request = ET.fromstring(body)
+        if request.tag != "{urn:didata.com:api:cloud:types}editTagKey":
+            raise InvalidRequestError(request.tag)
+        name = findtext(request, 'name', TYPES_URN)
+        description = findtext(request, 'description', TYPES_URN)
+        value_required = findtext(request, 'valueRequired', TYPES_URN)
+        display_on_report = findtext(request, 'displayOnReport', TYPES_URN)
+        if name is None:
+            raise ValueError("Name must have a value in the request")
+        if description is not None:
+            raise ValueError("Description should be empty")
+        if value_required is not None:
+            raise ValueError("valueRequired should be empty")
+        if display_on_report is not None:
+            raise ValueError("displayOnReport should be empty")
+        body = self.fixtures.load(
+            'tag_editTagKey.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def 
_caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_editTagKey_NOTNAME(self, 
method, url, body, headers):
+        request = ET.fromstring(body)
+        if request.tag != "{urn:didata.com:api:cloud:types}editTagKey":
+            raise InvalidRequestError(request.tag)
+        name = findtext(request, 'name', TYPES_URN)
+        description = findtext(request, 'description', TYPES_URN)
+        value_required = findtext(request, 'valueRequired', TYPES_URN)
+        display_on_report = findtext(request, 'displayOnReport', TYPES_URN)
+        if name is not None:
+            raise ValueError("Name should be empty")
+        if description is None:
+            raise ValueError("Description should not be empty")
+        if value_required is None:
+            raise ValueError("valueRequired should not be empty")
+        if display_on_report is None:
+            raise ValueError("displayOnReport should not be empty")
+        body = self.fixtures.load(
+            'tag_editTagKey.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def 
_caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_editTagKey_NOCHANGE(self, 
method, url, body, headers):
+        body = self.fixtures.load(
+            'tag_editTagKey_BADREQUEST.xml'
+        )
+        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_deleteTagKey(self, 
method, url, body, headers):
+        request = ET.fromstring(body)
+        if request.tag != "{urn:didata.com:api:cloud:types}deleteTagKey":
+            raise InvalidRequestError(request.tag)
+        body = self.fixtures.load(
+            'tag_deleteTagKey.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def 
_caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_deleteTagKey_NOEXIST(self, 
method, url, body, headers):
+        body = self.fixtures.load(
+            'tag_deleteTagKey_BADREQUEST.xml'
+        )
+        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_applyTags(self, 
method, url, body, headers):
+        request = ET.fromstring(body)
+        if request.tag != "{urn:didata.com:api:cloud:types}applyTags":
+            raise InvalidRequestError(request.tag)
+        asset_type = findtext(request, 'assetType', TYPES_URN)
+        asset_id = findtext(request, 'assetId', TYPES_URN)
+        tag = request.find(fixxpath('tag', TYPES_URN))
+        tag_key_name = findtext(tag, 'tagKeyName', TYPES_URN)
+        value = findtext(tag, 'value', TYPES_URN)
+        if asset_type is None:
+            raise ValueError("assetType should not be empty")
+        if asset_id is None:
+            raise ValueError("assetId should not be empty")
+        if tag_key_name is None:
+            raise ValueError("tagKeyName should not be empty")
+        if value is None:
+            raise ValueError("value should not be empty")
+
+        body = self.fixtures.load(
+            'tag_applyTags.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def 
_caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_applyTags_NOVALUE(self, 
method, url, body, headers):
+        request = ET.fromstring(body)
+        if request.tag != "{urn:didata.com:api:cloud:types}applyTags":
+            raise InvalidRequestError(request.tag)
+        asset_type = findtext(request, 'assetType', TYPES_URN)
+        asset_id = findtext(request, 'assetId', TYPES_URN)
+        tag = request.find(fixxpath('tag', TYPES_URN))
+        tag_key_name = findtext(tag, 'tagKeyName', TYPES_URN)
+        value = findtext(tag, 'value', TYPES_URN)
+        if asset_type is None:
+            raise ValueError("assetType should not be empty")
+        if asset_id is None:
+            raise ValueError("assetId should not be empty")
+        if tag_key_name is None:
+            raise ValueError("tagKeyName should not be empty")
+        if value is not None:
+            raise ValueError("value should be empty")
+
+        body = self.fixtures.load(
+            'tag_applyTags.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def 
_caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_applyTags_NOTAGKEY(self, 
method, url, body, headers):
+        body = self.fixtures.load(
+            'tag_applyTags_BADREQUEST.xml'
+        )
+        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_removeTags(self, 
method, url, body, headers):
+        request = ET.fromstring(body)
+        if request.tag != "{urn:didata.com:api:cloud:types}removeTags":
+            raise InvalidRequestError(request.tag)
+        body = self.fixtures.load(
+            'tag_removeTag.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def 
_caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_removeTags_NOTAG(self, 
method, url, body, headers):
+        body = self.fixtures.load(
+            'tag_removeTag_BADREQUEST.xml'
+        )
+        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_tag(self, method, 
url, body, headers):
+        body = self.fixtures.load(
+            'tag_tag_list.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_2_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_tag_tag_ALLPARAMS(self, 
method, url, body, headers):
+        (_, params) = url.split('?')
+        parameters = params.split('&')
+        for parameter in parameters:
+            (key, value) = parameter.split('=')
+            if key == 'assetId':
+                assert value == 'fake_asset_id'
+            elif key == 'assetType':
+                assert value == 'fake_asset_type'
+            elif key == 'valueRequired':
+                assert value == 'false'
+            elif key == 'displayOnReport':
+                assert value == 'false'
+            elif key == 'pageSize':
+                assert value == '250'
+            elif key == 'datacenterId':
+                assert value == 'fake_location'
+            elif key == 'value':
+                assert value == 'fake_value'
+            elif key == 'tagKeyName':
+                assert value == 'fake_tag_key_name'
+            elif key == 'tagKeyId':
+                assert value == 'fake_tag_key_id'
+            else:
+                raise ValueError("Could not find in url parameters 
{0}:{1}".format(key, value))
+        body = self.fixtures.load(
+            'tag_tag_list.xml'
+        )
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
 if __name__ == '__main__':
     sys.exit(unittest.main())

Reply via email to