Explorar el Código

Add NetworkService and Network classes

Enis Afgan hace 10 años
padre
commit
602b1ab547

+ 17 - 0
cloudbridge/cloud/base/resources.py

@@ -18,6 +18,7 @@ from cloudbridge.cloud.interfaces.resources import KeyPair
 from cloudbridge.cloud.interfaces.resources import LaunchConfig
 from cloudbridge.cloud.interfaces.resources import MachineImage
 from cloudbridge.cloud.interfaces.resources import MachineImageState
+from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.resources import ObjectLifeCycleMixin
 from cloudbridge.cloud.interfaces.resources import PageableObjectMixin
 from cloudbridge.cloud.interfaces.resources import PlacementZone
@@ -586,3 +587,19 @@ class BaseBucket(BasePageableObjectMixin, Bucket, BaseCloudResource):
     def __repr__(self):
         return "<CB-{0}: {1}>".format(self.__class__.__name__,
                                       self.name)
+
+
+class BaseNetwork(Network, BaseCloudResource):
+
+    def __init__(self, provider):
+        super(BaseNetwork, self).__init__(provider)
+
+    def __repr__(self):
+        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
+                                           self.id, self.name)
+
+    def __eq__(self, other):
+        return (isinstance(other, Network) and
+                # pylint:disable=protected-access
+                self._provider == other._provider and
+                self.id == other.id)

+ 14 - 0
cloudbridge/cloud/base/services.py

@@ -8,6 +8,7 @@ from cloudbridge.cloud.interfaces.services import ImageService
 from cloudbridge.cloud.interfaces.services import InstanceService
 from cloudbridge.cloud.interfaces.services import InstanceTypesService
 from cloudbridge.cloud.interfaces.services import KeyPairService
+from cloudbridge.cloud.interfaces.services import NetworkService
 from cloudbridge.cloud.interfaces.services import ObjectStoreService
 from cloudbridge.cloud.interfaces.services import RegionService
 from cloudbridge.cloud.interfaces.services import SecurityGroupService
