Browse Source

Merge pull request #145 from CloudVE/display_id_and_label

Name and label support
Nuwan Goonasekera 7 năm trước cách đây
mục cha
commit
d2cd53dca0
44 tập tin đã thay đổi với 1882 bổ sung1393 xóa
  1. 3 0
      .travis.yml
  2. 13 3
      cloudbridge/cloud/base/helpers.py
  3. 72 240
      cloudbridge/cloud/base/resources.py
  4. 1 1
      cloudbridge/cloud/base/services.py
  5. 15 1
      cloudbridge/cloud/interfaces/exceptions.py
  6. 2 2
      cloudbridge/cloud/interfaces/provider.py
  7. 150 85
      cloudbridge/cloud/interfaces/resources.py
  8. 54 53
      cloudbridge/cloud/interfaces/services.py
  9. 16 9
      cloudbridge/cloud/providers/aws/helpers.py
  10. 146 73
      cloudbridge/cloud/providers/aws/resources.py
  11. 126 92
      cloudbridge/cloud/providers/aws/services.py
  12. 34 5
      cloudbridge/cloud/providers/azure/azure_client.py
  13. 17 17
      cloudbridge/cloud/providers/azure/helpers.py
  14. 1 1
      cloudbridge/cloud/providers/azure/provider.py
  15. 182 143
      cloudbridge/cloud/providers/azure/resources.py
  16. 145 133
      cloudbridge/cloud/providers/azure/services.py
  17. 124 69
      cloudbridge/cloud/providers/openstack/resources.py
  18. 84 86
      cloudbridge/cloud/providers/openstack/services.py
  19. 2 2
      docs/api_docs/cloud/exceptions.rst
  20. 34 0
      docs/concepts.rst
  21. 20 11
      docs/getting_started.rst
  22. BIN
      docs/topics/captures/az-label-dash.png
  23. BIN
      docs/topics/captures/az-net-id.png
  24. BIN
      docs/topics/captures/az-net-label.png
  25. 1 1
      docs/topics/contributor_guide.rst
  26. 121 0
      docs/topics/dashboard.rst
  27. 0 37
      docs/topics/design-decisions.rst
  28. 118 0
      docs/topics/design_decisions.rst
  29. 1 1
      docs/topics/networking.rst
  30. 19 17
      docs/topics/setup.rst
  31. 4 2
      docs/topics/testing.rst
  32. 1 0
      setup.cfg
  33. 1 1
      setup.py
  34. 23 30
      test/helpers/__init__.py
  35. 106 35
      test/helpers/standard_interface_tests.py
  36. 35 42
      test/test_block_store_service.py
  37. 55 56
      test/test_compute_service.py
  38. 44 11
      test/test_image_service.py
  39. 40 41
      test/test_network_service.py
  40. 2 3
      test/test_object_life_cycle.py
  41. 14 25
      test/test_object_store_service.py
  42. 52 62
      test/test_security_service.py
  43. 3 2
      test/test_vm_types_service.py
  44. 1 1
      tox.ini

+ 3 - 0
.travis.yml

@@ -30,6 +30,9 @@ matrix:
       env: TOX_ENV=pypy-azure
       env: TOX_ENV=pypy-azure
     - python: pypy-5.3.1
     - python: pypy-5.3.1
       env: TOX_ENV=pypy-openstack
       env: TOX_ENV=pypy-openstack
+env:
+  global:
+    - PYTHONUNBUFFERED=True
 before_install:
 before_install:
     - |
     - |
       case "$TRAVIS_EVENT_TYPE" in
       case "$TRAVIS_EVENT_TYPE" in

+ 13 - 3
cloudbridge/cloud/base/helpers.py

@@ -1,4 +1,6 @@
+import fnmatch
 import os
 import os
+import re
 import sys
 import sys
 import traceback
 import traceback
 from contextlib import contextmanager
 from contextlib import contextmanager
@@ -39,9 +41,17 @@ def filter_by(prop_name, kwargs, objs):
     """
     """
     prop_val = kwargs.pop(prop_name, None)
     prop_val = kwargs.pop(prop_name, None)
     if prop_val:
     if prop_val:
-        match = (o for o in objs if getattr(o, prop_name) == prop_val)
-        return match
-    return objs
+        if isinstance(prop_val, six.string_types):
+            regex = fnmatch.translate(prop_val)
+            results = [o for o in objs
+                       if getattr(o, prop_name)
+                       and re.search(regex, getattr(o, prop_name))]
+        else:
+            results = [o for o in objs
+                       if getattr(o, prop_name) == prop_val]
+        return results
+    else:
+        return objs
 
 
 
 
 def generic_find(filter_names, kwargs, objs):
 def generic_find(filter_names, kwargs, objs):

+ 72 - 240
cloudbridge/cloud/base/resources.py

@@ -4,15 +4,18 @@ Base implementation for data objects exposed through a provider or service
 import inspect
 import inspect
 import itertools
 import itertools
 import logging
 import logging
+import os
 import re
 import re
 import shutil
 import shutil
 import time
 import time
+import uuid
 
 
 import six
 import six
 
 
 import cloudbridge.cloud.base.helpers as cb_helpers
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.interfaces.exceptions \
 from cloudbridge.cloud.interfaces.exceptions \
     import InvalidConfigurationException
     import InvalidConfigurationException
+from cloudbridge.cloud.interfaces.exceptions import InvalidLabelException
 from cloudbridge.cloud.interfaces.exceptions import InvalidNameException
 from cloudbridge.cloud.interfaces.exceptions import InvalidNameException
 from cloudbridge.cloud.interfaces.exceptions import WaitStateException
 from cloudbridge.cloud.interfaces.exceptions import WaitStateException
 from cloudbridge.cloud.interfaces.resources import AttachmentInfo
 from cloudbridge.cloud.interfaces.resources import AttachmentInfo
@@ -58,154 +61,51 @@ class BaseCloudResource(CloudResource):
     """
     """
     Base implementation of a CloudBridge Resource.
     Base implementation of a CloudBridge Resource.
     """
     """
-
-    # Regular expression for valid cloudbridge resource names.
-    # They, must match the same criteria as GCE labels.
-    # as discussed here: https://github.com/CloudVE/cloudbridge/issues/55
-    #
-    # NOTE: The following regex is based on GCEs internal validation logic,
-    # and is significantly complex to allow for international characters.
-    CB_NAME_PATTERN = re.compile(six.u(
-        r"^[\u0061-\u007A\u00B5\u00DF-\u00F6\u00F8-\u00FF\u0101\u0103\u0105"
-        "\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B"
-        "\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131"
-        "\u0133\u0135\u0137\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146"
-        "\u0148\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B"
-        "\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171"
-        "\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C"
-        "\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA"
-        "\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9\u01BA\u01BD-\u01BF\u01C6\u01C9"
-        "\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC\u01DD\u01DF"
-        "\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF\u01F0\u01F3\u01F5"
-        "\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D"
-        "\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223"
-        "\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F"
-        "\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371"
-        "\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0\u03D1\u03D5-"
-        "\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB"
-        "\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB\u03FC\u0430-\u045F\u0461\u0463"
-        "\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479"
-        "\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497"
-        "\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD"
-        "\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4"
-        "\u04C6\u04C8\u04CA\u04CC\u04CE\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9"
-        "\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF"
-        "\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505"
-        "\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B"
-        "\u051D\u051F\u0521\u0523\u0525\u0527\u0561-\u0587\u1D00-\u1D2B"
-        "\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D"
-        "\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23"
-        "\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39"
-        "\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F"
-        "\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65"
-        "\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B"
-        "\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91"
-        "\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD"
-        "\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3"
-        "\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9"
-        "\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF"
-        "\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15"
-        "\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67"
-        "\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4"
-        "\u1FB6\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6\u1FC7\u1FD0-\u1FD3\u1FD6\u1FD7"
-        "\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6\u1FF7\u210A\u210E\u210F\u2113\u212F"
-        "\u2134\u2139\u213C\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5E\u2C61"
-        "\u2C65\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73\u2C74\u2C76-\u2C7B\u2C81"
-        "\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97"
-        "\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD"
-        "\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3"
-        "\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9"
-        "\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25"
-        "\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651"
-        "\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667"
-        "\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F"
-        "\uA691\uA693\uA695\uA697\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-"
-        "\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745"
-        "\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B"
-        "\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-"
-        "\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791"
-        "\uA793\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7FA\uFB00-\uFB06\uFB13-\uFB17"
-        "\uFF41-\uFF5A\u00AA\u00BA\u01BB\u01C0-\u01C3\u0294\u05D0-\u05EA"
-        "\u05F0-\u05F2\u0620-\u063F\u0641-\u064A\u066E\u066F\u0671-\u06D3"
-        "\u06D5\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-"
-        "\u07A5\u07B1\u07CA-\u07EA\u0800-\u0815\u0840-\u0858\u08A0\u08A2-"
-        "\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0972-\u0977\u0979-"
-        "\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2"
-        "\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1"
-        "\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33"
-        "\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-"
-        "\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-"
-        "\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-"
-        "\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D"
-        "\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95"
-        "\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-"
-        "\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33"
-        "\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-"
-        "\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0"
-        "\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D"
-        "\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-"
-        "\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E45"
-        "\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-"
-        "\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2"
-        "\u0EB3\u0EBD\u0EC0-\u0EC4\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-"
-        "\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D"
-        "\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10D0-\u10FA"
-        "\u10FD-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-"
-        "\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0"
-        "\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A"
-        "\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A"
-        "\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751"
-        "\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17DC\u1820-\u1842\u1844-"
-        "\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D"
-        "\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54"
-        "\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5"
-        "\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C77\u1CE9-\u1CEC\u1CEE-\u1CF1"
-        "\u1CF5\u1CF6\u2135-\u2138\u2D30-\u2D67\u2D80-\u2D96\u2DA0-\u2DA6"
-        "\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE"
-        "\u2DD0-\u2DD6\u2DD8-\u2DDE\u3006\u303C\u3041-\u3096\u309F\u30A1-"
-        "\u30FA\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF"
-        "\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA014\uA016-\uA48C\uA4D0-\uA4F7"
-        "\uA500-\uA60B\uA610-\uA61F\uA62A\uA62B\uA66E\uA6A0-\uA6E5\uA7FB-"
-        "\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-"
-        "\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C"
-        "\uA984-\uA9B2\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA6F"
-        "\uAA71-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD"
-        "\uAAC0\uAAC2\uAADB\uAADC\uAAE0-\uAAEA\uAAF2\uAB01-\uAB06\uAB09-"
-        "\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-"
-        "\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB1D"
-        "\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43"
-        "\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-"
-        "\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF66-\uFF6F\uFF71-\uFF9D\uFFA0-"
-        "\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC\u0030-"
-        "\u0039\u00B2\u00B3\u00B9\u00BC-\u00BE\u0660-\u0669\u06F0-\u06F9"
-        "\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u09F4-\u09F9\u0A66-\u0A6F"
-        "\u0AE6-\u0AEF\u0B66-\u0B6F\u0B72-\u0B77\u0BE6-\u0BF2\u0C66-\u0C6F"
-        "\u0C78-\u0C7E\u0CE6-\u0CEF\u0D66-\u0D75\u0E50-\u0E59\u0ED0-\u0ED9"
-        "\u0F20-\u0F33\u1040-\u1049\u1090-\u1099\u1369-\u137C\u16EE-\u16F0"
-        "\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1946-\u194F\u19D0-\u19DA"
-        "\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49"
-        "\u1C50-\u1C59\u2070\u2074-\u2079\u2080-\u2089\u2150-\u2182\u2185-"
-        "\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2CFD\u3007\u3021-"
-        "\u3029\u3038-\u303A\u3192-\u3195\u3220-\u3229\u3248-\u324F\u3251-"
-        "\u325F\u3280-\u3289\u32B1-\u32BF\uA620-\uA629\uA6E6-\uA6EF\uA830-"
-        "\uA835\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uAA50-\uAA59\uABF0-"
-        "\uABF9\uFF10-\uFF19_-]{0,63}$"), re.UNICODE)
+    # Regular expression for valid cloudbridge resource names/labels.
+    # Can be alphanumeric string that does not start or end with a dash
+    # Must be at least 3 characters in length.
+    # Ref: https://stackoverflow.com/questions/2525327/regex-for-a-za-z0-9
+    # -with-dashes-allowed-in-between-but-not-at-the-start-or-e
+    CB_NAME_PATTERN = re.compile(r"^[a-z][-a-z0-9]{1,61}[a-z0-9]$")
 
 
     def __init__(self, provider):
     def __init__(self, provider):
         self.__provider = provider
         self.__provider = provider
 
 
     @staticmethod
     @staticmethod
     def is_valid_resource_name(name):
     def is_valid_resource_name(name):
-        return True if BaseCloudResource.CB_NAME_PATTERN.match(name) else False
+        if not name:
+            return False
+        else:
+            return (True if BaseCloudResource.CB_NAME_PATTERN.match(name)
+                    else False)
+
+    @staticmethod
+    def assert_valid_resource_label(name):
+        if not BaseCloudResource.is_valid_resource_name(name):
+            log.debug("InvalidLabelException raised on %s", name)
+            raise InvalidLabelException(
+                u"Invalid label: %s. Label must be at least 3 characters long"
+                " and at most 63 characters. It must consist of lowercase"
+                " letters, numbers, or dashes. The label must not start or"
+                " end with a dash." % name)
 
 
     @staticmethod
     @staticmethod
     def assert_valid_resource_name(name):
     def assert_valid_resource_name(name):
         if not BaseCloudResource.is_valid_resource_name(name):
         if not BaseCloudResource.is_valid_resource_name(name):
-            log.debug("InvalidNameException raised on %s", name)
+            log.debug("InvalidLabelException raised on %s", name)
             raise InvalidNameException(
             raise InvalidNameException(
-                u"Invalid name: %s. Name must be at most 63 characters "
-                "long and consist of lowercase letters, numbers, "
-                "underscores, dashes or international characters" % name)
+                u"Invalid name: %s. Name must be at least 3 characters long"
+                " and at most 63 characters. It must consist of lowercase"
+                " letters, numbers, or dashes. The name must not start or"
+                " end with a dash." % name)
+
+    @staticmethod
+    def _generate_name_from_label(label, default):
+        if not label:
+            label = default
+        name = label[:55] + '-' + uuid.uuid4().hex[:6]
+        BaseCloudResource.assert_valid_resource_name(name)
+        return name
 
 
     @property
     @property
     def _provider(self):
     def _provider(self):
@@ -217,6 +117,15 @@ class BaseCloudResource(CloudResource):
         js = {k: v for(k, v) in attr if not k.startswith('_')}
         js = {k: v for(k, v) in attr if not k.startswith('_')}
         return js
         return js
 
 
+    def __repr__(self):
+        name_or_label = getattr(self, 'label', self.name)
+        if name_or_label == self.id:
+            return "<CB-{0}: {1}>".format(
+                self.__class__.__name__, self.id)
+        else:
+            return "<CB-{0}: {1} ({2})>".format(
+                self.__class__.__name__, name_or_label, self.id)
+
 
 
 class BaseObjectLifeCycleMixin(ObjectLifeCycleMixin):
 class BaseObjectLifeCycleMixin(ObjectLifeCycleMixin):
     """
     """
@@ -228,6 +137,7 @@ class BaseObjectLifeCycleMixin(ObjectLifeCycleMixin):
     """
     """
 
 
     def wait_for(self, target_states, terminal_states=None, timeout=None,
     def wait_for(self, target_states, terminal_states=None, timeout=None,
+
                  interval=None):
                  interval=None):
         if timeout is None:
         if timeout is None:
             timeout = self._provider.config.default_wait_timeout
             timeout = self._provider.config.default_wait_timeout
@@ -383,10 +293,6 @@ class BaseVMType(BaseCloudResource, VMType):
     def size_total_disk(self):
     def size_total_disk(self):
         return self.size_root_disk + self.size_ephemeral_disks
         return self.size_root_disk + self.size_ephemeral_disks
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
-                                            self.name, self.id)
-
 
 
 class BaseInstance(BaseCloudResource, BaseObjectLifeCycleMixin, Instance):
 class BaseInstance(BaseCloudResource, BaseObjectLifeCycleMixin, Instance):
 
 
@@ -400,7 +306,7 @@ class BaseInstance(BaseCloudResource, BaseObjectLifeCycleMixin, Instance):
                 self.id == other.id and
                 self.id == other.id and
                 # check from most to least likely mutables
                 # check from most to least likely mutables
                 self.state == other.state and
                 self.state == other.state and
-                self.name == other.name and
+                self.label == other.label and
                 self.vm_firewalls == other.vm_firewalls and
                 self.vm_firewalls == other.vm_firewalls and
                 self.public_ips == other.public_ips and
                 self.public_ips == other.public_ips and
                 self.private_ips == other.private_ips and
                 self.private_ips == other.private_ips and
@@ -413,10 +319,6 @@ class BaseInstance(BaseCloudResource, BaseObjectLifeCycleMixin, Instance):
             timeout=timeout,
             timeout=timeout,
             interval=interval)
             interval=interval)
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
-                                            self.name, self.id)
-
 
 
 class BaseLaunchConfig(LaunchConfig):
 class BaseLaunchConfig(LaunchConfig):
 
 
@@ -504,7 +406,7 @@ class BaseMachineImage(
                 self.id == other.id and
                 self.id == other.id and
                 # check from most to least likely mutables
                 # check from most to least likely mutables
                 self.state == other.state and
                 self.state == other.state and
-                self.name == other.name and
+                self.label == other.label and
                 self.description == other.description)
                 self.description == other.description)
 
 
     def wait_till_ready(self, timeout=None, interval=None):
     def wait_till_ready(self, timeout=None, interval=None):
@@ -514,10 +416,6 @@ class BaseMachineImage(
             timeout=timeout,
             timeout=timeout,
             interval=interval)
             interval=interval)
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
-                                            self.name, self.id)
-
 
 
 class BaseAttachmentInfo(AttachmentInfo):
 class BaseAttachmentInfo(AttachmentInfo):
 
 
@@ -551,7 +449,7 @@ class BaseVolume(BaseCloudResource, BaseObjectLifeCycleMixin, Volume):
                 self.id == other.id and
                 self.id == other.id and
                 # check from most to least likely mutables
                 # check from most to least likely mutables
                 self.state == other.state and
                 self.state == other.state and
-                self.name == other.name)
+                self.label == other.label)
 
 
     def wait_till_ready(self, timeout=None, interval=None):
     def wait_till_ready(self, timeout=None, interval=None):
         self.wait_for(
         self.wait_for(
@@ -560,10 +458,6 @@ class BaseVolume(BaseCloudResource, BaseObjectLifeCycleMixin, Volume):
             timeout=timeout,
             timeout=timeout,
             interval=interval)
             interval=interval)
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
-                                            self.name, self.id)
-
 
 
 class BaseSnapshot(BaseCloudResource, BaseObjectLifeCycleMixin, Snapshot):
 class BaseSnapshot(BaseCloudResource, BaseObjectLifeCycleMixin, Snapshot):
 
 
@@ -577,7 +471,7 @@ class BaseSnapshot(BaseCloudResource, BaseObjectLifeCycleMixin, Snapshot):
                 self.id == other.id and
                 self.id == other.id and
                 # check from most to least likely mutables
                 # check from most to least likely mutables
                 self.state == other.state and
                 self.state == other.state and
-                self.name == other.name)
+                self.label == other.label)
 
 
     def wait_till_ready(self, timeout=None, interval=None):
     def wait_till_ready(self, timeout=None, interval=None):
         self.wait_for(
         self.wait_for(
@@ -586,10 +480,6 @@ class BaseSnapshot(BaseCloudResource, BaseObjectLifeCycleMixin, Snapshot):
             timeout=timeout,
             timeout=timeout,
             interval=interval)
             interval=interval)
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
-                                            self.name, self.id)
-
 
 
 class BaseKeyPair(BaseCloudResource, KeyPair):
 class BaseKeyPair(BaseCloudResource, KeyPair):
 
 
@@ -616,7 +506,7 @@ class BaseKeyPair(BaseCloudResource, KeyPair):
         """
         """
         Return the name of this key pair.
         Return the name of this key pair.
         """
         """
-        return self._key_pair.name
+        return self.id
 
 
     @property
     @property
     def material(self):
     def material(self):
@@ -638,9 +528,6 @@ class BaseKeyPair(BaseCloudResource, KeyPair):
         #  multiple providers.
         #  multiple providers.
         self._key_pair.delete()
         self._key_pair.delete()
 
 
-    def __repr__(self):
-        return "<CBKeyPair: {0}>".format(self.name)
-
 
 
 class BaseVMFirewall(BaseCloudResource, VMFirewall):
 class BaseVMFirewall(BaseCloudResource, VMFirewall):
 
 
@@ -675,7 +562,7 @@ class BaseVMFirewall(BaseCloudResource, VMFirewall):
         """
         """
         Return the name of this VM firewall.
         Return the name of this VM firewall.
         """
         """
-        return self._vm_firewall.name
+        return self.id
 
 
     @property
     @property
     def description(self):
     def description(self):
@@ -690,10 +577,6 @@ class BaseVMFirewall(BaseCloudResource, VMFirewall):
         """
         """
         return self._vm_firewall.delete()
         return self._vm_firewall.delete()
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
-                                            self.id, self.name)
-
 
 
 class BaseVMFirewallRuleContainer(BasePageableObjectMixin,
 class BaseVMFirewallRuleContainer(BasePageableObjectMixin,
                                   VMFirewallRuleContainer):
                                   VMFirewallRuleContainer):
@@ -787,10 +670,6 @@ class BasePlacementZone(BaseCloudResource, PlacementZone):
     def __init__(self, provider):
     def __init__(self, provider):
         super(BasePlacementZone, self).__init__(provider)
         super(BasePlacementZone, self).__init__(provider)
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1}>".format(self.__class__.__name__,
-                                      self.id)
-
     def __eq__(self, other):
     def __eq__(self, other):
         return (isinstance(other, PlacementZone) and
         return (isinstance(other, PlacementZone) and
                 # pylint:disable=protected-access
                 # pylint:disable=protected-access
@@ -803,10 +682,6 @@ class BaseRegion(BaseCloudResource, Region):
     def __init__(self, provider):
     def __init__(self, provider):
         super(BaseRegion, self).__init__(provider)
         super(BaseRegion, self).__init__(provider)
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1}>".format(self.__class__.__name__,
-                                      self.id)
-
     def __eq__(self, other):
     def __eq__(self, other):
         return (isinstance(other, Region) and
         return (isinstance(other, Region) and
                 # pylint:disable=protected-access
                 # pylint:disable=protected-access
@@ -816,7 +691,7 @@ class BaseRegion(BaseCloudResource, Region):
     def to_json(self):
     def to_json(self):
         attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
         attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
         js = {k: v for(k, v) in attr if not k.startswith('_')}
         js = {k: v for(k, v) in attr if not k.startswith('_')}
-        js['zones'] = [z.name for z in self.zones]
+        js['zones'] = [z.id for z in self.zones]
         return js
         return js
 
 
 
 
@@ -835,13 +710,15 @@ class BaseBucketObject(BaseCloudResource, BucketObject):
 
 
     @staticmethod
     @staticmethod
     def is_valid_resource_name(name):
     def is_valid_resource_name(name):
-        return True if BaseBucketObject.CB_NAME_PATTERN.match(name) else False
+        return (True if BaseBucketObject.CB_NAME_PATTERN.match(name)
+                else False)
 
 
     @staticmethod
     @staticmethod
     def assert_valid_resource_name(name):
     def assert_valid_resource_name(name):
         if not BaseBucketObject.is_valid_resource_name(name):
         if not BaseBucketObject.is_valid_resource_name(name):
-            log.debug("InvalidNameException raised on %s", name, exc_info=True)
-            raise InvalidNameException(
+            log.debug("InvalidLabelException raised on %s", name,
+                      exc_info=True)
+            raise InvalidLabelException(
                 u"Invalid object name: %s. Name must match criteria defined "
                 u"Invalid object name: %s. Name must match criteria defined "
                 "in: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMeta"
                 "in: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMeta"
                 "data.html#object-key-guidelines" % name)
                 "data.html#object-key-guidelines" % name)
@@ -857,37 +734,12 @@ class BaseBucketObject(BaseCloudResource, BucketObject):
                 # check from most to least likely mutables
                 # check from most to least likely mutables
                 self.name == other.name)
                 self.name == other.name)
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1}>".format(self.__class__.__name__,
-                                      self.name)
-
 
 
 class BaseBucket(BaseCloudResource, Bucket):
 class BaseBucket(BaseCloudResource, Bucket):
 
 
-    # Regular expression for valid bucket names.
-    # They, must match the following criteria: http://docs.aws.amazon.com/aws
-    # cloudtrail/latest/userguide/cloudtrail-s3-bucket-naming-requirements.html
-    #
-    # NOTE: The following regex is based on: https://stackoverflow.com/questio
-    # ns/2063213/regular-expression-for-validating-dns-label-host-name
-    CB_NAME_PATTERN = re.compile(r"^(?![0-9]+$)(?!-)[a-z0-9-]{3,63}(?<!-)$")
-
     def __init__(self, provider):
     def __init__(self, provider):
         super(BaseBucket, self).__init__(provider)
         super(BaseBucket, self).__init__(provider)
 
 
-    @staticmethod
-    def is_valid_resource_name(name):
-        return True if BaseBucket.CB_NAME_PATTERN.match(name) else False
-
-    @staticmethod
-    def assert_valid_resource_name(name):
-        if not BaseBucket.is_valid_resource_name(name):
-            log.debug("Invalid resource name %s", name, exc_info=True)
-            raise InvalidNameException(
-                u"Invalid bucket name: %s. Name must match criteria defined "
-                "in: http://docs.aws.amazon.com/awscloudtrail/latest/userguide"
-                "/cloudtrail-s3-bucket-naming-requirements.html" % name)
-
     def __eq__(self, other):
     def __eq__(self, other):
         return (isinstance(other, Bucket) and
         return (isinstance(other, Bucket) and
                 # pylint:disable=protected-access
                 # pylint:disable=protected-access
@@ -896,10 +748,6 @@ class BaseBucket(BaseCloudResource, Bucket):
                 # check from most to least likely mutables
                 # check from most to least likely mutables
                 self.name == other.name)
                 self.name == other.name)
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1}>".format(self.__class__.__name__,
-                                      self.name)
-
 
 
 class BaseBucketContainer(BasePageableObjectMixin, BucketContainer):
 class BaseBucketContainer(BasePageableObjectMixin, BucketContainer):
 
 
@@ -921,16 +769,12 @@ class BaseGatewayContainer(GatewayContainer, BasePageableObjectMixin):
 
 
 class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
 class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
 
 
-    CB_DEFAULT_NETWORK_NAME = cb_helpers.get_env('CB_DEFAULT_NETWORK_NAME',
-                                                 'cloudbridge-net')
+    CB_DEFAULT_NETWORK_LABEL = os.environ.get('CB_DEFAULT_NETWORK_LABEL',
+                                              'cloudbridge-net')
 
 
     def __init__(self, provider):
     def __init__(self, provider):
         super(BaseNetwork, self).__init__(provider)
         super(BaseNetwork, self).__init__(provider)
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
-                                            self.id, self.name)
-
     def wait_till_ready(self, timeout=None, interval=None):
     def wait_till_ready(self, timeout=None, interval=None):
         self.wait_for(
         self.wait_for(
             [NetworkState.AVAILABLE],
             [NetworkState.AVAILABLE],
@@ -938,9 +782,9 @@ class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
             timeout=timeout,
             timeout=timeout,
             interval=interval)
             interval=interval)
 
 
