mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
* server: fix resource count of primary storage if some volumes are Expunged but not removed Steps to reproduce the issue (1) create a vm and stop it. check resource count of primary storage (2) download volume. resource count of primary storage is not changed. (3) expunge the vm, the volume will be Expunged state as there is a volume snapshot on secondary storage. The resource count of primary storage decreased. (4) update resource count of the account (or domain), the resource count of primary storage is reset to the value in step (2). * New feature: Add support to destroy/recover volumes * Add integration test for volume destroy/recover * marvin: check resource count of more types * messages translate to JP * Update messages for CN * translate message for NL * fix two issues per Daan's comments Co-authored-by: Andrija Panic <45762285+andrijapanicsb@users.noreply.github.com>
508 lines
21 KiB
Python
508 lines
21 KiB
Python
# 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)
|