# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
#    Licensed 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 nfv_tempest_plugin.tests.common import shell_utilities as shell_utils
from nfv_tempest_plugin.tests.scenario import base_test
from oslo_log import log as logging
from tempest import config

CONF = config.CONF
LOG = logging.getLogger('{} [-] nfv_plugin_test'.format(__name__))


class TestNfvOffload(base_test.BaseTest):
    def __init__(self, *args, **kwargs):
        super(TestNfvOffload, self).__init__(*args, **kwargs)
        self.hypervisor_ip = None

    def setUp(self):
        """Set up a single tenant with an accessible server.

        If multi-host is enabled, save created server uuids.
        """
        super(TestNfvOffload, self).setUp()

    def test_offload_ovs_config(self):
        """Check ovs config for offload on all hypervisors

        """
        # Command to check if hw-offload is enabled in OVS
        cmd = ("sudo ovs-vsctl get open_vswitch . "
               "other_config:hw-offload")
        # Retrieve all hypvervisors
        hypervisors = self._get_hypervisor_ip_from_undercloud(
            shell=CONF.nfv_plugin_options.undercloud_rc_file)
        # Intialize results list
        result = []
        # Expected result is a list of dicts, each dict contains
        # a key which is hypervisor's IP and the value 'true'
        # Example:
        # [{192.0.60.1: 'true'}, {192.0.60.2: 'true'}]
        expected_result = [{ip: 'true'} for ip in hypervisors]
        for hypervisor in hypervisors:
            out = shell_utils.run_command_over_ssh(hypervisor, cmd)
            if out:
                # Strip newlines and remove double quotes
                output = out.rstrip().replace('"', '')
            # HW-Offload not enabled if no text returned
            else:
                output = 'false'
            LOG.info("Hypervisor '{h}' is OVS HW-offload "
                     "capable: '{r}'".format(h=hypervisor,
                                             r=output))
            result.append({hypervisor: output})
        msg = "Not all hypervisors have OVS HW-Offload enabled"
        self.assertItemsEqual(expected_result, result, msg)

    def test_offload_nic_eswitch_mode(self):
        """Check eswitch mode of nic for offload on all hypervisors

        By default, offload nics are auto discovered.
        But if the used would like to not perform the autodiscover and
        provide the nics, it could be done by modifying the
        CONF.nfv_plugin_options.offload_nics param in deployer-input file.
        """
        LOG.info('Starting offload_nic_eswitch_mode test')
        # Retrieve all hypervisors
        hypervisors = self._get_hypervisor_ip_from_undercloud(
            shell=CONF.nfv_plugin_options.undercloud_rc_file)
        offload_nics = CONF.nfv_plugin_options.offload_nics
        if not offload_nics:
            LOG.info('The offload nics are not provided. Detecting...')
            offload_nics = self.discover_hw_offload_nics(hypervisors)
        LOG.info('Test the following offload nics - {}'.format(offload_nics))
        # devlink cmd to retrieve switch mode of interface
        devlink_cmd = "sudo devlink dev eswitch show pci/{}"
        # Initialize results list
        result = []
        # Expected result is a list of dicts containing a dict of
        # hypervisor's IP, its offload nics as keys and the value 'true'
        # Example:
        # [{'192.0.160.1': [{'p6p1': 'true'}, {'p6p2': 'true'}]},
        #  {'192.0.160.2': [{'p6p1': 'true'}, {'p6p2': 'true'}]}]
        expected_result = [{ip: [{nic: 'true'} for nic, _ in nics.items()]}
                           for ip, nics in offload_nics.items()]
        for hypervisor, nics in offload_nics.items():
            dev_result = []
            # Check hw-offload config on hypervisor
            hyper_check = \
                'sudo ovs-vsctl get Open_vSwitch . other_config:hw-offload'
            hyper_offload_state = shell_utils.run_command_over_ssh(hypervisor,
                                                                   hyper_check)
            if not hyper_offload_state.strip() == '"true"':
                dev_result.append('No hw-offload on hypervisor')
                result.append({hypervisor: dev_result})
                LOG.info('No hw-offload on hypervisor {}'.format(hypervisor))
                continue
            LOG.info('Hw-offload configured on hyper - {}'.format(hypervisor))
            for nic, nic_options in nics.items():
                dev_query = shell_utils.run_command_over_ssh(
                    hypervisor, devlink_cmd.format(nic_options['bus-info']))
                if 'switchdev' in dev_query:
                    output = 'true'
                else:
                    output = 'false'
                LOG.info("Hypervisor '{h}' NIC '{n}' is in switchdev mode: {r}"
                         .format(h=hypervisor, n=nic, r=output))
                dev_result.append({nic: output})
            result.append({hypervisor: dev_result})
        msg = "Not all hypervisors contains nics in switchev mode"
        self.assertItemsEqual(expected_result, result, msg)

    def test_offload_ovs_flows(self, test='offload_flows'):
        """Check OVS offloaded flows

        The following test deploy vms, on hw-offload computes.
        It sends async ping and check offload flows exist in ovs.

        :param test: Test name from the external config file.
        """

        LOG.info('Start test_offload_ovs_flows test.')
        LOG.info('test_offload_ovs_flows create vms')
        # Create servers
        servers, key_pair = self.create_and_verify_resources(test=test,
                                                             num_servers=4)
        cmd = 'sudo ovs-appctl dpctl/dump-flows -m type=offloaded'
        # Iterate over created servers
        for server in servers:

            shell_utils.continuous_ping(server['fip'],
                                        duration=600)
            LOG.info('test_offload_ovs_flows verify flows on geust {}'.
                     format(server['fip']))

            out = shell_utils.\
                run_command_over_ssh(server['hypervisor_ip'],
                                     cmd)
            msg = ('Port with mac address {} is expected to be part of '
                   'offloaded flows')
            # Ping running only on floating ip
            int_port = self.get_internal_port_from_fip(server['fip'])
            self.assertTrue('capabilities' in
                            int_port['binding:profile'] and 'switchdev'
                            in int_port['binding:profile']['capabilities'],
                            "port has not 'capabilities'")
            self.assertIn(int_port['mac_address'], out,
                          msg.format(int_port['mac_address']))
            self.assertIn('offloaded:yes, dp:tc', out,
                          'Did not find "offloaded:yes, dp:tc"')
        # Pings are running check flows exist
        # Retrieve all hypvervisors
        hypervisors = self._get_hypervisor_ip_from_undercloud(
            shell=CONF.nfv_plugin_options.undercloud_rc_file)
        for hypervisor in hypervisors:
            out = shell_utils.run_command_over_ssh(hypervisor,
                                                   cmd)
            msg = 'Hypervisor {} has no offloaded flows in OVS'.format(
                hypervisor)
            self.assertNotEmpty(out, msg)
            LOG.info('Hypercisor {} has offloaded flows in OVS'.format(
                hypervisor))

        # send stop statistics signal
        shell_utils.stop_continuous_ping()
