# 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.

from marvin.cloudstackTestCase import cloudstackTestCase, unittest
from marvin.lib.utils import (cleanup_resources,
                              validateList,
                              get_hypervisor_type, get_process_status)
from marvin.lib.base import (Account,
                             Cluster,
                             Configurations,
                             Host,
                             VPC,
                             VirtualMachine,
                             Network,
                             Router,
                             ServiceOffering,
                             NetworkOffering)
from marvin.lib.common import (get_zone,
                               get_template,
                               verifyNetworkState,
                               wait_for_cleanup, list_routers, list_hosts)
from nose.plugins.attrib import attr
from marvin.sshClient import SshClient
from distutils.util import strtobool
from pyVmomi import vim, vmodl
from marvin.lib.vcenter import Vcenter
import logging

logger = logging.getLogger('TestPesistentNetwork')
stream_handler = logging.StreamHandler()
logger.setLevel(logging.DEBUG)
logger.addHandler(stream_handler)


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

        isKVM = cls.hypervisor.lower() in ["kvm"]
        isOVSEnabled = False
        hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__
        if isKVM :
            # Test only if all the hosts use OVS
            grepCmd = 'grep "network.bridge.type=openvswitch" /etc/cloudstack/agent/agent.properties'
            hosts = list_hosts(cls.api_client, type='Routing', hypervisor='kvm')
            for host in hosts :
                if len(SshClient(host.ipaddress, port=22, user=hostConfig["username"],
                    passwd=hostConfig["password"]).execute(grepCmd)) != 0 :
                        isOVSEnabled = True
                        break
        if isKVM and isOVSEnabled :
            cls.skipTest(cls, "KVM with OVS doesn't support persistent networks, skipping")

        # Fill services from the external config file
        cls.services = cls.testClient.getParsedTestDataConfig()
        cls.hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][
            0].__dict__
        # Get Zone and templates
        cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests())
        cls.template = get_template(
            cls.api_client,
            cls.zone.id,
            cls.services["ostype"]
        )

        cls.services["virtual_machine"]["zoneid"] = cls.zone.id
        cls.services["virtual_machine"]["template"] = cls.template.id
        cls.service_offering = ServiceOffering.create(
            cls.api_client,
            cls.services["service_offering"]
        )
        cls.l2_persistent_network_offering = cls.create_network_offering("nw_off_L2_persistent")
        cls.isolated_persistent_network_offering = cls.create_network_offering("nw_off_isolated_persistent")

        # network will be deleted as part of account cleanup
        cls._cleanup = [
            cls.service_offering,
            cls.isolated_persistent_network_offering,
            cls.l2_persistent_network_offering]
        return

    @classmethod
    def tearDownClass(cls):
        try:
            # Cleanup resources used
            cleanup_resources(cls.api_client, 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()
        self.hypervisor = self.testClient.getHypervisorInfo()
        self.cleanup = []
        return

    def tearDown(self):
        try:
            # Clean up, terminate the resources created
            cleanup_resources(self.apiclient, self.cleanup)
            self.cleanup[:] = []
        except Exception as e:
            raise Exception("Warning: Exception during cleanup : %s" % e)
        return

    @classmethod
    def is_ssh_enabled(cls):
        conf = Configurations.list(cls.apiclient, name="kvm.ssh.to.agent")
        if not conf:
            return False
        else:
            return bool(strtobool(conf[0].value)) if conf[0].value else False

    @classmethod
    def create_network_offering(cls, network_offering_type):
        network_offering = NetworkOffering.create(
            cls.api_client,
            cls.services[network_offering_type],
            conservemode=False
        )

        # Update network offering state from disabled to enabled.
        NetworkOffering.update(
            network_offering,
            cls.api_client,
            id=network_offering.id,
            state="enabled")
        return network_offering

    def get_ssh_client(self, ip, username, password, retries=10):
        """ Setup ssh client connection and return connection """
        try:
            ssh_client = SshClient(ip, 22, username, password, retries)
        except Exception as e:
            raise unittest.SkipTest("Unable to create ssh connection: " % e)

        self.assertIsNotNone(
            ssh_client, "Failed to setup ssh connection to ip=%s" % ip)

        return ssh_client

    def list_all_hosts_in_zone(self, zone_id):
        hosts = Host.list(
            self.apiclient,
            type='Routing',
            resourcestate='Enabled',
            state='Up',
            zoneid=zone_id
        )
        return hosts

    '''
    Verifies creation of bridge on KVM host
    '''
    def verify_bridge_creation(self, host, vlan_id):
        username = self.hostConfig["username"]
        password = self.hostConfig["password"]
        try:
            ssh_client = self.get_ssh_client(host.ipaddress, username, password)
            res = ssh_client.execute("ip addr | grep breth1-" + str(vlan_id) + " > /dev/null 2>&1; echo $?")
            return res[0]
        except Exception as e:
            self.fail(e)

    '''
    Gets all port groups on the host
    '''
    def capture_host_portgroups(self, host):
        host_portgroups = []
        for portgroup in host.config.network.portgroup:
            host_portgroups.append(portgroup.spec.name)
        return host_portgroups

    '''
    Fetches port group names based on VMware switch type - Distributed Virtual Switch(DVS) and
    Standard Virtual Switch(SVS)
    '''
    def get_port_group_name(self, switch_type, vlan_id, network_rate):
        if switch_type == 'DVS':
            return 'cloud.guest.' + str(vlan_id) + '.' + str(network_rate) + '.1-dvSwitch1'
        elif switch_type == 'SVS':
            return 'cloud.guest.' + str(vlan_id) + '.' + str(network_rate) + '.1-vSwitch1'
        else:
            return None

    '''
    Verifies creation of port group on the Distributed vSwitch or a host in a cluster connected to
    a Standard vSwitch
    '''
    def verify_port_group_creation(self, vlan_id):
        config = self.get_vmware_dc_config(self.zone.id)
        vc_object = Vcenter(config[0][0], config[0][1], 'P@ssword123')
        dvs = vc_object.get_dvswitches()
        port_group_present = False
        if dvs is not None:
            port_group_name = self.get_port_group_name('DVS', vlan_id,
                                                       self.isolated_persistent_network_offering.networkrate)
            port_group_present = port_group_name in dvs[0]['dvswitch']['portgroupNameList']

        else:
            port_group_name = self.get_port_group_name('SVS', vlan_id,
                                                       self.isolated_persistent_network_offering.networkrate)
            hosts = vc_object._get_obj([vim.HostSystem])
            host = hosts[0]['host']
            host_pg = self.capture_host_portgroups(host)
            port_group_present = port_group_name in host_pg
        return port_group_present

    '''
    Fetch vmware datacenter login details
    '''
    def get_vmware_dc_config(self, zone_id):
        zid = self.dbclient.execute("select id from data_center where uuid='%s';" %
                                    zone_id)
        vmware_dc_id = self.dbclient.execute(
            "select vmware_data_center_id from vmware_data_center_zone_map where zone_id='%s';" %
            zid[0])
        vmware_dc_config = self.dbclient.execute(
            "select vcenter_host, username, password from vmware_data_center where id = '%s';" % vmware_dc_id[0])

        return vmware_dc_config

    '''
    Verify VLAN creation on specific host in a cluster
    '''
    def verify_vlan_network_creation(self, host, vlan_id):
        username = self.hostConfig["username"]
        password = self.hostConfig["password"]
        try:
            ssh_client = self.get_ssh_client(host.ipaddress, username, password)
            res = ssh_client.execute(
                "xe vlan-list | grep -x  \"^\s*tag ( RO): \"" + str(vlan_id) + "> /dev/null 2>&1; echo $?")
            return res[0]
        except Exception as e:
            self.fail(e)

    def verify_network_setup_on_host_per_cluster(self, hypervisor, vlan_id):
        clusters = Cluster.list(
            self.apiclient,
            zoneid=self.zone.id,
            allocationstate="Enabled",
            listall=True
        )
        for cluster in clusters:
            hosts = Host.list(self.apiclient,
                              clusterid=cluster.id,
                              type="Routing",
                              state="Up",
                              resourcestate="Enabled")
            host = hosts[0]
            if hypervisor == "xenserver":
                result = self.verify_vlan_network_creation(host, vlan_id)
                self.assertEqual(
                    int(result),
                    0,
                    "Failed to find vlan on host: " + host.name + " in cluster: " + cluster.name)
            if hypervisor == "vmware":
                result = self.verify_port_group_creation(vlan_id)
                self.assertEqual(
                        result,
                        True,
                        "Failed to find port group on hosts of cluster: " + cluster.name)

    '''
    This test verifies that on creation of an Isolated network with network offering with isPersistent flag
    set to true the corresponding network resources are created without having to deploy a VM - VR created
    '''
    @attr(tags=["advanced", "isolated", "persistent", "network"], required_hardware="false")
    def test_01_isolated_persistent_network(self):
        network = Network.create(
            self.apiclient,
            self.services["isolated_network"],
            networkofferingid=self.isolated_persistent_network_offering.id,
            zoneid=self.zone.id)
        self.cleanup.append(network)
        networkVlan = network.vlan
        response = verifyNetworkState(
            self.apiclient,
            network.id,
            "implemented")
        exceptionOccurred = response[0]
        isNetworkInDesiredState = response[1]
        exceptionMessage = response[2]

        if (exceptionOccurred or (not isNetworkInDesiredState)):
            self.fail(exceptionMessage)
        self.assertIsNotNone(
            networkVlan,
            "vlan must not be null for persistent network")

        router = Router.list(self.apiclient, networkid=network.id)[0]
        router_host_id = router.hostid
        host = Host.list(self.apiclient, id=router_host_id)[0]
        if host.hypervisor.lower() in "kvm":
            result = self.verify_bridge_creation(host, networkVlan)
            self.assertEqual(
                int(result),
                0,
                "Failed to find bridge on the breth1$-{networkVlan}")
        elif host.hypervisor.lower() in ["xenserver", "vmware"]:
            self.verify_network_setup_on_host_per_cluster(host.hypervisor.lower(), networkVlan)

    '''
    This test verifies that on creation of an L2 network with network offering with isPersistent flag
    set to true the corresponding network resources are created without having to deploy a VM - VR created
    '''
    @attr(tags=["advanced", "l2", "persistent", "network"], required_hardware="false")
    def test_02_L2_persistent_network(self):
        network_vlan = 90
        network = Network.create(
            self.apiclient,
            self.services["l2_network"],
            networkofferingid=self.l2_persistent_network_offering.id,
            zoneid=self.zone.id,
            vlan=network_vlan)
        self.cleanup.append(network)
        response = verifyNetworkState(
            self.apiclient,
            network.id,
            "implemented")
        exceptionOccurred = response[0]
        isNetworkInDesiredState = response[1]
        exceptionMessage = response[2]

        if (exceptionOccurred or (not isNetworkInDesiredState)):
            self.fail(exceptionMessage)
        self.assertIsNotNone(
            network_vlan,
            "vlan must not be null for persistent network")

        self.validate_persistent_network_resources_created_on_host(network_vlan)

    @attr(tags=["advanced", "l2", "persistent", "network"], required_hardware="false")
    def test_03_deploy_and_destroy_VM_and_verify_network_resources_persist(self):
        network_vlan = 99
        network = Network.create(
            self.apiclient,
            self.services["l2_network"],
            networkofferingid=self.l2_persistent_network_offering.id,
            zoneid=self.zone.id,
            vlan=network_vlan)
        self.cleanup.append(network)
        response = verifyNetworkState(
            self.apiclient,
            network.id,
            "implemented")
        logger.debug(response)
        exceptionOccurred = response[0]
        isNetworkInDesiredState = response[1]
        exceptionMessage = response[2]

        if (exceptionOccurred or (not isNetworkInDesiredState)):
            self.fail(exceptionMessage)
        self.assertIsNotNone(
            network_vlan,
            "vlan must not be null for persistent network")
        try:
            virtual_machine = VirtualMachine.create(
                self.apiclient,
                self.services["virtual_machine"],
                networkids=[
                    network.id],
                serviceofferingid=self.service_offering.id)

            VirtualMachine.delete(virtual_machine, self.apiclient, expunge=True)

            self.validate_persistent_network_resources_created_on_host(network_vlan)
        except Exception as e:
            self.fail("Exception occurred: %s" % e)
        return


    def validate_persistent_network_resources_created_on_host(self, network_vlan):
        hosts = self.list_all_hosts_in_zone(self.zone.id)
        if self.hypervisor.lower() in "kvm":
            for host in hosts:
                result = self.verify_bridge_creation(host, network_vlan)
                self.assertEqual(
                    int(result),
                    0,
                    "Failed to find bridge on the breth1-" + str(network_vlan))
        elif self.hypervisor.lower() in ["xenserver", "vmware"]:
            self.verify_network_setup_on_host_per_cluster(self.hypervisor.lower(), network_vlan)
