Bläddra i källkod

Resolving merge conflicts

almahmoud 7 år sedan
förälder
incheckning
e2b81c9f24

+ 6 - 0
CHANGELOG.rst

@@ -1,3 +1,9 @@
+1.0.2 - September 25, 2018 (sha 621aeed1a8d7c5ad270649f8ee960e9682e57dae)
+-------
+* Added AWS instance types caching for better performance
+* Added ``router.subnets`` property
+* Ensure the default network for CloudBridge on AWS has subnets
+
 1.0.1 - September 7, 2018. (sha 3130492008c5e0e115b8dfec880d32a4ac90b761)
 -------
 * Fixed minor bug when retrieving buckets with only limited access.

+ 1 - 1
cloudbridge/__init__.py

@@ -2,7 +2,7 @@
 import logging
 
 # Current version of the library
-__version__ = '1.0.1'
+__version__ = '1.0.2'
 
 
 def get_version():

+ 34 - 0
cloudbridge/cloud/base/helpers.py

@@ -1,4 +1,5 @@
 import fnmatch
+import functools
 import os
 import re
 import sys
@@ -9,8 +10,12 @@ from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives import serialization as crypt_serialization
 from cryptography.hazmat.primitives.asymmetric import rsa
 
+from deprecation import deprecated
+
 import six
 
+import cloudbridge
+
 
 def generate_key_pair():
     """
@@ -129,3 +134,32 @@ def get_env(varname, default_value=None):
             value, six.text_type):
         return six.u(value)
     return value
+
+
+# Alias deprication decorator, following:
+# https://stackoverflow.com/questions/49802412/
+# how-to-implement-deprecation-in-python-with-argument-alias
+def deprecated_alias(**aliases):
+    def deco(f):
+        @functools.wraps(f)
+        def wrapper(*args, **kwargs):
+            rename_kwargs(f.__name__, kwargs, aliases)
+            return f(*args, **kwargs)
+        return wrapper
+    return deco
+
+
+def rename_kwargs(func_name, kwargs, aliases):
+    for alias, new in aliases.items():
+        if alias in kwargs:
+            if new in kwargs:
+                raise TypeError('{} received both {} and {}'.format(
+                    func_name, alias, new))
+            # Manually invoke the deprecated decorator with an empty lambda
+            # to signal deprecation
+            deprecated(deprecated_in='1.1',
+                       removed_in='2.0',
+                       current_version=cloudbridge.__version__,
+                       details='{} is deprecated, use {} instead'.format(
+                           alias, new))(lambda: None)()
+            kwargs[new] = kwargs.pop(alias)

+ 2 - 2
cloudbridge/cloud/base/resources.py

@@ -86,8 +86,8 @@ class BaseCloudResource(CloudResource):
             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)
+                " letters, numbers, or dashes. The label must start with a "
+                "letter and not end with a dash." % name)
 
     @staticmethod
     def assert_valid_resource_name(name):

+ 2 - 1
cloudbridge/cloud/factory.py

@@ -120,7 +120,8 @@ class CloudProviderFactory(object):
         cloud provider.
 
         :type name: str
-        :param name: Cloud provider name: one of ``aws``, ``openstack``.
+        :param name: Cloud provider name: one of ``aws``, ``openstack``,
+        ``azure``.
 
         :type config: an object with required fields
         :param config: This can be a Bunch or any other object whose fields can

+ 1 - 1
cloudbridge/cloud/interfaces/resources.py

@@ -731,7 +731,7 @@ class LaunchConfig(object):
         lc.add_block_device(...)
 
         inst = provider.compute.instances.create(
-            'MyVM', image, vm_type, subnet, launch_config=lc)
+            'MyVM', image, vm_type, subnet, zone, launch_config=lc)
     """
 
     @abstractmethod

+ 5 - 5
cloudbridge/cloud/interfaces/services.py

@@ -96,7 +96,7 @@ class ComputeService(CloudService):
 
             # launch a new instance
             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')[0]
             instance = provider.compute.instances.create('Hello', image, size)
             print(instance.id, instance.label)
 
@@ -730,7 +730,7 @@ class SubnetService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def create(self, label, network_id, cidr_block, zone):
+    def create(self, label, network, cidr_block, zone):
         """
         Create a new subnet within the supplied network.
 
@@ -1119,15 +1119,15 @@ class VMFirewallService(PageableObjectMixin, CloudService):
         pass
 
     @abstractmethod
-    def create(self, label, network_id, description=None):
+    def create(self, label, network, description=None):
         """
         Create a new VMFirewall.
 
         :type label: str
         :param label: The label for the new VM firewall.
 