-    def create_subnet(self, name, cidr_block, zone=None):
+    def create_subnet(self, label, cidr_block, zone=None):
         return self._provider.networking.subnets.create(
         return self._provider.networking.subnets.create(
-            name=name, network=self, cidr_block=cidr_block, zone=zone)
+            label=label, network=self, cidr_block=cidr_block, zone=zone)
 
 
     def __eq__(self, other):
     def __eq__(self, other):
         return (isinstance(other, Network) and
         return (isinstance(other, Network) and
@@ -951,22 +795,22 @@ class BaseNetwork(BaseCloudResource, BaseObjectLifeCycleMixin, Network):
 
 
 class BaseSubnet(BaseCloudResource, BaseObjectLifeCycleMixin, Subnet):
 class BaseSubnet(BaseCloudResource, BaseObjectLifeCycleMixin, Subnet):
 
 
-    CB_DEFAULT_SUBNET_NAME = cb_helpers.get_env('CB_DEFAULT_SUBNET_NAME',
-                                                'cloudbridge-subnet')
+    CB_DEFAULT_SUBNET_LABEL = os.environ.get('CB_DEFAULT_SUBNET_LABEL',
+                                             'cloudbridge-subnet')
 
 
     def __init__(self, provider):
     def __init__(self, provider):
         super(BaseSubnet, self).__init__(provider)
         super(BaseSubnet, self).__init__(provider)
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
-                                            self.id, self.name)
-
     def __eq__(self, other):
     def __eq__(self, other):
         return (isinstance(other, Subnet) and
         return (isinstance(other, Subnet) and
                 # pylint:disable=protected-access
                 # pylint:disable=protected-access
                 self._provider == other._provider and
                 self._provider == other._provider and
                 self.id == other.id)
                 self.id == other.id)
 
 
+    @property
+    def network(self):
+        return self._provider.networking.networks.get(self.network_id)
+
     def wait_till_ready(self, timeout=None, interval=None):
     def wait_till_ready(self, timeout=None, interval=None):
         self.wait_for(
         self.wait_for(
             [SubnetState.AVAILABLE],
             [SubnetState.AVAILABLE],
@@ -1004,7 +848,7 @@ class BaseFloatingIP(BaseCloudResource, BaseObjectLifeCycleMixin, FloatingIP):
 
 
     @property
     @property
     def name(self):
     def name(self):
-        # VM firewall rules don't support names, so pass
+        # VM firewall rules don't support labels
         return self.public_ip
         return self.public_ip
 
 
     @property
     @property
@@ -1019,10 +863,6 @@ class BaseFloatingIP(BaseCloudResource, BaseObjectLifeCycleMixin, FloatingIP):
             timeout=timeout,
             timeout=timeout,
             interval=interval)
             interval=interval)
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
-                                            self.id, self.public_ip)
-
     def __eq__(self, other):
     def __eq__(self, other):
         return (isinstance(other, FloatingIP) and
         return (isinstance(other, FloatingIP) and
                 # pylint:disable=protected-access
                 # pylint:disable=protected-access
@@ -1032,16 +872,12 @@ class BaseFloatingIP(BaseCloudResource, BaseObjectLifeCycleMixin, FloatingIP):
 
 
 class BaseRouter(BaseCloudResource, Router):
 class BaseRouter(BaseCloudResource, Router):
 
 
-    CB_DEFAULT_ROUTER_NAME = cb_helpers.get_env('CB_DEFAULT_ROUTER_NAME',
-                                                'cloudbridge-router')
+    CB_DEFAULT_ROUTER_LABEL = os.environ.get('CB_DEFAULT_ROUTER_LABEL',
+                                             'cloudbridge-router')
 
 
     def __init__(self, provider):
     def __init__(self, provider):
         super(BaseRouter, self).__init__(provider)
         super(BaseRouter, self).__init__(provider)
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__, self.id,
-                                            self.name)
-
     def __eq__(self, other):
     def __eq__(self, other):
         return (isinstance(other, Router) and
         return (isinstance(other, Router) and
                 # pylint:disable=protected-access
                 # pylint:disable=protected-access
@@ -1059,10 +895,6 @@ class BaseInternetGateway(BaseCloudResource, BaseObjectLifeCycleMixin,
         super(BaseInternetGateway, self).__init__(provider)
         super(BaseInternetGateway, self).__init__(provider)
         self.__provider = provider
         self.__provider = provider
 
 
-    def __repr__(self):
-        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__, self.id,
-                                            self.name)
-
     def __eq__(self, other):
     def __eq__(self, other):
         return (isinstance(other, InternetGateway) and
         return (isinstance(other, InternetGateway) and
                 # pylint:disable=protected-access
                 # pylint:disable=protected-access

+ 1 - 1
cloudbridge/cloud/base/services.py

@@ -186,7 +186,7 @@ class BaseSubnetService(
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
         obj_list = self
         obj_list = self
-        filters = ['name']
+        filters = ['label']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         return ClientPagedResultList(self._provider, list(matches))
         return ClientPagedResultList(self._provider, list(matches))
 
 

+ 15 - 1
cloudbridge/cloud/interfaces/exceptions.py

@@ -50,7 +50,7 @@ class ProviderConnectionException(CloudBridgeBaseException):
 class InvalidNameException(CloudBridgeBaseException):
 class InvalidNameException(CloudBridgeBaseException):
     """
     """
     Marker interface for any attempt to set an invalid name on
     Marker interface for any attempt to set an invalid name on
-    a CloudBridge resource.An example would be setting uppercase
+    a CloudBridge resource. An example would be setting uppercase
     letters, which are not allowed in a resource name.
     letters, which are not allowed in a resource name.
     """
     """
 
 
@@ -58,6 +58,20 @@ class InvalidNameException(CloudBridgeBaseException):
         super(InvalidNameException, self).__init__(msg)
         super(InvalidNameException, self).__init__(msg)
 
 
 
 
+class InvalidLabelException(InvalidNameException):
+    """
+    Marker interface for any attempt to set an invalid label on
+    a CloudBridge resource. An example would be setting uppercase
+    letters, which are not allowed in a resource label.
+    InvalidLabelExceptions inherit from, and are a special case
+    of InvalidNameExceptions. At present, these restrictions are
+    identical.
+    """
+
+    def __init__(self, msg):
+        super(InvalidLabelException, self).__init__(msg)
+
+
 class InvalidValueException(CloudBridgeBaseException):
 class InvalidValueException(CloudBridgeBaseException):
     """
     """
     Marker interface for any attempt to set an invalid value on a CloudBridge
     Marker interface for any attempt to set an invalid value on a CloudBridge

+ 2 - 2
cloudbridge/cloud/interfaces/provider.py

@@ -146,8 +146,8 @@ class CloudProvider(object):
         .. code-block:: python
         .. code-block:: python
 
 
             networks = provider.networking.networks.list()
             networks = provider.networking.networks.list()
-            network = provider.networking.networks.create(
-                           name="DevNet", cidr_block='10.0.0.0/16')
+            subnets = provider.networking.subnets.list()
+            routers = provider.networking.routers.list()
 
 
         :rtype: :class:`.NetworkingService`
         :rtype: :class:`.NetworkingService`
         :return:  a NetworkingService object
         :return:  a NetworkingService object

+ 150 - 85
cloudbridge/cloud/interfaces/resources.py

@@ -31,9 +31,12 @@ class CloudResource(object):
 
 
     This interface has a  _provider property that can be used to access the
     This interface has a  _provider property that can be used to access the
     provider associated with the resource, which is only intended for use by
     provider associated with the resource, which is only intended for use by
-    subclasses. Every cloudbridge resource also has an id and name property.
-    The id property is a unique identifier for the resource. The name property
-    is a display value.
+    subclasses. Every cloudbridge resource also has an id, a name and a
+    label property. The id property is a unique identifier for the resource.
+    The name is a more user-friendly version of an id, suitable for
+    display to an end-user. However, it cannot be used in place of id. See
+    @name documentation. The label property is a user-assignable
+    identifier for the resource.
     """
     """
     __metaclass__ = ABCMeta
     __metaclass__ = ABCMeta
 
 
@@ -66,29 +69,33 @@ class CloudResource(object):
     @abstractproperty
     @abstractproperty
     def name(self):
     def name(self):
         """
         """
-        Get the resource name.
-
-        The name property is typically a user-friendly display value for the
-        resource. Some resources may allow the resource name to be set.
-
-        The name property adheres to the following restrictions for most
-        cloudbridge resources:
-        * Names cannot be longer than 63 characters
-        * May only contain lowercase letters, numeric characters, underscores,
-          and dashes. International characters are allowed.
-
-        Some resources may relax/increase these restrictions (e.g. Buckets)
-        depending on their requirements. Consult the resource specific
-        documentation for exact restrictions.
-
-        Some resources may allow an existing resource name to be changed.
-        However, this may lead to cloud-dependent code because not all all
-        providers support this capability. See
-        http://cloudbridge.cloudve.org/en/latest/topics/design-decisions.html
-        for more details and potential implications.
+        Get the name id for the resource.
+
+        The name property is typically a user-friendly id value for the
+        resource. The name is different from the id property in the
+        following ways:
+        1. The name property is often a more user-friendly value to
+           display to the user than the id property.
+        2. The name may sometimes be the same as the id, but should never
+           be used in place of the id.
+        3. The id is what will uniquely identify a resource, and will be used
+           internally by cloudbridge for all get operations etc.
+        4. All resources have a name.
+        5. The name is read-only.
+        6. However, the name may not necessarily be unique, which is the
+           reason why it should not be used for uniquely identifying a
+           resource.
+        Example:
+        The AWS machine image name maps to a cloudbridge name. It is not
+        editable and is a user friendly name such as 'Ubuntu 14.04' and
+        corresponds to the ami-name. It is distinct from the ami-id, which
+        maps to cloudbridge's id property. The ami-name cannot be edited, and
+        is set at creation time. It is not necessarily unique.
+        In Azure, the machine image's name corresponds to cloudbridge's name
+        property. In Azure, it also happens to be the same as the id property.
 
 
-        :rtype: ``str``
-        :return: Name for this resource as returned by the cloud middleware.
+        The name property and the label property share the same character
+        restrictions. see :py:attr:`~LabeledCloudResource.label`
         """
         """
         pass
         pass
 
 
@@ -100,6 +107,35 @@ class CloudResource(object):
         pass
         pass
 
 
 
 
+class LabeledCloudResource(CloudResource):
+
+    @abstractproperty
+    def label(self):
+        """
+        Get the resource label.
+
+        The label property is a user-defined, editable identifier for a
+        resource. It will often correspond to a user editable resource label
+        in the underlying cloud provider, or be simulated through tags/labels.
+
+        The label property adheres to the following restrictions:
+        * Must be at least 3 characters in length.
+        * Cannot be longer than 63 characters.
+        * May only contain ASCII characters comprising of lowercase letters,
+          numeric characters, and dashes.
+        * Must begin with an alphanumeric character and end with one
+          (i.e. cannot begin or end with a dash)
+
+        Some resources may not support labels, in which case, a
+        NotImplementedError will be thrown.
+
+        :rtype: ``str``
+        :return: Label for this resource as returned by the cloud middleware.
+        :throws NotImplementedError if this resource does not support labels
+        """
+        pass
+
+
 class Configuration(dict):
 class Configuration(dict):
     """
     """
     Represents a cloudbridge configuration object
     Represents a cloudbridge configuration object
@@ -460,19 +496,15 @@ class InstanceState(object):
     ERROR = "error"
     ERROR = "error"
 
 
 
 
-class Instance(ObjectLifeCycleMixin, CloudResource):
+class Instance(ObjectLifeCycleMixin, LabeledCloudResource):
 
 
     __metaclass__ = ABCMeta
     __metaclass__ = ABCMeta
 
 
-    @CloudResource.name.setter
+    @LabeledCloudResource.label.setter
     @abstractmethod
     @abstractmethod
-    def name(self, value):
+    def label(self, value):
         """
         """
-        Set the instance name.
-
-        Note that the changing the name of an existing resource may result in
-        cloud-dependent code. See the following page for more details:
-        http://cloudbridge.cloudve.org/en/latest/topics/design-decisions.html
+        Set the instance label.
         """
         """
         pass
         pass
 
 
@@ -506,7 +538,7 @@ class Instance(ObjectLifeCycleMixin, CloudResource):
         object, you can use the ``instance.vm_type`` property instead.
         object, you can use the ``instance.vm_type`` property instead.
 
 
         :rtype: ``str``
         :rtype: ``str``
-        :return: VM type name for this instance (e.g., ``m1.large``)
+        :return: VM type id for this instance (e.g., ``m1.large``)
         """
         """
         pass
         pass
 
 
@@ -598,17 +630,17 @@ class Instance(ObjectLifeCycleMixin, CloudResource):
         pass
         pass
 
 
     @abstractproperty
     @abstractproperty
-    def key_pair_name(self):
+    def key_pair_id(self):
         """
         """
-        Get the name of the key pair associated with this instance.
+        Get the id of the key pair associated with this instance.
 
 
         :rtype: ``str``
         :rtype: ``str``
-        :return: Name of the ssh key pair associated with this instance.
+        :return: Id of the ssh key pair associated with this instance.
         """
         """
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def create_image(self, name):
+    def create_image(self, label):
         """
         """
         Create a new image based on this instance.
         Create a new image based on this instance.
 
 
@@ -698,8 +730,8 @@ class LaunchConfig(object):
         lc = provider.compute.instances.create_launch_config()
         lc = provider.compute.instances.create_launch_config()
         lc.add_block_device(...)
         lc.add_block_device(...)
 
 
-        inst = provider.compute.instances.create(name, image, vm_type,
-                                                 network, launch_config=lc)
+        inst = provider.compute.instances.create(
+            'MyVM', image, vm_type, subnet, launch_config=lc)
     """
     """
 
 
     @abstractmethod
     @abstractmethod
@@ -794,7 +826,7 @@ class LaunchConfig(object):
         pass
         pass
 
 
 
 
-class MachineImage(ObjectLifeCycleMixin, CloudResource):
+class MachineImage(ObjectLifeCycleMixin, LabeledCloudResource):
 
 
     __metaclass__ = ABCMeta
     __metaclass__ = ABCMeta
 
 
@@ -850,12 +882,20 @@ class NetworkState(object):
     ERROR = "error"
     ERROR = "error"
 
 
 
 
-class Network(ObjectLifeCycleMixin, CloudResource):
+class Network(ObjectLifeCycleMixin, LabeledCloudResource):
     """
     """
     Represents a software-defined network, like the Virtual Private Cloud.
     Represents a software-defined network, like the Virtual Private Cloud.
     """
     """
     __metaclass__ = ABCMeta
     __metaclass__ = ABCMeta
 
 
+    @LabeledCloudResource.label.setter
+    @abstractmethod
+    def label(self, value):
+        """
+        Set the resource label.
+        """
+        pass
+
     @abstractproperty
     @abstractproperty
     def external(self):
     def external(self):
         """
         """
@@ -910,13 +950,13 @@ class Network(ObjectLifeCycleMixin, CloudResource):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def create_subnet(self, name, cidr_block, zone=None):
+    def create_subnet(self, label, cidr_block, zone=None):
         """
         """
         Create a new network subnet and associate it with this Network.
         Create a new network subnet and associate it with this Network.
 
 
-        :type name: ``str``
-        :param name: The subnet name. The name will be set if the
-                     provider supports it.
+        :type label: ``str``
+        :param label: The subnet label. The subnet name will be derived from
+                      this label.
 
 
         :type cidr_block: ``str``
         :type cidr_block: ``str``
         :param cidr_block: CIDR block within this Network to assign to the
         :param cidr_block: CIDR block within this Network to assign to the
@@ -960,12 +1000,20 @@ class SubnetState(object):
     ERROR = "error"
     ERROR = "error"
 
 
 
 
-class Subnet(ObjectLifeCycleMixin, CloudResource):
+class Subnet(ObjectLifeCycleMixin, LabeledCloudResource):
     """
     """
     Represents a subnet, as part of a Network.
     Represents a subnet, as part of a Network.
     """
     """
     __metaclass__ = ABCMeta
     __metaclass__ = ABCMeta
 
 
+    @LabeledCloudResource.label.setter
+    @abstractmethod
+    def label(self, value):
+        """
+        Set the resource label.
+        """
+        pass
+
     @abstractproperty
     @abstractproperty
     def cidr_block(self):
     def cidr_block(self):
         """
         """
@@ -986,6 +1034,16 @@ class Subnet(ObjectLifeCycleMixin, CloudResource):
         """
         """
         pass
         pass
 
 
+    @abstractproperty
+    def network(self):
+        """
+        The parent network object associated with this this subnet.
+
+        :rtype: ``Network``
+        :return: `Network` object
+        """
+        pass
+
     @abstractproperty
     @abstractproperty
     def zone(self):
     def zone(self):
         """
         """
@@ -1043,7 +1101,7 @@ class FloatingIPContainer(PageableObjectMixin):
         """
         """
         Searches for a FloatingIP by a given list of attributes.
         Searches for a FloatingIP by a given list of attributes.
 
 
-        Supported attributes: name, public_ip
+        Supported attributes: label, public_ip
 
 
         Example:
         Example:
 
 
@@ -1156,12 +1214,25 @@ class RouterState(object):
     DETACHED = "detached"
     DETACHED = "detached"
 
 
 
 
-class Router(CloudResource):
+class Router(LabeledCloudResource):
     """
     """
     Represents a private network router.
     Represents a private network router.
+
+    This logical router is meant to roughly mimic the properties of a physical
+    router. Therefore, attaching a subnet can be thought of as plugging in a
+    network cable to enable routing to/from that subnet. Attaching a gateway
+    can be thought of as plugging in an upstream link.
     """
     """
     __metaclass__ = ABCMeta
     __metaclass__ = ABCMeta
 
 
+    @LabeledCloudResource.label.setter
+    @abstractmethod
+    def label(self, value):
+        """
+        Set the resource label.
+        """
+        pass
+
     @abstractproperty
     @abstractproperty
     def state(self):
     def state(self):
         """
         """
@@ -1273,8 +1344,7 @@ class GatewayContainer(PageableObjectMixin):
         provide internet routing to a network.
         provide internet routing to a network.
 
 
         :type  name: ``str``
         :type  name: ``str``
-        :param name: The gateway name. This applies only if creating a gateway
-                     and if the provider supports it.
+        :param name: The gateway label.
 
 
         :rtype: ``object``  of :class:`.InternetGateway` or ``None``
         :rtype: ``object``  of :class:`.InternetGateway` or ``None``
         :return: an InternetGateway object of ``None`` if not found.
         :return: an InternetGateway object of ``None`` if not found.
@@ -1402,22 +1472,18 @@ class VolumeState(object):
     ERROR = "error"
     ERROR = "error"
 
 
 
 
-class Volume(ObjectLifeCycleMixin, CloudResource):
+class Volume(ObjectLifeCycleMixin, LabeledCloudResource):
     """
     """
     Represents a block storage device (aka volume).
     Represents a block storage device (aka volume).
     """
     """
 
 
     __metaclass__ = ABCMeta
     __metaclass__ = ABCMeta
 
 
-    @CloudResource.name.setter
+    @LabeledCloudResource.label.setter
     @abstractmethod
     @abstractmethod
-    def name(self, value):
+    def label(self, value):
         """
         """
-        Set the volume name.
-
-        Note that the changing the name of an existing resource may result in
-        cloud-dependent code. See the following page for more details:
-        http://cloudbridge.cloudve.org/en/latest/topics/design-decisions.html
+        Set the volume label.
         """
         """
         pass
         pass
 
 
@@ -1427,7 +1493,7 @@ class Volume(ObjectLifeCycleMixin, CloudResource):
         Get the volume description.
         Get the volume description.
 
 
         Some cloud providers may not support this property, and will return the
         Some cloud providers may not support this property, and will return the
-        volume name instead.
+        volume label instead.
 
 
         :rtype: ``str``
         :rtype: ``str``
         :return: Description for this volume as returned by the cloud
         :return: Description for this volume as returned by the cloud
@@ -1443,7 +1509,7 @@ class Volume(ObjectLifeCycleMixin, CloudResource):
 
 
         Some cloud providers may not support this property, and setting the
         Some cloud providers may not support this property, and setting the
         description may have no effect (providers that do not support this
         description may have no effect (providers that do not support this
-        property will always return the volume name as the description).
+        property will always return the volume label as the description).
         """
         """
         pass
         pass
 
 
@@ -1542,12 +1608,12 @@ class Volume(ObjectLifeCycleMixin, CloudResource):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def create_snapshot(self, name, description=None):
+    def create_snapshot(self, label, description=None):
         """
         """
         Create a snapshot of this Volume.
         Create a snapshot of this Volume.
 
 
-        :type name: ``str``
-        :param name: The name of this snapshot.
+        :type label: ``str``
+        :param label: The label for this snapshot.
 
 
         :type description: ``str``
         :type description: ``str``
         :param description: A description of the snapshot.
         :param description: A description of the snapshot.
@@ -1587,22 +1653,18 @@ class SnapshotState(object):
     ERROR = "error"
     ERROR = "error"
 
 
 
 
-class Snapshot(ObjectLifeCycleMixin, CloudResource):
+class Snapshot(ObjectLifeCycleMixin, LabeledCloudResource):
     """
     """
     Represents a snapshot of a block storage device.
     Represents a snapshot of a block storage device.
     """
     """
 
 
     __metaclass__ = ABCMeta
     __metaclass__ = ABCMeta
 
 
-    @CloudResource.name.setter
+    @LabeledCloudResource.label.setter
     @abstractmethod
     @abstractmethod
-    def name(self, value):
+    def label(self, value):
         """
         """
-        Set the snapshot name.
-
-        Note that the changing the name of an existing resource may result in
-        cloud-dependent code. See the following page for more details:
-        http://cloudbridge.cloudve.org/en/latest/topics/design-decisions.html
+        Set the snapshot label.
         """
         """
         pass
         pass
 
 
@@ -1612,7 +1674,7 @@ class Snapshot(ObjectLifeCycleMixin, CloudResource):
         Get the snapshot description.
         Get the snapshot description.
 
 
         Some cloud providers may not support this property, and will return the
         Some cloud providers may not support this property, and will return the
-        snapshot name instead.
+        snapshot label instead.
 
 
         :rtype: ``str``
         :rtype: ``str``
         :return: Description for this snapshot as returned by the cloud
         :return: Description for this snapshot as returned by the cloud
@@ -1628,7 +1690,7 @@ class Snapshot(ObjectLifeCycleMixin, CloudResource):
 
 
         Some cloud providers may not support this property, and setting the
         Some cloud providers may not support this property, and setting the
         description may have no effect (providers that do not support this
         description may have no effect (providers that do not support this
-        property will always return the snapshot name as the description).
+        property will always return the snapshot label as the description).
 
 
         :type value: ``str``
         :type value: ``str``
         :param value: The value for the snapshot description.
         :param value: The value for the snapshot description.
@@ -1795,7 +1857,7 @@ class PlacementZone(CloudResource):
         A region this placement zone is associated with.
         A region this placement zone is associated with.
 
 
         :rtype: ``str``
         :rtype: ``str``
-        :return: The name of the region the zone is associated with.
+        :return: The id of the region the zone is associated with.
         """
         """
         pass
         pass
 
 
@@ -1892,7 +1954,7 @@ class VMType(CloudResource):
         pass
         pass
 
 
 
 
-class VMFirewall(CloudResource):
+class VMFirewall(LabeledCloudResource):
     """
     """
     Represents a firewall resource applied to virtual machines.
     Represents a firewall resource applied to virtual machines.
 
 
@@ -1901,6 +1963,14 @@ class VMFirewall(CloudResource):
 
 
     __metaclass__ = ABCMeta
     __metaclass__ = ABCMeta
 
 
+    @LabeledCloudResource.label.setter
+    @abstractmethod
+    def label(self, value):
+        """
+        Set the resource label.
+        """
+        pass
+
     @abstractproperty
     @abstractproperty
     def description(self):
     def description(self):
         """
         """
@@ -1954,7 +2024,7 @@ class VMFirewallRuleContainer(PageableObjectMixin):
 
 
             fw = provider.security.vm_firewalls.get('my_fw_id')
             fw = provider.security.vm_firewalls.get('my_fw_id')
             rule = fw.rules.get('rule_id')
             rule = fw.rules.get('rule_id')
-            print(rule.id, rule.name)
+            print(rule.id, rule.label)
 
 
         :rtype: :class:`.FirewallRule`
         :rtype: :class:`.FirewallRule`
         :return:  a FirewallRule instance
         :return:  a FirewallRule instance
@@ -2026,8 +2096,8 @@ class VMFirewallRuleContainer(PageableObjectMixin):
         """
         """
         Find a firewall rule filtered by the given parameters.
         Find a firewall rule filtered by the given parameters.
 
 
-        :type name: str
-        :param name: The name of the VM firewall to retrieve.
+        :type label: str
+        :param label: The label of the VM firewall to retrieve.
 
 
         :type protocol: ``str``
         :type protocol: ``str``
         :param protocol: Either ``tcp`` | ``udp`` | ``icmp``.
         :param protocol: Either ``tcp`` | ``udp`` | ``icmp``.
@@ -2289,11 +2359,6 @@ class Bucket(CloudResource):
         """
         """
         Retrieve the name of the current bucket.
         Retrieve the name of the current bucket.
 
 
-        The bucket name adheres to a naming requirement that is more
-        relaxed than the naming requirement enforced across CloudBridge. More
-        details are available here: http://docs.aws.amazon.com/awscloudtrail/
-        latest/userguide/cloudtrail-s3-bucket-naming-requirements.html
-
         :rtype: ``str``
         :rtype: ``str``
         :return: Name for this instance as returned by the cloud middleware.
         :return: Name for this instance as returned by the cloud middleware.
         """
         """
@@ -2384,7 +2449,7 @@ class BucketContainer(PageableObjectMixin):
     @abstractmethod
     @abstractmethod
     def find(self, **kwargs):
     def find(self, **kwargs):
         """
         """
-        Searche for an object by a given list of attributes.
+        Search for an object by a given list of attributes.
 
 
         Supported attributes: ``name``
         Supported attributes: ``name``
 
 

+ 54 - 53
cloudbridge/cloud/interfaces/services.py

@@ -48,15 +48,15 @@ class ComputeService(CloudService):
 
 
             # print all images
             # print all images
             for image in provider.compute.images:
             for image in provider.compute.images:
-                print(image.id, image.name)
+                print(image.id, image.name, image.label)
 
 
             # print only first 50 images
             # print only first 50 images
             for image in provider.compute.images.list(limit=50):
             for image in provider.compute.images.list(limit=50):
-                print(image.id, image.name)
+                print(image.id, image.name, image.label)
 
 
             # find image by name
             # find image by name
-            image = provider.compute.images.find(name='Ubuntu 16.04')
-            print(image.id, image.name)
+            image = provider.compute.images.find(name='Ubuntu 16.04')[0]
+            print(image.id, image.name, image.label)
 
 
         :rtype: :class:`.ImageService`
         :rtype: :class:`.ImageService`
         :return: an ImageService object
         :return: an ImageService object
@@ -77,7 +77,7 @@ class ComputeService(CloudService):
                 print(vm_type.id, vm_type.name)
                 print(vm_type.id, vm_type.name)
 
 
             # find a specific size by name
             # find a specific size by name
-            vm_type = provider.compute.vm_types.find(name='m1.small')
+            vm_type = provider.compute.vm_types.find(name='m1.small')[0]
             print(vm_type.vcpus)
             print(vm_type.vcpus)
 
 
         :rtype: :class:`.VMTypeService`
         :rtype: :class:`.VMTypeService`
@@ -98,7 +98,7 @@ class ComputeService(CloudService):
             image = provider.compute.images.find(name='Ubuntu 16.04')[0]
             image = provider.compute.images.find(name='Ubuntu 16.04')[0]
             size = provider.compute.vm_types.find(name='m1.small')
             size = provider.compute.vm_types.find(name='m1.small')
             instance = provider.compute.instances.create('Hello', image, size)
             instance = provider.compute.instances.create('Hello', image, size)
-            print(instance.id, instance.name)
+            print(instance.id, instance.label)
 
 
         :rtype: :class:`.InstanceService`
         :rtype: :class:`.InstanceService`
         :return: an InstanceService object
         :return: an InstanceService object
@@ -164,11 +164,14 @@ class InstanceService(PageableObjectMixin, CloudService):
         """
         """
         Searches for an instance by a given list of attributes.
         Searches for an instance by a given list of attributes.
 
 
-        Supported attributes: name
+        Supported attributes: name, label
 
 
         :type  name: ``str``
         :type  name: ``str``
         :param name: The name to search for
         :param name: The name to search for
 
 
+        :type  label: ``str``
+        :param label: The label to search for
+
         :rtype: List of ``object`` of :class:`.Instance`
         :rtype: List of ``object`` of :class:`.Instance`
         :return: A list of Instance objects matching the supplied attributes.
         :return: A list of Instance objects matching the supplied attributes.
         """
         """
@@ -210,15 +213,16 @@ class InstanceService(PageableObjectMixin, CloudService):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def create(self, name, image, vm_type, subnet, zone=None,
+    def create(self, label, image, vm_type, subnet, zone=None,
                key_pair=None, vm_firewalls=None, user_data=None,
                key_pair=None, vm_firewalls=None, user_data=None,
                launch_config=None,
                launch_config=None,
                **kwargs):
                **kwargs):
         """
         """
         Creates a new virtual machine instance.
         Creates a new virtual machine instance.
 
 
-        :type  name: ``str``
-        :param name: The name of the virtual machine instance
+        :type  label: ``str``
+        :param label: The label of the virtual machine instance. The instance
+                      name will be derived from this label.
 
 
         :type  image: ``MachineImage`` or ``str``
         :type  image: ``MachineImage`` or ``str``
         :param image: The MachineImage object or id to boot the virtual machine
         :param image: The MachineImage object or id to boot the virtual machine
@@ -240,7 +244,7 @@ class InstanceService(PageableObjectMixin, CloudService):
                        value but the behaviour is implementation specific.
                        value but the behaviour is implementation specific.
 
 
         :type  zone: ``Zone`` or ``str``
         :type  zone: ``Zone`` or ``str``
-        :param zone: The Zone or its name, where the instance should be placed.
+        :param zone: The Zone or its id, where the instance should be placed.
                      This parameter is provided for legacy compatibility (with
                      This parameter is provided for legacy compatibility (with
                      classic networks).
                      classic networks).
 
 
@@ -248,7 +252,7 @@ class InstanceService(PageableObjectMixin, CloudService):
                      parameter, but in its absence, this value will be used.
                      parameter, but in its absence, this value will be used.
 
 
         :type  key_pair: ``KeyPair`` or ``str``
         :type  key_pair: ``KeyPair`` or ``str``
-        :param key_pair: The KeyPair object or its name, to set for the
+        :param key_pair: The KeyPair object or its id, to set for the
                          instance.
                          instance.
 
 
         :type  vm_firewalls: A ``list`` of ``VMFirewall`` objects or a
         :type  vm_firewalls: A ``list`` of ``VMFirewall`` objects or a
@@ -311,7 +315,7 @@ class VolumeService(PageableObjectMixin, CloudService):
         """
         """
         Searches for a volume by a given list of attributes.
         Searches for a volume by a given list of attributes.
 
 
-        Supported attributes: name
+        Supported attributes: label
 
 
         :rtype: ``object`` of :class:`.Volume`
         :rtype: ``object`` of :class:`.Volume`
         :return: a Volume object or ``None`` if not found.
         :return: a Volume object or ``None`` if not found.
@@ -329,12 +333,12 @@ class VolumeService(PageableObjectMixin, CloudService):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def create(self, name, size, zone, snapshot=None, description=None):
+    def create(self, label, size, zone, snapshot=None, description=None):
         """
         """
         Creates a new volume.
         Creates a new volume.
 
 
-        :type  name: ``str``
-        :param name: The name of the volume.
+        :type  label: ``str``
+        :param label: The label for the volume.
 
 
         :type  size: ``int``
         :type  size: ``int``
         :param size: The size of the volume (in GB).
         :param size: The size of the volume (in GB).
@@ -378,7 +382,7 @@ class SnapshotService(PageableObjectMixin, CloudService):
         """
         """
         Searches for a snapshot by a given list of attributes.
         Searches for a snapshot by a given list of attributes.
 
 
-        Supported attributes: name
+        Supported attributes: label
 
 
         :rtype: list of :class:`.Snapshot`
         :rtype: list of :class:`.Snapshot`
         :return: a Snapshot object or an empty list if none found.
         :return: a Snapshot object or an empty list if none found.
@@ -396,12 +400,12 @@ class SnapshotService(PageableObjectMixin, CloudService):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def create(self, name, volume, description=None):
+    def create(self, label, volume, description=None):
         """
         """
         Creates a new snapshot off a volume.
         Creates a new snapshot off a volume.
 
 
-        :type  name: ``str``
-        :param name: The name of the snapshot
+        :type  label: ``str``
+        :param label: The label for the snapshot.
 
 
         :type  volume: ``str`` or ``Volume``
         :type  volume: ``str`` or ``Volume``
         :param volume: The volume to create a snapshot of.
         :param volume: The volume to create a snapshot of.
@@ -437,11 +441,11 @@ class StorageService(CloudService):
 
 
             # print all volumes
             # print all volumes
             for vol in provider.storage.volumes:
             for vol in provider.storage.volumes:
-                print(vol.id, vol.name)
+                print(vol.id, vol.name, vol.label)
 
 
-            # find volume by name
-            vol = provider.storage.volumes.find(name='my_vol')[0]
-            print(vol.id, vol.name)
+            # find volume by label
+            vol = provider.storage.volumes.find(label='my_vol')[0]
+            print(vol.id, vol.name, vol.label)
 
 
         :rtype: :class:`.VolumeService`
         :rtype: :class:`.VolumeService`
         :return: a VolumeService object
         :return: a VolumeService object
@@ -459,11 +463,11 @@ class StorageService(CloudService):
 
 
             # print all snapshots
             # print all snapshots
             for snap in provider.storage.snapshots:
             for snap in provider.storage.snapshots:
-                print(snap.id, snap.name)
+                print(snap.id, snap.name, snap.label)
 
 
-            # find snapshot by name
-            snap = provider.storage.snapshots.find(name='my_snap')[0]
-            print(snap.id, snap.name)
+            # find snapshot by label
+            snap = provider.storage.snapshots.find(label='my_snap')[0]
+            print(snap.id, snap.name, snap.label)
 
 
         :rtype: :class:`.SnapshotService`
         :rtype: :class:`.SnapshotService`
         :return: a SnapshotService object
         :return: a SnapshotService object
@@ -516,7 +520,7 @@ class ImageService(PageableObjectMixin, CloudService):
         """
         """
         Searches for an image by a given list of attributes
         Searches for an image by a given list of attributes
 
 
-        Supported attributes: name
+        Supported attributes: name, label
 
 
         :rtype: ``object`` of :class:`.Image`
         :rtype: ``object`` of :class:`.Image`
         :return:  an Image instance
         :return:  an Image instance
@@ -616,7 +620,7 @@ class NetworkService(PageableObjectMixin, CloudService):
         """
         """
         Searches for a network by a given list of attributes.
         Searches for a network by a given list of attributes.
 
 
-        Supported attributes: name
+        Supported attributes: name, label
 
 
         :rtype: List of ``object`` of :class:`.Network`
         :rtype: List of ``object`` of :class:`.Network`
         :return: A list of Network objects matching the supplied attributes.
         :return: A list of Network objects matching the supplied attributes.
@@ -624,13 +628,12 @@ class NetworkService(PageableObjectMixin, CloudService):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def create(self, name, cidr_block):
+    def create(self, label, cidr_block):
         """
         """
         Create a new network.
         Create a new network.
 
 
-        :type name: ``str``
-        :param name: A network name. The name will be set if the
-                     provider supports it.
+        :type label: ``str``
+        :param label: A label for the network.
 
 
         :type cidr_block: ``str``
         :type cidr_block: ``str``
         :param cidr_block: The cidr block for this network. Some providers
         :param cidr_block: The cidr block for this network. Some providers
@@ -668,11 +671,11 @@ class NetworkService(PageableObjectMixin, CloudService):
 
 
             # Print all subnets
             # Print all subnets
             for s in provider.networking.subnets:
             for s in provider.networking.subnets:
-                print(s.id, s.name)
+                print(s.id, s.name, s.label)
 
 
             # Get subnet by ID
             # Get subnet by ID
             s = provider.networking.subnets.get('subnet-id')
             s = provider.networking.subnets.get('subnet-id')
-            print(s.id, s.name)
+            print(s.id, s.name, s.label)
 
 
         :rtype: :class:`.SubnetService`
         :rtype: :class:`.SubnetService`
         :return: a SubnetService object
         :return: a SubnetService object
@@ -719,7 +722,7 @@ class SubnetService(PageableObjectMixin, CloudService):
         """
         """
         Searches for a subnet by a given list of attributes.
         Searches for a subnet by a given list of attributes.
 
 
-        Supported attributes: name
+        Supported attributes: name, label
 
 
         :rtype: List of ``object`` of :class:`.Subnet`
         :rtype: List of ``object`` of :class:`.Subnet`
         :return: A list of Subnet objects matching the supplied attributes.
         :return: A list of Subnet objects matching the supplied attributes.
@@ -727,13 +730,12 @@ class SubnetService(PageableObjectMixin, CloudService):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def create(self, name, network_id, cidr_block, zone):
+    def create(self, label, network_id, cidr_block, zone):
         """
         """
         Create a new subnet within the supplied network.
         Create a new subnet within the supplied network.
 
 
-        :type name: ``str``
-        :param name: The subnet name. The name will be set if the
-                     provider supports it.
+        :type label: ``str``
+        :param label: The subnet label.
 
 
         :type network: :class:`.Network` object or ``str``
         :type network: :class:`.Network` object or ``str``
         :param network: Network object or ID under which to create the subnet.
         :param network: Network object or ID under which to create the subnet.
@@ -759,7 +761,7 @@ class SubnetService(PageableObjectMixin, CloudService):
         are not particularly concerned with how the network is structured.
         are not particularly concerned with how the network is structured.
 
 
         A default network is one marked as such by the provider or matches the
         A default network is one marked as such by the provider or matches the
-        default name used by this library (e.g., cloudbridge-net).
+        default label used by this library (e.g., cloudbridge-net).
 
 
         :type zone: :class:`.PlacementZone` object ``str``
         :type zone: :class:`.PlacementZone` object ``str``
         :param zone: Placement zone where to look for the subnet.
         :param zone: Placement zone where to look for the subnet.
@@ -814,7 +816,7 @@ class RouterService(PageableObjectMixin, CloudService):
         """
         """
         Searches for a router by a given list of attributes.
         Searches for a router by a given list of attributes.
 
 
-        Supported attributes: name
+        Supported attributes: name, label
 
 
         :rtype: List of ``object`` of :class:`.Router`
         :rtype: List of ``object`` of :class:`.Router`
         :return: A list of Router objects matching the supplied attributes.
         :return: A list of Router objects matching the supplied attributes.
@@ -822,13 +824,12 @@ class RouterService(PageableObjectMixin, CloudService):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def create(self, name, network):
+    def create(self, label, network):
         """
         """
         Create a new router.
         Create a new router.
 
 
-        :type name: ``str``
-        :param name: A router name. The name will be set if the provider
-                     supports it.
+        :type label: ``str``
+        :param label: A router label.
 
 
         :type network: :class:`.Network` object or ``str``
         :type network: :class:`.Network` object or ``str``
         :param network: Network object or ID under which to create the router.
         :param network: Network object or ID under which to create the router.
@@ -1118,19 +1119,19 @@ class VMFirewallService(PageableObjectMixin, CloudService):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def create(self, name, description, network_id):
+    def create(self, label, network_id, description=None):
         """
         """
         Create a new VMFirewall.
         Create a new VMFirewall.
 
 
-        :type name: str
-        :param name: The name of the new VM firewall.
-
-        :type description: str
-        :param description: The description of the new VM firewall.
+        :type label: str
+        :param label: The label for the new VM firewall.
 
 
         :type  network_id: ``str``
         :type  network_id: ``str``
         :param network_id: Network ID under which to create the VM firewall.
         :param network_id: Network ID under which to create the VM firewall.
 
 
+        :type description: str
+        :param description: The description of the new VM firewall.
+
         :rtype: ``object`` of :class:`.VMFirewall`
         :rtype: ``object`` of :class:`.VMFirewall`
         :return:  A VMFirewall instance or ``None`` if one was not created.
         :return:  A VMFirewall instance or ``None`` if one was not created.
         """
         """

+ 16 - 9
cloudbridge/cloud/providers/aws/helpers.py

@@ -175,8 +175,10 @@ class BotoGenericService(object):
         PaginationConfig = {}
         PaginationConfig = {}
         if limit:
         if limit:
             PaginationConfig = {'MaxItems': limit, 'PageSize': limit}
             PaginationConfig = {'MaxItems': limit, 'PageSize': limit}
+
         if marker:
         if marker:
             PaginationConfig.update({'StartingToken': marker})
             PaginationConfig.update({'StartingToken': marker})
+
         params.update({'PaginationConfig': PaginationConfig})
         params.update({'PaginationConfig': PaginationConfig})
         args = trim_empty_params(params)
         args = trim_empty_params(params)
         pages = paginator.paginate(**args)
         pages = paginator.paginate(**args)
@@ -196,12 +198,13 @@ class BotoGenericService(object):
         if client.can_paginate(list_op):
         if client.can_paginate(list_op):
             log.debug("Supports server side pagination. Server will"
             log.debug("Supports server side pagination. Server will"
                       " limit and page results.")
                       " limit and page results.")
-            return self._get_paginated_results(limit, marker, collection)
+            res_token, items = self._get_paginated_results(limit, marker,
+                                                           collection)
+            return 'server', res_token, items
         else:
         else:
             log.debug("Does not support server side pagination. Client will"
             log.debug("Does not support server side pagination. Client will"
                       " limit and page results.")
                       " limit and page results.")
-            # Do not limit, let the ClientPagedResultList enforce limit
-            return (None, collection)
+            return 'client', None, collection
 
 
     def list(self, limit=None, marker=None, collection=None, **kwargs):
     def list(self, limit=None, marker=None, collection=None, **kwargs):
         """
         """
@@ -212,16 +215,20 @@ class BotoGenericService(object):
                            current resource. See http://boto3.readthedocs.io/
                            current resource. See http://boto3.readthedocs.io/
                            en/latest/guide/collections.html
                            en/latest/guide/collections.html
         """
         """
+        limit = limit or self.provider.config.default_result_limit
         collection = collection or self.boto_collection.filter(**kwargs)
         collection = collection or self.boto_collection.filter(**kwargs)
-        resume_token, boto_objs = self._make_query(collection, limit, marker)
-
+        pag_type, resume_token, boto_objs = self._make_query(collection,
+                                                             limit,
+                                                             marker)
         # Wrap in CB objects.
         # Wrap in CB objects.
         results = [self.cb_resource(self.provider, obj) for obj in boto_objs]
         results = [self.cb_resource(self.provider, obj) for obj in boto_objs]
 
 
-        if resume_token:
-            log.debug("Received a resume token, using server pagination.")
-            return ServerPagedResultList(is_truncated=True,
-                                         marker=resume_token,
+        if pag_type == 'server':
+            log.debug("Using server pagination.")
+            return ServerPagedResultList(is_truncated=True if resume_token
+                                         else False,
+                                         marker=resume_token if resume_token
+                                         else None,
                                          supports_total=False,
                                          supports_total=False,
                                          data=results)
                                          data=results)
         else:
         else:

+ 146 - 73
cloudbridge/cloud/providers/aws/resources.py

@@ -81,6 +81,21 @@ class AWSMachineImage(BaseMachineImage):
         except (AttributeError, ClientError) as e:
         except (AttributeError, ClientError) as e:
             log.warn("Cannot get name for image {0}: {1}".format(self.id, e))
             log.warn("Cannot get name for image {0}: {1}".format(self.id, e))
 
 
+    @property
+    # pylint:disable=arguments-differ
+    def label(self):
+        """
+        .. note:: an instance must have a (case sensitive) tag ``Name``
+        """
+        return find_tag_value(self._ec2_image.tags, 'Name')
+
+    @label.setter
+    # pylint:disable=arguments-differ
+    def label(self, value):
+        self.assert_valid_resource_label(value)
+        self._ec2_image.create_tags(Tags=[{'Key': 'Name',
+                                           'Value': value or ""}])
+
     @property
     @property
     def description(self):
     def description(self):
         try:
         try:
@@ -141,7 +156,7 @@ class AWSPlacementZone(BasePlacementZone):
 
 
     @property
     @property
     def name(self):
     def name(self):
-        return self._aws_zone
+        return self.id
 
 
     @property
     @property
     def region_name(self):
     def region_name(self):
@@ -160,7 +175,7 @@ class AWSVMType(BaseVMType):
 
 
     @property
     @property
     def name(self):
     def name(self):
-        return self._inst_dict['instance_type']
+        return self.id
 
 
     @property
     @property
     def family(self):
     def family(self):
@@ -168,7 +183,10 @@ class AWSVMType(BaseVMType):
 
 
     @property
     @property
     def vcpus(self):
     def vcpus(self):
-        return self._inst_dict.get('vCPU')
+        vcpus = self._inst_dict.get('vCPU')
+        if vcpus == 'N/A':
+            return None
+        return vcpus
 
 
     @property
     @property
     def ram(self):
     def ram(self):
@@ -216,24 +234,30 @@ class AWSInstance(BaseInstance):
     def __init__(self, provider, ec2_instance):
     def __init__(self, provider, ec2_instance):
         super(AWSInstance, self).__init__(provider)
         super(AWSInstance, self).__init__(provider)
         self._ec2_instance = ec2_instance
         self._ec2_instance = ec2_instance
+        self._unknown_state = False
 
 
     @property
     @property
     def id(self):
     def id(self):
         return self._ec2_instance.id
         return self._ec2_instance.id
 
 
     @property
     @property
-    # pylint:disable=arguments-differ
     def name(self):
     def name(self):
+        return self.id
+
+    @property
+    # pylint:disable=arguments-differ
+    def label(self):
         """
         """
         .. note:: an instance must have a (case sensitive) tag ``Name``
         .. note:: an instance must have a (case sensitive) tag ``Name``
         """
         """
         return find_tag_value(self._ec2_instance.tags, 'Name')
         return find_tag_value(self._ec2_instance.tags, 'Name')
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
-        self.assert_valid_resource_name(value)
-        self._ec2_instance.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
+    def label(self, value):
+        self.assert_valid_resource_label(value)
+        self._ec2_instance.create_tags(Tags=[{'Key': 'Name',
+                                              'Value': value or ""}])
 
 
     @property
     @property
     def public_ips(self):
     def public_ips(self):
@@ -287,17 +311,20 @@ class AWSInstance(BaseInstance):
         ]))
         ]))
 
 
     @property
     @property
-    def key_pair_name(self):
+    def key_pair_id(self):
         return self._ec2_instance.key_name
         return self._ec2_instance.key_name
 
 
-    def create_image(self, name):
-        self.assert_valid_resource_name(name)
+    def create_image(self, label):
+        self.assert_valid_resource_label(label)
+        name = self._generate_name_from_label(label, 'cb-img')
 
 
         image = AWSMachineImage(self._provider,
         image = AWSMachineImage(self._provider,
                                 self._ec2_instance.create_image(Name=name))
                                 self._ec2_instance.create_image(Name=name))
         # Wait for the image to exist
         # Wait for the image to exist
         self._provider.ec2_conn.meta.client.get_waiter('image_exists').wait(
         self._provider.ec2_conn.meta.client.get_waiter('image_exists').wait(
             ImageIds=[image.id])
             ImageIds=[image.id])
+        # Add image label
+        image.label = label
         # Return the image
         # Return the image
         image.refresh()
         image.refresh()
         return image
         return image
@@ -342,6 +369,8 @@ class AWSInstance(BaseInstance):
 
 
     @property
     @property
     def state(self):
     def state(self):
+        if self._unknown_state:
+            return InstanceState.UNKNOWN
         try:
         try:
             return AWSInstance.INSTANCE_STATE_MAP.get(
             return AWSInstance.INSTANCE_STATE_MAP.get(
                 self._ec2_instance.state['Name'], InstanceState.UNKNOWN)
                 self._ec2_instance.state['Name'], InstanceState.UNKNOWN)
@@ -352,10 +381,11 @@ class AWSInstance(BaseInstance):
     def refresh(self):
     def refresh(self):
         try:
         try:
             self._ec2_instance.reload()
             self._ec2_instance.reload()
+            self._unknown_state = False
         except ClientError:
         except ClientError:
             # The instance no longer exists and cannot be refreshed.
             # The instance no longer exists and cannot be refreshed.
             # set the state to unknown
             # set the state to unknown
-            self._ec2_instance.state = {'Name': InstanceState.UNKNOWN}
+            self._unknown_state = True
 
 
     # pylint:disable=unused-argument
     # pylint:disable=unused-argument
     def _wait_till_exists(self, timeout=None, interval=None):
     def _wait_till_exists(self, timeout=None, interval=None):
@@ -379,24 +409,29 @@ class AWSVolume(BaseVolume):
     def __init__(self, provider, volume):
     def __init__(self, provider, volume):
         super(AWSVolume, self).__init__(provider)
         super(AWSVolume, self).__init__(provider)
         self._volume = volume
         self._volume = volume
+        self._unknown_state = False
 
 
     @property
     @property
     def id(self):
     def id(self):
         return self._volume.id
         return self._volume.id
 
 
     @property
     @property
-    # pylint:disable=arguments-differ
     def name(self):
     def name(self):
+        return self.id
+
+    @property
+    # pylint:disable=arguments-differ
+    def label(self):
         try:
         try:
             return find_tag_value(self._volume.tags, 'Name')
             return find_tag_value(self._volume.tags, 'Name')
         except ClientError as e:
         except ClientError as e:
-            log.warn("Cannot get name for volume {0}: {1}".format(self.id, e))
+            log.warn("Cannot get label for volume {0}: {1}".format(self.id, e))
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
-        self.assert_valid_resource_name(value)
-        self._volume.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
+    def label(self, value):
+        self.assert_valid_resource_label(value)
+        self._volume.create_tags(Tags=[{'Key': 'Name', 'Value': value or ""}])
 
 
     @property
     @property
     def description(self):
     def description(self):
@@ -404,7 +439,8 @@ class AWSVolume(BaseVolume):
 
 
     @description.setter
     @description.setter
     def description(self, value):
     def description(self, value):
-        self._volume.create_tags(Tags=[{'Key': 'Description', 'Value': value}])
+        self._volume.create_tags(Tags=[{'Key': 'Description',
+                                        'Value': value or ""}])
 
 
     @property
     @property
     def size(self):
     def size(self):
@@ -449,12 +485,16 @@ class AWSVolume(BaseVolume):
                 Device=a.device,
                 Device=a.device,
                 Force=force)
                 Force=force)
 
 
-    def create_snapshot(self, name, description=None):
+    def create_snapshot(self, label, description=None):
+        self.assert_valid_resource_label(label)
         snap = AWSSnapshot(
         snap = AWSSnapshot(
             self._provider,
             self._provider,
             self._volume.create_snapshot(
             self._volume.create_snapshot(
-                Description=description))
-        snap.name = name
+                TagSpecifications=[{'ResourceType': 'snapshot',
+                                    'Tags': [{'Key': 'Name',
+                                              'Value': label}]}],
+                Description=description or ""))
+        snap.wait_till_ready()
         return snap
         return snap
 
 
     def delete(self):
     def delete(self):
