# 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.

import logging
import unittest
import random
import os
import json
import time
import math
import XenAPI
import collections
import distutils.util

logger = logging.getLogger(__name__)
logger_handler = logging.FileHandler('/var/tmp/{}.log'.format(__name__))
logger_formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
logger_handler.setFormatter(logger_formatter)
logger.addHandler(logger_handler)
logger.setLevel(logging.INFO)

# All tests inherit from cloudstackTestCase
from marvin.cloudstackTestCase import cloudstackTestCase

from nose.plugins.attrib import attr

# Import Integration Libraries

# base - contains all resources as entities and defines create, delete, list operations on them
from marvin.lib.base import (Account, DiskOffering, ServiceOffering,
                             StoragePool, User, VirtualMachine, Volume)

# common - commonly used methods for all tests are listed here
from marvin.lib.common import (get_domain, get_template, get_zone,
                               list_clusters, list_hosts,
                               list_virtual_machines,
                               list_volumes, list_disk_offering)

# utils - utility classes for common cleanup, external library wrappers, etc.
from marvin.lib.utils import cleanup_resources

from marvin.cloudstackAPI import resizeVolume

#from dfs_sdk import DateraApi
from dfs_sdk import get_api


class TestData():
    account = "account"
    capacityBytes = "capacitybytes"
    capacityIops = "capacityiops"
    clusterId = "clusterId"
    managedComputeOffering = "managedComputeoffering"
    nonManagedComputeOffering = "nonManagedComputeoffering"
    diskName = "diskname"
    diskOffering = "diskoffering"
    domainId = "domainId"
    hypervisor = "hypervisor"
    login = "login"
    mvip = "mvip"
    password = "password"
    port = "port"
    primaryStorage = "primarystorage"
    provider = "provider"
    scope = "scope"
    Datera = "datera"
    storageTag = "Datera_SAN_1"
    tags = "tags"
    templateCacheName = "centos56-x86-64-xen"  # TODO
    templateName = "templatename"
    testAccount = "testaccount"
    url = "url"
    user = "user"
    username = "username"
    virtualMachine = "virtualmachine"
    virtualMachine2 = "virtualmachine2"
    volume_1 = "volume_1"
    volume_2 = "volume_2"
    xenServer = "xenserver"
    zoneId = "zoneId"

    def __init__(self):
        self.testdata = {
            TestData.Datera: {
                TestData.mvip: "172.19.2.214",
                TestData.login: "admin",
                TestData.password: "password",
                TestData.port: 80,
                TestData.url: "https://172.19.2.214:443"
            },
            TestData.xenServer: {
                TestData.username: "root",
                TestData.password: "password"
            },
            TestData.account: {
                "email": "test@test.com",
                "firstname": "John",
                "lastname": "Doe",
                "username": "test",
                "password": "test"
            },
            TestData.testAccount: {
                "email": "test2@test2.com",
                "firstname": "Jane",
                "lastname": "Doe",
                "username": "test2",
                "password": "test"
            },
            TestData.user: {
                "email": "user@test.com",
                "firstname": "Jane",
                "lastname": "Doe",
                "username": "testuser",
                "password": "password"
            },
            TestData.primaryStorage: {
                "name": "Datera-%d" % random.randint(0, 100),
                TestData.scope: "ZONE",
                "url": "MVIP=172.19.2.214;SVIP=172.28.214.9;" +
                       "clusterAdminUsername=admin;clusterAdminPassword=password;" +
                       "clusterDefaultMinIops=10000;clusterDefaultMaxIops=15000;" +
                       "numReplicas=3;",
                TestData.provider: "Datera",
                TestData.tags: TestData.storageTag,
                TestData.capacityIops: 4500000,
                TestData.capacityBytes: 2251799813685248,
                TestData.hypervisor: "Any"
            },
            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.managedComputeOffering: {
                "name": "DT_CO_1",
                "displaytext": "DT_CO_1 (Min IOPS = 10,000; Max IOPS = 15,000)",
                "cpunumber": 1,
                "cpuspeed": 100,
                "memory": 128,
                "storagetype": "shared",
                "customizediops": False,
                "miniops": "10000",
                "maxiops": "15000",
                "hypervisorsnapshotreserve": 200,
                "tags": TestData.storageTag
            },
            TestData.nonManagedComputeOffering: {
                "name": "DT_CO_2",
                "displaytext": "DT_CO_2 (Min IOPS = 10,000; Max IOPS = 15,000)",
                "cpunumber": 1,
                "cpuspeed": 100,
                "memory": 128,
                "storagetype": "shared",
                "customizediops": False,
                "miniops": "10000",
                "maxiops": "15000",
                "hypervisorsnapshotreserve": 200,
                "tags": TestData.storageTag
            },

            TestData.diskOffering: {
                "name": "DT_DO_1",
                "displaytext": "DT_DO_1 (5GB Min IOPS = 300; Max IOPS = 500)",
                "disksize": 5,
                "customizediops": False,
                "miniops": 300,
                "maxiops": 500,
                "hypervisorsnapshotreserve": 200,
                TestData.tags: TestData.storageTag,
                "storagetype": "shared"
            },
            "testdiskofferings": {
                "customiopsdo": {
                    "name": "DT_Custom_Iops_DO",
                    "displaytext": "Customized Iops DO",
                    "disksize": 5,
                    "customizediops": True,
                    "miniops": 500,
                    "maxiops": 1000,
                    "hypervisorsnapshotreserve": 200,
                    TestData.tags: TestData.storageTag,
                    "storagetype": "shared"
                },
                "customsizedo": {
                    "name": "DT_Custom_Size_DO",
                    "displaytext": "Customized Size DO",
                    "disksize": 5,
                    "customizediops": False,
                    "miniops": 500,
                    "maxiops": 1000,
                    "hypervisorsnapshotreserve": 200,
                    TestData.tags: TestData.storageTag,
                    "storagetype": "shared"
                },
                "customsizeandiopsdo": {
                    "name": "DT_Custom_Iops_Size_DO",
                    "displaytext": "Customized Size and Iops DO",
                    "disksize": 10,
                    "customizediops": True,
                    "miniops": 400,
                    "maxiops": 800,
                    "hypervisorsnapshotreserve": 200,
                    TestData.tags: TestData.storageTag,
                    "storagetype": "shared"
                },
                "newiopsdo": {
                    "name": "DT_New_Iops_DO",
                    "displaytext": "New Iops (min=350, max = 700)",
                    "disksize": 5,
                    "miniops": 350,
                    "maxiops": 700,
                    "hypervisorsnapshotreserve": 200,
                    TestData.tags: TestData.storageTag,
                    "storagetype": "shared"
                },
                "newsizedo": {
                    "name": "DT_New_Size_DO",
                    "displaytext": "New Size: 10",
                    "disksize": 10,
                    "customizediops": False,
                    "miniops": 400,
                    "maxiops": 800,
                    "hypervisorsnapshotreserve": 200,
                    TestData.tags: TestData.storageTag,
                    "storagetype": "shared"
                },
                "newsizeandiopsdo": {
                    "name": "DT_New_Size_Iops_DO",
                    "displaytext": "New Size and Iops",
                    "disksize": 10,
                    "customizediops": False,
                    "miniops": 200,
                    "maxiops": 800,
                    "hypervisorsnapshotreserve": 200,
                    TestData.tags: TestData.storageTag,
                    "storagetype": "shared"
                }
            },
            TestData.volume_1: {
                TestData.diskName: "test-volume",
            },
            TestData.volume_2: {
                TestData.diskName: "test-volume-2",
            },
            TestData.templateName: "tiny linux kvm",  # TODO
            TestData.zoneId: 1,
            TestData.clusterId: 1,
            TestData.domainId: 1,
        }

    def update(self, overrideFileName):
        if os.path.exists(overrideFileName):
            with open(overrideFileName) as fd:
                self.testdata = self._update(self.testdata, json.loads(fd.read()))

    def _update(self, d, u):

        for k, v in u.items():
            if isinstance(v, collections.Mapping):
                r = self.update(d.get(k, {}), v)
                d[k] = r
            else:
                d[k] = u[k]
        return d


