# 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,
                                  listClusters,
                                  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"
    diskOfferingEncrypted = "diskOfferingEncrypted"
    diskOfferingEncrypted2 = "diskOfferingEncrypted2"
    cephDiskOffering = "cephDiskOffering"
    nfsDiskOffering = "nfsDiskOffering"
    diskOfferingTier1Tag = "diskOfferingTier1Tag"
    diskOfferingTier2Tag = "diskOfferingTier2Tag"
    diskOfferingTier1Template = "diskOfferingTier1Template"
    diskOfferingTier2Template = "diskOfferingTier2Template"
    diskOfferingWithTagsAndTempl = "diskOfferingWithTagsAndTempl"
    diskOfferingSmall = "diskOfferingSmall"
    diskOfferingMedium = "diskOfferingMedium"
    diskOfferingLarge = "diskOfferingLarge"
    diskOfferingCustom = "diskOfferingCustom"
    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"
    serviceOfferingEncrypted = "serviceOfferingEncrypted"
    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"
    sp_template_1 = 'sp_template_1'
    sp_template_2 = 'sp_template_2'

    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.serviceOfferingEncrypted: {
                "name": "Test-encrypted",
                "displaytext": "SP Encrypted",
                "cpunumber": 1,
                "cpuspeed": 500,
                "memory": 512,
                "storagetype": "shared",
                "customizediops": False,
                "hypervisorsnapshotreserve": 200,
                "encryptroot": 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.diskOfferingEncrypted: {
                "name": "ssd-encrypted",
                "displaytext": "ssd-encrypted",
                "disksize": 5,
                "hypervisorsnapshotreserve": 200,
                "encrypt": True,
                TestData.tags: sp_template_1,
                "storagetype": "shared"
            },
            TestData.diskOfferingEncrypted2: {
                "name": "ssd2-encrypted",
                "displaytext": "ssd2-encrypted",
                "disksize": 5,
                "hypervisorsnapshotreserve": 200,
                "encrypt": True,
                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.diskOfferingTier1Template: {
                "name": "tier1-template",
                "displaytext": "Tier1 using different StorPool template",
                "custom": True,
                "hypervisorsnapshotreserve": 200,
                TestData.tags: sp_template_1,
                "storagetype": "shared"
            },
            TestData.diskOfferingTier2Template: {
                "name": "tier2-template",
                "displaytext": "Tier2 using different StorPool template",
                "custom": True,
                "hypervisorsnapshotreserve": 200,
                TestData.tags: sp_template_1,
                "storagetype": "shared"
            },
            TestData.diskOfferingTier1Tag: {
                "name": "tier1-tag",
                "displaytext": "Tier1 using QOS tags",
                "custom": True,
                "hypervisorsnapshotreserve": 200,
                TestData.tags: sp_template_1,
                "storagetype": "shared"
            },
            TestData.diskOfferingTier2Tag: {
                "name": "tier2-tag",
                "displaytext": "Tier2 using QOS tags",
                "custom": True,
                "hypervisorsnapshotreserve": 200,
                TestData.tags: sp_template_1,
                "storagetype": "shared"
            },
            TestData.diskOfferingWithTagsAndTempl: {
                "name": "tier2-tag-template",
                "displaytext": "Tier2 using QOS tags and template",
                "custom": True,
                "hypervisorsnapshotreserve": 200,
                TestData.tags: sp_template_1,
                "storagetype": "shared"
            },
            TestData.diskOfferingSmall: {
                "name": "Test-Small",
                "displaytext": "Small Disk Offering",
                "disksize" : 5,
                "hypervisorsnapshotreserve": 200,
                TestData.tags: sp_template_1,
                "storagetype": "shared"
            },
            TestData.diskOfferingMedium: {
                "name": "Test-Medium",
                "displaytext": "Medium Disk Offering",
                "disksize": 20,
                "hypervisorsnapshotreserve": 200,
                TestData.tags: sp_template_1,
                "storagetype": "shared"
            },
            TestData.diskOfferingLarge: {
                "name": "Test-Large",
                "displaytext": "Large Disk Offering",
                "disksize": 100,
                "hypervisorsnapshotreserve": 200,
                TestData.tags: sp_template_1,
                "storagetype": "shared"
            },
            TestData.diskOfferingCustom: {
                "name": "Test-Custom",
                "displaytext": "Custom Disk Offering",
                "custom": True,
                "hypervisorsnapshotreserve": 200,
                TestData.tags: sp_template_1,
                "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",
            },
            TestData.sp_template_1: {
              "tags": "ssd"
            },
            TestData.sp_template_2: {
                "tags": "ssd2"
            },
        }
class StorPoolHelper():
    def setUpClass(cls):
        cls.logger = None

    @classmethod
    def logging(cls):
        return cls.logger

    @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))

    @classmethod
    def getClustersWithStorPool(cls, apiclient, zoneId,):
        cmd = listClusters.listClustersCmd()
        cmd.zoneid = zoneId
        cmd.allocationstate = "Enabled"
        clusters = apiclient.listClusters(cmd)
        clustersToDeploy = []
        for cluster in clusters:
            if cluster.resourcedetails['sp.cluster.id']:
                clustersToDeploy.append(cluster.id)

        return clustersToDeploy

    @classmethod
    def getHostToDeployOrMigrate(cls, apiclient, hostsToavoid, clustersToDeploy):
        hostsOnCluster = []
        for c in clustersToDeploy:
            hostsOnCluster.append(cls.list_hosts_by_cluster_id(apiclient, c))

        destinationHost = None
        for host in hostsOnCluster:

            if hostsToavoid is None:
                return host[0]
            if host[0].id not in hostsToavoid:
                destinationHost = host[0]
                break

        return destinationHost

    @classmethod
    def updateStoragePoolTags(cls, apiclient, poolId, tags):
        StoragePool.update(
            apiclient,
            id=poolId,
            tags=tags
        )

    @classmethod
    def get_pool(cls, zone):
        storage_pools = zone.primaryStorages
        sp_pools = []
        for storage in storage_pools:
            if storage['provider'] and "StorPool" in storage['provider']:
                sp_pools.append(storage)

        if len(sp_pools) < 2:
            cls.debug("Cannot perform the tests because there aren't the required count of StorPool storage pools %s" % sp_pools)
            return
        return sp_pools

    @classmethod
    def create_snapshot_template(cls, apiclient, services, snapshot_id, zone_id):
        cmd = createTemplate.createTemplateCmd()
        cmd.displaytext = "TemplateFromSnap"
        name = "-".join([cmd.displaytext, random_gen()])
        cmd.name = name
        if "ostypeid" in services:
            cmd.ostypeid = services["ostypeid"]
        elif "ostype" in services:
            sub_cmd = listOsTypes.listOsTypesCmd()
            sub_cmd.description = services["ostype"]
            ostypes = apiclient.listOsTypes(sub_cmd)

            if not isinstance(ostypes, list):
                cls.fail("Unable to find Ostype id with desc: %s" %
                         services["ostype"])
            cmd.ostypeid = ostypes[0].id
        else:
            cls.fail("Unable to find Ostype is required for creating template")

        cmd.isfeatured = True
        cmd.ispublic = True
        cmd.isextractable =  False

        cmd.snapshotid = snapshot_id
        cmd.zoneid = zone_id
        apiclient.createTemplate(cmd)
        templates = Template.list(apiclient, name=name, templatefilter="self")
        if not isinstance(templates, list) and len(templates) < 0:
            cls.fail("Unable to find created template with name %s" % name)
        template = Template(templates[0].__dict__)
        return template

    @classmethod
    def verify_snapshot_copies(cls, userapiclient, snapshot_id, zone_ids):
        snapshot_entries = Snapshot.list(userapiclient, id=snapshot_id, showunique=False)
        if not isinstance(snapshot_entries, list):
            cls.fail("Unable to list snapshot for multiple zones")
        snapshots = set()
        new_list = []
        for obj in snapshot_entries:
            if obj.zoneid not in snapshots:
                new_list.append(obj)
                snapshots.add(obj.zoneid)

        if len(new_list) != len(zone_ids):
            cls.fail("Undesired list snapshot size for multiple zones")
        for zone_id in zone_ids:
            zone_found = False
            for entry in new_list:
                if entry.zoneid == zone_id:
                    zone_found = True
                    break
            if zone_found == False:
                cls.fail("Unable to find snapshot entry for the zone ID: %s" % zone_id)
