Browse Source

Merge branch 'master' of https://github.com/gvlproject/cloudbridge

Nuwan Goonasekera 10 years ago
parent
commit
f5ee5b61c5

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

@@ -95,8 +95,8 @@ class BaseCloudProvider(CloudProvider):
         :param key: a field to look for in the ``self.config`` field
         :param key: a field to look for in the ``self.config`` field
 
 
         :type default_value: anything
         :type default_value: anything
-        : param default_value: the default value to return if a value for the
-                               ``key`` is not available
+        :param default_value: the default value to return if a value for the
+                              ``key`` is not available
 
 
         :return: a configuration value for the supplied ``key``
         :return: a configuration value for the supplied ``key``
         """
         """

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

@@ -142,11 +142,11 @@ class ObjectLifeCycleMixin(object):
     """
     """
     A mixin for an object with a defined life-cycle, such as an Instance,
     A mixin for an object with a defined life-cycle, such as an Instance,
     Volume, Image or Snapshot. An object that supports ObjectLifeCycleMixin
     Volume, Image or Snapshot. An object that supports ObjectLifeCycleMixin
-    will always have a state, defining which point in its lifecycle it is
+    will always have a state, defining which point in its life cycle it is
     currently at.
     currently at.
 
 
     It also defines a wait_till_ready operation, which indicates that the
     It also defines a wait_till_ready operation, which indicates that the
-    object is in a state in its lifecycle where it is ready to be used by an
+    object is in a state in its life cycle where it is ready to be used by an
     end-user.
     end-user.
 
 
     A refresh operation allows the object to synchronise its state with the
     A refresh operation allows the object to synchronise its state with the
@@ -506,7 +506,7 @@ class Instance(ObjectLifeCycleMixin, CloudResource):
         Reboot this instance (using the cloud middleware API).
         Reboot this instance (using the cloud middleware API).
 
 
         :rtype: bool
         :rtype: bool
-        :return: ``True`` if the reboot was succesful; ``False`` otherwise.
+        :return: ``True`` if the reboot was successful; ``False`` otherwise.
         """
         """
         pass
         pass
 
 
@@ -516,7 +516,7 @@ class Instance(ObjectLifeCycleMixin, CloudResource):
         Permanently terminate this instance.
         Permanently terminate this instance.
 
 
         :rtype: bool
         :rtype: bool
-        :return: ``True`` if the termination of the instance was succesfully
+        :return: ``True`` if the termination of the instance was successfully
                  initiated; ``False`` otherwise.
                  initiated; ``False`` otherwise.
         """
         """
         pass
         pass
@@ -524,7 +524,7 @@ class Instance(ObjectLifeCycleMixin, CloudResource):
     @abstractproperty
     @abstractproperty
     def image_id(self):
     def image_id(self):
         """
         """
-        Get the image ID for this insance.
+        Get the image ID for this instance.
 
 
         :rtype: str
         :rtype: str
         :return: Image ID (i.e., AMI) this instance is using.
         :return: Image ID (i.e., AMI) this instance is using.
@@ -756,7 +756,7 @@ class LaunchConfig(object):
         :type net_id: str
         :type net_id: str
         :param net_id: Network ID to launch an instance into. This is a
         :param net_id: Network ID to launch an instance into. This is a
                        preliminary implementation (pending full private cloud
                        preliminary implementation (pending full private cloud
-                       support within cloudbridge) so native network IDs need
+                       support within CloudBridge) so native network IDs need
                        to be supplied. For OpenStack, this is the Neutron
                        to be supplied. For OpenStack, this is the Neutron
                        network ID. For AWS, this is a VPC subnet ID. For the
                        network ID. For AWS, this is a VPC subnet ID. For the
                        time being, only a single network interface can be
                        time being, only a single network interface can be
@@ -1280,7 +1280,7 @@ class Snapshot(ObjectLifeCycleMixin, CloudResource):
         Set the snapshot description. Some cloud providers may not support this
         Set the snapshot description. Some cloud providers may not support this
         property, and setting the description may have no effect. (Providers
         property, and setting the description may have no effect. (Providers
         that do not support this property will always return the snapshot name
         that do not support this property will always return the snapshot name
-        as the description)
+        as the description).
         """
         """
         pass
         pass
 
 
@@ -1458,7 +1458,7 @@ class Region(CloudResource):
     @abstractproperty
     @abstractproperty
     def zones(self):
     def zones(self):
         """
         """