@@ -131,3 +132,16 @@ class BaseRegionService(
 
     def __init__(self, provider):
         super(BaseRegionService, self).__init__(provider)
+
+
+class BaseNetworkService(
+        BasePageableObjectMixin, NetworkService, BaseCloudService):
+
+    def __init__(self, provider):
+        super(BaseNetworkService, self).__init__(provider)
+
+    def delete(self, network_id):
+        network = self.get(network_id)
+        if network:
+            network.delete()
+        return True

+ 1 - 0
cloudbridge/cloud/interfaces/__init__.py

@@ -8,6 +8,7 @@ from .resources import InstanceState  # noqa
 from .resources import InvalidConfigurationException  # noqa
 from .resources import LaunchConfig  # noqa
 from .resources import MachineImageState  # noqa
+from .resources import NetworkState  # noqa
 from .resources import Region  # noqa
 from .resources import SnapshotState  # noqa
 from .resources import VolumeState  # noqa

+ 16 - 0
cloudbridge/cloud/interfaces/provider.py

@@ -110,6 +110,22 @@ class CloudProvider(object):
         """
         pass
 
+    @abstractproperty
+    def network(self):
+        """
+        Provide access to all network related services in this provider.
+
+        Example:
+
+        .. code-block:: python
+
+            networks = provider.network.list()
+            network = provider.network.create(name="DevNet")
+
+        :rtype: :class:`.NetworkService`
+        :return:  a NetworkService object
+        """
+
     @abstractproperty
     def security(self):
         """

+ 77 - 0
cloudbridge/cloud/interfaces/resources.py

@@ -781,6 +781,83 @@ class MachineImage(ObjectLifeCycleMixin, CloudResource):
         pass
 
 
+class NetworkState(object):
+
+    """
+    Standard states for a network.
+
+    :cvar UNKNOWN: Network state unknown.
+    :cvar PENDING: Network is being created.
+    :cvar AVAILABLE: Network is being available.
+    :cvar DOWN = Network is not operational.
+    :cvar ERROR = Network errored.
+    """
+    UNKNOWN = "unknown"
+    PENDING = "pending"
+    AVAILABLE = "available"
+    DOWN = "down"
+    ERROR = "error"
+
+
+class Network(CloudResource):
+    """
+    Represents a software-defined network, like the Virtual Private Cloud.
+    """
+    __metaclass__ = ABCMeta
+
+    @abstractproperty
+    def id(self):
+        """
+        Get the network identifier.
+
+        :rtype: ``str``
+        :return: ID for this network. Will generally correspond to the cloud
+                 middleware's ID, but should be treated as an opaque value.
+        """
+        pass
+
+    @abstractproperty
+    def name(self):
+        """
+        Get the network name.
+
+        :rtype: ``str``
+        :return: Name for this network as returned by the cloud middleware.
+        """
+        pass
+
+    @abstractproperty
+    def state(self):
+        """
+        The state of the network.
+
+        :rtype: ``str``
+        :return: One of ``unknown``, ``pending``, ``available``, ``down`` or
+                 ``error``.
+        """
+        pass
+
+    @abstractmethod
+    def delete(self):
+        """
+        Delete this network.
+
+        :rtype: ``bool``
+        :return: ``True`` is successful.
+        """
+        pass
+
+    @abstractmethod
+    def subnets(self):
+        """
+        The associated subnets.
+
+        :rtype: ``list`` of :class:`.Subnet`
+        :return: List of subnets associated with this network.
+        """
+        pass
+
+
 class VolumeState(object):
 
     """

+ 60 - 0
cloudbridge/cloud/interfaces/services.py

@@ -473,6 +473,66 @@ class ImageService(PageableObjectMixin, CloudService):
         pass
 
 
+class NetworkService(PageableObjectMixin, CloudService):
+
+    """
+    Base interface for a Network Service.
+    """
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def get(self, network_id):
+        """
+        Returns a Network given its ID or ``None`` if not found.
+
+        :type network_id: ``str``
+        :param network_id: The ID of the network to retrieve.
+
+        :rtype: ``object`` of :class:`.Network`
+        return: a Network object
+        """
+        pass
+
+    @abstractmethod
+    def list(self, limit=None, marker=None):
+        """
+        List all networks.
+
+        :rtype: ``list`` of :class:`.Network`
+        :return: list of Network objects
+        """
+        pass
+
+    @abstractmethod
+    def create(self, name=None):
+        """
+        Create a new network.
+
+        :type name: ``str``
+        :param name: An optional network name. The name will be set if the
+                     provider supports it.
+
+        :rtype: ``object`` of :class:`.Network`
+        :return:  A Network object
+        """
+        pass
+
+    @abstractmethod
+    def delete(self, network_id):
+        """
+        Delete an existing Network.
+
+        :type network_id: ``str``
+        :param network_id: The ID of the network to be deleted.
+
+        :rtype: ``bool``
+        :return:  ``True`` if the network does not exist, ``False`` otherwise.
+                  Note that this implies that the network may not have been
+                  deleted by this method but instead has not existed at all.
+        """
+        pass
+
+
 class ObjectStoreService(PageableObjectMixin, CloudService):
 
     """

+ 32 - 0
cloudbridge/cloud/providers/aws/provider.py

@@ -15,6 +15,7 @@ from cloudbridge.cloud.interfaces import TestMockHelperMixin
 
 from .services import AWSBlockStoreService
 from .services import AWSComputeService
+from .services import AWSNetworkService
 from .services import AWSObjectStoreService
 from .services import AWSSecurityService
 
@@ -42,10 +43,12 @@ class AWSCloudProvider(BaseCloudProvider):
 
         # service connections, lazily initialized
         self._ec2_conn = None
+        self._vpc_conn = None
         self._s3_conn = None
 
         # Initialize provider services
         self._compute = AWSComputeService(self)
+        self._network = AWSNetworkService(self)
         self._security = AWSSecurityService(self)
         self._block_store = AWSBlockStoreService(self)
         self._object_store = AWSObjectStoreService(self)
@@ -56,6 +59,12 @@ class AWSCloudProvider(BaseCloudProvider):
             self._ec2_conn = self._connect_ec2()
         return self._ec2_conn
 
+    @property
+    def vpc_conn(self):
+        if not self._vpc_conn:
+            self._vpc_conn = self._connect_vpc()
+        return self._vpc_conn
+
     @property
     def s3_conn(self):
         if not self._s3_conn:
@@ -66,6 +75,10 @@ class AWSCloudProvider(BaseCloudProvider):
     def compute(self):
         return self._compute
 
+    @property
+    def network(self):
+        return self._network
+
     @property
     def security(self):
         return self._security
@@ -97,6 +110,25 @@ class AWSCloudProvider(BaseCloudProvider):
             debug=2 if self.config.debug_mode else 0)
         return ec2_conn
 
