add volume, list_images, snapshot methods and tests

This commit is contained in:
Anthony Shaw 2017-04-30 15:04:50 +10:00
parent 3f3565429c
commit 277933e8d8
No known key found for this signature in database
GPG Key ID: AB4A19AE1CE85744
2 changed files with 392 additions and 21 deletions

View File

@ -197,12 +197,8 @@ def reboot_node(node_id, profile, **libcloud_kwargs):
salt myminion libcloud_compute.reboot_node as-2346 profile1 salt myminion libcloud_compute.reboot_node as-2346 profile1
''' '''
conn = _get_driver(profile=profile) conn = _get_driver(profile=profile)
matches = [node for node in conn.list_nodes(**libcloud_kwargs) if node.id == node_id] node = _get_by_id(conn.list_nodes(**libcloud_kwargs), node_id)
if len(matches) == 0: return conn.reboot_node(node, **libcloud_kwargs)
raise ValueError('Could not find a matching node')
elif len(matches) > 1:
raise ValueError('The node_id matched {0} nodes, not 1'.format(len(matches)))
return conn.reboot_node(matches[0], **libcloud_kwargs)
def destroy_node(node_id, profile, **libcloud_kwargs): def destroy_node(node_id, profile, **libcloud_kwargs):
@ -225,12 +221,8 @@ def destroy_node(node_id, profile, **libcloud_kwargs):
salt myminion libcloud_compute.destry_node as-2346 profile1 salt myminion libcloud_compute.destry_node as-2346 profile1
''' '''
conn = _get_driver(profile=profile) conn = _get_driver(profile=profile)
matches = [node for node in conn.list_nodes(**libcloud_kwargs) if node.id == node_id] node = _get_by_id(conn.list_nodes(**libcloud_kwargs), node_id)
if len(matches) == 0: return conn.destroy_node(node, **libcloud_kwargs)
raise ValueError('Could not find a matching node')
elif len(matches) > 1:
raise ValueError('The node_id matched {0} nodes, not 1'.format(len(matches)))
return conn.destroy_node(matches[0], **libcloud_kwargs)
def list_volumes(profile, **libcloud_kwargs): def list_volumes(profile, **libcloud_kwargs):
@ -258,19 +250,254 @@ def list_volumes(profile, **libcloud_kwargs):
ret.append(_simple_volume(volume)) ret.append(_simple_volume(volume))
return ret return ret
def list_volume_snapshots(volume_id, profile, **libcloud_kwargs):
'''
Return a list of storage volumes snapshots for this cloud
:param volume_id: The volume identifier
:type volume_id: ``str``
:param profile: The profile key
:type profile: ``str``
:param libcloud_kwargs: Extra arguments for the driver's list_volume_snapshots method
:type libcloud_kwargs: ``dict``
CLI Example:
.. code-block:: bash
salt myminion libcloud_compute.list_volume_snapshots vol1 profile1
'''
conn = _get_driver(profile=profile)
libcloud_kwargs = clean_kwargs(**libcloud_kwargs)
volume = _get_by_id(conn.list_volumes(), volume_id)
snapshots = conn.list_volume_snapshots(volume, **libcloud_kwargs)
ret = []
for snapshot in snapshots:
ret.append(_simple_volume_snapshot(snapshot))
return ret
def create_volume(size, name, profile, location_id=None, **libcloud_kwargs):
'''
Create a storage volume
:param size: Size of volume in gigabytes (required)
:type size: ``int``
:param name: Name of the volume to be created
:type name: ``str``
:param location_id: Which data center to create a volume in. If
empty, undefined behavior will be selected.
(optional)
:type location_id: ``str``
:param profile: The profile key
:type profile: ``str``
:param libcloud_kwargs: Extra arguments for the driver's list_volumes method
:type libcloud_kwargs: ``dict``
CLI Example:
.. code-block:: bash
salt myminion libcloud_compute.create_volume 1000 vol1 profile1
'''
conn = _get_driver(profile=profile)
libcloud_kwargs = clean_kwargs(**libcloud_kwargs)
if location_id is not None:
location = _get_by_id(conn.list_locations(), location_id)
else:
location = None
# TODO : Support creating from volume snapshot
volume = conn.create_volume(size, name, location, snapshot=None, **libcloud_kwargs)
return _simple_volume(volume)
def create_volume_snapshot(volume_id, profile, name=None, **libcloud_kwargs):
'''
Create a storage volume snapshot
:param volume_id: Volume ID from which to create the new
snapshot.
:type volume_id: ``str``
:param profile: The profile key
:type profile: ``str``
:param name: Name of the snapshot to be created (optional)
:type name: ``str``
:param libcloud_kwargs: Extra arguments for the driver's create_volume_snapshot method
:type libcloud_kwargs: ``dict``
CLI Example:
.. code-block:: bash
salt myminion libcloud_compute.create_volume_snapshot vol1 profile1
'''
conn = _get_driver(profile=profile)
libcloud_kwargs = clean_kwargs(**libcloud_kwargs)
volume = _get_by_id(conn.list_volumes(), volume_id)
snapshot = conn.create_volume_snapshot(volume, name=name, **libcloud_kwargs)
return _simple_volume_snapshot(snapshot)
def attach_volume(node_id, volume_id, profile, device=None, **libcloud_kwargs):
'''
Attaches volume to node.
:param node_id: Node ID to target
:type node_id: ``str``
:param volume_id: Volume ID from which to attach
:type volume_id: ``str``
:param profile: The profile key
:type profile: ``str``
:param device: Where the device is exposed, e.g. '/dev/sdb'
:type device: ``str``
:param libcloud_kwargs: Extra arguments for the driver's attach_volume method
:type libcloud_kwargs: ``dict``
CLI Example:
.. code-block:: bash
salt myminion libcloud_compute.detach_volume vol1 profile1
'''
conn = _get_driver(profile=profile)
libcloud_kwargs = clean_kwargs(**libcloud_kwargs)
volume = _get_by_id(conn.list_volumes(), volume_id)
node = _get_by_id(conn.list_nodes(), node_id)
return conn.attach_volume(node, volume, device=device, **libcloud_kwargs)
def detach_volume(volume_id, profile, **libcloud_kwargs):
'''
Detaches a volume from a node.
:param volume_id: Volume ID from which to detach
:type volume_id: ``str``
:param profile: The profile key
:type profile: ``str``
:param libcloud_kwargs: Extra arguments for the driver's detach_volume method
:type libcloud_kwargs: ``dict``
CLI Example:
.. code-block:: bash
salt myminion libcloud_compute.detach_volume vol1 profile1
'''
conn = _get_driver(profile=profile)
libcloud_kwargs = clean_kwargs(**libcloud_kwargs)
volume = _get_by_id(conn.list_volumes(), volume_id)
return conn.detach_volume(volume, **libcloud_kwargs)
def destroy_volume(volume_id, profile, **libcloud_kwargs):
'''
Destroy a volume.
:param volume_id: Volume ID from which to destroy
:type volume_id: ``str``
:param profile: The profile key
:type profile: ``str``
:param libcloud_kwargs: Extra arguments for the driver's destroy_volume method
:type libcloud_kwargs: ``dict``
CLI Example:
.. code-block:: bash
salt myminion libcloud_compute.destroy_volume vol1 profile1
'''
conn = _get_driver(profile=profile)
libcloud_kwargs = clean_kwargs(**libcloud_kwargs)
volume = _get_by_id(conn.list_volumes(), volume_id)
return conn.destroy_volume(volume, **libcloud_kwargs)
def destroy_volume_snapshot(volume_id, snapshot_id, profile, **libcloud_kwargs):
'''
Destroy a volume snapshot.
:param volume_id: Volume ID from which the snapshot belongs
:type volume_id: ``str``
:param snapshot_id: Volume Snapshot ID from which to destroy
:type snapshot_id: ``str``
:param profile: The profile key
:type profile: ``str``
:param libcloud_kwargs: Extra arguments for the driver's destroy_volume_snapshot method
:type libcloud_kwargs: ``dict``
CLI Example:
.. code-block:: bash
salt myminion libcloud_compute.destroy_volume_snapshot snap1 profile1
'''
conn = _get_driver(profile=profile)
libcloud_kwargs = clean_kwargs(**libcloud_kwargs)
volume = _get_by_id(conn.list_volumes(), volume_id)
snapshot = _get_by_id(conn.list_volume_snapshots(volume), snapshot_id)
return conn.destroy_volume_snapshot(snapshot, **libcloud_kwargs)
def list_images(profile, location_id=None, **libcloud_kwargs):
'''
Return a list of images for this cloud
:param profile: The profile key
:type profile: ``str``
:param location_id: The location key, from list_locations
:type location_id: ``str``
:param libcloud_kwargs: Extra arguments for the driver's list_images method
:type libcloud_kwargs: ``dict``
CLI Example:
.. code-block:: bash
salt myminion libcloud_compute.list_images profile1
'''
conn = _get_driver(profile=profile)
libcloud_kwargs = clean_kwargs(**libcloud_kwargs)
if location_id is not None:
location = _get_by_id(conn.list_locations(), location_id)
else:
location = None
images = conn.list_images(location=location, **libcloud_kwargs)
ret = []
for image in images:
ret.append(_simple_image(image))
return ret
''' '''
Remaining functions to implement: Remaining functions to implement:
def create_node(self, **kwargs): def create_node(self, **kwargs):
def deploy_node(self, **kwargs): def deploy_node(self, **kwargs):
def list_volume_snapshots(self, volume):
def create_volume(self, size, name, location=None, snapshot=None):
def create_volume_snapshot(self, volume, name=None):
def attach_volume(self, node, volume, device=None):
def detach_volume(self, volume):
def destroy_volume(self, volume):
def destroy_volume_snapshot(self, snapshot):
def list_images(self, location=None):
def create_image(self, node, name, description=None): def create_image(self, node, name, description=None):
def delete_image(self, node_image): def delete_image(self, node_image):
def get_image(self, image_id): def get_image(self, image_id):
@ -282,6 +509,17 @@ Remaining functions to implement:
def import_key_pair_from_file(self, name, key_file_path): def import_key_pair_from_file(self, name, key_file_path):
def delete_key_pair(self, key_pair): def delete_key_pair(self, key_pair):
''' '''
def _get_by_id(collection, id):
'''
Get item from a list by the id field
'''
matches = [item for item in collection if item.id == id]
if len(matches) == 0:
raise ValueError('Could not find a matching item')
elif len(matches) > 1:
raise ValueError('The id matched {0} items, not 1'.format(len(matches)))
return matches[0]
def _simple_volume(volume): def _simple_volume(volume):
return { return {
@ -323,3 +561,22 @@ def _simple_node(node):
'size': _simple_size(node.size) if node.size else {}, 'size': _simple_size(node.size) if node.size else {},
'extra': node.extra 'extra': node.extra
} }
def _simple_volume_snapshot(snapshot):
return {
'id': snapshot.id,
'name': snapshot.name,
'size': snapshot.size,
'extra': snapshot.extra,
'created': snapshot.created,
'state': snapshot.state
}
def _simple_image(image):
return {
'id': image.id,
'name': image.name,
'extra': image.extra,
}