@@ -462,6 +502,8 @@ class AWSVolume(BaseVolume):
 
 
     @property
     @property
     def state(self):
     def state(self):
+        if self._unknown_state:
+            return VolumeState.UNKNOWN
         try:
         try:
             return AWSVolume.VOLUME_STATE_MAP.get(
             return AWSVolume.VOLUME_STATE_MAP.get(
                 self._volume.state, VolumeState.UNKNOWN)
                 self._volume.state, VolumeState.UNKNOWN)
@@ -472,10 +514,11 @@ class AWSVolume(BaseVolume):
     def refresh(self):
     def refresh(self):
         try:
         try:
             self._volume.reload()
             self._volume.reload()
+            self._unknown_state = False
         except ClientError:
         except ClientError:
             # The volume no longer exists and cannot be refreshed.
             # The volume no longer exists and cannot be refreshed.
             # set the status to unknown
             # set the status to unknown
-            self._volume.state = VolumeState.UNKNOWN
+            self._unknown_state = True
 
 
 
 
 class AWSSnapshot(BaseSnapshot):
 class AWSSnapshot(BaseSnapshot):
@@ -492,24 +535,30 @@ class AWSSnapshot(BaseSnapshot):
     def __init__(self, provider, snapshot):
     def __init__(self, provider, snapshot):
         super(AWSSnapshot, self).__init__(provider)
         super(AWSSnapshot, self).__init__(provider)
         self._snapshot = snapshot
         self._snapshot = snapshot
+        self._unknown_state = False
 
 
     @property
     @property
     def id(self):
     def id(self):
         return self._snapshot.id
         return self._snapshot.id
 
 
     @property
     @property
-    # pylint:disable=arguments-differ
     def name(self):
     def name(self):
+        return self.id
+
+    @property
+    # pylint:disable=arguments-differ
+    def label(self):
         try:
         try:
             return find_tag_value(self._snapshot.tags, 'Name')
             return find_tag_value(self._snapshot.tags, 'Name')
         except ClientError as e:
         except ClientError as e:
-            log.warn("Cannot get name for snap {0}: {1}".format(self.id, e))
+            log.warn("Cannot get label for snap {0}: {1}".format(self.id, e))
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
-        self.assert_valid_resource_name(value)
-        self._snapshot.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
+    def label(self, value):
+        self.assert_valid_resource_label(value)
+        self._snapshot.create_tags(Tags=[{'Key': 'Name',
+                                          'Value': value or ""}])
 
 
     @property
     @property
     def description(self):
     def description(self):
@@ -518,7 +567,7 @@ class AWSSnapshot(BaseSnapshot):
     @description.setter
     @description.setter
     def description(self, value):
     def description(self, value):
         self._snapshot.create_tags(Tags=[{
         self._snapshot.create_tags(Tags=[{
-            'Key': 'Description', 'Value': value}])
+            'Key': 'Description', 'Value': value or ""}])
 
 
     @property
     @property
     def size(self):
     def size(self):
@@ -534,6 +583,8 @@ class AWSSnapshot(BaseSnapshot):
 
 
     @property
     @property
     def state(self):
     def state(self):
+        if self._unknown_state:
+            return SnapshotState.UNKNOWN
         try:
         try:
             return AWSSnapshot.SNAPSHOT_STATE_MAP.get(
             return AWSSnapshot.SNAPSHOT_STATE_MAP.get(
                 self._snapshot.state, SnapshotState.UNKNOWN)
                 self._snapshot.state, SnapshotState.UNKNOWN)
@@ -544,22 +595,23 @@ class AWSSnapshot(BaseSnapshot):
     def refresh(self):
     def refresh(self):
         try:
         try:
             self._snapshot.reload()
             self._snapshot.reload()
+            self._unknown_state = False
         except ClientError:
         except ClientError:
             # The snapshot no longer exists and cannot be refreshed.
             # The snapshot no longer exists and cannot be refreshed.
             # set the status to unknown
             # set the status to unknown
-            self._snapshot.state = SnapshotState.UNKNOWN
+            self._unknown_state = True
 
 
     def delete(self):
     def delete(self):
         self._snapshot.delete()
         self._snapshot.delete()
 
 
     def create_volume(self, placement, size=None, volume_type=None, iops=None):
     def create_volume(self, placement, size=None, volume_type=None, iops=None):
+        label = "from-snap-{0}".format(self.label or self.id)
         cb_vol = self._provider.storage.volumes.create(
         cb_vol = self._provider.storage.volumes.create(
-            name=self.name,
+            label=label,
             size=size,
             size=size,
             zone=placement,
             zone=placement,
             snapshot=self.id)
             snapshot=self.id)
         cb_vol.wait_till_ready()
         cb_vol.wait_till_ready()
-        cb_vol.name = "from_snap_{0}".format(self.name or self.id)
         return cb_vol
         return cb_vol
 
 
 
 
@@ -577,19 +629,24 @@ class AWSVMFirewall(BaseVMFirewall):
 
 
     @property
     @property
     def name(self):
     def name(self):
+        """
+        Return the name of this VM firewall.
+        """
+        return self._vm_firewall.group_name
+
+    @property
+    def label(self):
         try:
         try:
-            name = find_tag_value(self._vm_firewall.tags, 'Name')
-            if not name:  # Return group_name (which cannot be changed)
-                name = self._vm_firewall.group_name
-            return name
+            return find_tag_value(self._vm_firewall.tags, 'Name')
         except ClientError:
         except ClientError:
-            return self._vm_firewall.group_name
+            return None
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
-        self.assert_valid_resource_name(value)
-        self._vm_firewall.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
+    def label(self, value):
+        self.assert_valid_resource_label(value)
+        self._vm_firewall.create_tags(Tags=[{'Key': 'Name',
+                                             'Value': value or ""}])
 
 
     @property
     @property
     def network_id(self):
     def network_id(self):
@@ -826,7 +883,7 @@ class AWSBucket(BaseBucket):
 
 
     @property
     @property
     def name(self):
     def name(self):
-        return self._bucket.name
+        return self.id
 
 
     @property
     @property
     def objects(self):
     def objects(self):
@@ -917,6 +974,7 @@ class AWSNetwork(BaseNetwork):
         super(AWSNetwork, self).__init__(provider)
         super(AWSNetwork, self).__init__(provider)
         self._vpc = network
         self._vpc = network
         self._gtw_container = AWSGatewayContainer(provider, self)
         self._gtw_container = AWSGatewayContainer(provider, self)
+        self._unknown_state = False
 
 
     @property
     @property
     def id(self):
     def id(self):
@@ -924,13 +982,17 @@ class AWSNetwork(BaseNetwork):
 
 
     @property
     @property
     def name(self):
     def name(self):
+        return self.id
+
+    @property
+    def label(self):
         return find_tag_value(self._vpc.tags, 'Name')
         return find_tag_value(self._vpc.tags, 'Name')
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
-        self.assert_valid_resource_name(value)
-        self._vpc.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
+    def label(self, value):
+        self.assert_valid_resource_label(value)
+        self._vpc.create_tags(Tags=[{'Key': 'Name', 'Value': value or ""}])
 
 
     @property
     @property
     def external(self):
     def external(self):
@@ -942,6 +1004,8 @@ class AWSNetwork(BaseNetwork):
 
 
     @property
     @property
     def state(self):
     def state(self):
+        if self._unknown_state:
+            return NetworkState.UNKNOWN
         try:
         try:
             return AWSNetwork._NETWORK_STATE_MAP.get(
             return AWSNetwork._NETWORK_STATE_MAP.get(
                 self._vpc.state, NetworkState.UNKNOWN)
                 self._vpc.state, NetworkState.UNKNOWN)
@@ -966,7 +1030,7 @@ class AWSNetwork(BaseNetwork):
         except ClientError:
         except ClientError:
             # The network no longer exists and cannot be refreshed.
             # The network no longer exists and cannot be refreshed.
             # set the status to unknown
             # set the status to unknown
-            self._vpc.state = NetworkState.UNKNOWN
+            self._unknown_state = True
 
 
     def wait_till_ready(self, timeout=None, interval=None):
     def wait_till_ready(self, timeout=None, interval=None):
         self._provider.ec2_conn.meta.client.get_waiter('vpc_available').wait(
         self._provider.ec2_conn.meta.client.get_waiter('vpc_available').wait(
@@ -989,6 +1053,7 @@ class AWSSubnet(BaseSubnet):
     def __init__(self, provider, subnet):
     def __init__(self, provider, subnet):
         super(AWSSubnet, self).__init__(provider)
         super(AWSSubnet, self).__init__(provider)
         self._subnet = subnet
         self._subnet = subnet
+        self._unknown_state = False
 
 
     @property
     @property
     def id(self):
     def id(self):
@@ -996,13 +1061,17 @@ class AWSSubnet(BaseSubnet):
 
 
     @property
     @property
     def name(self):
     def name(self):
+        return self.id
+
+    @property
+    def label(self):
         return find_tag_value(self._subnet.tags, 'Name')
         return find_tag_value(self._subnet.tags, 'Name')
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
-        self.assert_valid_resource_name(value)
-        self._subnet.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
+    def label(self, value):
+        self.assert_valid_resource_label(value)
+        self._subnet.create_tags(Tags=[{'Key': 'Name', 'Value': value or ""}])
 
 
     @property
     @property
     def cidr_block(self):
     def cidr_block(self):
@@ -1022,6 +1091,8 @@ class AWSSubnet(BaseSubnet):
 
 
     @property
     @property
     def state(self):
     def state(self):
+        if self._unknown_state:
+            return SubnetState.UNKNOWN
         try:
         try:
             return self._SUBNET_STATE_MAP.get(
             return self._SUBNET_STATE_MAP.get(
                 self._subnet.state, SubnetState.UNKNOWN)
                 self._subnet.state, SubnetState.UNKNOWN)
@@ -1030,13 +1101,12 @@ class AWSSubnet(BaseSubnet):
             return SubnetState.UNKNOWN
             return SubnetState.UNKNOWN
 
 
     def refresh(self):
     def refresh(self):
-        subnet = self._provider.networking.subnets.get(self.id)
-        if subnet:
-            # pylint:disable=protected-access
-            self._subnet = subnet._subnet
-        else:
+        try:
+            self._subnet.reload()
+            self._unknown_state = False
+        except ClientError:
             # subnet no longer exists
             # subnet no longer exists
-            self._subnet.state = SubnetState.UNKNOWN
+            self._unknown_state = True
 
 
 
 
 class AWSFloatingIPContainer(BaseFloatingIPContainer):
 class AWSFloatingIPContainer(BaseFloatingIPContainer):
@@ -1105,13 +1175,18 @@ class AWSRouter(BaseRouter):
 
 
     @property
     @property
     def name(self):
     def name(self):
+        return self.id
+
+    @property
+    def label(self):
         return find_tag_value(self._route_table.tags, 'Name')
         return find_tag_value(self._route_table.tags, 'Name')
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
-        self.assert_valid_resource_name(value)
-        self._route_table.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
+    def label(self, value):
+        self.assert_valid_resource_label(value)
+        self._route_table.create_tags(Tags=[{'Key': 'Name',
+                                             'Value': value or ""}])
 
 
     def refresh(self):
     def refresh(self):
         try:
         try:
@@ -1171,12 +1246,9 @@ class AWSGatewayContainer(BaseGatewayContainer):
     def get_or_create_inet_gateway(self, name=None):
     def get_or_create_inet_gateway(self, name=None):
         log.debug("Get or create inet gateway %s on net %s", name,
         log.debug("Get or create inet gateway %s on net %s", name,
                   self._network)
                   self._network)
-        if name:
-            AWSInternetGateway.assert_valid_resource_name(name)
-
         network_id = self._network.id if isinstance(
         network_id = self._network.id if isinstance(
             self._network, AWSNetwork) else self._network
             self._network, AWSNetwork) else self._network
-        # Don't filter by name because it may conflict with at least the
+        # Don't filter by label because it may conflict with at least the
         # default VPC that most accounts have but that network is typically
         # default VPC that most accounts have but that network is typically
         # without a name.
         # without a name.
         gtw = self.svc.find(filter_name='attachment.vpc-id',
         gtw = self.svc.find(filter_name='attachment.vpc-id',
@@ -1186,7 +1258,14 @@ class AWSGatewayContainer(BaseGatewayContainer):
         # Gateway does not exist so create one and attach to the supplied net
         # Gateway does not exist so create one and attach to the supplied net
         cb_gateway = self.svc.create('create_internet_gateway')
         cb_gateway = self.svc.create('create_internet_gateway')
         if name:
         if name:
-            cb_gateway.name = name
+            AWSInternetGateway.assert_valid_resource_name(name)
+            cb_gateway._gateway.create_tags(
+                Tags=[{'Key': 'Name', 'Value': name}])
+        else:
+            cb_gateway._gateway.create_tags(
+                Tags=[{'Key': 'Name',
+                       'Value': AWSInternetGateway.CB_DEFAULT_INET_GATEWAY_NAME
+                       }])
         cb_gateway._gateway.attach_to_vpc(VpcId=network_id)
         cb_gateway._gateway.attach_to_vpc(VpcId=network_id)
         return cb_gateway
         return cb_gateway
 
 
@@ -1221,12 +1300,6 @@ class AWSInternetGateway(BaseInternetGateway):
     def name(self):
     def name(self):
         return find_tag_value(self._gateway.tags, 'Name')
         return find_tag_value(self._gateway.tags, 'Name')
 
 
-    @name.setter
-    # pylint:disable=arguments-differ
-    def name(self, value):
-        self.assert_valid_resource_name(value)
-        self._gateway.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
-
     def refresh(self):
     def refresh(self):
         try:
         try:
             self._gateway.reload()
             self._gateway.reload()

+ 126 - 92
cloudbridge/cloud/providers/aws/services.py

@@ -132,24 +132,28 @@ class AWSVMFirewallService(BaseVMFirewallService):
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
         return self.svc.list(limit=limit, marker=marker)
 
 
-    def create(self, name, description, network_id):
+    def create(self, label, network_id, description=None):
         log.debug("Creating Firewall Service with the parameters "
         log.debug("Creating Firewall Service with the parameters "
-                  "[name: %s id: %s description: %s]", name, network_id,
+                  "[label: %s id: %s description: %s]", label, network_id,
                   description)
                   description)
-        AWSVMFirewall.assert_valid_resource_name(name)
-        return self.svc.create('create_security_group', GroupName=name,
-                               Description=description, VpcId=network_id)
+        AWSVMFirewall.assert_valid_resource_label(label)
+        name = AWSVMFirewall._generate_name_from_label(label, 'cb-fw')
+        obj = self.svc.create('create_security_group', GroupName=name,
+                              Description=description or name,
+                              VpcId=network_id)
+        obj.label = label
+        return obj
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
-
+        # Filter by name or label
+        label = kwargs.pop('label', None)
+        log.debug("Searching for Firewall Service %s", label)
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
-
-        log.debug("Searching for Firewall Service %s", name)
-        return self.svc.find(filter_name='group-name', filter_value=name)
+                            " Supported attributes: %s" % (kwargs, 'label'))
+        return self.svc.find(filter_name='tag:Name',
+                             filter_value=label)
 
 
     def delete(self, firewall_id):
     def delete(self, firewall_id):
         log.info("Deleting Firewall Service with the id %s", firewall_id)
         log.info("Deleting Firewall Service with the id %s", firewall_id)
@@ -195,25 +199,25 @@ class AWSVolumeService(BaseVolumeService):
         return self.svc.get(volume_id)
         return self.svc.get(volume_id)
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        label = kwargs.pop('label', None)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs, 'label'))
 
 
-        log.debug("Searching for AWS Volume Service %s", name)
-        return self.svc.find(filter_name='tag:Name', filter_value=name)
+        log.debug("Searching for AWS Volume Service %s", label)
+        return self.svc.find(filter_name='tag:Name', filter_value=label)
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
         return self.svc.list(limit=limit, marker=marker)
 
 
-    def create(self, name, size, zone, snapshot=None, description=None):
+    def create(self, label, size, zone, snapshot=None, description=None):
         log.debug("Creating AWS Volume Service with the parameters "
         log.debug("Creating AWS Volume Service with the parameters "
-                  "[name: %s size: %s zone: %s snapshot: %s "
-                  "description: %s]", name, size, zone, snapshot,
+                  "[label: %s size: %s zone: %s snapshot: %s "
+                  "description: %s]", label, size, zone, snapshot,
                   description)
                   description)
-        AWSVolume.assert_valid_resource_name(name)
+        AWSVolume.assert_valid_resource_label(label)
 
 
         zone_id = zone.id if isinstance(zone, PlacementZone) else zone
         zone_id = zone.id if isinstance(zone, PlacementZone) else zone
         snapshot_id = snapshot.id if isinstance(
         snapshot_id = snapshot.id if isinstance(
@@ -224,7 +228,7 @@ class AWSVolumeService(BaseVolumeService):
                                  SnapshotId=snapshot_id)
                                  SnapshotId=snapshot_id)
         # Wait until ready to tag instance
         # Wait until ready to tag instance
         cb_vol.wait_till_ready()
         cb_vol.wait_till_ready()
-        cb_vol.name = name
+        cb_vol.label = label
         if description:
         if description:
             cb_vol.description = description
             cb_vol.description = description
         return cb_vol
         return cb_vol
@@ -244,34 +248,36 @@ class AWSSnapshotService(BaseSnapshotService):
         return self.svc.get(snapshot_id)
         return self.svc.get(snapshot_id)
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        label = kwargs.pop('label', None)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs, 'label'))
 
 
-        log.debug("Searching for AWS Snapshot Service %s", name)
-        return self.svc.find(filter_name='tag:Name', filter_value=name)
+        log.debug("Searching for AWS Snapshot Service %s", label)
+        return self.svc.find(filter_name='tag:Name', filter_value=label,
+                             OwnerIds=['self'])
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
-        return self.svc.list(limit=limit, marker=marker)
+        return self.svc.list(limit=limit, marker=marker,
+                             OwnerIds=['self'])
 
 
-    def create(self, name, volume, description=None):
+    def create(self, label, volume, description=None):
         """
         """
         Creates a new snapshot of a given volume.
         Creates a new snapshot of a given volume.
         """
         """
         log.debug("Creating a new AWS snapshot Service with the "
         log.debug("Creating a new AWS snapshot Service with the "
-                  "parameters [name: %s volume: %s description: %s]",
-                  name, volume, description)
-        AWSSnapshot.assert_valid_resource_name(name)
+                  "parameters [label: %s volume: %s description: %s]",
+                  label, volume, description)
+        AWSSnapshot.assert_valid_resource_label(label)
 
 
         volume_id = volume.id if isinstance(volume, AWSVolume) else volume
         volume_id = volume.id if isinstance(volume, AWSVolume) else volume
 
 
         cb_snap = self.svc.create('create_snapshot', VolumeId=volume_id)
         cb_snap = self.svc.create('create_snapshot', VolumeId=volume_id)
         # Wait until ready to tag instance
         # Wait until ready to tag instance
         cb_snap.wait_till_ready()
         cb_snap.wait_till_ready()
-        cb_snap.name = name
+        cb_snap.label = label
         if cb_snap.description:
         if cb_snap.description:
             cb_snap.description = description
             cb_snap.description = description
         return cb_snap
         return cb_snap
@@ -305,7 +311,7 @@ class AWSBucketService(BaseBucketService):
             # Bucket instance to allow further operations.
             # Bucket instance to allow further operations.
             # http://stackoverflow.com/questions/32331456/using-boto-upload-file-to-s3-
             # http://stackoverflow.com/questions/32331456/using-boto-upload-file-to-s3-
             # sub-folder-when-i-have-no-permissions-on-listing-fo
             # sub-folder-when-i-have-no-permissions-on-listing-fo
-            if e.response['Error']['Code'] == 403:
+            if e.response['Error']['Code'] == "403":
                 log.warning("AWS Bucket %s already exists but user doesn't "
                 log.warning("AWS Bucket %s already exists but user doesn't "
                             "have enough permissions to access the bucket",
                             "have enough permissions to access the bucket",
                             bucket_id)
                             bucket_id)
@@ -334,13 +340,31 @@ class AWSBucketService(BaseBucketService):
         # LocationConstraint results in an InvalidLocationConstraint.
         # LocationConstraint results in an InvalidLocationConstraint.
         # Therefore, it must be special-cased and omitted altogether.
         # Therefore, it must be special-cased and omitted altogether.
         # See: https://github.com/boto/boto3/issues/125
         # See: https://github.com/boto/boto3/issues/125
+        # In addition, us-east-1 also behaves differently when it comes
+        # to raising duplicate resource exceptions, so perform a manual
+        # check
         if loc_constraint == 'us-east-1':
         if loc_constraint == 'us-east-1':
-            return self.svc.create('create_bucket', Bucket=name)
+            try:
+                # check whether bucket already exists
+                self.provider.s3_conn.meta.client.head_bucket(Bucket=name)
+            except ClientError as e:
+                if e.response['Error']['Code'] == "404":
+                    # bucket doesn't exist, go ahead and create it
+                    return self.svc.create('create_bucket', Bucket=name)
+            raise DuplicateResourceException(
+                    'Bucket already exists with name {0}'.format(name))
         else:
         else:
-            return self.svc.create('create_bucket', Bucket=name,
-                                   CreateBucketConfiguration={
-                                       'LocationConstraint': loc_constraint
-                                   })
+            try:
+                return self.svc.create('create_bucket', Bucket=name,
+                                       CreateBucketConfiguration={
+                                           'LocationConstraint': loc_constraint
+                                        })
+            except ClientError as e:
+                if e.response['Error']['Code'] == "BucketAlreadyOwnedByYou":
+                    raise DuplicateResourceException(
+                        'Bucket already exists with name {0}'.format(name))
+                else:
+                    raise
 
 
 
 
 class AWSImageService(BaseImageService):
 class AWSImageService(BaseImageService):
@@ -356,15 +380,15 @@ class AWSImageService(BaseImageService):
         return self.svc.get(image_id)
         return self.svc.get(image_id)
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
+        # Filter by name or label
         name = kwargs.pop('name', None)
         name = kwargs.pop('name', None)
-
-        # All kwargs should have been popped at this time.
-        if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
-
-        log.debug("Searching for AWS Image Service %s", name)
-        return self.svc.find(filter_name='name', filter_value=name)
+        if name:
+            log.debug("Searching for AWS Image Service %s", name)
+            obj_list = self.svc.find(filter_name='name', filter_value=name)
+        else:
+            obj_list = self
+        filters = ['label']
+        return cb_helpers.generic_find(filters, kwargs, obj_list)
 
 
     def list(self, filter_by_owner=True, limit=None, marker=None):
     def list(self, filter_by_owner=True, limit=None, marker=None):
         return self.svc.list(Owners=['self'] if filter_by_owner else [],
         return self.svc.list(Owners=['self'] if filter_by_owner else [],
@@ -405,15 +429,15 @@ class AWSInstanceService(BaseInstanceService):
                                   cb_resource=AWSInstance,
                                   cb_resource=AWSInstance,
                                   boto_collection_name='instances')
                                   boto_collection_name='instances')
 
 
-    def create(self, name, image, vm_type, subnet, zone,
+    def create(self, label, image, vm_type, subnet, zone,
                key_pair=None, vm_firewalls=None, user_data=None,
                key_pair=None, vm_firewalls=None, user_data=None,
                launch_config=None, **kwargs):
                launch_config=None, **kwargs):
         log.debug("Creating AWS Instance Service with the params "
         log.debug("Creating AWS Instance Service with the params "
-                  "[name: %s image: %s type: %s subnet: %s zone: %s "
+                  "[label: %s image: %s type: %s subnet: %s zone: %s "
                   "key pair: %s firewalls: %s user data: %s config %s "
                   "key pair: %s firewalls: %s user data: %s config %s "
-                  "others: %s]", name, image, vm_type, subnet, zone,
+                  "others: %s]", label, image, vm_type, subnet, zone,
                   key_pair, vm_firewalls, user_data, launch_config, kwargs)
                   key_pair, vm_firewalls, user_data, launch_config, kwargs)
-        AWSInstance.assert_valid_resource_name(name)
+        AWSInstance.assert_valid_resource_label(label)
 
 
         image_id = image.id if isinstance(image, MachineImage) else image
         image_id = image.id if isinstance(image, MachineImage) else image
         vm_size = vm_type.id if \
         vm_size = vm_type.id if \
@@ -450,7 +474,7 @@ class AWSInstanceService(BaseInstanceService):
             # pylint:disable=protected-access
             # pylint:disable=protected-access
             inst[0]._wait_till_exists()
             inst[0]._wait_till_exists()
             # Tag the instance w/ the name
             # Tag the instance w/ the name
-            inst[0].name = name
+            inst[0].label = label
             return inst[0]
             return inst[0]
         raise ValueError(
         raise ValueError(
             'Expected a single object response, got a list: %s' % inst)
             'Expected a single object response, got a list: %s' % inst)
@@ -544,14 +568,14 @@ class AWSInstanceService(BaseInstanceService):
         return self.svc.get(instance_id)
         return self.svc.get(instance_id)
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        label = kwargs.pop('label', None)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs, 'label'))
 
 
-        return self.svc.find(filter_name='tag:Name', filter_value=name)
+        return self.svc.find(filter_name='tag:Name', filter_value=label)
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
         return self.svc.list(limit=limit, marker=marker)
@@ -653,26 +677,26 @@ class AWSNetworkService(BaseNetworkService):
         return self.svc.list(limit=limit, marker=marker)
         return self.svc.list(limit=limit, marker=marker)
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        label = kwargs.pop('label', None)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs, 'label'))
 
 
-        log.debug("Searching for AWS Network Service %s", name)
-        return self.svc.find(filter_name='tag:Name', filter_value=name)
+        log.debug("Searching for AWS Network Service %s", label)
+        return self.svc.find(filter_name='tag:Name', filter_value=label)
 
 
-    def create(self, name, cidr_block):
+    def create(self, label, cidr_block):
         log.debug("Creating AWS Network Service with the params "
         log.debug("Creating AWS Network Service with the params "
-                  "[name: %s block: %s]", name, cidr_block)
-        AWSNetwork.assert_valid_resource_name(name)
+                  "[label: %s block: %s]", label, cidr_block)
+        AWSNetwork.assert_valid_resource_label(label)
 
 
         cb_net = self.svc.create('create_vpc', CidrBlock=cidr_block)
         cb_net = self.svc.create('create_vpc', CidrBlock=cidr_block)
         # Wait until ready to tag instance
         # Wait until ready to tag instance
         cb_net.wait_till_ready()
         cb_net.wait_till_ready()
-        if name:
-            cb_net.name = name
+        if label:
+            cb_net.label = label
         return cb_net
         return cb_net
 
 
 
 
@@ -698,30 +722,33 @@ class AWSSubnetService(BaseSubnetService):
             return self.svc.list(limit=limit, marker=marker)
             return self.svc.list(limit=limit, marker=marker)
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        label = kwargs.pop('label', None)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs, 'label'))
 
 
-        log.debug("Searching for AWS Subnet Service %s", name)
-        return self.svc.find(filter_name='tag:Name', filter_value=name)
+        log.debug("Searching for AWS Subnet Service %s", label)
+        return self.svc.find(filter_name='tag:Name', filter_value=label)
 
 
-    def create(self, name, network, cidr_block, zone):
+    def create(self, label, network, cidr_block, zone):
         log.debug("Creating AWS Subnet Service with the params "
         log.debug("Creating AWS Subnet Service with the params "
-                  "[name: %s network: %s block: %s zone: %s]",
-                  name, network, cidr_block, zone)
-        AWSSubnet.assert_valid_resource_name(name)
+                  "[label: %s network: %s block: %s zone: %s]",
+                  label, network, cidr_block, zone)
+        AWSSubnet.assert_valid_resource_label(label)
+
+        zone_name = zone.name if isinstance(
+            zone, AWSPlacementZone) else zone
 
 
         network_id = network.id if isinstance(network, AWSNetwork) else network
         network_id = network.id if isinstance(network, AWSNetwork) else network
 
 
         subnet = self.svc.create('create_subnet',
         subnet = self.svc.create('create_subnet',
                                  VpcId=network_id,
                                  VpcId=network_id,
                                  CidrBlock=cidr_block,
                                  CidrBlock=cidr_block,
-                                 AvailabilityZone=zone)
-        if name:
-            subnet.name = name
+                                 AvailabilityZone=zone_name)
+        if label:
+            subnet.label = label
         return subnet
         return subnet
 
 
     def get_or_create_default(self, zone):
     def get_or_create_default(self, zone):
@@ -739,22 +766,29 @@ class AWSSubnetService(BaseSubnetService):
             # pylint:disable=protected-access
             # pylint:disable=protected-access
             if sn._subnet.default_for_az:
             if sn._subnet.default_for_az:
                 return sn
                 return sn
-        # No provider-default Subnet exists, look for a library-default one
-        for sn in snl:
-            # pylint:disable=protected-access
-            for tag in sn._subnet.tags or {}:
-                if (tag.get('Key') == 'Name' and
-                        tag.get('Value') == AWSSubnet.CB_DEFAULT_SUBNET_NAME):
-                    return sn
+
+        # Refresh the list for the default label
+        snl = self.find(label=AWSSubnet.CB_DEFAULT_SUBNET_LABEL)
+
+        if len(snl) > 0:
+            return snl[0]
+
         # No provider-default Subnet exists, try to create it (net + subnets)
         # No provider-default Subnet exists, try to create it (net + subnets)
-        default_net = self.provider.networking.networks.create(
-            name=AWSNetwork.CB_DEFAULT_NETWORK_NAME, cidr_block='10.0.0.0/16')
+        # Check if default net exists
+        default_nets = self.provider.networking.networks.find(
+            label=AWSNetwork.CB_DEFAULT_NETWORK_LABEL)
+        if len(default_nets) > 0:
+            default_net = default_nets[0]
+        else:
+            default_net = self.provider.networking.networks.create(
+                label=AWSNetwork.CB_DEFAULT_NETWORK_LABEL,
+                cidr_block='10.0.0.0/16')
         # Create a subnet in each of the region's zones
         # Create a subnet in each of the region's zones
         region = self.provider.compute.regions.get(self.provider.region_name)
         region = self.provider.compute.regions.get(self.provider.region_name)
         default_sn = None
         default_sn = None
         for i, z in enumerate(region.zones):
         for i, z in enumerate(region.zones):
-            sn = self.create(AWSSubnet.CB_DEFAULT_SUBNET_NAME, default_net,
-                             '10.0.{0}.0/24'.format(i), z.name)
+            sn = self.create(AWSSubnet.CB_DEFAULT_SUBNET_LABEL, default_net,
+                             '10.0.{0}.0/24'.format(i), z)
             if zone and zone == z.name:
             if zone and zone == z.name:
                 default_sn = sn
                 default_sn = sn
         # No specific zone was supplied; return the last created subnet
         # No specific zone was supplied; return the last created subnet
@@ -782,27 +816,27 @@ class AWSRouterService(BaseRouterService):
         return self.svc.get(router_id)
         return self.svc.get(router_id)
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        label = kwargs.pop('label', None)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs, 'label'))
 
 
-        log.debug("Searching for AWS Router Service %s", name)
-        return self.svc.find(filter_name='tag:Name', filter_value=name)
+        log.debug("Searching for AWS Router Service %s", label)
+        return self.svc.find(filter_name='tag:Name', filter_value=label)
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
         return self.svc.list(limit=limit, marker=marker)
 
 
-    def create(self, name, network):
+    def create(self, label, network):
         log.debug("Creating AWS Router Service with the params "
         log.debug("Creating AWS Router Service with the params "
-                  "[name: %s network: %s]", name, network)
-        AWSRouter.assert_valid_resource_name(name)
+                  "[label: %s network: %s]", label, network)
+        AWSRouter.assert_valid_resource_label(label)
 
 
         network_id = network.id if isinstance(network, AWSNetwork) else network
         network_id = network.id if isinstance(network, AWSNetwork) else network
 
 
         cb_router = self.svc.create('create_route_table', VpcId=network_id)
         cb_router = self.svc.create('create_route_table', VpcId=network_id)
-        if name:
-            cb_router.name = name
+        if label:
+            cb_router.label = label
         return cb_router
         return cb_router

+ 34 - 5
cloudbridge/cloud/providers/azure/azure_client.py

@@ -2,6 +2,7 @@ import datetime
 import logging
 import logging
 from io import BytesIO
 from io import BytesIO
 
 
+from azure.common import AzureConflictHttpError
 from azure.common.credentials import ServicePrincipalCredentials
 from azure.common.credentials import ServicePrincipalCredentials
 from azure.cosmosdb.table.tableservice import TableService
 from azure.cosmosdb.table.tableservice import TableService
 from azure.mgmt.compute import ComputeManagementClient
 from azure.mgmt.compute import ComputeManagementClient
@@ -19,7 +20,8 @@ from msrestazure.azure_exceptions import CloudError
 import tenacity
 import tenacity
 
 
 from cloudbridge.cloud.interfaces.exceptions import \
 from cloudbridge.cloud.interfaces.exceptions import \
-    InvalidNameException, ProviderConnectionException, WaitStateException
+    DuplicateResourceException, InvalidLabelException, \
+    ProviderConnectionException, WaitStateException
 
 
 from . import helpers as azure_helpers
 from . import helpers as azure_helpers
 
 
@@ -43,6 +45,10 @@ PUBLIC_IP_RESOURCE_ID = ['/subscriptions/{subscriptionId}/resourceGroups'
                          '/{resourceGroupName}/providers/Microsoft.Network'
                          '/{resourceGroupName}/providers/Microsoft.Network'
                          '/publicIPAddresses/{publicIpAddressName}',
                          '/publicIPAddresses/{publicIpAddressName}',
                          '{publicIpAddressName}']
                          '{publicIpAddressName}']