-        :type  network_id: ``str``
-        :param network_id: Network ID under which to create the VM firewall.
+        :type  network: ``str``
+        :param network: Network ID under which to create the VM firewall.
 
         :type description: str
         :param description: The description of the new VM firewall.

+ 3 - 2
cloudbridge/cloud/providers/aws/provider.py

@@ -2,6 +2,7 @@
 import logging as log
 
 import boto3
+
 try:
     # These are installed only for the case of a dev instance
     import responses
@@ -105,9 +106,9 @@ class AWSCloudProvider(BaseCloudProvider):
         """
         Get a boto ec2 connection object.
         """
-        return self._conect_ec2_region(region_name=self.region_name)
+        return self._connect_ec2_region(region_name=self.region_name)
 
-    def _conect_ec2_region(self, region_name=None):
+    def _connect_ec2_region(self, region_name=None):
         '''Get an EC2 resource object'''
         return self.session.resource(
             'ec2', region_name=region_name, **self.ec2_cfg)

+ 2 - 2
cloudbridge/cloud/providers/aws/resources.py

@@ -952,7 +952,7 @@ class AWSRegion(BaseRegion):
             conn = self._provider.ec2_conn
         else:
             # pylint:disable=protected-access
-            conn = self._provider._conect_ec2_region(region_name=self.id)
+            conn = self._provider._connect_ec2_region(region_name=self.id)
 
         zones = (conn.meta.client.describe_availability_zones()
                  .get('AvailabilityZones', []))
@@ -1155,7 +1155,7 @@ class AWSFloatingIP(BaseFloatingIP):
 
     @property
     def in_use(self):
-        return True if self._ip.instance_id else False
+        return True if self._ip.association_id else False
 
     def delete(self):
         self._ip.release()

+ 5 - 2
cloudbridge/cloud/providers/aws/services.py

@@ -30,6 +30,7 @@ from cloudbridge.cloud.interfaces.exceptions \
     import DuplicateResourceException, InvalidConfigurationException
 from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import MachineImage
+from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.resources import PlacementZone
 from cloudbridge.cloud.interfaces.resources import Snapshot
 from cloudbridge.cloud.interfaces.resources import VMFirewall
@@ -134,12 +135,14 @@ class AWSVMFirewallService(BaseVMFirewallService):
     def list(self, limit=None, marker=None):
         return self.svc.list(limit=limit, marker=marker)
 
-    def create(self, label, network_id, description=None):
+    @cb_helpers.deprecated_alias(network_id='network')
+    def create(self, label, network=None, description=None):
         log.debug("Creating Firewall Service with the parameters "
-                  "[label: %s id: %s description: %s]", label, network_id,
+                  "[label: %s id: %s description: %s]", label, network,
                   description)
         AWSVMFirewall.assert_valid_resource_label(label)
         name = AWSVMFirewall._generate_name_from_label(label, 'cb-fw')
+        network_id = network.id if isinstance(network, Network) else network
         obj = self.svc.create('create_security_group', GroupName=name,
                               Description=description or name,
                               VpcId=network_id)

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

@@ -408,8 +408,11 @@ class AzureClient(object):
         return self.network_management_client.security_rules. \
             delete(self.resource_group, vm_firewall, name).result()
 
-    def list_containers(self, prefix=None):
-        return self.blob_service.list_containers(prefix=prefix)
+    def list_containers(self, prefix=None, limit=None, marker=None):
+        results = self.blob_service.list_containers(prefix=prefix,
+                                                    num_results=limit,
+                                                    marker=marker)
+        return (results.items, results.next_marker)
 
     def create_container(self, container_name):
         try:

+ 23 - 2
cloudbridge/cloud/providers/azure/provider.py

@@ -1,10 +1,13 @@
 import logging
 import uuid
 
+from deprecation import deprecated
+
 from msrestazure.azure_exceptions import CloudError
 
 import tenacity
 
+import cloudbridge
 from cloudbridge.cloud.base import BaseCloudProvider
 from cloudbridge.cloud.base.helpers import get_env
 from cloudbridge.cloud.interfaces.exceptions import ProviderConnectionException