class TestVolumes(cloudstackTestCase):
    _should_only_be_one_vm_in_list_err_msg = "There should only be one VM in this list."
    _should_only_be_one_volume_in_list_err_msg = "There should only be one volume in this list."
    _volume_vm_id_and_vm_id_do_not_match_err_msg = "The volume's VM ID and the VM's ID do not match."
    _vm_not_in_running_state_err_msg = "The VM is not in the 'Running' state."
    _vm_not_in_stopped_state_err_msg = "The VM is not in the 'Stopped' state."
    _sr_not_shared_err_msg = "The SR is not shared."
    _list_should_be_empty = "The list should be empty."
    _volume_resize_err = "The Volume was not resized correctly."

    @classmethod
    def setUpXenServer(cls):

        # Set up xenAPI connection
        hosts = list_hosts(cls.apiClient, clusterid=cls.testdata[TestData.clusterId])
        xenserver_info = cls.testdata[TestData.xenServer]

        for h in hosts:
            host_ip = "https://" + h.ipaddress
            try:
                cls.xen_session = XenAPI.Session(host_ip)
                cls.xen_session.xenapi.login_with_password(xenserver_info[TestData.username],
                                                           xenserver_info[TestData.password])
                break
            except XenAPI.Failure as e:
                pass

        cls.compute_offering = ServiceOffering.create(
            cls.apiClient,
            cls.testdata[TestData.managedComputeOffering]
        )

        cls.device_name = 'xvdb'

    @classmethod
    def setUpKVM(cls):
        logger.info("Setting up KVM")
        # KVM doesn't support root disks
        cls.compute_offering = ServiceOffering.create(
            cls.apiClient,
            cls.testdata[TestData.nonManagedComputeOffering]
        )

        cls.device_name = 'vdb'

    @classmethod
    def setUpClass(cls):
        """
        1. Init ACS API and DB connection
        2. Init Datera API connection
        3. Create ACS Primary storage
        4. Create ACS compute and disk offering.
        5. Create ACS data disk without attaching to a VM
        """
        logger.info("Setting up Class")

        # Set up API client
        testclient = super(TestVolumes, cls).getClsTestClient()
        cls.apiClient = testclient.getApiClient()
        cls.dbConnection = testclient.getDbConnection()

        # Setup test data
        td = TestData()
        if cls.config.TestData and cls.config.TestData.Path:
            td.update(cls.config.TestData.Path)
        cls.testdata = td.testdata

        # Get Resources from Cloud Infrastructure
        cls.zone = get_zone(cls.apiClient, zone_name=cls.config.zones[0].name)
        cls.cluster = list_clusters(cls.apiClient)[0]
        cls.template = get_template(cls.apiClient, cls.zone.id)
        cls.domain = get_domain(cls.apiClient, cls.testdata[TestData.domainId])

        # Set up datera connection
        datera = cls.testdata[TestData.Datera]
        cls.dt_client = get_api(
            username=datera[TestData.login],
            password=datera[TestData.password],
            hostname=datera[TestData.mvip],
            version="v2"
        )

        # Create test account
        cls.account = Account.create(
            cls.apiClient,
            cls.testdata["account"],
            admin=1
        )

        # Set up connection to make customized API calls
        cls.user = User.create(
            cls.apiClient,
            cls.testdata["user"],
            account=cls.account.name,
            domainid=cls.domain.id
        )

        primarystorage = cls.testdata[TestData.primaryStorage]

        cls.primary_storage = StoragePool.create(
            cls.apiClient,
            primarystorage,
            scope=primarystorage[TestData.scope],
            zoneid=cls.zone.id,
            provider=primarystorage[TestData.provider],
            tags=primarystorage[TestData.tags],
            capacityiops=primarystorage[TestData.capacityIops],
            capacitybytes=primarystorage[TestData.capacityBytes],
            hypervisor=primarystorage[TestData.hypervisor]
        )

        cls.disk_offering = DiskOffering.create(
            cls.apiClient,
            cls.testdata[TestData.diskOffering]
        )

        cls.disk_offering_new = DiskOffering.create(
            cls.apiClient,
            cls.testdata['testdiskofferings']['newsizeandiopsdo']
        )

        cls.supports_resign = cls._get_supports_resign()

        # Set up hypervisor specific connections
        if cls.cluster.hypervisortype.lower() == 'xenserver':
            cls.setUpXenServer()
        if cls.cluster.hypervisortype.lower() == 'kvm':
            cls.setUpKVM()

        # Create 1 data volume_1
        cls.volume = Volume.create(
            cls.apiClient,
            cls.testdata[TestData.volume_1],
            account=cls.account.name,
            domainid=cls.domain.id,
            zoneid=cls.zone.id,
            diskofferingid=cls.disk_offering.id
        )

        # Resources that are to be destroyed
        cls._cleanup = [
            cls.volume,
            cls.compute_offering,
            cls.disk_offering,
            cls.disk_offering_new,
            cls.user,
            cls.account
        ]

    @classmethod
    def tearDownClass(cls):
        logger.info("Tearing Down Class")
        try:
            cleanup_resources(cls.apiClient, cls._cleanup)

            cls.primary_storage.delete(cls.apiClient)

            cls._purge_datera_volumes()

        except Exception as e:
            logging.debug("Exception in tearDownClass(cls): %s" % e)

    def setUp(self):
        logger.info("Setup test")
        self.attached = False
        self.cleanup = []

    def tearDown(self):
        logger.info("Tearing Down test")
        cleanup_resources(self.apiClient, self.cleanup)

    @classmethod
    def _set_supports_resign(cls, val):

        supports_resign = str(val).lower()
        cls.supports_resign = val

        # make sure you can connect to MySQL: https://teamtreehouse.com/community/cant-connect-remotely-to-mysql-server-with-mysql-workbench

        sql_query = "Update host_details Set value = '" + supports_resign + "' Where name = 'supportsResign'"
        cls.dbConnection.execute(sql_query)

        sql_query = "Update cluster_details Set value = '" + supports_resign + "' Where name = 'supportsResign'"
        cls.dbConnection.execute(sql_query)

    @classmethod
    def _get_supports_resign(cls):

        sql_query = "SELECT value from cluster_details Where name='supportsResign' AND cluster_id=%d" % cls.testdata[
            TestData.clusterId]

        sql_result = cls.dbConnection.execute(sql_query)
        logger.warn(sql_result)

        if len(sql_result) < 1:
            return False

        return bool(distutils.util.strtobool(sql_result[0][0].lower()))

    def _get_cs_storage_pool_db_id(self, storage_pool):
        return self._get_db_id("storage_pool", storage_pool)

    def _get_db_id(self, table, db_obj):
        sql_query = "Select id From " + table + " Where uuid = '" + str(db_obj.id) + "'"
        sql_result = self.dbConnection.execute(sql_query)
        return sql_result[0][0]

    @classmethod
    def _purge_datera_volumes(cls):
        logger.warn("Deleting all volumes")
        for ai in list(cls.dt_client.app_instances.get().values()):
            logger.warn(ai)
            if 'CS-T' in ai['name']:
                ai.set(admin_state="offline")
                ai.delete()

    def test_01_attach_new_volume_to_stopped_VM(self):

        '''Attach a volume to a stopped virtual machine, then start VM'''
        # Create VM and volume for tests
        virtual_machine = VirtualMachine.create(
            self.apiClient,
            self.testdata[TestData.virtualMachine],
            accountid=self.account.name,
            zoneid=self.zone.id,
            serviceofferingid=self.compute_offering.id,
            templateid=self.template.id,
            domainid=self.domain.id,
            startvm=True,
            mode='advanced'
        )
        self.cleanup.append(virtual_machine)

        template_volume_name = \
            self._get_app_instance_name_from_cs_volume(self.template,
                                                       vol_type='TEMPLATE')
        dt_volume = self._check_and_get_dt_volume(template_volume_name)

        virtual_machine.stop(self.apiClient, forced=True)

        new_volume = Volume.create(
            self.apiClient,
            self.testdata[TestData.volume_2],
            account=self.account.name,
            domainid=self.domain.id,
            zoneid=self.zone.id,
            diskofferingid=self.disk_offering.id
        )

        self.cleanup.append(new_volume)

        self._check_and_get_cs_volume(new_volume.id, self.testdata[TestData.volume_2][TestData.diskName])

        new_volume = virtual_machine.attach_volume(
            self.apiClient,
            new_volume
        )

        newvolume = self._check_and_get_cs_volume(new_volume.id, self.testdata[TestData.volume_2][TestData.diskName])

        virtual_machine.start(self.apiClient)

        vm = self._get_vm(virtual_machine.id)

        self.assertEqual(
            newvolume.virtualmachineid,
            vm.id,
            TestVolumes._volume_vm_id_and_vm_id_do_not_match_err_msg
        )

        self.assertEqual(
            vm.state.lower(),
            "running",
            TestVolumes._vm_not_in_running_state_err_msg
        )

        dt_volume_size = self._get_volume_size_with_hsr(newvolume)

        iqn = self._get_iqn(newvolume)

        dt_new_volname = self._get_app_instance_name_from_cs_volume(newvolume)

        dt_volume = self._check_and_get_dt_volume(dt_new_volname)

        self._check_size_and_iops(dt_volume, newvolume, dt_volume_size)

        initiator_group_name = self._get_initiator_group_name()

        self._check_initiator_group(dt_volume, initiator_group_name)

        self._check_hypervisor(iqn)
        logger.info("Detach volume from the VM")
        virtual_machine.detach_volume(
            self.apiClient,
            new_volume
        )

    def test_02_attach_detach_attach_volume(self):
        '''Attach, detach, and attach volume to a running VM'''

        # Create VM and volume for tests
        virtual_machine = VirtualMachine.create(
            self.apiClient,
            self.testdata[TestData.virtualMachine],
            accountid=self.account.name,
            zoneid=self.zone.id,
            serviceofferingid=self.compute_offering.id,
            templateid=self.template.id,
            domainid=self.domain.id,
            startvm=True,
            mode='advanced'
        )
        self.cleanup.append(virtual_machine)

        self._check_and_get_cs_volume(self.volume.id, self.testdata[TestData.volume_1][TestData.diskName])

        #######################################
        #######################################
        # STEP 1: Attach volume to running VM #
        #######################################
        #######################################
        self.volume = virtual_machine.attach_volume(
            self.apiClient,
            self.volume
        )

        self.attached = True

        vol = self._check_and_get_cs_volume(self.volume.id, self.testdata[TestData.volume_1][TestData.diskName])

        vm = self._get_vm(virtual_machine.id)

        initiator_group_name = self._get_initiator_group_name()

        self.assertEqual(
            vol.virtualmachineid,
            vm.id,
            TestVolumes._volume_vm_id_and_vm_id_do_not_match_err_msg
        )

        self.assertEqual(
            vm.state.lower(),
            'running',
            TestVolumes._vm_not_in_running_state_err_msg
        )

        iqn = self._get_iqn(self.volume)

        dt_volume_size = self._get_volume_size_with_hsr(self.volume)

        dt_volume_name = self._get_app_instance_name_from_cs_volume(self.volume)

        dt_volume = self._check_and_get_dt_volume(dt_volume_name)

        self._check_initiator_group(dt_volume, initiator_group_name)

        self._check_size_and_iops(dt_volume, vol, dt_volume_size)

        self._check_hypervisor(iqn)

        #########################################
        #########################################
        # STEP 2: Detach volume from running VM #
        #########################################
        #########################################

        self.volume = virtual_machine.detach_volume(
            self.apiClient,
            self.volume
        )

        self.attached = False

        vol = self._check_and_get_cs_volume(self.volume.id, self.testdata[TestData.volume_1][TestData.diskName])

        vm = self._get_vm(virtual_machine.id)

        self.assertEqual(
            vol.virtualmachineid,
            None,
            "The volume should not be attached to a VM."
        )

        self.assertEqual(
            vm.state.lower(),
            'running',
            str(vm.state)
        )

        dt_volume = self._check_and_get_dt_volume(dt_volume_name)

        self._check_initiator_group(dt_volume, initiator_group_name, False)

        self._check_hypervisor(iqn, False)

        #######################################
        #######################################
        # STEP 3: Attach volume to running VM #
        #######################################
        #######################################

        time.sleep(30)
        self.volume = virtual_machine.attach_volume(
            self.apiClient,
            self.volume
        )

        self.attached = True

        vol = self._check_and_get_cs_volume(self.volume.id, self.testdata[TestData.volume_1][TestData.diskName])

        vm = self._get_vm(virtual_machine.id)

        self.assertEqual(
            vol.virtualmachineid,
            vm.id,
            TestVolumes._volume_vm_id_and_vm_id_do_not_match_err_msg
        )

        self.assertEqual(
            vm.state.lower(),
            'running',
            TestVolumes._vm_not_in_running_state_err_msg
        )

        dt_volume = self._check_and_get_dt_volume(dt_volume_name)

        self._check_initiator_group(dt_volume, initiator_group_name)

        self._check_hypervisor(iqn)

    def test_03_attached_volume_reboot_VM(self):
        '''Attach volume to running VM, then reboot.'''
        # Create VM and volume for tests
        virtual_machine = VirtualMachine.create(
            self.apiClient,
            self.testdata[TestData.virtualMachine],
            accountid=self.account.name,
            zoneid=self.zone.id,
            serviceofferingid=self.compute_offering.id,
            templateid=self.template.id,
            domainid=self.domain.id,
            startvm=True,
            mode='advanced'
        )
        self.cleanup.append(virtual_machine)

        self._check_and_get_cs_volume(self.volume.id, self.testdata[TestData.volume_1][TestData.diskName])

        #######################################
        #######################################
        # STEP 1: Attach volume to running VM #
        #######################################
        #######################################

        self.volume = virtual_machine.attach_volume(
            self.apiClient,
            self.volume
        )

        self.attached = True

        dt_volume_name = self._get_app_instance_name_from_cs_volume(self.volume)

        vol = self._check_and_get_cs_volume(self.volume.id, self.testdata[TestData.volume_1][TestData.diskName])

        vm = self._get_vm(virtual_machine.id)

        initiator_group_name = self._get_initiator_group_name()

        self.assertEqual(
            vol.virtualmachineid,
            vm.id,
            TestVolumes._volume_vm_id_and_vm_id_do_not_match_err_msg
        )

        self.assertEqual(
            vm.state.lower(),
            'running',
            TestVolumes._vm_not_in_running_state_err_msg
        )

        iqn = self._get_iqn(self.volume)

        volume_size_gb = self._get_volume_size_with_hsr(self.volume)

        dt_volume = self._check_and_get_dt_volume(dt_volume_name)

        self._check_size_and_iops(dt_volume, vol, volume_size_gb)

        self._check_initiator_group(dt_volume, initiator_group_name)

        self._check_hypervisor(iqn)

        #######################################
        #######################################
        # STEP 2: Reboot VM with attached vol #
        #######################################
        #######################################
        virtual_machine.reboot(self.apiClient)

        vol = self._check_and_get_cs_volume(self.volume.id, self.testdata[TestData.volume_1][TestData.diskName])

        vm = self._get_vm(virtual_machine.id)

        iqn = self._get_iqn(self.volume)

        dt_volume_size = self._get_volume_size_with_hsr(self.volume)

        dt_volume = self._check_and_get_dt_volume(dt_volume_name)

        self._check_size_and_iops(dt_volume, vol, dt_volume_size)

        self._check_initiator_group(dt_volume, initiator_group_name)

        self._check_hypervisor(iqn)

    def _check_if_device_visible_in_vm(self, vm, dev_name):

        try:
            ssh_client = vm.get_ssh_client()
        except Exception as e:
            self.fail("SSH failed for virtual machine: %s - %s" %
                      (vm.ipaddress, e))

        cmd = "iostat | grep %s" % dev_name
        res = ssh_client.execute(cmd)
        logger.warn(cmd)
        logger.warn(res)

        if not res:
            self.fail("Device %s not found on VM: %s" % (dev_name, vm.ipaddress))

    def _check_list(self, in_list, expected_size_of_list, err_msg):
        self.assertEqual(
            isinstance(in_list, list),
            True,
            "'in_list' is not a list."
        )

        self.assertEqual(
            len(in_list),
            expected_size_of_list,
            err_msg
        )

    def _check_initiator_group(self, dt_volume, initiator_group_name, should_exist=True):

        volume_initiator_groups = dt_volume['storage_instances']['storage-1']['acl_policy']['initiator_groups']

        if should_exist:
            self.assertTrue(
                initiator_group_name in volume_initiator_groups[0],
                "Initiator group not assigned to volume"
            )

        else:

            self.assertTrue(
                len(volume_initiator_groups) == 0,
                "Initiator group still asigined to volume, should have been removed"
            )

    def _check_volume(self, volume, volume_name, disk_offering):
        self.assertTrue(
            volume.name.startswith(volume_name),
            "The volume name is incorrect."
        )

        self.assertEqual(
            volume.diskofferingid,
            disk_offering.id,
            "The disk offering is incorrect."
        )

        self.assertEqual(
            volume.zoneid,
            self.zone.id,
            "The zone is incorrect."
        )

        self.assertEqual(
            volume.storagetype,
            self.disk_offering.storagetype,
            "The storage type is incorrect."
        )

    def _check_size_and_iops(self, dt_volume, cs_volume, size):

        dt_max_total_iops = dt_volume['storage_instances']['storage-1']['volumes']['volume-1']['performance_policy'][
            'total_iops_max']
        self.assertEqual(
            dt_max_total_iops,
            cs_volume.maxiops,
            "Check QOS - Max IOPS: " + str(dt_max_total_iops)
        )

        dt_volume_size = dt_volume['storage_instances']['storage-1']['volumes']['volume-1']['size']
        self.assertEqual(
            dt_volume_size,
            size,
            "Check volume size: " + str(dt_volume_size)
        )

    def _check_and_get_cs_volume(self, volume_id, volume_name, disk_offering=None):

        if not disk_offering:
            disk_offering = self.disk_offering

        list_volumes_response = list_volumes(
            self.apiClient,
            id=volume_id
        )

        self._check_list(list_volumes_response, 1, TestVolumes._should_only_be_one_volume_in_list_err_msg)

        cs_volume = list_volumes_response[0]

        self._check_volume(cs_volume, volume_name, disk_offering)

        return cs_volume

    def _get_app_instance_name_from_cs_volume(self, cs_volume, vol_type='VOLUME'):
        """
        Get Datera app_instance name based on ACS data object types
        Eg. CS-V-test-volume-7XWJ5Q-dfc41254-371a-40b3-b410-129eb79893c0
        """
        app_inst_prefix = 'CS'

        if vol_type == 'VOLUME':
            vol_type_char = 'V'
            uuid = cs_volume.id
            name = cs_volume.name
            app_instance_name = app_inst_prefix + '-' + vol_type_char + '-' + name + '-' + uuid

        if vol_type == 'TEMPLATE':
            vol_type_char = 'T'
            uuid = cs_volume.id
            primary_storage_db_id = str(self._get_cs_storage_pool_db_id(self.primary_storage))
            app_instance_name = app_inst_prefix + '-' + vol_type_char + '-' + uuid + '-' + primary_storage_db_id

        return app_instance_name

    def _get_iqn(self, cs_volume):
        """
        Get IQN for the CS volume from Datera
        """
        app_instance_name = self._get_app_instance_name_from_cs_volume(cs_volume)
        app_instance = self.dt_client.app_instances.get(app_instance_name)
        return app_instance['storage_instances']['storage-1']['access']['iqn']

    def _get_cs_volume_size_with_hsr(self, cs_volume):

        disk_size_bytes = cs_volume.size

        disk_offering_id = cs_volume.diskofferingid

        disk_offering = list_disk_offering(self.apiClient, id=disk_offering_id)[0]

        hsr = disk_offering.hypervisorsnapshotreserve

        disk_size_with_hsr_bytes = disk_size_bytes + (disk_size_bytes * hsr) / 100

        disk_size_with_hsr_gb = int(math.ceil(disk_size_with_hsr_bytes / (1024 ** 3)))

        return disk_size_with_hsr_gb

    def _get_volume_size_with_hsr(self, cs_volume):

        app_instance_name = self._get_app_instance_name_from_cs_volume(cs_volume)
        app_instance = self.dt_client.app_instances.get(app_instance_name)

        volume_size_gb = app_instance['storage_instances']['storage-1']['volumes']['volume-1']['size']

        self.assertEqual(
            isinstance(volume_size_gb, int),
            True,
            "The volume size should be a non-zero integer."
        )

        return volume_size_gb

    def _get_initiator_group_name(self):
        init_group_prefix = 'CS-InitiatorGroup'
        initiator_group_name = init_group_prefix + '-' + self.cluster.id
        self.dt_client.initiator_groups.get(initiator_group_name)
        return initiator_group_name

    def _get_dt_volumes(self):
        return self.dt_client.app_instances.get()

    def _get_vm(self, vm_id):
        list_vms_response = list_virtual_machines(self.apiClient, id=vm_id)

        self._check_list(list_vms_response, 1, TestVolumes._should_only_be_one_vm_in_list_err_msg)

        return list_vms_response[0]

    def _check_and_get_dt_volume(self, dt_volume_name, should_exist=True):
        dt_volume = None
        dt_volumes = self._get_dt_volumes()

        for volume in list(dt_volumes.values()):
            if volume['name'] == dt_volume_name:
                dt_volume = volume
                break

        if should_exist:
            self.assertNotEqual(
                dt_volume,
                None,
                "Check if Datera volume was created: " + str(dt_volumes)
            )
        else:
            self.assertEqual(
                dt_volume,
                None,
                "Check if volume was deleted: " + str(dt_volumes)
            )

        return dt_volume

    def _resize_volume(self, volume, new_disk_offering):

        cmd = resizeVolume.resizeVolumeCmd()
        cmd.id = self.volume.id
        cmd.diskofferingid = new_disk_offering.id

        self.apiClient.resizeVolume(cmd)

        do_size_bytes = int(new_disk_offering.disksize * (1024 ** 3))
        retries = 3
        success = False

        while retries > 0:
            retries -= 1

            list_volumes_response = list_volumes(
                self.apiClient,
                id=volume.id
            )

            for vol in list_volumes_response:
                if vol.id == volume.id and \
                                int(vol.size) == do_size_bytes and \
                                vol.state == 'Ready':
                    success = True

            if success:
                break
            else:
                time.sleep(10)

        self.assertEqual(success, True, self._volume_resize_err)

    def _check_hypervisor(self, iqn, should_exist=True):
        if self.cluster.hypervisortype.lower() == 'xenserver':
            self._check_xen_sr(iqn, should_exist)
        else:
            return

    def _check_xen_sr(self, iqn, should_exist=True):

        xen_sr_name = "/" + iqn + "/0"
        if should_exist:
            xen_sr = self.xen_session.xenapi.SR.get_by_name_label(xen_sr_name)[0]

            self.sr_shared = self.xen_session.xenapi.SR.get_shared(xen_sr)

            self.assertEqual(
                self.sr_shared,
                True,
                TestVolumes._sr_not_shared_err_msg
            )
        else:
            xen_sr = self.xen_session.xenapi.SR.get_by_name_label(xen_sr_name)

            self._check_list(xen_sr, 0, TestVolumes._list_should_be_empty)

    def _check_if_device_removed_in_vm(self, vm, dev_name):

        try:
            ssh_client = vm.get_ssh_client()
        except Exception as e:
            self.fail("SSH failed for virtual machine: %s - %s" %
                      (vm.ipaddress, e))

        cmd = "iostat | grep %s" % dev_name
        res = ssh_client.execute(cmd)
        logger.warn(cmd)
        logger.warn(res)

        if res:
            self.fail("Device %s still attached on VM: %s" % (dev_name, vm.ipaddress))

    def _start_device_io(self, vm, dev_name):

        try:
            ssh_client = vm.get_ssh_client()
        except Exception as e:
            self.fail("SSH failed for virtual machine: %s - %s" %
                      (vm.ipaddress, e))

        cmd = "dd if=/dev/urandom of=/dev/%s &" % dev_name
        res = ssh_client.execute(cmd)
        logger.warn(cmd)
        logger.warn(res)

    def _stop_device_io(self, vm, dev_name):

        try:
            ssh_client = vm.get_ssh_client()
        except Exception as e:
            self.fail("SSH failed for virtual machine: %s - %s" %
                      (vm.ipaddress, e))

        cmd = "killall -9 dd"
        res = ssh_client.execute(cmd)
        logger.warn(cmd)
        logger.warn(res)

    def _get_bytes_written(self, vm, dev_name):

        try:
            ssh_client = vm.get_ssh_client()
        except Exception as e:
            self.fail("SSH failed for virtual machine: %s - %s" %
                      (vm.ipaddress, e))

        cmd = "iostat | grep %s " % dev_name
        res = ssh_client.execute(cmd)
        logger.warn(cmd)
        logger.warn(res)

        self.assertNotEqual(res, None, "Error getting iostat info")

        ret_data = ' '.join(map(str, res)).strip()
        return int(ret_data.split()[-1])
