# 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.
""" Test cases for validating global limit for concurrent snapshots
"""
from nose.plugins.attrib import attr
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.lib.utils import (cleanup_resources,
                              validateList)
from marvin.lib.base import (Account,
                             ServiceOffering,
                             VirtualMachine,
                             Snapshot,
                             Volume,
                             Configurations
                             )
from marvin.lib.common import (get_domain,
                               get_zone,
                               get_template
                               )

from marvin.codes import PASS, BACKED_UP
from threading import Thread


class TestConcurrentSnapshotLimit(cloudstackTestCase):

    @classmethod
    def setUpClass(cls):
        testClient = super(TestConcurrentSnapshotLimit, cls).getClsTestClient()
        cls.apiclient = testClient.getApiClient()
        cls.testdata = testClient.getParsedTestDataConfig()
        cls.hypervisor = cls.testClient.getHypervisorInfo()

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

        cls.template = get_template(
            cls.apiclient,
            cls.zone.id,
            cls.testdata["ostype"])

        cls._cleanup = []
        cls.supportedHypervisor = True

        if cls.hypervisor.lower() in [
            "hyperv",
            "lxc"]:
            cls.supportedHypervisor = False
            return

        # Create Service offering
        cls.service_offering = ServiceOffering.create(
            cls.apiclient,
            cls.testdata["service_offering"],
        )
        cls._cleanup.append(cls.service_offering)
        return

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

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

        self.exceptionOccurred = False

        if not self.supportedHypervisor:
            self.skipTest("Snapshot not supported on %s" % self.hypervisor)

    def createSnapshot(self, volumeid):
        try:
            Snapshot.create(
                self.apiclient,
                volumeid
            )
        except Exception as e:
            self.debug("Exception occurred: %s" % e)
            self.exceptionOccurred = True

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

    @attr(tags=["advanced", "basic"], required_hardware="true")
    def test_01_concurrent_snapshot_global_limit(self):
        """ Test if global value concurrent.snapshots.threshold.perhost
            value respected
            This is positive test cases and tests if we are able to create
            as many snapshots mentioned in global value
        # 1. Create an account and a VM in it
        # 2. Read the global value for concurrent.snapshots.threshold.perhost
        # 3. If the value is Null, create at least 10 concurrent snapshots
             and verify they are created successfully
        # 4. Else, create as many snapshots specified in the global value, and
             verify they are created successfully
        """

        # Create an account
        account = Account.create(
            self.apiclient,
            self.testdata["account"],
            domainid=self.domain.id
        )

        self.cleanup.append(account)
        # Create user api client of the account
        userapiclient = self.testClient.getUserApiClient(
            UserName=account.name,
            DomainName=account.domain
        )

        # Create VM
        virtual_machine = VirtualMachine.create(
            userapiclient,
            self.testdata["small"],
            templateid=self.template.id,
            accountid=account.name,
            domainid=account.domainid,
            serviceofferingid=self.service_offering.id,
            zoneid=self.zone.id
        )

        # Create 10 concurrent snapshots by default
        # We can have any value, so keeping it 10 as it
        # seems good enough to test
        concurrentSnapshots = 10

        # Step 1
        # Get ROOT Volume Id
        volumes = Volume.list(
            self.apiclient,
            virtualmachineid=virtual_machine.id,
            type='ROOT',
            listall=True
        )

        self.assertEqual(validateList(volumes)[0], PASS,
                         "Volumes list validation failed")

        root_volume = volumes[0]

        config = Configurations.list(
            self.apiclient,
            name="concurrent.snapshots.threshold.perhost"
        )
        if config[0].value:
            self.assertEqual(
                isinstance(
                    config,
                    list),
                True,
                "concurrent.snapshots.threshold.perhost should be present\
                in global config")
            concurrentSnapshots = int(config[0].value)
        self.debug("concurrent Snapshots: %s" % concurrentSnapshots)

        threads = []
        for i in range(0, (concurrentSnapshots)):
            thread = Thread(
                target=Snapshot.create,
                args=(
                    self.apiclient,
                    root_volume.id
                ))
            threads.append(thread)
            thread.start()
        for thread in threads:
            thread.join()

        snapshots = Snapshot.list(self.apiclient,
                                  volumeid=root_volume.id,
                                  listall=True)

        self.assertEqual(validateList(snapshots)[0], PASS,
                         "Snapshots list validation failed")
        self.assertEqual(
            len(snapshots),
            concurrentSnapshots,
            "There should be exactly %s snapshots present" %
            concurrentSnapshots)

        for snapshot in snapshots:
            self.assertEqual(str(snapshot.state).lower(), BACKED_UP,
                             "Snapshot state should be backedUp but it is\
                            %s" % snapshot.state)
        return

    @attr(tags=["advanced", "basic"], required_hardware="true")
    def test_02_concurrent_snapshot_global_limit(self):
        """ Test if global value concurrent.snapshots.threshold.perhost
            value is respected
            This is negative test cases and tests no more concurrent
            snapshots as specified in global value are created
        # 1. Read the global value for concurrent.snapshots.threshold.perhost
        # 2. If the value is Null, skip the test case
        # 3. Create an account and a VM in it
        # 4. Create more concurrent snapshots than specified in
             global allowed limit
        # 5. Verify that exception is raised while creating snapshots
        """

        config = Configurations.list(
            self.apiclient,
            name="concurrent.snapshots.threshold.perhost"
        )
        if config[0].value:
            self.assertEqual(
                isinstance(
                    config,
                    list),
                True,
                "concurrent.snapshots.threshold.perhost should be present\
                in global config")
            concurrentSnapshots = int(config[0].value)
        else:
            self.skipTest("Skipping tests as the config value \
                    concurrent.snapshots.threshold.perhost is Null")

        # Create an account
        account = Account.create(
            self.apiclient,
            self.testdata["account"],
            domainid=self.domain.id
        )

        self.cleanup.append(account)
        # Create user api client of the account
        userapiclient = self.testClient.getUserApiClient(
            UserName=account.name,
            DomainName=account.domain
        )

        # Create VM
        virtual_machine = VirtualMachine.create(
            userapiclient,
            self.testdata["small"],
            templateid=self.template.id,
            accountid=account.name,
            domainid=account.domainid,
            serviceofferingid=self.service_offering.id,
            zoneid=self.zone.id
        )

        # Step 1
        # Get ROOT Volume Id
        volumes = Volume.list(
            self.apiclient,
            virtualmachineid=virtual_machine.id,
            type='ROOT',
            listall=True
        )

        self.assertEqual(validateList(volumes)[0], PASS,
                         "Volumes list validation failed")

        root_volume = volumes[0]

        threads = []
        for i in range(0, (concurrentSnapshots + 1)):
            thread = Thread(
                target=self.createSnapshot,
                args=(
                    self.apiclient,
                    root_volume.id
                ))
            threads.append(thread)
            thread.start()

        for thread in threads:
            thread.join()

        self.assertTrue(self.exceptionOccurred, "Concurrent snapshots\
                more than concurrent.snapshots.threshold.perhost\
                value successfully created")
        return

    @attr(tags=["advanced", "basic"], required_hardware="false")
    def test_03_concurrent_snapshot_global_value_assignment(self):
        """ Test verifies that exception is raised if string value is assigned to
             concurrent.snapshots.threshold.perhost parameter.
        """
        with self.assertRaises(Exception):
            Configurations.update(
                self.apiclient,
                "concurrent.snapshots.threshold.perhost",
                "String"
            )
        return