@@ -54,8 +57,9 @@ class AzureCloudProvider(BaseCloudProvider):
                                str(self.resource_group)))[-6:]))
 
         self.vm_default_user_name = self._get_config_value(
-            'azure_vm_default_user_name', get_env(
-                'AZURE_VM_DEFAULT_USER_NAME', 'cbuser'))
+                'azure_vm_default_username', get_env(
+                    'AZURE_VM_DEFAULT_USERNAME', None)) \
+            or self.__get_deprecated_username('cbuser')
 
         self.public_key_storage_table_name = self._get_config_value(
             'azure_public_key_storage_table_name', get_env(
@@ -68,6 +72,23 @@ class AzureCloudProvider(BaseCloudProvider):
         self._compute = AzureComputeService(self)
         self._networking = AzureNetworkingService(self)
 
+    def __get_deprecated_username(self, default):
+        username = self._get_config_value(
+            'azure_vm_default_user_name', get_env(
+                'AZURE_VM_DEFAULT_USER_NAME', None))
+        if username:
+            return self.__wrap_deprecated_username(username)
+        else:
+            return default
+
+    @deprecated(deprecated_in='1.1',
+                removed_in='2.0',
+                current_version=cloudbridge.__version__,
+                details='AZURE_VM_DEFAULT_USER_NAME was deprecated in favor '
+                        'of AZURE_VM_DEFAULT_USERNAME')
+    def __wrap_deprecated_username(self, username):
+        return username
+
     @property
     def compute(self):
         return self._compute

+ 3 - 7
cloudbridge/cloud/providers/azure/resources.py

@@ -1124,7 +1124,7 @@ class AzureSubnet(BaseSubnet):
     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
+        network = self.network
         az_network = network._network
         return az_network.tags.get(self.tag_name, None)
 
@@ -1132,7 +1132,7 @@ class AzureSubnet(BaseSubnet):
     # pylint:disable=arguments-differ
     def label(self, value):
         self.assert_valid_resource_label(value)
-        network = self._network
+        network = self.network
         az_network = network._network
         kwargs = {self.tag_name: value or ""}
         az_network.tags.update(**kwargs)
@@ -1153,7 +1153,7 @@ class AzureSubnet(BaseSubnet):
     def zone(self):
         # pylint:disable=protected-access
         region = self._provider.compute.regions.get(
-            self._network._network.location)
+            self.network._network.location)
         return region.zones[0]
 
     @property
@@ -1164,10 +1164,6 @@ class AzureSubnet(BaseSubnet):
     def network_id(self):
         return self._provider.azure_client.get_network_id_for_subnet(self.id)
 
-    @property
-    def _network(self):
-        return self._provider.networking.networks.get(self.network_id)
-
     def delete(self):
         self._provider.azure_client.delete_subnet(self.id)
 

+ 80 - 71
cloudbridge/cloud/providers/azure/services.py

@@ -48,76 +48,6 @@ class AzureSecurityService(BaseSecurityService):
         return self._vm_firewalls
 
 
-class AzureKeyPairService(BaseKeyPairService):
-    PARTITION_KEY = '00000000-0000-0000-0000-000000000000'
-
-    def __init__(self, provider):
-        super(AzureKeyPairService, self).__init__(provider)
-
-    def get(self, key_pair_id):
-        try:
-            key_pair = self.provider.azure_client.\
-                get_public_key(key_pair_id)
-
-            if key_pair:
-                return AzureKeyPair(self.provider, key_pair)
-            return None
-        except AzureException as error:
-            log.debug("KeyPair %s was not found.", key_pair_id)
-            log.debug(error)
-            return None
-
-    def list(self, limit=None, marker=None):
-        key_pairs, resume_marker = self.provider.azure_client.list_public_keys(
-            AzureKeyPairService.PARTITION_KEY, marker=marker,
-            limit=limit or self.provider.config.default_result_limit)
-        results = [AzureKeyPair(self.provider, key_pair)
-                   for key_pair in key_pairs]
-        return ServerPagedResultList(is_truncated=resume_marker,
-                                     marker=resume_marker,
-                                     supports_total=False,
-                                     data=results)
-
-    def find(self, **kwargs):
-        obj_list = self
-        filters = ['name']
-        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
-
-        # 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, name, public_key_material=None):
-        AzureKeyPair.assert_valid_resource_name(name)
-
-        key_pair = self.get(name)
-
-        if key_pair:
-            raise DuplicateResourceException(
-                'Keypair already exists with name {0}'.format(name))
-
-        private_key = None
-        if not public_key_material:
-            public_key_material, private_key = cb_helpers.generate_key_pair()
-
-        entity = {
-            'PartitionKey': AzureKeyPairService.PARTITION_KEY,
-            'RowKey': str(uuid.uuid4()),
-            'Name': name,
-            'Key': public_key_material
-        }
-
-        self.provider.azure_client.create_public_key(entity)
-        key_pair = self.get(name)
-        key_pair.material = private_key
-        return key_pair
-
-
 class AzureVMFirewallService(BaseVMFirewallService):
     def __init__(self, provider):
         super(AzureVMFirewallService, self).__init__(provider)
@@ -136,7 +66,8 @@ class AzureVMFirewallService(BaseVMFirewallService):
                for fw in self.provider.azure_client.list_vm_firewall()]
         return ClientPagedResultList(self.provider, fws, limit, marker)
 
