| # 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. |
| """ tests for Volume improvement (Destroy/Recover) in cloudstack 4.14.0.0 |
| |
| """ |
| # Import Local Modules |
| from nose.plugins.attrib import attr |
| from marvin.cloudstackTestCase import cloudstackTestCase |
| from marvin.cloudstackAPI import (deleteVolume, extractVolume, recoverVolume) |
| from marvin.lib.utils import (validateList, |
| cleanup_resources) |
| from marvin.lib.base import (Resources, |
| Volume, |
| Account, |
| Domain, |
| Network, |
| NetworkOffering, |
| VirtualMachine, |
| ServiceOffering, |
| DiskOffering, |
| Zone) |
| from marvin.lib.common import (get_domain, |
| get_zone, |
| get_template, |
| matchResourceCount, |
| isAccountResourceCountEqualToExpectedCount) |
| from marvin.codes import (PASS, FAILED, RESOURCE_PRIMARY_STORAGE, RESOURCE_VOLUME) |
| import logging |
| import random |
| import time |
| |
| class TestVolumeDestroyRecover(cloudstackTestCase): |
| @classmethod |
| def setUpClass(cls): |
| cls.testClient = super( |
| TestVolumeDestroyRecover, |
| cls).getClsTestClient() |
| cls.apiclient = cls.testClient.getApiClient() |
| cls.services = cls.testClient.getParsedTestDataConfig() |
| zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) |
| cls.zone = Zone(zone.__dict__) |
| cls._cleanup = [] |
| |
| cls.logger = logging.getLogger("TestVolumeDestroyRecover") |
| cls.stream_handler = logging.StreamHandler() |
| cls.logger.setLevel(logging.DEBUG) |
| cls.logger.addHandler(cls.stream_handler) |
| |
| # Get Domain and templates |
| cls.domain = get_domain(cls.apiclient) |
| |
| cls.template = get_template(cls.apiclient, cls.zone.id, hypervisor="KVM") |
| if cls.template == FAILED: |
| sys.exit(1) |
| cls.templatesize = (cls.template.size / (1024 ** 3)) |
| |
| cls.services['mode'] = cls.zone.networktype |
| # Create Account |
| cls.account = Account.create( |
| cls.apiclient, |
| cls.services["account"], |
| admin=True, |
| domainid=cls.domain.id |
| ) |
| accounts = Account.list(cls.apiclient, id=cls.account.id) |
| cls.expectedCount = int(accounts[0].primarystoragetotal) |
| cls.volumeTotal = int(accounts[0].volumetotal) |
| |
| if cls.zone.securitygroupsenabled: |
| cls.services["shared_network_offering"]["specifyVlan"] = 'True' |
| cls.services["shared_network_offering"]["specifyIpRanges"] = 'True' |
| |
| cls.network_offering = NetworkOffering.create( |
| cls.apiclient, |
| cls.services["shared_network_offering"] |
| ) |
| cls.network_offering.update(cls.apiclient, state='Enabled') |
| |
| cls.account_network = Network.create( |
| cls.apiclient, |
| cls.services["network2"], |
| networkofferingid=cls.network_offering.id, |
| zoneid=cls.zone.id, |
| accountid=cls.account.name, |
| domainid=cls.account.domainid |
| ) |
| else: |
| cls.network_offering = NetworkOffering.create( |
| cls.apiclient, |
| cls.services["isolated_network_offering"], |
| ) |
| # Enable Network offering |
| cls.network_offering.update(cls.apiclient, state='Enabled') |
| |
| # Create account network |
| cls.services["network"]["zoneid"] = cls.zone.id |
| cls.services["network"]["networkoffering"] = cls.network_offering.id |
| cls.account_network = Network.create( |
| cls.apiclient, |
| cls.services["network"], |
| cls.account.name, |
| cls.account.domainid |
| ) |
| |
| # Create small service offering |
| cls.service_offering = ServiceOffering.create( |
| cls.apiclient, |
| cls.services["service_offerings"]["small"] |
| ) |
| |
| # Create disk offering |
| cls.disk_offering = DiskOffering.create( |
| cls.apiclient, |
| cls.services["disk_offering"], |
| ) |
| |
| cls._cleanup.append(cls.disk_offering) |
| cls._cleanup.append(cls.service_offering) |
| cls._cleanup.append(cls.account); |
| cls._cleanup.append(cls.network_offering) |
| |
| @classmethod |
| def tearDownClass(self): |
| try: |
| cleanup_resources(self.apiclient, self._cleanup) |
| except Exception as e: |
| raise Exception("Warning: Exception during cleanup : %s" % e) |
| return |
| |
| def setUp(self): |
| self.apiclient = self.testClient.getApiClient() |
| self.cleanup = [] |
| return |
| |
| def tearDown(self): |
| try: |
| cleanup_resources(self.apiclient, self.cleanup) |
| except Exception as e: |
| raise Exception("Warning: Exception during cleanup : %s" % e) |
| return |
| |
| def verify_resource_count_primary_storage(self, expectedCount, volumeTotal): |
| response = matchResourceCount( |
| self.apiclient, expectedCount, |
| RESOURCE_PRIMARY_STORAGE, |
| accountid=self.account.id) |
| self.assertEqual(response[0], PASS, response[1]) |
| |
| result = isAccountResourceCountEqualToExpectedCount( |
| self.apiclient, self.account.domainid, self.account.name, |
| expectedCount, RESOURCE_PRIMARY_STORAGE) |
| self.assertFalse(result[0], result[1]) |
| self.assertTrue(result[2], "Resource count of primary storage does not match") |
| |
| response = matchResourceCount( |
| self.apiclient, volumeTotal, |
| RESOURCE_VOLUME, |
| accountid=self.account.id) |
| self.assertEqual(response[0], PASS, response[1]) |
| |
| result = isAccountResourceCountEqualToExpectedCount( |
| self.apiclient, self.account.domainid, self.account.name, |
| volumeTotal, RESOURCE_VOLUME) |
| self.assertFalse(result[0], result[1]) |
| self.assertTrue(result[2], "Resource count of volume does not match") |
| |
| @attr(tags=["advanced", "advancedsg"], required_hardware="false") |
| def test_01_create_vm_with_data_disk(self): |
| """Create VM with DATA disk, then destroy it (expunge=False) and expunge it |
| |
| Steps: |
| # 1. create vm with root disk and data disk |
| # 2. destroy vm, resource count of primary storage is not changed |
| # 3. expunge vm, resource count of primary storage decreased with size of root disk. |
| # 4. delete volume (data disk), resource count of primary storage decreased with size of data disk |
| """ |
| |
| try: |
| virtual_machine_1 = VirtualMachine.create( |
| self.apiclient, |
| self.services["virtual_machine"], |
| accountid=self.account.name, |
| domainid=self.account.domainid, |
| serviceofferingid=self.service_offering.id, |
| diskofferingid=self.disk_offering.id, |
| templateid=self.template.id, |
| zoneid=self.zone.id |
| ) |
| except Exception as e: |
| self.fail("Exception while deploying virtual machine: %s" % e) |
| |
| self.expectedCount = self.expectedCount + self.templatesize + self.disk_offering.disksize |
| self.volumeTotal = self.volumeTotal + 2 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| root_volumes_list = Volume.list( |
| self.apiclient, |
| virtualmachineid=virtual_machine_1.id, |
| type='ROOT', |
| listall=True |
| ) |
| status = validateList(root_volumes_list) |
| self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed") |
| root_volume_id = root_volumes_list[0].id |
| |
| data_volumes_list = Volume.list( |
| self.apiclient, |
| virtualmachineid=virtual_machine_1.id, |
| type='DATADISK', |
| listall=True |
| ) |
| status = validateList(data_volumes_list) |
| self.assertEqual(status[0], PASS, "DATADISK Volume List Validation Failed") |
| data_volume_id = data_volumes_list[0].id |
| |
| # destroy vm |
| virtual_machine_1.delete(self.apiclient, expunge=False) |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal) |
| |
| # expunge vm |
| virtual_machine_1.expunge(self.apiclient) |
| self.expectedCount = self.expectedCount - self.templatesize |
| self.volumeTotal = self.volumeTotal - 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal) |
| |
| # delete datadisk |
| cmd = deleteVolume.deleteVolumeCmd() |
| cmd.id = data_volume_id |
| self.apiclient.deleteVolume(cmd) |
| self.expectedCount = self.expectedCount - self.disk_offering.disksize |
| self.volumeTotal = self.volumeTotal - 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal) |
| |
| @attr(tags=["advanced", "advancedsg"], required_hardware="false") |
| def test_02_destroy_allocated_volume(self): |
| """Create volume, destroy it when expunge=false and expunge=true |
| |
| Steps: |
| # 1. create volume, resource count increases. |
| # 2. destroy volume (expunge = false), Exception happened. resource count no changes |
| # 3. destroy volume (expunge = True), resource count of primary storage decreased with size of volume. |
| """ |
| |
| # Create volume |
| volume = Volume.create( |
| self.apiclient, self.services["volume"], |
| zoneid=self.zone.id, account=self.account.name, |
| domainid=self.account.domainid, diskofferingid=self.disk_offering.id |
| ) |
| self.expectedCount = self.expectedCount + self.disk_offering.disksize |
| self.volumeTotal = self.volumeTotal + 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| # Destroy volume (expunge=False) |
| with self.assertRaises(Exception): |
| volume.destroy(self.apiclient) |
| |
| # Destroy volume (expunge=True) |
| volume.destroy(self.apiclient, expunge=True) |
| |
| self.expectedCount = self.expectedCount - self.disk_offering.disksize |
| self.volumeTotal = self.volumeTotal - 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| @attr(tags=["advanced", "advancedsg"], required_hardware="false") |
| def test_03_destroy_detached_volume(self): |
| """Create volume, attach/detach it, then destroy it when expunge=false and expunge=true |
| |
| Steps: |
| # 1. create vm without data disk, resource count increases. |
| # 2. create volume, resource count increases. |
| # 3. attach volume to a vm. resource count no changes. |
| # 4. detach volume from a vm. resource count no changes. |
| # 5. destroy volume (expunge = false), volume is Destroy. resource count decreased with size of volume. |
| # 6. destroy volume (expunge = true), volume is not found. resource count no changes. |
| # 7. destroy vm (expunge=True). resource count decreased with size of root disk |
| """ |
| # Create vm |
| try: |
| virtual_machine_2 = VirtualMachine.create( |
| self.apiclient, |
| self.services["virtual_machine"], |
| accountid=self.account.name, |
| domainid=self.account.domainid, |
| serviceofferingid=self.service_offering.id, |
| templateid=self.template.id, |
| zoneid=self.zone.id |
| ) |
| except Exception as e: |
| self.fail("Exception while deploying virtual machine: %s" % e) |
| |
| self.expectedCount = self.expectedCount + self.templatesize |
| self.volumeTotal = self.volumeTotal + 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| # Create volume |
| volume = Volume.create( |
| self.apiclient, self.services["volume"], |
| zoneid=self.zone.id, account=self.account.name, |
| domainid=self.account.domainid, diskofferingid=self.disk_offering.id |
| ) |
| self.expectedCount = self.expectedCount + self.disk_offering.disksize |
| self.volumeTotal = self.volumeTotal + 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| # Attach volume to vm |
| virtual_machine_2.attach_volume(self.apiclient, volume) |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| # Detach volume from vm |
| virtual_machine_2.detach_volume(self.apiclient, volume) |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| # Destroy volume (expunge=False) |
| volume.destroy(self.apiclient) |
| self.expectedCount = self.expectedCount - self.disk_offering.disksize |
| self.volumeTotal = self.volumeTotal - 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| # Destroy volume (expunge=True) |
| volume.destroy(self.apiclient, expunge=True) |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| # Destroy VM (expunge=True) |
| virtual_machine_2.delete(self.apiclient, expunge=True) |
| self.expectedCount = self.expectedCount - self.templatesize |
| self.volumeTotal = self.volumeTotal - 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| @attr(tags=["advanced", "advancedsg"], required_hardware="false") |
| def test_04_recover_root_volume_after_restorevm(self): |
| """Restore VM, recover/delete old root disk |
| |
| Steps: |
| # 1. create vm without data disk, resource count increases. |
| # 2. restore vm. resource count no changes. |
| # 3. check old root disk , should be Destroy state |
| # 4. recover old root disk. resource count increases. |
| # 5. delete old root disk . resource count decreases. |
| # 6. destroy vm (expunge=True). resource count decreased with size of root disk |
| """ |
| |
| # Create vm |
| try: |
| virtual_machine_3 = VirtualMachine.create( |
| self.apiclient, |
| self.services["virtual_machine"], |
| accountid=self.account.name, |
| domainid=self.account.domainid, |
| serviceofferingid=self.service_offering.id, |
| templateid=self.template.id, |
| zoneid=self.zone.id |
| ) |
| except Exception as e: |
| self.fail("Exception while deploying virtual machine: %s" % e) |
| |
| self.expectedCount = self.expectedCount + self.templatesize |
| self.volumeTotal = self.volumeTotal + 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| # Get id of root disk |
| root_volumes_list = Volume.list( |
| self.apiclient, |
| virtualmachineid=virtual_machine_3.id, |
| type='ROOT', |
| listall=True |
| ) |
| status = validateList(root_volumes_list) |
| self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed") |
| root_volume_id = root_volumes_list[0].id |
| |
| # restore vm |
| virtual_machine_3.restore(self.apiclient) |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| # check old root disk state |
| root_volumes_list = Volume.list( |
| self.apiclient, |
| id=root_volume_id, |
| listall=True |
| ) |
| status = validateList(root_volumes_list) |
| self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed") |
| root_volume = root_volumes_list[0] |
| self.assertEqual(root_volume['state'], 'Destroy', "ROOT volume should be Destroy after restorevm") |
| |
| # recover old root disk |
| cmd = recoverVolume.recoverVolumeCmd() |
| cmd.id = root_volume.id |
| self.apiclient.recoverVolume(cmd) |
| self.expectedCount = self.expectedCount + self.templatesize |
| self.volumeTotal = self.volumeTotal + 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| # delete old root disk |
| cmd = deleteVolume.deleteVolumeCmd() |
| cmd.id = root_volume.id |
| self.apiclient.deleteVolume(cmd) |
| self.expectedCount = self.expectedCount - self.templatesize |
| self.volumeTotal = self.volumeTotal - 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal) |
| |
| # Destroy VM (expunge=True) |
| virtual_machine_3.delete(self.apiclient, expunge=True) |
| self.expectedCount = self.expectedCount - self.templatesize |
| self.volumeTotal = self.volumeTotal - 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| @attr(tags=["advanced", "advancedsg"], required_hardware="false") |
| def test_05_extract_root_volume_and_destroy_vm(self): |
| """Create VM, extract root volume, then destroy vm and volume |
| |
| Steps: |
| # 1. create vm without data disk, resource count increases. |
| # 2. stop vm |
| # 3. extract root volume |
| # 4. expunge vm, root volume in Expunged state. resource count decreased with size of root disk. |
| # 5. destroy volume (expunge = false), Exception happened. resource count no changes |
| # 6. destroy volume (expunge = true). volume is not found. resource count no changes. |
| """ |
| |
| # Create vm |
| try: |
| virtual_machine_4 = VirtualMachine.create( |
| self.apiclient, |
| self.services["virtual_machine"], |
| accountid=self.account.name, |
| domainid=self.account.domainid, |
| serviceofferingid=self.service_offering.id, |
| templateid=self.template.id, |
| zoneid=self.zone.id |
| ) |
| except Exception as e: |
| self.fail("Exception while deploying virtual machine: %s" % e) |
| |
| self.expectedCount = self.expectedCount + self.templatesize |
| self.volumeTotal = self.volumeTotal + 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| # Get id of root disk |
| root_volumes_list = Volume.list( |
| self.apiclient, |
| virtualmachineid=virtual_machine_4.id, |
| type='ROOT', |
| listall=True |
| ) |
| status = validateList(root_volumes_list) |
| self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed") |
| root_volume_id = root_volumes_list[0].id |
| |
| # Stop vm |
| virtual_machine_4.stop(self.apiclient) |
| |
| # extract root volume |
| cmd = extractVolume.extractVolumeCmd() |
| cmd.id = root_volume_id |
| cmd.mode = "HTTP_DOWNLOAD" |
| cmd.zoneid = self.zone.id |
| self.apiclient.extractVolume(cmd) |
| |
| # Destroy VM (expunge=True) |
| virtual_machine_4.delete(self.apiclient, expunge=True) |
| self.expectedCount = self.expectedCount - self.templatesize |
| self.volumeTotal = self.volumeTotal - 1 |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal); |
| |
| # check root disk state |
| root_volumes_list = Volume.list( |
| self.apiclient, |
| id=root_volume_id, |
| listall=True |
| ) |
| status = validateList(root_volumes_list) |
| self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed") |
| root_volume = root_volumes_list[0] |
| self.assertEqual(root_volume['state'], 'Expunged', "ROOT volume should be Destroy after restorevm") |
| |
| # delete root disk |
| cmd = deleteVolume.deleteVolumeCmd() |
| cmd.id = root_volume.id |
| self.apiclient.deleteVolume(cmd) |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal) |
| |
| @attr(tags=["advanced", "advancedsg"], required_hardware="false") |
| def test_06_delete_network(self): |
| """Delete account network, resource count should not be changed |
| |
| Steps: |
| # 1. Delete account network |
| # 2. resource count should not be changed |
| """ |
| self.account_network.delete(self.apiclient) |
| self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal) |