blob: aac0b5d2ba441669572bed593eeac4e6c1884475 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information regarding copyright
# ownership. The ASF licenses this file to you under the Apache License,
# Version 2.0 (the "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
""" Smoke test for the StorPool-specific listVolumesForImport/importVolume/unmanageVolume flow.
Exercises the StorPool primary storage driver fast path (StorPoolPrimaryDataStoreDriver
.listVolumesForImport, used instead of dispatching a command to a KVM host) and verifies
that importVolume tags the underlying StorPool volume with "cs"/"uuid" (and the disk
offering's StorPool QoS tier, if set), while unmanageVolume clears those tags again so the
volume is reported again by listVolumesForImport.
Also covers importing a volume/VM whose disk offering carries the StorPool QoS class detail
(SP_QOSCLASS), which must end up as the "qc" tag on the StorPool volume.
The importVm tests use a volume created from a snapshot of a real VM's ROOT disk (rather than
a blank data volume), so the imported VM actually has an OS on it, and verify over SSH that it
boots - not just that the import API calls succeeded.
"""
import math
import uuid
from marvin.cloudstackAPI import unmanageVolume, listVolumesForImport, importVolume, importVm
from marvin.cloudstackTestCase import cloudstackTestCase, unittest
from marvin.codes import FAILED
from marvin.lib.base import (Account,
DiskOffering,
ServiceOffering,
Snapshot,
Tag,
VirtualMachine,
Volume)
from marvin.lib.common import (get_domain,
get_zone,
get_suitable_test_template,
list_clusters,
list_networks,
list_storage_pools)
from nose.plugins.attrib import attr
from storpool import spapi, sptypes
from sp_util import TestData, StorPoolHelper
get_suitable_test_template.__test__ = False
_multiprocess_shared_ = True
class TestStorPoolImportVolume(cloudstackTestCase):
@classmethod
def setUpClass(cls):
testClient = super(TestStorPoolImportVolume, cls).getClsTestClient()
cls.apiclient = testClient.getApiClient()
cls.testdata = testClient.getParsedTestDataConfig()
cls.hypervisor = testClient.getHypervisorInfo()
if cls.hypervisor.lower() != "kvm":
raise unittest.SkipTest("StorPool import/unmanage volume is only available for KVM")
config = cls.getClsConfig()
zone = config.zones[0]
StorPoolHelper.logger = cls
cls.helper = StorPoolHelper()
sp_pools = cls.helper.get_pool(zone)
if sp_pools is None:
raise unittest.SkipTest("This test needs at least 2 StorPool primary storages configured in the zone")
cls.spapi = spapi.Api(host=zone.spEndpoint, port=zone.spEndpointPort, auth=zone.spAuthToken, multiCluster=True)
sp_testdata = TestData().testdata
cls.pool_tag = sp_testdata[TestData.sp_template_1]["tags"]
storage_pools = list_storage_pools(cls.apiclient, name=sp_pools[0]["name"])
if not storage_pools:
raise unittest.SkipTest("StorPool primary storage %s is not registered in CloudStack yet" % sp_pools[0]["name"])
cls.primary_storage = storage_pools[0]
cls.domain = get_domain(cls.apiclient)
cls.zone = get_zone(cls.apiclient)
cls._cleanup = []
cls.account = Account.create(
cls.apiclient,
cls.testdata["account"],
domainid=cls.domain.id
)
cls._cleanup.append(cls.account)
cls.disk_offering = DiskOffering.create(
cls.apiclient,
sp_testdata[TestData.diskOfferingSmall]
)
cls._cleanup.append(cls.disk_offering)
cls.qos_tag = cls.pool_tag
cls.disk_offering_qos = DiskOffering.create(
cls.apiclient,
sp_testdata[TestData.diskOfferingSmall],
details=[{"SP_QOSCLASS": cls.qos_tag}]
)
cls._cleanup.append(cls.disk_offering_qos)
service_offering_data = dict(cls.testdata["service_offerings"]["tiny"])
service_offering_data["name"] = "StorPoolImportTest-%s" % uuid.uuid4()
service_offering_data["tags"] = cls.pool_tag
cls.service_offering = ServiceOffering.create(cls.apiclient, service_offering_data)
cls._cleanup.append(cls.service_offering)
service_offering_qos_data = dict(cls.testdata["service_offerings"]["tiny"])
service_offering_qos_data["name"] = "StorPoolImportQosTest-%s" % uuid.uuid4()
service_offering_qos_data["tags"] = cls.pool_tag
cls.service_offering_qos = ServiceOffering.create(
cls.apiclient,
service_offering_qos_data,
diskofferingid=cls.disk_offering_qos.id
)
cls._cleanup.append(cls.service_offering_qos)
template = get_suitable_test_template(
cls.apiclient,
cls.primary_storage.zoneid,
cls.testdata["ostype"],
cls.hypervisor
)
if template == FAILED:
assert False, "get_suitable_test_template() failed to return a template"
cls.virtual_machine = VirtualMachine.create(
cls.apiclient,
cls.testdata["virtual_machine"],
templateid=template.id,
zoneid=cls.primary_storage.zoneid,
accountid=cls.account.name,
domainid=cls.account.domainid,
serviceofferingid=cls.service_offering.id,
mode=cls.zone.networktype
)
cls._cleanup.append(cls.virtual_machine)
cls.virtual_machine.stop(cls.apiclient, forced=True)
root_volume = Volume.list(cls.apiclient, virtualmachineid=cls.virtual_machine.id,
type="ROOT", listall=True)[0]
cls.root_snapshot = Snapshot.create(
cls.apiclient,
volume_id=root_volume.id,
account=cls.account.name,
domainid=cls.account.domainid
)
cls._cleanup.append(cls.root_snapshot)
disk_offering_boot_data = dict(sp_testdata[TestData.diskOfferingSmall])
disk_offering_boot_data["name"] = "StorPoolImportBootTest"
disk_offering_boot_data["disksize"] = int(math.ceil(cls.root_snapshot.virtualsize / (1024.0 ** 3)))
cls.disk_offering_boot = DiskOffering.create(cls.apiclient, disk_offering_boot_data)
cls._cleanup.append(cls.disk_offering_boot)
@classmethod
def tearDownClass(cls):
super(TestStorPoolImportVolume, cls).tearDownClass()
def setUp(self):
self.apiclient = self.testClient.getApiClient()
self.cleanup = []
def storpool_volume(self, path):
name = path.split("/")[3]
volumes = self.spapi.volumeList(volumeName="~" + name)
self.assertTrue(len(volumes) > 0, "Could not find StorPool volume for path %s" % path)
return volumes[0]
def create_storpool_volume(self, diskname, disk_offering_id):
return Volume.create(
self.apiclient,
{"diskname": diskname},
zoneid=self.primary_storage.zoneid,
account=self.account.name,
domainid=self.account.domainid,
diskofferingid=disk_offering_id,
storageid=self.primary_storage.id
)
def create_bootable_storpool_volume(self, diskname):
""" Clones cls.root_snapshot (a snapshot of a real VM's ROOT disk) into a new volume, so
it has an actual OS on it - unlike create_storpool_volume()'s blank data volume, this is
usable as a VM's ROOT disk after import. storageid can't be combined with snapshotid
(CreateVolumeCmd.getStorageId() rejects that combination), so disk_offering_boot's StorPool
tag is what pins the resulting volume onto cls.primary_storage instead. """
return Volume.create(
self.apiclient,
{"diskname": diskname},
zoneid=self.primary_storage.zoneid,
account=self.account.name,
domainid=self.account.domainid,
diskofferingid=self.disk_offering_boot.id,
snapshotid=self.root_snapshot.id
)
def assert_vm_boots_over_ssh(self, vm):
""" Starts the (powered-off, just-imported) VM and runs a command over SSH, to prove it
actually boots the cloned OS disk - not just that the import API calls succeeded. """
vm.start(self.apiclient)
ssh_client = vm.get_ssh_client(ipaddress=vm.ipaddress, retries=30)
result = ssh_client.execute("echo storpool-import-boot-check")
self.assertEqual(["storpool-import-boot-check"], result,
"The imported VM should boot and be reachable over SSH")
def create_sp_volume_directly(self, tags):
""" Creates a volume on StorPool without going through CloudStack at all - simulates a
volume that pre-exists on the pool (e.g. provisioned by external tooling), the realistic
"import pre-existing storage" scenario, as opposed to create_storpool_volume()'s
CloudStack-created-then-unmanaged volume. Returns (sp_volume_name, path); the caller must
delete the volume directly via self.spapi.volumeDelete() if it is never imported, since
self.cleanup only drains CloudStack-tracked marvin.lib.base.Volume instances. """
volume_desc = sptypes.VolumeCreateDesc(
size=1 * 1024 * 1024 * 1024,
template=self.pool_tag,
tags=tags
)
created = self.spapi.volumeCreate(volume_desc)
sp_volume_name = created.name
path = "/dev/storpool-byid/%s" % sp_volume_name.lstrip("~")
return sp_volume_name, path
@attr(tags=["advanced", "basic", "sg"], required_hardware=False)
def test_01_import_volume_tags_storpool_volume_with_cs_and_uuid(self):
""" importVolume should tag the StorPool volume with cs/uuid (and unmanageVolume
should clear those tags again), so a volume can round-trip between CloudStack-managed
and available-for-import. """
volume = self.create_storpool_volume("StorPoolImportTest", self.disk_offering.id)
sp_volume = self.storpool_volume(volume.path)
self.assertEqual("volume", sp_volume.tags.get("cs"),
"A CloudStack-managed volume should carry the cs=volume StorPool tag")
cmd = unmanageVolume.unmanageVolumeCmd()
cmd.id = volume.id
self.apiclient.unmanageVolume(cmd)
sp_volume = self.storpool_volume(volume.path)
self.assertFalse(sp_volume.tags.get("cs"),
"unmanageVolume should clear the StorPool cs tag")
cmd = listVolumesForImport.listVolumesForImportCmd()
cmd.storageid = self.primary_storage.id
volumes_for_import = self.apiclient.listVolumesForImport(cmd)
self.assertTrue(isinstance(volumes_for_import, list),
"listVolumesForImport should return a valid list")
matching = [v for v in volumes_for_import if v.path == volume.path]
self.assertEqual(1, len(matching),
"The unmanaged volume should be listed as available for import")
self.assertEqual("QCOW2", matching[0].format,
"StorPool volumes are always reported as QCOW2")
cmd = importVolume.importVolumeCmd()
cmd.storageid = self.primary_storage.id
cmd.path = volume.path
cmd.diskofferingid = self.disk_offering.id
imported = self.apiclient.importVolume(cmd)
self.assertIsNotNone(imported, "importVolume should return the imported volume")
self.cleanup.append(Volume(imported.__dict__))
sp_volume = self.storpool_volume(volume.path)
self.assertEqual("volume", sp_volume.tags.get("cs"),
"importVolume should re-tag the StorPool volume as cs=volume")
self.assertEqual(imported.id, sp_volume.tags.get("uuid"),
"importVolume should tag the StorPool volume with the new CloudStack volume uuid")
cmd = listVolumesForImport.listVolumesForImportCmd()
cmd.storageid = self.primary_storage.id
volumes_for_import = self.apiclient.listVolumesForImport(cmd)
matching = [v for v in (volumes_for_import or []) if v.path == volume.path]
self.assertEqual(0, len(matching),
"A re-imported volume should no longer be listed as available for import")
@attr(tags=["advanced", "basic", "sg"], required_hardware=False)
def test_02_list_volumes_for_import_by_path(self):
""" listVolumesForImport with a specific path should look up that one StorPool volume
directly (StorPoolPrimaryDataStoreDriver's single-volume Describe lookup), instead of
browsing/filtering the whole pool. """
volume = self.create_storpool_volume("StorPoolImportPathTest", self.disk_offering.id)
cmd = unmanageVolume.unmanageVolumeCmd()
cmd.id = volume.id
self.apiclient.unmanageVolume(cmd)
cmd = listVolumesForImport.listVolumesForImportCmd()
cmd.storageid = self.primary_storage.id
cmd.path = volume.path
volumes_for_import = self.apiclient.listVolumesForImport(cmd)
self.assertTrue(isinstance(volumes_for_import, list) and len(volumes_for_import) == 1,
"listVolumesForImport with a path should return exactly that one volume")
self.assertEqual(volume.path, volumes_for_import[0].path,
"listVolumesForImport with a path should return the matching volume")
self.assertEqual("QCOW2", volumes_for_import[0].format,
"StorPool volumes are always reported as QCOW2")
cmd = importVolume.importVolumeCmd()
cmd.storageid = self.primary_storage.id
cmd.path = volume.path
cmd.diskofferingid = self.disk_offering.id
imported = self.apiclient.importVolume(cmd)
self.assertIsNotNone(imported, "importVolume should return the imported volume")
self.cleanup.append(Volume(imported.__dict__))
@attr(tags=["advanced", "basic", "sg"], required_hardware=False)
def test_03_import_vm_from_shared_storage_on_storpool(self):
""" importVm(importSource=SHARED) against a disk already sitting on a StorPool pool
should record the disk as the VM's ROOT volume, and tag the StorPool volume with
cs/uuid/cvm, since the disk is now owned by (and attached to) a CloudStack VM. The disk
is a clone of a real VM's ROOT disk, so the imported VM is actually bootable, and is
verified to boot over SSH. """
volume = self.create_bootable_storpool_volume("StorPoolImportVmTest")
cmd = unmanageVolume.unmanageVolumeCmd()
cmd.id = volume.id
self.apiclient.unmanageVolume(cmd)
networks = list_networks(self.apiclient, account=self.account.name, domainid=self.account.domainid)
self.assertTrue(isinstance(networks, list) and len(networks) > 0, "account should have a network")
clusters = list_clusters(self.apiclient, zoneid=self.primary_storage.zoneid)
self.assertTrue(isinstance(clusters, list) and len(clusters) > 0, "zone should have a cluster")
cmd = importVm.importVmCmd()
cmd.zoneid = self.primary_storage.zoneid
cmd.hypervisor = "KVM"
cmd.importsource = "shared"
cmd.clusterid = clusters[0].id
cmd.name = "StorPoolImportVm-%s" % uuid.uuid4()
cmd.serviceofferingid = self.service_offering.id
cmd.diskpath = volume.path
cmd.networkid = networks[0].id
cmd.storageid = self.primary_storage.id
cmd.account = self.account.name
cmd.domainid = self.account.domainid
imported = self.apiclient.importVm(cmd)
self.assertIsNotNone(imported, "importVm should return the imported VM")
imported_vm = VirtualMachine(imported.__dict__, self.testdata)
try:
root_volumes = Volume.list(self.apiclient, virtualmachineid=imported.id, type="ROOT", listall=True)
self.assertTrue(isinstance(root_volumes, list) and len(root_volumes) > 0,
"the imported VM should have a ROOT volume")
root_volume = root_volumes[0]
self.assertEqual(volume.path, root_volume.path,
"the imported ROOT volume should be the disk that was unmanaged")
sp_volume = self.storpool_volume(root_volume.path)
self.assertEqual("volume", sp_volume.tags.get("cs"),
"importVm should tag the StorPool volume as cs=volume")
self.assertEqual(root_volume.id, sp_volume.tags.get("uuid"),
"importVm should tag the StorPool volume with the new CloudStack volume uuid")
self.assertEqual(imported.id, sp_volume.tags.get("cvm"),
"importVm should tag the StorPool volume with the owning VM's uuid")
self.assert_vm_boots_over_ssh(imported_vm)
finally:
imported_vm.delete(self.apiclient, expunge=True)
@attr(tags=["advanced", "basic", "sg"], required_hardware=False)
def test_04_import_volume_tags_storpool_volume_with_qos_class(self):
""" importVolume should tag the StorPool volume with "qc" set to the QOS_CLASS
(SP_QOSCLASS) detail configured on the disk offering used for the import. """
volume = self.create_storpool_volume("StorPoolImportQosTest", self.disk_offering_qos.id)
cmd = unmanageVolume.unmanageVolumeCmd()
cmd.id = volume.id
self.apiclient.unmanageVolume(cmd)
cmd = importVolume.importVolumeCmd()
cmd.storageid = self.primary_storage.id
cmd.path = volume.path
cmd.diskofferingid = self.disk_offering_qos.id
imported = self.apiclient.importVolume(cmd)
self.assertIsNotNone(imported, "importVolume should return the imported volume")
self.cleanup.append(Volume(imported.__dict__))
sp_volume = self.storpool_volume(volume.path)
self.assertEqual(self.qos_tag, sp_volume.tags.get("qc"),
"importVolume should tag the StorPool volume with the disk offering's "
"QOS_CLASS (SP_QOSCLASS) detail")
@attr(tags=["advanced", "basic", "sg"], required_hardware=False)
def test_05_import_vm_from_shared_storage_on_storpool_with_qos_class(self):
""" importVm(importSource=SHARED) using a service offering linked to a disk offering
that has a QOS_CLASS (SP_QOSCLASS) detail should tag the imported ROOT volume's
StorPool volume with cs/uuid/cvm, as well as "qc" set to that QOS_CLASS value. The disk
is a clone of a real VM's ROOT disk, so the imported VM is actually bootable, and is
verified to boot over SSH. """
volume = self.create_bootable_storpool_volume("StorPoolImportVmQosTest")
cmd = unmanageVolume.unmanageVolumeCmd()
cmd.id = volume.id
self.apiclient.unmanageVolume(cmd)
networks = list_networks(self.apiclient, account=self.account.name, domainid=self.account.domainid)
self.assertTrue(isinstance(networks, list) and len(networks) > 0, "account should have a network")
clusters = list_clusters(self.apiclient, zoneid=self.primary_storage.zoneid)
self.assertTrue(isinstance(clusters, list) and len(clusters) > 0, "zone should have a cluster")
cmd = importVm.importVmCmd()
cmd.zoneid = self.primary_storage.zoneid
cmd.hypervisor = "KVM"
cmd.importsource = "shared"
cmd.clusterid = clusters[0].id
cmd.name = "StorPoolImportVmQos-%s" % uuid.uuid4()
cmd.serviceofferingid = self.service_offering_qos.id
cmd.diskpath = volume.path
cmd.networkid = networks[0].id
cmd.storageid = self.primary_storage.id
cmd.account = self.account.name
cmd.domainid = self.account.domainid
imported = self.apiclient.importVm(cmd)
self.assertIsNotNone(imported, "importVm should return the imported VM")
imported_vm = VirtualMachine(imported.__dict__, self.testdata)
try:
root_volumes = Volume.list(self.apiclient, virtualmachineid=imported.id, type="ROOT", listall=True)
self.assertTrue(isinstance(root_volumes, list) and len(root_volumes) > 0,
"the imported VM should have a ROOT volume")
root_volume = root_volumes[0]
self.assertEqual(volume.path, root_volume.path,
"the imported ROOT volume should be the disk that was unmanaged")
sp_volume = self.storpool_volume(root_volume.path)
self.assertEqual("volume", sp_volume.tags.get("cs"),
"importVm should tag the StorPool volume as cs=volume")
self.assertEqual(root_volume.id, sp_volume.tags.get("uuid"),
"importVm should tag the StorPool volume with the new CloudStack volume uuid")
self.assertEqual(imported.id, sp_volume.tags.get("cvm"),
"importVm should tag the StorPool volume with the owning VM's uuid")
self.assertEqual(self.qos_tag, sp_volume.tags.get("qc"),
"importVm should tag the StorPool volume with the linked disk offering's "
"QOS_CLASS (SP_QOSCLASS) detail")
self.assert_vm_boots_over_ssh(imported_vm)
finally:
imported_vm.delete(self.apiclient, expunge=True)
@attr(tags=["advanced", "basic", "sg"], required_hardware=False)
def test_06_list_volumes_for_import_by_path_excludes_managed_volume(self):
""" listVolumesForImport with a path should never report an already CloudStack-managed
(StorPool "cs"-tagged) volume as available for import - StorPoolPrimaryDataStoreDriver's
single-volume Describe lookup must apply the same "already managed" exclusion the
browse-all case does, unlike the internal lookup unmanageVolume() uses to find that same
volume (see test_01, which unmanages successfully, exercising the same volume while it is
still managed, the way this test's path-lookup check does). """
volume = self.create_storpool_volume("StorPoolListImportExcludesManagedTest", self.disk_offering.id)
self.cleanup.append(volume)
sp_volume = self.storpool_volume(volume.path)
self.assertEqual("volume", sp_volume.tags.get("cs"),
"A CloudStack-managed volume should carry the cs=volume StorPool tag")
cmd = listVolumesForImport.listVolumesForImportCmd()
cmd.storageid = self.primary_storage.id
cmd.path = volume.path
volumes_for_import = self.apiclient.listVolumesForImport(cmd)
matching = [v for v in (volumes_for_import or []) if v.path == volume.path]
self.assertEqual(0, len(matching),
"A still-managed volume should not be listed as available for import, "
"even when looked up by its exact path")
@attr(tags=["advanced", "basic", "sg"], required_hardware=False)
def test_07_import_volume_created_directly_on_storpool(self):
""" A volume StorPool provisioned directly - one CloudStack never created and so never
tagged cs/uuid on - should show up via listVolumesForImport and be importable, exactly
like a CloudStack-created-then-unmanaged volume. Every other test here only ever exercises
the latter (create via CloudStack, unmanage, re-import), never a genuinely external volume,
which is the realistic "import pre-existing storage" scenario this feature targets. """
sp_volume_name, path = self.create_sp_volume_directly({"createdby": "storpool-direct-test"})
imported = None
try:
sp_volume = self.storpool_volume(path)
self.assertIsNone(sp_volume.tags.get("cs"),
"A volume StorPool created directly should not carry CloudStack's cs tag")
cmd = listVolumesForImport.listVolumesForImportCmd()
cmd.storageid = self.primary_storage.id
volumes_for_import = self.apiclient.listVolumesForImport(cmd)
matching = [v for v in (volumes_for_import or []) if v.path == path]
self.assertEqual(1, len(matching),
"A volume created directly on StorPool should be listed as "
"available for import")
self.assertEqual("QCOW2", matching[0].format,
"StorPool volumes are always reported as QCOW2")
cmd = importVolume.importVolumeCmd()
cmd.storageid = self.primary_storage.id
cmd.path = path
cmd.diskofferingid = self.disk_offering.id
imported = self.apiclient.importVolume(cmd)
self.assertIsNotNone(imported, "importVolume should return the imported volume")
self.cleanup.append(Volume(imported.__dict__))
sp_volume = self.storpool_volume(path)
self.assertEqual("volume", sp_volume.tags.get("cs"),
"importVolume should tag the StorPool volume as cs=volume")
self.assertEqual(imported.id, sp_volume.tags.get("uuid"),
"importVolume should tag the StorPool volume with the new CloudStack volume uuid")
finally:
if imported is None:
self.spapi.volumeDelete(volumeName=sp_volume_name)
@attr(tags=["advanced", "basic", "sg"], required_hardware=False)
def test_08_list_volumes_for_import_excludes_externally_cs_tagged_volume(self):
""" listVolumesForImport must exclude a volume tagged cs=volume regardless of whether
CloudStack itself ever created or knew about it - the StorPool "cs" tag alone is the
source of truth for "already managed", not a CloudStack DB record (contrast test_07,
where the same kind of directly-created, CloudStack-unknown volume - but untagged - is
correctly importable). This is checked browsing all volumes, by exact path, and then by
actually attempting importVolume against the same disk - all three must reject it. """
sp_volume_name, path = self.create_sp_volume_directly({"cs": "volume"})
try:
cmd = listVolumesForImport.listVolumesForImportCmd()
cmd.storageid = self.primary_storage.id
volumes_for_import = self.apiclient.listVolumesForImport(cmd)
matching = [v for v in (volumes_for_import or []) if v.path == path]
self.assertEqual(0, len(matching),
"A volume tagged cs=volume should not be listed as available for "
"import, even though CloudStack has no record of it")
cmd = listVolumesForImport.listVolumesForImportCmd()
cmd.storageid = self.primary_storage.id
cmd.path = path
volumes_for_import = self.apiclient.listVolumesForImport(cmd)
matching = [v for v in (volumes_for_import or []) if v.path == path]
self.assertEqual(0, len(matching),
"A volume tagged cs=volume should also be excluded when looked up "
"by its exact path")
cmd = importVolume.importVolumeCmd()
cmd.storageid = self.primary_storage.id
cmd.path = path
cmd.diskofferingid = self.disk_offering.id
try:
self.apiclient.importVolume(cmd)
self.fail("importVolume should fail for a volume already tagged cs=volume")
except Exception as e:
self.debug("importVolume correctly failed for a cs-tagged volume: %s" % e)
finally:
self.spapi.volumeDelete(volumeName=sp_volume_name)
@attr(tags=["advanced", "basic", "sg"], required_hardware=False)
def test_09_unmanage_volume_clears_all_storpool_tags(self):
""" unmanageVolume should clear every StorPool tag CloudStack set - cs, uuid, qc, cvm
and vc-policy - checked in isolation from a subsequent re-import (contrast test_01,
which only asserts on cs/uuid, and only as part of an unmanage-then-reimport round
trip). The volume is attached to cls.virtual_machine (so it picks up the cvm tag) and
the VM is given a vc-policy resource tag (so the volume picks that up too) before being
detached and unmanaged. CloudStack rejects unmanageVolume on a still-attached volume, so
the volume has to be detached first anyway - the tags are checked as cleared here too in
case that invariant ever changes. """
volume = self.create_storpool_volume("StorPoolUnmanageClearsTagsTest", self.disk_offering_qos.id)
self.virtual_machine.attach_volume(self.apiclient, volume)
Tag.create(
self.apiclient,
resourceIds=self.virtual_machine.id,
resourceType='UserVm',
tags={'vc-policy': 'test'}
)
sp_volume = self.storpool_volume(volume.path)
self.assertEqual("volume", sp_volume.tags.get("cs"),
"A CloudStack-managed volume should carry the cs=volume StorPool tag")
self.assertEqual(volume.id, sp_volume.tags.get("uuid"),
"A CloudStack-managed volume should carry its CloudStack uuid as a StorPool tag")
self.assertEqual(self.qos_tag, sp_volume.tags.get("qc"),
"A CloudStack-managed volume on a QoS disk offering should carry the qc tag")
self.assertEqual(self.virtual_machine.id, sp_volume.tags.get("cvm"),
"A volume attached to a VM should carry the owning VM's uuid as the cvm tag")
self.assertEqual("test", sp_volume.tags.get("vc-policy"),
"A vc-policy tag set on the owning VM should be pushed to the StorPool volume")
try:
self.virtual_machine.detach_volume(self.apiclient, volume)
cmd = unmanageVolume.unmanageVolumeCmd()
cmd.id = volume.id
self.apiclient.unmanageVolume(cmd)
sp_volume = self.storpool_volume(volume.path)
self.assertFalse(sp_volume.tags.get("cs"), "unmanageVolume should clear the cs tag")
self.assertFalse(sp_volume.tags.get("uuid"), "unmanageVolume should clear the uuid tag")
self.assertFalse(sp_volume.tags.get("qc"), "unmanageVolume should clear the qc tag")
self.assertFalse(sp_volume.tags.get("cvm"), "unmanageVolume should clear the cvm tag")
self.assertFalse(sp_volume.tags.get("vc-policy"), "unmanageVolume should clear the vc-policy tag")
finally:
# The volume is unmanaged, so it is no longer a CloudStack-tracked resource for
# self.cleanup to delete - remove it directly on StorPool instead.
self.spapi.volumeDelete(volumeName="~" + volume.path.split("/")[3])