+    def _connect_vpc(self):
+        """
+        Get a boto VPC connection object.
+        """
+        r = RegionInfo(name=self.region_name, endpoint=self.region_endpoint)
+        vpc_conn = boto.connect_vpc(
+            aws_access_key_id=self.a_key,
+            aws_secret_access_key=self.s_key,
+            # api_version is needed for availability
+            # zone support for EC2
+            api_version='2012-06-01' if self.cloud_type == 'aws' else None,
+            is_secure=self.is_secure,
+            region=r,
+            port=self.ec2_port,
+            path=self.ec2_conn_path,
+            validate_certs=False,
+            debug=2 if self.config.debug_mode else 0)
+        return vpc_conn
+
     def _connect_s3(self):
         """
         Get a boto S3 connection object.

+ 49 - 0
cloudbridge/cloud/providers/aws/resources.py

@@ -13,6 +13,7 @@ from cloudbridge.cloud.base.resources import BaseInstance
 from cloudbridge.cloud.base.resources import BaseInstanceType
 from cloudbridge.cloud.base.resources import BaseKeyPair
 from cloudbridge.cloud.base.resources import BaseMachineImage
+from cloudbridge.cloud.base.resources import BaseNetwork
 from cloudbridge.cloud.base.resources import BasePlacementZone
 from cloudbridge.cloud.base.resources import BaseRegion
 from cloudbridge.cloud.base.resources import BaseSecurityGroup
@@ -22,6 +23,7 @@ from cloudbridge.cloud.base.resources import BaseVolume
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.interfaces.resources import InstanceState
 from cloudbridge.cloud.interfaces.resources import MachineImageState
+from cloudbridge.cloud.interfaces.resources import NetworkState
 from cloudbridge.cloud.interfaces.resources import SnapshotState
 from cloudbridge.cloud.interfaces.resources import VolumeState
 
@@ -703,3 +705,50 @@ class AWSRegion(BaseRegion):
         Accesss information about placement zones within this region.
         """
         pass
+
+
+class AWSNetwork(BaseNetwork):
+
+    # Ref:
+    # docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVpcs.html
+    _NETWORK_STATE_MAP = {
+        'pending': NetworkState.PENDING,
+        'available': VolumeState.AVAILABLE,
+    }
+
+    def __init__(self, provider, network):
+        super(AWSNetwork, self).__init__(provider)
+        self._vpc = network
+
+    @property
+    def id(self):
+        return self._vpc.id
+
+    @property
+    def name(self):
+        """
+        Get the network name.
+
+        .. note:: the network must have a (case sensitive) tag ``Name``
+        """
+        return self._vpc.tags.get('Name')
+
+    @name.setter
+    # pylint:disable=arguments-differ
+    def name(self, value):
+        """
+        Set the network name.
+        """
+        self._vpc.add_tag('Name', value)
+
+    @property
+    def state(self):
+        return AWSNetwork._NETWORK_STATE_MAP.get(
+            self._vpc.update(), NetworkState.UNKNOWN)
+
+    def delete(self):
+        return self._vpc.delete()
+
+    def subnets(self):
+        raise NotImplementedError(
+            'subnets not implemented by this provider.')

+ 31 - 0
cloudbridge/cloud/providers/aws/services.py

@@ -17,6 +17,7 @@ from cloudbridge.cloud.base.services import BaseImageService
 from cloudbridge.cloud.base.services import BaseInstanceService
 from cloudbridge.cloud.base.services import BaseInstanceTypesService
 from cloudbridge.cloud.base.services import BaseKeyPairService
+from cloudbridge.cloud.base.services import BaseNetworkService
 from cloudbridge.cloud.base.services import BaseObjectStoreService
 from cloudbridge.cloud.base.services import BaseRegionService
 from cloudbridge.cloud.base.services import BaseSecurityGroupService
@@ -28,6 +29,7 @@ from cloudbridge.cloud.interfaces.resources \
 from cloudbridge.cloud.interfaces.resources import InstanceType
 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 SecurityGroup
 from cloudbridge.cloud.interfaces.resources import Snapshot