+ROUTER_RESOURCE_ID = ['/subscriptions/{subscriptionId}'
+                      '/resourceGroups/{resourceGroupName}'
+                      '/providers/Microsoft.Network/routeTables/{routerName}',
+                      '{routerName}']
 SNAPSHOT_RESOURCE_ID = ['/subscriptions/{subscriptionId}/resourceGroups/'
 SNAPSHOT_RESOURCE_ID = ['/subscriptions/{subscriptionId}/resourceGroups/'
                         '{resourceGroupName}/providers/Microsoft.Compute/'
                         '{resourceGroupName}/providers/Microsoft.Compute/'
                         'snapshots/{snapshotName}',
                         'snapshots/{snapshotName}',
@@ -78,6 +84,7 @@ IMAGE_NAME = 'imageName'
 NETWORK_NAME = 'virtualNetworkName'
 NETWORK_NAME = 'virtualNetworkName'
 NETWORK_INTERFACE_NAME = 'networkInterfaceName'
 NETWORK_INTERFACE_NAME = 'networkInterfaceName'
 PUBLIC_IP_NAME = 'publicIpAddressName'
 PUBLIC_IP_NAME = 'publicIpAddressName'
+ROUTER_NAME = 'routerName'
 SNAPSHOT_NAME = 'snapshotName'
 SNAPSHOT_NAME = 'snapshotName'
 SUBNET_NAME = 'subnetName'
 SUBNET_NAME = 'subnetName'
 VM_NAME = 'vmName'
 VM_NAME = 'vmName'
@@ -315,7 +322,7 @@ class AzureClient(object):
                         self._storage_account = \
                         self._storage_account = \
                             self.create_storage_account(self.storage_account,
                             self.create_storage_account(self.storage_account,
                                                         storage_account_params)
                                                         storage_account_params)
-                    except CloudError as cloud_error2:
+                    except CloudError as cloud_error2:  # pragma: no cover
                         if cloud_error2.error.error == "AuthorizationFailed":
                         if cloud_error2.error.error == "AuthorizationFailed":
                             mess = 'The following error was returned by ' \
                             mess = 'The following error was returned by ' \
                                    'Azure:\n%s\n\nThis is likely because the' \
                                    'Azure:\n%s\n\nThis is likely because the' \
@@ -343,7 +350,7 @@ class AzureClient(object):
                                    'azure/azure-resource-manager/resource-' \
                                    'azure/azure-resource-manager/resource-' \
                                    'manager-storage-account-name-errors\n' \
                                    'manager-storage-account-name-errors\n' \
                                    % cloud_error2
                                    % cloud_error2
-                            raise InvalidNameException(mess)
+                            raise InvalidLabelException(mess)
                         else:
                         else:
                             raise cloud_error2
                             raise cloud_error2
                 else:
                 else:
@@ -405,7 +412,18 @@ class AzureClient(object):
         return self.blob_service.list_containers(prefix=prefix)
         return self.blob_service.list_containers(prefix=prefix)
 
 
     def create_container(self, container_name):
     def create_container(self, container_name):
-        self.blob_service.create_container(container_name)
+        try:
+            self.blob_service.create_container(container_name,
+                                               fail_on_exist=True)
+        except AzureConflictHttpError as cloud_error:
+            if cloud_error.error_code == "ContainerAlreadyExists":
+                msg = "The given Bucket name '%s' already exists. Please " \
+                      "use the `get` or `find` method to get a reference to " \
+                      "an existing Bucket, or specify a new Bucket name to " \
+                      "create.\nNote that in Azure, Buckets are contained " \
+                      "in Storage Accounts." % container_name
+                raise DuplicateResourceException(msg)
+
         return self.blob_service.get_container_properties(container_name)
         return self.blob_service.get_container_properties(container_name)
 
 
     def get_container(self, container_name):
     def get_container(self, container_name):
@@ -694,6 +712,14 @@ class AzureClient(object):
             public_ip_addresses.delete(self.resource_group,
             public_ip_addresses.delete(self.resource_group,
                                        public_ip_name).wait()
                                        public_ip_name).wait()
 
 
+    def update_fip_tags(self, fip_id, tags):
+        url_params = azure_helpers.parse_url(PUBLIC_IP_RESOURCE_ID,
+                                             fip_id)
+        fip_name = url_params.get(PUBLIC_IP_NAME)
+        self.network_management_client.public_ip_addresses. \
+            create_or_update(self.resource_group,
+                             fip_name, tags).result()
+
     def list_floating_ips(self):
     def list_floating_ips(self):
         return self.network_management_client.public_ip_addresses.list(
         return self.network_management_client.public_ip_addresses.list(
             self.resource_group)
             self.resource_group)
@@ -891,8 +917,11 @@ class AzureClient(object):
             route_tables.list(self.resource_group)
             route_tables.list(self.resource_group)
 
 
     def get_route_table(self, router_id):
     def get_route_table(self, router_id):
+        url_params = azure_helpers.parse_url(ROUTER_RESOURCE_ID,
+                                             router_id)
+        router_name = url_params.get(ROUTER_NAME)
         return self.network_management_client. \
         return self.network_management_client. \
-            route_tables.get(self.resource_group, router_id)
+            route_tables.get(self.resource_group, router_name)
 
 
     def create_route_table(self, route_table_name, params):
     def create_route_table(self, route_table_name, params):
         return self.network_management_client. \
         return self.network_management_client. \

+ 17 - 17
cloudbridge/cloud/providers/azure/helpers.py

@@ -1,23 +1,23 @@
 from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
 from cloudbridge.cloud.interfaces.exceptions import InvalidValueException
 
 
 
 
-def filter_by_tag(list_items, filters):
-    """
-    This function filter items on the tags
-    :param list_items:
-    :param filters:
-    :return:
-    """
-    filtered_list = []
-    if filters:
-        for obj in list_items:
-            for key in filters:
-                if obj.tags and filters[key] in obj.tags.get(key, ''):
-                    filtered_list.append(obj)
-
-        return filtered_list
-    else:
-        return list_items
+# def filter_by_tag(list_items, filters):
+#     """
+#     This function filter items on the tags
+#     :param list_items:
+#     :param filters:
+#     :return:
+#     """
+#     filtered_list = []
+#     if filters:
+#         for obj in list_items:
+#             for key in filters:
+#                 if obj.tags and filters[key] in obj.tags.get(key, ''):
+#                     filtered_list.append(obj)
+#
+#         return filtered_list
+#     else:
+#         return list_items
 
 
 
 
 def parse_url(template_urls, original_url):
 def parse_url(template_urls, original_url):

+ 1 - 1
cloudbridge/cloud/providers/azure/provider.py

@@ -128,7 +128,7 @@ class AzureCloudProvider(BaseCloudProvider):
                     self._azure_client.\
                     self._azure_client.\
                         create_resource_group(self.resource_group,
                         create_resource_group(self.resource_group,
                                               resource_group_params)
                                               resource_group_params)
-                except CloudError as cloud_error2:
+                except CloudError as cloud_error2:  # pragma: no cover
                     if cloud_error2.error.error == "AuthorizationFailed":
                     if cloud_error2.error.error == "AuthorizationFailed":
                         mess = 'The following error was returned by Azure:\n' \
                         mess = 'The following error was returned by Azure:\n' \
                                '%s\n\nThis is likely because the Role' \
                                '%s\n\nThis is likely because the Role' \

+ 182 - 143
cloudbridge/cloud/providers/azure/resources.py

@@ -3,7 +3,7 @@ DataTypes used by this provider
 """
 """
 import collections
 import collections
 import logging
 import logging
-import uuid
+from uuid import uuid4
 
 
 from azure.common import AzureException
 from azure.common import AzureException
 from azure.mgmt.devtestlabs.models import GalleryImageReference
 from azure.mgmt.devtestlabs.models import GalleryImageReference
@@ -52,12 +52,16 @@ class AzureVMFirewall(BaseVMFirewall):
 
 
     @property
     @property
     def name(self):
     def name(self):
-        return self._vm_firewall.tags.get('Name', self._vm_firewall.name)
+        return self._vm_firewall.name
 
 
-    @name.setter
-    def name(self, value):
-        self.assert_valid_resource_name(value)
-        self._vm_firewall.tags.update(Name=value)
+    @property
+    def label(self):
+        return self._vm_firewall.tags.get('Label', None)
+
+    @label.setter
+    def label(self, value):
+        self.assert_valid_resource_label(value)
+        self._vm_firewall.tags.update(Label=value or "")
         self._provider.azure_client.update_vm_firewall_tags(
         self._provider.azure_client.update_vm_firewall_tags(
             self.id, self._vm_firewall.tags)
             self.id, self._vm_firewall.tags)
 
 
@@ -67,7 +71,7 @@ class AzureVMFirewall(BaseVMFirewall):
 
 
     @description.setter
     @description.setter
     def description(self, value):
     def description(self, value):
-        self._vm_firewall.tags.update(Description=value)
+        self._vm_firewall.tags.update(Description=value or "")
         self._provider.azure_client.\
         self._provider.azure_client.\
             update_vm_firewall_tags(self.id,
             update_vm_firewall_tags(self.id,
                                     self._vm_firewall.tags)
                                     self._vm_firewall.tags)
@@ -141,7 +145,7 @@ class AzureVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
             cidr = '0.0.0.0/0'
             cidr = '0.0.0.0/0'
 
 
         count = len(self.firewall._vm_firewall.security_rules) + 1
         count = len(self.firewall._vm_firewall.security_rules) + 1
-        rule_name = "Rule - " + str(count)
+        rule_name = "cb-rule-" + str(count)
         priority = 1000 + count
         priority = 1000 + count
         destination_port_range = str(from_port) + "-" + str(to_port)
         destination_port_range = str(from_port) + "-" + str(to_port)
         source_port_range = '*'
         source_port_range = '*'
@@ -177,15 +181,15 @@ class AzureVMFirewallRule(BaseVMFirewallRule):
     def id(self):
     def id(self):
         return self._rule.id
         return self._rule.id
 
 
+    @property
+    def name(self):
+        return self._rule.name
+
     @property
     @property
     def direction(self):
     def direction(self):
         return (TrafficDirection.INBOUND if self._rule.direction == "Inbound"
         return (TrafficDirection.INBOUND if self._rule.direction == "Inbound"
                 else TrafficDirection.OUTBOUND)
                 else TrafficDirection.OUTBOUND)
 
 
-    @property
-    def name(self):
-        return self._rule.name
-
     @property
     @property
     def protocol(self):
     def protocol(self):
         return self._rule.protocol
         return self._rule.protocol
@@ -240,9 +244,6 @@ class AzureBucketObject(BaseBucketObject):
 
 
     @property
     @property
     def name(self):
     def name(self):
-        """
-        Get this object's name.
-        """
         return self._key.name
         return self._key.name
 
 
     @property
     @property
@@ -267,7 +268,7 @@ class AzureBucketObject(BaseBucketObject):
         iterable.
         iterable.
         """
         """
         content_stream = self._provider.azure_client. \
         content_stream = self._provider.azure_client. \
-            get_blob_content(self._container.name, self._key.name)
+            get_blob_content(self._container.id, self._key.name)
         if content_stream:
         if content_stream:
             content_stream.seek(0)
             content_stream.seek(0)
         return content_stream
         return content_stream
@@ -279,7 +280,7 @@ class AzureBucketObject(BaseBucketObject):
         """
         """
         try:
         try:
             self._provider.azure_client.create_blob_from_text(
             self._provider.azure_client.create_blob_from_text(
-                self._container.name, self.name, data)
+                self._container.id, self.id, data)
             return True
             return True
         except AzureException as azureEx:
         except AzureException as azureEx:
             log.exception(azureEx)
             log.exception(azureEx)
@@ -291,7 +292,7 @@ class AzureBucketObject(BaseBucketObject):
         """
         """
         try:
         try:
             self._provider.azure_client.create_blob_from_file(
             self._provider.azure_client.create_blob_from_file(
-                self._container.name, self.name, path)
+                self._container.id, self.id, path)
             return True
             return True
         except AzureException as azureEx:
         except AzureException as azureEx:
             log.exception(azureEx)
             log.exception(azureEx)
@@ -304,19 +305,19 @@ class AzureBucketObject(BaseBucketObject):
         :rtype: bool
         :rtype: bool
         :return: True if successful
         :return: True if successful
         """
         """
-        self._provider.azure_client.delete_blob(self._container.name,
-                                                self.name)
+        self._provider.azure_client.delete_blob(self._container.id,
+                                                self.id)
 
 
     def generate_url(self, expires_in):
     def generate_url(self, expires_in):
         """
         """
         Generate a URL to this object.
         Generate a URL to this object.
         """
         """
         return self._provider.azure_client.get_blob_url(
         return self._provider.azure_client.get_blob_url(
-            self._container.name, self.name, expires_in)
+            self._container.id, self.id, expires_in)
 
 
     def refresh(self):
     def refresh(self):
         self._key = self._provider.azure_client.get_blob(
         self._key = self._provider.azure_client.get_blob(
-            self._container.name, self._key.name)
+            self._container.id, self._key.id)
 
 
 
 
 class AzureBucket(BaseBucket):
 class AzureBucket(BaseBucket):
@@ -363,7 +364,8 @@ class AzureBucketContainer(BaseBucketContainer):
         Retrieve a given object from this bucket.
         Retrieve a given object from this bucket.
         """
         """
         try:
         try:
-            obj = self._provider.azure_client.get_blob(self.bucket.name, key)
+            obj = self._provider.azure_client.get_blob(self.bucket.name,
+                                                       key)
             return AzureBucketObject(self._provider, self.bucket, obj)
             return AzureBucketObject(self._provider, self.bucket, obj)
         except AzureException as azureEx:
         except AzureException as azureEx:
             log.exception(azureEx)
             log.exception(azureEx)
@@ -433,28 +435,31 @@ class AzureVolume(BaseVolume):
     def resource_id(self):
     def resource_id(self):
         return self._volume.id
         return self._volume.id
 
 
+    @property
+    def name(self):
+        return self._volume.name
+
     @property
     @property
     def tags(self):
     def tags(self):
         return self._volume.tags
         return self._volume.tags
 
 
     @property
     @property
-    def name(self):
+    def label(self):
         """
         """
-        Get the volume name.
+        Get the volume label.
 
 
-        .. note:: an instance must have a (case sensitive) tag ``Name``
+        .. note:: an instance must have a (case sensitive) tag ``Label``
         """
         """
-        return self._volume.tags.get('Name', self._volume.name)
+        return self._volume.tags.get('Label', None)
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
+    def label(self, value):
         """
         """
-        Set the volume name.
+        Set the volume label.
         """
         """
-        # self._volume.name = value
-        self.assert_valid_resource_name(value)
-        self._volume.tags.update(Name=value)
+        self.assert_valid_resource_label(value)
+        self._volume.tags.update(Label=value or "")
         self._provider.azure_client. \
         self._provider.azure_client. \
             update_disk_tags(self.id,
             update_disk_tags(self.id,
                              self._volume.tags)
                              self._volume.tags)
@@ -465,7 +470,7 @@ class AzureVolume(BaseVolume):
 
 
     @description.setter
     @description.setter
     def description(self, value):
     def description(self, value):
-        self._volume.tags.update(Description=value)
+        self._volume.tags.update(Description=value or "")
         self._provider.azure_client. \
         self._provider.azure_client. \
             update_disk_tags(self.id,
             update_disk_tags(self.id,
                              self._volume.tags)
                              self._volume.tags)
@@ -531,11 +536,12 @@ class AzureVolume(BaseVolume):
                     vm.storage_profile.data_disks.remove(item)
                     vm.storage_profile.data_disks.remove(item)
                     self._provider.azure_client.update_vm(vm.id, vm)
                     self._provider.azure_client.update_vm(vm.id, vm)
 
 
-    def create_snapshot(self, name, description=None):
+    def create_snapshot(self, label, description=None):
         """
         """
         Create a snapshot of this Volume.
         Create a snapshot of this Volume.
         """
         """
-        return self._provider.storage.snapshots.create(name, self)
+        return self._provider.storage.snapshots.create(label, self,
+                                                       description)
 
 
     def delete(self):
     def delete(self):
         """
         """
@@ -587,27 +593,31 @@ class AzureSnapshot(BaseSnapshot):
     def id(self):
     def id(self):
         return self._snapshot.id
         return self._snapshot.id
 
 
+    @property
+    def name(self):
+        return self._snapshot.name
+
     @property
     @property
     def resource_id(self):
     def resource_id(self):
         return self._snapshot.id
         return self._snapshot.id
 
 
     @property
     @property
-    def name(self):
+    def label(self):
         """
         """
-        Get the snapshot name.
+        Get the snapshot label.
 
 
-        .. note:: an instance must have a (case sensitive) tag ``Name``
+        .. note:: an instance must have a (case sensitive) tag ``Label``
         """
         """
-        return self._snapshot.tags.get('Name', self._snapshot.name)
+        return self._snapshot.tags.get('Label', None)
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
+    def label(self, value):
         """
         """
-        Set the snapshot name.
+        Set the snapshot label.
         """
         """
-        self.assert_valid_resource_name(value)
-        self._snapshot.tags.update(Name=value)
+        self.assert_valid_resource_label(value)
+        self._snapshot.tags.update(Label=value or "")
         self._provider.azure_client. \
         self._provider.azure_client. \
             update_snapshot_tags(self.id,
             update_snapshot_tags(self.id,
                                  self._snapshot.tags)
                                  self._snapshot.tags)
@@ -618,7 +628,7 @@ class AzureSnapshot(BaseSnapshot):
 
 
     @description.setter
     @description.setter
     def description(self, value):
     def description(self, value):
-        self._snapshot.tags.update(Description=value)
+        self._snapshot.tags.update(Description=value or "")
         self._provider.azure_client. \
         self._provider.azure_client. \
             update_snapshot_tags(self.id,
             update_snapshot_tags(self.id,
                                  self._snapshot.tags)
                                  self._snapshot.tags)
@@ -667,8 +677,7 @@ class AzureSnapshot(BaseSnapshot):
         Create a new Volume from this Snapshot.
         Create a new Volume from this Snapshot.
         """
         """
         return self._provider.storage.volumes. \
         return self._provider.storage.volumes. \
-            create(self.name, self.size,
-                   zone=placement, snapshot=self)
+            create(self.name, self.size, zone=placement, snapshot=self)
 
 
 
 
 class AzureMachineImage(BaseMachineImage):
 class AzureMachineImage(BaseMachineImage):
@@ -698,39 +707,40 @@ class AzureMachineImage(BaseMachineImage):
         :rtype: ``str``
         :rtype: ``str``
         :return: ID for this instance as returned by the cloud middleware.
         :return: ID for this instance as returned by the cloud middleware.
         """
         """
-        if isinstance(self._image, GalleryImageReference):
+        if self.is_gallery_image:
             return azure_helpers.generate_urn(self._image)
             return azure_helpers.generate_urn(self._image)
         else:
         else:
             return self._image.id
             return self._image.id
 
 
+    @property
+    def name(self):
+        if self.is_gallery_image:
+            return azure_helpers.generate_urn(self._image)
+        else:
+            return self._image.name
+
     @property
     @property
     def resource_id(self):
     def resource_id(self):
-        if isinstance(self._image, GalleryImageReference):
+        if self.is_gallery_image:
             return azure_helpers.generate_urn(self._image)
             return azure_helpers.generate_urn(self._image)
         else:
         else:
             return self._image.id
             return self._image.id
 
 
     @property
     @property
-    def name(self):
-        """
-        Get the image name.
-
-        :rtype: ``str``
-        :return: Name for this image as returned by the cloud middleware.
-        """
-        if isinstance(self._image, GalleryImageReference):
+    def label(self):
+        if self.is_gallery_image:
             return azure_helpers.generate_urn(self._image)
             return azure_helpers.generate_urn(self._image)
         else:
         else:
-            return self._image.tags.get('Name', self._image.name)
+            return self._image.tags.get('Label', None)
 
 
-    @name.setter
-    def name(self, value):
+    @label.setter
+    def label(self, value):
         """
         """
-        Set the image name.
+        Set the image label when it is a private image.
         """
         """
-        if not isinstance(self._image, GalleryImageReference):
-            self.assert_valid_resource_name(value)
-            self._image.tags.update(Name=value)
+        if not self.is_gallery_image:
+            self.assert_valid_resource_label(value)
+            self._image.tags.update(Label=value or "")
             self._provider.azure_client. \
             self._provider.azure_client. \
                 update_image_tags(self.id, self._image.tags)
                 update_image_tags(self.id, self._image.tags)
 
 
@@ -742,7 +752,7 @@ class AzureMachineImage(BaseMachineImage):
         :rtype: ``str``
         :rtype: ``str``
         :return: Description for this image as returned by the cloud middleware
         :return: Description for this image as returned by the cloud middleware
         """
         """
-        if isinstance(self._image, GalleryImageReference):
+        if self.is_gallery_image:
             return 'Public gallery image from the Azure Marketplace: '\
             return 'Public gallery image from the Azure Marketplace: '\
                     + self.name
                     + self.name
         else:
         else:
@@ -753,8 +763,8 @@ class AzureMachineImage(BaseMachineImage):
         """
         """
         Set the image description.
         Set the image description.
         """
         """
-        if not isinstance(self._image, GalleryImageReference):
-            self._image.tags.update(Description=value)
+        if not self.is_gallery_image:
+            self._image.tags.update(Description=value or "")
             self._provider.azure_client. \
             self._provider.azure_client. \
                 update_image_tags(self.id, self._image.tags)
                 update_image_tags(self.id, self._image.tags)
 
 
@@ -769,7 +779,7 @@ class AzureMachineImage(BaseMachineImage):
         :rtype: ``int``
         :rtype: ``int``
         :return: The minimum disk size needed by this image
         :return: The minimum disk size needed by this image
         """
         """
-        if isinstance(self._image, GalleryImageReference):
+        if self.is_gallery_image:
             return 0
             return 0
         else:
         else:
             return self._image.storage_profile.os_disk.disk_size_gb or 0
             return self._image.storage_profile.os_disk.disk_size_gb or 0
@@ -778,12 +788,12 @@ class AzureMachineImage(BaseMachineImage):
         """
         """
         Delete this image
         Delete this image
         """
         """
-        if not isinstance(self._image, GalleryImageReference):
+        if not self.is_gallery_image:
             self._provider.azure_client.delete_image(self.id)
             self._provider.azure_client.delete_image(self.id)
 
 
     @property
     @property
     def state(self):
     def state(self):
-        if isinstance(self._image, GalleryImageReference):
+        if self.is_gallery_image:
             return MachineImageState.AVAILABLE
             return MachineImageState.AVAILABLE
         else:
         else:
             return AzureMachineImage.IMAGE_STATE_MAP.get(
             return AzureMachineImage.IMAGE_STATE_MAP.get(
@@ -802,7 +812,7 @@ class AzureMachineImage(BaseMachineImage):
         Refreshes the state of this instance by re-querying the cloud provider
         Refreshes the state of this instance by re-querying the cloud provider
         for its latest state.
         for its latest state.
         """
         """
-        if not isinstance(self._image, dict):
+        if not self.is_gallery_image:
             try:
             try:
                 self._image = self._provider.azure_client.get_image(self.id)
                 self._image = self._provider.azure_client.get_image(self.id)
                 self._state = self._image.provisioning_state
                 self._state = self._image.provisioning_state
@@ -824,11 +834,7 @@ class AzureGatewayContainer(BaseGatewayContainer):
                                                       network)
                                                       network)
 
 
     def get_or_create_inet_gateway(self, name=None):
     def get_or_create_inet_gateway(self, name=None):
-        if name:
-            AzureInternetGateway.assert_valid_resource_name(name)
         gateway = AzureInternetGateway(self._provider, None, self._network)
         gateway = AzureInternetGateway(self._provider, None, self._network)
-        if name:
-            gateway.name = name
         return gateway
         return gateway
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
@@ -856,27 +862,31 @@ class AzureNetwork(BaseNetwork):
     def id(self):
     def id(self):
         return self._network.id
         return self._network.id
 
 
+    @property
+    def name(self):
+        return self._network.name
+
     @property
     @property
     def resource_id(self):
     def resource_id(self):
         return self._network.id
         return self._network.id
 
 
     @property
     @property
-    def name(self):
+    def label(self):
         """
         """
-        Get the network name.
+        Get the network label.
 
 
-        .. note:: the network must have a (case sensitive) tag ``Name``
+        .. note:: the network must have a (case sensitive) tag ``Label``
         """
         """
-        return self._network.tags.get('Name', self._network.name)
+        return self._network.tags.get('Label', None)
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
+    def label(self, value):
         """
         """
-        Set the network name.
+        Set the network label.
         """
         """
-        self.assert_valid_resource_name(value)
-        self._network.tags.update(Name=value)
+        self.assert_valid_resource_label(value)
+        self._network.tags.update(Label=value or "")
         self._provider.azure_client. \
         self._provider.azure_client. \
             update_network_tags(self.id, self._network)
             update_network_tags(self.id, self._network)
 
 
@@ -930,16 +940,16 @@ class AzureNetwork(BaseNetwork):
         """
         """
         return self._provider.networking.subnets.list(network=self.id)
         return self._provider.networking.subnets.list(network=self.id)
 
 
-    def create_subnet(self, cidr_block, name=None, zone=None):
+    def create_subnet(self, label, cidr_block, zone=None):
         """
         """
         Create the subnet with cidr_block
         Create the subnet with cidr_block
         :param cidr_block:
         :param cidr_block:
-        :param name:
+        :param label:
         :param zone:
         :param zone:
         :return:
         :return:
         """
         """
         return self._provider.networking.subnets. \
         return self._provider.networking.subnets. \
-            create(network=self.id, cidr_block=cidr_block, name=name)
+            create(label=label, network=self.id, cidr_block=cidr_block)
 
 
     @property
     @property
     def gateways(self):
     def gateways(self):
@@ -967,14 +977,15 @@ class AzureFloatingIPContainer(BaseFloatingIPContainer):
                                      limit=limit, marker=marker)
                                      limit=limit, marker=marker)
 
 
     def create(self):
     def create(self):
-        public_ip_address_name = "{0}-{1}".format(
-            'public_ip', uuid.uuid4().hex[:6])
         public_ip_parameters = {
         public_ip_parameters = {
             'location': self._provider.azure_client.region_name,
             'location': self._provider.azure_client.region_name,
             'public_ip_allocation_method': 'Static'
             'public_ip_allocation_method': 'Static'
         }
         }
+
+        public_ip_name = 'cb-fip-' + uuid4().hex[:6]
+
         floating_ip = self._provider.azure_client.\
         floating_ip = self._provider.azure_client.\
-            create_floating_ip(public_ip_address_name, public_ip_parameters)
+            create_floating_ip(public_ip_name, public_ip_parameters)
         return AzureFloatingIP(self._provider, floating_ip, self._network_id)
         return AzureFloatingIP(self._provider, floating_ip, self._network_id)
 
 
 
 
@@ -989,6 +1000,10 @@ class AzureFloatingIP(BaseFloatingIP):
     def id(self):
     def id(self):
         return self._ip.id
         return self._ip.id
 
 
+    @property
+    def name(self):
+        return self._ip.ip_address
+
     @property
     @property
     def resource_id(self):
     def resource_id(self):
         return self._ip.id
         return self._ip.id
@@ -1093,23 +1108,46 @@ class AzureSubnet(BaseSubnet):
         super(AzureSubnet, self).__init__(provider)
         super(AzureSubnet, self).__init__(provider)
         self._subnet = subnet
         self._subnet = subnet
         self._state = self._subnet.provisioning_state
         self._state = self._subnet.provisioning_state
+        self._tag_name = None
 
 
     @property
     @property
     def id(self):
     def id(self):
         return self._subnet.id
         return self._subnet.id
 
 
     @property
     @property
-    def resource_id(self):
-        return self._subnet.id
+    def name(self):
+        net_name = self.network_id.split('/')[-1]
+        sn_name = self._subnet.name
+        return '{0}/{1}'.format(net_name, sn_name)
 
 
     @property
     @property
-    def name(self):
-        """
-        Get the subnet name.
+    def label(self):
+        # Although Subnet doesn't support labels, we use the parent Network's
+        # tags to track the subnet's labels
+        network = self._network
+        az_network = network._network
+        return az_network.tags.get(self.tag_name, None)
 
 
-        .. note:: the subnet must have a (case sensitive) tag ``Name``
-        """
-        return self._subnet.name
+    @label.setter
+    # pylint:disable=arguments-differ
+    def label(self, value):
+        self.assert_valid_resource_label(value)
+        network = self._network
+        az_network = network._network
+        kwargs = {self.tag_name: value or ""}
+        az_network.tags.update(**kwargs)
+        self._provider.azure_client.update_network_tags(
+            az_network.id, az_network)
+
+    @property
+    def tag_name(self):
+        if not self._tag_name:
+            self._tag_name = 'SubnetLabel_' + self._subnet.name
+        return self._tag_name
+
+    @property
+    def resource_id(self):
+        return self._subnet.id
 
 
     @property
     @property
     def zone(self):
     def zone(self):
@@ -1203,27 +1241,34 @@ class AzureInstance(BaseInstance):
         """
         """
         return self._vm.id
         return self._vm.id
 
 
+    @property
+    def name(self):
+        """
+        Get the instance name.
+        """
+        return self._vm.name
+
     @property
     @property
     def resource_id(self):
     def resource_id(self):
         return self._vm.id
         return self._vm.id
 
 
     @property
     @property
-    def name(self):
+    def label(self):
         """
         """
-        Get the instance name.
+        Get the instance label.
 
 
-        .. note:: an instance must have a (case sensitive) tag ``Name``
+        .. note:: an instance must have a (case sensitive) tag ``Label``
         """
         """
-        return self._vm.tags.get('Name', self._vm.name)
+        return self._vm.tags.get('Label', None)
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
+    def label(self, value):
         """
         """
-        Set the instance name.
+        Set the instance label.
         """
         """
-        self.assert_valid_resource_name(value)
-        self._vm.tags.update(Name=value)
+        self.assert_valid_resource_label(value)
+        self._vm.tags.update(Label=value or "")
         self._provider.azure_client. \
         self._provider.azure_client. \
             update_vm_tags(self.id, self._vm)
             update_vm_tags(self.id, self._vm)
 
 
@@ -1297,10 +1342,13 @@ class AzureInstance(BaseInstance):
         """
         """
         # Not tested for resource group images
         # Not tested for resource group images
         reference_dict = self._vm.storage_profile.image_reference.as_dict()
         reference_dict = self._vm.storage_profile.image_reference.as_dict()
-        return ':'.join([reference_dict['publisher'],
-                         reference_dict['offer'],
-                         reference_dict['sku'],
-                         reference_dict['version']])
+        if reference_dict.get('publisher'):
+            return ':'.join([reference_dict['publisher'],
+                             reference_dict['offer'],
+                             reference_dict['sku'],
+                             reference_dict['version']])
+        else:
+            return reference_dict['id']
 
 
     @property
     @property
     def zone_id(self):
     def zone_id(self):
@@ -1335,13 +1383,13 @@ class AzureInstance(BaseInstance):
                 if nic.network_security_group]
                 if nic.network_security_group]
 
 
     @property
     @property
-    def key_pair_name(self):
+    def key_pair_id(self):
         """
         """
         Get the name of the key pair associated with this instance.
         Get the name of the key pair associated with this instance.
         """
         """
         return self._vm.tags.get('Key_Pair')
         return self._vm.tags.get('Key_Pair')
 
 
-    def create_image(self, name, private_key_path=None):
+    def create_image(self, label, private_key_path=None):
         """
         """
         Create a new image based on this instance. Documentation for create
         Create a new image based on this instance. Documentation for create
         image available at https://docs.microsoft.com/en-us/azure/virtual-ma
         image available at https://docs.microsoft.com/en-us/azure/virtual-ma
@@ -1354,7 +1402,8 @@ class AzureInstance(BaseInstance):
         CloudBridge interface to pass the private key file path
         CloudBridge interface to pass the private key file path
         """
         """
 
 
-        self.assert_valid_resource_name(name)
+        self.assert_valid_resource_label(label)
+        name = self._generate_name_from_label(label, 'cb-img')
 
 
         if not self._state == 'VM generalized':
         if not self._state == 'VM generalized':
             if not self._state == 'VM running':
             if not self._state == 'VM running':
@@ -1370,10 +1419,11 @@ class AzureInstance(BaseInstance):
             'source_virtual_machine': {
             'source_virtual_machine': {
                 'id': self.resource_id
                 'id': self.resource_id
             },
             },
-            'tags': {'Name': name}
+            'tags': {'Label': label}
         }
         }
 
 
-        image = self._provider.azure_client.create_image(name, create_params)
+        image = self._provider.azure_client.create_image(name,
+                                                         create_params)
         return AzureMachineImage(self._provider, image)
         return AzureMachineImage(self._provider, image)
 
 
     def _deprovision(self, private_key_path):
     def _deprovision(self, private_key_path):
@@ -1592,6 +1642,10 @@ class AzureRouter(BaseRouter):
 
 
     @property
     @property
     def id(self):
     def id(self):
+        return self._route_table.id
+
+    @property
+    def name(self):
         return self._route_table.name
         return self._route_table.name
 
 
     @property
     @property
@@ -1599,22 +1653,22 @@ class AzureRouter(BaseRouter):
         return self._route_table.id
         return self._route_table.id
 
 
     @property
     @property
-    def name(self):
+    def label(self):
         """
         """
-        Get the router name.
+        Get the router label.
 
 
-        .. note:: the router must have a (case sensitive) tag ``Name``
+        .. note:: the router must have a (case sensitive) tag ``Label``
         """
         """
-        return self._route_table.tags.get('Name', self._route_table.name)
+        return self._route_table.tags.get('Label', None)
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
+    def label(self, value):
         """
         """
-        Set the router name.
+        Set the router label.
         """
         """
-        self.assert_valid_resource_name(value)
-        self._route_table.tags.update(Name=value)
+        self.assert_valid_resource_label(value)
+        self._route_table.tags.update(Label=value or "")
         self._provider.azure_client. \
         self._provider.azure_client. \
             update_route_table_tags(self._route_table.name,
             update_route_table_tags(self._route_table.name,
                                     self._route_table)
                                     self._route_table)
@@ -1660,7 +1714,6 @@ class AzureInternetGateway(BaseInternetGateway):
     def __init__(self, provider, gateway, gateway_net):
     def __init__(self, provider, gateway, gateway_net):
         super(AzureInternetGateway, self).__init__(provider)
         super(AzureInternetGateway, self).__init__(provider)
         self._gateway = gateway
         self._gateway = gateway
-        self._name = None
         self._network_id = gateway_net.id if isinstance(
         self._network_id = gateway_net.id if isinstance(
             gateway_net, AzureNetwork) else gateway_net
             gateway_net, AzureNetwork) else gateway_net
         self._state = ''
         self._state = ''
@@ -1669,25 +1722,11 @@ class AzureInternetGateway(BaseInternetGateway):
 
 
     @property
     @property
     def id(self):
     def id(self):
-        return self._name
+        return "cb-gateway-wrapper"
 
 
     @property
     @property
     def name(self):
     def name(self):
-        """
-        Get the gateway name.
-
-        .. note:: the gateway must have a (case sensitive) tag ``Name``
-        """
-        return self._name
-
-    @name.setter
-    # pylint:disable=arguments-differ
-    def name(self, value):
-        """
-        Set the router name.
-        """
-        self.assert_valid_resource_name(value)
-        self._name = value
+        return "cb-gateway-wrapper"
 
 
     def refresh(self):
     def refresh(self):
         pass
         pass

+ 145 - 133
cloudbridge/cloud/providers/azure/services.py

@@ -22,7 +22,6 @@ from cloudbridge.cloud.interfaces.exceptions import \
 from cloudbridge.cloud.interfaces.resources import MachineImage, \
 from cloudbridge.cloud.interfaces.resources import MachineImage, \
     Network, PlacementZone, Snapshot, Subnet, VMFirewall, VMType, Volume
     Network, PlacementZone, Snapshot, Subnet, VMFirewall, VMType, Volume
 
 
-from . import helpers as azure_helpers
 from .resources import AzureBucket, \
 from .resources import AzureBucket, \
     AzureInstance, AzureKeyPair, \
     AzureInstance, AzureKeyPair, \
     AzureLaunchConfig, AzureMachineImage, AzureNetwork, \
     AzureLaunchConfig, AzureMachineImage, AzureNetwork, \
@@ -67,15 +66,17 @@ class AzureVMFirewallService(BaseVMFirewallService):
                for fw in self.provider.azure_client.list_vm_firewall()]
                for fw in self.provider.azure_client.list_vm_firewall()]
         return ClientPagedResultList(self.provider, fws, limit, marker)
         return ClientPagedResultList(self.provider, fws, limit, marker)
 
 
-    def create(self, name, description, network_id=None):
-        AzureVMFirewall.assert_valid_resource_name(name)
+    def create(self, label, description=None, network_id=None):
+        AzureVMFirewall.assert_valid_resource_label(label)
+        name = AzureVMFirewall._generate_name_from_label(label, "cb-fw")
         parameters = {"location": self.provider.region_name,
         parameters = {"location": self.provider.region_name,
-                      'tags': {'Name': name}}
+                      "tags": {'Label': label}}
 
 
         if description:
         if description:
             parameters['tags'].update(Description=description)
             parameters['tags'].update(Description=description)
 
 
-        fw = self.provider.azure_client.create_vm_firewall(name, parameters)
+        fw = self.provider.azure_client.create_vm_firewall(name,
+                                                           parameters)
 
 
         # Add default rules to negate azure default rules.
         # Add default rules to negate azure default rules.
         # See: https://github.com/CloudVE/cloudbridge/issues/106
         # See: https://github.com/CloudVE/cloudbridge/issues/106
@@ -86,7 +87,7 @@ class AzureVMFirewallService(BaseVMFirewallService):
             # only 0-4096 are allowed for custom rules
             # only 0-4096 are allowed for custom rules
             rule.priority = rule.priority - 61440
             rule.priority = rule.priority - 61440
             rule.access = "Deny"
             rule.access = "Deny"
-            self._provider.azure_client.create_vm_firewall_rule(
+            self.provider.azure_client.create_vm_firewall_rule(
                 fw.id, rule_name, rule)
                 fw.id, rule_name, rule)
 
 
         # Add a new custom rule allowing all outbound traffic to the internet
         # Add a new custom rule allowing all outbound traffic to the internet
@@ -98,7 +99,7 @@ class AzureVMFirewallService(BaseVMFirewallService):
                       "destination_address_prefix": "Internet",
                       "destination_address_prefix": "Internet",
                       "access": "Allow",
                       "access": "Allow",
                       "direction": "Outbound"}
                       "direction": "Outbound"}
-        result = self._provider.azure_client.create_vm_firewall_rule(
+        result = self.provider.azure_client.create_vm_firewall_rule(
             fw.id, "cb-default-internet-outbound", parameters)
             fw.id, "cb-default-internet-outbound", parameters)
         fw.security_rules.append(result)
         fw.security_rules.append(result)
 
 
@@ -106,18 +107,18 @@ class AzureVMFirewallService(BaseVMFirewallService):
         return cb_fw
         return cb_fw
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['label', 'name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
 
-        filters = {'Name': name}
-        fws = [AzureVMFirewall(self.provider, vm_firewall)
-               for vm_firewall in azure_helpers.filter_by_tag(
-               self.provider.azure_client.list_vm_firewall(), filters)]
-        return ClientPagedResultList(self.provider, fws)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
 
     def delete(self, group_id):
     def delete(self, group_id):
         self.provider.azure_client.delete_vm_firewall(group_id)
         self.provider.azure_client.delete_vm_firewall(group_id)
@@ -153,16 +154,18 @@ class AzureKeyPairService(BaseKeyPairService):
                                      data=results)
                                      data=results)
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
 
-        key_pair = self.get(name)
         return ClientPagedResultList(self.provider,
         return ClientPagedResultList(self.provider,
-                                     [key_pair] if key_pair else [])
+                                     matches if matches else [])
 
 
     def create(self, name, public_key_material=None):
     def create(self, name, public_key_material=None):
         AzureKeyPair.assert_valid_resource_name(name)
         AzureKeyPair.assert_valid_resource_name(name)
@@ -207,17 +210,18 @@ class AzureBucketService(BaseBucketService):
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
 
-        buckets = [AzureBucket(self.provider, bucket)
-                   for bucket in
-                   self.provider.azure_client.list_containers(prefix=name)]
-        return ClientPagedResultList(self.provider, buckets)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
         """
         """
@@ -233,7 +237,7 @@ class AzureBucketService(BaseBucketService):
         Create a new bucket.
         Create a new bucket.
         """
         """
         AzureBucket.assert_valid_resource_name(name)
         AzureBucket.assert_valid_resource_name(name)
-        bucket = self.provider.azure_client.create_container(name.lower())
+        bucket = self.provider.azure_client.create_container(name)
         return AzureBucket(self.provider, bucket)
         return AzureBucket(self.provider, bucket)
 
 
 
 
@@ -276,18 +280,18 @@ class AzureVolumeService(BaseVolumeService):
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name', 'label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
 
-        filters = {'Name': name}
-        cb_vols = [AzureVolume(self.provider, volume)
-                   for volume in azure_helpers.filter_by_tag(
-                   self.provider.azure_client.list_disks(), filters)]
-        return ClientPagedResultList(self.provider, cb_vols)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
         """
         """
@@ -298,18 +302,22 @@ class AzureVolumeService(BaseVolumeService):
         return ClientPagedResultList(self.provider, cb_vols,
         return ClientPagedResultList(self.provider, cb_vols,
                                      limit=limit, marker=marker)
                                      limit=limit, marker=marker)
 
 
-    def create(self, name, size, zone=None, snapshot=None, description=None):
+    def create(self, label, size, zone=None, description=None,
+               snapshot=None):
         """
         """
         Creates a new volume.
         Creates a new volume.
         """
         """
-        AzureVolume.assert_valid_resource_name(name)
+        AzureVolume.assert_valid_resource_label(label)
+        disk_name = AzureVolume._generate_name_from_label(label, "cb-vol")
+        tags = {'Label': label}
+
         zone_id = zone.id if isinstance(zone, PlacementZone) else zone
         zone_id = zone.id if isinstance(zone, PlacementZone) else zone
         snapshot = (self.provider.storage.snapshots.get(snapshot)
         snapshot = (self.provider.storage.snapshots.get(snapshot)
                     if snapshot and isinstance(snapshot, str) else snapshot)
                     if snapshot and isinstance(snapshot, str) else snapshot)
-        disk_name = "{0}-{1}".format(name, uuid.uuid4().hex[:6])
-        tags = {'Name': name}
+
         if description:
         if description:
             tags.update(Description=description)
             tags.update(Description=description)
+
         if snapshot:
         if snapshot:
             params = {
             params = {
                 'location':
                 'location':
@@ -332,7 +340,8 @@ class AzureVolumeService(BaseVolumeService):
                 'creation_data': {
                 'creation_data': {
                     'create_option': DiskCreateOption.empty
                     'create_option': DiskCreateOption.empty
                 },
                 },
-                'tags': tags}
+                'tags': tags
+            }
 
 
             disk = self.provider.azure_client.create_empty_disk(disk_name,
             disk = self.provider.azure_client.create_empty_disk(disk_name,
                                                                 params)
                                                                 params)
@@ -360,18 +369,18 @@ class AzureSnapshotService(BaseSnapshotService):
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name', 'label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
 
-        filters = {'Name': name}
-        cb_snapshots = [AzureSnapshot(self.provider, snapshot)
-                        for snapshot in azure_helpers.filter_by_tag(
-                        self.provider.azure_client.list_snapshots(), filters)]
-        return ClientPagedResultList(self.provider, cb_snapshots)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
         """
         """
@@ -382,20 +391,20 @@ class AzureSnapshotService(BaseSnapshotService):
                  self.provider.azure_client.list_snapshots()]
                  self.provider.azure_client.list_snapshots()]
         return ClientPagedResultList(self.provider, snaps, limit, marker)
         return ClientPagedResultList(self.provider, snaps, limit, marker)
 
 
-    def create(self, name, volume, description=None):
+    def create(self, label, volume, description=None):
         """
         """
         Creates a new snapshot of a given volume.
         Creates a new snapshot of a given volume.
         """
         """
-        AzureSnapshot.assert_valid_resource_name(name)
-        volume = (self.provider.storage.volumes.get(volume)
-                  if isinstance(volume, str) else volume)
-
-        tags = {'Name': name}
-        snapshot_name = "{0}-{1}".format(name, uuid.uuid4().hex[:6])
-
+        AzureSnapshot.assert_valid_resource_label(label)
+        snapshot_name = AzureSnapshot._generate_name_from_label(label,
+                                                                "cb-snap")
+        tags = {'Label': label}
         if description:
         if description:
             tags.update(Description=description)
             tags.update(Description=description)
 
 