-    def create(self, label, description=None, network_id=None):
+    @cb_helpers.deprecated_alias(network_id='network')
+    def create(self, label, network=None, description=None):
         AzureVMFirewall.assert_valid_resource_label(label)
         name = AzureVMFirewall._generate_name_from_label(label, "cb-fw")
         parameters = {"location": self.provider.region_name,
@@ -194,6 +125,76 @@ class AzureVMFirewallService(BaseVMFirewallService):
         self.provider.azure_client.delete_vm_firewall(group_id)
 
 
+class AzureKeyPairService(BaseKeyPairService):
+    PARTITION_KEY = '00000000-0000-0000-0000-000000000000'
+
+    def __init__(self, provider):
+        super(AzureKeyPairService, self).__init__(provider)
+
+    def get(self, key_pair_id):
+        try:
+            key_pair = self.provider.azure_client.\
+                get_public_key(key_pair_id)
+
+            if key_pair:
+                return AzureKeyPair(self.provider, key_pair)
+            return None
+        except AzureException as error:
+            log.debug("KeyPair %s was not found.", key_pair_id)
+            log.debug(error)
+            return None
+
+    def list(self, limit=None, marker=None):
+        key_pairs, resume_marker = self.provider.azure_client.list_public_keys(
+            AzureKeyPairService.PARTITION_KEY, marker=marker,
+            limit=limit or self.provider.config.default_result_limit)
+        results = [AzureKeyPair(self.provider, key_pair)
+                   for key_pair in key_pairs]
+        return ServerPagedResultList(is_truncated=resume_marker,
+                                     marker=resume_marker,
+                                     supports_total=False,
+                                     data=results)
+
+    def find(self, **kwargs):
+        obj_list = self
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+
+        # 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, name, public_key_material=None):
+        AzureKeyPair.assert_valid_resource_name(name)
+
+        key_pair = self.get(name)
+
+        if key_pair:
+            raise DuplicateResourceException(
+                'Keypair already exists with name {0}'.format(name))
+
+        private_key = None
+        if not public_key_material:
+            public_key_material, private_key = cb_helpers.generate_key_pair()
+
+        entity = {
+            'PartitionKey': AzureKeyPairService.PARTITION_KEY,
+            'RowKey': str(uuid.uuid4()),
+            'Name': name,
+            'Key': public_key_material
+        }
+
+        self.provider.azure_client.create_public_key(entity)
+        key_pair = self.get(name)
+        key_pair.material = private_key
+        return key_pair
+
+
 class AzureStorageService(BaseStorageService):
     def __init__(self, provider):
         super(AzureStorageService, self).__init__(provider)
@@ -1016,6 +1017,14 @@ class AzureSubnetService(BaseSubnetService):
     def delete(self, subnet):
         subnet_id = subnet.id if isinstance(subnet, Subnet) else subnet
         self.provider.azure_client.delete_subnet(subnet_id)
+        # Although Subnet doesn't support labels, we use the parent Network's
+        # tags to track the subnet's labels, thus that network-level tag must
+        # be deleted with the subnet
+        network = subnet.network
+        az_network = network._network
+        az_network.tags.pop(subnet.tag_name)
+        self._provider.azure_client.update_network_tags(
+            az_network.id, az_network)
 
 
 class AzureRouterService(BaseRouterService):

+ 3 - 6
cloudbridge/cloud/providers/openstack/resources.py

@@ -453,12 +453,10 @@ class OpenStackInstance(BaseInstance):
         """
         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(label)
         img = OpenStackMachineImage(
             self._provider, self._provider.compute.images.get(image_id))
-        img.label = label
         return img
 
     def _get_fip(self, floating_ip):
@@ -785,13 +783,12 @@ class OpenStackSnapshot(BaseSnapshot):
         Create a new Volume from this Snapshot.
         """
         vol_label = "from-snap-{0}".format(self.id or self.label)
-        name = self._generate_name_from_label(vol_label, 'cb-vol')
+        self.assert_valid_resource_label(vol_label)
         size = size if size else self._snapshot.size
         os_vol = self._provider.cinder.volumes.create(
-            size, name=name, availability_zone=placement,
+            size, name=vol_label, availability_zone=placement,
             snapshot_id=self._snapshot.id)
         cb_vol = OpenStackVolume(self._provider, os_vol)
-        cb_vol.label = vol_label
         return cb_vol
 
 

+ 5 - 7
cloudbridge/cloud/providers/openstack/services.py

@@ -210,14 +210,14 @@ class OpenStackVMFirewallService(BaseVMFirewallService):
         return ClientPagedResultList(self.provider, firewalls,
                                      limit=limit, marker=marker)
 
-    def create(self, label, description, network_id):
+    @cb_helpers.deprecated_alias(network_id='network')
+    def create(self, label, network, description=None):
         OpenStackVMFirewall.assert_valid_resource_label(label)
-        name = OpenStackVMFirewall._generate_name_from_label(label, 'cb-fw')
         log.debug("Creating OpenStack VM Firewall with the params: "
                   "[label: %s network id: %s description: %s]", label,
-                  network_id, description)
+                  network, description)
         sg = self.provider.os_conn.network.create_security_group(
-            name=name, description=description or name)
+            name=label, description=description or label)
         if sg:
             return OpenStackVMFirewall(self.provider, sg)
         return None