@@ -38,6 +40,7 @@ from .resources import AWSInstance
 from .resources import AWSInstanceType
 from .resources import AWSKeyPair
 from .resources import AWSMachineImage
+from .resources import AWSNetwork
 from .resources import AWSRegion
 from .resources import AWSSecurityGroup
 from .resources import AWSSnapshot
@@ -627,3 +630,31 @@ class AWSRegionService(BaseRegionService):
                    for region in self.provider.ec2_conn.get_all_regions()]
         return ClientPagedResultList(self.provider, regions,
                                      limit=limit, marker=marker)
+
+
+class AWSNetworkService(BaseNetworkService):
+
+    def __init__(self, provider):
+        super(AWSNetworkService, self).__init__(provider)
+
+    def get(self, network_id):
+        network = self.provider.vpc_conn.get_all_vpcs(vpc_ids=[network_id])
+        if network:
+            return AWSNetwork(self.provider, network[0])
+        return None
+
+    def list(self, limit=None, marker=None):
+        networks = [AWSNetwork(self.provider, network)
+                    for network in self.provider.vpc_conn.get_all_vpcs()]
+        return ClientPagedResultList(self.provider, networks,
+                                     limit=limit, marker=marker)
+
+    def create(self, name=None):
+        # AWS requried CIDR block to be specified when creating a network
+        # so set a default one and use the largest possible netmask.
+        default_cidr = '10.0.0.0/16'
+        network = self.provider.vpc_conn.create_vpc(cidr_block=default_cidr)
+        cb_network = AWSNetwork(self.provider, network)
+        if name:
+            cb_network.name = name
+        return cb_network

+ 6 - 0
cloudbridge/cloud/providers/openstack/provider.py

@@ -18,6 +18,7 @@ from cloudbridge.cloud.base import BaseCloudProvider
 
 from .services import OpenStackBlockStoreService
 from .services import OpenStackComputeService
+from .services import OpenStackNetworkService
 from .services import OpenStackObjectStoreService
 from .services import OpenStackSecurityService
 
@@ -51,6 +52,7 @@ class OpenStackCloudProvider(BaseCloudProvider):
 
         # Initialize provider services
         self._compute = OpenStackComputeService(self)
+        self._network = OpenStackNetworkService(self)
         self._security = OpenStackSecurityService(self)
         self._block_store = OpenStackBlockStoreService(self)
         self._object_store = OpenStackObjectStoreService(self)
@@ -95,6 +97,10 @@ class OpenStackCloudProvider(BaseCloudProvider):
     def compute(self):
         return self._compute
 
+    @property
+    def network(self):
+        return self._network
+
     @property
     def security(self):
         return self._security

+ 47 - 0
cloudbridge/cloud/providers/openstack/resources.py

@@ -12,6 +12,7 @@ from cloudbridge.cloud.base.resources import BaseInstance
 from cloudbridge.cloud.base.resources import BaseInstanceType
 from cloudbridge.cloud.base.resources import BaseKeyPair
 from cloudbridge.cloud.base.resources import BaseMachineImage
+from cloudbridge.cloud.base.resources import BaseNetwork
 from cloudbridge.cloud.base.resources import BasePlacementZone
 from cloudbridge.cloud.base.resources import BaseRegion
 from cloudbridge.cloud.base.resources import BaseSecurityGroup
@@ -20,6 +21,7 @@ from cloudbridge.cloud.base.resources import BaseSnapshot
 from cloudbridge.cloud.base.resources import BaseVolume
 from cloudbridge.cloud.interfaces.resources import InstanceState
 from cloudbridge.cloud.interfaces.resources import MachineImageState
+from cloudbridge.cloud.interfaces.resources import NetworkState
 from cloudbridge.cloud.interfaces.resources import SnapshotState
 from cloudbridge.cloud.interfaces.resources import VolumeState
 from cloudbridge.cloud.providers.openstack import helpers as oshelpers
@@ -532,6 +534,51 @@ class OpenStackSnapshot(BaseSnapshot):
         return cb_vol
 
 
