# 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. from marvin.codes import FAILED, KVM, PASS, XEN_SERVER, RUNNING from nose.plugins.attrib import attr from marvin.cloudstackTestCase import cloudstackTestCase from marvin.lib.utils import random_gen, cleanup_resources, validateList, is_snapshot_on_nfs, isAlmostEqual from marvin.lib.base import (Account, Cluster, Configurations, ServiceOffering, Snapshot, StoragePool, Template, VirtualMachine, VmSnapshot, Volume) from marvin.lib.common import (get_zone, get_domain, get_template, list_disk_offering, list_hosts, list_snapshots, list_storage_pools, list_volumes, list_virtual_machines, list_configurations, list_service_offering, list_clusters, list_zones) from marvin.cloudstackAPI import (listOsTypes, listTemplates, listHosts, createTemplate, createVolume, getVolumeSnapshotDetails, resizeVolume, authorizeSecurityGroupIngress, migrateVirtualMachineWithVolume, destroyVirtualMachine, deployVirtualMachine, createAccount, startVirtualMachine, ) import time import pprint import random from marvin.configGenerator import configuration import uuid import logging import subprocess import json from storpool import spapi from storpool import sptypes class TestData(): account = "account" capacityBytes = "capacitybytes" capacityIops = "capacityiops" clusterId = "clusterId" diskName = "diskname" diskOffering = "diskoffering" diskOffering2 = "diskoffering2" cephDiskOffering = "cephDiskOffering" nfsDiskOffering = "nfsDiskOffering" domainId = "domainId" hypervisor = "hypervisor" login = "login" mvip = "mvip" password = "password" port = "port" primaryStorage = "primarystorage" primaryStorage2 = "primarystorage2" primaryStorage3 = "primarystorage3" primaryStorage4 = "primaryStorage4" provider = "provider" serviceOffering = "serviceOffering" serviceOfferingssd2 = "serviceOffering-ssd2" serviceOfferingsPrimary = "serviceOfferingsPrimary" serviceOfferingsIops = "serviceOfferingsIops" serviceOfferingsCeph = "serviceOfferingsCeph" scope = "scope" StorPool = "StorPool" storageTag = ["ssd", "ssd2"] tags = "tags" virtualMachine = "virtualmachine" virtualMachine2 = "virtualmachine2" volume_1 = "volume_1" volume_2 = "volume_2" volume_3 = "volume_3" volume_4 = "volume_4" volume_5 = "volume_5" volume_6 = "volume_6" volume_7 = "volume_7" zoneId = "zoneId" def __init__(self): sp_template_1 = 'ssd' sp_template_2 = 'ssd2' sp_template_3 = 'test-primary' self.testdata = { TestData.primaryStorage: { "name": sp_template_1, TestData.scope: "ZONE", "url": sp_template_1, TestData.provider: "StorPool", "path": "/dev/storpool", TestData.capacityBytes: 2251799813685248, TestData.hypervisor: "KVM" }, TestData.primaryStorage2: { "name": sp_template_2, TestData.scope: "ZONE", "url": sp_template_2, TestData.provider: "StorPool", "path": "/dev/storpool", TestData.capacityBytes: 2251799813685248, TestData.hypervisor: "KVM" }, TestData.primaryStorage3: { "name": sp_template_3, TestData.scope: "ZONE", "url": sp_template_3, TestData.provider: "StorPool", "path": "/dev/storpool", TestData.capacityBytes: 2251799813685248, TestData.hypervisor: "KVM" }, TestData.primaryStorage4: { "name": "ceph", TestData.scope: "ZONE", TestData.provider: "RBD", TestData.hypervisor: "KVM" }, TestData.virtualMachine: { "name": "TestVM", "displayname": "TestVM", "privateport": 22, "publicport": 22, "protocol": "tcp" }, TestData.virtualMachine2: { "name": "TestVM2", "displayname": "TestVM2", "privateport": 22, "publicport": 22, "protocol": "tcp" }, TestData.serviceOffering:{ "name": sp_template_1, "displaytext": "SP_CO_2 (Min IOPS = 10,000; Max IOPS = 15,000)", "cpunumber": 1, "cpuspeed": 500, "memory": 512, "storagetype": "shared", "customizediops": False, "hypervisorsnapshotreserve": 200, "tags": sp_template_1 }, TestData.serviceOfferingssd2:{ "name": sp_template_2, "displaytext": "SP_CO_2 (Min IOPS = 10,000; Max IOPS = 15,000)", "cpunumber": 1, "cpuspeed": 500, "memory": 512, "storagetype": "shared", "customizediops": False, "hypervisorsnapshotreserve": 200, "tags": sp_template_2 }, TestData.serviceOfferingsPrimary:{ "name": "nfs", "displaytext": "SP_CO_2 (Min IOPS = 10,000; Max IOPS = 15,000)", "cpunumber": 1, "cpuspeed": 500, "memory": 512, "storagetype": "shared", "customizediops": False, "hypervisorsnapshotreserve": 200, "tags": "nfs" }, TestData.serviceOfferingsCeph:{ "name": "ceph", "displaytext": "Ceph Service offerings", "cpunumber": 1, "cpuspeed": 500, "memory": 512, "storagetype": "shared", "customizediops": False, "hypervisorsnapshotreserve": 200, "tags": "ceph" }, TestData.serviceOfferingsIops:{ "name": "iops", "displaytext": "Testing IOPS on StorPool", "cpunumber": 1, "cpuspeed": 500, "memory": 512, "storagetype": "shared", "customizediops": True, "tags": sp_template_1, }, TestData.diskOffering: { "name": "SP_DO_1", "displaytext": "SP_DO_1 (5GB Min IOPS = 300; Max IOPS = 500)", "disksize": 5, "customizediops": False, "miniops": 300, "maxiops": 500, "hypervisorsnapshotreserve": 200, TestData.tags: sp_template_1, "storagetype": "shared" }, TestData.diskOffering2: { "name": "SP_DO_1", "displaytext": "SP_DO_1 (5GB Min IOPS = 300; Max IOPS = 500)", "disksize": 5, "customizediops": False, "miniops": 300, "maxiops": 500, "hypervisorsnapshotreserve": 200, TestData.tags: sp_template_2, "storagetype": "shared" }, TestData.cephDiskOffering: { "name": "ceph", "displaytext": "Ceph fixed disk offering", "disksize": 5, "customizediops": False, "miniops": 300, "maxiops": 500, "hypervisorsnapshotreserve": 200, TestData.tags: "ceph", "storagetype": "shared" }, TestData.nfsDiskOffering: { "name": "nfs", "displaytext": "NFS fixed disk offering", "disksize": 5, "customizediops": False, "miniops": 300, "maxiops": 500, "hypervisorsnapshotreserve": 200, TestData.tags: "nfs", "storagetype": "shared" }, TestData.volume_1: { TestData.diskName: "test-volume-1", }, TestData.volume_2: { TestData.diskName: "test-volume-2", }, TestData.volume_3: { TestData.diskName: "test-volume-3", }, TestData.volume_4: { TestData.diskName: "test-volume-4", }, TestData.volume_5: { TestData.diskName: "test-volume-5", }, TestData.volume_6: { TestData.diskName: "test-volume-6", }, TestData.volume_7: { TestData.diskName: "test-volume-7", }, } class StorPoolHelper(): @classmethod def create_template_from_snapshot(self, apiclient, services, snapshotid=None, volumeid=None): """Create template from Volume""" # Create template from Virtual machine and Volume ID cmd = createTemplate.createTemplateCmd() cmd.displaytext = "StorPool_Template" cmd.name = "-".join(["StorPool-", random_gen()]) if "ostypeid" in services: cmd.ostypeid = services["ostypeid"] elif "ostype" in services: # Find OSTypeId from Os type sub_cmd = listOsTypes.listOsTypesCmd() sub_cmd.description = services["ostype"] ostypes = apiclient.listOsTypes(sub_cmd) if not isinstance(ostypes, list): raise Exception( "Unable to find Ostype id with desc: %s" % services["ostype"]) cmd.ostypeid = ostypes[0].id else: raise Exception( "Unable to find Ostype is required for creating template") cmd.isfeatured = True cmd.ispublic = True cmd.isextractable = False if snapshotid: cmd.snapshotid = snapshotid if volumeid: cmd.volumeid = volumeid return Template(apiclient.createTemplate(cmd).__dict__) @classmethod def getCfgFromUrl(self, url): cfg = dict([ option.split('=') for option in url.split(';') ]) host, port = cfg['SP_API_HTTP'].split(':') auth = cfg['SP_AUTH_TOKEN'] return host, int(port), auth @classmethod def get_remote_storpool_cluster(cls): logging.debug("######################## get_remote_storpool_cluster") storpool_clusterid = subprocess.check_output(['storpool_confshow', 'CLUSTER_ID']).strip() clusterid = storpool_clusterid.split("=")[1].split(".")[1] logging.debug("######################## %s" % storpool_clusterid) cmd = ["storpool", "-j", "cluster", "list"] proc = subprocess.Popen(cmd,stdout=subprocess.PIPE).stdout.read() csl = json.loads(proc) logging.debug("######################## %s" % csl) clusters = csl.get("data").get("clusters") logging.debug("######################## %s" % clusters) for c in clusters: c_id = c.get("id") if c_id != clusterid: return c.get("name") @classmethod def get_local_cluster(cls, apiclient, zoneid): storpool_clusterid = subprocess.check_output(['storpool_confshow', 'CLUSTER_ID']) clusterid = storpool_clusterid.split("=") logging.debug(storpool_clusterid) clusters = list_clusters(apiclient, zoneid = zoneid) for c in clusters: configuration = list_configurations( apiclient, clusterid = c.id ) for conf in configuration: if conf.name == 'sp.cluster.id' and (conf.value in clusterid[1]): return c @classmethod def get_remote_cluster(cls, apiclient, zoneid): storpool_clusterid = subprocess.check_output(['storpool_confshow', 'CLUSTER_ID']) clusterid = storpool_clusterid.split("=") logging.debug(storpool_clusterid) clusters = list_clusters(apiclient, zoneid = zoneid) for c in clusters: configuration = list_configurations( apiclient, clusterid = c.id ) for conf in configuration: if conf.name == 'sp.cluster.id' and (conf.value not in clusterid[1]): return c @classmethod def get_snapshot_template_id(self, apiclient, snapshot, storage_pool_id): try: cmd = getVolumeSnapshotDetails.getVolumeSnapshotDetailsCmd() cmd.snapshotid = snapshot.id snapshot_details = apiclient.getVolumeSnapshotDetails(cmd) logging.debug("Snapshot details %s" % snapshot_details) logging.debug("Snapshot with uuid %s" % snapshot.id) for s in snapshot_details: if s["snapshotDetailsName"] == storage_pool_id: return s["snapshotDetailsValue"] except Exception as err: raise Exception(err) return None @classmethod def getDestinationHost(self, hostsToavoid, hosts): destinationHost = None for host in hosts: if host.id not in hostsToavoid: destinationHost = host break return destinationHost @classmethod def getDestinationPool(self, poolsToavoid, migrateto, pools ): """ Get destination pool which has scope same as migrateto and which is not in avoid set """ destinationPool = None # Get Storage Pool Id to migrate to for storagePool in pools: if storagePool.scope == migrateto: if storagePool.name not in poolsToavoid: destinationPool = storagePool break return destinationPool @classmethod def get_destination_pools_hosts(self, apiclient, vm, hosts): vol_list = list_volumes( apiclient, virtualmachineid=vm.id, listall=True) # Get destination host destinationHost = self.getDestinationHost(vm.hostid, hosts) return destinationHost, vol_list @classmethod def list_hosts_by_cluster_id(cls, apiclient, clusterid): """List all Hosts matching criteria""" cmd = listHosts.listHostsCmd() cmd.clusterid = clusterid return(apiclient.listHosts(cmd)) @classmethod def set_securityGroups(cls, apiclient, account, domainid, id): cmd = authorizeSecurityGroupIngress.authorizeSecurityGroupIngressCmd() cmd.protocol = 'TCP' cmd.startport = 22 cmd.endport = 22 cmd.cidrlist = '0.0.0.0/0' cmd.securitygroupid = id cmd.account = account cmd.domainid = domainid apiclient.authorizeSecurityGroupIngress(cmd) cmd.protocol = 'ICMP' cmd.icmptype = "-1" cmd.icmpcode = "-1" # Authorize to only account not CIDR cmd.securitygroupid = id cmd.account = account cmd.domainid = domainid apiclient.authorizeSecurityGroupIngress(cmd) @classmethod def migrateVm(self, apiclient, vm, destinationHost): """ This method is to migrate a VM using migrate virtual machine API """ vm.migrate( apiclient, hostid=destinationHost.id, ) vm.getState( apiclient, "Running" ) # check for the VM's host and volume's storage post migration migrated_vm_response = list_virtual_machines(apiclient, id=vm.id) assert isinstance(migrated_vm_response, list), "Check list virtual machines response for valid list" assert migrated_vm_response[0].hostid == destinationHost.id, "VM did not migrate to a specified host" return migrated_vm_response[0] @classmethod def migrateVmWithVolumes(self, apiclient, vm, destinationHost, volumes, pool): """ This method is used to migrate a vm and its volumes using migrate virtual machine with volume API INPUTS: 1. vm -> virtual machine object 2. destinationHost -> the host to which VM will be migrated 3. volumes -> list of volumes which are to be migrated 4. pools -> list of destination pools """ vol_pool_map = {vol.id: pool.id for vol in volumes} cmd = migrateVirtualMachineWithVolume.migrateVirtualMachineWithVolumeCmd() cmd.hostid = destinationHost.id cmd.migrateto = [] cmd.virtualmachineid = self.virtual_machine.id for volume, pool1 in vol_pool_map.items(): cmd.migrateto.append({ 'volume': volume, 'pool': pool1 }) apiclient.migrateVirtualMachineWithVolume(cmd) vm.getState( apiclient, "Running" ) # check for the VM's host and volume's storage post migration migrated_vm_response = list_virtual_machines(apiclient, id=vm.id) assert isinstance(migrated_vm_response, list), "Check list virtual machines response for valid list" assert migrated_vm_response[0].hostid == destinationHost.id, "VM did not migrate to a specified host" for vol in volumes: migrated_volume_response = list_volumes( apiclient, virtualmachineid=migrated_vm_response[0].id, name=vol.name, listall=True) assert isinstance(migrated_volume_response, list), "Check list virtual machines response for valid list" assert migrated_volume_response[0].storageid == pool.id, "Volume did not migrate to a specified pool" assert str(migrated_volume_response[0].state).lower().eq('ready'), "Check migrated volume is in Ready state" return migrated_vm_response[0] @classmethod def create_sp_template_and_storage_pool(self, apiclient, template_name, primary_storage, zoneid): spapiRemote = spapi.Api.fromConfig() logging.debug("================ %s" % spapiRemote) sp_api = spapi.Api.fromConfig(multiCluster= True) logging.debug("================ %s" % sp_api) remote_cluster = self.get_remote_storpool_cluster() logging.debug("================ %s" % remote_cluster) newTemplate = sptypes.VolumeTemplateCreateDesc(name = template_name, placeAll = "ssd", placeTail = "ssd", placeHead = "ssd", replication=1) template_on_remote = spapiRemote.volumeTemplateCreate(newTemplate, clusterName = remote_cluster) template_on_local = spapiRemote.volumeTemplateCreate(newTemplate) storage_pool = StoragePool.create(apiclient, primary_storage, zoneid = zoneid,) return storage_pool, spapiRemote, sp_api @classmethod def destroy_vm(self, apiclient, virtualmachineid): cmd = destroyVirtualMachine.destroyVirtualMachineCmd() cmd.id = virtualmachineid cmd.expunge = True apiclient.destroyVirtualMachine(cmd) @classmethod def check_storpool_volume_size(cls, volume, spapi): name = volume.path.split("/")[3] try: spvolume = spapi.volumeList(volumeName = "~" + name) if spvolume[0].size != volume.size: raise Exception("Storpool volume size is not the same as CloudStack db size") except spapi.ApiError as err: raise Exception(err) @classmethod def check_storpool_volume_iops(cls, spapi, volume,): name = volume.path.split("/")[3] try: spvolume = spapi.volumeList(volumeName = "~" + name) logging.debug(spvolume[0].iops) logging.debug(volume.maxiops) if spvolume[0].iops != volume.maxiops: raise Exception("Storpool volume size is not the same as CloudStack db size") except spapi.ApiError as err: raise Exception(err) @classmethod def create_custom_disk(cls, apiclient, services, size = None, miniops = None, maxiops =None, diskofferingid=None, zoneid=None, account=None, domainid=None, snapshotid=None): """Create Volume from Custom disk offering""" cmd = createVolume.createVolumeCmd() cmd.name = services["diskname"] if diskofferingid: cmd.diskofferingid = diskofferingid if size: cmd.size = size if miniops: cmd.miniops = miniops if maxiops: cmd.maxiops = maxiops if account: cmd.account = account if domainid: cmd.domainid = domainid if snapshotid: cmd.snapshotid = snapshotid cmd.zoneid = zoneid return Volume(apiclient.createVolume(cmd).__dict__) @classmethod def create_vm_custom(cls, apiclient, services, templateid=None, zoneid=None, serviceofferingid=None, method='GET', hypervisor=None, cpuNumber=None, cpuSpeed=None, memory=None, minIops=None, maxIops=None, hostid=None, rootdisksize=None, account=None, domainid=None ): """Create the instance""" cmd = deployVirtualMachine.deployVirtualMachineCmd() if serviceofferingid: cmd.serviceofferingid = serviceofferingid elif "serviceoffering" in services: cmd.serviceofferingid = services["serviceoffering"] if zoneid: cmd.zoneid = zoneid elif "zoneid" in services: cmd.zoneid = services["zoneid"] if hypervisor: cmd.hypervisor = hypervisor if hostid: cmd.hostid = hostid if "displayname" in services: cmd.displayname = services["displayname"] if "name" in services: cmd.name = services["name"] if templateid: cmd.templateid = templateid elif "template" in services: cmd.templateid = services["template"] cmd.details = [{}] if cpuNumber: cmd.details[0]["cpuNumber"] = cpuNumber if cpuSpeed: cmd.details[0]["cpuSpeed"] = cpuSpeed if memory: cmd.details[0]["memory"] = memory if minIops: cmd.details[0]["minIops"] = minIops if maxIops: cmd.details[0]["maxIops"] = maxIops if rootdisksize >= 0: cmd.details[0]["rootdisksize"] = rootdisksize if account: cmd.account = account if domainid: cmd.domainid = domainid virtual_machine = apiclient.deployVirtualMachine(cmd, method=method) return VirtualMachine(virtual_machine.__dict__, services) @classmethod def resize_volume(cls, apiclient, volume, shrinkOk=None, disk_offering =None, size=None, maxiops=None, miniops=None): cmd = resizeVolume.resizeVolumeCmd() cmd.id = volume.id if disk_offering: cmd.diskofferingid = disk_offering.id if size: cmd.size = size if maxiops: cmd.maxiops = maxiops if miniops: cmd.miniops cmd.shrinkok = shrinkOk apiclient.resizeVolume(cmd) new_size = Volume.list( apiclient, id=volume.id ) volume_size = new_size[0].size return new_size[0] @classmethod def create_account(cls, apiclient, services, accounttype=None, domainid=None, roleid=None): """Creates an account""" cmd = createAccount.createAccountCmd() # 0 - User, 1 - Root Admin, 2 - Domain Admin if accounttype: cmd.accounttype = accounttype else: cmd.accounttype = 1 cmd.email = services["email"] cmd.firstname = services["firstname"] cmd.lastname = services["lastname"] cmd.password = services["password"] username = services["username"] # Limit account username to 99 chars to avoid failure # 6 chars start string + 85 chars apiclientid + 6 chars random string + 2 chars joining hyphen string = 99 username = username[:6] apiclientid = apiclient.id[-85:] if len(apiclient.id) > 85 else apiclient.id cmd.username = "-".join([username, random_gen(id=apiclientid, size=6)]) if "accountUUID" in services: cmd.accountid = "-".join([services["accountUUID"], random_gen()]) if "userUUID" in services: cmd.userid = "-".join([services["userUUID"], random_gen()]) if domainid: cmd.domainid = domainid if roleid: cmd.roleid = roleid account = apiclient.createAccount(cmd) return Account(account.__dict__) def start(cls, apiclient, vmid, hostid): """Start the instance""" cmd = startVirtualMachine.startVirtualMachineCmd() cmd.id = vmid cmd.hostid = hostid return (apiclient.startVirtualMachine(cmd))