@@ -857,15 +857,13 @@ class OpenStackSubnetService(BaseSubnetService):
                   "[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)
                       else network)
-        subnet_info = {'name': name, 'network_id': network_id,
+        subnet_info = {'name': label, 'network_id': network_id,
                        'cidr': cidr_block, 'ip_version': 4}
         subnet = (self.provider.neutron.create_subnet({'subnet': subnet_info})
                   .get('subnet'))
         cb_subnet = OpenStackSubnet(self.provider, subnet)
-        cb_subnet.label = label
         return cb_subnet
 
     def get_or_create_default(self, zone):

+ 4 - 3
docs/getting_started.rst

@@ -103,7 +103,7 @@ on disk as a read-only file.
 .. code-block:: python
 
     import os
-    kp = provider.security.key_pairs.create('cloudbridge_intro')
+    kp = provider.security.key_pairs.create('cloudbridge-intro')
     with open('cloudbridge_intro.pem', 'w') as f:
         f.write(kp.material)
     os.chmod('cloudbridge_intro.pem', 0o400)
@@ -148,12 +148,13 @@ also add the network interface as a launch argument.
 .. code-block:: python
 
     img = provider.compute.images.get(image_id)
+    zone = provider.compute.regions.get(provider.region_name).zones[0]
     vm_type = sorted([t for t in provider.compute.vm_types
                       if t.vcpus >= 2 and t.ram >= 4],
                       key=lambda x: x.vcpus*x.ram)[0]
     inst = provider.compute.instances.create(
         image=img, vm_type=vm_type, label='cloudbridge-intro',
-        subnet=sn, key_pair=kp, vm_firewalls=[fw])
+        subnet=sn, zone=zone, key_pair=kp, vm_firewalls=[fw])
     # Wait until ready
     inst.wait_till_ready()  # This is a blocking call
     # Show instance state
@@ -205,7 +206,7 @@ their provider mappings, see :doc:`topics/resource_types_and_mappings`.
 
     # Key Pair
     kp = provider.security.key_pairs.get('keypair ID')
-    kp_list = provider.security.key_pairs.find(name='cloudbridge_intro')
+    kp_list = provider.security.key_pairs.find(name='cloudbridge-intro')
     kp = kp_list[0]
 
     # Floating IPs

+ 1 - 1
docs/topics/design_decisions.rst

@@ -15,7 +15,7 @@ Require zone parameter when creating a default subnet
   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_.)
+  lead to a mismatch. (Related to 63_.)
 
 Resource identification, naming, and labeling
 ---------------------------------------------

+ 5 - 4
docs/topics/launch.rst

@@ -39,7 +39,7 @@ if you don't have those resources under your account, take a look at the
 
 .. code-block:: python
 
-    kp = provider.security.key_pairs.find(name='cloudbridge_intro')[0]
+    kp = provider.security.key_pairs.find(name='cloudbridge-intro')[0]
     fw = provider.security.vm_firewalls.list()[0]
 
 Launch an instance