+        volume = (self.provider.storage.volumes.get(volume)
+                  if isinstance(volume, str) else volume)
+
         params = {
         params = {
             'location': self.provider.azure_client.region_name,
             'location': self.provider.azure_client.region_name,
             'creation_data': {
             'creation_data': {
@@ -440,14 +449,14 @@ class AzureInstanceService(BaseInstanceService):
     def __init__(self, provider):
     def __init__(self, provider):
         super(AzureInstanceService, self).__init__(provider)
         super(AzureInstanceService, self).__init__(provider)
 
 
-    def create(self, name, image, vm_type, subnet=None, zone=None,
+    def create(self, label, image, vm_type, subnet=None, zone=None,
                key_pair=None, vm_firewalls=None, user_data=None,
                key_pair=None, vm_firewalls=None, user_data=None,
                launch_config=None, **kwargs):
                launch_config=None, **kwargs):
 
 
-        instance_name = name.replace("_", "-") if name \
-            else "{0} - {1}".format("cb", uuid.uuid4())
+        AzureInstance.assert_valid_resource_label(label)
 
 
-        AzureInstance.assert_valid_resource_name(instance_name)
+        instance_name = AzureInstance._generate_name_from_label(label,
+                                                                "cb-ins")
 
 
         image = (image if isinstance(image, AzureMachineImage) else
         image = (image if isinstance(image, AzureMachineImage) else
                  self.provider.compute.images.get(image))
                  self.provider.compute.images.get(image))
@@ -478,7 +487,7 @@ class AzureInstanceService(BaseInstanceService):
                                                        instance_name, zone_id)
                                                        instance_name, zone_id)
 
 
         nic_params = {
         nic_params = {
-            'location': self._provider.region_name,
+            'location': self.provider.region_name,
             'ip_configurations': [{
             'ip_configurations': [{
                 'name': instance_name + '_ip_config',
                 'name': instance_name + '_ip_config',
                 'private_ip_allocation_method': 'Dynamic',
                 'private_ip_allocation_method': 'Dynamic',
@@ -504,15 +513,15 @@ class AzureInstanceService(BaseInstanceService):
         # Key_pair is mandatory in azure and it should not be None.
         # Key_pair is mandatory in azure and it should not be None.
         temp_key_pair = None
         temp_key_pair = None
         if key_pair:
         if key_pair:
-            key_pair = (self.provider.security.key_pairs.get(key_pair)
-                        if isinstance(key_pair, str) else key_pair)
+            key_pair = (key_pair if isinstance(key_pair, AzureKeyPair)
+                        else self.provider.security.key_pairs.get(key_pair))
         else:
         else:
             # Create a temporary keypair if none is provided to keep Azure
             # Create a temporary keypair if none is provided to keep Azure
             # happy, but the private key will be discarded, so it'll be all
             # happy, but the private key will be discarded, so it'll be all
             # but useless. However, this will allow an instance to be launched
             # but useless. However, this will allow an instance to be launched
             # without specifying a keypair, so users may still be able to login
             # without specifying a keypair, so users may still be able to login
             # if they have a preinstalled keypair/password baked into the image
             # if they have a preinstalled keypair/password baked into the image
-            temp_kp_name = "".join(["cb_default_kp_",
+            temp_kp_name = "".join(["cb-default-kp-",
                                    str(uuid.uuid5(uuid.NAMESPACE_OID,
                                    str(uuid.uuid5(uuid.NAMESPACE_OID,
                                                   instance_name))[-6:]])
                                                   instance_name))[-6:]])
             key_pair = self.provider.security.key_pairs.create(
             key_pair = self.provider.security.key_pairs.create(
@@ -520,7 +529,7 @@ class AzureInstanceService(BaseInstanceService):
             temp_key_pair = key_pair
             temp_key_pair = key_pair
 
 
         params = {
         params = {
-            'location': zone_id or self._provider.region_name,
+            'location': zone_id or self.provider.region_name,
             'os_profile': {
             'os_profile': {
                 'admin_username': self.provider.vm_default_user_name,
                 'admin_username': self.provider.vm_default_user_name,
                 'computer_name': instance_name,
                 'computer_name': instance_name,
@@ -545,7 +554,7 @@ class AzureInstanceService(BaseInstanceService):
                 }]
                 }]
             },
             },
             'storage_profile': storage_profile,
             'storage_profile': storage_profile,
-            'tags': {'Name': name}
+            'tags': {'Label': label}
         }
         }
 
 
         for disk_def in storage_profile.get('data_disks', []):
         for disk_def in storage_profile.get('data_disks', []):
@@ -556,7 +565,7 @@ class AzureInstanceService(BaseInstanceService):
             params['os_profile']['custom_data'] = str(custom_data, 'utf-8')
             params['os_profile']['custom_data'] = str(custom_data, 'utf-8')
 
 
         if not temp_key_pair:
         if not temp_key_pair:
-            params['tags'].update(Key_Pair=key_pair.name)
+            params['tags'].update(Key_Pair=key_pair.id)
 
 
         try:
         try:
             vm = self.provider.azure_client.create_vm(instance_name, params)
             vm = self.provider.azure_client.create_vm(instance_name, params)
@@ -575,7 +584,7 @@ class AzureInstanceService(BaseInstanceService):
                 temp_key_pair.delete()
                 temp_key_pair.delete()
         return AzureInstance(self.provider, vm)
         return AzureInstance(self.provider, vm)
 
 
-    def _resolve_launch_options(self, name, subnet=None, zone_id=None,
+    def _resolve_launch_options(self, inst_name, subnet=None, zone_id=None,
                                 vm_firewalls=None):
                                 vm_firewalls=None):
         if subnet:
         if subnet:
             # subnet's zone takes precedence
             # subnet's zone takes precedence
@@ -595,7 +604,8 @@ class AzureInstanceService(BaseInstanceService):
 
 
             if len(vm_firewalls) > 1:
             if len(vm_firewalls) > 1:
                 new_fw = self.provider.security.vm_firewalls.\
                 new_fw = self.provider.security.vm_firewalls.\
-                    create('{0}-fw'.format(name), 'Merge vm firewall {0}'.
+                    create(label='{0}-fw'.format(inst_name),
+                           description='Merge vm firewall {0}'.
                            format(','.join(vm_firewalls_ids)))
                            format(','.join(vm_firewalls_ids)))
 
 
                 for fw in vm_firewalls:
                 for fw in vm_firewalls:
@@ -735,18 +745,18 @@ class AzureInstanceService(BaseInstanceService):
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name', 'label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
 
-        filtr = {'Name': name}
-        instances = [AzureInstance(self.provider, inst)
-                     for inst in azure_helpers.filter_by_tag(
-                     self.provider.azure_client.list_vm(), filtr)]
-        return ClientPagedResultList(self.provider, instances)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
 
 
 
 class AzureImageService(BaseImageService):
 class AzureImageService(BaseImageService):
@@ -766,30 +776,26 @@ class AzureImageService(BaseImageService):
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name', 'label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
-
-        filters = {'Name': name}
-        cb_images = [AzureMachineImage(self.provider, image)
-                     for image in azure_helpers.filter_by_tag(
-                     self.provider.azure_client.list_images(), filters)]
-        # All gallery image properties (id, resource_id, name) are the URN
-        # Improvement: wrap the filters by publisher, offer, etc...
-        cb_images.extend([AzureMachineImage(self.provider, image) for image
-                          in self.provider.azure_client.list_gallery_refs()
-                          if azure_helpers.generate_urn(image) == name])
-        return ClientPagedResultList(self.provider, cb_images)
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
 
-    def list(self, limit=None, marker=None):
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
+
+    def list(self, filter_by_owner=True, limit=None, marker=None):
         """
         """
         List all images.
         List all images.
         """
         """
         azure_images = self.provider.azure_client.list_images()
         azure_images = self.provider.azure_client.list_images()
-        azure_gallery_refs = self.provider.azure_client.list_gallery_refs()
+        azure_gallery_refs = self.provider.azure_client.list_gallery_refs() \
+            if not filter_by_owner else []
         cb_images = [AzureMachineImage(self.provider, img)
         cb_images = [AzureMachineImage(self.provider, img)
                      for img in azure_images + azure_gallery_refs]
                      for img in azure_images + azure_gallery_refs]
         return ClientPagedResultList(self.provider, cb_images,
         return ClientPagedResultList(self.provider, cb_images,
@@ -859,36 +865,31 @@ class AzureNetworkService(BaseNetworkService):
                                      limit=limit, marker=marker)
                                      limit=limit, marker=marker)
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name', 'label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
-
-        filters = {'Name': name}
-        networks = [
-            AzureNetwork(self.provider, network) for network
-            in azure_helpers.filter_by_tag(
-                self.provider.azure_client.list_networks(), filters)]
-        return ClientPagedResultList(self.provider, networks)
-
-    def create(self, name, cidr_block):
-        # Azure requires CIDR block to be specified when creating a network
-        # so set a default one and use the largest allowed netmask.
-        network_name = AzureNetwork.CB_DEFAULT_NETWORK_NAME
-        if name:
-            network_name = "{0}-{1}".format(name, uuid.uuid4().hex[:6])
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
 
-        AzureNetwork.assert_valid_resource_name(network_name)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
 
+    def create(self, label, cidr_block):
+        AzureNetwork.assert_valid_resource_label(label)
         params = {
         params = {
             'location': self.provider.azure_client.region_name,
             'location': self.provider.azure_client.region_name,
             'address_space': {
             'address_space': {
                 'address_prefixes': [cidr_block]
                 'address_prefixes': [cidr_block]
             },
             },
-            'tags': {'Name': name or AzureNetwork.CB_DEFAULT_NETWORK_NAME}
+            'tags': {'Label': label}
         }
         }
+
+        network_name = AzureNetwork._generate_name_from_label(label, 'cb-net')
+
         az_network = self.provider.azure_client.create_network(network_name,
         az_network = self.provider.azure_client.create_network(network_name,
                                                                params)
                                                                params)
         cb_network = AzureNetwork(self.provider, az_network)
         cb_network = AzureNetwork(self.provider, az_network)
@@ -969,7 +970,7 @@ class AzureSubnetService(BaseSubnetService):
                         net.id
                         net.id
                     ))
                     ))
                 except CloudError as cloud_error:
                 except CloudError as cloud_error:
-                    if cloud_error.error.error == "ResourceNotFound":
+                    if "NotFound" in cloud_error.error.error:
                         log.exception(cloud_error)
                         log.exception(cloud_error)
                     else:
                     else:
                         raise cloud_error
                         raise cloud_error
@@ -980,23 +981,30 @@ class AzureSubnetService(BaseSubnetService):
 
 
     def find(self, network=None, **kwargs):
     def find(self, network=None, **kwargs):
         obj_list = self._list_subnets(network)
         obj_list = self._list_subnets(network)
-        filters = ['name']
+        filters = ['label']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
-        return ClientPagedResultList(self._provider, list(matches))
 
 
-    def create(self, network, cidr_block, name=None, **kwargs):
+        # All kwargs should have been popped at this time.
+        if len(kwargs) > 0:
+            raise TypeError("Unrecognised parameters for search: %s."
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
+
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
+
+    def create(self, label, network, cidr_block, **kwargs):
         """
         """
         Create subnet
         Create subnet
         """
         """
-        AzureSubnet.assert_valid_resource_name(name)
+        # Although Subnet doesn't support labels, we use the parent Network's
+        # tags to track the subnet's labels
+        AzureSubnet.assert_valid_resource_label(label)
+        subnet_name = AzureSubnet._generate_name_from_label(label, "cb-sn")
+
         network_id = network.id \
         network_id = network.id \
             if isinstance(network, Network) else network
             if isinstance(network, Network) else network
 
 
-        if not name:
-            subnet_name = AzureSubnet.CB_DEFAULT_SUBNET_NAME
-        else:
-            subnet_name = name
-
         subnet_info = self.provider.azure_client\
         subnet_info = self.provider.azure_client\
             .create_subnet(
             .create_subnet(
                 network_id,
                 network_id,
@@ -1006,28 +1014,30 @@ class AzureSubnetService(BaseSubnetService):
                 }
                 }
             )
             )
 
 
-        return AzureSubnet(self.provider, subnet_info)
+        subnet = AzureSubnet(self.provider, subnet_info)
+        subnet.label = label
+        return subnet
 
 
     def get_or_create_default(self, zone):
     def get_or_create_default(self, zone):
         default_cidr = '10.0.1.0/24'
         default_cidr = '10.0.1.0/24'
 
 
         # No provider-default Subnet exists, look for a library-default one
         # No provider-default Subnet exists, look for a library-default one
-        matches = self.find(name=AzureSubnet.CB_DEFAULT_SUBNET_NAME)
+        matches = self.find(label=AzureSubnet.CB_DEFAULT_SUBNET_LABEL)
         if matches:
         if matches:
             return matches[0]
             return matches[0]
 
 
         # No provider-default Subnet exists, try to create it (net + subnets)
         # No provider-default Subnet exists, try to create it (net + subnets)
         networks = self.provider.networking.networks.find(
         networks = self.provider.networking.networks.find(
-            name=AzureNetwork.CB_DEFAULT_NETWORK_NAME)
+            label=AzureNetwork.CB_DEFAULT_NETWORK_LABEL)
 
 
         if networks:
         if networks:
             network = networks[0]
             network = networks[0]
         else:
         else:
             network = self.provider.networking.networks.create(
             network = self.provider.networking.networks.create(
-                AzureNetwork.CB_DEFAULT_NETWORK_NAME, '10.0.0.0/16')
+                AzureNetwork.CB_DEFAULT_NETWORK_LABEL, '10.0.0.0/16')
 
 
-        subnet = self.create(network, default_cidr,
-                             name=AzureSubnet.CB_DEFAULT_SUBNET_NAME)
+        subnet = self.create(AzureSubnet.CB_DEFAULT_SUBNET_LABEL, network,
+                             default_cidr)
         return subnet
         return subnet
 
 
     def delete(self, subnet):
     def delete(self, subnet):
@@ -1049,19 +1059,18 @@ class AzureRouterService(BaseRouterService):
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        obj_list = self
+        filters = ['name', 'label']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
-
-        filters = {'Name': name}
-        routes = [AzureRouter(self.provider, route)
-                  for route in azure_helpers.filter_by_tag(
-                  self.provider.azure_client.list_route_tables(), filters)]
+                            " Supported attributes: %s" % (kwargs,
+                                                           ", ".join(filters)))
 
 
-        return ClientPagedResultList(self.provider, routes)
+        return ClientPagedResultList(self.provider,
+                                     matches if matches else [])
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
         routes = [AzureRouter(self.provider, route)
         routes = [AzureRouter(self.provider, route)
@@ -1071,10 +1080,13 @@ class AzureRouterService(BaseRouterService):
                                      routes,
                                      routes,
                                      limit=limit, marker=marker)
                                      limit=limit, marker=marker)
 
 
-    def create(self, name, network):
-        AzureRouter.assert_valid_resource_name(name)
+    def create(self, label, network):
+        AzureRouter.assert_valid_resource_label(label)
+        router_name = AzureRouter._generate_name_from_label(label, "cb-router")
+
         parameters = {"location": self.provider.region_name,
         parameters = {"location": self.provider.region_name,
-                      'tags': {'Name': name}}
+                      "tags": {'Label': label}}
+
         route = self.provider.azure_client. \
         route = self.provider.azure_client. \
-            create_route_table(name, parameters)
+            create_route_table(router_name, parameters)
         return AzureRouter(self.provider, route)
         return AzureRouter(self.provider, route)

+ 124 - 69
cloudbridge/cloud/providers/openstack/resources.py

@@ -99,10 +99,27 @@ class OpenStackMachineImage(BaseMachineImage):
     @property
     @property
     def name(self):
     def name(self):
         """
         """
-        Get the image name.
+        Get the image identifier.
+        """
+        return self._os_image.id
+
+    @property
+    def label(self):
+        """
+        Get the image label.
         """
         """
         return self._os_image.name
         return self._os_image.name
 
 
+    @label.setter
+    # pylint:disable=arguments-differ
+    def label(self, value):
+        """
+        Set the image label.
+        """
+        self.assert_valid_resource_label(value)
+        self._provider.os_conn.image.update_image(
+            self._os_image, name=value or "")
+
     @property
     @property
     def description(self):
     def description(self):
         """
         """
@@ -280,23 +297,30 @@ class OpenStackInstance(BaseInstance):
         return self._os_instance.id
         return self._os_instance.id
 
 
     @property
     @property
-    # pylint:disable=arguments-differ
     def name(self):
     def name(self):
         """
         """
-        Get the instance name.
+        Get the instance identifier.
+        """
+        return self.id
+
+    @property
+    # pylint:disable=arguments-differ
+    def label(self):
+        """
+        Get the instance label.
         """
         """
         return self._os_instance.name
         return self._os_instance.name
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
+    def label(self, value):
         """
         """
-        Set the instance name.
+        Set the instance label.
         """
         """
-        self.assert_valid_resource_name(value)
+        self.assert_valid_resource_label(value)
 
 
         self._os_instance.name = value
         self._os_instance.name = value
-        self._os_instance.update(name=value)
+        self._os_instance.update(name=value or "cb-inst")
 
 
     @property
     @property
     def public_ips(self):
     def public_ips(self):
@@ -417,22 +441,25 @@ class OpenStackInstance(BaseInstance):
         return [fw.id for fw in self.vm_firewalls]
         return [fw.id for fw in self.vm_firewalls]
 
 
     @property
     @property
-    def key_pair_name(self):
+    def key_pair_id(self):
         """
         """
-        Get the name of the key pair associated with this instance.
+        Get the id of the key pair associated with this instance.
         """
         """
         return self._os_instance.key_name
         return self._os_instance.key_name
 
 
-    def create_image(self, name):
+    def create_image(self, label):
         """
         """
         Create a new image based on this instance.
         Create a new image based on this instance.
         """
         """
-        log.debug("Creating OpenStack Image with the name %s", name)
-        self.assert_valid_resource_name(name)
+        log.debug("Creating OpenStack Image with the label %s", label)
+        self.assert_valid_resource_label(label)
+        name = self._generate_name_from_label(label, 'cb-img')
 
 
         image_id = self._os_instance.create_image(name)
         image_id = self._os_instance.create_image(name)
-        return OpenStackMachineImage(
+        img = OpenStackMachineImage(
             self._provider, self._provider.compute.images.get(image_id))
             self._provider, self._provider.compute.images.get(image_id))
+        img.label = label
+        return img
 
 
     def _get_fip(self, floating_ip):
     def _get_fip(self, floating_ip):
         """Get a floating IP object based on the supplied ID."""
         """Get a floating IP object based on the supplied ID."""
@@ -508,8 +535,7 @@ class OpenStackRegion(BaseRegion):
 
 
     @property
     @property
     def name(self):
     def name(self):
-        return (self._os_region.id if type(self._os_region) == Region else
-                self._os_region)
+        return self.id
 
 
     @property
     @property
     def zones(self):
     def zones(self):
@@ -557,22 +583,26 @@ class OpenStackVolume(BaseVolume):
         return self._volume.id
         return self._volume.id
 
 
     @property
     @property
-    # pylint:disable=arguments-differ
     def name(self):
     def name(self):
+        return self.id
+
+    @property
+    # pylint:disable=arguments-differ
+    def label(self):
         """
         """
-        Get the volume name.
+        Get the volume label.
         """
         """
         return self._volume.name
         return self._volume.name
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
+    def label(self, value):
         """
         """
-        Set the volume name.
+        Set the volume label.
         """
         """
-        self.assert_valid_resource_name(value)
+        self.assert_valid_resource_label(value)
         self._volume.name = value
         self._volume.name = value
-        self._volume.update(name=value)
+        self._volume.update(name=value or "")
 
 
     @property
     @property
     def description(self):
     def description(self):
@@ -628,14 +658,14 @@ class OpenStackVolume(BaseVolume):
         """
         """
         self._volume.detach()
         self._volume.detach()
 
 
-    def create_snapshot(self, name, description=None):
+    def create_snapshot(self, label, description=None):
         """
         """
         Create a snapshot of this Volume.
         Create a snapshot of this Volume.
         """
         """
         log.debug("Creating snapchat of volume: %s with the "
         log.debug("Creating snapchat of volume: %s with the "
-                  "description: %s", name, description)
+                  "description: %s", label, description)
         return self._provider.storage.snapshots.create(
         return self._provider.storage.snapshots.create(
-            name, self, description=description)
+            label, self, description=description)
 
 
     def delete(self):
     def delete(self):
         """
         """
@@ -683,22 +713,26 @@ class OpenStackSnapshot(BaseSnapshot):
         return self._snapshot.id
         return self._snapshot.id
 
 
     @property
     @property
-    # pylint:disable=arguments-differ
     def name(self):
     def name(self):
+        return self.id
+
+    @property
+    # pylint:disable=arguments-differ
+    def label(self):
         """
         """
-        Get the snapshot name.
+        Get the snapshot label.
         """
         """
         return self._snapshot.name
         return self._snapshot.name
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
+    def label(self, value):
         """
         """
-        Set the snapshot name.
+        Set the snapshot label.
         """
         """
-        self.assert_valid_resource_name(value)
+        self.assert_valid_resource_label(value)
         self._snapshot.name = value
         self._snapshot.name = value
-        self._snapshot.update(name=value)
+        self._snapshot.update(name=value or "")
 
 
     @property
     @property
     def description(self):
     def description(self):
@@ -750,13 +784,14 @@ class OpenStackSnapshot(BaseSnapshot):
         """
         """
         Create a new Volume from this Snapshot.
         Create a new Volume from this Snapshot.
         """
         """
-        vol_name = "from_snap_{0}".format(self.id or self.name)
+        vol_label = "from-snap-{0}".format(self.id or self.label)
+        name = self._generate_name_from_label(vol_label, 'cb-vol')
         size = size if size else self._snapshot.size
         size = size if size else self._snapshot.size
         os_vol = self._provider.cinder.volumes.create(
         os_vol = self._provider.cinder.volumes.create(
-            size, name=vol_name, availability_zone=placement,
+            size, name=name, availability_zone=placement,
             snapshot_id=self._snapshot.id)
             snapshot_id=self._snapshot.id)
         cb_vol = OpenStackVolume(self._provider, os_vol)
         cb_vol = OpenStackVolume(self._provider, os_vol)
-        cb_vol.name = vol_name
+        cb_vol.label = vol_label
         return cb_vol
         return cb_vol
 
 
 
 
@@ -774,7 +809,7 @@ class OpenStackGatewayContainer(BaseGatewayContainer):
         # all available networks and perform an assignment test to infer valid
         # all available networks and perform an assignment test to infer valid
         # floating ip nets.
         # floating ip nets.
         dummy_router = self._provider.networking.routers.create(
         dummy_router = self._provider.networking.routers.create(
-            network=self._network, name='cb_conn_test_router')
+            label='cb-conn-test-router', network=self._network)
         with cb_helpers.cleanup_action(lambda: dummy_router.delete()):
         with cb_helpers.cleanup_action(lambda: dummy_router.delete()):
             try:
             try:
                 dummy_router.attach_gateway(external_net)
                 dummy_router.attach_gateway(external_net)
@@ -784,9 +819,6 @@ class OpenStackGatewayContainer(BaseGatewayContainer):
 
 
     def get_or_create_inet_gateway(self, name=None):
     def get_or_create_inet_gateway(self, name=None):
         """For OS, inet gtw is any net that has `external` property set."""
         """For OS, inet gtw is any net that has `external` property set."""
-        if name:
-            OpenStackInternetGateway.assert_valid_resource_name(name)
-
         external_nets = (n for n in self._provider.networking.networks
         external_nets = (n for n in self._provider.networking.networks
                          if n.external)
                          if n.external)
         for net in external_nets:
         for net in external_nets:
@@ -833,16 +865,20 @@ class OpenStackNetwork(BaseNetwork):
 
 
     @property
     @property
     def name(self):
     def name(self):
+        return self.id
+
+    @property
+    def label(self):
         return self._network.get('name', None)
         return self._network.get('name', None)
 
 
-    @name.setter
-    def name(self, value):  # pylint:disable=arguments-differ
+    @label.setter
+    def label(self, value):  # pylint:disable=arguments-differ
         """
         """
-        Set the network name.
+        Set the network label.
         """
         """
-        self.assert_valid_resource_name(value)
-        self._provider.neutron.update_network(self.id,
-                                              {'network': {'name': value}})
+        self.assert_valid_resource_label(value)
+        self._provider.neutron.update_network(
+            self.id, {'network': {'name': value or ""}})
         self.refresh()
         self.refresh()
 
 
     @property
     @property
@@ -910,16 +946,20 @@ class OpenStackSubnet(BaseSubnet):
 
 
     @property
     @property
     def name(self):
     def name(self):
+        return self.id
+
+    @property
+    def label(self):
         return self._subnet.get('name', None)
         return self._subnet.get('name', None)
 
 
-    @name.setter
-    def name(self, value):  # pylint:disable=arguments-differ
+    @label.setter
+    def label(self, value):  # pylint:disable=arguments-differ
         """
         """
-        Set the subnet name.
+        Set the subnet label.
         """
         """
-        self.assert_valid_resource_name(value)
+        self.assert_valid_resource_label(value)
         self._provider.neutron.update_subnet(
         self._provider.neutron.update_subnet(
-            self.id, {'subnet': {'name': value}})
+            self.id, {'subnet': {'name': value or ""}})
         self._subnet['name'] = value
         self._subnet['name'] = value
 
 
     @property
     @property
@@ -1032,16 +1072,20 @@ class OpenStackRouter(BaseRouter):
 
 
     @property
     @property
     def name(self):
     def name(self):
+        return self.id
+
+    @property
+    def label(self):
         return self._router.get('name', None)
         return self._router.get('name', None)
 
 
-    @name.setter
-    def name(self, value):  # pylint:disable=arguments-differ
+    @label.setter
+    def label(self, value):  # pylint:disable=arguments-differ
         """
         """
-        Set the router name.
+        Set the router label.
         """
         """
-        self.assert_valid_resource_name(value)
+        self.assert_valid_resource_label(value)
         self._provider.neutron.update_router(
         self._provider.neutron.update_router(
-            self.id, {'router': {'name': value}})
+            self.id, {'router': {'name': value or ""}})
         self.refresh()
         self.refresh()
 
 
     def refresh(self):
     def refresh(self):
@@ -1112,14 +1156,18 @@ class OpenStackInternetGateway(BaseInternetGateway):
 
 
     @property
     @property
     def name(self):
     def name(self):
-        return self._gateway_net.get('name', None)
+        return self.id
 
 
-    @name.setter
+    @property
+    def label(self):
+        return self._gateway_net.tags.get('gateway_name', None)
+
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
-        self.assert_valid_resource_name(value)
-        self._provider.neutron.update_network(self.id,
-                                              {'network': {'name': value}})
+    def label(self, value):
+        self.assert_valid_resource_label(value)
+        self._provider.neutron_client.add_tag(
+            'network', self.id, {'gateway_name': value or ""})
         self.refresh()
         self.refresh()
 
 
     @property
     @property
@@ -1173,14 +1221,21 @@ class OpenStackVMFirewall(BaseVMFirewall):
 
 
     @property
     @property
     def name(self):
     def name(self):
+        """
+        Return the name of this VM firewall.
+        """
+        return self.id
+
+    @property
+    def label(self):
         return self._vm_firewall.name
         return self._vm_firewall.name
 
 
-    @name.setter
+    @label.setter
     # pylint:disable=arguments-differ
     # pylint:disable=arguments-differ
-    def name(self, value):
-        self.assert_valid_resource_name(value)
-        self._provider.os_conn.network.update_security_group(self.id,
-                                                             name=value)
+    def label(self, value):
+        self.assert_valid_resource_label(value)
+        self._provider.os_conn.network.update_security_group(
+            self.id, name=value or "")
         self.refresh()
         self.refresh()
 
 
     @property
     @property
@@ -1319,7 +1374,7 @@ class OpenStackBucketObject(BaseBucketObject):
     @property
     @property
     def name(self):
     def name(self):
         """Get this object's name."""
         """Get this object's name."""
-        return self._obj.get("name")
+        return self.id
 
 
     @property
     @property
     def size(self):
     def size(self):
@@ -1432,7 +1487,7 @@ class OpenStackBucket(BaseBucket):
 
 
     @property
     @property
     def name(self):
     def name(self):
-        return self._bucket.get("name")
+        return self.id
 
 
     @property
     @property
     def objects(self):
     def objects(self):

+ 84 - 86
cloudbridge/cloud/providers/openstack/services.py

@@ -1,9 +1,7 @@
 """
 """
 Services implemented by the OpenStack provider.
 Services implemented by the OpenStack provider.
 """
 """
-import fnmatch
 import logging
 import logging
-import re
 
 
 from cinderclient.exceptions import NotFound as CinderNotFound
 from cinderclient.exceptions import NotFound as CinderNotFound
 
 
@@ -14,6 +12,8 @@ from novaclient.exceptions import NotFound as NovaNotFound
 from openstack.exceptions import NotFoundException
 from openstack.exceptions import NotFoundException
 from openstack.exceptions import ResourceNotFound
 from openstack.exceptions import ResourceNotFound
 
 
+from swiftclient import ClientException as SwiftClientException
+
 import cloudbridge.cloud.base.helpers as cb_helpers
 import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import BaseLaunchConfig
 from cloudbridge.cloud.base.resources import BaseLaunchConfig
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.resources import ClientPagedResultList
@@ -47,7 +47,6 @@ from cloudbridge.cloud.providers.openstack import helpers as oshelpers
 
 
 from .resources import OpenStackBucket
 from .resources import OpenStackBucket
 from .resources import OpenStackInstance
 from .resources import OpenStackInstance
-from .resources import OpenStackInternetGateway
 from .resources import OpenStackKeyPair
 from .resources import OpenStackKeyPair
 from .resources import OpenStackMachineImage
 from .resources import OpenStackMachineImage
 from .resources import OpenStackNetwork
 from .resources import OpenStackNetwork
@@ -211,27 +210,28 @@ class OpenStackVMFirewallService(BaseVMFirewallService):
         return ClientPagedResultList(self.provider, firewalls,
         return ClientPagedResultList(self.provider, firewalls,
                                      limit=limit, marker=marker)
                                      limit=limit, marker=marker)
 
 
-    def create(self, name, description, network_id):
-        OpenStackVMFirewall.assert_valid_resource_name(name)
+    def create(self, label, description, network_id):
+        OpenStackVMFirewall.assert_valid_resource_label(label)
+        name = OpenStackVMFirewall._generate_name_from_label(label, 'cb-fw')
         log.debug("Creating OpenStack VM Firewall with the params: "
         log.debug("Creating OpenStack VM Firewall with the params: "
-                  "[name: %s network id: %s description: %s]", name,
+                  "[label: %s network id: %s description: %s]", label,
                   network_id, description)
                   network_id, description)
         sg = self.provider.os_conn.network.create_security_group(
         sg = self.provider.os_conn.network.create_security_group(
-            name=name, description=description)
+            name=name, description=description or name)
         if sg:
         if sg:
             return OpenStackVMFirewall(self.provider, sg)
             return OpenStackVMFirewall(self.provider, sg)
         return None
         return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        label = kwargs.pop('label', None)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs, 'label'))
 
 
-        log.debug("Searching for %s", name)
-        sgs = [self.provider.os_conn.network.find_security_group(name)]
+        log.debug("Searching for %s", label)
+        sgs = [self.provider.os_conn.network.find_security_group(label)]
         results = [OpenStackVMFirewall(self.provider, sg)
         results = [OpenStackVMFirewall(self.provider, sg)
                    for sg in sgs if sg]
                    for sg in sgs if sg]
         return ClientPagedResultList(self.provider, results)
         return ClientPagedResultList(self.provider, results)
@@ -262,21 +262,9 @@ class OpenStackImageService(BaseImageService):
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
-
-        # All kwargs should have been popped at this time.
-        if len(kwargs) > 0:
-            raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
-
-        log.debug("Searching for the OpenStack image with the name: %s", name)
-        regex = fnmatch.translate(name)
-        cb_images = [
-            img
-            for img in self
-            if img.name and re.search(regex, img.name)]
-
-        return oshelpers.to_server_paged_list(self.provider, cb_images)
+        filters = ['name', 'label']
+        obj_list = self
+        return cb_helpers.generic_find(filters, kwargs, obj_list)
 
 
     def list(self, filter_by_owner=True, limit=None, marker=None):
     def list(self, filter_by_owner=True, limit=None, marker=None):
         """
         """
@@ -352,15 +340,15 @@ class OpenStackVolumeService(BaseVolumeService):
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        label = kwargs.pop('label', None)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs, 'label'))
 
 
-        log.debug("Searching for an OpenStack Volume with the name %s", name)
-        search_opts = {'name': name}
+        log.debug("Searching for an OpenStack Volume with the label %s", label)
+        search_opts = {'name': label}
         cb_vols = [
         cb_vols = [
             OpenStackVolume(self.provider, vol)
             OpenStackVolume(self.provider, vol)
             for vol in self.provider.cinder.volumes.list(
             for vol in self.provider.cinder.volumes.list(
@@ -382,21 +370,21 @@ class OpenStackVolumeService(BaseVolumeService):
 
 
         return oshelpers.to_server_paged_list(self.provider, cb_vols, limit)
         return oshelpers.to_server_paged_list(self.provider, cb_vols, limit)
 
 
-    def create(self, name, size, zone, snapshot=None, description=None):
+    def create(self, label, size, zone, snapshot=None, description=None):
         """
         """
         Creates a new volume.
         Creates a new volume.
         """
         """
         log.debug("Creating a new volume with the params: "
         log.debug("Creating a new volume with the params: "
-                  "[name: %s size: %s zone: %s snapshot: %s description: %s]",
-                  name, size, zone, snapshot, description)
-        OpenStackVolume.assert_valid_resource_name(name)
+                  "[label: %s size: %s zone: %s snapshot: %s description: %s]",
+                  label, size, zone, snapshot, description)
+        OpenStackVolume.assert_valid_resource_label(label)
 
 
         zone_id = zone.id if isinstance(zone, PlacementZone) else zone
         zone_id = zone.id if isinstance(zone, PlacementZone) else zone
         snapshot_id = snapshot.id if isinstance(
         snapshot_id = snapshot.id if isinstance(
             snapshot, OpenStackSnapshot) and snapshot else snapshot
             snapshot, OpenStackSnapshot) and snapshot else snapshot
 
 
         os_vol = self.provider.cinder.volumes.create(
         os_vol = self.provider.cinder.volumes.create(
-            size, name=name, description=description,
+            size, name=label, description=description,
             availability_zone=zone_id, snapshot_id=snapshot_id)
             availability_zone=zone_id, snapshot_id=snapshot_id)
         return OpenStackVolume(self.provider, os_vol)
         return OpenStackVolume(self.provider, os_vol)
 
 
@@ -420,22 +408,22 @@ class OpenStackSnapshotService(BaseSnapshotService):
             return None
             return None
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        label = kwargs.pop('label', None)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs, 'label'))
 
 
-        search_opts = {'name': name,  # TODO: Cinder is ignoring name
+        search_opts = {'name': label,  # TODO: Cinder is ignoring name
                        'limit': oshelpers.os_result_limit(self.provider),
                        'limit': oshelpers.os_result_limit(self.provider),
                        'marker': None}
                        'marker': None}
-        log.debug("Searching for an OpenStack volume with the following "
+        log.debug("Searching for an OpenStack snapshot with the following "
                   "params: %s", search_opts)
                   "params: %s", search_opts)
         cb_snaps = [
         cb_snaps = [
             OpenStackSnapshot(self.provider, snap) for
             OpenStackSnapshot(self.provider, snap) for
             snap in self.provider.cinder.volume_snapshots.list(search_opts)
             snap in self.provider.cinder.volume_snapshots.list(search_opts)
-            if snap.name == name]
+            if snap.name == label]
 
 
         return oshelpers.to_server_paged_list(self.provider, cb_snaps)
         return oshelpers.to_server_paged_list(self.provider, cb_snaps)
 
 
@@ -451,18 +439,18 @@ class OpenStackSnapshotService(BaseSnapshotService):
                              'marker': marker})]
                              'marker': marker})]
         return oshelpers.to_server_paged_list(self.provider, cb_snaps, limit)
         return oshelpers.to_server_paged_list(self.provider, cb_snaps, limit)
 
 
-    def create(self, name, volume, description=None):
+    def create(self, label, volume, description=None):
         """
         """
         Creates a new snapshot of a given volume.
         Creates a new snapshot of a given volume.
         """
         """
-        log.debug("Creating a new snapshot of the %s volume.", name)
-        OpenStackSnapshot.assert_valid_resource_name(name)
+        log.debug("Creating a new snapshot of the %s volume.", label)
+        OpenStackSnapshot.assert_valid_resource_label(label)
 
 
         volume_id = (volume.id if isinstance(volume, OpenStackVolume)
         volume_id = (volume.id if isinstance(volume, OpenStackVolume)
                      else volume)
                      else volume)
 
 
         os_snap = self.provider.cinder.volume_snapshots.create(
         os_snap = self.provider.cinder.volume_snapshots.create(
-            volume_id, name=name,
+            volume_id, name=label,
             description=description)
             description=description)
         return OpenStackSnapshot(self.provider, os_snap)
         return OpenStackSnapshot(self.provider, os_snap)
 
 
@@ -522,9 +510,13 @@ class OpenStackBucketService(BaseBucketService):
         """
         """
         log.debug("Creating a new OpenStack Bucket with the name: %s", name)
         log.debug("Creating a new OpenStack Bucket with the name: %s", name)
         OpenStackBucket.assert_valid_resource_name(name)
         OpenStackBucket.assert_valid_resource_name(name)
-
-        self.provider.swift.put_container(name)
-        return self.get(name)
+        try:
+            self.provider.swift.head_container(name)
+            raise DuplicateResourceException(
+                'Bucket already exists with name {0}'.format(name))
+        except SwiftClientException:
+            self.provider.swift.put_container(name)
+            return self.get(name)
 
 
 
 
 class OpenStackRegionService(BaseRegionService):
 class OpenStackRegionService(BaseRegionService):
@@ -596,12 +588,12 @@ class OpenStackInstanceService(BaseInstanceService):
     def __init__(self, provider):
     def __init__(self, provider):
         super(OpenStackInstanceService, self).__init__(provider)
         super(OpenStackInstanceService, self).__init__(provider)
 
 
-    def create(self, name, image, vm_type, subnet, zone,
+    def create(self, label, image, vm_type, subnet, zone,
                key_pair=None, vm_firewalls=None, user_data=None,
                key_pair=None, vm_firewalls=None, user_data=None,
                launch_config=None,
                launch_config=None,
                **kwargs):
                **kwargs):
         """Create a new virtual machine instance."""
         """Create a new virtual machine instance."""
-        OpenStackInstance.assert_valid_resource_name(name)
+        OpenStackInstance.assert_valid_resource_label(label)
 
 
         image_id = image.id if isinstance(image, MachineImage) else image
         image_id = image.id if isinstance(image, MachineImage) else image
         vm_size = vm_type.id if \
         vm_size = vm_type.id if \
@@ -632,7 +624,7 @@ class OpenStackInstanceService(BaseInstanceService):
         nics = None
         nics = None
         if subnet_id:
         if subnet_id:
             log.debug("Creating network port for %s in subnet: %s",
             log.debug("Creating network port for %s in subnet: %s",
-                      name, subnet_id)
+                      label, subnet_id)
             sg_list = []
             sg_list = []
             if vm_firewalls:
             if vm_firewalls:
                 if isinstance(vm_firewalls, list) and \
                 if isinstance(vm_firewalls, list) and \
