# 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.
""" P1 tests for VM Schedule
"""
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.lib.base import Account, ServiceOffering, VirtualMachine, VMSchedule
from marvin.lib.common import get_domain, get_zone, get_template
from marvin.lib.utils import cleanup_resources

# Import Local Modules
from nose.plugins.attrib import attr

import datetime
import time


class Services:
    """Test Snapshots Services"""

    def __init__(self):
        self.services = {
            "account": {
                "email": "test@test.com",
                "firstname": "Test",
                "lastname": "User",
                "username": "test",
                # Random characters are appended for unique
                # username
                "password": "password",
            },
            "service_offering": {
                "name": "Tiny Instance",
                "displaytext": "Tiny Instance",
                "cpunumber": 1,
                "cpuspeed": 200,  # in MHz
                "memory": 256,  # In MBs
            },
            "disk_offering": {
                "displaytext": "Small Disk",
                "name": "Small Disk",
                "disksize": 1,
            },
            "server": {
                "displayname": "TestVM",
                "username": "root",
                "password": "password",
                "ssh_port": 22,
                "privateport": 22,
                "publicport": 22,
                "protocol": "TCP",
            },
            "mgmt_server": {
                "ipaddress": "192.168.100.21",
                "username": "root",
                "password": "password",
                "port": 22,
            },
            "templates": {
                "displaytext": "Template",
                "name": "Template",
                "ostype": "CentOS 5.3 (64-bit)",
                "templatefilter": "self",
            },
            "ostype": "CentOS 5.3 (64-bit)",
        }