@@ -50,7 +50,7 @@ Once we have all the desired pieces, we'll use them to launch an instance:
 
     inst = provider.compute.instances.create(
         name='cloudbridge-vpc', image=img, vm_type=vm_type,
-        subnet=subnet, key_pair=kp, vm_firewalls=[fw])
+        subnet=subnet, zone=zone, key_pair=kp, vm_firewalls=[fw])
 
 Private networking
 ~~~~~~~~~~~~~~~~~~
@@ -73,7 +73,7 @@ that subnet.
 
     inst = provider.compute.instances.create(
         name='cloudbridge-vpc', image=img, vm_type=vm_type,
-        subnet=sn, key_pair=kp, vm_firewalls=[fw])
+        subnet=sn, zone=zone, key_pair=kp, vm_firewalls=[fw])
 
 For more information on how to create and setup a private network, take a look
 at `Networking <./networking.html>`_.
@@ -95,7 +95,8 @@ refer to :class:`.LaunchConfig`.
     lc.add_volume_device(source=img, size=11, is_root=True)
     inst = provider.compute.instances.create(
         name='cloudbridge-bdm', image=img,  vm_type=vm_type,
-        launch_config=lc, key_pair=kp, vm_firewalls=[fw])
+        launch_config=lc, key_pair=kp, vm_firewalls=[fw],
+        subnet=subnet, zone=zone)
 
 where ``img`` is the :class:`.Image` object to use for the root volume.
 

+ 4 - 4
docs/topics/networking.rst

@@ -51,9 +51,9 @@ several common scenarios.
      receive incoming traffic from Tier 2, but must not be able to make
      outgoing traffic outside of its subnet.
 
-    At present, CloudBridge does not provide support for this scenario,
-    primarily because OpenStack's FwaaS (Firewall-as-a-Service) is not widely
-    available.
+   At present, CloudBridge does not provide support for this scenario,
+   primarily because OpenStack's FwaaS (Firewall-as-a-Service) is not widely
+   available.
 
 1. Allowing internet access from a launched VM
 ----------------------------------------------
@@ -90,7 +90,7 @@ The additional step that's required here is to assign a floating IP to the VM:
         name='my-network', cidr_block='10.0.0.0/16')
     sn = net.create_subnet(name='my-subnet', cidr_block='10.0.0.0/28', zone=zone)
 
-    vm = provider.compute.instances.create('my-inst', subnet=sn, ...)
+    vm = provider.compute.instances.create('my-inst', subnet=sn, zone=zone, ...)
 
     router = provider.networking.routers.create(network=net, name='my-router')
     router.attach_subnet(sn)

+ 10 - 10
docs/topics/release_process.rst

@@ -1,21 +1,21 @@
 Release Process
 ~~~~~~~~~~~~~~~
 
-1. Increment version number in ``cloudbridge/__init__.py`` as per
-   `semver rules <https://semver.org/>_.
+1. Make sure `all tests pass <https://travis-ci.org/CloudVE/cloudbridge>`_.
 
-2. Freeze all library dependencies in ``setup.py``. The version numbers can be
-   a range with the upper limit being the latest known working version, and the
-   lowest being the last known working version.
+2. Increment version number in ``cloudbridge/__init__.py`` as per
+   `semver rules <https://semver.org/>`_.
+
+3. Freeze all library dependencies in ``setup.py`` and commit.
+   The version numbers can be a range with the upper limit being the latest
+   known working version, and the lowest being the last known working version.
 
    In general, our strategy is to make provider sdk libraries fixed within
    relatively known compatibility ranges, so that we reduce the chances of
-   breakage. If someone uses cloudbridge, presumably, they do not use the sdks
+   breakage. If someone uses CloudBridge, presumably, they do not use the SDKs
    directly. For all other libraries, especially, general purpose libraries
-   (e.g. six), our strategy is to make compatibility as broad and unrestricted
-   as possible.
-
-3. Run all ``tox`` tests.
+   (e.g. ``six``), our strategy is to make compatibility as broad and
+   unrestricted as possible.
 
 4. Add release notes to ``CHANGELOG.rst``. Also add last commit hash to
    changelog. List of commits can be obtained using

+ 2 - 1
setup.py

@@ -22,7 +22,8 @@ REQS_BASE = [
     'bunch>=1.0.1',
     'six>=1.11',
     'tenacity>=4.12.0,<=5.0',
-    'cachetools'
+    'cachetools>=2.1.0',
+    'deprecated>=1.2.3'
 ]
 REQS_AWS = ['boto3<1.8.0']
 # Install azure>=3.0.0 package to find which of the azure libraries listed