Source code for taf.testlib.dev_vethcross

# Copyright (c) 2011 - 2017, Intel Corporation.
#
# 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.


"""``dev_vethcross.py``

`Cross connection based on creating Virtual Ethernet devices`

"""

from subprocess import Popen, PIPE

from . import loggers

from .custom_exceptions import CrossException
from .dev_basecross import GenericXConnectMixin


[docs]class VethCross(GenericXConnectMixin): """Xconnect based on creating virtual ethernet interfaces. Notes: It is used for simulated environment. """ class_logger = loggers.ClassLogger()
[docs] def __init__(self, config, opts): """Initialize VethCross class. Args: config(dict): Configuration information. opts(OptionParser): py.test config.option object which contains all py.test cli options. """ self.class_logger.info("VethCross is selected.") self.id = config['id'] self.type = config['instance_type'] self.opts = opts # Connections info: self.connections = [] # Do xconnect on create? self.autoconnect = config['autoconnect'] if "autoconnect" in config else True self.related_conf = {} if "related_conf" in list(config.keys()): self.related_conf = config['related_conf'] # Set On/Off(True/False) status according to get_only option. self.status = self.opts.get_only # Aliases to support different connection types: # Remote TG connections self.netns_tg_nns = self.netns_netns self.nns_tg_nns = self.netns_netns self.generic_tg_nns = self.generic_netns self.tg_nns = self.generic_netns self.generic_tg = self.generic_generic self.tg = self.generic_generic
[docs] def get_name_port(self, dev_id, port_id): """Get port name. Args: dev_id(int): Device ID port_id(int): Port ID Returns: tuple: Device name, Port name """ dev_name = self.related_conf[dev_id]['name'].encode("ascii") dev_port = self.related_conf[dev_id]['ports'][port_id - 1].encode("ascii") return dev_name, dev_port
[docs] def cross_connect(self, conn_list=None): """Create connections. Args: conn_list(list[list]): List of connections """ self.class_logger.debug("Connection list: {0}".format(conn_list)) for conn in conn_list: self.class_logger.info("Make connection {0}:{1}, and {2}:{3}.". format(conn[0], conn[1], conn[2], conn[3])) self.xconnect(conn)
[docs] def cross_disconnect(self, disconn_list=None): """Destroy connections. Args: disconn_list(list[list]): List of connections """ self.class_logger.debug("Disconnection list: {0}".format(disconn_list)) for conn in disconn_list: self.class_logger.info("Destroy connection {0}:{1}, and {2}:{3}.". format(conn[0], conn[1], conn[2], conn[3])) self.xdisconnect(conn)
[docs] def netns_netns(self, connection, action): """Returns set of commands to create/destroy connection between 2 Linux Network Namespaces. Args: connection(list): Connection info in format [sw1, port1, sw2, port2] action(str): Action for connection. """ commands = [] src_name, src_port = self.get_name_port(connection[0], connection[1]) dst_name, dst_port = self.get_name_port(connection[2], connection[3]) if action == "Connecting": commands.append(["ip", "link", "add", "name", src_port, "type", "veth", "peer", "name", dst_port, "netns", dst_name]) commands.append(["ip", "link", "set", src_port, "netns", src_name]) commands.append(["ip", "netns", "exec", src_name, "ifconfig", src_port, "up"]) commands.append(["ip", "netns", "exec", dst_name, "ifconfig", dst_port, "up"]) else: commands.append(["ip", "netns", src_name, "exec", "ip", "link", "delete", src_port]) self.class_logger.debug("{0} {1}".format(action, [src_name, src_port, dst_name, dst_port], )) return commands
[docs] def generic_netns(self, connection, action): """Returns set of commands to create/destroy connection between default NNS and custom NNS. Args: connection(list): Connection info in format [sw1, port1, sw2, port2] action(str): Action for connection. """ commands = [] con1 = self.get_name_port(connection[0], connection[1]) con2 = self.get_name_port(connection[2], connection[3]) src_name, src_port = con1 dst_name, dst_port = con2 generic_ip = self.related_obj[connection[0]].ipaddr if action == "Connecting": # Here we need to determine if generic is the same host or not. # Check if generic's IP is IP of the localhost. if generic_ip not in ["localhost", "127.0.0.1"]: commands.append(["ip", "link", "set", dst_port, "netns", dst_name]) else: commands.append(["ip", "link", "add", "name", src_port, "type", "veth", "peer", "name", dst_port, "netns", dst_name]) commands.append(["ifconfig", src_port, "up"]) commands.append(["ip", "netns", "exec", dst_name, "ifconfig", dst_port, "up"]) else: if generic_ip in ["localhost", "127.0.0.1"]: commands.append(["ip", "link", "delete", src_port]) else: self.class_logger.debug("Skip following interface deleting as they weren't created by this cross.") self.class_logger.debug("{0} {1}".format(action, [src_name, src_port, dst_name, dst_port], )) return commands
[docs] def generic_generic(self, connection, action): """Returns set of commands to create connection between 2 default NNS. This is used to create just veth interfaces without netns. Args: connection(list): Connection info in format [sw1, port1, sw2, port2] action(str): Action for connection. """ commands = [] src_name, src_port = self.get_name_port(connection[0], connection[1]) dst_name, dst_port = self.get_name_port(connection[2], connection[3]) if action == "Connecting": commands.append(["ip", "link", "add", "name", src_port, "type", "veth", "peer", "name", dst_port]) commands.append(["ifconfig", src_port, "up"]) commands.append(["ifconfig", dst_port, "up"]) else: commands.append(["ip", "link", "delete", src_port]) self.class_logger.debug("{0} {1}".format(action, [src_name, src_port, dst_name, dst_port], )) return commands
[docs] def xconnect(self, connection): """Create single connection. Args: connection(list): Connection info in format [sw1, port1, sw2, port2] """ self._exec_x_command(connection, "Connecting")
[docs] def xdisconnect(self, connection): """Destroy single connection. Args: connection(list): Connection info in format [sw1, port1, sw2, port2] """ self._exec_x_command(connection, "Disconnecting")
[docs] def _exec_x_command(self, connection, action): """Create single connection. Args: connection(list): Connection info in format [sw1, port1, sw2, port2] action(str): Action for connection. Raises: CrossException: incorrect action type, error on connection """ if action not in ["Connecting", "Disconnecting"]: message = "Incorrect action type {0}. action has to be in ['connect', 'disconnect']".format(action) self.class_logger.error(message) raise CrossException(message) type1 = self.related_obj[connection[0]].type type2 = self.related_obj[connection[2]].type try: # Connection order has to be the same as types order in method name if sorted([type1, type2]) != [type1, type2]: connection = connection[2:] + connection[:2] commands = getattr(self, "{0}_{1}".format(*sorted([type1, type2])))(connection, action) except AttributeError: message = "Connection type doesn't supported: {0} <-> {1}".format(type1, type2) self.class_logger.error(message) raise CrossException(message) for command in commands: self.class_logger.debug("{0} command: {1}".format(action, [command])) process = Popen(command, stdout=PIPE, stderr=PIPE) process.wait() # TODO: Investigate problem with rc == 1 in case of successful command execution if process.returncode != 0 and process.returncode != 1: self.class_logger.error("Cannot perform connection command.") out = process.stdout.read() if out: self.class_logger.error("StdOut:\n{0}".format(out)) out = process.stderr.read() if out: self.class_logger.error("StdErr:\n{0}".format(out)) self.class_logger.error("Cannot perform connection command.") message = ("{0} command {1} failed. Return code = {2}.". format(action, command, process.returncode)) self.class_logger.error(message) raise CrossException(message)
ENTRY_TYPE = "cross" INSTANCES = {"veth": VethCross} NAME = "cross"