Procházet zdrojové kódy

Added more properties to volumes, including attachment info.

Nuwan Goonasekera před 10 roky
rodič
revize
eb939eba5f

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

@@ -10,6 +10,7 @@ import six
 
 
 from cloudbridge.cloud.interfaces.resources \
 from cloudbridge.cloud.interfaces.resources \
     import InvalidConfigurationException
     import InvalidConfigurationException
+from cloudbridge.cloud.interfaces.resources import AttachmentInfo
 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
 from cloudbridge.cloud.interfaces.resources import CloudResource
 from cloudbridge.cloud.interfaces.resources import CloudResource
@@ -352,6 +353,26 @@ class BaseMachineImage(
                                             self.name, self.id)
                                             self.name, self.id)
 
 
 
 
+class BaseAttachmentInfo(AttachmentInfo):
+
+    def __init__(self, volume, instance_id, device):
+        self._volume = volume
+        self._instance_id = instance_id
+        self._device = device
+
+    @property
+    def volume(self):
+        return self._volume
+
+    @property
+    def instance_id(self):
+        return self._instance_id
+
+    @property
+    def device(self):
+        return self._device
+
+
 class BaseVolume(BaseCloudResource, BaseObjectLifeCycleMixin, Volume):
 class BaseVolume(BaseCloudResource, BaseObjectLifeCycleMixin, Volume):
 
 
     def __init__(self, provider):
     def __init__(self, provider):

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

@@ -953,6 +953,42 @@ class Subnet(CloudResource):
         pass
         pass
 
 
 
 
+class AttachmentInfo(object):
+    """
+    Contains attachment information for a volume.
+    """
+
+    @abstractproperty
+    def volume(self):
+        """
+        Get the volume instance related to this attachment.
+
+        :rtype: ``Volume``
+        :return: Volume object that this attachment info belongs to
+        """
+        pass
+
+    @abstractproperty
+    def instance_id(self):
+        """
+        Get the instance_id related to this attachment.
+
+        :rtype: ``str``
+        :return: Instance id that this attachment info belongs to
+        """
+        pass
+
+    @abstractproperty
+    def device(self):
+        """
+        Get the device the volume is mapped as.
+
+        :rtype: ``str``
+        :return: Device that the volume is mapped as
+        """
+        pass
+
+
 class VolumeState(object):
 class VolumeState(object):
 
 
     """
     """
@@ -1009,6 +1045,83 @@ class Volume(ObjectLifeCycleMixin, CloudResource):
         """
         """
         pass
         pass
 
 
+    @abstractproperty
+    def description(self):
+        """
+        Get the volume description. Some cloud providers may not support this
+        property, and will return the volume name instead.
+
+        :rtype: ``str``
+        :return: Description for this volume as returned by the cloud
+        middleware.
+        """
+        pass
+
+    @description.setter
+    @abstractmethod
+    def description(self, value):
+        """
+        Set the volume description. Some cloud providers may not support this
+        property, and setting the description may have no effect. (Providers
+        that do not support this property will always return the volume name
+        as the description)
+        """
+        pass
+
+    @abstractproperty
+    def size(self):
+        """
+        Get the volume size (in GB).
+
+        :rtype: ``int``
+        :return: Size for this volume as returned by the cloud middleware.
+        """
+        pass
+
+    @abstractproperty
+    def create_time(self):
+        """
+        Get the creation data and time for this volume.
+
+        :rtype: ``DateTime``
+        :return: Creation time for this volume as returned by the cloud
+        middleware.
+        """
+        pass
+
+    @abstractproperty
+    def zone_id(self):
+        """
+        Get the placement zone id that this volume belongs to.
+
+        :rtype: ``str``
+        :return: PlacementZone for this volume as returned by the cloud
+        middleware.
+        """
+        pass
+
+    @abstractproperty
+    def source(self):
+        """
+        If available, get the source that this volume is based on (can be
+        a Snapshot or an Image). Returns None if no source.
+
+        :rtype: ``Snapshot`` or ``Image``
+        :return: Snapshot or Image source for this volume as returned by the
+        cloud middleware.
+        """
+        pass
+
+    @abstractproperty
+    def attachments(self):
+        """
+        Get attachment information for this volume.
+
+        :rtype: ``AttachmentInfo``
+        :return: Returns an AttachmentInfo object.
+        """
+        pass
+
     @abstractmethod
     @abstractmethod
     def attach(self, instance, device):
     def attach(self, instance, device):
         """
         """

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

@@ -9,6 +9,7 @@ from boto.exception import EC2ResponseError
 from boto.s3.key import Key
 from boto.s3.key import Key
 from retrying import retry
 from retrying import retry
 
 
+from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseBucket
 from cloudbridge.cloud.base.resources import BaseBucket
 from cloudbridge.cloud.base.resources import BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseInstance
 from cloudbridge.cloud.base.resources import BaseInstance
@@ -387,6 +388,40 @@ class AWSVolume(BaseVolume):
         """
         """
         self._volume.add_tag('Name', value)
         self._volume.add_tag('Name', value)
 
 