+class OpenStackNetwork(BaseNetwork):
+
+    # Ref: https://github.com/openstack/neutron/blob/master/neutron/plugins/
+    #      common/constants.py
+    _NETWORK_STATE_MAP = {
+        'PENDING_CREATE': NetworkState.PENDING,
+        'PENDING_UPDATE': NetworkState.PENDING,
+        'PENDING_DELETE': NetworkState.PENDING,
+        'CREATED': NetworkState.PENDING,
+        'INACTIVE': NetworkState.PENDING,
+        'DOWN': NetworkState.DOWN,
+        'ERROR': NetworkState.ERROR,
+        'ACTIVE': NetworkState.AVAILABLE
+    }
+
+    def __init__(self, provider, network):
+        super(OpenStackNetwork, self).__init__(provider)
+        self._network = network
+
+    @property
+    def id(self):
+        return self._network.get('id', None)
+
+    @property
+    def name(self):
+        return self._network.get('name', None)
+
+    @property
+    def state(self):
+        return OpenStackNetwork._NETWORK_STATE_MAP.get(
+            self._network.get('status', None),
+            NetworkState.UNKNOWN)
+
+    def delete(self):
+        self._provider.neutron.delete_network(self.id)
+        # Adhear to the interface docs
+        if self.id not in self._provider.neutron.list_networks():
+            return True
+        return False
+
+    def subnets(self):
+        raise NotImplementedError(
+            'subnets not implemented by this provider.')
+
+
 class OpenStackKeyPair(BaseKeyPair):
 
     def __init__(self, provider, key_pair):

+ 24 - 0
cloudbridge/cloud/providers/openstack/services.py

@@ -15,6 +15,7 @@ from cloudbridge.cloud.base.services import BaseImageService
 from cloudbridge.cloud.base.services import BaseInstanceService
 from cloudbridge.cloud.base.services import BaseInstanceTypesService
 from cloudbridge.cloud.base.services import BaseKeyPairService
+from cloudbridge.cloud.base.services import BaseNetworkService
 from cloudbridge.cloud.base.services import BaseObjectStoreService
 from cloudbridge.cloud.base.services import BaseRegionService
 from cloudbridge.cloud.base.services import BaseSecurityGroupService
@@ -35,6 +36,7 @@ from .resources import OpenStackInstance
 from .resources import OpenStackInstanceType
 from .resources import OpenStackKeyPair
 from .resources import OpenStackMachineImage
+from .resources import OpenStackNetwork
 from .resources import OpenStackRegion
 from .resources import OpenStackSecurityGroup
 from .resources import OpenStackSnapshot
@@ -633,3 +635,25 @@ class OpenStackInstanceService(BaseInstanceService):
             return OpenStackInstance(self.provider, os_instance)
         except NovaNotFound:
             return None
+
+
+class OpenStackNetworkService(BaseNetworkService):
+
+    def __init__(self, provider):
+        super(OpenStackNetworkService, self).__init__(provider)
+
+    def get(self, network_id):
+        network = (n for n in self.list() if n.id == network_id)
+        return next(network, None)
+
+    def list(self, limit=None, marker=None):
+        networks = [OpenStackNetwork(self.provider, network)
+                    for network in self.provider.neutron.list_networks()
+                    .get('networks', [])]
+        return ClientPagedResultList(self.provider, networks,
+                                     limit=limit, marker=marker)
+
+    def create(self, name=''):
+        net_info = {'name': name}
+        network = self.provider.neutron.create_network({'network': net_info})
+        return OpenStackNetwork(self.provider, network.get('network'))

+ 14 - 1
setup.py

@@ -1,5 +1,18 @@
+import ast
+import os
+import re
 from setuptools import setup, find_packages
 
+# Cannot use "from cloudbridge import get_version" because that would try to
+# import the six package which may not be installed yet.
+reg = re.compile(r'__version__\s*=\s*(.+)')
+with open(os.path.join('cloudbridge', '__init__.py')) as f:
+    for line in f:
+        m = reg.match(line)
+        if m:
+            version = ast.literal_eval(m.group(1))
+            break
+
 base_reqs = ['bunch>=1.00', 'six>=1.9.0', 'retrying']
 openstack_reqs = ['python-novaclient', 'python-glanceclient',
                   'python-cinderclient', 'python-swiftclient',
@@ -9,7 +22,7 @@ full_reqs = base_reqs + aws_reqs + openstack_reqs
 dev_reqs = ['tox', 'moto>=0.4.18', 'sphinx'] + full_reqs
 
 setup(name='cloudbridge',
-      version=0.1,
+      version=version,
       description='A simple layer of abstraction over multiple cloud'
       'providers.',
       author='Galaxy and GVL Projects',