# Copyright (c) 2016 Cisco and/or its affiliates.
# 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.

"""Library that supports Auto Configuration."""
from __future__ import absolute_import, division, print_function

import logging
import os
import re
from ipaddress import ip_address

import yaml

from vpplib.VPPUtil import VPPUtil
from vpplib.VppPCIUtil import VppPCIUtil
from vpplib.VppHugePageUtil import VppHugePageUtil
from vpplib.CpuUtils import CpuUtils
from vpplib.VppGrubUtil import VppGrubUtil
from vpplib.QemuUtils import QemuUtils

#  Python2/3 compatible
try:
    input = raw_input  # noqa
except NameError:
    pass

__all__ = ["AutoConfig"]

# Constants
MIN_SYSTEM_CPUS = 2
MIN_TOTAL_HUGE_PAGES = 1024
MAX_PERCENT_FOR_HUGE_PAGES = 70

IPERFVM_XML = 'configs/iperf-vm.xml'
IPERFVM_IMAGE = 'images/xenial-mod.img'
IPERFVM_ISO = 'configs/cloud-config.iso'


class AutoConfig(object):
    """Auto Configuration Tools"""

    def __init__(self, rootdir, filename, clean=False):
        """
        The Auto Configure class.

        :param rootdir: The root directory for all the auto configuration files
        :param filename: The autoconfiguration file
        :param clean: When set initialize the nodes from the auto-config file
        :type rootdir: str
        :type filename: str
        :type clean: bool
        """
        self._autoconfig_filename = rootdir + filename
        self._rootdir = rootdir
        self._metadata = {}
        self._nodes = {}
        self._vpp_devices_node = {}
        self._hugepage_config = ""
        self._clean = clean
        self._loadconfig()
        self._sockfilename = ""

    def get_nodes(self):
        """
        Returns the nodes dictionary.

        :returns: The nodes
        :rtype: dictionary
        """

        return self._nodes

    @staticmethod
    def _autoconfig_backup_file(filename):
        """
        Create a backup file.

        :param filename: The file to backup
        :type filename: str
        """

        # Does a copy of the file exist, if not create one
        ofile = filename + '.orig'
        (ret, stdout, stderr) = VPPUtil.exec_command('ls {}'.format(ofile))
        if ret != 0:
            logging.debug(stderr)
            if stdout.strip('\n') != ofile:
                cmd = 'sudo cp {} {}'.format(filename, ofile)
                (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
                if ret != 0:
                    logging.debug(stderr)

    # noinspection PyBroadException
    @staticmethod
    def _ask_user_ipv4():
        """
        Asks the user for a number within a range.
        default is returned if return is entered.

        :returns: IP address with cidr
        :rtype: str
        """

        while True:
            answer = input("Please enter the IPv4 Address [n.n.n.n/n]: ")
            try:
                ipinput = answer.split('/')
                ipaddr = ip_address(ipinput[0])
                if len(ipinput) > 1:
                    plen = answer.split('/')[1]
                else:
                    answer = input("Please enter the netmask [n.n.n.n]: ")
                    plen = ip_address(answer).netmask_bits()
                return '{}/{}'.format(ipaddr, plen)
            except None:
                print("Please enter a valid IPv4 address.")

    @staticmethod
    def _ask_user_range(question, first, last, default):
        """
        Asks the user for a number within a range.
        default is returned if return is entered.

        :param question: Text of a question.
        :param first: First number in the range
        :param last: Last number in the range
        :param default: The value returned when return is entered
        :type question: string
        :type first: int
        :type last: int
        :type default: int
        :returns: The answer to the question
        :rtype: int
        """

        while True:
            answer = input(question)
            if answer == '':
                answer = default
                break
            if re.findall(r'[0-9+]', answer):
                if int(answer) in range(first, last + 1):
                    break
                else:
                    print("Please a value between {} and {} or Return.".
                          format(first, last))
            else:
                print("Please a number between {} and {} or Return.".
                      format(first, last))

        return int(answer)

    @staticmethod
    def _ask_user_yn(question, default):
        """
        Asks the user for a yes or no question.

        :param question: Text of a question.
        :param default: The value returned when return is entered
        :type question: string
        :type default: string
        :returns: The answer to the question
        :rtype: string
        """

        input_valid = False
        default = default.lower()
        answer = ''
        while not input_valid:
            answer = input(question)
            if answer == '':
                answer = default
            if re.findall(r'[YyNn]', answer):
                input_valid = True
                answer = answer[0].lower()
            else:
                print("Please answer Y, N or Return.")

        return answer

    def _loadconfig(self):
        """
        Load the testbed configuration, given the auto configuration file.

        """

        # Get the Topology, from the topology layout file
        topo = {}
        with open(self._autoconfig_filename, 'r') as stream:
            try:
                topo = yaml.load(stream)
                if 'metadata' in topo:
                    self._metadata = topo['metadata']
            except yaml.YAMLError as exc:
                raise RuntimeError(
                    "Couldn't read the Auto config file {}.".format(
                        self._autoconfig_filename, exc))

        systemfile = self._rootdir + self._metadata['system_config_file']
        if self._clean is False and os.path.isfile(systemfile):
            with open(systemfile, 'r') as sysstream:
                try:
                    systopo = yaml.load(sysstream)
                    if 'nodes' in systopo:
                        self._nodes = systopo['nodes']
                except yaml.YAMLError as sysexc:
                    raise RuntimeError(
                        "Couldn't read the System config file {}.".format(
                            systemfile, sysexc))
        else:
            # Get the nodes from Auto Config
            if 'nodes' in topo:
                self._nodes = topo['nodes']

        # Set the root directory in all the nodes
        for i in self._nodes.items():
            node = i[1]
            node['rootdir'] = self._rootdir

    def updateconfig(self):
        """
        Update the testbed configuration, given the auto configuration file.
        We will write the system configuration file with the current node
        information

        """

        # Initialize the yaml data
        ydata = {'metadata': self._metadata, 'nodes': self._nodes}

        # Write the system config file
        filename = self._rootdir + self._metadata['system_config_file']
        with open(filename, 'w') as yamlfile:
            yaml.dump(ydata, yamlfile)

    def _update_auto_config(self):
        """
        Write the auto configuration file with the new configuration data,
        input from the user.

        """

        # Initialize the yaml data
        nodes = {}
        with open(self._autoconfig_filename, 'r') as stream:
            try:
                ydata = yaml.load(stream)
                if 'nodes' in ydata:
                    nodes = ydata['nodes']
            except yaml.YAMLError as exc:
                print(exc)
                return

        for i in nodes.items():
            key = i[0]
            node = i[1]

            # Interfaces
            node['interfaces'] = {}
            for item in self._nodes[key]['interfaces'].items():
                port = item[0]
                interface = item[1]

                node['interfaces'][port] = {}
                addr = '{}'.format(interface['pci_address'])
                node['interfaces'][port]['pci_address'] = addr
                if 'mac_address' in interface:
                    node['interfaces'][port]['mac_address'] = \
                        interface['mac_address']

            if 'total_other_cpus' in self._nodes[key]['cpu']:
                node['cpu']['total_other_cpus'] = \
                    self._nodes[key]['cpu']['total_other_cpus']
            if 'total_vpp_cpus' in self._nodes[key]['cpu']:
                node['cpu']['total_vpp_cpus'] = \
                    self._nodes[key]['cpu']['total_vpp_cpus']
            if 'reserve_vpp_main_core' in self._nodes[key]['cpu']:
                node['cpu']['reserve_vpp_main_core'] = \
                    self._nodes[key]['cpu']['reserve_vpp_main_core']

            # TCP
            if 'active_open_sessions' in self._nodes[key]['tcp']:
                node['tcp']['active_open_sessions'] = \
                    self._nodes[key]['tcp']['active_open_sessions']
            if 'passive_open_sessions' in self._nodes[key]['tcp']:
                node['tcp']['passive_open_sessions'] = \
                    self._nodes[key]['tcp']['passive_open_sessions']

            # Huge pages
            node['hugepages']['total'] = self._nodes[key]['hugepages']['total']

        # Write the auto config config file
        with open(self._autoconfig_filename, 'w') as yamlfile:
            yaml.dump(ydata, yamlfile)

    def apply_huge_pages(self):
        """
        Apply the huge page config

        """

        for i in self._nodes.items():
            node = i[1]

            hpg = VppHugePageUtil(node)
            hpg.hugepages_dryrun_apply()

    @staticmethod
    def _apply_vpp_unix(node):
        """
        Apply the VPP Unix config

        :param node: Node dictionary with cpuinfo.
        :type node: dict
        """

        unix = '  nodaemon\n'
        if 'unix' not in node['vpp']:
            return ''

        unixv = node['vpp']['unix']
        if 'interactive' in unixv:
            interactive = unixv['interactive']
            if interactive is True:
                unix = '  interactive\n'

        return unix.rstrip('\n')

    @staticmethod
    def _apply_vpp_cpu(node):
        """
        Apply the VPP cpu config

        :param node: Node dictionary with cpuinfo.
        :type node: dict
        """

        # Get main core
        cpu = '\n'
        if 'vpp_main_core' in node['cpu']:
            vpp_main_core = node['cpu']['vpp_main_core']
        else:
            vpp_main_core = 0
        if vpp_main_core is not 0:
            cpu += '  main-core {}\n'.format(vpp_main_core)

        # Get workers
        vpp_workers = node['cpu']['vpp_workers']
        vpp_worker_len = len(vpp_workers)
        if vpp_worker_len > 0:
            vpp_worker_str = ''
            for i, worker in enumerate(vpp_workers):
                if i > 0:
                    vpp_worker_str += ','
                if worker[0] == worker[1]:
                    vpp_worker_str += "{}".format(worker[0])
                else:
                    vpp_worker_str += "{}-{}".format(worker[0], worker[1])

            cpu += '  corelist-workers {}\n'.format(vpp_worker_str)

        return cpu

    @staticmethod
    def _apply_vpp_devices(node):
        """
        Apply VPP PCI Device configuration to vpp startup.

        :param node: Node dictionary with cpuinfo.
        :type node: dict
        """

        devices = ''
        ports_per_numa = node['cpu']['ports_per_numa']
        total_mbufs = node['cpu']['total_mbufs']

        for item in ports_per_numa.items():
            value = item[1]
            interfaces = value['interfaces']

            # if 0 was specified for the number of vpp workers, use 1 queue
            num_rx_queues = None
            num_tx_queues = None
            if 'rx_queues' in value:
                num_rx_queues = value['rx_queues']
            if 'tx_queues' in value:
                num_tx_queues = value['tx_queues']

            num_rx_desc = None
            num_tx_desc = None

            # Create the devices string
            for interface in interfaces:
                pci_address = interface['pci_address']
                pci_address = pci_address.lstrip("'").rstrip("'")
                devices += '\n'
                devices += '  dev {} {{ \n'.format(pci_address)
                if num_rx_queues:
                    devices += '    num-rx-queues {}\n'.format(num_rx_queues)
                else:
                    devices += '    num-rx-queues {}\n'.format(1)
                if num_tx_queues:
                    devices += '    num-tx-queues {}\n'.format(num_tx_queues)
                if num_rx_desc:
                    devices += '    num-rx-desc {}\n'.format(num_rx_desc)
                if num_tx_desc:
                    devices += '    num-tx-desc {}\n'.format(num_tx_desc)
                devices += '  }'

        # If the total mbufs is not 0 or less than the default, set num-bufs
        logging.debug("Total mbufs: {}".format(total_mbufs))
        if total_mbufs is not 0 and total_mbufs > 16384:
            devices += '\n  num-mbufs {}'.format(total_mbufs)

        return devices

    @staticmethod
    def _calc_vpp_workers(node, vpp_workers, numa_node, other_cpus_end,
                          total_vpp_workers,
                          reserve_vpp_main_core):
        """
        Calculate the VPP worker information

        :param node: Node dictionary
        :param vpp_workers: List of VPP workers
        :param numa_node: Numa node
        :param other_cpus_end: The end of the cpus allocated for cores
        other than vpp
        :param total_vpp_workers: The number of vpp workers needed
        :param reserve_vpp_main_core: Is there a core needed for
        the vpp main core
        :type node: dict
        :type numa_node: int
        :type other_cpus_end: int
        :type total_vpp_workers: int
        :type reserve_vpp_main_core: bool
        :returns: Is a core still needed for the vpp main core
        :rtype: bool
        """

        # Can we fit the workers in one of these slices
        cpus = node['cpu']['cpus_per_node'][numa_node]
        for cpu in cpus:
            start = cpu[0]
            end = cpu[1]
            if start <= other_cpus_end:
                start = other_cpus_end + 1

            if reserve_vpp_main_core:
                start += 1

            workers_end = start + total_vpp_workers - 1

            if workers_end <= end:
                if reserve_vpp_main_core:
                    node['cpu']['vpp_main_core'] = start - 1
                reserve_vpp_main_core = False
                if total_vpp_workers:
                    vpp_workers.append((start, workers_end))
                break

        # We still need to reserve the main core
        if reserve_vpp_main_core:
            node['cpu']['vpp_main_core'] = other_cpus_end + 1

        return reserve_vpp_main_core

    @staticmethod
    def _calc_desc_and_queues(total_numa_nodes,
                              total_ports_per_numa,
                              total_rx_queues,
                              ports_per_numa_value):
        """
        Calculate the number of descriptors and queues

        :param total_numa_nodes: The total number of numa nodes
        :param total_ports_per_numa: The total number of ports for this
        numa node
        :param total_rx_queues: The total number of rx queues / port
        :param ports_per_numa_value: The value from the ports_per_numa
        dictionary
        :type total_numa_nodes: int
        :type total_ports_per_numa: int
        :type total_rx_queues: int
        :type ports_per_numa_value: dict
        :returns The total number of message buffers
        :rtype: int
        """

        # Get the number of rx queues
        rx_queues = max(1, total_rx_queues)
        tx_queues = rx_queues * total_numa_nodes + 1

        # Get the descriptor entries
        desc_entries = 1024
        ports_per_numa_value['rx_queues'] = rx_queues
        total_mbufs = (((rx_queues * desc_entries) +
                        (tx_queues * desc_entries)) *
                       total_ports_per_numa)
        total_mbufs = total_mbufs

        return total_mbufs

    @staticmethod
    def _create_ports_per_numa(node, interfaces):
        """
        Create a dictionary or ports per numa node
        :param node: Node dictionary
        :param interfaces: All the interfaces to be used by vpp
        :type node: dict
        :type interfaces: dict
        :returns: The ports per numa dictionary
        :rtype: dict
        """

        # Make a list of ports by numa node
        ports_per_numa = {}
        for item in interfaces.items():
            i = item[1]
            if i['numa_node'] not in ports_per_numa:
                ports_per_numa[i['numa_node']] = {'interfaces': []}
                ports_per_numa[i['numa_node']]['interfaces'].append(i)
            else:
                ports_per_numa[i['numa_node']]['interfaces'].append(i)
        node['cpu']['ports_per_numa'] = ports_per_numa

        return ports_per_numa

    def calculate_cpu_parameters(self):
        """
        Calculate the cpu configuration.

        """

        # Calculate the cpu parameters, needed for the
        # vpp_startup and grub configuration
        for i in self._nodes.items():
            node = i[1]

            # get total number of nic ports
            interfaces = node['interfaces']

            # Make a list of ports by numa node
            ports_per_numa = self._create_ports_per_numa(node, interfaces)

            # Get the number of cpus to skip, we never use the first cpu
            other_cpus_start = 1
            other_cpus_end = other_cpus_start + \
                node['cpu']['total_other_cpus'] - 1
            other_workers = None
            if other_cpus_end is not 0:
                other_workers = (other_cpus_start, other_cpus_end)
            node['cpu']['other_workers'] = other_workers

            # Allocate the VPP main core and workers
            vpp_workers = []
            reserve_vpp_main_core = node['cpu']['reserve_vpp_main_core']
            total_vpp_cpus = node['cpu']['total_vpp_cpus']
            total_rx_queues = node['cpu']['total_rx_queues']

            # If total_vpp_cpus is 0 or is less than the numa nodes with ports
            #  then we shouldn't get workers
            total_workers_node = 0
            if len(ports_per_numa):
                total_workers_node = total_vpp_cpus // len(ports_per_numa)
            total_main = 0
            if reserve_vpp_main_core:
                total_main = 1
            total_mbufs = 0
            if total_main + total_workers_node is not 0:
                for item in ports_per_numa.items():
                    numa_node = item[0]
                    value = item[1]

                    # Get the number of descriptors and queues
                    mbufs = self._calc_desc_and_queues(
                        len(ports_per_numa),
                        len(value['interfaces']), total_rx_queues, value)
                    total_mbufs += mbufs

                    # Get the VPP workers
                    reserve_vpp_main_core = self._calc_vpp_workers(
                        node, vpp_workers, numa_node,
                        other_cpus_end, total_workers_node,
                        reserve_vpp_main_core)

                total_mbufs *= 2.5
                total_mbufs = int(total_mbufs)
            else:
                total_mbufs = 0

            # Save the info
            node['cpu']['vpp_workers'] = vpp_workers
            node['cpu']['total_mbufs'] = total_mbufs

        # Write the config
        self.updateconfig()

    @staticmethod
    def _apply_vpp_tcp(node):
        """
        Apply the VPP Unix config

        :param node: Node dictionary with cpuinfo.
        :type node: dict
        """

        active_open_sessions = node['tcp']['active_open_sessions']
        aos = int(active_open_sessions)

        passive_open_sessions = node['tcp']['passive_open_sessions']
        pos = int(passive_open_sessions)

        # Generate the api-segment gid vpp sheit in any case
        if (aos + pos) == 0:
            tcp = '\n'.join([
                "api-segment {",
                "  gid vpp",
                "}"
            ])
            return tcp.rstrip('\n')

        tcp = '\n'.join([
            "# TCP stack-related configuration parameters",
            "# expecting {:d} client sessions, {:d} server sessions\n".format(
                aos, pos),
            "heapsize 4g\n",
            "api-segment {",
            "  global-size 2000M",
            "  api-size 1G",
            "}\n",

            "session {",
            "  event-queue-length {:d}".format(aos + pos),
            "  preallocated-sessions {:d}".format(aos + pos),
            "  v4-session-table-buckets {:d}".format((aos + pos) // 4),
            "  v4-session-table-memory 3g\n"
        ])
        if aos > 0:
            tcp = tcp + "  v4-halfopen-table-buckets {:d}".format(
                (aos + pos) // 4) + "\n"
            tcp = tcp + "  v4-halfopen-table-memory 3g\n"
            tcp = tcp + "  local-endpoints-table-buckets {:d}".format(
                (aos + pos) // 4) + "\n"
            tcp = tcp + "  local-endpoints-table-memory 3g\n"
        tcp = tcp + "}\n\n"

        tcp = tcp + "tcp {\n"
        tcp = tcp + "  preallocated-connections {:d}".format(aos + pos) + "\n"
        if aos > 0:
            tcp = tcp + "  preallocated-half-open-connections {:d}".format(
                aos) + "\n"
        tcp = tcp + "}\n\n"

        return tcp.rstrip('\n')

    def apply_vpp_startup(self):
        """
        Apply the vpp startup configration

        """

        # Apply the VPP startup configruation
        for i in self._nodes.items():
            node = i[1]

            # Get the startup file
            rootdir = node['rootdir']
            sfile = rootdir + node['vpp']['startup_config_file']

            # Get the devices
            devices = self._apply_vpp_devices(node)

            # Get the CPU config
            cpu = self._apply_vpp_cpu(node)

            # Get the unix config
            unix = self._apply_vpp_unix(node)

            # Get the TCP configuration, if any
            tcp = self._apply_vpp_tcp(node)

            # Make a backup if needed
            self._autoconfig_backup_file(sfile)

            # Get the template
            tfile = sfile + '.template'
            (ret, stdout, stderr) = \
                VPPUtil.exec_command('cat {}'.format(tfile))
            if ret != 0:
                raise RuntimeError('Executing cat command failed to node {}'.
                                   format(node['host']))
            startup = stdout.format(unix=unix,
                                    cpu=cpu,
                                    devices=devices,
                                    tcp=tcp)

            (ret, stdout, stderr) = \
                VPPUtil.exec_command('rm {}'.format(sfile))
            if ret != 0:
                logging.debug(stderr)

            cmd = "sudo cat > {0} << EOF\n{1}\n".format(sfile, startup)
            (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
            if ret != 0:
                raise RuntimeError('Writing config failed node {}'.
                                   format(node['host']))

    def apply_grub_cmdline(self):
        """
        Apply the grub cmdline

        """

        for i in self._nodes.items():
            node = i[1]

            # Get the isolated CPUs
            other_workers = node['cpu']['other_workers']
            vpp_workers = node['cpu']['vpp_workers']
            if 'vpp_main_core' in node['cpu']:
                vpp_main_core = node['cpu']['vpp_main_core']
            else:
                vpp_main_core = 0
            all_workers = []
            if other_workers is not None:
                all_workers = [other_workers]
            if vpp_main_core is not 0:
                all_workers += [(vpp_main_core, vpp_main_core)]
            all_workers += vpp_workers
            isolated_cpus = ''
            for idx, worker in enumerate(all_workers):
                if worker is None:
                    continue
                if idx > 0:
                    isolated_cpus += ','
                if worker[0] == worker[1]:
                    isolated_cpus += "{}".format(worker[0])
                else:
                    isolated_cpus += "{}-{}".format(worker[0], worker[1])

            vppgrb = VppGrubUtil(node)
            current_cmdline = vppgrb.get_current_cmdline()
            if 'grub' not in node:
                node['grub'] = {}
            node['grub']['current_cmdline'] = current_cmdline
            node['grub']['default_cmdline'] = \
                vppgrb.apply_cmdline(node, isolated_cpus)

        self.updateconfig()

    def get_hugepages(self):
        """
        Get the hugepage configuration

        """

        for i in self._nodes.items():
            node = i[1]

            hpg = VppHugePageUtil(node)
            max_map_count, shmmax = hpg.get_huge_page_config()
            node['hugepages']['max_map_count'] = max_map_count
            node['hugepages']['shmax'] = shmmax
            total, free, size, memtotal, memfree = hpg.get_actual_huge_pages()
            node['hugepages']['actual_total'] = total
            node['hugepages']['free'] = free
            node['hugepages']['size'] = size
            node['hugepages']['memtotal'] = memtotal
            node['hugepages']['memfree'] = memfree

        self.updateconfig()

    def get_grub(self):
        """
        Get the grub configuration

        """

        for i in self._nodes.items():
            node = i[1]

            vppgrb = VppGrubUtil(node)
            current_cmdline = vppgrb.get_current_cmdline()
            default_cmdline = vppgrb.get_default_cmdline()

            # Get the total number of isolated CPUs
            current_iso_cpus = 0
            iso_cpur = re.findall(r'isolcpus=[\w+\-,]+', current_cmdline)
            iso_cpurl = len(iso_cpur)
            if iso_cpurl > 0:
                iso_cpu_str = iso_cpur[0]
                iso_cpu_str = iso_cpu_str.split('=')[1]
                iso_cpul = iso_cpu_str.split(',')
                for iso_cpu in iso_cpul:
                    isocpuspl = iso_cpu.split('-')
                    if len(isocpuspl) is 1:
                        current_iso_cpus += 1
                    else:
                        first = int(isocpuspl[0])
                        second = int(isocpuspl[1])
                        if first == second:
                            current_iso_cpus += 1
                        else:
                            current_iso_cpus += second - first

            if 'grub' not in node:
                node['grub'] = {}
            node['grub']['current_cmdline'] = current_cmdline
            node['grub']['default_cmdline'] = default_cmdline
            node['grub']['current_iso_cpus'] = current_iso_cpus

        self.updateconfig()

    @staticmethod
    def _get_device(node):
        """
        Get the device configuration for a single node

        :param node: Node dictionary with cpuinfo.
        :type node: dict

        """

        vpp = VppPCIUtil(node)
        vpp.get_all_devices()

        # Save the device information
        node['devices'] = {}
        node['devices']['dpdk_devices'] = vpp.get_dpdk_devices()
        node['devices']['kernel_devices'] = vpp.get_kernel_devices()
        node['devices']['other_devices'] = vpp.get_other_devices()
        node['devices']['linkup_devices'] = vpp.get_link_up_devices()

    def get_devices_per_node(self):
        """
        Get the device configuration for all the nodes

        """

        for i in self._nodes.items():
            node = i[1]
            # Update the interface data

            self._get_device(node)

        self.updateconfig()

    @staticmethod
    def get_cpu_layout(node):
        """
        Get the cpu layout

        using lscpu -p get the cpu layout.
        Returns a list with each item representing a single cpu.

        :param node: Node dictionary.
        :type node: dict
        :returns: The cpu layout
        :rtype: list
        """

        cmd = 'lscpu -p'
        (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
        if ret != 0:
            raise RuntimeError('{} failed on node {} {}'.
                               format(cmd, node['host'], stderr))

        pcpus = []
        lines = stdout.split('\n')
        for line in lines:
            if line == '' or line[0] == '#':
                continue
            linesplit = line.split(',')
            layout = {'cpu': linesplit[0], 'core': linesplit[1],
                      'socket': linesplit[2], 'node': linesplit[3]}

            # cpu, core, socket, node
            pcpus.append(layout)

        return pcpus

    def get_cpu(self):
        """
        Get the cpu configuration

        """

        # Get the CPU layout
        CpuUtils.get_cpu_layout_from_all_nodes(self._nodes)

        for i in self._nodes.items():
            node = i[1]

            # Get the cpu layout
            layout = self.get_cpu_layout(node)
            node['cpu']['layout'] = layout

            cpuinfo = node['cpuinfo']
            smt_enabled = CpuUtils.is_smt_enabled(cpuinfo)
            node['cpu']['smt_enabled'] = smt_enabled

            # We don't want to write the cpuinfo
            node['cpuinfo'] = ""

        # Write the config
        self.updateconfig()

    def discover(self):
        """
        Get the current system configuration.

        """

        # Get the Huge Page configuration
        self.get_hugepages()

        # Get the device configuration
        self.get_devices_per_node()

        # Get the CPU configuration
        self.get_cpu()

        # Get the current grub cmdline
        self.get_grub()

    def _modify_cpu_questions(self, node, total_cpus, numa_nodes):
        """
        Ask the user questions related to the cpu configuration.

        :param node: Node dictionary
        :param total_cpus: The total number of cpus in the system
        :param numa_nodes: The list of numa nodes in the system
        :type node: dict
        :type total_cpus: int
        :type numa_nodes: list
        """

        print("\nYour system has {} core(s) and {} Numa Nodes.".
              format(total_cpus, len(numa_nodes)))
        print("To begin, we suggest not reserving any cores for "
              "VPP or other processes.")
        print("Then to improve performance start reserving cores and "
              "adding queues as needed.")

        # Leave 1 for the general system
        total_cpus -= 1
        max_vpp_cpus = min(total_cpus, 4)
        total_vpp_cpus = 0
        if max_vpp_cpus > 0:
            question = "\nHow many core(s) shall we reserve for " \
                       "VPP [0-{}][0]? ".format(max_vpp_cpus)
            total_vpp_cpus = self._ask_user_range(question, 0, max_vpp_cpus, 0)
            node['cpu']['total_vpp_cpus'] = total_vpp_cpus

        total_other_cpus = 0
        max_other_cores = total_cpus - total_vpp_cpus
        if max_other_cores > 0:
            question = 'How many core(s) do you want to reserve for ' \
                       'processes other than VPP? [0-{}][0]? '. \
                       format(str(max_other_cores))
            total_other_cpus = self._ask_user_range(question, 0, max_other_cores, 0)
            node['cpu']['total_other_cpus'] = total_other_cpus

        max_main_cpus = total_cpus - total_vpp_cpus - total_other_cpus
        reserve_vpp_main_core = False
        if max_main_cpus > 0:
            question = "Should we reserve 1 core for the VPP Main thread? "
            question += "[y/N]? "
            answer = self._ask_user_yn(question, 'n')
            if answer == 'y':
                reserve_vpp_main_core = True
            node['cpu']['reserve_vpp_main_core'] = reserve_vpp_main_core
            node['cpu']['vpp_main_core'] = 0

        question = "How many RX queues per port shall we use for " \
                   "VPP [1-4][1]? ".format(max_vpp_cpus)
        total_rx_queues = self._ask_user_range(question, 1, 4, 1)
        node['cpu']['total_rx_queues'] = total_rx_queues

    def modify_cpu(self, ask_questions=True):
        """
        Modify the cpu configuration, asking for the user for the values.

        :param ask_questions: When true ask the user for config parameters

        """

        # Get the CPU layout
        CpuUtils.get_cpu_layout_from_all_nodes(self._nodes)

        for i in self._nodes.items():
            node = i[1]
            total_cpus = 0
            total_cpus_per_slice = 0
            cpus_per_node = {}
            numa_nodes = []
            cores = []
            cpu_layout = self.get_cpu_layout(node)

            # Assume the number of cpus per slice is always the same as the
            # first slice
            first_node = '0'
            for cpu in cpu_layout:
                if cpu['node'] != first_node:
                    break
                total_cpus_per_slice += 1

            # Get the total number of cpus, cores, and numa nodes from the
            # cpu layout
            for cpul in cpu_layout:
                numa_node = cpul['node']
                core = cpul['core']
                cpu = cpul['cpu']
                total_cpus += 1

                if numa_node not in cpus_per_node:
                    cpus_per_node[numa_node] = []
                cpuperslice = int(cpu) % total_cpus_per_slice
                if cpuperslice == 0:
                    cpus_per_node[numa_node].append((int(cpu), int(cpu) +
                                                     total_cpus_per_slice - 1))
                if numa_node not in numa_nodes:
                    numa_nodes.append(numa_node)
                if core not in cores:
                    cores.append(core)
            node['cpu']['cpus_per_node'] = cpus_per_node

            # Ask the user some questions
            if ask_questions and total_cpus >= 4:
                self._modify_cpu_questions(node, total_cpus, numa_nodes)

            # Populate the interfaces with the numa node
            if 'interfaces' in node:
                ikeys = node['interfaces'].keys()
                VPPUtil.get_interfaces_numa_node(node, *tuple(ikeys))

            # We don't want to write the cpuinfo
            node['cpuinfo'] = ""

        # Write the configs
        self._update_auto_config()
        self.updateconfig()

    def _modify_other_devices(self, node,
                              other_devices, kernel_devices, dpdk_devices):
        """
        Modify the devices configuration, asking for the user for the values.

        """

        odevices_len = len(other_devices)
        if odevices_len > 0:
            print("\nThese device(s) are currently NOT being used "
                  "by VPP or the OS.\n")
            VppPCIUtil.show_vpp_devices(other_devices, show_interfaces=False)
            question = "\nWould you like to give any of these devices"
            question += " back to the OS [Y/n]? "
            answer = self._ask_user_yn(question, 'Y')
            if answer == 'y':
                vppd = {}
                for dit in other_devices.items():
                    dvid = dit[0]
                    device = dit[1]
                    question = "Would you like to use device {} for". \
                        format(dvid)
                    question += " the OS [y/N]? "
                    answer = self._ask_user_yn(question, 'n')
                    if answer == 'y':
                        if 'unused' in device and len(
                                device['unused']) != 0 and \
                                device['unused'][0] != '':
                            driver = device['unused'][0]
                            ret = VppPCIUtil.bind_vpp_device(
                                node, driver, dvid)
                            if ret:
                                logging.debug(
                                    'Could not bind device {}'.format(dvid))
                            else:
                                vppd[dvid] = device
                for dit in vppd.items():
                    dvid = dit[0]
                    device = dit[1]
                    kernel_devices[dvid] = device
                    del other_devices[dvid]

        odevices_len = len(other_devices)
        if odevices_len > 0:
            print("\nThese device(s) are still NOT being used "
                  "by VPP or the OS.\n")
            VppPCIUtil.show_vpp_devices(other_devices, show_interfaces=False)
            question = "\nWould you like use any of these for VPP [y/N]? "
            answer = self._ask_user_yn(question, 'N')
            if answer == 'y':
                vppd = {}
                for dit in other_devices.items():
                    dvid = dit[0]
                    device = dit[1]
                    question = "Would you like to use device {} ".format(dvid)
                    question += "for VPP [y/N]? "
                    answer = self._ask_user_yn(question, 'n')
                    if answer == 'y':
                        vppd[dvid] = device
                for dit in vppd.items():
                    dvid = dit[0]
                    device = dit[1]
                    if 'unused' in device and len(device['unused']) != 0 and \
                            device['unused'][0] != '':
                        driver = device['unused'][0]
                        logging.debug(
                            'Binding device {} to driver {}'.format(dvid,
                                                                    driver))
                        ret = VppPCIUtil.bind_vpp_device(node, driver, dvid)
                        if ret:
                            logging.debug(
                                'Could not bind device {}'.format(dvid))
                        else:
                            dpdk_devices[dvid] = device
                            del other_devices[dvid]

    def update_interfaces_config(self):
        """
        Modify the interfaces directly from the config file.

        """

        for i in self._nodes.items():
            node = i[1]
            devices = node['devices']
            all_devices = devices['other_devices']
            all_devices.update(devices['dpdk_devices'])
            all_devices.update(devices['kernel_devices'])

            current_ifcs = {}
            interfaces = {}
            if 'interfaces' in node:
                current_ifcs = node['interfaces']
            if current_ifcs:
                for ifc in current_ifcs.values():
                    dvid = ifc['pci_address']
                    if dvid in all_devices:
                        VppPCIUtil.vpp_create_interface(interfaces, dvid,
                                                        all_devices[dvid])
            node['interfaces'] = interfaces

        self.updateconfig()

    def modify_devices(self):
        """
        Modify the devices configuration, asking for the user for the values.

        """

        for i in self._nodes.items():
            node = i[1]
            devices = node['devices']
            other_devices = devices['other_devices']
            kernel_devices = devices['kernel_devices']
            dpdk_devices = devices['dpdk_devices']

            if other_devices:
                self._modify_other_devices(node, other_devices,
                                           kernel_devices, dpdk_devices)

                # Get the devices again for this node
                self._get_device(node)
                devices = node['devices']
                kernel_devices = devices['kernel_devices']
                dpdk_devices = devices['dpdk_devices']

            klen = len(kernel_devices)
            if klen > 0:
                print("\nThese devices are safe to be used with VPP.\n")
                VppPCIUtil.show_vpp_devices(kernel_devices)
                question = "\nWould you like to use any of these " \
                           "device(s) for VPP [y/N]? "
                answer = self._ask_user_yn(question, 'n')
                if answer == 'y':
                    vppd = {}
                    for dit in kernel_devices.items():
                        dvid = dit[0]
                        device = dit[1]
                        question = "Would you like to use device {} ".format(dvid)
                        question += "for VPP [y/N]? "
                        answer = self._ask_user_yn(question, 'n')
                        if answer == 'y':
                            vppd[dvid] = device
                    for dit in vppd.items():
                        dvid = dit[0]
                        device = dit[1]
                        if 'unused' in device and len(
                                device['unused']) != 0 and device['unused'][
                                0] != '':
                            driver = device['unused'][0]
                            question = "Would you like to bind the driver {} for {} [y/N]? ".format(driver, dvid)
                            answer = self._ask_user_yn(question, 'n')
                            if answer == 'y':
                                logging.debug('Binding device {} to driver {}'.format(dvid, driver))
                                ret = VppPCIUtil.bind_vpp_device(node, driver, dvid)
                                if ret:
                                    logging.debug('Could not bind device {}'.format(dvid))
                        dpdk_devices[dvid] = device
                        del kernel_devices[dvid]

            dlen = len(dpdk_devices)
            if dlen > 0:
                print("\nThese device(s) are already using DPDK.\n")
                VppPCIUtil.show_vpp_devices(dpdk_devices,
                                            show_interfaces=False)
                question = "\nWould you like to remove any of "
                question += "these device(s) [y/N]? "
                answer = self._ask_user_yn(question, 'n')
                if answer == 'y':
                    vppdl = {}
                    for dit in dpdk_devices.items():
                        dvid = dit[0]
                        device = dit[1]
                        question = "Would you like to remove {} [y/N]? ". \
                            format(dvid)
                        answer = self._ask_user_yn(question, 'n')
                        if answer == 'y':
                            vppdl[dvid] = device
                    for dit in vppdl.items():
                        dvid = dit[0]
                        device = dit[1]
                        if 'unused' in device and len(
                                device['unused']) != 0 and device['unused'][
                                0] != '':
                            driver = device['unused'][0]
                            logging.debug(
                                'Binding device {} to driver {}'.format(
                                    dvid, driver))
                            ret = VppPCIUtil.bind_vpp_device(node, driver,
                                                             dvid)
                            if ret:
                                logging.debug(
                                    'Could not bind device {}'.format(dvid))
                            else:
                                kernel_devices[dvid] = device
                                del dpdk_devices[dvid]

            interfaces = {}
            for dit in dpdk_devices.items():
                dvid = dit[0]
                device = dit[1]
                VppPCIUtil.vpp_create_interface(interfaces, dvid, device)
            node['interfaces'] = interfaces

        self._update_auto_config()
        self.updateconfig()

    def modify_huge_pages(self):
        """
        Modify the huge page configuration, asking for the user for the values.

        """

        for i in self._nodes.items():
            node = i[1]

            total = node['hugepages']['actual_total']
            free = node['hugepages']['free']
            size = node['hugepages']['size']
            memfree = node['hugepages']['memfree'].split(' ')[0]
            hugesize = int(size.split(' ')[0])
            # The max number of huge pages should be no more than
            # 70% of total free memory
            maxpages = (int(memfree) * MAX_PERCENT_FOR_HUGE_PAGES // 100) // \
                hugesize
            print("\nThere currently {} {} huge pages free.".format(
                free, size))
            question = "Do you want to reconfigure the number of " \
                       "huge pages [y/N]? "
            answer = self._ask_user_yn(question, 'n')
            if answer == 'n':
                node['hugepages']['total'] = total
                continue

            print("\nThere currently a total of {} huge pages.".
                  format(total))
            question = "How many huge pages do you want [{} - {}][{}]? ". \
                format(MIN_TOTAL_HUGE_PAGES, maxpages, MIN_TOTAL_HUGE_PAGES)
            answer = self._ask_user_range(question, 1024, maxpages, 1024)
            node['hugepages']['total'] = str(answer)

        # Update auto-config.yaml
        self._update_auto_config()

        # Rediscover just the hugepages
        self.get_hugepages()

    def get_tcp_params(self):
        """
        Get the tcp configuration

        """
        # maybe nothing to do here?
        self.updateconfig()

    def acquire_tcp_params(self):
        """
        Ask the user for TCP stack configuration parameters

        """

        for i in self._nodes.items():
            node = i[1]

            question = "\nHow many active-open / tcp client sessions are " \
                       "expected [0-10000000][0]? "
            answer = self._ask_user_range(question, 0, 10000000, 0)
            # Less than 10K is equivalent to 0
            if int(answer) < 10000:
                answer = 0
            node['tcp']['active_open_sessions'] = answer

            question = "How many passive-open / tcp server sessions are " \
                       "expected [0-10000000][0]? "
            answer = self._ask_user_range(question, 0, 10000000, 0)
            # Less than 10K is equivalent to 0
            if int(answer) < 10000:
                answer = 0
            node['tcp']['passive_open_sessions'] = answer

        # Update auto-config.yaml
        self._update_auto_config()

        # Rediscover tcp parameters
        self.get_tcp_params()

    @staticmethod
    def patch_qemu(node):
        """
        Patch qemu with the correct patches.

        :param node: Node dictionary
        :type node: dict
        """

        print('\nWe are patching the node "{}":\n'.format(node['host']))
        QemuUtils.build_qemu(node, force_install=True, apply_patch=True)

    @staticmethod
    def cpu_info(node):
        """
        print the CPU information

        """

        cpu = CpuUtils.get_cpu_info_per_node(node)

        item = 'Model name'
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))
        item = 'CPU(s)'
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))
        item = 'Thread(s) per core'
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))
        item = 'Core(s) per socket'
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))
        item = 'Socket(s)'
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))
        item = 'NUMA node(s)'
        numa_nodes = 0
        if item in cpu:
            numa_nodes = int(cpu[item])
        for i in range(0, numa_nodes):
            item = "NUMA node{} CPU(s)".format(i)
            print("{:>20}:    {}".format(item, cpu[item]))
        item = 'CPU max MHz'
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))
        item = 'CPU min MHz'
        if item in cpu:
            print("{:>20}:    {}".format(item, cpu[item]))

        if node['cpu']['smt_enabled']:
            smt = 'Enabled'
        else:
            smt = 'Disabled'
        print("{:>20}:    {}".format('SMT', smt))

        # VPP Threads
        print("\nVPP Threads: (Name: Cpu Number)")
        vpp_processes = cpu['vpp_processes']
        for i in vpp_processes.items():
            print("  {:10}: {:4}".format(i[0], i[1]))

    @staticmethod
    def device_info(node):
        """
        Show the device information.

        """

        if 'cpu' in node and 'total_mbufs' in node['cpu']:
            total_mbufs = node['cpu']['total_mbufs']
            if total_mbufs is not 0:
                print("Total Number of Buffers: {}".format(total_mbufs))

        vpp = VppPCIUtil(node)
        vpp.get_all_devices()
        linkup_devs = vpp.get_link_up_devices()
        if len(linkup_devs):
            print("\nDevices with link up (can not be used with VPP):")
            vpp.show_vpp_devices(linkup_devs, show_header=False)
            # for dev in linkup_devs:
            #    print ("    " + dev)
        kernel_devs = vpp.get_kernel_devices()
        if len(kernel_devs):
            print("\nDevices bound to kernel drivers:")
            vpp.show_vpp_devices(kernel_devs, show_header=False)
        else:
            print("\nNo devices bound to kernel drivers")

        dpdk_devs = vpp.get_dpdk_devices()
        if len(dpdk_devs):
            print("\nDevices bound to DPDK drivers:")
            vpp.show_vpp_devices(dpdk_devs, show_interfaces=True,
                                 show_header=False)
        else:
            print("\nNo devices bound to DPDK drivers")

        other_devs = vpp.get_other_devices()
        if len(other_devs):
            print("\nDevices not bound to Kernel or DPDK drivers:")
            vpp.show_vpp_devices(other_devs, show_interfaces=True,
                                 show_header=False)
        else:
            print("\nNo devices not bound to Kernel or DPDK drivers")

        vpputl = VPPUtil()
        interfaces = vpputl.get_hardware(node)
        if interfaces == {}:
            return

        print("\nDevices in use by VPP:")

        if len(interfaces.items()) < 2:
            print("None")
            return

        print("{:30} {:4} {:4} {:7} {:4} {:7}".
              format('Name', 'Numa', 'RXQs',
                     'RXDescs', 'TXQs', 'TXDescs'))
        for intf in sorted(interfaces.items()):
            name = intf[0]
            value = intf[1]
            if name == 'local0':
                continue
            numa = rx_qs = rx_ds = tx_qs = tx_ds = ''
            if 'numa' in value:
                numa = int(value['numa'])
            if 'rx queues' in value:
                rx_qs = int(value['rx queues'])
            if 'rx descs' in value:
                rx_ds = int(value['rx descs'])
            if 'tx queues' in value:
                tx_qs = int(value['tx queues'])
            if 'tx descs' in value:
                tx_ds = int(value['tx descs'])

            print("{:30} {:>4} {:>4} {:>7} {:>4} {:>7}".
                  format(name, numa, rx_qs, rx_ds, tx_qs, tx_ds))

    @staticmethod
    def hugepage_info(node):
        """
        Show the huge page information.

        """

        hpg = VppHugePageUtil(node)
        hpg.show_huge_pages()

    @staticmethod
    def has_interfaces(node):
        """
        Check for interfaces, return tru if there is at least one

        :returns: boolean
        """
        if 'interfaces' in node and len(node['interfaces']):
            return True
        else:
            return False

    @staticmethod
    def min_system_resources(node):
        """
        Check the system for basic minimum resources, return true if
        there is enough.

        :returns: boolean
        """

        min_sys_res = True

        # CPUs
        if 'layout' in node['cpu']:
            total_cpus = len(node['cpu']['layout'])
            if total_cpus < 2:
                print("\nThere is only {} CPU(s) available on this system. "
                      "This is not enough to run VPP.".format(total_cpus))
                min_sys_res = False

        # System Memory
        if 'free' in node['hugepages'] and \
                'memfree' in node['hugepages'] and \
                'size' in node['hugepages']:
            free = node['hugepages']['free']
            memfree = float(node['hugepages']['memfree'].split(' ')[0])
            hugesize = float(node['hugepages']['size'].split(' ')[0])

            memhugepages = MIN_TOTAL_HUGE_PAGES * hugesize
            percentmemhugepages = (memhugepages / memfree) * 100
            if free is '0' and \
                    percentmemhugepages > MAX_PERCENT_FOR_HUGE_PAGES:
                print(
                    "\nThe System has only {} of free memory. You will not "
                    "be able to allocate enough Huge Pages for VPP.".format(
                        int(
                            memfree))
                )
                min_sys_res = False

        return min_sys_res

    def sys_info(self):
        """
        Print the system information

        """

        for i in self._nodes.items():
            print("\n==============================")
            name = i[0]
            node = i[1]

            print("NODE: {}\n".format(name))

            # CPU
            print("CPU:")
            self.cpu_info(node)

            # Grub
            print("\nGrub Command Line:")
            if 'grub' in node:
                print("  Current: {}".format(
                    node['grub']['current_cmdline']))
                print("  Configured: {}".format(
                    node['grub']['default_cmdline']))

            # Huge Pages
            print("\nHuge Pages:")
            self.hugepage_info(node)

            # Devices
            print("\nDevices:")
            self.device_info(node)

            # Status
            print("\nVPP Service Status:")
            state, errors = VPPUtil.status(node)
            print("  {}".format(state))
            for e in errors:
                print("  {}".format(e))

            # Minimum system resources
            self.min_system_resources(node)

            print("\n==============================")

    def _ipv4_interface_setup_questions(self, node):
        """
        Ask the user some questions and get a list of interfaces
        and IPv4 addresses associated with those interfaces

        :param node: Node dictionary.
        :type node: dict
        :returns: A list or interfaces with ip addresses
        :rtype: dict
        """

        vpputl = VPPUtil()
        interfaces = vpputl.get_hardware(node)
        if interfaces == {}:
            return

        interfaces_with_ip = []
        for intf in sorted(interfaces.items()):
            name = intf[0]
            if name == 'local0':
                continue

            question = "Would you like add address to " \
                       "interface {} [Y/n]? ".format(name)
            answer = self._ask_user_yn(question, 'y')
            if answer == 'y':
                address = {}
                addr = self._ask_user_ipv4()
                address['name'] = name
                address['addr'] = addr
                interfaces_with_ip.append(address)

        return interfaces_with_ip

    def ipv4_interface_setup(self):
        """
        After asking the user some questions, get a list of interfaces
        and IPv4 addresses associated with those interfaces

        """

        for i in self._nodes.items():
            node = i[1]

            # Show the current interfaces with IP addresses
            current_ints = VPPUtil.get_int_ip(node)
            if current_ints is not {}:
                print("\nThese are the current interfaces with IP addresses:")
                for items in sorted(current_ints.items()):
                    name = items[0]
                    value = items[1]
                    if 'address' not in value:
                        address = 'Not Set'
                    else:
                        address = value['address']
                    print("{:30} {:20} {:10}".format(name, address,
                                                     value['state']))
                question = "\nWould you like to keep this configuration " \
                           "[Y/n]? "
                answer = self._ask_user_yn(question, 'y')
                if answer == 'y':
                    continue
            else:
                print("\nThere are currently no interfaces with IP "
                      "addresses.")

            # Create a script that add the ip addresses to the interfaces
            # and brings the interfaces up
            ints_with_addrs = self._ipv4_interface_setup_questions(node)
            content = ''
            for ints in ints_with_addrs:
                name = ints['name']
                addr = ints['addr']
                setipstr = 'set int ip address {} {}\n'.format(name, addr)
                setintupstr = 'set int state {} up\n'.format(name)
                content += setipstr + setintupstr

            # Write the content to the script
            rootdir = node['rootdir']
            filename = rootdir + '/vpp/vpp-config/scripts/set_int_ipv4_and_up'
            with open(filename, 'w+') as sfile:
                sfile.write(content)

            # Execute the script
            cmd = 'vppctl exec {}'.format(filename)
            (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
            if ret != 0:
                logging.debug(stderr)

            print("\nA script as been created at {}".format(filename))
            print("This script can be run using the following:")
            print("vppctl exec {}\n".format(filename))

    def _create_vints_questions(self, node):
        """
        Ask the user some questions and get a list of interfaces
        and IPv4 addresses associated with those interfaces

        :param node: Node dictionary.
        :type node: dict
        :returns: A list or interfaces with ip addresses
        :rtype: list
        """

        vpputl = VPPUtil()
        interfaces = vpputl.get_hardware(node)
        if interfaces == {}:
            return []

        # First delete all the Virtual interfaces
        for intf in sorted(interfaces.items()):
            name = intf[0]
            if name[:7] == 'Virtual':
                cmd = 'vppctl delete vhost-user {}'.format(name)
                (ret, stdout, stderr) = vpputl.exec_command(cmd)
                if ret != 0:
                    logging.debug('{} failed on node {} {}'.format(
                        cmd, node['host'], stderr))

        # Create a virtual interface, for each interface the user wants to use
        interfaces = vpputl.get_hardware(node)
        if interfaces == {}:
            return []
        interfaces_with_virtual_interfaces = []
        inum = 1
        for intf in sorted(interfaces.items()):
            name = intf[0]
            if name == 'local0':
                continue

            question = "Would you like connect this interface {} to " \
                       "the VM [Y/n]? ".format(name)
            answer = self._ask_user_yn(question, 'y')
            if answer == 'y':
                sockfilename = '/var/run/vpp/{}.sock'.format(
                    name.replace('/', '_'))
                if os.path.exists(sockfilename):
                    os.remove(sockfilename)
                cmd = 'vppctl create vhost-user socket {} server'.format(
                    sockfilename)
                (ret, stdout, stderr) = vpputl.exec_command(cmd)
                if ret != 0:
                    raise RuntimeError(
                        "Couldn't execute the command {}, {}.".format(cmd,
                                                                      stderr))
                vintname = stdout.rstrip('\r\n')

                cmd = 'chmod 777 {}'.format(sockfilename)
                (ret, stdout, stderr) = vpputl.exec_command(cmd)
                if ret != 0:
                    raise RuntimeError(
                        "Couldn't execute the command {}, {}.".format(cmd,
                                                                      stderr))

                interface = {'name': name,
                             'virtualinterface': '{}'.format(vintname),
                             'bridge': '{}'.format(inum)}
                inum += 1
                interfaces_with_virtual_interfaces.append(interface)

        return interfaces_with_virtual_interfaces

    def create_and_bridge_virtual_interfaces(self):
        """
        After asking the user some questions, create a VM and connect
        the interfaces to VPP interfaces

        """

        for i in self._nodes.items():
            node = i[1]

            # Show the current bridge and interface configuration
            print("\nThis the current bridge configuration:")
            VPPUtil.show_bridge(node)
            question = "\nWould you like to keep this configuration [Y/n]? "
            answer = self._ask_user_yn(question, 'y')
            if answer == 'y':
                continue

            # Create a script that builds a bridge configuration with
            # physical interfaces and virtual interfaces
            ints_with_vints = self._create_vints_questions(node)
            content = ''
            for intf in ints_with_vints:
                vhoststr = '\n'.join([
                    'comment { The following command creates the socket }',
                    'comment { and returns a virtual interface }',
                    'comment {{ create vhost-user socket '
                    '/var/run/vpp/sock{}.sock server }}\n'.format(
                        intf['bridge'])
                ])

                setintdnstr = 'set interface state {} down\n'.format(
                    intf['name'])

                setintbrstr = 'set interface l2 bridge {} {}\n'.format(
                    intf['name'], intf['bridge'])
                setvintbrstr = 'set interface l2 bridge {} {}\n'.format(
                    intf['virtualinterface'], intf['bridge'])

                # set interface state VirtualEthernet/0/0/0 up
                setintvststr = 'set interface state {} up\n'.format(
                    intf['virtualinterface'])

                # set interface state VirtualEthernet/0/0/0 down
                setintupstr = 'set interface state {} up\n'.format(
                    intf['name'])

                content += vhoststr + setintdnstr + setintbrstr + \
                    setvintbrstr + setintvststr + setintupstr

            # Write the content to the script
            rootdir = node['rootdir']
            filename = rootdir + \
                '/vpp/vpp-config/scripts/create_vms_and_connect_to_vpp'
            with open(filename, 'w+') as sfile:
                sfile.write(content)

            # Execute the script
            cmd = 'vppctl exec {}'.format(filename)
            (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
            if ret != 0:
                logging.debug(stderr)

            print("\nA script as been created at {}".format(filename))
            print("This script can be run using the following:")
            print("vppctl exec {}\n".format(filename))

    def _iperf_vm_questions(self, node):
        """
        Ask the user some questions and get a list of interfaces
        and IPv4 addresses associated with those interfaces

        :param node: Node dictionary.
        :type node: dict
        :returns: A list or interfaces with ip addresses
        :rtype: list
        """

        vpputl = VPPUtil()
        interfaces = vpputl.get_hardware(node)
        if interfaces == {}:
            return []

        # First delete all the Virtual interfaces
        for intf in sorted(interfaces.items()):
            name = intf[0]
            if name[:7] == 'Virtual':
                cmd = 'vppctl delete vhost-user {}'.format(name)
                (ret, stdout, stderr) = vpputl.exec_command(cmd)
                if ret != 0:
                    logging.debug('{} failed on node {} {}'.format(
                        cmd, node['host'], stderr))

        # Create a virtual interface, for each interface the user wants to use
        interfaces = vpputl.get_hardware(node)
        if interfaces == {}:
            return []
        interfaces_with_virtual_interfaces = []
        inum = 1

        while True:
            print('\nPlease pick one interface to connect to the iperf VM.')
            for intf in sorted(interfaces.items()):
                name = intf[0]
                if name == 'local0':
                    continue

                question = "Would you like connect this interface {} to " \
                           "the VM [y/N]? ".format(name)
                answer = self._ask_user_yn(question, 'n')
                if answer == 'y':
                    self._sockfilename = '/var/run/vpp/{}.sock'.format(
                        name.replace('/', '_'))
                    if os.path.exists(self._sockfilename):
                        os.remove(self._sockfilename)
                    cmd = 'vppctl create vhost-user socket {} server'.format(
                        self._sockfilename)
                    (ret, stdout, stderr) = vpputl.exec_command(cmd)
                    if ret != 0:
                        raise RuntimeError(
                            "Couldn't execute the command {}, {}.".format(
                                cmd, stderr))
                    vintname = stdout.rstrip('\r\n')

                    cmd = 'chmod 777 {}'.format(self._sockfilename)
                    (ret, stdout, stderr) = vpputl.exec_command(cmd)
                    if ret != 0:
                        raise RuntimeError(
                            "Couldn't execute the command {}, {}.".format(
                                cmd, stderr))

                    interface = {'name': name,
                                 'virtualinterface': '{}'.format(vintname),
                                 'bridge': '{}'.format(inum)}
                    inum += 1
                    interfaces_with_virtual_interfaces.append(interface)
                    return interfaces_with_virtual_interfaces

    def create_and_bridge_iperf_virtual_interface(self):
        """
        After asking the user some questions, and create and bridge a
        virtual interface to be used with iperf VM

        """

        for i in self._nodes.items():
            node = i[1]

            # Show the current bridge and interface configuration
            print("\nThis the current bridge configuration:")
            ifaces = VPPUtil.show_bridge(node)
            question = "\nWould you like to keep this configuration [Y/n]? "
            answer = self._ask_user_yn(question, 'y')
            if answer == 'y':
                self._sockfilename = '/var/run/vpp/{}.sock'.format(
                    ifaces[0]['name'].replace('/', '_'))
                if os.path.exists(self._sockfilename):
                    continue

            # Create a script that builds a bridge configuration with
            # physical interfaces and virtual interfaces
            ints_with_vints = self._iperf_vm_questions(node)
            content = ''
            for intf in ints_with_vints:
                vhoststr = '\n'.join([
                    'comment { The following command creates the socket }',
                    'comment { and returns a virtual interface }',
                    'comment {{ create vhost-user socket '
                    '/var/run/vpp/sock{}.sock server }}\n'.format(
                        intf['bridge'])
                ])

                setintdnstr = 'set interface state {} down\n'.format(
                    intf['name'])

                setintbrstr = 'set interface l2 bridge {} {}\n'.format(
                    intf['name'], intf['bridge'])
                setvintbrstr = 'set interface l2 bridge {} {}\n'.format(
                    intf['virtualinterface'], intf['bridge'])

                # set interface state VirtualEthernet/0/0/0 up
                setintvststr = 'set interface state {} up\n'.format(
                    intf['virtualinterface'])

                # set interface state VirtualEthernet/0/0/0 down
                setintupstr = 'set interface state {} up\n'.format(
                    intf['name'])

                content += vhoststr + setintdnstr + setintbrstr + \
                    setvintbrstr + setintvststr + setintupstr

            # Write the content to the script
            rootdir = node['rootdir']
            filename = rootdir + '/vpp/vpp-config/scripts/create_iperf_vm'
            with open(filename, 'w+') as sfile:
                sfile.write(content)

            # Execute the script
            cmd = 'vppctl exec {}'.format(filename)
            (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
            if ret != 0:
                logging.debug(stderr)

            print("\nA script as been created at {}".format(filename))
            print("This script can be run using the following:")
            print("vppctl exec {}\n".format(filename))

    @staticmethod
    def destroy_iperf_vm(name):
        """
        After asking the user some questions, create a VM and connect
        the interfaces to VPP interfaces

        :param name: The name of the VM to be be destroyed
        :type name: str
        """

        cmd = 'virsh list'
        (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
        if ret != 0:
            logging.debug(stderr)
            raise RuntimeError(
                "Couldn't execute the command {} : {}".format(cmd, stderr))

        if re.findall(name, stdout):
            cmd = 'virsh destroy {}'.format(name)
            (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
            if ret != 0:
                logging.debug(stderr)
                raise RuntimeError(
                    "Couldn't execute the command {} : {}".format(
                        cmd, stderr))

    def create_iperf_vm(self, vmname):
        """
        After asking the user some questions, create a VM and connect
        the interfaces to VPP interfaces

        """

        # Read the iperf VM template file
        distro = VPPUtil.get_linux_distro()
        if distro[0] == 'Ubuntu':
            tfilename = \
                '{}/vpp/vpp-config/configs/iperf-ubuntu.xml.template'.format(
                    self._rootdir)
        else:
            tfilename = \
                '{}/vpp/vpp-config/configs/iperf-centos.xml.template'.format(
                    self._rootdir)

        with open(tfilename, 'r') as tfile:
            tcontents = tfile.read()
        tfile.close()

        # Add the variables
        imagename = '{}/vpp/vpp-config/{}'.format(
            self._rootdir, IPERFVM_IMAGE)
        isoname = '{}/vpp/vpp-config/{}'.format(self._rootdir, IPERFVM_ISO)
        tcontents = tcontents.format(vmname=vmname, imagename=imagename,
                                     isoname=isoname,
                                     vhostsocketname=self._sockfilename)

        # Write the xml
        ifilename = '{}/vpp/vpp-config/{}'.format(self._rootdir, IPERFVM_XML)
        with open(ifilename, 'w+') as ifile:
            ifile.write(tcontents)
        ifile.close()

        cmd = 'virsh create {}'.format(ifilename)
        (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
        if ret != 0:
            logging.debug(stderr)
            raise RuntimeError(
                "Couldn't execute the command {} : {}".format(cmd, stderr))
