Updated Branches: refs/heads/master eae22d2ff -> 21ce3befc
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/setup/db/db/schema-410to420.sql ---------------------------------------------------------------------- diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index ce651a0..14aa2eb 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -23,7 +23,9 @@ SET foreign_key_checks = 0; ALTER TABLE `cloud`.`hypervisor_capabilities` ADD COLUMN `max_hosts_per_cluster` int unsigned DEFAULT NULL COMMENT 'Max. hosts in cluster supported by hypervisor'; +ALTER TABLE `cloud`.`hypervisor_capabilities` ADD COLUMN `storage_motion_supported` int(1) unsigned DEFAULT 0 COMMENT 'Is storage motion supported'; UPDATE `cloud`.`hypervisor_capabilities` SET `max_hosts_per_cluster`=32 WHERE `hypervisor_type`='VMware'; +INSERT IGNORE INTO `cloud`.`hypervisor_capabilities`(hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled, max_data_volumes_limit, storage_motion_supported) VALUES ('XenServer', '6.1.0', 50, 1, 13, 1); INSERT IGNORE INTO `cloud`.`hypervisor_capabilities`(hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled, max_hosts_per_cluster) VALUES ('VMware', '5.1', 128, 0, 32); DELETE FROM `cloud`.`configuration` where name='vmware.percluster.host.max'; INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'AgentManager', 'xen.nics.max', '7', 'Maximum allowed nics for Vms created on Xen'); http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/test/integration/component/test_storage_motion.py ---------------------------------------------------------------------- diff --git a/test/integration/component/test_storage_motion.py b/test/integration/component/test_storage_motion.py new file mode 100644 index 0000000..cc55a08 --- /dev/null +++ b/test/integration/component/test_storage_motion.py @@ -0,0 +1,298 @@ +# 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. +""" P1 tests for Storage motion +""" +#Import Local Modules +import marvin +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.remoteSSHClient import remoteSSHClient +from marvin.integration.lib.utils import * +from marvin.integration.lib.base import * +from marvin.integration.lib.common import * +from nose.plugins.attrib import attr +#Import System modules +import time + +_multiprocess_shared_ = True +class Services: + """Test VM Life Cycle Services + """ + + def __init__(self): + self.services = { + "disk_offering":{ + "displaytext": "Small", + "name": "Small", + "disksize": 1 + }, + "account": { + "email": "[email protected]", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended in create account to + # ensure unique username generated each time + "password": "password", + }, + "small": + # Create a small virtual machine instance with disk offering + { + "displayname": "testserver", + "username": "root", # VM creds for SSH + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "service_offerings": + { + "small": + { + # Small service offering ID to for change VM + # service offering from medium to small + "name": "Small Instance", + "displaytext": "Small Instance", + "cpunumber": 1, + "cpuspeed": 100, + "memory": 256, + } + }, + "template": { + "displaytext": "Cent OS Template", + "name": "Cent OS Template", + "passwordenabled": True, + }, + "diskdevice": '/dev/xvdd', + # Disk device where ISO is attached to instance + "mount_dir": "/mnt/tmp", + "sleep": 60, + "timeout": 10, + #Migrate VM to hostid + "ostype": 'CentOS 5.3 (64-bit)', + # CentOS 5.3 (64-bit) + } + +class TestStorageMotion(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.api_client = super(TestStorageMotion, cls).getClsTestClient().getApiClient() + cls.services = Services().services + + # Get Zone, Domain and templates + domain = get_domain(cls.api_client, cls.services) + zone = get_zone(cls.api_client, cls.services) + cls.services['mode'] = zone.networktype + + template = get_template( + cls.api_client, + zone.id, + cls.services["ostype"] + ) + # Set Zones and disk offerings + cls.services["small"]["zoneid"] = zone.id + cls.services["small"]["template"] = template.id + + # Create VMs, NAT Rules etc + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=domain.id + ) + + cls.small_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offerings"]["small"] + ) + + #create a virtual machine + cls.virtual_machine = VirtualMachine.create( + cls.api_client, + cls.services["small"], + accountid=cls.account.account.name, + domainid=cls.account.account.domainid, + serviceofferingid=cls.small_offering.id, + mode=cls.services["mode"] + ) + cls._cleanup = [ + cls.small_offering, + cls.account + ] + + @classmethod + def tearDownClass(cls): + cls.api_client = super(TestStorageMotion, cls).getClsTestClient().getApiClient() + cleanup_resources(cls.api_client, cls._cleanup) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + + def tearDown(self): + #Clean up, terminate the created ISOs + cleanup_resources(self.apiclient, self.cleanup) + return + + @attr(tags = ["advanced", "basic", "multicluster", "storagemotion", "xenserver"]) + def test_01_migrate_vm_with_volume(self): + """Test migrate virtual machine with its volumes + """ + # Validate the following + # 1. List hosts for migration of a vm. Pick a host that + # requires storage motion too. + # 2. Migrate vm to a host. + # 3. listVM command should return this VM.State of this VM + # should be "Running" and the host should be the host + # to which the VM was migrated to in a different cluster + + hosts = Host.listForMigration( + self.apiclient, + virtualmachineid=self.virtual_machine.id + ) + + self.assertEqual( + isinstance(hosts, list), + True, + "Check the number of hosts in the zone" + ) + + # Migrate to a host that requires storage motion + hosts[:] = [host for host in hosts if host.requiresStorageMotion] + + host = hosts[0] + self.debug("Migrating VM-ID: %s to Host: %s" % ( + self.virtual_machine.id, + host.id + )) + + cmd = migrateVirtualMachineWithVolume.migrateVirtualMachineWithVolumeCmd() + cmd.hostid = host.id + cmd.virtualmachineid = self.virtual_machine.id + self.apiclient.migrateVirtualMachineWithVolume(cmd) + + list_vm_response = list_virtual_machines( + self.apiclient, + id=self.virtual_machine.id + ) + self.assertEqual( + isinstance(list_vm_response, list), + True, + "Check list response returns a valid list" + ) + + self.assertNotEqual( + list_vm_response, + None, + "Check virtual machine is listVirtualMachines" + ) + + vm_response = list_vm_response[0] + + self.assertEqual( + vm_response.id, + self.virtual_machine.id, + "Check virtual machine ID of migrated VM" + ) + + self.assertEqual( + vm_response.hostid, + host.id, + "Check destination hostID of migrated VM" + ) + + self.assertEqual( + vm_response.state, + 'Running', + "Check the state of VM" + ) + return + + @attr(tags = ["advanced", "basic", "multipool", "storagemotion", "xenserver"]) + def test_02_migrate_volume(self): + """Test migrate volume of a running vm + """ + # Validate the following + # 1. List all the volumes of a vm. For each volume do step 2 to 4. + # 2. List storage pools for migrating volume of a vm. Multiple + # storage pools should be present in the cluster. + # 3. Migrate volume of the vm to another pool. + # 4. Check volume is present in the new pool and is in Ready state. + + list_volumes_response = list_volumes( + self.apiclient, + virtualmachineid=self.virtual_machine.id, + listall=True + ) + self.assertEqual( + isinstance(list_volumes_response, list), + True, + "Check list volumes response for valid list" + ) + self.assertNotEqual( + list_volumes_response, + None, + "Check if volume exists in ListVolumes" + ) + + for volume in list_volumes_response: + pools = StoragePool.listForMigration( + self.apiclient, + id=volume.id + ) + pool = pools[0] + self.debug("Migrating Volume-ID: %s to Pool: %s" % ( + volume.id, + pool.id + )) + Volume.migrate( + self.apiclient, + volumeid=volume.id, + storageid=pool.id, + livemigrate='true' + ) + migrated_volume_response = list_volumes( + self.apiclient, + id=volume.id + ) + self.assertEqual( + isinstance(migrated_volume_response, list), + True, + "Check list volumes response for valid list" + ) + self.assertNotEqual( + migrated_volume_response, + None, + "Check if volume exists in ListVolumes" + ) + migrated_volume = migrated_volume_response[0] + self.assertEqual( + migrated_volume.state, + 'Ready', + "Check migrated volume is in Ready state" + ) + self.assertEqual( + migrated_volume.storage, + pool.name, + "Check volume is on migrated pool" + ) + + return \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/tools/marvin/marvin/integration/lib/base.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/integration/lib/base.py b/tools/marvin/marvin/integration/lib/base.py index 0185c87..915d478 100755 --- a/tools/marvin/marvin/integration/lib/base.py +++ b/tools/marvin/marvin/integration/lib/base.py @@ -539,6 +539,13 @@ class Volume: [setattr(cmd, k, v) for k, v in kwargs.items()] return(apiclient.resizeVolume(cmd)) + @classmethod + def migrate(cls, apiclient, **kwargs): + """Migrate a volume""" + cmd = migrateVolume.migrateVolumeCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.migrateVolume(cmd)) + class Snapshot: """Manage Snapshot Lifecycle """ @@ -1493,6 +1500,14 @@ class Host: [setattr(cmd, k, v) for k, v in kwargs.items()] return(apiclient.listHosts(cmd)) + @classmethod + def listForMigration(cls, apiclient, **kwargs): + """List all Hosts for migration matching criteria""" + + cmd = findHostsForMigration.findHostsForMigrationCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.findHostsForMigration(cmd)) + class StoragePool: """Manage Storage pools (Primary Storage)""" @@ -1554,6 +1569,13 @@ class StoragePool: [setattr(cmd, k, v) for k, v in kwargs.items()] return(apiclient.listStoragePools(cmd)) + @classmethod + def listForMigration(cls, apiclient, **kwargs): + """List all storage pools for migration matching criteria""" + + cmd = findStoragePoolsForMigration.findStoragePoolsForMigrationCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.findStoragePoolsForMigration(cmd)) class Network: """Manage Network pools"""
