# 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 Local Modules
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,
                             ServiceOffering,
                             VirtualMachine,
                             VmSnapshot)
from marvin.lib.common import (get_zone,
                               get_domain,
                               get_suitable_test_template,
                               list_volumes,
                               list_snapshots,
                               list_storage_pools,
                               list_virtual_machines)
import time


class TestVmSnapshot(cloudstackTestCase):

    @classmethod
    def setUpClass(cls):
        testClient = super(TestVmSnapshot, cls).getClsTestClient()
        cls.apiclient = testClient.getApiClient()
        cls._cleanup = []
        cls.unsupportedHypervisor = False
        cls.hypervisor = testClient.getHypervisorInfo()
        if cls.hypervisor.lower() in ("hyperv", "lxc"):
            cls.unsupportedHypervisor = True
            return

        cls.services = testClient.getParsedTestDataConfig()
        # Get Zone, Domain and templates
        cls.domain = get_domain(cls.apiclient)
        cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())

        template = get_suitable_test_template(
            cls.apiclient,
            cls.zone.id,
            cls.services["ostype"],
            cls.hypervisor
        )
        if template == FAILED:
            assert False, "get_suitable_test_template() failed to return template\
                    with description %s" % cls.services["ostype"]

        cls.services["domainid"] = cls.domain.id
        cls.services["small"]["zoneid"] = cls.zone.id
        cls.services["templates"]["ostypeid"] = template.ostypeid
        cls.services["zoneid"] = cls.zone.id

        # Create VMs, NAT Rules etc
        cls.account = Account.create(
            cls.apiclient,
            cls.services["account"],
            domainid=cls.domain.id
        )
        cls._cleanup.append(cls.account)

        cls.service_offering = ServiceOffering.create(
            cls.apiclient,
            cls.services["service_offerings"]["tiny"]
        )
        cls._cleanup.append(cls.service_offering)
        cls.virtual_machine = VirtualMachine.create(
            cls.apiclient,
            cls.services["small"],
            templateid=template.id,
            accountid=cls.account.name,
            domainid=cls.account.domainid,
            serviceofferingid=cls.service_offering.id,
            mode=cls.zone.networktype
        )
        volumes = list_volumes(
            cls.apiclient,
            virtualmachineid=cls.virtual_machine.id,
            type='ROOT',
            listall=True
        )
        volume = volumes[0]
        volume_pool_response = list_storage_pools(
            cls.apiclient,
            id=volume.storageid
        )
        cls.volume_pool = volume_pool_response[0]
        cls.random_data_0 = random_gen(size=100)
        cls.test_dir = "$HOME"
        cls.random_data = "random.data"
        return

    @classmethod
    def tearDownClass(cls):
        try:
            # Cleanup resources used
            cleanup_resources(cls.apiclient, cls._cleanup)
        except Exception as e:
            raise Exception("Warning: Exception during cleanup : %s" % e)
        return

    def setUp(self):
        self.apiclient = self.testClient.getApiClient()
        self.dbclient = self.testClient.getDbConnection()

        if self.unsupportedHypervisor:
            self.skipTest("Skipping test because unsupported hypervisor\
                    %s" % self.hypervisor)
        return

    def tearDown(self):
        return

    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
    def test_01_create_vm_snapshots(self):
        """Test to create VM snapshots
        """

        try:
            # Login to VM and write data to file system
            ssh_client = self.virtual_machine.get_ssh_client()

            cmds = [
                "echo %s > %s/%s" %
                (self.random_data_0, self.test_dir, self.random_data),
                "cat %s/%s" %
                (self.test_dir, self.random_data)]

            for c in cmds:
                self.debug(c)
                result = ssh_client.execute(c)
                self.debug(result)

        except Exception:
            self.fail("SSH failed for Virtual machine: %s" %
                      self.virtual_machine.ipaddress)
        self.assertEqual(
            self.random_data_0,
            result[0],
            "Check the random data has be write into temp file!"
        )

        time.sleep(self.services["sleep"])

        #KVM VM Snapshot needs to set snapshot with memory
        MemorySnapshot = False
        if self.hypervisor.lower() in (KVM.lower()) and self.volume_pool.type.lower() != "powerflex":
           MemorySnapshot = True

        vm_snapshot = VmSnapshot.create(
            self.apiclient,
            self.virtual_machine.id,
            MemorySnapshot,
            "TestVmSnapshot",
            "Test VM Snapshot"
        )
        self.assertEqual(
            vm_snapshot.state,
            "Ready",
            "Check the snapshot of vm is ready!"
        )

        return

    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
    def test_02_revert_vm_snapshots(self):
        """Test to revert VM snapshots
        """

        try:
            ssh_client = self.virtual_machine.get_ssh_client()

            cmds = [
                "rm -rf %s/%s" % (self.test_dir, self.random_data),
                "ls %s/%s" % (self.test_dir, self.random_data)
            ]

            for c in cmds:
                self.debug(c)
                result = ssh_client.execute(c)
                self.debug(result)

        except Exception:
            self.fail("SSH failed for Virtual machine: %s" %
                      self.virtual_machine.ipaddress)

        if str(result[0]).index("No such file or directory") == -1:
            self.fail("Check the random data has be delete from temp file!")

        time.sleep(self.services["sleep"])

        list_snapshot_response = VmSnapshot.list(
            self.apiclient,
            vmid=self.virtual_machine.id,
            listall=True)

        self.assertEqual(
            isinstance(list_snapshot_response, list),
            True,
            "Check list response returns a valid list"
        )
        self.assertNotEqual(
            list_snapshot_response,
            None,
            "Check if snapshot exists in ListSnapshot"
        )

        self.assertEqual(
            list_snapshot_response[0].state,
            "Ready",
            "Check the snapshot of vm is ready!"
        )

        #We don't need to stop the VM when taking a VM Snapshot on KVM
        if self.hypervisor.lower() in (KVM.lower()) and self.volume_pool.type.lower() != "powerflex":
           pass
        else:
           self.virtual_machine.stop(self.apiclient)

        VmSnapshot.revertToSnapshot(
            self.apiclient,
            list_snapshot_response[0].id)

        #We don't need to start the VM when taking a VM Snapshot on KVM
        if self.hypervisor.lower() in (KVM.lower()) and self.volume_pool.type.lower() != "powerflex":
           pass
        else:
           self.virtual_machine.start(self.apiclient)

        try:
            ssh_client = self.virtual_machine.get_ssh_client(reconnect=True)

            cmds = [
                "cat %s/%s" % (self.test_dir, self.random_data)
            ]

            for c in cmds:
                self.debug(c)
                result = ssh_client.execute(c)
                self.debug(result)

        except Exception:
            self.fail("SSH failed for Virtual machine: %s" %
                      self.virtual_machine.ipaddress)

        self.assertEqual(
            self.random_data_0,
            result[0],
            "Check the random data is equal with the ramdom file!"
        )

    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
    def test_03_delete_vm_snapshots(self):
        """Test to delete vm snapshots
        """

        list_snapshot_response = VmSnapshot.list(
            self.apiclient,
            vmid=self.virtual_machine.id,
            listall=True)

        self.assertEqual(
            isinstance(list_snapshot_response, list),
            True,
            "Check list response returns a valid list"
        )
        self.assertNotEqual(
            list_snapshot_response,
            None,
            "Check if snapshot exists in ListSnapshot"
        )
        VmSnapshot.deleteVMSnapshot(
            self.apiclient,
            list_snapshot_response[0].id)

        time.sleep(self.services["sleep"] * 3)

        list_snapshot_response = VmSnapshot.list(
            self.apiclient,
            vmid=self.virtual_machine.id,
            listall=True)

        self.assertEqual(
            list_snapshot_response,
            None,
            "Check list vm snapshot has be deleted"
        )

