# 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_vlabcross.py```
`ONS Vlab virtual cross specific functionality`
"""
from os.path import join as os_path_join
import sys
import time
import socket
from subprocess import Popen
from . import loggers
from . import environment
from . import dev_basecross
from .custom_exceptions import CrossException
from .xmlrpc_proxy import TimeoutServerProxy as xmlrpcProxy
[docs]class VlabEnv(dev_basecross.GenericXConnectMixin):
"""Vlab from device viewpoint.
"""
class_logger = loggers.ClassLogger()
DEFAULT_TIMEOUT = 1
[docs] def __init__(self, config, opts):
"""Initialize VlabEnv class.
Args:
config(dict): Configuration information.
opts(OptionParser): py.test config.option object which contains all py.test cli options.
Raises:
CrossException: error in vlab path
"""
self.id = config['id']
self.type = config['instance_type']
self.ipaddr = config['ip_host']
self.port = config['ip_port'] if "ip_port" in config else "8050"
self.ifaces = config['ports']
self.opts = opts
# 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']
self.tgmap = []
if "tgmap" in list(config.keys()):
self.tgmap = config['tgmap']
if "portmap" in list(config.keys()):
self.portmap = config['portmap']
if "bridged_ifaces" in list(config.keys()):
self.bridged_ifaces = config['bridged_ifaces']
self.ports_count = len(self.ifaces) - len(self.bridged_ifaces)
else:
self.ports_count = len(self.ifaces)
self.bind_iface = config['ip_iface'] if "ip_iface" in config else None
self.build_path = environment.get_absolute_build_path(opts.build_path)
if not self.build_path:
raise CrossException("Could not find vlab binaries path - %s." % (opts.build_path, ))
self.class_logger.info("Vlab binaries path: %s." % (self.build_path, ))
self.xmlproxy = xmlrpcProxy("http://%s:%s/RPC2" % (self.ipaddr, self.port), timeout=45)
self.popen = None
self.popen_logfile = "vlab%s.output.log" % (self.id, )
# Set On/Off(True/False) status according to get_only option.
self.status = self.opts.get_only
[docs] def probe_port(self):
"""Establishing a connection to a remote host.
Returns:
bool: True if connection is established
"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(3)
try:
sock.connect((self.ipaddr, int(self.port)))
sock.close()
return True
except Exception:
return False
[docs] def probe(self):
"""Check if Vlab instance is run.
Returns:
dict: Vlab status
"""
result = {'isup': False, 'type': "unknown", 'prop': {}}
if self.probe_port():
result['isup'] = True
try:
instance_prop = self.xmlproxy.vlab.sysinfo()
result['type'] = "vlab"
result['prop'] = instance_prop
self.class_logger.info("Found a running vlab instance on %s:%s." % (self.ipaddr, self.port, ))
self.class_logger.info("Revision: %s" % result['prop']['revision'])
except Exception:
pass
return result
[docs] def waiton(self, timeout=30):
"""Waiting until Vlab port is up.
Args:
timeout(int): Waiting timeout
Raises:
CrossException: error on vlab start
Returns:
dict: Vlab status
"""
status = None
message = "Waiting until vlab on %s port #%s is up." % (self.ipaddr, self.port, )
self.class_logger.info(message)
stop_flag = False
end_time = time.time() + timeout
while not stop_flag:
if loggers.LOG_STREAM:
sys.stdout.write(".")
sys.stdout.flush()
if time.time() < end_time:
status = self.probe()
if status["isup"] and status["type"] == "vlab":
stop_flag = True
self.class_logger.info(("VLAB started on host %(host)s port %(port)s: " +
"uptime - %(uptime)s, workdir - %(workdir)s, hostname - %(hostname)s," +
"xmlRpcPort - %(xmlRpcPort)s, port - %(xport)s, revision - %(revision)s") %
{'host': self.ipaddr, 'port': self.port, 'uptime': status['prop']['uptime'],
'workdir': status['prop']['workdir'],
'hostname': status['prop']['hostname'], 'xmlRpcPort': status['prop']['xmlRpcPort'],
'xport': status['prop']['port'], 'revision': status['prop']['revision']})
else:
if status["isup"] and status["type"] != "vlab":
message = (("Port %s on host %s is busy. " +
"Check if vlab already started or other application use the same port.") %
(self.port, self.ipaddr))
else:
message = "Timeout exceeded."
self.class_logger.warning(message)
raise CrossException(message)
if not stop_flag:
time.sleep(self.DEFAULT_TIMEOUT)
return status
[docs] def waitoff(self, timeout=30):
"""Waiting until Vlab port is down.
Args:
timeout(int): Waiting timeout
Raises:
CrossException: error on vlab stop
Returns:
dict: Vlab status
"""
status = None
message = "Waiting until vlab on %s port #%s is down." % (self.ipaddr, self.port, )
self.class_logger.info(message)
stop_flag = False
end_time = time.time() + timeout
while not stop_flag:
if loggers.LOG_STREAM:
sys.stdout.write(".")
sys.stdout.flush()
if time.time() < end_time:
status = self.probe_port()
if not status:
stop_flag = True
else:
if status:
message = "Timeout exceeded. The port %s on host %s is still open" % (self.port, self.ipaddr)
self.class_logger.warning(message)
raise CrossException(message)
if not stop_flag:
time.sleep(self.DEFAULT_TIMEOUT)
message = "Waiting until vlab process with %d pid stop" % (self.popen.pid, )
self.class_logger.info(message)
while True:
if loggers.LOG_STREAM:
sys.stdout.write(".")
sys.stdout.flush()
if time.time() < end_time:
if self.popen.poll() is not None:
self.class_logger.info("Exit code of the vlab process with PID %s = %s" %
(self.popen.pid, self.popen.poll()))
break
else:
message = "Timeout exceeded. Vlab process with PID %d still exists." % (self.popen.pid, )
self.class_logger.warning(message)
raise CrossException(message)
time.sleep(self.DEFAULT_TIMEOUT)
[docs] def start(self):
"""Starts vlab based on provided host and port info with specified number of interfaces.
Raises:
CrossException: not local environment, vlab is stopped
Exception: error on vlab start
"""
def check_rc():
"""Checking Vlab process.
"""
rc = process.poll()
if rc is not None:
raise CrossException("Vlab process is terminated with signal %s." % (rc, ))
process = None
bin_path = os_path_join(self.build_path, "bin", "vlab")
# TODO: Add possibility to run vlab instance on remote hosts, any port using paramiko.
if (self.ipaddr != "localhost") and (self.port != "8050"):
message = "Only local environment is supported at the moment."
self.class_logger.error(message)
raise CrossException(message)
try:
self.class_logger.info("Starting Vlab on %s:%s" % (self.ipaddr, self.port))
command_lst = [bin_path, "-v", "%s" % (self.ports_count, )]
if hasattr(self, "bridged_ifaces"):
for b_iface in self.bridged_ifaces:
command_lst.append("-P")
command_lst.append(b_iface)
self.class_logger.debug("Start command: %s" % (" ".join(command_lst), ))
log_wrap_out, log_wrap_err = loggers.pipe_loggers("vlab%s" % (self.id, ), self.popen_logfile)
process = Popen(command_lst, stdout=log_wrap_out, stderr=log_wrap_err,
cwd=self.build_path, env={"LD_LIBRARY_PATH": os_path_join(self.build_path, "lib")})
check_rc()
except Exception as err:
self.class_logger.error("Error executing vlab Popen process.")
self.class_logger.error(str(err))
raise
# let's wait until device is up and running:
self.waiton()
self.popen = process
check_rc()
self.status = True
[docs] def stop(self):
"""Stops vlab based on provided host and port info.
Raises:
CrossException: error on vlab stop
"""
if not self.popen:
message = "No Popen object exists for Vlab. Exiting stop() method without processing."
self.class_logger.error(message)
raise CrossException(message)
# Send xmlrpc shutdown query
result = self.xmlproxy.system.shutdown("")
if result is not 0:
message = "Error stopping vlab instance. XMLRPC query response = %s" % (result, )
self.class_logger.error(message)
raise CrossException(message)
else:
# let's wait until device is fully stopped:
self.waitoff()
self.status = False
[docs] def restart(self):
"""Restarting Vlab instance.
"""
try:
self.stop()
except Exception as err:
self.class_logger.warning("Fail to stop vlab instance with error: %s" % err)
finally:
self.start()
[docs] def check(self):
"""Checking Vlab instance status.
"""
if self.status:
self.waiton()
else:
self.class_logger.info("Skip check method for vlab id:%s because it has Off status." % (self.id, ))
[docs]class VlabCross(VlabEnv):
"""Vlab from xconnect viewpoint.
"""
class_logger = loggers.ClassLogger()
[docs] def _get_ports_from_config(self, connection=None):
"""Get ports from configuration.
Args:
connection(list): Connection info in format [sw1, port1, sw2, port2]
Raises:
CrossException: unsupported connection type
ValueError: error in configuration file
Returns:
list: Ports from configuration
"""
def get_port(conn):
"""Get port ID.
"""
# If device linked to another via bridged interface
if hasattr(self, 'portmap'):
for elem in self.portmap:
if conn == elem[:2]:
return [0, elem[2] - 1]
# If device id in connection == vlab id or id of related TG
if conn[0] == self.id or conn[0] in self.tgmap:
# Return vlab self id and vlab port id from list of ifaces
return [0, conn[1] - 1]
# other devices Id.
else:
for rkey in list(self.related_conf.keys()):
rconf = self.related_conf[rkey]
if rconf['id'] == conn[0] and rconf['entry_type'] == "switch":
# Return switch ID and port ID from config
# [devId: conf_port_Id] --> [devId: port_Id_for_vlab]
# E.G.
# conn = [1, 3]
# rconf = {'id': 1, ports: ["5", "6", "7"]}
# conf_port_id = 3, real_dev_port_id = "7", port_Id_for_vlab = 6 = (7 - 1)
# Vlab port list index start from 0, but switch port index from 1, so do -1
# Switch ID = PortNo - 8080. E.g. 8082 - 8080 = 2
return [int(rconf['ip_port']) - 8080, int(rconf['ports'][conn[1] - 1]) - 1]
elif rconf['id'] == conn[0] and rconf['entry_type'] == "hub":
return [rconf['hub_id'], int(rconf['ports'][conn[1] - 1]) - 1]
elif rconf['id'] == conn[0] and not rconf['entry_type'] in ["switch", "hub"]:
message = "Only connections to switch, hub or Vlab itself are supported. But found entry type = %s" % rconf['entry_type']
self.class_logger.error(message)
raise CrossException(message)
# Part 1
vconn1 = get_port(connection[:2])
# Part 2
vconn2 = get_port(connection[2:])
try:
vconn_full = vconn1 + vconn2
return vconn_full
except Exception:
raise ValueError("Cannot make requested connection. Check config. Got following args: %s, %s" % (vconn1, vconn2))
[docs] def xconnect(self, connection):
"""Create single connection.
Args:
connection(list): Connection info in format [sw1, port1, sw2, port2]
"""
vconn = self._get_ports_from_config(connection)
self.class_logger.debug("Connect VLAB ports: %s" % vconn)
return self.xmlproxy.vlab.cross.connect(vconn[0], vconn[1], vconn[2], vconn[3])
[docs] def xdisconnect(self, connection):
"""Destroy single connection.
Args:
connection(list): Connection info in format [sw1, port1, sw2, port2]
"""
vconn = self._get_ports_from_config(connection)
self.class_logger.debug("Disconnect VLAB ports: %s" % vconn)
return self.xmlproxy.vlab.cross.disconnect(vconn[0], vconn[1], vconn[2], vconn[3])
[docs] def cross_connect(self, conn_list):
"""Make connections between switches.
Args:
conn_list(list[list]): Set of connections in format: [[sw1, port1, sw2, port2], ... ]
Raises:
CrossException: devices from conn_list are not in related configurations,
error on connection creation
Returns:
bool: True if success or raise an error if connections were not created.
Examples::
cross_connect([[0, 1, 1, 1], [0, 2, 1, 2]])
"""
if self.related_conf and conn_list:
list_id = []
for conn in conn_list:
list_id.append(conn[0])
list_id.append(conn[2])
if set(self.related_conf.keys()) != set(list_id):
message = ("Set of cross connected devices %s is not appropriate related config %s."
% (list(set(list_id)), list(set(self.related_conf.keys()))))
self.class_logger.error(message)
raise CrossException(message)
for conn in conn_list:
# make connections
self.class_logger.info("Make connection %(sw1)s,%(port1)s, and %(sw2)s,%(port2)s." %
{'sw1': conn[0], 'port1': conn[1], 'sw2': conn[2], 'port2': conn[3]})
if self.xconnect(conn) == 0:
message = "Cannot create connection: %s" % conn
self.class_logger.error(message)
raise CrossException(message)
return True
[docs] def cross_disconnect(self, disconn_list):
"""Destroy connections between switches.
Args:
disconn_list(list[list]): Set of connections in format: [[sw1, port1, sw2, port2], ... ]
Raises:
CrossException: error on connection destroying
Returns:
bool: True if success or False if connections were not destroyed.
Examples::
cross_disconnect([[0, 1, 1, 1], [0, 2, 1, 2]])
"""
# Destroy connections using Virtual Lab
for conn in disconn_list:
self.class_logger.info("Destroy connection %(sw1)s,%(port1)s, and %(sw2)s,%(port2)s." %
{'sw1': conn[0], 'port1': conn[1], 'sw2': conn[2], 'port2': conn[3]})
if self.xdisconnect(conn) == 0:
message = "Cannot destroy connection: %s" % conn
self.class_logger.error(message)
raise CrossException(message)
return True
[docs] def cross_clear(self):
"""Clear all connections between switches
Raises:
CrossException: error on connections clearing
Returns:
bool: True if success or False if all connections were not cleared.
Examples::
cross_clear(env)
"""
self.class_logger.info("Clear all connections.")
if self.xmlproxy.vlab.cross.clear() == 0:
message = "Cannot clear all connections"
self.class_logger.error(message)
raise CrossException(message)
return True
ENTRY_TYPE = "cross"
INSTANCES = {"vlab": VlabCross}
NAME = "cross"