View File

@ -19,7 +19,8 @@ import salt.modules.libcloud_compute as libcloud_compute
from libcloud.compute.base import (BaseDriver, Node, from libcloud.compute.base import (BaseDriver, Node,
NodeSize, NodeState, NodeLocation, NodeSize, NodeState, NodeLocation,
StorageVolume, StorageVolumeState) StorageVolume, StorageVolumeState,
VolumeSnapshot, NodeImage)
class MockComputeDriver(BaseDriver): class MockComputeDriver(BaseDriver):
@ -48,6 +49,21 @@ class MockComputeDriver(BaseDriver):
'ex_key': 'ex_value' 'ex_key': 'ex_value'
} }
) )
self._TEST_VOLUME_SNAPSHOT = VolumeSnapshot(
id='snap1',
name='snap_name',
size=80960,
driver=self
)
self._TEST_IMAGE = NodeImage(
id='image1',
name='test_image',
extra={
'ex_key': 'ex_value'
},
driver=self
)
def list_nodes(self): def list_nodes(self):
return [self._TEST_NODE] return [self._TEST_NODE]
@ -71,6 +87,47 @@ class MockComputeDriver(BaseDriver):
def list_volumes(self): def list_volumes(self):
return [self._TEST_VOLUME] return [self._TEST_VOLUME]
def list_volume_snapshots(self, volume):
assert volume.id == 'vol1'
return [self._TEST_VOLUME_SNAPSHOT]
def create_volume(self, size, name, location=None, snapshot=None):
assert size == 9000
assert name == 'test_new_volume'
if location:
assert location.country == 'Australia'
return self._TEST_VOLUME
def create_volume_snapshot(self, volume, name=None):
assert volume.id == 'vol1'
if name:
assert name == 'test_snapshot'
return self._TEST_VOLUME_SNAPSHOT
def attach_volume(self, node, volume, device=None):
assert node.id == 'test_id'
assert volume.id == 'vol1'
if device:
assert device == '/dev/sdc'
return True
def detach_volume(self, volume):
assert volume.id == 'vol1'
return True
def destroy_volume(self, volume):
assert volume.id == 'vol1'
return True
def destroy_volume_snapshot(self, snapshot):
assert snapshot.id == 'snap1'
return True
def list_images(self, location=None):
if location:
assert location.id == 'test_location'
return [self._TEST_IMAGE]
@skipIf(NO_MOCK, NO_MOCK_REASON) @skipIf(NO_MOCK, NO_MOCK_REASON)
@patch('salt.modules.libcloud_compute._get_driver', @patch('salt.modules.libcloud_compute._get_driver',
@ -126,6 +183,16 @@ class LibcloudComputeModuleTestCase(TestCase, LoaderModuleMockMixin):
self.assertEqual(volume['state'], 'available') self.assertEqual(volume['state'], 'available')
self.assertEqual(volume['extra'], {'ex_key': 'ex_value'}) self.assertEqual(volume['extra'], {'ex_key': 'ex_value'})
def _validate_volume_snapshot(self, volume):
self.assertEqual(volume['id'], 'snap1')
self.assertEqual(volume['name'], 'snap_name')
self.assertEqual(volume['size'], 80960)
def _validate_image(self, image):
self.assertEqual(image['id'], 'image1')
self.assertEqual(image['name'], 'test_image')
self.assertEqual(image['extra'], {'ex_key': 'ex_value'})
def test_list_nodes(self): def test_list_nodes(self):
nodes = libcloud_compute.list_nodes('test') nodes = libcloud_compute.list_nodes('test')
self.assertEqual(len(nodes), 1) self.assertEqual(len(nodes), 1)
@ -166,3 +233,50 @@ class LibcloudComputeModuleTestCase(TestCase, LoaderModuleMockMixin):
volumes = libcloud_compute.list_volumes('test') volumes = libcloud_compute.list_volumes('test')
self.assertEqual(len(volumes), 1) self.assertEqual(len(volumes), 1)
self._validate_volume(volumes[0]) self._validate_volume(volumes[0])
def test_list_volume_snapshots(self):
volumes = libcloud_compute.list_volume_snapshots('vol1', 'test')
self.assertEqual(len(volumes), 1)
self._validate_volume_snapshot(volumes[0])
def test_create_volume(self):
volume = libcloud_compute.create_volume(9000, 'test_new_volume', 'test')
self._validate_volume(volume)
def test_create_volume_in_location(self):
volume = libcloud_compute.create_volume(9000, 'test_new_volume', 'test', location_id='test_location')
self._validate_volume(volume)
def test_create_volume_snapshot(self):
snapshot = libcloud_compute.create_volume_snapshot('vol1', 'test')
self._validate_volume_snapshot(snapshot)
def test_create_volume_snapshot_named(self):
snapshot = libcloud_compute.create_volume_snapshot('vol1', 'test', name='test_snapshot')
self._validate_volume_snapshot(snapshot)
def test_attach_volume(self):
result = libcloud_compute.attach_volume('test_id', 'vol1', 'test')
self.assertTrue(result)
def test_detatch_volume(self):
result = libcloud_compute.detach_volume('vol1', 'test')
self.assertTrue(result)
def test_destroy_volume(self):
result = libcloud_compute.destroy_volume('vol1', 'test')
self.assertTrue(result)
def test_destroy_volume_snapshot(self):
result = libcloud_compute.destroy_volume_snapshot('vol1', 'snap1', 'test')
self.assertTrue(result)
def test_list_images(self):
images = libcloud_compute.list_images('test')
self.assertEqual(len(images), 1)
self._validate_image(images[0])
def test_list_images_in_location(self):
images = libcloud_compute.list_images('test', location_id='test_location')
self.assertEqual(len(images), 1)
self._validate_image(images[0])