class Utils:

    def __init__(self):
        self.added_service_offerings = {
            'testOffering1' : {'displaytext': 'Test Offering 1', 'cpuspeed': 600, 'cpunumber': 1, 'name': 'Test Offering 1', 'memory': 256},
            'testOffering2' : {'displaytext': 'Test Offering 2', 'cpuspeed': 600, 'cpunumber': 2, 'name': 'Test Offering 2', 'memory': 512}
        }

class TestChangeServiceOfferingForVmWithSnapshots(cloudstackTestCase):

    @classmethod
    def setUpClass(cls):
        try:
            cls._cleanup = []
            cls.testClient = super(TestChangeServiceOfferingForVmWithSnapshots, cls).getClsTestClient()
            cls.api_client = cls.testClient.getApiClient()
            cls.services = cls.testClient.getParsedTestDataConfig()
            cls.hypervisor = cls.testClient.getHypervisorInfo()
            cls.unsupportedHypervisor = False
            if cls.hypervisor.lower() in (KVM.lower(), "hyperv", "lxc"):
                cls.unsupportedHypervisor = True
                return

            cls.domain = get_domain(cls.api_client)
            cls.zone = get_zone(
                cls.api_client,
                cls.testClient.getZoneForTests()
            )
            cls.services["small"]["zoneid"] = cls.zone.id

            cls.template = get_suitable_test_template(
                cls.api_client,
                cls.zone.id,
                cls.services["ostype"],
                cls.hypervisor
            )
            if cls.template == FAILED:
                assert False, "get_suitable_test_template() failed to return template\
                    with description %s" % cls.services["ostype"]

            test_offerings = Utils().added_service_offerings
            for offering in test_offerings:
                cls.services["service_offerings"][offering] = test_offerings[offering]

            # Create 2 different service offerings
            cls.service_offering_1 = ServiceOffering.create(
                cls.api_client,
                cls.services["service_offerings"]["testOffering1"]
            )
            cls._cleanup.append(cls.service_offering_1)

            cls.service_offering_2 = ServiceOffering.create(
                cls.api_client,
                cls.services["service_offerings"]["testOffering2"]
            )
            cls._cleanup.append(cls.service_offering_2)

            cls.account = Account.create(
                cls.api_client,
                cls.services["account"],
                domainid=cls.domain.id
            )
            cls._cleanup.append(cls.account)

        except Exception as e:
            cls.tearDownClass()
            raise Exception("Warning: Exception in setup : %s" % e)
        return

    def setUp(self):
        self.apiclient = self.testClient.getApiClient()
        self.dbclient = self.testClient.getDbConnection()
        self.cleanup = []

        if self.unsupportedHypervisor:
            self.skipTest("Skipping test because unsupported hypervisor\
                    %s" % self.hypervisor)

    def tearDown(self):
        # Clean up, terminate the created resources
        cleanup_resources(self.apiclient, self.cleanup)
        return

    @classmethod
    def tearDownClass(cls):
        try:
            cleanup_resources(cls.api_client, cls._cleanup)
        except Exception as e:
            raise Exception("Warning: Exception during cleanup : %s" % e)

        return

    def wait_vm_start(self, apiclient, vmid, timeout, sleep):
        while timeout:
            vms = VirtualMachine.list(apiclient, id=vmid)
            vm_list_validation_result = validateList(vms)
            if vm_list_validation_result[0] == PASS and vm_list_validation_result[1].state == RUNNING:
                return timeout
            time.sleep(sleep)
            timeout = timeout - 1

        return timeout

    def checkCPUAndMemory(self, ssh, service_offering):
        cpuinfo = ssh.execute("cat /proc/cpuinfo")
        cpu_cnt = len([i for i in cpuinfo if "processor" in i])
        # 'cpu MHz\t\t: 2660.499'
        cpu_speed = [i for i in cpuinfo if "cpu MHz" in i][0].split()[3]
        meminfo = ssh.execute("cat /proc/meminfo")
        # MemTotal:        1017464 kB
        total_mem = [i for i in meminfo if "MemTotal" in i][0].split()[1]

        self.debug(
            "CPU count: %s, CPU Speed: %s, Mem Info: %s" % (cpu_cnt, cpu_speed, total_mem)
        )
        self.assertAlmostEqual(
            int(cpu_cnt),
            service_offering.cpunumber,
            "Check CPU Count for service offering"
        )

        range = 40
        if self.hypervisor.lower() == "hyperv":
            range = 200
        self.assertTrue(
            isAlmostEqual(int(int(total_mem) / 1024),
                          int(service_offering.memory),
                          range=range
            ),
            "Check Memory(kb) for service offering"
        )

    @attr(tags=["advanced", "smoke"], required_hardware="true")
    def test_change_service_offering_for_vm_with_snapshots(self):
        """Test to change service offering for instances with vm snapshots
        """

        # 1) Create Virtual Machine using service offering 1
        self.debug("Creating VM using Service Offering 1")
        virtual_machine = VirtualMachine.create(
            self.apiclient,
            self.services["small"],
            accountid=self.account.name,
            domainid=self.account.domainid,
            templateid=self.template.id,
            zoneid=self.zone.id,
            hypervisor=self.hypervisor,
            mode=self.zone.networktype,
            serviceofferingid=self.service_offering_1.id
        )

        # Verify Service OFfering 1 CPU cores and memory
        try:
            ssh_client = virtual_machine.get_ssh_client(reconnect=True)
            self.checkCPUAndMemory(ssh_client, self.service_offering_1)
        except Exception as e:
            self.fail("SSH failed for virtual machine: %s - %s" % (virtual_machine.ipaddress, e))

        # 2) Take VM Snapshot
        self.debug("Taking VM Snapshot for VM - ID: %s" % virtual_machine.id)
        vm_snapshot = VmSnapshot.create(
            self.apiclient,
            virtual_machine.id,
        )

        # 3) Stop Virtual Machine
        self.debug("Stopping VM - ID: %s" % virtual_machine.id)
        try:
            virtual_machine.stop(self.apiclient)

            timeout = self.services["timeout"]

            while True:
                time.sleep(self.services["sleep"])

                # Ensure that VM is in stopped state
                list_vm_response = list_virtual_machines(
                    self.apiclient,
                    id=virtual_machine.id
                )

                if isinstance(list_vm_response, list):
                    vm = list_vm_response[0]
                    if vm.state == 'Stopped':
                        self.debug("VM state: %s" % vm.state)
                        break

                if timeout == 0:
                    raise Exception(
                        "Failed to stop VM (ID: %s) in change service offering" % vm.id)

                timeout = timeout - 1
        except Exception as e:
            self.fail("Failed to stop VM: %s" % e)

        # 4) Change service offering for VM with snapshots from Service Offering 1 to Service Offering 2
        self.debug("Changing service offering from Service Offering 1 to Service Offering 2 for VM - ID: %s" % virtual_machine.id)
        virtual_machine.change_service_offering(self.apiclient, self.service_offering_2.id)

        # 5) Start VM
        self.debug("Starting VM - ID: %s" % virtual_machine.id)
        try:
            virtual_machine.start(self.apiclient)
        except Exception as e:
            self.fail("Failed to start virtual machine: %s, %s" % (virtual_machine.name, e))

        # Wait for vm to start
        timeout = self.wait_vm_start(self.apiclient, virtual_machine.id, self.services["timeout"],
                            self.services["sleep"])
        if timeout == 0:
            self.fail("The virtual machine %s failed to start even after %s minutes"
                   % (virtual_machine.name, self.services["timeout"]))

        list_vm_response = list_virtual_machines(
            self.apiclient,
            id=virtual_machine.id
        )
        self.assertEqual(
            isinstance(list_vm_response, list),
            True,
            "Check list response returns a valid list"
        )
        self.assertNotEqual(
            len(list_vm_response),
            0,
            "Check VM available in List Virtual Machines"
        )
        self.assertEqual(
            list_vm_response[0].state,
            "Running",
            "Check virtual machine is in running state"
        )
        self.assertEqual(
            list_vm_response[0].id,
            virtual_machine.id,
            "Check virtual machine id"
        )

        # 6) Verify service offering has changed
        try:
            ssh_client_2 = virtual_machine.get_ssh_client(reconnect=True)
            self.checkCPUAndMemory(ssh_client_2, self.service_offering_2)
        except Exception as e:
            self.fail("SSH failed for virtual machine: %s - %s" % (virtual_machine.ipaddress, e))

        # 7) Stop Virtual Machine
        self.debug("Stopping VM - ID: %s" % virtual_machine.id)
        try:
            virtual_machine.stop(self.apiclient)
        except Exception as e:
            self.fail("Failed to stop VM: %s" % e)

        time.sleep(30)
        # 8) Revert to VM Snapshot
        self.debug("Revert to vm snapshot: %s" % vm_snapshot.id)
        try:
            VmSnapshot.revertToSnapshot(
                self.apiclient,
                vm_snapshot.id
            )
        except Exception as e:
            self.fail("Failed to revert to VM Snapshot: %s - %s" % (vm_snapshot.id, e))

        # 9) Start VM
        self.debug("Starting VM - ID: %s" % virtual_machine.id)
        try:
            virtual_machine.start(self.apiclient)
        except Exception as e:
            self.fail("Failed to start virtual machine: %s, %s" % (virtual_machine.name, e))

        # 10) Verify service offering has changed to Service Offering 1 (from VM Snapshot)
        try:
            ssh_client_3 = virtual_machine.get_ssh_client(reconnect=True)
            self.checkCPUAndMemory(ssh_client_3, self.service_offering_1)
        except Exception as e:
            self.fail("SSH failed for virtual machine: %s - %s" % (virtual_machine.ipaddress, e))

        return
