# 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.
""" tests for Volume improvement (Destroy/Recover) in cloudstack 4.14.0.0

"""
# Import Local Modules
from nose.plugins.attrib import attr
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.cloudstackAPI import (deleteVolume, extractVolume, recoverVolume)
from marvin.lib.utils import (validateList,
                              cleanup_resources)
from marvin.lib.base import (Resources,
                             Volume,
                             Account,
                             Domain,
                             Network,
                             NetworkOffering,
                             VirtualMachine,
                             ServiceOffering,
                             DiskOffering,
                             Zone)
from marvin.lib.common import (get_domain,
                               get_zone,
                               get_template,
                               matchResourceCount,
                               isAccountResourceCountEqualToExpectedCount)
from marvin.codes import (PASS, FAILED, RESOURCE_PRIMARY_STORAGE, RESOURCE_VOLUME)
import logging
import random
import time

class TestVolumeDestroyRecover(cloudstackTestCase):
    @classmethod
    def setUpClass(cls):
        cls.testClient = super(
            TestVolumeDestroyRecover,
            cls).getClsTestClient()
        cls.apiclient = cls.testClient.getApiClient()
        cls.services = cls.testClient.getParsedTestDataConfig()
        zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
        cls.zone = Zone(zone.__dict__)
        cls._cleanup = []

        cls.logger = logging.getLogger("TestVolumeDestroyRecover")
        cls.stream_handler = logging.StreamHandler()
        cls.logger.setLevel(logging.DEBUG)
        cls.logger.addHandler(cls.stream_handler)

        # Get Domain and templates
        cls.domain = get_domain(cls.apiclient)

        cls.template = get_template(cls.apiclient, cls.zone.id, hypervisor="KVM")
        if cls.template == FAILED:
            sys.exit(1)
        cls.templatesize = (cls.template.size / (1024 ** 3))

        cls.services['mode'] = cls.zone.networktype
        # Create Account
        cls.account = Account.create(
            cls.apiclient,
            cls.services["account"],
            admin=True,
            domainid=cls.domain.id
        )
        accounts = Account.list(cls.apiclient, id=cls.account.id)
        cls.expectedCount = int(accounts[0].primarystoragetotal)
        cls.volumeTotal = int(accounts[0].volumetotal)

        if cls.zone.securitygroupsenabled:
            cls.services["shared_network_offering"]["specifyVlan"] = 'True'
            cls.services["shared_network_offering"]["specifyIpRanges"] = 'True'

            cls.network_offering = NetworkOffering.create(
                cls.apiclient,
                cls.services["shared_network_offering"]
            )
            cls.network_offering.update(cls.apiclient, state='Enabled')

            cls.account_network = Network.create(
                cls.apiclient,
                cls.services["network2"],
                networkofferingid=cls.network_offering.id,
                zoneid=cls.zone.id,
                accountid=cls.account.name,
                domainid=cls.account.domainid
            )
        else:
            cls.network_offering = NetworkOffering.create(
                cls.apiclient,
                cls.services["isolated_network_offering"],
            )
            # Enable Network offering
            cls.network_offering.update(cls.apiclient, state='Enabled')

            # Create account network
            cls.services["network"]["zoneid"] = cls.zone.id
            cls.services["network"]["networkoffering"] = cls.network_offering.id
            cls.account_network = Network.create(
                cls.apiclient,
                cls.services["network"],
                cls.account.name,
                cls.account.domainid
            )

        # Create small service offering
        cls.service_offering = ServiceOffering.create(
            cls.apiclient,
            cls.services["service_offerings"]["small"]
        )

        # Create disk offering
        cls.disk_offering = DiskOffering.create(
            cls.apiclient,
            cls.services["disk_offering"],
        )

        cls._cleanup.append(cls.disk_offering)
        cls._cleanup.append(cls.service_offering)
        cls._cleanup.append(cls.account);
        cls._cleanup.append(cls.network_offering)

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

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

    def tearDown(self):
        try:
            cleanup_resources(self.apiclient, self.cleanup)
        except Exception as e:
            raise Exception("Warning: Exception during cleanup : %s" % e)
        return

    def verify_resource_count_primary_storage(self, expectedCount, volumeTotal):
        response = matchResourceCount(
                        self.apiclient, expectedCount,
                        RESOURCE_PRIMARY_STORAGE,
                        accountid=self.account.id)
        self.assertEqual(response[0], PASS, response[1])

        result = isAccountResourceCountEqualToExpectedCount(
            self.apiclient, self.account.domainid, self.account.name,
            expectedCount, RESOURCE_PRIMARY_STORAGE)
        self.assertFalse(result[0], result[1])
        self.assertTrue(result[2], "Resource count of primary storage does not match")

        response = matchResourceCount(
                        self.apiclient, volumeTotal,
                        RESOURCE_VOLUME,
                        accountid=self.account.id)
        self.assertEqual(response[0], PASS, response[1])

        result = isAccountResourceCountEqualToExpectedCount(
            self.apiclient, self.account.domainid, self.account.name,
            volumeTotal, RESOURCE_VOLUME)
        self.assertFalse(result[0], result[1])
        self.assertTrue(result[2], "Resource count of volume does not match")

    @attr(tags=["advanced", "advancedsg"], required_hardware="false")
    def test_01_create_vm_with_data_disk(self):
        """Create VM with DATA disk, then destroy it (expunge=False) and expunge it

            Steps:
            # 1. create vm with root disk and data disk
            # 2. destroy vm, resource count of primary storage is not changed
            # 3. expunge vm, resource count of primary storage decreased with size of root disk.
            # 4. delete volume (data disk), resource count of primary storage decreased with size of data disk
        """

        try:
            virtual_machine_1 = VirtualMachine.create(
                self.apiclient,
                self.services["virtual_machine"],
                accountid=self.account.name,
                domainid=self.account.domainid,
                serviceofferingid=self.service_offering.id,
                diskofferingid=self.disk_offering.id,
                templateid=self.template.id,
                zoneid=self.zone.id
            )
        except Exception as e:
            self.fail("Exception while deploying virtual machine: %s" % e)

        self.expectedCount = self.expectedCount + self.templatesize + self.disk_offering.disksize
        self.volumeTotal = self.volumeTotal + 2
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

        root_volumes_list = Volume.list(
            self.apiclient,
            virtualmachineid=virtual_machine_1.id,
            type='ROOT',
            listall=True
        )
        status = validateList(root_volumes_list)
        self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed")
        root_volume_id = root_volumes_list[0].id

        data_volumes_list = Volume.list(
            self.apiclient,
            virtualmachineid=virtual_machine_1.id,
            type='DATADISK',
            listall=True
        )
        status = validateList(data_volumes_list)
        self.assertEqual(status[0], PASS, "DATADISK Volume List Validation Failed")
        data_volume_id = data_volumes_list[0].id

        # destroy vm
        virtual_machine_1.delete(self.apiclient, expunge=False)
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal)

        # expunge vm
        virtual_machine_1.expunge(self.apiclient)
        self.expectedCount = self.expectedCount - self.templatesize
        self.volumeTotal = self.volumeTotal - 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal)

        # delete datadisk
        cmd = deleteVolume.deleteVolumeCmd()
        cmd.id = data_volume_id
        self.apiclient.deleteVolume(cmd)
        self.expectedCount = self.expectedCount - self.disk_offering.disksize
        self.volumeTotal = self.volumeTotal - 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal)

    @attr(tags=["advanced", "advancedsg"], required_hardware="false")
    def test_02_destroy_allocated_volume(self):
        """Create volume, destroy it when expunge=false and expunge=true

            Steps:
            # 1. create volume, resource count increases.
            # 2. destroy volume (expunge = false), Exception happened. resource count no changes
            # 3. destroy volume (expunge = True), resource count of primary storage decreased with size of volume.
        """

        # Create volume
        volume = Volume.create(
            self.apiclient, self.services["volume"],
            zoneid=self.zone.id, account=self.account.name,
            domainid=self.account.domainid, diskofferingid=self.disk_offering.id
        )
        self.expectedCount = self.expectedCount + self.disk_offering.disksize
        self.volumeTotal = self.volumeTotal + 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

        # Destroy volume (expunge=False)
        with self.assertRaises(Exception):
            volume.destroy(self.apiclient)

        # Destroy volume (expunge=True)
        volume.destroy(self.apiclient, expunge=True)

        self.expectedCount = self.expectedCount - self.disk_offering.disksize
        self.volumeTotal = self.volumeTotal - 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

    @attr(tags=["advanced", "advancedsg"], required_hardware="false")
    def test_03_destroy_detached_volume(self):
        """Create volume, attach/detach it, then destroy it when expunge=false and expunge=true

            Steps:
            # 1. create vm without data disk, resource count increases.
            # 2. create volume, resource count increases.
            # 3. attach volume to a vm. resource count  no changes.
            # 4. detach volume from a vm. resource count no changes.
            # 5. destroy volume (expunge = false), volume is Destroy.  resource count decreased with size of volume.
            # 6. destroy volume (expunge = true), volume is not found. resource count no changes.
            # 7. destroy vm (expunge=True). resource count decreased with size of root disk
        """
        # Create vm
        try:
            virtual_machine_2 = VirtualMachine.create(
                self.apiclient,
                self.services["virtual_machine"],
                accountid=self.account.name,
                domainid=self.account.domainid,
                serviceofferingid=self.service_offering.id,
                templateid=self.template.id,
                zoneid=self.zone.id
            )
        except Exception as e:
            self.fail("Exception while deploying virtual machine: %s" % e)

        self.expectedCount = self.expectedCount + self.templatesize
        self.volumeTotal = self.volumeTotal + 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

        # Create volume
        volume = Volume.create(
            self.apiclient, self.services["volume"],
            zoneid=self.zone.id, account=self.account.name,
            domainid=self.account.domainid, diskofferingid=self.disk_offering.id
        )
        self.expectedCount = self.expectedCount + self.disk_offering.disksize
        self.volumeTotal = self.volumeTotal + 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

        # Attach volume to vm
        virtual_machine_2.attach_volume(self.apiclient, volume)
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

        # Detach volume from vm
        virtual_machine_2.detach_volume(self.apiclient, volume)
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

        # Destroy volume (expunge=False)
        volume.destroy(self.apiclient)
        self.expectedCount = self.expectedCount - self.disk_offering.disksize
        self.volumeTotal = self.volumeTotal - 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

        # Destroy volume (expunge=True)
        volume.destroy(self.apiclient, expunge=True)
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

        # Destroy VM (expunge=True)
        virtual_machine_2.delete(self.apiclient, expunge=True)
        self.expectedCount = self.expectedCount - self.templatesize
        self.volumeTotal = self.volumeTotal - 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

    @attr(tags=["advanced", "advancedsg"], required_hardware="false")
    def test_04_recover_root_volume_after_restorevm(self):
        """Restore VM, recover/delete old root disk

            Steps:
            # 1. create vm without data disk, resource count increases.
            # 2. restore vm. resource count no changes. 
            # 3. check old root disk , should be Destroy state
            # 4. recover old root disk. resource count increases.
            # 5. delete old root disk . resource count decreases.
            # 6. destroy vm (expunge=True). resource count decreased with size of root disk
        """
        
        # Create vm
        try:
            virtual_machine_3 = VirtualMachine.create(
                self.apiclient,
                self.services["virtual_machine"],
                accountid=self.account.name,
                domainid=self.account.domainid,
                serviceofferingid=self.service_offering.id,
                templateid=self.template.id,
                zoneid=self.zone.id
            )
        except Exception as e:
            self.fail("Exception while deploying virtual machine: %s" % e)

        self.expectedCount = self.expectedCount + self.templatesize
        self.volumeTotal = self.volumeTotal + 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

        # Get id of root disk 
        root_volumes_list = Volume.list(
            self.apiclient,
            virtualmachineid=virtual_machine_3.id,
            type='ROOT',
            listall=True
        )
        status = validateList(root_volumes_list)
        self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed")
        root_volume_id = root_volumes_list[0].id

        # restore vm
        virtual_machine_3.restore(self.apiclient)
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

        # check old root disk state
        root_volumes_list = Volume.list(
            self.apiclient,
            id=root_volume_id,
            listall=True
        )
        status = validateList(root_volumes_list)
        self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed")
        root_volume = root_volumes_list[0]
        self.assertEqual(root_volume['state'], 'Destroy', "ROOT volume should be Destroy after restorevm")

        # recover old root disk
        cmd = recoverVolume.recoverVolumeCmd()
        cmd.id = root_volume.id
        self.apiclient.recoverVolume(cmd)
        self.expectedCount = self.expectedCount + self.templatesize
        self.volumeTotal = self.volumeTotal + 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

        # delete old root disk
        cmd = deleteVolume.deleteVolumeCmd()
        cmd.id = root_volume.id
        self.apiclient.deleteVolume(cmd)
        self.expectedCount = self.expectedCount - self.templatesize
        self.volumeTotal = self.volumeTotal - 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal)

        # Destroy VM (expunge=True)
        virtual_machine_3.delete(self.apiclient, expunge=True)
        self.expectedCount = self.expectedCount - self.templatesize
        self.volumeTotal = self.volumeTotal - 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

    @attr(tags=["advanced", "advancedsg"], required_hardware="false")
    def test_05_extract_root_volume_and_destroy_vm(self):
        """Create VM, extract root volume, then destroy vm and volume

            Steps:
            # 1. create vm without data disk, resource count increases.
            # 2. stop vm
            # 3. extract root volume
            # 4. expunge vm, root volume in Expunged state. resource count decreased with size of root disk.
            # 5. destroy volume (expunge = false), Exception happened. resource count no changes
            # 6. destroy volume (expunge = true). volume is not found. resource count no changes.
        """
        
        # Create vm
        try:
            virtual_machine_4 = VirtualMachine.create(
                self.apiclient,
                self.services["virtual_machine"],
                accountid=self.account.name,
                domainid=self.account.domainid,
                serviceofferingid=self.service_offering.id,
                templateid=self.template.id,
                zoneid=self.zone.id
            )
        except Exception as e:
            self.fail("Exception while deploying virtual machine: %s" % e)

        self.expectedCount = self.expectedCount + self.templatesize
        self.volumeTotal = self.volumeTotal + 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

        # Get id of root disk
        root_volumes_list = Volume.list(
            self.apiclient,
            virtualmachineid=virtual_machine_4.id,
            type='ROOT',
            listall=True
        )
        status = validateList(root_volumes_list)
        self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed")
        root_volume_id = root_volumes_list[0].id

        # Stop vm
        virtual_machine_4.stop(self.apiclient)

        # extract root volume
        cmd = extractVolume.extractVolumeCmd()
        cmd.id = root_volume_id
        cmd.mode = "HTTP_DOWNLOAD"
        cmd.zoneid = self.zone.id
        self.apiclient.extractVolume(cmd)

        # Destroy VM (expunge=True)
        virtual_machine_4.delete(self.apiclient, expunge=True)
        self.expectedCount = self.expectedCount - self.templatesize
        self.volumeTotal = self.volumeTotal - 1
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);

        # check root disk state
        root_volumes_list = Volume.list(
            self.apiclient,
            id=root_volume_id,
            listall=True
        )
        status = validateList(root_volumes_list)
        self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed")
        root_volume = root_volumes_list[0]
        self.assertEqual(root_volume['state'], 'Expunged', "ROOT volume should be Destroy after restorevm")

        # delete root disk
        cmd = deleteVolume.deleteVolumeCmd()
        cmd.id = root_volume.id
        self.apiclient.deleteVolume(cmd)
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal)

    @attr(tags=["advanced", "advancedsg"], required_hardware="false")
    def test_06_delete_network(self):
        """Delete account network, resource count should not be changed

            Steps:
            # 1. Delete account network
            # 2. resource count should not be changed
        """
        self.account_network.delete(self.apiclient)
        self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal)