-        Accesss information about placement zones within this region.
+        Access information about placement zones within this region.
 
 
         :rtype: iterable
         :rtype: iterable
         :return: Iterable of available placement zones in this region.
         :return: Iterable of available placement zones in this region.
@@ -1545,7 +1545,7 @@ class InstanceType(CloudResource):
     @abstractproperty
     @abstractproperty
     def ram(self):
     def ram(self):
         """
         """
-        The amount of RAM (in mb) supported by this instance type.
+        The amount of RAM (in MB) supported by this instance type.
 
 
         :rtype: int
         :rtype: int
         :return: Total RAM (in MB).
         :return: Total RAM (in MB).

+ 9 - 8
cloudbridge/cloud/interfaces/services.py

@@ -39,7 +39,7 @@ class ComputeService(CloudService):
     def images(self):
     def images(self):
         """
         """
         Provides access to all Image related services in this provider.
         Provides access to all Image related services in this provider.
-        (e.g. Glance in Openstack)
+        (e.g. Glance in OpenStack)
 
 
         Example:
         Example:
 
 
@@ -639,7 +639,7 @@ class ObjectStoreService(PageableObjectMixin, CloudService):
     def get(self, bucket_id):
     def get(self, bucket_id):
         """
         """
         Returns a bucket given its ID. Returns ``None`` if the bucket
         Returns a bucket given its ID. Returns ``None`` if the bucket
-        does not exist. On some providers, such as AWS and Openstack,
+        does not exist. On some providers, such as AWS and OpenStack,
         the bucket id is the same as its name.
         the bucket id is the same as its name.
 
 
         Example:
         Example:
@@ -780,7 +780,7 @@ class KeyPairService(PageableObjectMixin, CloudService):
         """
         """
         Return a KeyPair given its ID or ``None`` if not found.
         Return a KeyPair given its ID or ``None`` if not found.
 
 
-        On some providers, such as AWS and Openstack, the KeyPair ID is
+        On some providers, such as AWS and OpenStack, the KeyPair ID is
         the same as its name.
         the same as its name.
 
 
         Example:
         Example:
@@ -818,13 +818,13 @@ class KeyPairService(PageableObjectMixin, CloudService):
     @abstractmethod
     @abstractmethod
     def create(self, name):
     def create(self, name):
         """
         """
-        Create a new keypair or return an existing one by the same name.
+        Create a new key pair or raise an exception if one already exists.
 
 
         :type name: str
         :type name: str
         :param name: The name of the key pair to be created.
         :param name: The name of the key pair to be created.
 
 
         :rtype: ``object`` of :class:`.KeyPair`
         :rtype: ``object`` of :class:`.KeyPair`
-        :return:  A keypair instance
+        :return:  A keypair instance or ``None``.
         """
         """
         pass
         pass
 
 
@@ -978,11 +978,12 @@ class RegionService(PageableObjectMixin, CloudService):
     @abstractproperty
     @abstractproperty
     def current(self):
     def current(self):
         """
         """
-        Returns the current region that this provider is connected
-        to
+        Returns the current region that this provider is connected to.
+
+        If the current region cannot be discovered, return ``None``.
 
 
         :rtype: ``object`` of :class:`.Region`
         :rtype: ``object`` of :class:`.Region`
-        :return:  a Region instance
+        :return:  a Region instance or ``None``
         """
         """
         pass
         pass
 
 

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