class TestVMSchedule(cloudstackTestCase):
    @classmethod
    def setUpClass(cls):
        cls.testClient = super(TestVMSchedule, cls).getClsTestClient()
        cls.api_client = cls.testClient.getApiClient()

        cls._cleanup = []

        cls.hypervisor = cls.testClient.getHypervisorInfo()

        cls.services = Services().services
        # Get Zone, Domain and templates
        cls.domain = get_domain(cls.api_client)
        cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests())
        cls.services["mode"] = cls.zone.networktype
        template = get_template(cls.api_client, cls.zone.id, cls.services["ostype"])

        cls.services["domainid"] = cls.domain.id
        cls.services["server"]["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.api_client, cls.services["account"], domainid=cls.domain.id
        )
        cls._cleanup.append(cls.account)

        cls.services["account"] = cls.account.name

        cls.service_offering = ServiceOffering.create(
            cls.api_client, cls.services["service_offering"]
        )
        cls._cleanup.append(cls.service_offering)
        cls.virtual_machine = VirtualMachine.create(
            cls.api_client,
            cls.services["server"],
            templateid=template.id,
            accountid=cls.account.name,
            domainid=cls.account.domainid,
            serviceofferingid=cls.service_offering.id,
        )
        return

    @classmethod
    def tearDownClass(cls):
        super(TestVMSchedule, cls).tearDownClass()

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

    def tearDown(self):
        super(TestVMSchedule, self).tearDown()

    @attr(tags=["advanced", "basic"], required_hardware="false")
    def test_01_vmschedule_create(self):
        """Test VM Schedule Creation in cron format and validate responses"""

        # Validate the following
        # 1. Create VM Schedule in cron format
        # 2. List VM Schedule and verify the response
        # 3. Delete VM Schedule and verify the response

        # Create VM Schedule
        schedule = "0 0 1 * *"
        vmschedule = VMSchedule.create(
            self.apiclient,
            self.virtual_machine.id,
            "start",
            schedule,
            datetime.datetime.now().astimezone().tzinfo,
            # Current date minutes in format "2014-01-01 00:00:00"
            (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
                "%Y-%m-%d %H:%M:%S"
            ),
            enabled=True,
        )

        self.cleanup.append(vmschedule)

        self.debug("Created VM Schedule with ID: %s" % vmschedule.id)

        # List VM Schedule
        vmschedules = VMSchedule.list(
            self.apiclient, self.virtual_machine.id, id=vmschedule.id
        )

        self.assertEqual(
            isinstance(vmschedules, list),
            True,
            "Check list response returns a valid list",
        )

        self.assertNotEqual(len(vmschedules), 0, "Check VM Schedule list")

        self.debug("List VM Schedule response: %s" % vmschedules[0].__dict__)

        self.assertEqual(
            vmschedules[0].id,
            vmschedule.id,
            "Check VM Schedule ID in list resources call",
        )

        self.assertEqual(
            vmschedules[0].virtualmachineid,
            self.virtual_machine.id,
            "Check VM ID in list resources call",
        )

        self.assertEqual(
            vmschedules[0].schedule,
            schedule,
            "Check VM Schedule in list resources call",
        )

        self.assertEqual(
            vmschedules[0].timezone,
            str(datetime.datetime.now().astimezone().tzinfo),
            "Check VM Schedule timezone in list resources call",
        )

        # Check for entry in vm_scheduled_job in db
        vmscheduled_job = self.dbclient.execute(
            "select * from vm_scheduled_job where vm_schedule_id IN (SELECT id FROM vm_schedule WHERE uuid = '%s')"
            % vmschedule.id,
            db="cloud",
        )

        self.assertIsInstance(
            vmscheduled_job,
            list,
            "Check if VM Schedule exists in vm_scheduled_job table",
        )

        self.assertGreater(
            len(vmscheduled_job),
            0,
            "Check if VM Schedule exists in vm_scheduled_job table",
        )
        return

    @attr(tags=["advanced", "basic"], required_hardware="false")
    def test_02_vmschedule_create_parameter_exceptions(self):
        """Test VM Schedule Creation exceptions with invalid parameters"""

        # Validate the following
        # 1. Create VM Schedule with invalid virtual machine ID
        # 2. Create VM Schedule with invalid schedule
        # 3. Create VM Schedule with invalid start date
        # 5. Create VM Schedule with invalid action
        # 6. Create VM Schedule with invalid end date

        # Create VM Schedule with invalid virtual machine ID
        with self.assertRaises(Exception):
            VMSchedule.create(
                self.apiclient,
                "invalid",
                "start",
                "0 0 1 * *",
                datetime.datetime.now().astimezone().tzinfo,
                # Current date minutes in format "2014-01-01 00:00:00"
                (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
                    "%Y-%m-%d %H:%M:%S"
                ),
            )

        # Create VM Schedule with invalid schedule
        with self.assertRaises(Exception):
            VMSchedule.create(
                self.apiclient,
                self.virtual_machine.id,
                "start",
                "invalid",
                datetime.datetime.now().astimezone().tzinfo,
                # Current date minutes in format "2014-01-01 00:00:00"
                (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
                    "%Y-%m-%d %H:%M:%S"
                ),
            )

        # Create VM Schedule with invalid start date
        with self.assertRaises(Exception):
            VMSchedule.create(
                self.apiclient,
                self.virtual_machine.id,
                "start",
                "0 0 1 * *",
                datetime.datetime.now().astimezone().tzinfo,
                # Current date minutes in format "2014-01-01 00:00:00"
                "invalid",
            )

        # Create VM Schedule with invalid action
        with self.assertRaises(Exception):
            VMSchedule.create(
                self.apiclient,
                self.virtual_machine.id,
                "invalid",
                "0 0 1 * *",
                datetime.datetime.now().astimezone().tzinfo,
                # Current date minutes in format "2014-01-01 00:00:00"
                (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
                    "%Y-%m-%d %H:%M:%S"
                ),
            )

        # test invalid end date
        with self.assertRaises(Exception):
            VMSchedule.create(
                self.apiclient,
                self.virtual_machine.id,
                "start",
                "0 0 1 * *",
                datetime.datetime.now().astimezone().tzinfo,
                # Current date minutes in format "2014-01-01 00:00:00"
                (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
                    "%Y-%m-%d %H:%M:%S"
                ),
                enddate="invalid",
            )
        return

    @attr(tags=["advanced", "basic"], required_hardware="false")
    def test_03_vmschedule_update(self):
        """Test VM Schedule Update in cron format and validate responses"""

        # Validate the following
        # 1. Create VM Schedule in cron format
        # 2. Update VM Schedule and verify the response

        # Create VM Schedule
        schedule = "0 0 1 * *"
        vmschedule = VMSchedule.create(
            self.apiclient,
            self.virtual_machine.id,
            "start",
            schedule,
            datetime.datetime.now().astimezone().tzinfo,
            # Current date minutes in format "2014-01-01 00:00:00"
            (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
                "%Y-%m-%d %H:%M:%S"
            ),
        )

        self.cleanup.append(vmschedule)

        self.debug("Created VM Schedule with ID: %s" % vmschedule.id)

        # Update VM Schedule
        new_schedule = "0 0 2 * *"
        vmschedule.update(
            self.apiclient,
            id=vmschedule.id,
            virtualmachineid=self.virtual_machine.id,
            description="TestVM",
            schedule=new_schedule,
            timezone=datetime.datetime.now().astimezone().tzinfo,
            startdate=(
                datetime.datetime.now() + datetime.timedelta(minutes=10)
            ).strftime("%Y-%m-%d %H:%M:%S"),
            enddate=(datetime.datetime.now() + datetime.timedelta(hours=10)).strftime(
                "%Y-%m-%d %H:%M:%S"
            ),
        )

        self.debug("Updated VM Schedule with ID: %s" % vmschedule.id)

        # List VM Schedule
        vmschedules = VMSchedule.list(
            self.apiclient, self.virtual_machine.id, id=vmschedule.id
        )
        self.assertEqual(
            isinstance(vmschedules, list),
            True,
            "Check list response returns a valid list",
        )

        self.assertNotEqual(len(vmschedules), 0, "Check VM Schedule list")

        self.debug("List VM Schedule response: %s" % vmschedules[0].__dict__)

        self.assertEqual(
            vmschedules[0].id,
            vmschedule.id,
            "Check VM Schedule ID in list resources call",
        )

        self.assertEqual(
            vmschedules[0].virtualmachineid,
            self.virtual_machine.id,
            "Check VM ID in list resources call",
        )

        self.assertEqual(
            vmschedules[0].schedule,
            new_schedule,
            "Check VM Schedule in list resources call",
        )
        return

    @attr(tags=["advanced", "basic"], required_hardware="false")
    def test_04_vmschedule_update_parameter_exceptions(self):
        """Test VM Schedule Update exceptions with invalid parameters"""

        # Validate the following
        # 1. Update VM Schedule with invalid schedule
        # 2. Update VM Schedule with invalid start date
        # 3. Update VM Schedule with invalid ID
        # 4. Update VM Schedule with invalid end date

        # Create VM Schedule
        schedule = "0 0 1 * *"
        vmschedule = VMSchedule.create(
            self.apiclient,
            self.virtual_machine.id,
            "start",
            schedule,
            datetime.datetime.now().astimezone().tzinfo,
            # Current date minutes in format "2014-01-01 00:00:00"
            (datetime.datetime.now() + datetime.timedelta(minutes=5)).strftime(
                "%Y-%m-%d %H:%M:%S"
            ),
        )

        self.cleanup.append(vmschedule)

        self.debug("Created VM Schedule with ID: %s" % vmschedule.id)

        # Update VM Schedule with invalid schedule
        with self.assertRaises(Exception):
            vmschedule.update(
                self.apiclient,
                id=vmschedule.id,
                virtualmachineid=self.virtual_machine.id,
                description="TestVM",
                schedule="invalid",
                timezone=datetime.datetime.now().astimezone().tzinfo,
                startdate=(
                    datetime.datetime.now() + datetime.timedelta(minutes=5)
                ).strftime("%Y-%m-%d %H:%M:%S"),
            )

        # Update VM Schedule with invalid start date
        with self.assertRaises(Exception):
            vmschedule.update(
                self.apiclient,
                id=vmschedule.id,
                virtualmachineid=self.virtual_machine.id,
                description="TestVM",
                schedule=schedule,
                timezone=datetime.datetime.now().astimezone().tzinfo,
                startdate=(
                    datetime.datetime.now() - datetime.timedelta(days=1)
                ).strftime("%Y-%m-%d %H:%M:%S"),
            )

        # Update VM Schedule with invalid ID
        with self.assertRaises(Exception):
            vmschedule.update(
                self.apiclient,
                id="invalid",
                virtualmachineid=self.virtual_machine.id,
                description="TestVM",
                schedule=schedule,
                timezone=datetime.datetime.now().astimezone().tzinfo,
                startdate=(
                    datetime.datetime.now() + datetime.timedelta(minutes=5)
                ).strftime("%Y-%m-%d %H:%M:%S"),
            )

        # Update VM Schedule with invalid end date
        with self.assertRaises(Exception):
            vmschedule.update(
                self.apiclient,
                id=vmschedule.id,
                virtualmachineid=self.virtual_machine.id,
                description="TestVM",
                schedule=schedule,
                timezone=datetime.datetime.now().astimezone().tzinfo,
                startdate=(
                    datetime.datetime.now() + datetime.timedelta(minutes=5)
                ).strftime("%Y-%m-%d %H:%M:%S"),
                enddate=(
                    datetime.datetime.now() - datetime.timedelta(minutes=5)
                ).strftime("%Y-%m-%d %H:%M:%S"),
            )

        return

    @attr(tags=["advanced", "basic"], required_hardware="false")
    def test_05_vmschedule_test_e2e(self):
        # Validate the following
        # 1. Create 2 VM Schedules - start and stop
        # 2. Verify VM Schedule is created
        # 3. Verify VM is stopped after schedule time
        # 4. Verify VM is started after schedule time
        # 5. Delete VM Schedule
        # 6. Verify VM Schedule is deleted
        # 7. Verify VM is not stopped after schedule time
        # 8. Verify VM is not started after schedule time

        # Create VM Schedule - start
        start_schedule = "*/2 * * * *"
        start_vmschedule = VMSchedule.create(
            self.apiclient,
            self.virtual_machine.id,
            "start",
            start_schedule,
            datetime.datetime.now().astimezone().tzinfo,
            # Current date minutes in format "2014-01-01 00:00:00"
            (datetime.datetime.now() + datetime.timedelta(seconds=5)).strftime(
                "%Y-%m-%d %H:%M:%S"
            ),
            enabled=True,
        )

        self.debug("Created VM Schedule with ID: %s" % start_vmschedule.id)

        # Create VM Schedule - stop
        stop_schedule = "*/1 * * * *"
        stop_vmschedule = VMSchedule.create(
            self.apiclient,
            self.virtual_machine.id,
            "stop",
            stop_schedule,
            datetime.datetime.now().astimezone().tzinfo,
            # Current date minutes in format "2014-01-01 00:00:00"
            (datetime.datetime.now() + datetime.timedelta(seconds=5)).strftime(
                "%Y-%m-%d %H:%M:%S"
            ),
            enabled=True,
        )

        self.debug("Created VM Schedule with ID: %s" % stop_vmschedule.id)

        # Verify VM Schedule is created
        vmschedules = VMSchedule.list(
            self.apiclient, self.virtual_machine.id, id=start_vmschedule.id
        )

        self.assertEqual(
            isinstance(vmschedules, list),
            True,
            "Check list response returns a valid list",
        )
        self.assertNotEqual(len(vmschedules), 0, "Check VM Schedule is created")

        # poll every 10 seconds (max waiting time is 6 minutes) and check VM's state for changes
        previous_state = self.virtual_machine.state
        self.debug("VM state: %s" % self.virtual_machine.state)
        is_stop_schedule_working = False
        is_start_schedule_working = False
        for i in range(0, 36):
            time.sleep(10)
            current_state = self.virtual_machine.update(self.apiclient).state
            self.debug("Polling VM state: %s" % current_state)
            if previous_state in ("Running", "Starting") and current_state in (
                "Stopped",
                "Stopping",
            ):
                is_stop_schedule_working = True
            elif previous_state in ("Stopped", "Stopping") and current_state in (
                "Running",
                "Starting",
            ):
                is_start_schedule_working = True
            if is_start_schedule_working and is_stop_schedule_working:
                break
            previous_state = current_state

        self.debug("Is stop schedule working: %s" % is_stop_schedule_working)
        self.debug("Is start schedule working: %s" % is_start_schedule_working)

        self.assertTrue(
            is_stop_schedule_working,
            "VM switched states from Running to Stopped at least once",
        )

        self.assertTrue(
            is_start_schedule_working,
            "VM switched states from Stopped to Running at least once",
        )

        # Delete VM Schedule
        start_vmschedule.delete(self.apiclient)
        stop_vmschedule.delete(self.apiclient)

        # To ensure that all vm schedules have been deleted and all of their jobs have been completed
        time.sleep(60)

        # Verify VM Schedule is deleted
        self.assertEqual(
            VMSchedule.list(
                self.apiclient, self.virtual_machine.id, id=start_vmschedule.id
            ),
            None,
            "Check VM Schedule is deleted",
        )
        self.assertEqual(
            VMSchedule.list(
                self.apiclient, self.virtual_machine.id, id=stop_vmschedule.id
            ),
            None,
            "Check VM Schedule is deleted",
        )

        # Verify VM does not switch states after deleting schedules at least for 2 minutes
        previous_state = self.virtual_machine.update(self.apiclient).state
        state_changed = False
        for i in range(0, 4):
            time.sleep(30)
            current_state = self.virtual_machine.update(self.apiclient).state
            if previous_state != current_state:
                # Add these checks because VMs can take some time to start or stop
                if (previous_state == 'Starting' and current_state in ('Starting', 'Running')) or (
                        previous_state == 'Stopping' and current_state in ('Stopping', 'Stopped')):
                    continue
                self.debug(
                    "VM changed state from %s to %s" % (previous_state, current_state)
                )
                state_changed = True
                break

        self.assertFalse(
            state_changed,
            "VM did not switch states after schedule time",
        )
        return