+    @property
+    def description(self):
+        return self._volume.tags.get('Description')
+
+    @description.setter
+    def description(self, value):
+        self._volume.add_tag('Description', value)
+
+    @property
+    def size(self):
+        return self._volume.size
+
+    @property
+    def create_time(self):
+        return self._volume.create_time
+
+    @property
+    def zone_id(self):
+        return self._volume.zone
+
+    @property
+    def source(self):
+        return self._provider.block_store.snapshots.get(
+            self._volume.snapshot_id)
+
+    @property
+    def attachments(self):
+        if self._volume.attach_data and self._volume.attach_data.id:
+            return BaseAttachmentInfo(self,
+                                      self._volume.attach_data.instance_id,
+                                      self._volume.attach_data.device)
+        else:
+            return None
+
     def attach(self, instance, device):
     def attach(self, instance, device):
         """
         """
         Attach this volume to an instance.
         Attach this volume to an instance.

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

@@ -6,6 +6,8 @@ import json
 import shutil
 import shutil
 
 
 import ipaddress
 import ipaddress
+
+from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseBucket
 from cloudbridge.cloud.base.resources import BaseBucket
 from cloudbridge.cloud.base.resources import BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseInstance
 from cloudbridge.cloud.base.resources import BaseInstance
@@ -438,6 +440,42 @@ class OpenStackVolume(BaseVolume):
         self._volume.name = value
         self._volume.name = value
         self._volume.update()
         self._volume.update()
 
 
+    @property
+    def description(self):
+        return self._volume.description
+
+    @description.setter
+    def description(self, value):
+        self._volume.description = value
+        self._volume.update()
+
+    @property
+    def size(self):
+        return self._volume.size
+
+    @property
+    def create_time(self):
+        return self._volume.created_at
+
+    @property
+    def zone_id(self):
+        return self._volume.availability_zone
+
+    @property
+    def source(self):
+        return self._provider.block_store.snapshots.get(
+            self._volume.snapshot_id)
+
+    @property
+    def attachments(self):
+        if self._volume.attachments:
+            return BaseAttachmentInfo(
+                self,
+                self._volume.attachments[0].get('server_id'),
+                self._volume.attachments[0].get('device'))
+        else:
+            return None
+
     def attach(self, instance, device):
     def attach(self, instance, device):
         """
         """
         Attach this volume to an instance.
         Attach this volume to an instance.

+ 49 - 0
test/test_block_store_service.py

@@ -1,5 +1,7 @@
 import uuid
 import uuid
 
 
+import six
+
 from cloudbridge.cloud.interfaces import SnapshotState
 from cloudbridge.cloud.interfaces import SnapshotState
 from cloudbridge.cloud.interfaces import VolumeState
 from cloudbridge.cloud.interfaces import VolumeState
 from test.helpers import ProviderTestBase
 from test.helpers import ProviderTestBase
@@ -111,6 +113,53 @@ class CloudBlockStoreServiceTestCase(ProviderTestBase):
                     [VolumeState.AVAILABLE],
                     [VolumeState.AVAILABLE],
                     terminal_states=[VolumeState.ERROR, VolumeState.DELETED])
                     terminal_states=[VolumeState.ERROR, VolumeState.DELETED])
 
 
+    def test_volume_properties(self):
+        """
+        Test volume properties
+        """
+        instance_name = "CBVolProps-{0}-{1}".format(
+            self.provider.name,
+            uuid.uuid4())
+        test_instance = helpers.get_test_instance(self.provider, instance_name)
+        with helpers.cleanup_action(lambda: test_instance.terminate()):
+            name = "CBUnitTestVolProps-{0}".format(uuid.uuid4())
+            test_vol = self.provider.block_store.volumes.create(
+                name, 1, test_instance.placement_zone)
+            with helpers.cleanup_action(lambda: test_vol.delete()):
+                test_vol.wait_till_ready()
+                self.assertTrue(
+                    isinstance(test_vol.size, six.integer_types) and
+                    test_vol.size >= 0,
+                    "Volume.size must be a positive number, but got %s"
+                    % test_vol.size)
+                self.assertTrue(
+                    test_vol.description is None or
+                    isinstance(test_vol.description, six.string_types),
+                    "Volume.description must be None or a string. Got: %s"
+                    % test_vol.description)
+                self.assertIsNone(test_vol.source)
+                self.assertIsNone(test_vol.source)
+                self.assertIsNotNone(test_vol.create_time)
+                self.assertIsNotNone(test_vol.zone_id)
+                self.assertIsNone(test_vol.attachments)
+                test_vol.attach(test_instance, '/dev/sda2')
+                test_vol.wait_for(
+                    [VolumeState.IN_USE],
+                    terminal_states=[VolumeState.ERROR, VolumeState.DELETED])
+                self.assertIsNotNone(test_vol.attachments)
+                self.assertEqual(test_vol.attachments.volume, test_vol)
+                self.assertEqual(test_vol.attachments.instance_id,
+                                 test_instance.id)
+                self.assertEqual(test_vol.attachments.device,
+                                 "/dev/sda2")
+                test_vol.detach()
+                # Force a refresh before checking attachment status
+                test_vol.refresh()
+                self.assertIsNone(test_vol.attachments)
+                test_vol.wait_for(
+                    [VolumeState.AVAILABLE],
+                    terminal_states=[VolumeState.ERROR, VolumeState.DELETED])
+
     def test_crud_snapshot(self):
     def test_crud_snapshot(self):
         """
         """
         Create a new volume, create a snapshot of the volume, and check
         Create a new volume, create a snapshot of the volume, and check