@@ -177,8 +177,7 @@ class MockAWSCloudProvider(AWSCloudProvider, TestMockHelperMixin):
         self.s3mock.start()
         self.s3mock.start()
         HTTPretty.register_uri(
         HTTPretty.register_uri(
             method="GET",
             method="GET",
-            uri="https://swift.rc.nectar.org.au:8888/v1/"
-            "AUTH_377/cloud-bridge/aws/instance_data.json",
+            uri="https://cloudbridgelib.s3.amazonaws.com/aws_instance_data.json",
             body="""
             body="""
 [
 [
   {
   {

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

@@ -631,15 +631,22 @@ class AWSSecurityGroup(BaseSecurityGroup):
         :rtype: :class:``.SecurityGroupRule``
         :rtype: :class:``.SecurityGroupRule``
         :return: Rule object if successful or ``None``.
         :return: Rule object if successful or ``None``.
         """
         """
-        if self._security_group.authorize(
-                ip_protocol=ip_protocol,
-                from_port=from_port,
-                to_port=to_port,
-                cidr_ip=cidr_ip,
-                # pylint:disable=protected-access
-                src_group=src_group._security_group if src_group else None):
-            return self.get_rule(ip_protocol, from_port, to_port, cidr_ip,
-                                 src_group)
+        try:
+            if self._security_group.authorize(
+                    ip_protocol=ip_protocol,
+                    from_port=from_port,
+                    to_port=to_port,
+                    cidr_ip=cidr_ip,
+                    # pylint:disable=protected-access
+                    src_group=src_group._security_group if src_group else None):
+                return self.get_rule(ip_protocol, from_port, to_port, cidr_ip,
+                                     src_group)
+        except EC2ResponseError as ec2e:
+            if ec2e.code == "InvalidPermission.Duplicate":
+                return self.get_rule(ip_protocol, from_port, to_port, cidr_ip,
+                                     src_group)
+            else:
+                raise EC2ResponseError
         return None
         return None
 
 
     def get_rule(self, ip_protocol=None, from_port=None, to_port=None,
     def get_rule(self, ip_protocol=None, from_port=None, to_port=None,

+ 6 - 7
cloudbridge/cloud/providers/aws/services.py

@@ -122,7 +122,7 @@ class AWSKeyPairService(BaseKeyPairService):
 
 
     def create(self, name):
     def create(self, name):
         """
         """
-        Create a new key pair or return an existing one by the same name.
+        Create a new key pair or raise an exception if one already exists.
 
 
         :type name: str
         :type name: str
         :param name: The name of the key pair to be created.
         :param name: The name of the key pair to be created.
@@ -130,11 +130,10 @@ class AWSKeyPairService(BaseKeyPairService):
         :rtype: ``object`` of :class:`.KeyPair`
         :rtype: ``object`` of :class:`.KeyPair`
         :return:  A key pair instance or ``None`` if one was not be created.
         :return:  A key pair instance or ``None`` if one was not be created.
         """
         """
-        kp = self.get(name)
-        if kp:
-            return kp
         kp = self.provider.ec2_conn.create_key_pair(name)
         kp = self.provider.ec2_conn.create_key_pair(name)
-        return AWSKeyPair(self.provider, kp)
+        if kp:
+            return AWSKeyPair(self.provider, kp)
+        return None
 
 
 
 
 class AWSSecurityGroupService(BaseSecurityGroupService):
 class AWSSecurityGroupService(BaseSecurityGroupService):
@@ -598,8 +597,8 @@ class AWSInstanceService(BaseInstanceService):
                                      reservations.next_token,
                                      reservations.next_token,
                                      False, data=instances)
                                      False, data=instances)
 
 
-AWS_INSTANCE_DATA_DEFAULT_URL = "https://swift.rc.nectar.org.au:8888/v1/" \
-                                "AUTH_377/cloud-bridge/aws/instance_data.json"
+AWS_INSTANCE_DATA_DEFAULT_URL = "https://cloudbridgelib.s3.amazonaws.com/" \
+                                "aws_instance_data.json"
 
 
 
 
 class AWSInstanceTypesService(BaseInstanceTypesService):
 class AWSInstanceTypesService(BaseInstanceTypesService):

+ 2 - 2
cloudbridge/cloud/providers/openstack/helpers.py

@@ -7,7 +7,7 @@ from cloudbridge.cloud.base.resources import ServerPagedResultList
 
 
 def os_result_limit(provider, requested_limit):
 def os_result_limit(provider, requested_limit):
     """
     """
-    Calculates the limit for openstack.
+    Calculates the limit for OpenStack.
     """
     """
     limit = requested_limit or provider.config.default_result_limit
     limit = requested_limit or provider.config.default_result_limit
     # fetch one more than the limit to help with paging.
     # fetch one more than the limit to help with paging.
@@ -19,7 +19,7 @@ def os_result_limit(provider, requested_limit):
 
 
 def to_server_paged_list(provider, objects, limit):
 def to_server_paged_list(provider, objects, limit):
     """
     """
-    A convenience function for wrapping a list of openstack native objects in
+    A convenience function for wrapping a list of OpenStack native objects in
     a ServerPagedResultList. OpenStack
     a ServerPagedResultList. OpenStack
     initial list of objects. Thereafter, the return list is wrapped in a
     initial list of objects. Thereafter, the return list is wrapped in a
     BaseResultList, enabling extra properties like
     BaseResultList, enabling extra properties like

+ 124 - 29
cloudbridge/cloud/providers/openstack/provider.py

@@ -8,7 +8,6 @@ import os
 from cinderclient import client as cinder_client
 from cinderclient import client as cinder_client
 from keystoneclient import client as keystone_client
 from keystoneclient import client as keystone_client
 from keystoneclient import session
 from keystoneclient import session
-from keystoneclient.auth.identity import Password
 from neutronclient.v2_0 import client as neutron_client
 from neutronclient.v2_0 import client as neutron_client
 from novaclient import client as nova_client
 from novaclient import client as nova_client
 from novaclient import shell as nova_shell
 from novaclient import shell as nova_shell
@@ -41,6 +40,16 @@ class OpenStackCloudProvider(BaseCloudProvider):
             'os_auth_url', os.environ.get('OS_AUTH_URL', None))
             'os_auth_url', os.environ.get('OS_AUTH_URL', None))
         self.region_name = self._get_config_value(
         self.region_name = self._get_config_value(
             'os_region_name', os.environ.get('OS_REGION_NAME', None))
             'os_region_name', os.environ.get('OS_REGION_NAME', None))
+        self.project_name = self._get_config_value(
+            'os_project_name', os.environ.get('OS_PROJECT_NAME', None))
+        self.project_domain_name = self._get_config_value(
+            'os_project_domain_name',
+            os.environ.get('OS_PROJECT_DOMAIN_NAME', None))
+        self.user_domain_name = self._get_config_value(
+            'os_user_domain_name', os.environ.get('OS_USER_DOMAIN_NAME', None))
+        self.identity_api_version = self._get_config_value(
+            'os_identity_api_version',
+            os.environ.get('OS_IDENTITY_API_VERSION', None))
         # Allow individual service connections to have their own values but
         # Allow individual service connections to have their own values but
         # default to a the ones defined above.
         # default to a the ones defined above.
         self.swift_username = self._get_config_value(
         self.swift_username = self._get_config_value(
@@ -86,6 +95,46 @@ class OpenStackCloudProvider(BaseCloudProvider):
             self._keystone = self._connect_keystone()
             self._keystone = self._connect_keystone()
         return self._keystone
         return self._keystone
 
 
+    @property
+    def _keystone_version(self):
+        """
+        Return the numeric version of remote Keystone server.
+
+        :rtype: ``int``
+        :return: Keystone version as an int (currently, 2 or 3).
+        """
+        ks_version = keystone_client.Client(auth_url=self.auth_url).version
+        if ks_version == 'v3':
+            return 3
+        return 2
+
+    @property
+    def _keystone_session(self):
+        """
+        Connect to Keystone and return a session object.
+
+        :rtype: :class:`keystoneclient.session.Session`
+        :return: A Keystone session object.
+        """
+        def connect_v2():
+            from keystoneclient.auth.identity import Password as password_v2
+            auth = password_v2(self.auth_url, username=self.username,
+                               password=self.password,
+                               tenant_name=self.tenant_name)
+            return session.Session(auth=auth)
+
+        def connect_v3():
+            from keystoneclient.auth.identity.v3 import Password as password_v3
+            auth = password_v3(auth_url=self.auth_url,
+                               username=self.username,
+                               password=self.password,
+                               user_domain_name=self.user_domain_name,
+                               project_domain_name=self.project_domain_name,
+                               project_name=self.project_name)
+            return session.Session(auth=auth)
+
+        return connect_v3() if self._keystone_version == 3 else connect_v2()
+
 #     @property
 #     @property
 #     def glance(self):
 #     def glance(self):
 #         if not self._glance:
 #         if not self._glance:
@@ -137,6 +186,27 @@ class OpenStackCloudProvider(BaseCloudProvider):
         """
         """
         Get an OpenStack Nova (compute) client object for the given cloud.
         Get an OpenStack Nova (compute) client object for the given cloud.
         """
         """
+        def connect_pwd():
+            """
+            Connect using username and password parameters.
+            """
+            nova = nova_client.Client(
+                api_version, username=self.username, api_key=self.password,
+                project_id=self.tenant_name, auth_url=self.auth_url,
+                region_name=region_name, service_name=service_name,
+                http_log_debug=True if self.config.debug_mode else False)
+            nova.authenticate()
+            return nova
+
+        def connect_sess():
+            """
+            Connect using a Keystone session object.
+            """
+            return nova_client.Client(
+                api_version, session=self._keystone_session,
+                service_name=service_name,
+                http_log_debug=True if self.config.debug_mode else False)
+
         api_version = self._get_config_value(
         api_version = self._get_config_value(
             'os_compute_api_version',
             'os_compute_api_version',
             os.environ.get('OS_COMPUTE_API_VERSION', 2))
             os.environ.get('OS_COMPUTE_API_VERSION', 2))
@@ -147,46 +217,59 @@ class OpenStackCloudProvider(BaseCloudProvider):
         if self.config.debug_mode:
         if self.config.debug_mode:
             nova_shell.OpenStackComputeShell().setup_debugging(True)
             nova_shell.OpenStackComputeShell().setup_debugging(True)
 
 
-        nova = nova_client.Client(
-            api_version, username=self.username, api_key=self.password,
-            project_id=self.tenant_name, auth_url=self.auth_url,
-            region_name=region_name, service_name=service_name,
-            http_log_debug=True if self.config.debug_mode else False)
-        nova.authenticate()
-        return nova
+        return connect_sess() if self._keystone_version == 3 else connect_pwd()
 
 
     def _connect_keystone(self):
     def _connect_keystone(self):
         """
         """
         Get an OpenStack Keystone (identity) client object for the given cloud.
         Get an OpenStack Keystone (identity) client object for the given cloud.
         """
         """
-        auth = Password(self.auth_url, username=self.username,
-                        password=self.password, tenant_name=self.tenant_name)
-        # Wow, the internal keystoneV2 implementation is terribly buggy. It
-        # needs both a separate Session object and the username, password again
-        # for things to work correctly. Plus, a manual call to authenticate()
-        # is also required if the service catalogue needs to be queried.
-        keystone = keystone_client.Client(
-            session=session.Session(auth=auth),
-            auth_url=self.auth_url,
-            username=self.username,
-            password=self.password,
-            tenant_name=self.tenant_name,
-            region_name=self.region_name)
-        keystone.authenticate()
-        return keystone
+        def connect_v2():
+            # Wow, the internal keystoneV2 implementation is terribly buggy. It
+            # needs both a separate Session object and the username, password
+            # again for things to work correctly. Plus, a manual call to
+            # authenticate() is also required if the service catalog needs
+            # to be queried.
+            keystone = keystone_client.Client(
+                session=self._keystone_session,
+                auth_url=self.auth_url,
+                username=self.username,
+                password=self.password,
+                tenant_name=self.tenant_name,
+                region_name=self.region_name)
+            keystone.authenticate()
+            return keystone
+
+        def connect_v3():
+            return keystone_client.Client(session=self._keystone_session,
+                                          auth_url=self.auth_url)
+
+        return connect_v3() if self._keystone_version == 3 else connect_v2()
 
 
     def _connect_cinder(self):
     def _connect_cinder(self):
         """
         """
         Get an OpenStack Cinder (block storage) client object for the given
         Get an OpenStack Cinder (block storage) client object for the given
         cloud.
         cloud.
         """
         """
+        def connect_pwd():
+            """
+            Connect using username and password parameters.
+            """
+            return cinder_client.Client(
+                api_version, username=self.username, api_key=self.password,
+                project_id=self.tenant_name, auth_url=self.auth_url)
+
+        def connect_sess():
+            """
+            Connect using a Keystone session object.
+            """
+            return cinder_client.Client(
+                api_version, session=self._keystone_session)
+
         api_version = self._get_config_value(
         api_version = self._get_config_value(
             'os_volume_api_version',
             'os_volume_api_version',
             os.environ.get('OS_VOLUME_API_VERSION', 2))
             os.environ.get('OS_VOLUME_API_VERSION', 2))
 
 
-        return cinder_client.Client(
-            api_version, username=self.username, api_key=self.password,
-            project_id=self.tenant_name, auth_url=self.auth_url)
+        return connect_sess() if self._keystone_version == 3 else connect_pwd()
 
 
 #     def _connect_glance(self):
 #     def _connect_glance(self):
 #         """
 #         """
@@ -217,6 +300,18 @@ class OpenStackCloudProvider(BaseCloudProvider):
         Get an OpenStack Neutron (networking) client object for the given
         Get an OpenStack Neutron (networking) client object for the given
         cloud.
         cloud.
         """
         """
-        return neutron_client.Client(
-            username=self.username, password=self.password,
-            tenant_name=self.tenant_name, auth_url=self.auth_url)
+        def connect_pwd():
+            """
+            Connect using username and password parameters.
+            """
+            return neutron_client.Client(
+                username=self.username, password=self.password,
+                tenant_name=self.tenant_name, auth_url=self.auth_url)
+
+        def connect_sess():
+            """
+            Connect using a Keystone session object.
+            """
+            return neutron_client.Client(session=self._keystone_session)
+
+        return connect_sess() if self._keystone_version == 3 else connect_pwd()

+ 10 - 7
cloudbridge/cloud/providers/openstack/resources.py

@@ -27,6 +27,7 @@ import json
 
 
 import ipaddress
 import ipaddress
 
 
+from keystoneclient.v3.regions import Region
 import novaclient.exceptions as novaex
 import novaclient.exceptions as novaex
 import swiftclient.exceptions as swiftex
 import swiftclient.exceptions as swiftex
 
 
@@ -156,7 +157,7 @@ class OpenStackInstanceType(BaseInstanceType):
 
 
     @property
     @property
     def family(self):
     def family(self):
-        # TODO: This may not be standardised accross openstack
+        # TODO: This may not be standardised across OpenStack
         # but NeCTAR is using it this way
         # but NeCTAR is using it this way
         return self.extra_data.get('flavor_class:name')
         return self.extra_data.get('flavor_class:name')
 
 
@@ -248,8 +249,8 @@ class OpenStackInstance(BaseInstance):
         """
         """
         Get all the public IP addresses for this instance.
         Get all the public IP addresses for this instance.
         """
         """
-        # Openstack doesn't provide an easy way to figure our whether an ip is
-        # public or private, since the returned ips are grouped by an arbitrary
+        # OpenStack doesn't provide an easy way to figure our whether an IP is
+        # public or private, since the returned IPs are grouped by an arbitrary
         # network label. Therefore, it's necessary to parse the address and
         # network label. Therefore, it's necessary to parse the address and
         # determine whether it's public or private
         # determine whether it's public or private
         return [address
         return [address
@@ -384,11 +385,13 @@ class OpenStackRegion(BaseRegion):
 
 
     @property
     @property
     def id(self):
     def id(self):
-        return self._os_region
+        return (self._os_region.id if type(self._os_region) == Region else
+                self._os_region)
 
 
     @property
     @property
     def name(self):
     def name(self):
-        return self._os_region
+        return (self._os_region.id if type(self._os_region) == Region else
+                self._os_region)
 
 
     @property
     @property
     def zones(self):
     def zones(self):
@@ -749,7 +752,7 @@ class OpenStackSecurityGroup(BaseSecurityGroup):
 
 
     @property
     @property
     def rules(self):
     def rules(self):
-        # Update SG object; otherwise, recenlty added rules do now show
+        # Update SG object; otherwise, recently added rules do now show
         self._security_group = self._provider.nova.security_groups.get(
         self._security_group = self._provider.nova.security_groups.get(
             self._security_group)
             self._security_group)
         return [OpenStackSecurityGroupRule(self._provider, r, self)
         return [OpenStackSecurityGroupRule(self._provider, r, self)
@@ -810,7 +813,7 @@ class OpenStackSecurityGroup(BaseSecurityGroup):
 
 
     def get_rule(self, ip_protocol=None, from_port=None, to_port=None,
     def get_rule(self, ip_protocol=None, from_port=None, to_port=None,
                  cidr_ip=None, src_group=None):
                  cidr_ip=None, src_group=None):
-        # Update SG object; otherwise, recenlty added rules do now show
+        # Update SG object; otherwise, recently added rules do now show
         self._security_group = self._provider.nova.security_groups.get(
         self._security_group = self._provider.nova.security_groups.get(
             self._security_group)
             self._security_group)
         for rule in self._security_group.rules:
         for rule in self._security_group.rules:

+ 37 - 26
cloudbridge/cloud/providers/openstack/services.py

@@ -116,7 +116,7 @@ class OpenStackKeyPairService(BaseKeyPairService):
 
 
     def create(self, name):
     def create(self, name):
         """
         """
-        Create a new key pair or return an existing one by the same name.
+        Create a new key pair or raise an exception if one already exists.
 
 
         :type name: str
         :type name: str
         :param name: The name of the key pair to be created.
         :param name: The name of the key pair to be created.
@@ -124,11 +124,10 @@ class OpenStackKeyPairService(BaseKeyPairService):
         :rtype: ``object`` of :class:`.KeyPair`
         :rtype: ``object`` of :class:`.KeyPair`
         :return:  A key pair instance or ``None`` if one was not be created.
         :return:  A key pair instance or ``None`` if one was not be created.
         """
         """
-        kp = self.get(name)
-        if kp:
-            return kp
         kp = self.provider.nova.keypairs.create(name)
         kp = self.provider.nova.keypairs.create(name)
-        return OpenStackKeyPair(self.provider, kp)
+        if kp:
+            return OpenStackKeyPair(self.provider, kp)
+        return None
 
 
 
 
 class OpenStackSecurityGroupService(BaseSecurityGroupService):
 class OpenStackSecurityGroupService(BaseSecurityGroupService):
@@ -454,29 +453,41 @@ class OpenStackRegionService(BaseRegionService):
         return next(region, None)
         return next(region, None)
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
-        # TODO: KeyStone V3 onwards will support directly listing regions
-        # but for now, this convoluted method is necessary
-        regions = (
-            endpoint.get('region') or endpoint.get('region_id')
-            for svc in self.provider.keystone.service_catalog.get_data()
-            for endpoint in svc.get('endpoints', [])
-        )
-        regions = set(region for region in regions if region)
-        os_regions = [OpenStackRegion(self.provider, region)
-                      for region in regions]
-
-        return ClientPagedResultList(self.provider, os_regions,
-                                     limit=limit, marker=marker)
+        def keystone_v2():
+            # Keystone v3 onwards supports directly listing regions
+            # but for v2, this convoluted method is necessary.
+            regions = (
+                endpoint.get('region') or endpoint.get('region_id')
+                for svc in self.provider.keystone.service_catalog.get_data()
+                for endpoint in svc.get('endpoints', [])
+            )
+            regions = set(region for region in regions if region)
+            os_regions = [OpenStackRegion(self.provider, region)
+                          for region in regions]
+
+            return ClientPagedResultList(self.provider, os_regions,
+                                         limit=limit, marker=marker)
+
+        def keystone_v3():
+            os_regions = [OpenStackRegion(self.provider, region)
+                          for region in self.provider.keystone.regions.list()]
+            return ClientPagedResultList(self.provider, os_regions,
+                                         limit=limit, marker=marker)
+
+        return keystone_v3() if self.provider._keystone_version == 3 else \
+            keystone_v2()  # pylint:disable=protected-access
 
 
     @property
     @property
     def current(self):
     def current(self):
-        nova_region = [
-            endpoint.get('region') or endpoint.get('region_id')
-            for svc in self.provider.keystone.service_catalog.get_data()
-            for endpoint in svc.get('endpoints', [])
-            if endpoint.get('publicURL', None) ==
-            self.provider.nova.client.management_url]
-        return self.get(nova_region[0])
+        if self.provider.keystone.has_service_catalog():
+            nova_region = [
+                endpoint.get('region') or endpoint.get('region_id')
+                for svc in self.provider.keystone.service_catalog.get_data()
+                for endpoint in svc.get('endpoints', [])
+                if endpoint.get('publicURL', None) ==
+                self.provider.nova.client.management_url]
+            return self.get(nova_region[0])
+        return None
 
 
 
 
 class OpenStackComputeService(BaseComputeService):
 class OpenStackComputeService(BaseComputeService):
@@ -709,7 +720,7 @@ class OpenStackSubnetService(BaseSubnetService):
         subnet_id = (subnet.id if isinstance(subnet, OpenStackSubnet)
         subnet_id = (subnet.id if isinstance(subnet, OpenStackSubnet)
                      else subnet)
                      else subnet)
         self.provider.neutron.delete_subnet(subnet_id)
         self.provider.neutron.delete_subnet(subnet_id)
-        # Adhear to the interface docs
+        # Adhere to the interface docs
         if subnet_id not in self.list():
         if subnet_id not in self.list():
             return True
             return True
         return False
         return False

+ 3 - 1
docs/getting_started.rst

@@ -93,7 +93,9 @@ get a base Ubuntu image ``ami-d85e75b0`` and launch an instance.
 .. code-block:: python
 .. code-block:: python
 
 
     img = provider.compute.images.get(image_id)
     img = provider.compute.images.get(image_id)
-    inst_type = provider.compute.instance_types.find(name='m1.small')[0]
+    inst_type = sorted([t for t in provider.compute.instance_types.list()
+                        if t.vcpus >= 2 and t.ram >= 4],
+                       key=lambda x: x.vcpus*x.ram)[0]
     inst = provider.compute.instances.create(
     inst = provider.compute.instances.create(
         name='CloudBridge-intro', image=img, instance_type=inst_type,
         name='CloudBridge-intro', image=img, instance_type=inst_type,
         key_pair=kp, security_groups=[sg])
         key_pair=kp, security_groups=[sg])

+ 3 - 3
docs/topics/contributor_guide.rst

@@ -1,8 +1,8 @@
-Contributer Guide
+Contributor Guide
 =================
 =================
 This section has information on how to contribute to CloudBridge development,
 This section has information on how to contribute to CloudBridge development,
 and a walkthrough of the process of getting started on developing a new
 and a walkthrough of the process of getting started on developing a new
-CloudBridge Provider. 
+CloudBridge Provider.
 
 
 .. toctree::
 .. toctree::
    :maxdepth: 1
    :maxdepth: 1
@@ -10,4 +10,4 @@ CloudBridge Provider.
     Design Goals <design_goals.rst>
     Design Goals <design_goals.rst>
     Testing <testing.rst>
     Testing <testing.rst>
     Provider development walkthrough <provider_development.rst>
     Provider development walkthrough <provider_development.rst>
-    
+

+ 15 - 4
docs/topics/install.rst

@@ -3,14 +3,24 @@ Installation
 
 
 **Prerequisites**: CloudBridge runs on Python 2.7 and higher. Python 3 is recommended.
 **Prerequisites**: CloudBridge runs on Python 2.7 and higher. Python 3 is recommended.
 
 
+We highly recommend installing CloudBridge in a
+`virtualenv <http://virtualenv.readthedocs.org/>`_. Creating a new virtualenv
+is simple:
+
+.. code-block:: shell
+
+    pip install virtualenv
+    virtualenv .venv
+    source .venv/bin/activate
+
 Latest release
 Latest release
 --------------
 --------------
 The latest release of cloudbridge can be installed from PyPI::
 The latest release of cloudbridge can be installed from PyPI::
 
 
     pip install cloudbridge
     pip install cloudbridge
 
 
-Manual installation
--------------------
+Latest unreleased dev version
+-----------------------------
 The development version of the library can be installed from the
 The development version of the library can be installed from the
 `Github repo <https://github.com/gvlproject/cloudbridge>`_::
 `Github repo <https://github.com/gvlproject/cloudbridge>`_::
 
 
@@ -21,9 +31,10 @@ The development version of the library can be installed from the
 Developer installation
 Developer installation
 ----------------------
 ----------------------
 To install additional libraries required by CloudBridge contributors, such as
 To install additional libraries required by CloudBridge contributors, such as
-`tox <https://tox.readthedocs.org/en/latest/>`_, run the following command::
+`tox <https://tox.readthedocs.org/en/latest/>`_, clone the source code
+repository and run the following command from the repository root directory::
 
 
-    pip install cloudbridge[dev]
+    pip install -e .[dev]
 
 
 ----------
 ----------
 
 

+ 2 - 1
docs/topics/provider_development.rst

@@ -7,7 +7,8 @@ for CloudBridge.
 1. We start off by creating a new folder for the provider within the
 1. We start off by creating a new folder for the provider within the
 ``cloudbridge/cloud/providers`` folder. In this case: ``gce``. Further, install
 ``cloudbridge/cloud/providers`` folder. In this case: ``gce``. Further, install
 the native cloud provider Python library, here
 the native cloud provider Python library, here
-``pip install google-api-python-client``.
+``pip install google-api-python-client==1.4.2`` and a couple of its requirements
+``oauth2client==1.5.2`` and ``pycrypto==2.6.1``.
 
 
 2. Add a ``provider.py`` file. This file will contain the main implementation
 2. Add a ``provider.py`` file. This file will contain the main implementation
 of the cloud provider and will be the entry point that CloudBridge uses for all
 of the cloud provider and will be the entry point that CloudBridge uses for all