@@ -640,13 +632,14 @@ class OpenStackInstanceService(BaseInstanceService):
                     sg_list = vm_firewalls
                     sg_list = vm_firewalls
                 else:
                 else:
                     sg_list = (self.provider.security.vm_firewalls
                     sg_list = (self.provider.security.vm_firewalls
-                               .find(name=sg) for sg in vm_firewalls)
+                               .find(label=sg) for sg in vm_firewalls)
                     sg_list = (sg[0] for sg in sg_list if sg)
                     sg_list = (sg[0] for sg in sg_list if sg)
             sg_id_list = [sg.id for sg in sg_list]
             sg_id_list = [sg.id for sg in sg_list]
             port_def = {
             port_def = {
                 "port": {
                 "port": {
                     "admin_state_up": True,
                     "admin_state_up": True,
-                    "name": name,
+                    "name": OpenStackInstance._generate_name_from_label(
+                        label, 'cb-port'),
                     "network_id": net_id,
                     "network_id": net_id,
                     "fixed_ips": [{"subnet_id": subnet_id}],
                     "fixed_ips": [{"subnet_id": subnet_id}],
                     "security_groups": sg_id_list
                     "security_groups": sg_id_list
@@ -660,11 +653,13 @@ class OpenStackInstanceService(BaseInstanceService):
                         isinstance(vm_firewalls[0], VMFirewall):
                         isinstance(vm_firewalls[0], VMFirewall):
                     sg_name_list = [sg.name for sg in vm_firewalls]
                     sg_name_list = [sg.name for sg in vm_firewalls]
                 else:
                 else:
-                    sg_name_list = vm_firewalls
+                    sg_list = (self.provider.security.vm_firewalls.get(sg)
+                               for sg in vm_firewalls)
+                    sg_name_list = (sg[0].name for sg in sg_list if sg)
 
 
         log.debug("Launching in subnet %s", subnet_id)
         log.debug("Launching in subnet %s", subnet_id)
         os_instance = self.provider.nova.servers.create(
         os_instance = self.provider.nova.servers.create(
-            name,
+            label,
             None if self._has_root_device(launch_config) else image_id,
             None if self._has_root_device(launch_config) else image_id,
             vm_size,
             vm_size,
             min_count=1,
             min_count=1,
@@ -731,14 +726,14 @@ class OpenStackInstanceService(BaseInstanceService):
         return BaseLaunchConfig(self.provider)
         return BaseLaunchConfig(self.provider)
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        label = kwargs.pop('label', None)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs, 'label'))
 
 
-        search_opts = {'name': name}
+        search_opts = {'name': label}
         cb_insts = [
         cb_insts = [
             OpenStackInstance(self.provider, inst)
             OpenStackInstance(self.provider, inst)
             for inst in self.provider.nova.servers.list(
             for inst in self.provider.nova.servers.list(
@@ -809,29 +804,30 @@ class OpenStackNetworkService(BaseNetworkService):
                                      limit=limit, marker=marker)
                                      limit=limit, marker=marker)
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
-        name = kwargs.pop('name', None)
+        label = kwargs.pop('label', None)
 
 
         # All kwargs should have been popped at this time.
         # All kwargs should have been popped at this time.
         if len(kwargs) > 0:
         if len(kwargs) > 0:
             raise TypeError("Unrecognised parameters for search: %s."
             raise TypeError("Unrecognised parameters for search: %s."
-                            " Supported attributes: %s" % (kwargs, 'name'))
+                            " Supported attributes: %s" % (kwargs, 'label'))
 
 
-        log.debug("Searching for the OpenStack Network with the "
-                  "name: %s", name)
+        log.debug("Searching for OpenStack Network with label: %s", label)
         networks = [OpenStackNetwork(self.provider, network)
         networks = [OpenStackNetwork(self.provider, network)
                     for network in self.provider.neutron.list_networks(
                     for network in self.provider.neutron.list_networks(
-                        name=name)
+                        name=label)
                     .get('networks') if network]
                     .get('networks') if network]
         return ClientPagedResultList(self.provider, networks)
         return ClientPagedResultList(self.provider, networks)
 
 
-    def create(self, name, cidr_block):
+    def create(self, label, cidr_block):
         log.debug("Creating OpenStack Network with the params: "
         log.debug("Creating OpenStack Network with the params: "
-                  "[name: %s Cinder Block: %s]", name, cidr_block)
-        OpenStackNetwork.assert_valid_resource_name(name)
-
-        net_info = {'name': name}
+                  "[label: %s Cinder Block: %s]", label, cidr_block)
+        OpenStackNetwork.assert_valid_resource_label(label)
+        net_info = {'name': label or ""}
         network = self.provider.neutron.create_network({'network': net_info})
         network = self.provider.neutron.create_network({'network': net_info})
-        return OpenStackNetwork(self.provider, network.get('network'))
+        cb_net = OpenStackNetwork(self.provider, network.get('network'))
+        if label:
+            cb_net.label = label
+        return cb_net
 
 
 
 
 class OpenStackSubnetService(BaseSubnetService):
 class OpenStackSubnetService(BaseSubnetService):
@@ -856,40 +852,42 @@ class OpenStackSubnetService(BaseSubnetService):
         return ClientPagedResultList(self.provider, subnets,
         return ClientPagedResultList(self.provider, subnets,
                                      limit=limit, marker=marker)
                                      limit=limit, marker=marker)
 
 
-    def create(self, name, network, cidr_block, zone):
+    def create(self, label, network, cidr_block, zone):
         """zone param is ignored."""
         """zone param is ignored."""
         log.debug("Creating OpenStack Subnet with the params: "
         log.debug("Creating OpenStack Subnet with the params: "
-                  "[Name: %s Network: %s Cinder Block: %s Zone: -ignored-]",
-                  name, network, cidr_block)
-        OpenStackSubnet.assert_valid_resource_name(name)
-
+                  "[Label: %s Network: %s Cinder Block: %s Zone: -ignored-]",
+                  label, network, cidr_block)
+        OpenStackSubnet.assert_valid_resource_label(label)
+        name = OpenStackSubnet._generate_name_from_label(label, 'cb-subnet')
         network_id = (network.id if isinstance(network, OpenStackNetwork)
         network_id = (network.id if isinstance(network, OpenStackNetwork)
                       else network)
                       else network)
         subnet_info = {'name': name, 'network_id': network_id,
         subnet_info = {'name': name, 'network_id': network_id,
                        'cidr': cidr_block, 'ip_version': 4}
                        'cidr': cidr_block, 'ip_version': 4}
         subnet = (self.provider.neutron.create_subnet({'subnet': subnet_info})
         subnet = (self.provider.neutron.create_subnet({'subnet': subnet_info})
                   .get('subnet'))
                   .get('subnet'))
-        return OpenStackSubnet(self.provider, subnet)
+        cb_subnet = OpenStackSubnet(self.provider, subnet)
+        cb_subnet.label = label
+        return cb_subnet
 
 
     def get_or_create_default(self, zone):
     def get_or_create_default(self, zone):
         """
         """
         Subnet zone is not supported by OpenStack and is thus ignored.
         Subnet zone is not supported by OpenStack and is thus ignored.
         """
         """
         try:
         try:
-            sn = self.find(name=OpenStackSubnet.CB_DEFAULT_SUBNET_NAME)
+            sn = self.find(label=OpenStackSubnet.CB_DEFAULT_SUBNET_LABEL)
             if sn:
             if sn:
                 return sn[0]
                 return sn[0]
             # No default; create one
             # No default; create one
             net = self.provider.networking.networks.create(
             net = self.provider.networking.networks.create(
-                name=OpenStackNetwork.CB_DEFAULT_NETWORK_NAME,
+                label=OpenStackNetwork.CB_DEFAULT_NETWORK_LABEL,
                 cidr_block='10.0.0.0/16')
                 cidr_block='10.0.0.0/16')
-            sn = net.create_subnet(name=OpenStackSubnet.CB_DEFAULT_SUBNET_NAME,
-                                   cidr_block='10.0.0.0/24')
+            sn = net.create_subnet(
+                label=OpenStackSubnet.CB_DEFAULT_SUBNET_LABEL,
+                cidr_block='10.0.0.0/24')
             router = self.provider.networking.routers.create(
             router = self.provider.networking.routers.create(
-                network=net, name=OpenStackRouter.CB_DEFAULT_ROUTER_NAME)
+                network=net, label=OpenStackRouter.CB_DEFAULT_ROUTER_LABEL)
             router.attach_subnet(sn)
             router.attach_subnet(sn)
-            gteway = net.gateways.get_or_create_inet_gateway(
-                        OpenStackInternetGateway.CB_DEFAULT_INET_GATEWAY_NAME)
+            gteway = net.gateways.get_or_create_inet_gateway()
             router.attach_gateway(gteway)
             router.attach_gateway(gteway)
             return sn
             return sn
         except NeutronClientException:
         except NeutronClientException:
@@ -924,11 +922,11 @@ class OpenStackRouterService(BaseRouterService):
 
 
     def find(self, **kwargs):
     def find(self, **kwargs):
         obj_list = self
         obj_list = self
-        filters = ['name']
+        filters = ['label']
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         matches = cb_helpers.generic_find(filters, kwargs, obj_list)
         return ClientPagedResultList(self._provider, list(matches))
         return ClientPagedResultList(self._provider, list(matches))
 
 
-    def create(self, name, network):
+    def create(self, label, network):
         """
         """
         ``network`` is not used by OpenStack.
         ``network`` is not used by OpenStack.
 
 
@@ -936,9 +934,9 @@ class OpenStackRouterService(BaseRouterService):
         https://developer.openstack.org/api-ref/networking/v2/
         https://developer.openstack.org/api-ref/networking/v2/
             ?expanded=delete-router-detail,create-router-detail#create-router
             ?expanded=delete-router-detail,create-router-detail#create-router
         """
         """
-        log.debug("Creating OpenStack Router with the name: %s", name)
-        OpenStackRouter.assert_valid_resource_name(name)
+        log.debug("Creating OpenStack Router with the label: %s", label)
+        OpenStackRouter.assert_valid_resource_label(label)
 
 
-        body = {'router': {'name': name}} if name else None
+        body = {'router': {'name': label}} if label else None
         router = self.provider.neutron.create_router(body)
         router = self.provider.neutron.create_router(body)
         return OpenStackRouter(self.provider, router.get('router'))
         return OpenStackRouter(self.provider, router.get('router'))

+ 2 - 2
docs/api_docs/cloud/exceptions.rst

@@ -23,9 +23,9 @@ ProviderConnectionException
 .. autoclass:: cloudbridge.cloud.interfaces.exceptions.ProviderConnectionException
 .. autoclass:: cloudbridge.cloud.interfaces.exceptions.ProviderConnectionException
     :members:
     :members:
 
 
-InvalidNameException
+InvalidLabelException
 -----------------------------
 -----------------------------
-.. autoclass:: cloudbridge.cloud.interfaces.exceptions.InvalidNameException
+.. autoclass:: cloudbridge.cloud.interfaces.exceptions.InvalidLabelException
     :members:
     :members:
 
 
 InvalidValueException
 InvalidValueException

+ 34 - 0
docs/concepts.rst

@@ -1,6 +1,9 @@
 Concepts and Organisation
 Concepts and Organisation
 =========================
 =========================
 
 
+Object types
+------------
+
 Conceptually, CloudBridge consists of the following types of objects.
 Conceptually, CloudBridge consists of the following types of objects.
 
 
 1. Providers - Represents a connection to a cloud provider, and is
 1. Providers - Represents a connection to a cloud provider, and is
@@ -25,6 +28,37 @@ an instance. Similarly, VolumeService.create() will return a Volume object.
 
 
 The actual source code structure of CloudBridge also mirrors this organisation.
 The actual source code structure of CloudBridge also mirrors this organisation.
 
 
+Object identification and naming
+---------------------------------
+
+In order to function uniformly across across cloud providers, object identity
+and naming must be conceptually consistent. In CloudBridge, there are three
+main properties for identifying and naming an object.
+
+1.Id - The `id` corresponds to a unique identifier that can be reliably used to
+reference a resource. All CloudBridge resources have an id. Most methods in
+CloudBridge services, such as `get`, use the `id` property to identify and
+retrieve objects.
+
+2. Name - The `name` property is a more human-readable identifier for
+a particular resource, and is often useful to display to the end user instead
+of the `id`. While it is often unique, it is not guaranteed to be so, and
+therefore, the `id` property must always be used for uniquely identifying
+objects. All CloudBridge resources have a `name` property. The `name` property
+is often assigned during resource creation, and is often derived from the
+`label` property by appending some unique characters to it. Once assigned
+however, it is unchangeable.
+
+3. Label - Most resources also support a `label` property, which is a user
+changeable value that can be used to describe an object. When creating
+resources, cloudbridge often accepts a `label` property as a parameter.
+The `name` property is derived from the `label`, by appending some unique
+characters to it. However, there are some resources which do not support a
+`label` property, such as key pairs and buckets. In the latter case, the
+`name` can be specified during resource creation, but cannot be changed
+thereafter.
+
+
 Detailed class relationships
 Detailed class relationships
 ----------------------------
 ----------------------------
 
 

+ 20 - 11
docs/getting_started.rst

@@ -86,7 +86,7 @@ Once you have a reference to a provider, explore the cloud platform:
 
 
 .. code-block:: python
 .. code-block:: python
 
 
-    provider.security.security_groups.list()
+    provider.security.firewalls.list()
     provider.compute.vm_types.list()
     provider.compute.vm_types.list()
     provider.storage.snapshots.list()
     provider.storage.snapshots.list()
     provider.storage.buckets.list()
     provider.storage.buckets.list()
@@ -116,10 +116,10 @@ attaching an internet gateway to the subnet via a router.
 
 
 .. code-block:: python
 .. code-block:: python
 
 
-    net = provider.networking.networks.create(
-        name='my-network', cidr_block='10.0.0.0/16')
-    sn = net.create_subnet(name='my-subnet', cidr_block='10.0.0.0/28')
-    router = provider.networking.routers.create(network=net, name='my-router')
+    net = provider.networking.networks.create(cidr_block='10.0.0.0/16',
+                                              label='my-network')
+    sn = net.create_subnet(cidr_block='10.0.0.0/28', label='my-subnet')
+    router = provider.networking.routers.create(network=net, label='my-router')
     router.attach_subnet(sn)
     router.attach_subnet(sn)
     gateway = net.gateways.get_or_create_inet_gateway(name='my-gateway')
     gateway = net.gateways.get_or_create_inet_gateway(name='my-gateway')
     router.attach_gateway(gateway)
     router.attach_gateway(gateway)
@@ -135,7 +135,8 @@ a private network.
 
 
     from cloudbridge.cloud.interfaces.resources import TrafficDirection
     from cloudbridge.cloud.interfaces.resources import TrafficDirection
     fw = provider.security.vm_firewalls.create(
     fw = provider.security.vm_firewalls.create(
-        'cloudbridge-intro', 'A VM firewall used by CloudBridge', net.id)
+        label='cloudbridge-intro', description='A VM firewall used by
+        CloudBridge', network=net.id)
     fw.rules.create(TrafficDirection.INBOUND, 'tcp', 22, 22, '0.0.0.0/0')
     fw.rules.create(TrafficDirection.INBOUND, 'tcp', 22, 22, '0.0.0.0/0')
 
 
 Launch an instance
 Launch an instance
@@ -151,7 +152,7 @@ also add the network interface as a launch argument.
                       if t.vcpus >= 2 and t.ram >= 4],
                       if t.vcpus >= 2 and t.ram >= 4],
                       key=lambda x: x.vcpus*x.ram)[0]
                       key=lambda x: x.vcpus*x.ram)[0]
     inst = provider.compute.instances.create(
     inst = provider.compute.instances.create(
-        name='cloudbridge-intro', image=img, vm_type=vm_type,
+        image=img, vm_type=vm_type, label='cloudbridge-intro',
         subnet=sn, key_pair=kp, vm_firewalls=[fw])
         subnet=sn, key_pair=kp, vm_firewalls=[fw])
     # Wait until ready
     # Wait until ready
     inst.wait_till_ready()  # This is a blocking call
     inst.wait_till_ready()  # This is a blocking call
@@ -190,10 +191,10 @@ From the command prompt, you can now ssh into the instance
 Get a resource
 Get a resource
 --------------
 --------------
 When a resource already exists, a reference to it can be retrieved using either
 When a resource already exists, a reference to it can be retrieved using either
-its ID or name. It is important to note that while IDs are unique, multiple
-resources of the same type could use the same name on some providers, thus the
+its ID, name, or label. It is important to note that while IDs and names are
+unique, multiple resources of the same type could use the same label, thus the
 `find` method always returns a list, while the `get` method returns a single
 `find` method always returns a list, while the `get` method returns a single
-object. While the methods are similar across resources, they are explicitely
+object. While the methods are similar across resources, they are explicitly
 listed in order to help map each resource with the service that handles it.
 listed in order to help map each resource with the service that handles it.
 
 
 .. code-block:: python
 .. code-block:: python
@@ -206,19 +207,24 @@ listed in order to help map each resource with the service that handles it.
     # Network
     # Network
     net = provider.networking.networks.get('network ID')
     net = provider.networking.networks.get('network ID')
     net_list = provider.networking.networks.find(name='my-network')
     net_list = provider.networking.networks.find(name='my-network')
+    net_list = provider.networking.networks.find(label='my-network')
     net = net_list[0]
     net = net_list[0]
 
 
     # Subnet
     # Subnet
     sn = provider.networking.subnets.get('subnet ID')
     sn = provider.networking.subnets.get('subnet ID')
     # Unknown network
     # Unknown network
     sn_list = provider.networking.subnets.find(name='my-subnet')
     sn_list = provider.networking.subnets.find(name='my-subnet')
+    sn_list = provider.networking.subnets.find(label='my-subnet')
     # Known network
     # Known network
     sn_list = provider.networking.subnets.find(network=net.id, name='my-subnet')
     sn_list = provider.networking.subnets.find(network=net.id, name='my-subnet')
+    sn_list = provider.networking.subnets.find(network=net.id,
+                                               label='my-subnet')
     sn = sn_list(0)
     sn = sn_list(0)
 
 
     # Router
     # Router
     router = provider.networking.routers.get('router ID')
     router = provider.networking.routers.get('router ID')
     router_list = provider.networking.routers.find(name='my-router')
     router_list = provider.networking.routers.find(name='my-router')
+    router_list = provider.networking.routers.find(label='my-router')
     router = router_list[0]
     router = router_list[0]
 
 
     # Gateway
     # Gateway
@@ -228,18 +234,21 @@ listed in order to help map each resource with the service that handles it.
     fip = gateway.floating_ips.get('FloatingIP ID')
     fip = gateway.floating_ips.get('FloatingIP ID')
     # Find using public IP address
     # Find using public IP address
     fip_list = gateway.floating_ips.find(public_ip='IP address')
     fip_list = gateway.floating_ips.find(public_ip='IP address')
-    # Find using tagged name
+    # Find using name or tag
     fip_list = net.gateways.floating_ips.find(name='my-fip')
     fip_list = net.gateways.floating_ips.find(name='my-fip')
+    fip_list = net.gateways.floating_ips.find(label='my-fip')
     fip = fip_list[0]
     fip = fip_list[0]
 
 
     # Firewall
     # Firewall
     fw = provider.security.vm_firewalls.get('firewall ID')
     fw = provider.security.vm_firewalls.get('firewall ID')
     fw_list = provider.security.vm_firewalls.find(name='cloudbridge-intro')
     fw_list = provider.security.vm_firewalls.find(name='cloudbridge-intro')
+    fw_list = provider.security.vm_firewalls.find(label='cloudbridge-intro')
     fw = fw_list[0]
     fw = fw_list[0]
 
 
     # Instance
     # Instance
     inst = provider.compute.instances.get('instance ID')
     inst = provider.compute.instances.get('instance ID')
     inst_list = provider.compute.instances.list(name='cloudbridge-intro')
     inst_list = provider.compute.instances.list(name='cloudbridge-intro')
+    inst_list = provider.compute.instances.list(label='cloudbridge-intro')
     inst = inst_list[0]
     inst = inst_list[0]
 
 
 
 

BIN
docs/topics/captures/az-label-dash.png


BIN
docs/topics/captures/az-net-id.png


BIN
docs/topics/captures/az-net-label.png


+ 1 - 1
docs/topics/contributor_guide.rst

@@ -8,7 +8,7 @@ CloudBridge Provider.
    :maxdepth: 1
    :maxdepth: 1
 
 
     Design Goals <design_goals.rst>
     Design Goals <design_goals.rst>
-    Design Decisions <design-decisions.rst>
+    Design Decisions <design_decisions.rst>
     Testing <testing.rst>
     Testing <testing.rst>
     Provider Development Walkthrough <provider_development.rst>
     Provider Development Walkthrough <provider_development.rst>
     Release Process <release_process.rst>
     Release Process <release_process.rst>

+ 121 - 0
docs/topics/dashboard.rst

@@ -0,0 +1,121 @@
+Dashboard Mapping
+=================
+
+Cross-Platform Concepts
+-----------------------
+
+Given cloudbridge's goal to work uniformly across cloud providers, some
+compromises were necessary in order to bridge the many differences between
+providers' resources and features. Notably, in order to create a robust and
+conceptually consistent cross-cloud library, resources were given three main
+properties: ID, name, and label.
+The `ID` corresponds to a unique identifier that can be reliably used to
+reference a resource. Users can safely use an ID knowing that it will always
+point to the same resource.
+The `name` property corresponds to an unchangeable and unique designation for
+a particular resource. This property is meant to be, in some ways, a more
+human-readable identifier. However, when no conceptually comparable property
+exists for a given resource in a particular provider, the ID is returned
+instead, as is the case for OpenStack resources. When the name can be
+determined by a user at resource creation, either the name parameter will be
+used for resources that support it, or the label will be used, when provided
+as a prefix, with an appended uuid to ensure that the name remains unique.
+The `label` property, conversely, is a changeable value that does not need
+to be unique. Unlike the name property, it is not used to identify a
+particular resource, but rather label a resource for easier distinction. It
+is however important to note that not all resources support labels. When
+supported, labels given at creation will also be used as a prefix to the name.
+
+Properties per Resource per Provider
+------------------------------------
+The sections below will present a summary table detailing the cloudbridge
+properties implemented for each resource, and their corresponding value in
+the provider's dashboard.
+
+Azure
+-----
++-----------------------------------+-------+---------------+---------------+
+| CloudServiceType                 	| CB_ID	| CB_Name      	| CB_Label  	|
++===================================+=======+===============+===============+
+| Instance                         	| ID   	| Name       	| Tags:Label 	|
++-----------------------------------+-------+---------------+---------------+
+| MachineImage (Private)           	| ID   	| Name       	| Tags:Label 	|
+| MachineImage (Gallery Reference) 	| URN  	| URN        	| URN        	|
++-----------------------------------+-------+---------------+---------------+
+| Network                          	| ID   	| Name       	| Tags:Label 	|
++-----------------------------------+-------+---------------+---------------+
+| Subnet                           	| ID   	| Name       	| Tags:Label 	|
++-----------------------------------+-------+---------------+---------------+
+| FloatingIP                       	| ID   	| Name       	| Tags:Label 	|
++-----------------------------------+-------+---------------+---------------+
+| Router                           	| ID   	| Name       	| Tags:Label 	|
++-----------------------------------+-------+---------------+---------------+
+| InternetGateway                  	| None 	| None       	| -          	|
++-----------------------------------+-------+---------------+---------------+
+| Volume                           	| ID   	| Name       	| Tags:Label 	|
++-----------------------------------+-------+---------------+---------------+
+| Snapshot                         	| ID   	| Name       	| Tags:Label 	|
++-----------------------------------+-------+---------------+---------------+
+| KeyPair                          	| Name 	| Name       	| -          	|
++-----------------------------------+-------+---------------+---------------+
+| VMFirewall                       	| ID   	| Name       	| Tags:Label 	|
++-----------------------------------+-------+---------------+---------------+
+| VMFirewallRule                   	| ID   	| Name       	| -          	|
++-----------------------------------+-------+---------------+---------------+
+| Bucket                           	| Name 	| Name       	| -          	|
++-----------------------------------+-------+---------------+---------------+
+| BucketObject                     	| Name 	| Name       	| -          	|
++-----------------------------------+-------+---------------+---------------+
+
+One of the major discrepancies in Azure is the non-existence of an Internet
+Gateway. In fact, Azure resources are automatically exposed to the internet,
+and thus an internet gateway object is not necessary for this purpose. Thus,
+a gateway object created through cloudbridge in Azure will not appear on the
+dashboard, as a cloudbridge-level wrapper object is returned when trying to
+create or get a gateway, but no object corresponds to that concept in Azure.
+For a succinct comparison between AWS Gateways and Azure, see:
+https://social.msdn.microsoft.com/Forums/en-US/
+814ccee0-9fbb-4c04-8135-49d0aaea5f38/
+equivalent-of-aws-internet-gateways-in-azure?
+forum=WAVirtualMachinesVirtualNetwork
+
+
+.. figure:: captures/az-label-dash.png
+   :scale: 50 %
+   :alt: name and label properties in Azure portal
+
+   The cloudbridge `name` property always maps to the unchangeable resource
+   name in Azure. The `label` property maps to the tag with key 'Label' in
+   Azure. By default, this label will appear in the tags column, but can also
+   be made into its own column, following the button indicated in the
+   screenshot above.
+
+.. figure:: captures/az-net-id.png
+   :scale: 50 %
+   :alt: network id in Azure portal
+
+   The cloudbridge `ID` property most often maps to the Resource ID in Azure,
+   which can be found under the properties tab within a resource. The above
+   screenshot shows where to find a resource's label in Azure's web portal
+
+.. figure:: captures/az-net-label.png
+   :scale: 50 %
+   :alt: network label in Azure portal
+
+   The cloudbridge `label` property most often maps to the tag with key
+   'Label' in Azure, which can be found under the tags tab within a resource.
+   The above screenshot shows where to find a resource's label in Azure's
+   web portal.
+
+.. figure:: captures/az-storacc.png
+   :scale: 50 %
+   :alt: storage account in Azure portal
+
+   Bucket and Key Pair objects are different than other resources in Azure,
+   as they are not resources residing in a resource group, but rather reside
+   in a storage account. As a result of this difference, these resources do
+   not support labels, and cannot be seen on the default dashboard. In order
+   to find these resources in the Azure web portal, one must head to the
+   storage account containing them, and look in the `Blobs` and `Tables`
+   services respectively for `Buckets` and `KeyPairs`.
+

+ 0 - 37
docs/topics/design-decisions.rst

@@ -1,37 +0,0 @@
-Design decisions
-~~~~~~~~~~~~~~~~
-
-This document captures outcomes and, in some cases, the through process behind
-some of the design decisions that took place while architecting CloudBridge.
-It is intended as a reference.
-
-- **Require zone parameter when creating a default subnet.**
-
-  Placement zone is required because it is an explicit application decision,
-  even though ideally *default* would not require input. Before requiring it,
-  the implementations would create a subnet in each availability zone and return
-  the first one in the list. This could potentially return different values over
-  time. Another factor influencing the decision was the example of creating a
-  volume followed by creating an instance with presumably the two needing to be
-  in the same zone. By requiring the zone across the board, it is less likely to
-  lead to a miss match. (Related to 63_.)
-
-- **Name property updates will result in cloud-dependent code.**
-
-  Some providers (e.g., GCE, Azure) do not allow names of resources to be
-  changed after a resource has been created. Similarly, AWS does not allow VM
-  firewall (i.e., security group) names to be changed. Providers seem to be
-  gravitating toward use of tags (or labels) to support arbitrary naming and
-  name changes. Yet, OpenStack for example, does not have a concept of resource
-  tags so CloudBridge cannot rely solely on tags. Further, tags do not need to
-  be unique across multiple resources, while names do (at least for some
-  resources, such as vmfirewalls within a private network). Overall, consistency
-  is challenging to achieve with resource renaming. With that, CloudBridge will
-  support resource renaming to the best extent possible and balance between the
-  use of resource name property and resource tags. However, because of the
-  inconsistency of rename functionality across the providers, using the rename
-  capabilities within CloudBridge will lead to cloud-dependent code. (Related to
-  131_.)
-
-  .. _63: https://github.com/CloudVE/cloudbridge/issues/63
-  .. _131: https://github.com/CloudVE/cloudbridge/issues/131

+ 118 - 0
docs/topics/design_decisions.rst

@@ -0,0 +1,118 @@
+Design decisions
+~~~~~~~~~~~~~~~~
+
+This document captures outcomes and, in some cases, the through process behind
+some of the design decisions that took place while architecting CloudBridge.
+It is intended as a reference.
+
+- **Require zone parameter when creating a default subnet.**
+
+  Placement zone is required because it is an explicit application decision,
+  even though ideally *default* would not require input. Before requiring it,
+  the implementations would create a subnet in each availability zone and return
+  the first one in the list. This could potentially return different values over
+  time. Another factor influencing the decision was the example of creating a
+  volume followed by creating an instance with presumably the two needing to be
+  in the same zone. By requiring the zone across the board, it is less likely to
+  lead to a miss match. (Related to 63_.)
+
+- **Resource identification, naming and labeling**
+
+  While it would be reasonable to expect that complex constructs like
+  networking would be the most difficult to abstract away uniformly across
+  providers, in retrospect, simple naming of objects has arguably been the most
+  complex and convoluted to map consistently. CloudBridge has been through
+  several iterations of naming and labeling, before finally settling on the
+  current design. This section captures that history and design rationale.
+
+  ***First iteration***
+  In the early days, when CloudBridge supported only AWS and OpenStack, there
+  were only two concepts, id and name. The id was straightforward enough, as it
+  usually mapped to a unique identifier, auto-generated by the provider. The
+  name generally mapped to a tag in the case of AWS, and a name field in the
+  case of OpenStack. However, even then, there were inconsistencies within
+  individual providers. For example, while AWS generally supported tags, it had
+  a dedicated name field for machine images called ami-name. Furthermore, this
+  name field could only be set at image creation time, and could not be changed
+  thereafter. Similarly, AWS does not allow VM firewall (i.e., security group)
+  names to be changed. Nevertheless, CloudBridge continued to use id and name,
+  with the name being changeable for some resources, and read-only in others.
+  
+  As CloudBridge evolved and support was added for Azure and GCE, things only
+  became more complex. Some providers (e.g. Azure and GCE) used a user-provided
+  value instead of an auto-generated value as an `id`, which would also be
+  displayed in their respective dashboards as `Name`. This meant that they were
+  treating their servers as individually named pets, instead of adopting the
+  cattle model, should one be tempted to use that macabre `pets vs cattle`_
+  analogy. These user provided names could not be changed after a resource had
+  been created. Instead, these providers seemed to be gravitating toward the
+  use of tags (or labels) to support arbitrary naming and name changes. Yet,
+  not all resources support tags so CloudBridge could not rely solely on tags.
+  Further, tags do not need to be unique across multiple resources, while names
+  do (at least for some resources, such as vmfirewalls within a private
+  network). Overall, consistency was challenging to achieve with resource
+  naming. Therefore, it was decided that CloudBridge would continue to support
+  resource renaming to the best extent possible and balance between the
+  use of the resource name property and resource tags. However, because of the
+  inconsistency in rename functionality across providers, using the rename
+  capabilities within CloudBridge would lead to cloud-dependent code (Related to
+  131_.) and therefore, the only option was to continue to stick a caveat emptor
+  to resource renaming.
+  
+  ***Second iteration***
+  However, it soon became apparent that this overloaded terminology was
+  continuing to cause confusion. The `id` property in cloudbridge mapped to the
+  unchangeable name property in Azure and GCE, and the `name` property in
+  cloudbridge sometimes mapped to a tag in certain providers, and a name in
+  other providers and they were sometimes read-only, sometimes writable. In an
+  attempt to disambiguate these concepts, it was then decided that perhaps
+  three concepts were needed - id, display_id and label.
+  The id would continue to refer to a unique identifier for a resource and be
+  mapped accordingly to the underlying provider. The display_id would be a more
+  user-friendly version of an id, suitable for display to an end-user and be
+  unchangeable, but on rare occasions, not unique. For example, ami-name was a
+  `display_id` while the ami-id was an `id`. Similarly, an Azure resource name
+  mapped to the `display_id`, since it was an unchangeable, user-friendly
+  identifier. Finally, label was a changeable, user-assignable value that would
+  be mapped often to a tag on the resource, or the name of the resource, should
+  the name be changeable. This clearly disambiguated between unique
+  identifiers, user-assignable values and read-only, user-friendly values. At
+  object creation, all services would accept a label as an optional parameter.
+  If provided, the display_id would sometimes be derived from the label by
+  appending a uuid to the label, depending on the provider. At other times, it
+  would simply map to an id.
+  
+  ***Third iteration***
+  It soon became apparent that some resources like keypairs could not have a
+  label at all, yet needed to be named during object creation. However, we
+  could not use display_id for this purpose became the display_id, by
+  definition, is unchangeable. It could not be called label because the label,
+  in contrast, was changeable. Therefore, it seemed like we were back to
+  calling it `name` instead, introducing yet a fourth term. To simplify this,
+  it was then decided that `display_id` and `name` would be collapsed together
+  into one term and be called `name` instead. All resources would have an `id`
+  and a `name`, and resources that support it would have a `label`. To make
+  things even simpler and consistent, it was also decided that label would be
+  made mandatory for all resources during object creation, and follow the same
+  restrictions as name, which is to have a 3 character minimum. (this was to
+  deal with an exception in OpenStack, where the label mapped to instance name,
+  but could not be empty. Therefore, by making all labels mandatory and adhere
+  to minimum length restrictions, we could make the overall conventions uniform
+  across all resources and therefore easier to remember and enforce)
+  
+  ***TL;DR***
+  CloudBridge has three concepts when it comes to naming and identifying
+  objects. The `id` is a unique identifier for an object, always
+  auto-generated. The `name` is a read-only, user-friendly value which is
+  suitable for display to the end-user. The `label` is a user-assignable value
+  that can be changed. The `name` is often derived from the `label` but not
+  always. Not all resources support `labels`. Some only accept `names` which
+  can be specified at object creation time (e.g. keypairs). Both `names` and
+  `labels` adhere to the same restrictions - a minimum length of 3 which
+  should be alphanumeric characters or dashes only. Names or labels should
+  not begin or end with a dash, or have consecutive dashes.
+   
+
+  .. _63: https://github.com/CloudVE/cloudbridge/issues/63
+  .. _131: https://github.com/CloudVE/cloudbridge/issues/131
+  .. _pets vs cattle: http://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/

+ 1 - 1
docs/topics/networking.rst

@@ -94,7 +94,7 @@ The additional step that's required here is to assign a floating IP to the VM:
 
 
     router = provider.networking.routers.create(network=net, name='my-router')
     router = provider.networking.routers.create(network=net, name='my-router')
     router.attach_subnet(sn)
     router.attach_subnet(sn)
-    gateway = net.gateways.get_or_create_inet_gateway(net, name)
+    gateway = net.gateways.get_or_create_inet_gateway(name)
     router.attach_gateway(gateway)
     router.attach_gateway(gateway)
 
 
     fip = provider.networking.floating_ips.create()
     fip = provider.networking.floating_ips.create()

+ 19 - 17
docs/topics/setup.rst

@@ -164,20 +164,22 @@ In addition to the provider specific configuration variables above, there are
 some general configuration environment variables that apply to CloudBridge as
 some general configuration environment variables that apply to CloudBridge as
 a whole
 a whole
 
 
-======================  ==================
-Variable		            Description
-======================  ==================
-CB_DEBUG                Setting ``CB_DEBUG=True`` will cause detailed debug
-                        output to be printed for each provider (including HTTP
-                        traces).
-CB_USE_MOCK_PROVIDERS   Setting this to ``True`` will cause the CloudBridge test
-                        suite to use mock drivers when available.
-CB_TEST_PROVIDER        Set this value to a valid :class:`.ProviderList` value
-                        such as ``aws``, to limit tests to that provider only.
-CB_DEFAULT_SUBNET_NAME  Name to be used for a subnet that will be considered
-                        the 'default' by the library. This default will be used
-                        only in cases there is no subnet marked as the default by the provider.
-CB_DEFAULT_NETWORK_NAME Name to be used for a network that will be considered
-                        the 'default' by the library. This default will be used
-                        only in cases there is no network marked as the default by the provider.
-======================= ==================
+======================== ======================================================
+Variable		                            Description
+======================== ======================================================
+CB_DEBUG                 Setting ``CB_DEBUG=True`` will cause detailed debug
+                         output to be printed for each provider (including HTTP
+                         traces).
+CB_USE_MOCK_PROVIDERS    Setting this to ``True`` will cause the CloudBridge
+                         test suite to use mock drivers when available.
+CB_TEST_PROVIDER         Set this value to a valid :class:`.ProviderList` value
+                         such as ``aws``, to limit tests to that provider only.
+CB_DEFAULT_SUBNET_LABEL  Name to be used for a subnet that will be considered
+                         the 'default' by the library. This default will be
+                         used only in cases there is no subnet marked as the
+                         default by the provider.
+CB_DEFAULT_NETWORK_LABEL Name to be used for a network that will be considered
+                         the 'default' by the library. This default will be
+                         used only in cases there is no network marked as the
+                         default by the provider.
+======================== ======================================================

+ 4 - 2
docs/topics/testing.rst

@@ -48,10 +48,12 @@ against a specific infrastructure, say aws, use a command like this:
 Specific test cases
 Specific test cases
 ~~~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~~~
 You can run a specific test case, as follows:
 You can run a specific test case, as follows:
-``tox -- -s test.test_cloud_factory.CloudFactoryTestCase``
+``tox -- test/test_image_service.py:CloudImageServiceTestCase.test_create_and_list_imag``
 
 
 It can also be restricted to a particular environment as follows:
 It can also be restricted to a particular environment as follows:
-``tox -e "py27-aws" -- -s test.test_cloud_factory.CloudFactoryTestCase``
+``tox -e "py27-aws" -- test/test_cloud_factory.py:CloudFactoryTestCase``
+
+See nosetest documentation for other parameters that can be passed in.
 
 
 Using unittest directly
 Using unittest directly
 ~~~~~~~~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~~~~~~~~

+ 1 - 0
setup.cfg

@@ -3,6 +3,7 @@ branch = True
 source = cloudbridge
 source = cloudbridge
 omit =
 omit =
   cloudbridge/cloud/interfaces/*
   cloudbridge/cloud/interfaces/*
+  cloudbridge/__init__.py
 
 
 [nosetests]
 [nosetests]
 with-coverage=1
 with-coverage=1

+ 1 - 1
setup.py

@@ -23,7 +23,7 @@ REQS_BASE = [
     'six>=1.10.0',
     'six>=1.10.0',
     'tenacity>=4.12.0'
     'tenacity>=4.12.0'
 ]
 ]
-REQS_AWS = ['boto3']
+REQS_AWS = ['boto3<1.8.0']
 # Install azure>=3.0.0 package to find which of the azure libraries listed
 # Install azure>=3.0.0 package to find which of the azure libraries listed
 # below are compatible with each other. List individual libraries instead
 # below are compatible with each other. List individual libraries instead
 # of using the azure umbrella package to speed up installation.
 # of using the azure umbrella package to speed up installation.

+ 23 - 30
test/helpers/__init__.py

@@ -86,10 +86,10 @@ TEST_DATA_CONFIG = {
         "placement": get_env('CB_PLACEMENT_AWS', 'us-east-1a'),
         "placement": get_env('CB_PLACEMENT_AWS', 'us-east-1a'),
     },
     },
     "OpenStackCloudProvider": {
     "OpenStackCloudProvider": {
-        "image": get_env('CB_IMAGE_OS',
-                         'acb53109-941f-4593-9bf8-4a53cb9e0739'),
-        "vm_type": get_env('CB_VM_TYPE_OS', 'm1.tiny'),
-        "placement": get_env('CB_PLACEMENT_OS', 'zone-r1'),
+        "image": os.environ.get('CB_IMAGE_OS',
+                                'c66bdfa1-62b1-43be-8964-e9ce208ac6a5'),
+        "vm_type": os.environ.get('CB_VM_TYPE_OS', 'm1.tiny'),
+        "placement": os.environ.get('CB_PLACEMENT_OS', 'nova'),
     },
     },
     "AzureCloudProvider": {
     "AzureCloudProvider": {
         "placement":
         "placement":
@@ -113,16 +113,12 @@ def get_provider_test_data(provider, key):
     return None
     return None
 
 
 
 
-def create_test_network(provider, name):
+def get_or_create_default_subnet(provider):
     """
     """
-    Create a network with one subnet, returning the network and subnet objects.
+    Return the default subnet to be used for tests
     """
     """
-    net = provider.networking.networks.create(name=name,
-                                              cidr_block='10.0.0.0/16')
-    cidr_block = (net.cidr_block).split('/')[0] or '10.0.0.1'
-    sn = net.create_subnet(cidr_block='{0}/28'.format(cidr_block), name=name,
-                           zone=get_provider_test_data(provider, 'placement'))
-    return net, sn
+    return provider.networking.subnets.get_or_create_default(
+        zone=get_provider_test_data(provider, 'placement'))
 
 
 
 
 def delete_test_network(network):
 def delete_test_network(network):
@@ -135,34 +131,31 @@ def delete_test_network(network):
                 pass
                 pass
 
 
 
 
-def get_test_gateway(provider, name):
+def get_test_gateway(provider):
     """
     """
     Get an internet gateway for testing.
     Get an internet gateway for testing.
 
 
     This includes creating a network for the gateway, which is also returned.
     This includes creating a network for the gateway, which is also returned.
     """
     """
-    net_name = 'cb_testgwnet-{0}'.format(get_uuid())
-    net = provider.networking.networks.create(
-        name=net_name, cidr_block='10.0.0.0/16')
-    return net, net.gateways.get_or_create_inet_gateway(name)
+    sn = get_or_create_default_subnet(provider)
+    net = sn.network
+    return net.gateways.get_or_create_inet_gateway()
 
 
 
 
-def delete_test_gateway(network, gateway):
+def delete_test_gateway(gateway):
     """
     """
     Delete the supplied network and gateway.
     Delete the supplied network and gateway.
     """
     """
-    with cleanup_action(lambda: network.delete()):
-        with cleanup_action(lambda: gateway.delete()):
-            pass
+    with cleanup_action(lambda: gateway.delete()):
+        pass
 
 
 
 
 def create_test_instance(
 def create_test_instance(
-        provider, instance_name, subnet, launch_config=None,
+        provider, instance_label, subnet, launch_config=None,
         key_pair=None, vm_firewalls=None, user_data=None):
         key_pair=None, vm_firewalls=None, user_data=None):
 
 
     instance = provider.compute.instances.create(
     instance = provider.compute.instances.create(
-        instance_name,
-        get_provider_test_data(provider, 'image'),
+        instance_label, get_provider_test_data(provider, 'image'),
         get_provider_test_data(provider, 'vm_type'),
         get_provider_test_data(provider, 'vm_type'),
         subnet=subnet,
         subnet=subnet,
         zone=get_provider_test_data(provider, 'placement'),
         zone=get_provider_test_data(provider, 'placement'),
@@ -174,12 +167,12 @@ def create_test_instance(
     return instance
     return instance
 
 
 
 
-def get_test_instance(provider, name, key_pair=None, vm_firewalls=None,
+def get_test_instance(provider, label, key_pair=None, vm_firewalls=None,
                       subnet=None, user_data=None):
                       subnet=None, user_data=None):
     launch_config = None
     launch_config = None
     instance = create_test_instance(
     instance = create_test_instance(
         provider,
         provider,
-        name,
+        label,
         subnet=subnet,
         subnet=subnet,
         key_pair=key_pair,
         key_pair=key_pair,
         vm_firewalls=vm_firewalls,
         vm_firewalls=vm_firewalls,
@@ -200,8 +193,8 @@ def delete_test_instance(instance):
                           terminal_states=[InstanceState.ERROR])
                           terminal_states=[InstanceState.ERROR])
 
 
 
 
-def cleanup_test_resources(instance=None, network=None, vm_firewall=None,
-                           key_pair=None):
+def cleanup_test_resources(instance=None, vm_firewall=None,
+                           key_pair=None, network=None):
     """Clean up any combination of supplied resources."""
     """Clean up any combination of supplied resources."""
     with cleanup_action(lambda: delete_test_network(network)
     with cleanup_action(lambda: delete_test_network(network)
                         if network else None):
                         if network else None):
@@ -212,7 +205,7 @@ def cleanup_test_resources(instance=None, network=None, vm_firewall=None,
 
 
 
 
 def get_uuid():
 def get_uuid():
-    return str(uuid.uuid4())
+    return str(uuid.uuid4())[:6]
 
 
 
 
 class ProviderTestBase(unittest.TestCase):
 class ProviderTestBase(unittest.TestCase):
@@ -243,7 +236,7 @@ class ProviderTestBase(unittest.TestCase):
                                                     get_mock=use_mock_drivers)
                                                     get_mock=use_mock_drivers)
         config = {'default_wait_interval':
         config = {'default_wait_interval':
                   self.get_provider_wait_interval(provider_class),
                   self.get_provider_wait_interval(provider_class),
-                  'default_result_limit': 1}
+                  'default_result_limit': 5}
         return provider_class(config)
         return provider_class(config)
 
 
     @property
     @property

+ 106 - 35
test/helpers/standard_interface_tests.py

@@ -7,8 +7,11 @@ This includes:
 """
 """
 import uuid
 import uuid
 
 
+import tenacity
+
 from cloudbridge.cloud.interfaces.exceptions \
 from cloudbridge.cloud.interfaces.exceptions \
     import InvalidNameException
     import InvalidNameException
+from cloudbridge.cloud.interfaces.resources import LabeledCloudResource
 from cloudbridge.cloud.interfaces.resources import ObjectLifeCycleMixin
 from cloudbridge.cloud.interfaces.resources import ObjectLifeCycleMixin
 from cloudbridge.cloud.interfaces.resources import ResultList
 from cloudbridge.cloud.interfaces.resources import ResultList
 
 
@@ -27,14 +30,21 @@ def check_json(test, obj):
     val = obj.to_json()
     val = obj.to_json()
     test.assertEqual(val.get('id'), obj.id)
     test.assertEqual(val.get('id'), obj.id)
     test.assertEqual(val.get('name'), obj.name)
     test.assertEqual(val.get('name'), obj.name)
+    if isinstance(obj, LabeledCloudResource):
+        test.assertEqual(val.get('label'), obj.label)
 
 
 
 
 def check_obj_properties(test, obj):
 def check_obj_properties(test, obj):
     test.assertEqual(obj, obj, "Object should be equal to itself")
     test.assertEqual(obj, obj, "Object should be equal to itself")
     test.assertFalse(obj != obj, "Object inequality should be false")
     test.assertFalse(obj != obj, "Object inequality should be false")
     check_obj_name(test, obj)
     check_obj_name(test, obj)
+    check_obj_label(test, obj)
 
 
 
 
+@tenacity.retry(stop=tenacity.stop_after_attempt(10),
+                retry=tenacity.retry_if_exception_type(AssertionError),
+                wait=tenacity.wait_fixed(10),
+                reraise=True)
 def check_list(test, service, obj):
 def check_list(test, service, obj):
     list_objs = service.list()
     list_objs = service.list()
     test.assertIsInstance(list_objs, ResultList)
     test.assertIsInstance(list_objs, ResultList)
@@ -66,18 +76,26 @@ def check_iter(test, service, obj):
 
 
 def check_find(test, service, obj):
 def check_find(test, service, obj):
     # check find
     # check find
-    find_objs = service.find(name=obj.name)
+    if isinstance(obj, LabeledCloudResource):
+        find_objs = service.find(label=obj.label)
+    else:
+        find_objs = service.find(name=obj.name)
     test.assertTrue(
     test.assertTrue(
         len(find_objs) == 1,
         len(find_objs) == 1,
         "Find objects for %s does not return the expected object: %s. Got %s"
         "Find objects for %s does not return the expected object: %s. Got %s"
-        % (type(obj).__name__, obj.name, find_objs))
+        % (type(obj).__name__, getattr(obj, 'label', obj.name), find_objs))
     test.assertEqual(find_objs[0].id, obj.id)
     test.assertEqual(find_objs[0].id, obj.id)
     return find_objs
     return find_objs
 
 
 
 
-def check_find_non_existent(test, service):
+def check_find_non_existent(test, service, obj):
     # check find
     # check find
-    find_objs = service.find(name="random_imagined_obj_name")
+    if isinstance(obj, LabeledCloudResource):
+        find_objs = service.find(label="random_imagined_obj_name")
+    else:
+        find_objs = service.find(name="random_imagined_obj_name")
+    with test.assertRaises(TypeError):
+        service.find(notaparameter="random_imagined_obj_name")
     test.assertTrue(
     test.assertTrue(
         len(find_objs) == 0,
         len(find_objs) == 0,
         "Find non-existent object for %s returned unexpected objects: %s"
         "Find non-existent object for %s returned unexpected objects: %s"
@@ -100,6 +118,10 @@ def check_get_non_existent(test, service):
         % (type(service).__name__, get_objs))
         % (type(service).__name__, get_objs))
 
 
 
 
+@tenacity.retry(stop=tenacity.stop_after_attempt(10),
+                retry=tenacity.retry_if_exception_type(AssertionError),
+                wait=tenacity.wait_fixed(10),
+                reraise=True)
 def check_delete(test, service, obj, perform_delete=False):
 def check_delete(test, service, obj, perform_delete=False):
     if perform_delete:
     if perform_delete:
         obj.delete()
         obj.delete()
@@ -113,6 +135,12 @@ def check_delete(test, service, obj, perform_delete=False):
 
 
 
 
 def check_obj_name(test, obj):
 def check_obj_name(test, obj):
+    name_property = getattr(type(obj), 'name', None)
+    test.assertIsInstance(name_property, property)
+    test.assertIsNone(name_property.fset, "Name should not have a setter")
+
+
+def check_obj_label(test, obj):
     """
     """
     Cloudbridge identifiers must be 1-63 characters long, and comply with
     Cloudbridge identifiers must be 1-63 characters long, and comply with
     RFC1035. In addition, identifiers should contain only lowercase letters,
     RFC1035. In addition, identifiers should contain only lowercase letters,
@@ -120,31 +148,44 @@ def check_obj_name(test, obj):
     characters are allowed.
     characters are allowed.
     """
     """
 
 
-    # if name has a setter, make sure invalid values cannot be set
-    name_property = getattr(type(obj), 'name', None)
-    if isinstance(name_property, property) and name_property.fset:
-        # setting letters, numbers and international characters should succeed
-        # TODO: Unicode characters trip up Moto. Add following: \u0D85\u0200
-        VALID_NAME = u"hello_world-123"
-        original_name = obj.name
-        obj.name = VALID_NAME
+    # if label property exists, make sure invalid values cannot be set
+    label_property = getattr(type(obj), 'label', None)
+    if isinstance(label_property, property):
+        test.assertIsInstance(obj, LabeledCloudResource)
+        original_label = obj.label
+        # Three character labels should be allowed
+        obj.label = "abc"
+        VALID_LABEL = u"hello-world-123"
+        obj.label = VALID_LABEL
+        # Two character labels should not be allowed
+        with test.assertRaises(InvalidNameException):
+            obj.label = "ab"
+        # A none value should not be allowed
+        with test.assertRaises(InvalidNameException):
+            obj.label = None
         # setting spaces should raise an exception
         # setting spaces should raise an exception
         with test.assertRaises(InvalidNameException):
         with test.assertRaises(InvalidNameException):
-            obj.name = "hello world"
+            obj.label = "hello world"
         # setting upper case characters should raise an exception
         # setting upper case characters should raise an exception
         with test.assertRaises(InvalidNameException):
         with test.assertRaises(InvalidNameException):
-            obj.name = "helloWorld"
+            obj.label = "helloWorld"
         # setting special characters should raise an exception
         # setting special characters should raise an exception
         with test.assertRaises(InvalidNameException):
         with test.assertRaises(InvalidNameException):
-            obj.name = "hello.world:how_goes_it"
+            obj.label = "hello.world:how_goes_it"
+        # Starting with a dash should raise an exception
+        with test.assertRaises(InvalidNameException):
+            obj.label = "-hello"
+        # Ending with a dash should raise an exception
+        with test.assertRaises(InvalidNameException):
+            obj.label = "hello-"
         # setting a length > 63 should result in an exception
         # setting a length > 63 should result in an exception
         with test.assertRaises(InvalidNameException,
         with test.assertRaises(InvalidNameException,
-                               msg="Name of length > 64 should be disallowed"):
-            obj.name = "a" * 64
-        # refreshing should yield the last successfully set name
+                               msg="Label of length > 64 is not allowed"):
+            obj.label = "a" * 64
+        # refreshing should yield the last successfully set label
         obj.refresh()
         obj.refresh()
-        test.assertEqual(obj.name, VALID_NAME)
-        obj.name = original_name
+        test.assertEqual(obj.label, VALID_LABEL)
+        obj.label = original_label
 
 
 
 
 def check_standard_behaviour(test, service, obj):
 def check_standard_behaviour(test, service, obj):
@@ -158,7 +199,7 @@ def check_standard_behaviour(test, service, obj):
     objs_list = check_list(test, service, obj)
     objs_list = check_list(test, service, obj)
     objs_iter = check_iter(test, service, obj)
     objs_iter = check_iter(test, service, obj)
     objs_find = check_find(test, service, obj)
     objs_find = check_find(test, service, obj)
-    check_find_non_existent(test, service)
+    check_find_non_existent(test, service, obj)
     obj_get = check_get(test, service, obj)
     obj_get = check_get(test, service, obj)
     check_get_non_existent(test, service)
     check_get_non_existent(test, service)
 
 
@@ -178,27 +219,56 @@ def check_standard_behaviour(test, service, obj):
                                            objs_find[0].id, obj_get.id,
                                            objs_find[0].id, obj_get.id,
                                            obj.id))
                                            obj.id))
 
 
+    if isinstance(obj, LabeledCloudResource):
+        test.assertTrue(
+            obj.label == objs_list[0].label == objs_iter[0].label ==
+            objs_find[0].label == obj_get.label,
+            "Labels returned by list: {0}, iter: {1}, find: {2} and get: {3} "
+            " are not as expected: {4}".format(objs_list[0].id,
+                                               objs_iter[0].id,
+                                               objs_find[0].id, obj_get.id,
+                                               obj.id))
+
 
 
 def check_create(test, service, iface, name_prefix,
 def check_create(test, service, iface, name_prefix,
                  create_func, cleanup_func):
                  create_func, cleanup_func):
-    # check create with invalid name
+    # check create with invalid label
     with test.assertRaises(InvalidNameException):
     with test.assertRaises(InvalidNameException):
         # spaces should raise an exception
         # spaces should raise an exception
         create_func("hello world")
         create_func("hello world")
-    # check create with invalid name
+    # check create with invalid label
     with test.assertRaises(InvalidNameException):
     with test.assertRaises(InvalidNameException):
         # uppercase characters should raise an exception
         # uppercase characters should raise an exception
         create_func("helloWorld")
         create_func("helloWorld")
     # setting special characters should raise an exception
     # setting special characters should raise an exception
     with test.assertRaises(InvalidNameException):
     with test.assertRaises(InvalidNameException):
         create_func("hello.world:how_goes_it")
         create_func("hello.world:how_goes_it")
+    # Starting with a dash should raise an exception
+    with test.assertRaises(InvalidNameException):
+        create_func("-hello")
+    # Ending with a dash should raise an exception
+    with test.assertRaises(InvalidNameException):
+        create_func("hello-")
+    # underscores are not allowed
+    with test.assertRaises(InvalidNameException):
+        create_func("hello_bucket")
     # setting a length > 63 should result in an exception
     # setting a length > 63 should result in an exception
     with test.assertRaises(InvalidNameException,
     with test.assertRaises(InvalidNameException,
-                           msg="Name of length > 64 should be disallowed"):
+                           msg="Label of length > 63 should be disallowed"):
         create_func("a" * 64)
         create_func("a" * 64)
+    #  name cannot be an IP address
+    with test.assertRaises(InvalidNameException):
+        create_func("197.10.100.42")
 
 
+    # empty name are not allowed
+    with test.assertRaises(InvalidNameException):
+        create_func(None)
+    # names of length less than 3 should raise an exception
+    with test.assertRaises(InvalidNameException):
+        create_func("cb")
 
 
-def check_crud(test, service, iface, name_prefix,
+
+def check_crud(test, service, iface, label_prefix,
                create_func, cleanup_func, extra_test_func=None,
                create_func, cleanup_func, extra_test_func=None,
                custom_check_delete=None, skip_name_check=False):
                custom_check_delete=None, skip_name_check=False):
     """
     """
@@ -219,14 +289,14 @@ def check_crud(test, service, iface, name_prefix,
     :param iface: The type to test behaviour against. This type must be a
     :param iface: The type to test behaviour against. This type must be a
                   subclass of ``CloudResource``.
                   subclass of ``CloudResource``.
 
 
-    :type  name_prefix: ``str``
-    :param name_prefix: The name to prefix all created objects with. This
-                        function will generated a new name with the
-                        specified name_prefix for each test object created
-                        and pass that name into the create_func
+    :type  label_prefix: ``str``
+    :param label_prefix: The label to prefix all created objects with. This
+                        function will generated a new label with the
+                        specified label_prefix for each test object created
+                        and pass that label into the create_func
 
 
     :type  create_func: ``func``
     :type  create_func: ``func``
-    :param create_func: The create_func must accept the name of the object to
+    :param create_func: The create_func must accept the label of the object to
                         create as a parameter and return the constructed
                         create as a parameter and return the constructed
                         object.
                         object.
 
 
@@ -247,17 +317,18 @@ def check_crud(test, service, iface, name_prefix,
                                 to make sure that the object has been deleted.
                                 to make sure that the object has been deleted.
 
 
     :type  skip_name_check: ``boolean``
     :type  skip_name_check: ``boolean``
-    :param skip_name_check:  If True, the invalid name checking will be
+    :param skip_name_check:  If True, the name related checking will be
                              skipped.
                              skipped.
+
     """
     """
 
 
     obj = None
     obj = None
     with helpers.cleanup_action(lambda: cleanup_func(obj)):
     with helpers.cleanup_action(lambda: cleanup_func(obj)):
+        label = "{0}-{1}".format(label_prefix, helpers.get_uuid())
         if not skip_name_check:
         if not skip_name_check:
-            check_create(test, service, iface, name_prefix,
+            check_create(test, service, iface, label_prefix,
                          create_func, cleanup_func)
                          create_func, cleanup_func)
-        name = "{0}-{1}".format(name_prefix, helpers.get_uuid())
-        obj = create_func(name)
+        obj = create_func(label)
         if issubclass(iface, ObjectLifeCycleMixin):
         if issubclass(iface, ObjectLifeCycleMixin):
             obj.wait_till_ready()
             obj.wait_till_ready()
         check_standard_behaviour(test, service, obj)
         check_standard_behaviour(test, service, obj)

+ 35 - 42
test/test_block_store_service.py

@@ -1,5 +1,4 @@
 import time
 import time
-import uuid
 
 
 import six
 import six
 
 
@@ -26,10 +25,9 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
         Create a new volume, check whether the expected values are set,
         Create a new volume, check whether the expected values are set,
         and delete it
         and delete it
         """
         """
-        def create_vol(name):
+        def create_vol(label):
             return self.provider.storage.volumes.create(
             return self.provider.storage.volumes.create(
-                name,
-                1,
+                label, 1,
                 helpers.get_provider_test_data(self.provider, "placement"))
                 helpers.get_provider_test_data(self.provider, "placement"))
 
 
         def cleanup_vol(vol):
         def cleanup_vol(vol):
@@ -39,27 +37,26 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
                              terminal_states=[VolumeState.ERROR])
                              terminal_states=[VolumeState.ERROR])
 
 
         sit.check_crud(self, self.provider.storage.volumes, Volume,
         sit.check_crud(self, self.provider.storage.volumes, Volume,
-                       "cb_createvol", create_vol, cleanup_vol)
+                       "cb-createvol", create_vol, cleanup_vol)
 
 
     @helpers.skipIfNoService(['storage.volumes'])
     @helpers.skipIfNoService(['storage.volumes'])
     def test_attach_detach_volume(self):
     def test_attach_detach_volume(self):
         """
         """
         Create a new volume, and attempt to attach it to an instance
         Create a new volume, and attempt to attach it to an instance
         """
         """
-        name = "cb_attachvol-{0}".format(helpers.get_uuid())
+        label = "cb-attachvol-{0}".format(helpers.get_uuid())
         # Declare these variables and late binding will allow
         # Declare these variables and late binding will allow
         # the cleanup method access to the most current values
         # the cleanup method access to the most current values
-        net = None
         test_instance = None
         test_instance = None
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
-                test_instance, net)):
-            net, subnet = helpers.create_test_network(
-                self.provider, name)
+                test_instance)):
+            subnet = helpers.get_or_create_default_subnet(
+                self.provider)
             test_instance = helpers.get_test_instance(
             test_instance = helpers.get_test_instance(
-                self.provider, name, subnet=subnet)
+                self.provider, label, subnet=subnet)
 
 
             test_vol = self.provider.storage.volumes.create(
             test_vol = self.provider.storage.volumes.create(
-                name, 1, test_instance.zone_id)
+                label, 1, test_instance.zone_id)
             with helpers.cleanup_action(lambda: test_vol.delete()):
             with helpers.cleanup_action(lambda: test_vol.delete()):
                 test_vol.wait_till_ready()
                 test_vol.wait_till_ready()
                 test_vol.attach(test_instance, '/dev/sda2')
                 test_vol.attach(test_instance, '/dev/sda2')
@@ -76,21 +73,20 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
         """
         """
         Test volume properties
         Test volume properties
         """
         """
-        name = "cb_volprops-{0}".format(helpers.get_uuid())
+        label = "cb-volprops-{0}".format(helpers.get_uuid())
         vol_desc = 'newvoldesc1'
         vol_desc = 'newvoldesc1'
         # Declare these variables and late binding will allow
         # Declare these variables and late binding will allow
         # the cleanup method access to the most current values
         # the cleanup method access to the most current values
         test_instance = None
         test_instance = None
-        net = None
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
-                test_instance, net)):
-            net, subnet = helpers.create_test_network(
-                self.provider, name)
+                test_instance)):
+            subnet = helpers.get_or_create_default_subnet(
+                self.provider)
             test_instance = helpers.get_test_instance(
             test_instance = helpers.get_test_instance(
-                self.provider, name, subnet=subnet)
+                self.provider, label, subnet=subnet)
 
 
             test_vol = self.provider.storage.volumes.create(
             test_vol = self.provider.storage.volumes.create(
-                name, 1, test_instance.zone_id, description=vol_desc)
+                label, 1, test_instance.zone_id, description=vol_desc)
             with helpers.cleanup_action(lambda: test_vol.delete()):
             with helpers.cleanup_action(lambda: test_vol.delete()):
                 test_vol.wait_till_ready()
                 test_vol.wait_till_ready()
                 self.assertTrue(
                 self.assertTrue(
@@ -121,11 +117,11 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
                     self.assertEqual(test_vol.attachments.device,
                     self.assertEqual(test_vol.attachments.device,
                                      "/dev/sda2")
                                      "/dev/sda2")
                 test_vol.detach()
                 test_vol.detach()
-                test_vol.name = 'newvolname1'
+                test_vol.label = 'newvolname1'
                 test_vol.wait_for(
                 test_vol.wait_for(
                     [VolumeState.AVAILABLE],
                     [VolumeState.AVAILABLE],
                     terminal_states=[VolumeState.ERROR, VolumeState.DELETED])
                     terminal_states=[VolumeState.ERROR, VolumeState.DELETED])
-                self.assertEqual(test_vol.name, 'newvolname1')
+                self.assertEqual(test_vol.label, 'newvolname1')
                 self.assertEqual(test_vol.description, vol_desc)
                 self.assertEqual(test_vol.description, vol_desc)
                 self.assertIsNone(test_vol.attachments)
                 self.assertIsNone(test_vol.attachments)
                 test_vol.wait_for(
                 test_vol.wait_for(
@@ -139,17 +135,16 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
         whether list_snapshots properly detects the new snapshot.
         whether list_snapshots properly detects the new snapshot.
         Delete everything afterwards.
         Delete everything afterwards.
         """
         """
-        name = "cb_crudsnap-{0}".format(helpers.get_uuid())
+        label = "cb-crudsnap-{0}".format(helpers.get_uuid())
         test_vol = self.provider.storage.volumes.create(
         test_vol = self.provider.storage.volumes.create(
-            name,
-            1,
+            label, 1,
             helpers.get_provider_test_data(self.provider, "placement"))
             helpers.get_provider_test_data(self.provider, "placement"))
         with helpers.cleanup_action(lambda: test_vol.delete()):
         with helpers.cleanup_action(lambda: test_vol.delete()):
             test_vol.wait_till_ready()
             test_vol.wait_till_ready()
 
 
-            def create_snap(name):
-                return test_vol.create_snapshot(name=name,
-                                                description=name)
+            def create_snap(label):
+                return test_vol.create_snapshot(label=label,
+                                                description=label)
 
 
             def cleanup_snap(snap):
             def cleanup_snap(snap):
                 if snap:
                 if snap:
@@ -158,34 +153,33 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
                                   terminal_states=[SnapshotState.ERROR])
                                   terminal_states=[SnapshotState.ERROR])
 
 
             sit.check_crud(self, self.provider.storage.snapshots, Snapshot,
             sit.check_crud(self, self.provider.storage.snapshots, Snapshot,
-                           "cb_snap", create_snap, cleanup_snap)
+                           "cb-snap", create_snap, cleanup_snap)
 
 
             # Test creation of a snap via SnapshotService
             # Test creation of a snap via SnapshotService
-            def create_snap2(name):
+            def create_snap2(label):
                 return self.provider.storage.snapshots.create(
                 return self.provider.storage.snapshots.create(
-                    name=name, volume=test_vol, description=name)
+                    label=label, volume=test_vol, description=label)
 
 
             if (self.provider.PROVIDER_ID == ProviderList.AWS and
             if (self.provider.PROVIDER_ID == ProviderList.AWS and
                     not isinstance(self.provider, TestMockHelperMixin)):
                     not isinstance(self.provider, TestMockHelperMixin)):
                 time.sleep(15)  # Or get SnapshotCreationPerVolumeRateExceeded
                 time.sleep(15)  # Or get SnapshotCreationPerVolumeRateExceeded
             sit.check_crud(self, self.provider.storage.snapshots, Snapshot,
             sit.check_crud(self, self.provider.storage.snapshots, Snapshot,
-                           "cb_snaptwo", create_snap2, cleanup_snap)
+                           "cb-snaptwo", create_snap2, cleanup_snap)
 
 
     @helpers.skipIfNoService(['storage.snapshots'])
     @helpers.skipIfNoService(['storage.snapshots'])
     def test_snapshot_properties(self):
     def test_snapshot_properties(self):
         """
         """
         Test snapshot properties
         Test snapshot properties
         """
         """
-        name = "cb_snapprop-{0}".format(uuid.uuid4())
+        label = "cb-snapprop-{0}".format(helpers.get_uuid())
         test_vol = self.provider.storage.volumes.create(
         test_vol = self.provider.storage.volumes.create(
-            name,
-            1,
+            label, 1,
             helpers.get_provider_test_data(self.provider, "placement"))
             helpers.get_provider_test_data(self.provider, "placement"))
         with helpers.cleanup_action(lambda: test_vol.delete()):
         with helpers.cleanup_action(lambda: test_vol.delete()):
             test_vol.wait_till_ready()
             test_vol.wait_till_ready()
-            snap_name = "cb_snap-{0}".format(name)
-            test_snap = test_vol.create_snapshot(name=snap_name,
-                                                 description=snap_name)
+            snap_label = "cb-snap-{0}".format(label)
+            test_snap = test_vol.create_snapshot(label=snap_label,
+                                                 description=snap_label)
 
 
             def cleanup_snap(snap):
             def cleanup_snap(snap):
                 if snap:
                 if snap:
@@ -207,17 +201,16 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
                     % test_vol.description)
                     % test_vol.description)
                 self.assertEqual(test_vol.id, test_snap.volume_id)
                 self.assertEqual(test_vol.id, test_snap.volume_id)
                 self.assertIsNotNone(test_vol.create_time)
                 self.assertIsNotNone(test_vol.create_time)
-                test_snap.name = 'snapnewname1'
+                test_snap.label = 'snapnewname1'
                 test_snap.description = 'snapnewdescription1'
                 test_snap.description = 'snapnewdescription1'
                 test_snap.refresh()
                 test_snap.refresh()
-                self.assertEqual(test_snap.name, 'snapnewname1')
+                self.assertEqual(test_snap.label, 'snapnewname1')
                 self.assertEqual(test_snap.description, 'snapnewdescription1')
                 self.assertEqual(test_snap.description, 'snapnewdescription1')
 
 
                 # Test volume creation from a snapshot (via VolumeService)
                 # Test volume creation from a snapshot (via VolumeService)
-                sv_name = "cb_snapvol_{0}".format(test_snap.name)
+                sv_label = "cb-snapvol-{0}".format(test_snap.name)
                 snap_vol = self.provider.storage.volumes.create(
                 snap_vol = self.provider.storage.volumes.create(
-                    sv_name,
-                    1,
+                    sv_label, 1,
                     helpers.get_provider_test_data(self.provider, "placement"),
                     helpers.get_provider_test_data(self.provider, "placement"),
                     snapshot=test_snap)
                     snapshot=test_snap)
                 with helpers.cleanup_action(lambda: snap_vol.delete()):
                 with helpers.cleanup_action(lambda: snap_vol.delete()):

+ 55 - 56
test/test_compute_service.py

@@ -21,16 +21,15 @@ class CloudComputeServiceTestCase(ProviderTestBase):
 
 
     @helpers.skipIfNoService(['compute.instances', 'networking.networks'])
     @helpers.skipIfNoService(['compute.instances', 'networking.networks'])
     def test_crud_instance(self):
     def test_crud_instance(self):
-        name = "cb_instcrud-{0}".format(helpers.get_uuid())
+        label = "cb-instcrud-{0}".format(helpers.get_uuid())
         # Declare these variables and late binding will allow
         # Declare these variables and late binding will allow
         # the cleanup method access to the most current values
         # the cleanup method access to the most current values
-        net = None
         subnet = None
         subnet = None
 
 
-        def create_inst(name):
+        def create_inst(label):
             # Also test whether sending in an empty_dict for user_data
             # Also test whether sending in an empty_dict for user_data
             # results in an automatic conversion to string.
             # results in an automatic conversion to string.
-            return helpers.get_test_instance(self.provider, name,
+            return helpers.get_test_instance(self.provider, label,
                                              subnet=subnet, user_data={})
                                              subnet=subnet, user_data={})
 
 
         def cleanup_inst(inst):
         def cleanup_inst(inst):
@@ -46,15 +45,13 @@ class CloudComputeServiceTestCase(ProviderTestBase):
                     InstanceState.DELETED,
                     InstanceState.DELETED,
                     InstanceState.UNKNOWN),
                     InstanceState.UNKNOWN),
                 "Instance %s should have been deleted but still exists." %
                 "Instance %s should have been deleted but still exists." %
-                name)
+                label)
 
 
-        with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
-                                               network=net)):
-            net, subnet = helpers.create_test_network(self.provider, name)
+        subnet = helpers.get_or_create_default_subnet(self.provider)
 
 
-            sit.check_crud(self, self.provider.compute.instances, Instance,
-                           "cb_instcrud", create_inst, cleanup_inst,
-                           custom_check_delete=check_deleted)
+        sit.check_crud(self, self.provider.compute.instances, Instance,
+                       "cb-instcrud", create_inst, cleanup_inst,
+                       custom_check_delete=check_deleted)
 
 
     def _is_valid_ip(self, address):
     def _is_valid_ip(self, address):
         try:
         try:
@@ -67,28 +64,28 @@ class CloudComputeServiceTestCase(ProviderTestBase):
                               'security.vm_firewalls',
                               'security.vm_firewalls',
                               'security.key_pairs'])
                               'security.key_pairs'])
     def test_instance_properties(self):
     def test_instance_properties(self):
-        name = "cb_inst_props-{0}".format(helpers.get_uuid())
+        label = "cb-inst-props-{0}".format(helpers.get_uuid())
 
 
         # Declare these variables and late binding will allow
         # Declare these variables and late binding will allow
         # the cleanup method access to the most current values
         # the cleanup method access to the most current values
         test_instance = None
         test_instance = None
-        net = None
         fw = None
         fw = None
         kp = None
         kp = None
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
-                test_instance, net, fw, kp)):
-            net, subnet = helpers.create_test_network(self.provider, name)
-            kp = self.provider.security.key_pairs.create(name=name)
+                test_instance, fw, kp)):
+            subnet = helpers.get_or_create_default_subnet(self.provider)
+            net = subnet.network
+            kp = self.provider.security.key_pairs.create(name=label)
             fw = self.provider.security.vm_firewalls.create(
             fw = self.provider.security.vm_firewalls.create(
-                name=name, description=name, network_id=net.id)
+                label=label, description=label, network_id=net.id)
             test_instance = helpers.get_test_instance(self.provider,
             test_instance = helpers.get_test_instance(self.provider,
-                                                      name, key_pair=kp,
+                                                      label, key_pair=kp,
                                                       vm_firewalls=[fw],
                                                       vm_firewalls=[fw],
                                                       subnet=subnet)
                                                       subnet=subnet)
             self.assertEqual(
             self.assertEqual(
-                test_instance.name, name,
-                "Instance name {0} is not equal to the expected name"
-                " {1}".format(test_instance.name, name))
+                test_instance.label, label,
+                "Instance label {0} is not equal to the expected label"
+                " {1}".format(test_instance.label, label))
             image_id = helpers.get_provider_test_data(self.provider, "image")
             image_id = helpers.get_provider_test_data(self.provider, "image")
             self.assertEqual(test_instance.image_id, image_id,
             self.assertEqual(test_instance.image_id, image_id,
                              "Image id {0} is not equal to the expected id"
                              "Image id {0} is not equal to the expected id"
@@ -107,8 +104,8 @@ class CloudComputeServiceTestCase(ProviderTestBase):
             self.assertTrue(test_instance.private_ips[0], "private ip should"
             self.assertTrue(test_instance.private_ips[0], "private ip should"
                             " contain a valid value")
                             " contain a valid value")
             self.assertEqual(
             self.assertEqual(
-                test_instance.key_pair_name,
-                kp.name)
+                test_instance.key_pair_id,
+                kp.id)
             self.assertIsInstance(test_instance.vm_firewalls, list)
             self.assertIsInstance(test_instance.vm_firewalls, list)
             self.assertEqual(
             self.assertEqual(
                 test_instance.vm_firewalls[0],
                 test_instance.vm_firewalls[0],
@@ -217,21 +214,20 @@ class CloudComputeServiceTestCase(ProviderTestBase):
     @helpers.skipIfNoService(['compute.instances', 'compute.images',
     @helpers.skipIfNoService(['compute.instances', 'compute.images',
                               'compute.vm_types', 'storage.volumes'])
                               'compute.vm_types', 'storage.volumes'])
     def test_block_device_mapping_attachments(self):
     def test_block_device_mapping_attachments(self):
-        name = "cb_blkattch-{0}".format(helpers.get_uuid())
+        label = "cb-blkattch-{0}".format(helpers.get_uuid())
 
 
         if self.provider.PROVIDER_ID == ProviderList.OPENSTACK:
         if self.provider.PROVIDER_ID == ProviderList.OPENSTACK:
             raise self.skipTest("Not running BDM tests because OpenStack is"
             raise self.skipTest("Not running BDM tests because OpenStack is"
                                 " not stable enough yet")
                                 " not stable enough yet")
 
 
         test_vol = self.provider.storage.volumes.create(
         test_vol = self.provider.storage.volumes.create(
-           name,
-           1,
+           label, 1,
            helpers.get_provider_test_data(self.provider,
            helpers.get_provider_test_data(self.provider,
                                           "placement"))
                                           "placement"))
         with helpers.cleanup_action(lambda: test_vol.delete()):
         with helpers.cleanup_action(lambda: test_vol.delete()):
             test_vol.wait_till_ready()
             test_vol.wait_till_ready()
-            test_snap = test_vol.create_snapshot(name=name,
-                                                 description=name)
+            test_snap = test_vol.create_snapshot(label=label,
+                                                 description=label)
 
 
             def cleanup_snap(snap):
             def cleanup_snap(snap):
                 if snap:
                 if snap:
@@ -277,45 +273,49 @@ class CloudComputeServiceTestCase(ProviderTestBase):
                 for _ in range(vm_type.num_ephemeral_disks):
                 for _ in range(vm_type.num_ephemeral_disks):
                     lc.add_ephemeral_device()
                     lc.add_ephemeral_device()
 
 
-                net, subnet = helpers.create_test_network(self.provider, name)
+                subnet = helpers.get_or_create_default_subnet(
+                    self.provider)
+
+                inst = helpers.create_test_instance(
+                    self.provider,
+                    label,
+                    subnet=subnet,
+                    launch_config=lc)
 
 
                 with helpers.cleanup_action(lambda:
                 with helpers.cleanup_action(lambda:
-                                            helpers.delete_test_network(net)):
-
-                    inst = helpers.create_test_instance(
-                        self.provider,
-                        name,
-                        subnet=subnet,
-                        launch_config=lc)
-
-                    with helpers.cleanup_action(lambda:
-                                                helpers.delete_test_instance(
-                                                    inst)):
-                        try:
-                            inst.wait_till_ready()
-                        except WaitStateException as e:
-                            self.fail("The block device mapped launch did not "
-                                      " complete successfully: %s" % e)
-                        # TODO: Check instance attachments and make sure they
-                        # correspond to requested mappings
+                                            helpers.delete_test_instance(
+                                                inst)):
+                    try:
+                        inst.wait_till_ready()
+                    except WaitStateException as e:
+                        self.fail("The block device mapped launch did not "
+                                  " complete successfully: %s" % e)
+                    # TODO: Check instance attachments and make sure they
+                    # correspond to requested mappings
 
 
     @helpers.skipIfNoService(['compute.instances', 'networking.networks',
     @helpers.skipIfNoService(['compute.instances', 'networking.networks',
                               'security.vm_firewalls'])
                               'security.vm_firewalls'])
     def test_instance_methods(self):
     def test_instance_methods(self):
-        name = "cb_instmethods-{0}".format(helpers.get_uuid())
+        label = "cb-instmethods-{0}".format(helpers.get_uuid())
 
 
         # Declare these variables and late binding will allow
         # Declare these variables and late binding will allow
         # the cleanup method access to the most current values
         # the cleanup method access to the most current values
-        test_inst = None
         net = None
         net = None
+        test_inst = None
         fw = None
         fw = None
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
-                test_inst, net, fw)):
-            net, subnet = helpers.create_test_network(self.provider, name)
-            test_inst = helpers.get_test_instance(self.provider, name,
+                instance=test_inst, vm_firewall=fw, network=net)):
+            net = self.provider.networking.networks.create(
+                label=label, cidr_block='10.0.0.0/16')
+            cidr = '10.0.1.0/24'
+            subnet = net.create_subnet(label=label, cidr_block=cidr,
+                                       zone=helpers.get_provider_test_data(
+                                                    self.provider,
+                                                    'placement'))
+            test_inst = helpers.get_test_instance(self.provider, label,
                                                   subnet=subnet)
                                                   subnet=subnet)
             fw = self.provider.security.vm_firewalls.create(
             fw = self.provider.security.vm_firewalls.create(
-                name=name, description=name, network_id=net.id)
+                label=label, description=label, network_id=net.id)
 
 
             # Check adding a VM firewall to a running instance
             # Check adding a VM firewall to a running instance
             test_inst.add_vm_firewall(fw)
             test_inst.add_vm_firewall(fw)
@@ -334,8 +334,8 @@ class CloudComputeServiceTestCase(ProviderTestBase):
                 (fw, test_inst.vm_firewalls))
                 (fw, test_inst.vm_firewalls))
 
 
             # check floating ips
             # check floating ips
-            router = self.provider.networking.routers.create(name, net)
-            gateway = None
+            router = self.provider.networking.routers.create(label, net)
+            gateway = net.gateways.get_or_create_inet_gateway(name=label)
 
 
             def cleanup_router(router, gateway):
             def cleanup_router(router, gateway):
                 with helpers.cleanup_action(lambda: router.delete()):
                 with helpers.cleanup_action(lambda: router.delete()):
@@ -346,7 +346,6 @@ class CloudComputeServiceTestCase(ProviderTestBase):
             with helpers.cleanup_action(lambda: cleanup_router(router,
             with helpers.cleanup_action(lambda: cleanup_router(router,
                                                                gateway)):
                                                                gateway)):
                 router.attach_subnet(subnet)
                 router.attach_subnet(subnet)
-                gateway = net.gateways.get_or_create_inet_gateway(name)
                 router.attach_gateway(gateway)
                 router.attach_gateway(gateway)
                 # check whether adding an elastic ip works
                 # check whether adding an elastic ip works
                 fip = gateway.floating_ips.create()
                 fip = gateway.floating_ips.create()

+ 44 - 11
test/test_image_service.py

@@ -1,5 +1,5 @@
 from cloudbridge.cloud.interfaces import MachineImageState
 from cloudbridge.cloud.interfaces import MachineImageState
-from cloudbridge.cloud.interfaces.resources import MachineImage
+from cloudbridge.cloud.interfaces.resources import Instance, MachineImage
 
 
 from test import helpers
 from test import helpers
 from test.helpers import ProviderTestBase
 from test.helpers import ProviderTestBase
@@ -16,18 +16,18 @@ class CloudImageServiceTestCase(ProviderTestBase):
         """
         """
         Create a new image and check whether that image can be listed.
         Create a new image and check whether that image can be listed.
         This covers waiting till the image is ready, checking that the image
         This covers waiting till the image is ready, checking that the image
-        name is the expected one and whether list_images is functional.
+        label is the expected one and whether list_images is functional.
         """
         """
-        instance_name = "cb_crudimage-{0}".format(helpers.get_uuid())
+        instance_label = "cb-crudimage-{0}".format(helpers.get_uuid())
+        img_inst_label = "cb-crudimage-{0}".format(helpers.get_uuid())
 
 
         # Declare these variables and late binding will allow
         # Declare these variables and late binding will allow
         # the cleanup method access to the most current values
         # the cleanup method access to the most current values
         test_instance = None
         test_instance = None
-        net = None
         subnet = None
         subnet = None
 
 
-        def create_img(name):
-            return test_instance.create_image(name)
+        def create_img(label):
+            return test_instance.create_image(label=label)
 
 
         def cleanup_img(img):
         def cleanup_img(img):
             if img:
             if img:
@@ -40,13 +40,46 @@ class CloudImageServiceTestCase(ProviderTestBase):
             img.refresh()
             img.refresh()
             self.assertGreater(img.min_disk, 0, "Minimum disk"
             self.assertGreater(img.min_disk, 0, "Minimum disk"
                                " size required by image is invalid")
                                " size required by image is invalid")
+            create_instance_from_image(img)
+
+        def create_instance_from_image(img):
+            img_instance = None
+            with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
+                    img_instance)):
+                img_instance = self.provider.compute.instances.create(
+                    img_inst_label, img,
+                    helpers.get_provider_test_data(self.provider, 'vm_type'),
+                    subnet=subnet,
+                    zone=helpers.get_provider_test_data(
+                        self.provider, 'placement'))
+                img_instance.wait_till_ready()
+                self.assertIsInstance(img_instance, Instance)
+                self.assertEqual(
+                    img_instance.label, img_inst_label,
+                    "Instance label {0} is not equal to the expected label"
+                    " {1}".format(img_instance.label, img_inst_label))
+                image_id = img.id
+                self.assertEqual(img_instance.image_id, image_id,
+                                 "Image id {0} is not equal to the expected id"
+                                 " {1}".format(img_instance.image_id,
+                                               image_id))
+                self.assertIsInstance(img_instance.public_ips, list)
+                if img_instance.public_ips:
+                    self.assertTrue(
+                        img_instance.public_ips[0],
+                        "public ip should contain a"
+                        " valid value if a list of public_ips exist")
+                self.assertIsInstance(img_instance.private_ips, list)
+                self.assertTrue(img_instance.private_ips[0],
+                                "private ip should"
+                                " contain a valid value")
 
 
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
-                test_instance, net)):
-            net, subnet = helpers.create_test_network(
-                self.provider, instance_name)
+                test_instance)):
+            subnet = helpers.get_or_create_default_subnet(
+                self.provider)
             test_instance = helpers.get_test_instance(
             test_instance = helpers.get_test_instance(
-                self.provider, instance_name, subnet=subnet)
+                self.provider, instance_label, subnet=subnet)
             sit.check_crud(self, self.provider.compute.images, MachineImage,
             sit.check_crud(self, self.provider.compute.images, MachineImage,
-                           "cb_listimg", create_img, cleanup_img,
+                           "cb-listimg", create_img, cleanup_img,
                            extra_test_func=extra_tests)
                            extra_test_func=extra_tests)

+ 40 - 41
test/test_network_service.py

@@ -16,23 +16,23 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
     @helpers.skipIfNoService(['networking.networks'])
     @helpers.skipIfNoService(['networking.networks'])
     def test_crud_network(self):
     def test_crud_network(self):
 
 
-        def create_net(name):
+        def create_net(label):
             return self.provider.networking.networks.create(
             return self.provider.networking.networks.create(
-                name=name, cidr_block='10.0.0.0/16')
+                label=label, cidr_block='10.0.0.0/16')
 
 
         def cleanup_net(net):
         def cleanup_net(net):
             if net:
             if net:
                 self.provider.networking.networks.delete(network_id=net.id)
                 self.provider.networking.networks.delete(network_id=net.id)
 
 
         sit.check_crud(self, self.provider.networking.networks, Network,
         sit.check_crud(self, self.provider.networking.networks, Network,
-                       "cb_crudnetwork", create_net, cleanup_net)
+                       "cb-crudnetwork", create_net, cleanup_net)
 
 
     @helpers.skipIfNoService(['networking.networks'])
     @helpers.skipIfNoService(['networking.networks'])
     def test_network_properties(self):
     def test_network_properties(self):
-        name = 'cb_propnetwork-{0}'.format(helpers.get_uuid())
-        subnet_name = 'cb_propsubnet-{0}'.format(helpers.get_uuid())
+        label = 'cb-propnetwork-{0}'.format(helpers.get_uuid())
+        subnet_label = 'cb-propsubnet-{0}'.format(helpers.get_uuid())
         net = self.provider.networking.networks.create(
         net = self.provider.networking.networks.create(
-            name=name, cidr_block='10.0.0.0/16')
+            label=label, cidr_block='10.0.0.0/16')
         with helpers.cleanup_action(
         with helpers.cleanup_action(
             lambda: net.delete()
             lambda: net.delete()
         ):
         ):
@@ -48,9 +48,9 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
                 "Network CIDR %s does not contain the expected value."
                 "Network CIDR %s does not contain the expected value."
                 % net.cidr_block)
                 % net.cidr_block)
 
 
-            cidr = '10.0.1.0/24'
+            cidr = '10.0.20.0/24'
             sn = net.create_subnet(
             sn = net.create_subnet(
-                name=subnet_name, cidr_block=cidr,
+                label=subnet_label, cidr_block=cidr,
                 zone=helpers.get_provider_test_data(self.provider,
                 zone=helpers.get_provider_test_data(self.provider,
                                                     'placement'))
                                                     'placement'))
             with helpers.cleanup_action(lambda: sn.delete()):
             with helpers.cleanup_action(lambda: sn.delete()):
@@ -69,10 +69,15 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
                     net.subnets, [sn],
                     net.subnets, [sn],
                     "Network should have exactly one subnet: %s." % sn.id)
                     "Network should have exactly one subnet: %s." % sn.id)
 
 
-                self.assertIn(
+                self.assertEqual(
                     net.id, sn.network_id,
                     net.id, sn.network_id,
-                    "Network ID %s should be specified in the subnet's network"
-                    " id %s." % (net.id, sn.network_id))
+                    "Network ID %s and subnet's network id %s should be"
+                    " equal." % (net.id, sn.network_id))
+
+                self.assertEqual(
+                    net, sn.network,
+                    "Network obj %s and subnet's parent net obj %s"
+                    " should be equal." % (net, sn.network))
 
 
                 self.assertEqual(
                 self.assertEqual(
                     cidr, sn.cidr_block,
                     cidr, sn.cidr_block,
@@ -82,11 +87,12 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
     def test_crud_subnet(self):
     def test_crud_subnet(self):
         # Late binding will make sure that create_subnet gets the
         # Late binding will make sure that create_subnet gets the
         # correct value
         # correct value
-        net = None
+        sn = helpers.get_or_create_default_subnet(self.provider)
+        net = sn.network
 
 
-        def create_subnet(name):
+        def create_subnet(label):
             return self.provider.networking.subnets.create(
             return self.provider.networking.subnets.create(
-                network=net, cidr_block="10.0.0.0/24", name=name,
+                label=label, network=net, cidr_block="10.0.10.0/24",
                 zone=helpers.get_provider_test_data(
                 zone=helpers.get_provider_test_data(
                     self.provider, 'placement'))
                     self.provider, 'placement'))
 
 
@@ -94,21 +100,14 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
             if subnet:
             if subnet:
                 self.provider.networking.subnets.delete(subnet=subnet)
                 self.provider.networking.subnets.delete(subnet=subnet)
 
 
-        net_name = 'cb_crudsubnet-{0}'.format(helpers.get_uuid())
-        net = self.provider.networking.networks.create(
-            name=net_name, cidr_block='10.0.0.0/16')
-        with helpers.cleanup_action(
-            lambda:
-                self.provider.networking.networks.delete(network_id=net.id)
-        ):
-            sit.check_crud(self, self.provider.networking.subnets, Subnet,
-                           "cb_crudsubnet", create_subnet, cleanup_subnet)
+        sit.check_crud(self, self.provider.networking.subnets, Subnet,
+                       "cb-crudsubnet", create_subnet, cleanup_subnet)
 
 
     def test_crud_floating_ip(self):
     def test_crud_floating_ip(self):
-        net, gw = helpers.get_test_gateway(
-            self.provider, 'cb_crudfipgw-{0}'.format(helpers.get_uuid()))
+        gw = helpers.get_test_gateway(
+            self.provider)
 
 
-        def create_fip(name):
+        def create_fip(label):
             fip = gw.floating_ips.create()
             fip = gw.floating_ips.create()
             return fip
             return fip
 
 
@@ -117,18 +116,18 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
                 gw.floating_ips.delete(fip.id)
                 gw.floating_ips.delete(fip.id)
 
 
         with helpers.cleanup_action(
         with helpers.cleanup_action(
-                lambda: helpers.delete_test_gateway(net, gw)):
+                lambda: helpers.delete_test_gateway(gw)):
             sit.check_crud(self, gw.floating_ips, FloatingIP,
             sit.check_crud(self, gw.floating_ips, FloatingIP,
-                           "cb_crudfip", create_fip, cleanup_fip,
+                           "cb-crudfip", create_fip, cleanup_fip,
                            skip_name_check=True)
                            skip_name_check=True)
 
 
     def test_floating_ip_properties(self):
     def test_floating_ip_properties(self):
         # Check floating IP address
         # Check floating IP address
-        net, gw = helpers.get_test_gateway(
-            self.provider, 'cb_crudfipgw-{0}'.format(helpers.get_uuid()))
+        gw = helpers.get_test_gateway(
+            self.provider)
         fip = gw.floating_ips.create()
         fip = gw.floating_ips.create()
         with helpers.cleanup_action(
         with helpers.cleanup_action(
-                lambda: helpers.delete_test_gateway(net, gw)):
+                lambda: helpers.delete_test_gateway(gw)):
             with helpers.cleanup_action(lambda: fip.delete()):
             with helpers.cleanup_action(lambda: fip.delete()):
                 fipl = list(gw.floating_ips)
                 fipl = list(gw.floating_ips)
                 self.assertIn(fip, fipl)
                 self.assertIn(fip, fipl)
@@ -151,13 +150,13 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
 
 
         def _cleanup(net, subnet, router, gateway):
         def _cleanup(net, subnet, router, gateway):
             with helpers.cleanup_action(lambda: net.delete()):
             with helpers.cleanup_action(lambda: net.delete()):
-                with helpers.cleanup_action(lambda: subnet.delete()):
-                    with helpers.cleanup_action(lambda: gateway.delete()):
-                        with helpers.cleanup_action(lambda: router.delete()):
+                with helpers.cleanup_action(lambda: router.delete()):
+                    with helpers.cleanup_action(lambda: subnet.delete()):
+                        with helpers.cleanup_action(lambda: gateway.delete()):
                             router.detach_subnet(subnet)
                             router.detach_subnet(subnet)
                             router.detach_gateway(gateway)
                             router.detach_gateway(gateway)
 
 
-        name = 'cb_crudrouter-{0}'.format(helpers.get_uuid())
+        label = 'cb-crudrouter-{0}'.format(helpers.get_uuid())
         # Declare these variables and late binding will allow
         # Declare these variables and late binding will allow
         # the cleanup method access to the most current values
         # the cleanup method access to the most current values
         net = None
         net = None
@@ -166,11 +165,11 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
         gteway = None
         gteway = None
         with helpers.cleanup_action(lambda: _cleanup(net, sn, router, gteway)):
         with helpers.cleanup_action(lambda: _cleanup(net, sn, router, gteway)):
             net = self.provider.networking.networks.create(
             net = self.provider.networking.networks.create(
-                name=name, cidr_block='10.0.0.0/16')
-            router = self.provider.networking.routers.create(network=net,
-                                                             name=name)
-            cidr = '10.0.1.0/24'
-            sn = net.create_subnet(name=name, cidr_block=cidr,
+                label=label, cidr_block='10.0.0.0/16')
+            router = self.provider.networking.routers.create(label=label,
+                                                             network=net)
+            cidr = '10.0.15.0/24'
+            sn = net.create_subnet(label=label, cidr_block=cidr,
                                    zone=helpers.get_provider_test_data(
                                    zone=helpers.get_provider_test_data(
                                        self.provider, 'placement'))
                                        self.provider, 'placement'))
 
 
@@ -188,7 +187,7 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
 #                     router.id, router.network_id))
 #                     router.id, router.network_id))
 
 
             router.attach_subnet(sn)
             router.attach_subnet(sn)
-            gteway = net.gateways.get_or_create_inet_gateway(name)
+            gteway = net.gateways.get_or_create_inet_gateway(name=label)
             router.attach_gateway(gteway)
             router.attach_gateway(gteway)
             # TODO: add a check for routes after that's been implemented
             # TODO: add a check for routes after that's been implemented
 
 

+ 2 - 3
test/test_object_life_cycle.py

@@ -14,10 +14,9 @@ class CloudObjectLifeCycleTestCase(ProviderTestBase):
         """
         """
         Test object life cycle methods by using a volume.
         Test object life cycle methods by using a volume.
         """
         """
-        name = "cb_objlifecycle-{0}".format(helpers.get_uuid())
+        label = "cb-objlifecycle-{0}".format(helpers.get_uuid())
         test_vol = self.provider.storage.volumes.create(
         test_vol = self.provider.storage.volumes.create(
-            name,
-            1,
+            label, 1,
             helpers.get_provider_test_data(self.provider, "placement"))
             helpers.get_provider_test_data(self.provider, "placement"))
 
 
         # Waiting for an invalid timeout should raise an exception
         # Waiting for an invalid timeout should raise an exception

+ 14 - 25
test/test_object_store_service.py

@@ -1,14 +1,13 @@
 import filecmp
 import filecmp
 import os
 import os
 import tempfile
 import tempfile
-import uuid
 from datetime import datetime
 from datetime import datetime
 from io import BytesIO
 from io import BytesIO
 from unittest import skip
 from unittest import skip
 
 
 import requests
 import requests
 
 
-from cloudbridge.cloud.interfaces.exceptions import InvalidNameException
+from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
 from cloudbridge.cloud.interfaces.provider import TestMockHelperMixin
 from cloudbridge.cloud.interfaces.provider import TestMockHelperMixin
 from cloudbridge.cloud.interfaces.resources import Bucket
 from cloudbridge.cloud.interfaces.resources import Bucket
 from cloudbridge.cloud.interfaces.resources import BucketObject
 from cloudbridge.cloud.interfaces.resources import BucketObject
@@ -36,25 +35,14 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
             if bucket:
             if bucket:
                 bucket.delete()
                 bucket.delete()
 
 
-        with self.assertRaises(InvalidNameException):
-            # underscores are not allowed in bucket names
-            create_bucket("cb_bucket")
-
-        with self.assertRaises(InvalidNameException):
-            # names of length less than 3 should raise an exception
-            create_bucket("cb")
-
-        with self.assertRaises(InvalidNameException):
-            # names of length greater than 63 should raise an exception
-            create_bucket("a" * 64)
-
-        with self.assertRaises(InvalidNameException):
-            # bucket name cannot be an IP address
-            create_bucket("197.10.100.42")
+        def extra_tests(bucket):
+            # Recreating existing bucket should raise an exception
+            with self.assertRaises(DuplicateResourceException):
+                self.provider.storage.buckets.create(name=bucket.name)
 
 
         sit.check_crud(self, self.provider.storage.buckets, Bucket,
         sit.check_crud(self, self.provider.storage.buckets, Bucket,
                        "cb-crudbucket", create_bucket, cleanup_bucket,
                        "cb-crudbucket", create_bucket, cleanup_bucket,
-                       skip_name_check=True)
+                       extra_test_func=extra_tests)
 
 
     @helpers.skipIfNoService(['storage.buckets'])
     @helpers.skipIfNoService(['storage.buckets'])
     def test_crud_bucket_object(self):
     def test_crud_bucket_object(self):
@@ -74,11 +62,11 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
                 bucket_obj.delete()
                 bucket_obj.delete()
 
 
         with helpers.cleanup_action(lambda: test_bucket.delete()):
         with helpers.cleanup_action(lambda: test_bucket.delete()):
-            name = "cb-crudbucketobj-{0}".format(uuid.uuid4())
+            name = "cb-crudbucketobj-{0}".format(helpers.get_uuid())
             test_bucket = self.provider.storage.buckets.create(name)
             test_bucket = self.provider.storage.buckets.create(name)
 
 
             sit.check_crud(self, test_bucket.objects, BucketObject,
             sit.check_crud(self, test_bucket.objects, BucketObject,
-                           "cb_bucketobj", create_bucket_obj,
+                           "cb-bucketobj", create_bucket_obj,
                            cleanup_bucket_obj, skip_name_check=True)
                            cleanup_bucket_obj, skip_name_check=True)
 
 
     @helpers.skipIfNoService(['storage.buckets'])
     @helpers.skipIfNoService(['storage.buckets'])
@@ -88,7 +76,7 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
         check whether list properly detects the new content.
         check whether list properly detects the new content.
         Delete everything afterwards.
         Delete everything afterwards.
         """
         """
-        name = "cbtestbucketobjs-{0}".format(uuid.uuid4())
+        name = "cbtestbucketobjs-{0}".format(helpers.get_uuid())
         test_bucket = self.provider.storage.buckets.create(name)
         test_bucket = self.provider.storage.buckets.create(name)
 
 
         # ensure that the bucket is empty
         # ensure that the bucket is empty
@@ -146,7 +134,7 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
 
 
     @helpers.skipIfNoService(['storage.buckets'])
     @helpers.skipIfNoService(['storage.buckets'])
     def test_upload_download_bucket_content(self):
     def test_upload_download_bucket_content(self):
-        name = "cbtestbucketobjs-{0}".format(uuid.uuid4())
+        name = "cbtestbucketobjs-{0}".format(helpers.get_uuid())
         test_bucket = self.provider.storage.buckets.create(name)
         test_bucket = self.provider.storage.buckets.create(name)
 
 
         with helpers.cleanup_action(lambda: test_bucket.delete()):
         with helpers.cleanup_action(lambda: test_bucket.delete()):
@@ -169,7 +157,7 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
 
 
     @helpers.skipIfNoService(['storage.buckets'])
     @helpers.skipIfNoService(['storage.buckets'])
     def test_generate_url(self):
     def test_generate_url(self):
-        name = "cbtestbucketobjs-{0}".format(uuid.uuid4())
+        name = "cbtestbucketobjs-{0}".format(helpers.get_uuid())
         test_bucket = self.provider.storage.buckets.create(name)
         test_bucket = self.provider.storage.buckets.create(name)
 
 
         with helpers.cleanup_action(lambda: test_bucket.delete()):
         with helpers.cleanup_action(lambda: test_bucket.delete()):
@@ -191,7 +179,7 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
 
 
     @helpers.skipIfNoService(['storage.buckets'])
     @helpers.skipIfNoService(['storage.buckets'])
     def test_upload_download_bucket_content_from_file(self):
     def test_upload_download_bucket_content_from_file(self):
-        name = "cbtestbucketobjs-{0}".format(uuid.uuid4())
+        name = "cbtestbucketobjs-{0}".format(helpers.get_uuid())
         test_bucket = self.provider.storage.buckets.create(name)
         test_bucket = self.provider.storage.buckets.create(name)
 
 
         with helpers.cleanup_action(lambda: test_bucket.delete()):
         with helpers.cleanup_action(lambda: test_bucket.delete()):
@@ -222,7 +210,8 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
             out.truncate(6 * 1024 * 1024 * 1024)  # 6 Gig...
             out.truncate(6 * 1024 * 1024 * 1024)  # 6 Gig...
         with helpers.cleanup_action(lambda: os.remove(six_gig_file)):
         with helpers.cleanup_action(lambda: os.remove(six_gig_file)):
             download_file = "{0}/cbtestfile-{1}".format(temp_dir, file_name)
             download_file = "{0}/cbtestfile-{1}".format(temp_dir, file_name)
-            bucket_name = "cbtestbucketlargeobjs-{0}".format(uuid.uuid4())
+            bucket_name = "cbtestbucketlargeobjs-{0}".format(
+                                                            helpers.get_uuid())
             test_bucket = self.provider.storage.buckets.create(bucket_name)
             test_bucket = self.provider.storage.buckets.create(bucket_name)
             with helpers.cleanup_action(lambda: test_bucket.delete()):
             with helpers.cleanup_action(lambda: test_bucket.delete()):
                 test_obj = test_bucket.objects.create(file_name)
                 test_obj = test_bucket.objects.create(file_name)

+ 52 - 62
test/test_security_service.py

@@ -31,12 +31,12 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
                 self.provider.security.key_pairs.create(name=kp.name)
                 self.provider.security.key_pairs.create(name=kp.name)
 
 
         sit.check_crud(self, self.provider.security.key_pairs, KeyPair,
         sit.check_crud(self, self.provider.security.key_pairs, KeyPair,
-                       "cb_crudkp", create_kp, cleanup_kp,
+                       "cb-crudkp", create_kp, cleanup_kp,
                        extra_test_func=extra_tests)
                        extra_test_func=extra_tests)
 
 
     @helpers.skipIfNoService(['security.key_pairs'])
     @helpers.skipIfNoService(['security.key_pairs'])
     def test_key_pair_properties(self):
     def test_key_pair_properties(self):
-        name = 'cb_kpprops-{0}'.format(helpers.get_uuid())
+        name = 'cb-kpprops-{0}'.format(helpers.get_uuid())
         kp = self.provider.security.key_pairs.create(name=name)
         kp = self.provider.security.key_pairs.create(name=name)
         with helpers.cleanup_action(lambda: kp.delete()):
         with helpers.cleanup_action(lambda: kp.delete()):
             self.assertIsNotNone(
             self.assertIsNotNone(
@@ -49,7 +49,7 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
 
 
     @helpers.skipIfNoService(['security.key_pairs'])
     @helpers.skipIfNoService(['security.key_pairs'])
     def test_import_key_pair(self):
     def test_import_key_pair(self):
-        name = 'cb_kpimport-{0}'.format(helpers.get_uuid())
+        name = 'cb-kpimport-{0}'.format(helpers.get_uuid())
 
 
         public_key, _ = cb_helpers.generate_key_pair()
         public_key, _ = cb_helpers.generate_key_pair()
         kp = self.provider.security.key_pairs.create(
         kp = self.provider.security.key_pairs.create(
@@ -60,85 +60,75 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
 
 
     @helpers.skipIfNoService(['security.vm_firewalls'])
     @helpers.skipIfNoService(['security.vm_firewalls'])
     def test_crud_vm_firewall(self):
     def test_crud_vm_firewall(self):
-        name = 'cb_crudfw-{0}'.format(helpers.get_uuid())
 
 
-        # Declare these variables and late binding will allow
-        # the cleanup method access to the most current values
-        net = None
+        subnet = helpers.get_or_create_default_subnet(self.provider)
+        net = subnet.network
 
 
-        def create_fw(name):
+        def create_fw(label):
             return self.provider.security.vm_firewalls.create(
             return self.provider.security.vm_firewalls.create(
-                name=name, description=name, network_id=net.id)
+                label=label, description=label, network_id=net.id)
 
 
         def cleanup_fw(fw):
         def cleanup_fw(fw):
             if fw:
             if fw:
                 fw.delete()
                 fw.delete()
 
 
-        with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
-                network=net)):
-            net, _ = helpers.create_test_network(self.provider, name)
-
-            sit.check_crud(self, self.provider.security.vm_firewalls,
-                           VMFirewall, "cb_crudfw", create_fw, cleanup_fw)
+        sit.check_crud(self, self.provider.security.vm_firewalls,
+                       VMFirewall, "cb-crudfw", create_fw, cleanup_fw)
 
 
     @helpers.skipIfNoService(['security.vm_firewalls'])
     @helpers.skipIfNoService(['security.vm_firewalls'])
     def test_vm_firewall_properties(self):
     def test_vm_firewall_properties(self):
-        name = 'cb_propfw-{0}'.format(helpers.get_uuid())
+        label = 'cb-propfw-{0}'.format(helpers.get_uuid())
 
 
         # Declare these variables and late binding will allow
         # Declare these variables and late binding will allow
         # the cleanup method access to the most current values
         # the cleanup method access to the most current values
-        net = None
         fw = None
         fw = None
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
-                network=net, vm_firewall=fw)):
-            net, _ = helpers.create_test_network(self.provider, name)
+                vm_firewall=fw)):
+            subnet = helpers.get_or_create_default_subnet(self.provider)
+            net = subnet.network
             fw = self.provider.security.vm_firewalls.create(
             fw = self.provider.security.vm_firewalls.create(
-                name=name, description=name, network_id=net.id)
+                label=label, description=label, network_id=net.id)
 
 
-            self.assertEqual(name, fw.description)
+            self.assertEqual(label, fw.description)
 
 
     @helpers.skipIfNoService(['security.vm_firewalls'])
     @helpers.skipIfNoService(['security.vm_firewalls'])
     def test_crud_vm_firewall_rules(self):
     def test_crud_vm_firewall_rules(self):
-        name = 'cb_crudfw_rules-{0}'.format(helpers.get_uuid())
+        label = 'cb-crudfw-rules-{0}'.format(helpers.get_uuid())
 
 
-        # Declare these variables and late binding will allow
-        # the cleanup method access to the most current values
-        net = None
-        with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
-                network=net)):
-            net, _ = helpers.create_test_network(self.provider, name)
+        subnet = helpers.get_or_create_default_subnet(self.provider)
+        net = subnet.network
 
 
-            fw = None
-            with helpers.cleanup_action(lambda: fw.delete()):
-                fw = self.provider.security.vm_firewalls.create(
-                    name=name, description=name, network_id=net.id)
+        fw = None
+        with helpers.cleanup_action(lambda: fw.delete()):
+            fw = self.provider.security.vm_firewalls.create(
+                label=label, description=label, network_id=net.id)
 
 
-                def create_fw_rule(name):
-                    return fw.rules.create(
-                        direction=TrafficDirection.INBOUND, protocol='tcp',
-                        from_port=1111, to_port=1111, cidr='0.0.0.0/0')
+            def create_fw_rule(label):
+                return fw.rules.create(
+                    direction=TrafficDirection.INBOUND, protocol='tcp',
+                    from_port=1111, to_port=1111, cidr='0.0.0.0/0')
 
 
-                def cleanup_fw_rule(rule):
-                    if rule:
-                        rule.delete()
+            def cleanup_fw_rule(rule):
+                if rule:
+                    rule.delete()
 
 
-                sit.check_crud(self, fw.rules, VMFirewallRule, "cb_crudfwrule",
-                               create_fw_rule, cleanup_fw_rule,
-                               skip_name_check=True)
+            sit.check_crud(self, fw.rules, VMFirewallRule, "cb-crudfwrule",
+                           create_fw_rule, cleanup_fw_rule,
+                           skip_name_check=True)
 
 
     @helpers.skipIfNoService(['security.vm_firewalls'])
     @helpers.skipIfNoService(['security.vm_firewalls'])
     def test_vm_firewall_rule_properties(self):
     def test_vm_firewall_rule_properties(self):
-        name = 'cb_propfwrule-{0}'.format(helpers.get_uuid())
+        label = 'cb-propfwrule-{0}'.format(helpers.get_uuid())
 
 
         # Declare these variables and late binding will allow
         # Declare these variables and late binding will allow
         # the cleanup method access to the most current values
         # the cleanup method access to the most current values
-        net = None
         fw = None
         fw = None
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
-                network=net, vm_firewall=fw)):
-            net, _ = helpers.create_test_network(self.provider, name)
+                vm_firewall=fw)):
+            subnet = helpers.get_or_create_default_subnet(self.provider)
+            net = subnet.network
             fw = self.provider.security.vm_firewalls.create(
             fw = self.provider.security.vm_firewalls.create(
-                name=name, description=name, network_id=net.id)
+                label=label, description=label, network_id=net.id)
 
 
             rule = fw.rules.create(
             rule = fw.rules.create(
                 direction=TrafficDirection.INBOUND, protocol='tcp',
                 direction=TrafficDirection.INBOUND, protocol='tcp',
@@ -151,18 +141,18 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
 
 
     @helpers.skipIfNoService(['security.vm_firewalls'])
     @helpers.skipIfNoService(['security.vm_firewalls'])
     def test_vm_firewall_rule_add_twice(self):
     def test_vm_firewall_rule_add_twice(self):
-        name = 'cb_fwruletwice-{0}'.format(helpers.get_uuid())
+        label = 'cb-fwruletwice-{0}'.format(helpers.get_uuid())
 
 
         # Declare these variables and late binding will allow
         # Declare these variables and late binding will allow
         # the cleanup method access to the most current values
         # the cleanup method access to the most current values
-        net = None
         fw = None
         fw = None
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
-                network=net, vm_firewall=fw)):
+                vm_firewall=fw)):
 
 
-            net, _ = helpers.create_test_network(self.provider, name)
+            subnet = helpers.get_or_create_default_subnet(self.provider)
+            net = subnet.network
             fw = self.provider.security.vm_firewalls.create(
             fw = self.provider.security.vm_firewalls.create(
-                name=name, description=name, network_id=net.id)
+                label=label, description=label, network_id=net.id)
 
 
             rule = fw.rules.create(
             rule = fw.rules.create(
                 direction=TrafficDirection.INBOUND, protocol='tcp',
                 direction=TrafficDirection.INBOUND, protocol='tcp',
@@ -175,17 +165,17 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
 
 
     @helpers.skipIfNoService(['security.vm_firewalls'])
     @helpers.skipIfNoService(['security.vm_firewalls'])
     def test_vm_firewall_group_rule(self):
     def test_vm_firewall_group_rule(self):
-        name = 'cb_fwrule-{0}'.format(helpers.get_uuid())
+        label = 'cb-fwrule-{0}'.format(helpers.get_uuid())
 
 
         # Declare these variables and late binding will allow
         # Declare these variables and late binding will allow
         # the cleanup method access to the most current values
         # the cleanup method access to the most current values
-        net = None
         fw = None
         fw = None
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
         with helpers.cleanup_action(lambda: helpers.cleanup_test_resources(
-                network=net, vm_firewall=fw)):
-            net, _ = helpers.create_test_network(self.provider, name)
+                vm_firewall=fw)):
+            subnet = helpers.get_or_create_default_subnet(self.provider)
+            net = subnet.network
             fw = self.provider.security.vm_firewalls.create(
             fw = self.provider.security.vm_firewalls.create(
-                name=name, description=name, network_id=net.id)
+                label=label, description=label, network_id=net.id)
             rules = list(fw.rules)
             rules = list(fw.rules)
             self.assertTrue(
             self.assertTrue(
                 # TODO: This should be made consistent across all providers.
                 # TODO: This should be made consistent across all providers.
@@ -200,9 +190,9 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
                 direction=TrafficDirection.INBOUND, src_dest_fw=fw,
                 direction=TrafficDirection.INBOUND, src_dest_fw=fw,
                 protocol='tcp', from_port=1, to_port=65535)
                 protocol='tcp', from_port=1, to_port=65535)
             self.assertTrue(
             self.assertTrue(
-                rule.src_dest_fw.name == name,
-                "Expected VM firewall rule name {0}. Got {1}."
-                .format(name, rule.src_dest_fw.name))
+                rule.src_dest_fw.label == fw.label,
+                "Expected VM firewall rule label {0}. Got {1}."
+                .format(fw.label, rule.src_dest_fw.label))
             for r in fw.rules:
             for r in fw.rules:
                 r.delete()
                 r.delete()
             fw = self.provider.security.vm_firewalls.get(fw.id)  # update
             fw = self.provider.security.vm_firewalls.get(fw.id)  # update
@@ -211,8 +201,8 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
                 "Deleting VMFirewallRule should delete it: {0}".format(
                 "Deleting VMFirewallRule should delete it: {0}".format(
                     fw.rules))
                     fw.rules))
         fwl = self.provider.security.vm_firewalls.list()
         fwl = self.provider.security.vm_firewalls.list()
-        found_fw = [f for f in fwl if f.name == name]
+        found_fw = [f for f in fwl if f.label == label]
         self.assertTrue(
         self.assertTrue(
             len(found_fw) == 0,
             len(found_fw) == 0,
             "VM firewall {0} should have been deleted but still exists."
             "VM firewall {0} should have been deleted but still exists."
-            .format(name))
+            .format(label))

+ 3 - 2
test/test_vm_types_service.py

@@ -24,13 +24,14 @@ class CloudVMTypeServiceTestCase(ProviderTestBase):
                 vm_type.family is None or isinstance(
                 vm_type.family is None or isinstance(
                     vm_type.family,
                     vm_type.family,
                     six.string_types),
                     six.string_types),
-                "VMType family family be None or a"
+                "VMType family must be None or a"
                 " string but is: {0}".format(vm_type.family))
                 " string but is: {0}".format(vm_type.family))
             self.assertTrue(
             self.assertTrue(
                 vm_type.vcpus is None or (
                 vm_type.vcpus is None or (
                     isinstance(vm_type.vcpus, six.integer_types) and
                     isinstance(vm_type.vcpus, six.integer_types) and
                     vm_type.vcpus >= 0),
                     vm_type.vcpus >= 0),
-                "VMType vcpus family be None or a positive integer")
+                "VMType vcpus must be None or a positive integer but is: {0}"
+                .format(vm_type.vcpus))
             self.assertTrue(
             self.assertTrue(
                 vm_type.ram is None or vm_type.ram >= 0,
                 vm_type.ram is None or vm_type.ram >= 0,
                 "VMType ram must be None or a positive number")
                 "VMType ram must be None or a positive number")

+ 1 - 1
tox.ini

@@ -24,7 +24,7 @@ setenv =
     azure: CB_TEST_PROVIDER=azure
     azure: CB_TEST_PROVIDER=azure
     openstack: CB_TEST_PROVIDER=openstack
     openstack: CB_TEST_PROVIDER=openstack
 passenv =
 passenv =
-    CB_USE_MOCK_PROVIDERS
+    CB_USE_MOCK_PROVIDERS PYTHONUNBUFFERED
     aws: CB_IMAGE_AWS CB_INSTANCE_TYPE_AWS CB_PLACEMENT_AWS AWS_ACCESS_KEY AWS_SECRET_KEY
     aws: CB_IMAGE_AWS CB_INSTANCE_TYPE_AWS CB_PLACEMENT_AWS AWS_ACCESS_KEY AWS_SECRET_KEY
     azure: AZURE_SUBSCRIPTION_ID AZURE_CLIENT_ID AZURE_SECRET AZURE_TENANT AZURE_REGION_NAME AZURE_RESOURCE_GROUP AZURE_STORAGE_ACCOUNT AZURE_VM_DEFAULT_USER_NAME AZURE_PUBLIC_KEY_STORAGE_TABLE_NAME
     azure: AZURE_SUBSCRIPTION_ID AZURE_CLIENT_ID AZURE_SECRET AZURE_TENANT AZURE_REGION_NAME AZURE_RESOURCE_GROUP AZURE_STORAGE_ACCOUNT AZURE_VM_DEFAULT_USER_NAME AZURE_PUBLIC_KEY_STORAGE_TABLE_NAME
     openstack:  CB_IMAGE_OS CB_INSTANCE_TYPE_OS CB_PLACEMENT_OS OS_AUTH_URL OS_PASSWORD OS_PROJECT_NAME OS_TENANT_NAME OS_USERNAME OS_REGION_NAME OS_USER_DOMAIN_NAME OS_PROJECT_DOMAIN_NAME NOVA_SERVICE_NAME
     openstack:  CB_IMAGE_OS CB_INSTANCE_TYPE_OS CB_PLACEMENT_OS OS_AUTH_URL OS_PASSWORD OS_PROJECT_NAME OS_TENANT_NAME OS_USERNAME OS_REGION_NAME OS_USER_DOMAIN_NAME OS_PROJECT_DOMAIN_NAME NOVA_SERVICE_NAME