class TestSSVMs(cloudstackTestCase):
    def setUp(self):
        self.logger = MarvinLog('test').getLogger()
        self.apiclient = self.testClient.getApiClient()
        self.hypervisor = self.testClient.getHypervisorInfo()
        self.cleanup = []
        self.services = self.testClient.getParsedTestDataConfig()
        self.zone = get_zone(self.apiclient, self.testClient.getZoneForTests())

        self.services["sleep"] = 2
        self.services["timeout"] = 240
        # Default value is 120 seconds. That's just too much.
        self.services["configurableData"]["systemVmDelay"] = 60

        return

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

        except Exception as e:
            raise Exception("Warning: Exception during cleanup : %s" % e)
        return

    def wait_for_system_vm_agent(self, vmname):
        self.logger.debug("Waiting for system VM %s agent to be UP" % vmname)
        timeout = self.services["timeout"]
        sleep_interval = self.services["sleep"]
        while timeout > 0:
            list_host_response = list_hosts(self.apiclient, name=vmname)

            if list_host_response and list_host_response[0].state == 'Up':
                self.debug("System VM %s agent is UP" % vmname)
                break

            time.sleep(sleep_interval)
            timeout = timeout - sleep_interval

        if timeout <= 0 and list_host_response[0].state != 'Up':
            self.fail("Timed out waiting for SVM agent to be Up")

    def test_list_svm_vm(self, svm_type):
        # Validate the following:
        # 1. listSystemVM
        #    should return only ONE SVM per zone
        # 2. The returned SVM should be in Running state
        # 3. listSystemVM for should list publicip, privateip and link-localip
        # 4. The gateway programmed on the SVM by listSystemVm should be
        #    the same as the gateway returned by listVlanIpRanges
        # 5. DNS entries must match those given for the zone

        list_svm_response = list_ssvms(
            self.apiclient,
            systemvmtype=svm_type,
            state='Running',
        )
        self.assertEqual(isinstance(list_svm_response, list), True,
                         "Check list response returns a valid list")
        # Verify SSVM response
        self.assertNotEqual(len(list_svm_response), 0,
                            "Check list System VMs response")

        list_zones_response = list_zones(self.apiclient)

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

        self.logger.debug("Number of zones: %s" % len(list_zones_response))
        self.logger.debug("Number of System VMs: %s" % len(list_svm_response))
        # Number of Sec storage VMs = No of Zones
        self.assertEqual(len(list_svm_response), len(list_zones_response),
                         "Check number of System VMs with number of zones")
        # For each secondary storage VM check private IP,
        # public IP, link local IP and DNS
        for svm in list_svm_response:

            self.logger.debug("SVM state: %s" % svm.state)
            self.assertEqual(svm.state, 'Running',
                             "Check whether state of System VM is running")

            self.assertEqual(hasattr(svm, 'privateip'), True,
                             "Check whether System VM has private IP field")

            self.assertEqual(
                hasattr(svm, 'linklocalip'), True,
                "Check whether System VM has link local IP field")

            self.assertEqual(hasattr(svm, 'publicip'), True,
                             "Check whether System VM has public IP field")

            # Fetch corresponding ip ranges information from listVlanIpRanges
            ipranges_response = list_vlan_ipranges(self.apiclient,
                                                   zoneid=svm.zoneid)
            self.assertEqual(isinstance(ipranges_response, list), True,
                             "Check list response returns a valid list")
            iprange = ipranges_response[0]

            # Execute the following assertion in all zones except basic Zones
            if not (self.zone.networktype.lower() == 'basic'):
                self.assertEqual(
                    svm.gateway, iprange.gateway,
                    "Check gateway with that of corresponding ip range")

            # Fetch corresponding zone information from listZones
            zone_response = list_zones(self.apiclient, id=svm.zoneid)
            self.assertEqual(isinstance(zone_response, list), True,
                             "Check list response returns a valid list")
            self.assertEqual(svm.dns1, zone_response[0].dns1,
                             "Check DNS1 with that of corresponding zone")

            self.assertEqual(svm.dns2, zone_response[0].dns2,
                             "Check DNS2 with that of corresponding zone")
        return

    @attr(tags=["advanced", "advancedns", "smoke", "basic", "sg"],
          required_hardware="true")
    def test_01_list_sec_storage_vm(self):
        self.test_list_svm_vm('secondarystoragevm')

    @attr(tags=["advanced", "advancedns", "smoke", "basic", "sg"],
          required_hardware="true")
    def test_02_list_cpvm_vm(self):
        self.test_list_svm_vm('consoleproxy')

    @attr(tags=["advanced", "advancedns", "smoke", "basic", "sg"],
          required_hardware="true")
    def test_03_ssvm_internals(self):
        """Test SSVM Internals"""

        # Validate the following
        # 1. The SSVM check script should not return any
        #    WARN|ERROR|FAIL messages
        # 2. If you are unable to login to the SSVM with the signed key
        #    then test is deemed a failure
        # 3. There should be only one ""cloud"" process running within the SSVM
        # 4. If no process is running/multiple process are running
        #    then the test is a failure

        list_ssvm_response = list_ssvms(self.apiclient,
                                        systemvmtype='secondarystoragevm',
                                        state='Running',
                                        zoneid=self.zone.id)
        self.assertEqual(isinstance(list_ssvm_response, list), True,
                         "Check list response returns a valid list")
        ssvm = list_ssvm_response[0]

        hosts = list_hosts(self.apiclient, id=ssvm.hostid)
        self.assertEqual(isinstance(hosts, list), True,
                         "Check list response returns a valid list")
        host = hosts[0]

        self.logger.debug("Running SSVM check script")

        try:
            host.user, host.passwd = get_host_credentials(
                self.config, host.ipaddress)
            result = get_process_status(
                host.ipaddress, 22, host.user, host.passwd, ssvm.linklocalip,
                "/opt/cosmic/agent/ssvm-check.sh |grep -e ERROR -e WARNING -e FAIL"
            )
        except KeyError:
            self.skipTest(
                "Marvin configuration has no host credentials to check router services"
            )

        res = str(result)
        self.logger.debug("SSVM script output: %s" % res)

        self.assertEqual(res.count("ERROR"), 1, "Check for Errors in tests")

        self.assertEqual(res.count("WARNING"), 1,
                         "Check for warnings in tests")

        # Check status of cloud service
        try:
            host.user, host.passwd = get_host_credentials(
                self.config, host.ipaddress)
            result = get_process_status(host.ipaddress, 22, host.user,
                                        host.passwd, ssvm.linklocalip,
                                        "service cloud status")
        except KeyError:
            self.skipTest(
                "Marvin configuration has no host credentials to check router services"
            )
        res = str(result)
        self.logger.debug("Cloud Process status: %s" % res)
        # cloud.com service (type=secstorage) is running: process id: 2346
        self.assertEqual(res.count("is running"), 1,
                         "Check cloud service is running or not")

        linklocal_ip = None
        # Check status of cloud service
        try:
            linklocal_ip = ssvm.linklocalip
            host.user, host.passwd = get_host_credentials(
                self.config, host.ipaddress)
            result = get_process_status(
                host.ipaddress, 22, host.user, host.passwd, ssvm.linklocalip,
                "cat /var/cache/cloud/cmdline | xargs | sed \"s/ /\\n/g\" | grep eth0ip= | sed \"s/\=/ /g\" | awk '{print $2}'"
            )
        except KeyError:
            self.skipTest(
                "Marvin configuration has no host credentials to check router services"
            )
        res = result[0]
        self.logger.debug("Cached Link Local IP: %s" % res)
        self.assertEqual(
            linklocal_ip, res,
            "The cached Link Local should be the same as the current Link Local IP, but they are different! Current ==> %s; Cached ==> %s "
            % (linklocal_ip, res))

        return

    @attr(tags=["advanced", "advancedns", "smoke", "basic", "sg"],
          required_hardware="true")
    def test_04_cpvm_internals(self):
        """Test CPVM Internals"""

        # Validate the following
        # 1. test that telnet access on 8250 is available to
        #    the management server for the CPVM
        # 2. No telnet access, test FAIL
        # 3. Service cloud status should report cloud agent status to be
        #    running

        list_cpvm_response = list_ssvms(self.apiclient,
                                        systemvmtype='consoleproxy',
                                        state='Running',
                                        zoneid=self.zone.id)
        self.assertEqual(isinstance(list_cpvm_response, list), True,
                         "Check list response returns a valid list")
        cpvm = list_cpvm_response[0]

        hosts = list_hosts(self.apiclient, id=cpvm.hostid)
        self.assertEqual(isinstance(hosts, list), True,
                         "Check list response returns a valid list")
        host = hosts[0]

        try:
            telnetlib.Telnet(str(self.apiclient.connection.mgtSvr), '8250')
            self.logger.debug("Telnet management server (IP: %s)" %
                              self.apiclient.connection.mgtSvr)
        except Exception as e:
            self.fail("Telnet Access failed for %s: %s" %
                      (self.apiclient.connection.mgtSvr, e))

        self.logger.debug("Checking cloud process status")

        try:
            host.user, host.passwd = get_host_credentials(
                self.config, host.ipaddress)
            result = get_process_status(host.ipaddress, 22, host.user,
                                        host.passwd, cpvm.linklocalip,
                                        "service cloud status")
        except KeyError:
            self.skipTest(
                "Marvin configuration has no host credentials to check router services"
            )
        res = str(result)
        self.logger.debug("Cloud Process status: %s" % res)
        self.assertEqual(res.count("is running"), 1,
                         "Check cloud service is running or not")

        linklocal_ip = None
        # Check status of cloud service
        try:
            linklocal_ip = cpvm.linklocalip
            host.user, host.passwd = get_host_credentials(
                self.config, host.ipaddress)
            result = get_process_status(
                host.ipaddress, 22, host.user, host.passwd, cpvm.linklocalip,
                "cat /var/cache/cloud/cmdline | xargs | sed \"s/ /\\n/g\" | grep eth0ip= | sed \"s/\=/ /g\" | awk '{print $2}'"
            )
        except KeyError:
            self.skipTest(
                "Marvin configuration has no host credentials to check router services"
            )
        res = result[0]
        self.logger.debug("Cached Link Local IP: %s" % res)
        self.assertEqual(
            linklocal_ip, res,
            "The cached Link Local should be the same as the current Link Local IP, but they are different! Current ==> %s; Cached ==> %s "
            % (linklocal_ip, res))

        return

    @attr(tags=["advanced", "advancedns", "smoke", "basic", "sg"],
          required_hardware="true")
    def test_07_reboot_ssvm(self):
        """Test reboot SSVM
        """
        # Validate the following
        # 1. The SSVM should go to stop and return to Running state
        # 2. SSVM's public-ip and private-ip must remain the same
        #    before and after reboot
        # 3. The cloud process should still be running within the SSVM

        list_ssvm_response = list_ssvms(self.apiclient,
                                        systemvmtype='secondarystoragevm',
                                        state='Running',
                                        zoneid=self.zone.id)

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

        ssvm_response = list_ssvm_response[0]

        hosts = list_hosts(self.apiclient, id=ssvm_response.hostid)
        self.assertEqual(isinstance(hosts, list), True,
                         "Check list response returns a valid list")

        # Store the public & private IP values before reboot
        old_public_ip = ssvm_response.publicip
        old_private_ip = ssvm_response.privateip

        self.logger.debug("Rebooting SSVM: %s" % ssvm_response.id)
        cmd = rebootSystemVm.rebootSystemVmCmd()
        cmd.id = ssvm_response.id
        self.apiclient.rebootSystemVm(cmd)

        timeout = self.services["timeout"]
        while True:
            list_ssvm_response = list_ssvms(self.apiclient,
                                            id=ssvm_response.id)
            if isinstance(list_ssvm_response, list):
                if list_ssvm_response[0].state == 'Running':
                    break
            if timeout == 0:
                raise Exception("List SSVM call failed!")

            time.sleep(self.services["sleep"])
            timeout = timeout - 1

        ssvm_response = list_ssvm_response[0]
        self.logger.debug("SSVM State: %s" % ssvm_response.state)
        self.assertEqual('Running', str(ssvm_response.state),
                         "Check whether CPVM is running or not")

        self.assertEqual(
            ssvm_response.publicip, old_public_ip,
            "Check Public IP after reboot with that of before reboot")

        # Private IP Address of System VMs are allowed to change after reboot - CLOUDSTACK-7745

        # Wait for the agent to be up
        self.wait_for_system_vm_agent(ssvm_response.name)

        # Wait for some time before running diagnostic scripts on SSVM
        # as it may take some time to start all service properly
        time.sleep(int(self.services["configurableData"]["systemVmDelay"]))

        # Call to verify cloud process is running
        self.test_03_ssvm_internals()
        return

    @attr(tags=["advanced", "advancedns", "smoke", "basic", "sg"],
          required_hardware="true")
    def test_08_reboot_cpvm(self):
        """Test reboot CPVM
        """
        # Validate the following
        # 1. The CPVM should go to stop and return to Running state
        # 2. CPVM's public-ip and private-ip must remain
        #    the same before and after reboot
        # 3. the cloud process should still be running within the CPVM

        list_cpvm_response = list_ssvms(self.apiclient,
                                        systemvmtype='consoleproxy',
                                        state='Running',
                                        zoneid=self.zone.id)
        self.assertEqual(isinstance(list_cpvm_response, list), True,
                         "Check list response returns a valid list")
        cpvm_response = list_cpvm_response[0]

        hosts = list_hosts(self.apiclient, id=cpvm_response.hostid)
        self.assertEqual(isinstance(hosts, list), True,
                         "Check list response returns a valid list")

        # Store the public & private IP values before reboot
        old_public_ip = cpvm_response.publicip
        old_private_ip = cpvm_response.privateip

        self.logger.debug("Rebooting CPVM: %s" % cpvm_response.id)

        cmd = rebootSystemVm.rebootSystemVmCmd()
        cmd.id = cpvm_response.id
        self.apiclient.rebootSystemVm(cmd)

        timeout = self.services["timeout"]
        while True:
            list_cpvm_response = list_ssvms(self.apiclient,
                                            id=cpvm_response.id)
            if isinstance(list_cpvm_response, list):
                if list_cpvm_response[0].state == 'Running':
                    break
            if timeout == 0:
                raise Exception("List CPVM call failed!")

            time.sleep(self.services["sleep"])
            timeout = timeout - 1

        cpvm_response = list_cpvm_response[0]

        self.logger.debug("CPVM state: %s" % cpvm_response.state)
        self.assertEqual('Running', str(cpvm_response.state),
                         "Check whether CPVM is running or not")

        self.assertEqual(
            cpvm_response.publicip, old_public_ip,
            "Check Public IP after reboot with that of before reboot")

        # Private IP Address of System VMs are allowed to change after reboot - CLOUDSTACK-7745

        # Wait for the agent to be up
        self.wait_for_system_vm_agent(cpvm_response.name)

        # Wait for some time before running diagnostic scripts on SSVM
        # as it may take some time to start all service properly
        time.sleep(int(self.services["configurableData"]["systemVmDelay"]))

        # Call to verify cloud process is running
        self.test_04_cpvm_internals()
        return

    @attr(tags=["advanced", "advancedns", "smoke", "basic", "sg"],
          required_hardware="true")
    def test_09_destroy_ssvm(self):
        """Test destroy SSVM
        """

        # Validate the following
        # 1. SSVM should be completely destroyed and a new one will spin up
        # 2. listSystemVMs will show a different name for the
        #    systemVM from what it was before
        # 3. new SSVM will have a public/private and link-local-ip
        # 4. cloud process within SSVM must be up and running

        list_ssvm_response = list_ssvms(self.apiclient,
                                        systemvmtype='secondarystoragevm',
                                        state='Running',
                                        zoneid=self.zone.id)
        self.assertEqual(isinstance(list_ssvm_response, list), True,
                         "Check list response returns a valid list")
        ssvm_response = list_ssvm_response[0]

        old_name = ssvm_response.name

        self.logger.debug("Destroying SSVM: %s" % ssvm_response.id)
        cmd = destroySystemVm.destroySystemVmCmd()
        cmd.id = ssvm_response.id
        self.apiclient.destroySystemVm(cmd)

        timeout = self.services["timeout"]
        while True:
            list_ssvm_response = list_ssvms(self.apiclient,
                                            zoneid=self.zone.id,
                                            systemvmtype='secondarystoragevm')
            if isinstance(list_ssvm_response, list):
                if list_ssvm_response[0].state == 'Running':
                    break
            if timeout == 0:
                self.logger.debug(
                    "Warning: List SSVM didn't return systemvms in Running state. This is a known issue, ignoring it for now!"
                )
                return

            time.sleep(self.services["sleep"])
            timeout = timeout - 1

        ssvm_response = list_ssvm_response[0]

        # Verify Name, Public IP, Private IP and Link local IP
        # for newly created SSVM
        self.assertNotEqual(ssvm_response.name, old_name,
                            "Check SSVM new name with name of destroyed SSVM")
        self.assertEqual(hasattr(ssvm_response, 'privateip'), True,
                         "Check whether SSVM has private IP field")

        self.assertEqual(hasattr(ssvm_response, 'linklocalip'), True,
                         "Check whether SSVM has link local IP field")

        self.assertEqual(hasattr(ssvm_response, 'publicip'), True,
                         "Check whether SSVM has public IP field")

        # Wait for the agent to be up
        self.wait_for_system_vm_agent(ssvm_response.name)

        # Wait for some time before running diagnostic scripts on SSVM
        # as it may take some time to start all service properly
        time.sleep(int(self.services["configurableData"]["systemVmDelay"]))

        # Call to verify cloud process is running
        self.test_03_ssvm_internals()
        return

    @attr(tags=["advanced", "advancedns", "smoke", "basic", "sg"],
          required_hardware="true")
    def test_10_destroy_cpvm(self):
        """Test destroy CPVM
        """

        # Validate the following
        # 1. CPVM should be completely destroyed and a new one will spin up
        # 2. listSystemVMs will show a different name for the systemVM from
        #    what it was before
        # 3. new CPVM will have a public/private and link-local-ip
        # 4. cloud process within CPVM must be up and running

        list_cpvm_response = list_ssvms(self.apiclient,
                                        systemvmtype='consoleproxy',
                                        zoneid=self.zone.id)
        self.assertEqual(isinstance(list_cpvm_response, list), True,
                         "Check list response returns a valid list")
        cpvm_response = list_cpvm_response[0]

        old_name = cpvm_response.name

        self.logger.debug("Destroying CPVM: %s" % cpvm_response.id)
        cmd = destroySystemVm.destroySystemVmCmd()
        cmd.id = cpvm_response.id
        self.apiclient.destroySystemVm(cmd)

        timeout = self.services["timeout"]
        while True:
            list_cpvm_response = list_ssvms(self.apiclient,
                                            systemvmtype='consoleproxy',
                                            zoneid=self.zone.id)
            if isinstance(list_cpvm_response, list):
                if list_cpvm_response[0].state == 'Running':
                    break
            if timeout == 0:
                self.logger.debug(
                    "Warning: List CPVM didn't return systemvms in Running state. This is a known issue, ignoring it for now!"
                )
                return

            time.sleep(self.services["sleep"])
            timeout = timeout - 1

        cpvm_response = list_cpvm_response[0]

        # Verify Name, Public IP, Private IP and Link local IP
        # for newly created CPVM
        self.assertNotEqual(cpvm_response.name, old_name,
                            "Check SSVM new name with name of destroyed CPVM")
        self.assertEqual(hasattr(cpvm_response, 'privateip'), True,
                         "Check whether CPVM has private IP field")

        self.assertEqual(hasattr(cpvm_response, 'linklocalip'), True,
                         "Check whether CPVM has link local IP field")

        self.assertEqual(hasattr(cpvm_response, 'publicip'), True,
                         "Check whether CPVM has public IP field")

        # Wait for the agent to be up
        self.wait_for_system_vm_agent(cpvm_response.name)

        # Wait for some time before running diagnostic scripts on SSVM
        # as it may take some time to start all service properly
        time.sleep(int(self.services["configurableData"]["systemVmDelay"]))

        # Call to verify cloud process is running
        self.test_04_cpvm_internals()
        return