2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2019-04-30 07:31:47 +01:00
|
|
|
client.py: implementation of the VnodeClient class for issuing commands
|
2013-08-29 15:21:13 +01:00
|
|
|
over a control channel to the vnoded process running in a network namespace.
|
|
|
|
The control channel can be accessed via calls to the vcmd Python module or
|
|
|
|
by invoking the vcmd shell command.
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2019-02-16 17:50:19 +00:00
|
|
|
import logging
|
2017-04-25 16:45:34 +01:00
|
|
|
import os
|
2018-02-27 18:48:01 +00:00
|
|
|
|
2019-06-03 03:06:25 +01:00
|
|
|
from subprocess import Popen, PIPE
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2019-04-30 07:31:47 +01:00
|
|
|
from core import CoreCommandError, utils
|
2017-04-25 16:45:34 +01:00
|
|
|
from core import constants
|
2013-08-29 15:21:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
class VnodeClient(object):
|
2017-05-03 21:20:56 +01:00
|
|
|
"""
|
|
|
|
Provides client functionality for interacting with a virtual node.
|
|
|
|
"""
|
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def __init__(self, name, ctrlchnlname):
|
2017-05-03 21:20:56 +01:00
|
|
|
"""
|
|
|
|
Create a VnodeClient instance.
|
|
|
|
|
|
|
|
:param str name: name for client
|
|
|
|
:param str ctrlchnlname: control channel name
|
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
self.name = name
|
|
|
|
self.ctrlchnlname = ctrlchnlname
|
|
|
|
self._addr = {}
|
|
|
|
|
2018-02-27 18:48:01 +00:00
|
|
|
def _verify_connection(self):
|
|
|
|
"""
|
|
|
|
Checks that the vcmd client is properly connected.
|
|
|
|
|
|
|
|
:return: nothing
|
2018-03-01 17:17:58 +00:00
|
|
|
:raises IOError: when not connected
|
2018-02-27 18:48:01 +00:00
|
|
|
"""
|
|
|
|
if not self.connected():
|
2018-03-01 17:17:58 +00:00
|
|
|
raise IOError("vcmd not connected")
|
2018-02-27 18:48:01 +00:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def connected(self):
|
2017-05-03 21:20:56 +01:00
|
|
|
"""
|
|
|
|
Check if node is connected or not.
|
|
|
|
|
|
|
|
:return: True if connected, False otherwise
|
|
|
|
:rtype: bool
|
|
|
|
"""
|
2019-06-03 03:06:25 +01:00
|
|
|
return True
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2016-03-04 21:57:17 +00:00
|
|
|
def close(self):
|
2017-05-03 21:20:56 +01:00
|
|
|
"""
|
|
|
|
Close the client connection.
|
|
|
|
|
|
|
|
:return: nothing
|
|
|
|
"""
|
2019-06-03 03:06:25 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
def _cmd_args(self):
|
|
|
|
return [constants.VCMD_BIN, "-c", self.ctrlchnlname, "--"]
|
2016-03-04 21:57:17 +00:00
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
def cmd(self, args, wait=True):
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Execute a command on a node and return the status (return code).
|
2017-05-03 21:20:56 +01:00
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
:param list[str]|str args: command arguments
|
2017-05-03 21:20:56 +01:00
|
|
|
:param bool wait: wait for command to end or not
|
|
|
|
:return: command status
|
|
|
|
:rtype: int
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-02-27 18:48:01 +00:00
|
|
|
self._verify_connection()
|
2018-03-02 00:23:58 +00:00
|
|
|
args = utils.split_args(args)
|
2018-02-27 18:48:01 +00:00
|
|
|
|
2018-02-27 18:55:57 +00:00
|
|
|
# run command, return process when not waiting
|
2019-06-03 03:06:25 +01:00
|
|
|
cmd = self._cmd_args() + args
|
|
|
|
logging.info("cmd wait(%s): %s", wait, cmd)
|
|
|
|
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
|
2018-02-27 18:48:01 +00:00
|
|
|
if not wait:
|
2018-03-01 21:21:25 +00:00
|
|
|
return 0
|
2017-05-03 21:20:56 +01:00
|
|
|
|
2018-02-27 18:55:57 +00:00
|
|
|
# wait for and return exit status
|
2018-03-02 00:23:58 +00:00
|
|
|
return p.wait()
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
def cmd_output(self, args):
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Execute a command on a node and return a tuple containing the
|
|
|
|
exit status and result string. stderr output
|
|
|
|
is folded into the stdout result string.
|
2017-05-03 21:20:56 +01:00
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
:param list[str]|str args: command to run
|
2017-05-03 21:20:56 +01:00
|
|
|
:return: command status and combined stdout and stderr output
|
|
|
|
:rtype: tuple[int, str]
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-03-02 00:23:58 +00:00
|
|
|
p, stdin, stdout, stderr = self.popen(args)
|
2018-02-27 18:48:01 +00:00
|
|
|
stdin.close()
|
2018-03-01 17:17:58 +00:00
|
|
|
output = stdout.read() + stderr.read()
|
2018-02-27 18:48:01 +00:00
|
|
|
stdout.close()
|
|
|
|
stderr.close()
|
|
|
|
status = p.wait()
|
2019-06-03 03:06:25 +01:00
|
|
|
return status, output.decode("utf-8").strip()
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
def check_cmd(self, args):
|
2018-03-01 17:17:58 +00:00
|
|
|
"""
|
2018-03-01 21:21:25 +00:00
|
|
|
Run command and return exit status and combined stdout and stderr.
|
2018-03-01 17:17:58 +00:00
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
:param list[str]|str args: command to run
|
2018-03-02 21:57:50 +00:00
|
|
|
:return: combined stdout and stderr
|
|
|
|
:rtype: str
|
2018-03-03 00:22:20 +00:00
|
|
|
:raises core.CoreCommandError: when there is a non-zero exit status
|
2018-03-01 17:17:58 +00:00
|
|
|
"""
|
2018-03-02 00:23:58 +00:00
|
|
|
status, output = self.cmd_output(args)
|
2018-03-02 21:39:44 +00:00
|
|
|
if status != 0:
|
2018-03-03 00:22:20 +00:00
|
|
|
raise CoreCommandError(status, args, output)
|
2018-03-02 21:57:50 +00:00
|
|
|
return output.strip()
|
2018-03-01 17:17:58 +00:00
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
def popen(self, args):
|
2017-05-03 21:20:56 +01:00
|
|
|
"""
|
|
|
|
Execute a popen command against the node.
|
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
:param list[str]|str args: command arguments
|
2017-05-03 21:20:56 +01:00
|
|
|
:return: popen object, stdin, stdout, and stderr
|
|
|
|
:rtype: tuple
|
|
|
|
"""
|
2018-02-27 18:48:01 +00:00
|
|
|
self._verify_connection()
|
2018-03-02 00:23:58 +00:00
|
|
|
args = utils.split_args(args)
|
2019-06-03 03:06:25 +01:00
|
|
|
# if isinstance(args, list):
|
|
|
|
# args = " ".join(args)
|
|
|
|
cmd = self._cmd_args() + args
|
|
|
|
logging.info("popen: %s", cmd)
|
|
|
|
p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
|
|
|
|
return p, p.stdin, p.stdout, p.stderr
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
def icmd(self, args):
|
2017-05-03 21:20:56 +01:00
|
|
|
"""
|
|
|
|
Execute an icmd against a node.
|
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
:param list[str]|str args: command arguments
|
2017-05-03 21:20:56 +01:00
|
|
|
:return: command result
|
|
|
|
:rtype: int
|
|
|
|
"""
|
2018-03-02 00:23:58 +00:00
|
|
|
args = utils.split_args(args)
|
2018-03-13 23:20:50 +00:00
|
|
|
return os.spawnlp(os.P_WAIT, constants.VCMD_BIN, constants.VCMD_BIN, "-c", self.ctrlchnlname, "--", *args)
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
def redircmd(self, infd, outfd, errfd, args, wait=True):
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
Execute a command on a node with standard input, output, and
|
|
|
|
error redirected according to the given file descriptors.
|
2017-05-03 21:20:56 +01:00
|
|
|
|
|
|
|
:param infd: stdin file descriptor
|
|
|
|
:param outfd: stdout file descriptor
|
|
|
|
:param errfd: stderr file descriptor
|
2018-03-02 00:23:58 +00:00
|
|
|
:param list[str]|str args: command arguments
|
2017-05-03 21:20:56 +01:00
|
|
|
:param bool wait: wait flag
|
|
|
|
:return: command status
|
|
|
|
:rtype: int
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-02-27 18:48:01 +00:00
|
|
|
self._verify_connection()
|
|
|
|
|
2018-02-27 18:55:57 +00:00
|
|
|
# run command, return process when not waiting
|
2018-03-02 00:23:58 +00:00
|
|
|
args = utils.split_args(args)
|
2019-06-03 03:06:25 +01:00
|
|
|
cmd = self._cmd_args() + args
|
|
|
|
logging.info("redircmd: %s", cmd)
|
|
|
|
p = Popen(cmd, stdin=infd, stdout=outfd, stderr=errfd)
|
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
if not wait:
|
2018-02-27 18:55:57 +00:00
|
|
|
return p
|
2018-02-27 18:48:01 +00:00
|
|
|
|
2018-02-27 18:55:57 +00:00
|
|
|
# wait for and return exit status
|
|
|
|
status = p.wait()
|
|
|
|
if status:
|
2019-06-03 22:36:21 +01:00
|
|
|
logging.warning("cmd exited with status %s: %s", status, args)
|
2018-02-27 18:55:57 +00:00
|
|
|
return status
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
def term(self, sh="/bin/sh"):
|
2017-05-03 21:20:56 +01:00
|
|
|
"""
|
|
|
|
Open a terminal on a node.
|
|
|
|
|
|
|
|
:param str sh: shell to open terminal with
|
|
|
|
:return: terminal command result
|
|
|
|
:rtype: int
|
|
|
|
"""
|
2018-03-13 23:20:50 +00:00
|
|
|
args = ("xterm", "-ut", "-title", self.name, "-e", constants.VCMD_BIN, "-c", self.ctrlchnlname, "--", sh)
|
2015-05-22 01:55:48 +01:00
|
|
|
if "SUDO_USER" in os.environ:
|
2018-03-02 00:23:58 +00:00
|
|
|
args = ("su", "-s", "/bin/sh", "-c",
|
2018-03-02 21:39:44 +00:00
|
|
|
"exec " + " ".join(map(lambda x: "'%s'" % x, args)),
|
|
|
|
os.environ["SUDO_USER"])
|
2018-03-02 00:23:58 +00:00
|
|
|
return os.spawnvp(os.P_NOWAIT, args[0], args)
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
def termcmdstring(self, sh="/bin/sh"):
|
2017-05-03 21:20:56 +01:00
|
|
|
"""
|
|
|
|
Create a terminal command string.
|
|
|
|
|
|
|
|
:param str sh: shell to execute command in
|
|
|
|
:return: str
|
|
|
|
"""
|
2018-03-13 23:20:50 +00:00
|
|
|
return "%s -c %s -- %s" % (constants.VCMD_BIN, self.ctrlchnlname, sh)
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2018-03-01 21:21:25 +00:00
|
|
|
def shcmd(self, cmd, sh="/bin/sh"):
|
2017-05-03 21:20:56 +01:00
|
|
|
"""
|
|
|
|
Execute a shell command.
|
|
|
|
|
2018-03-01 21:21:25 +00:00
|
|
|
:param str cmd: command string
|
2017-05-03 21:20:56 +01:00
|
|
|
:param str sh: shell to run command in
|
|
|
|
:return: command result
|
|
|
|
:rtype: int
|
|
|
|
"""
|
2018-03-01 21:21:25 +00:00
|
|
|
return self.cmd([sh, "-c", cmd])
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2018-02-08 03:13:51 +00:00
|
|
|
def shcmd_result(self, cmd, sh="/bin/sh"):
|
|
|
|
"""
|
|
|
|
Execute a shell command and return the exist status and combined output.
|
|
|
|
|
|
|
|
:param str cmd: shell command to run
|
|
|
|
:param str sh: shell to run command in
|
|
|
|
:return: exist status and combined output
|
|
|
|
:rtype: tuple[int, str]
|
|
|
|
"""
|
2018-03-01 21:21:25 +00:00
|
|
|
return self.cmd_output([sh, "-c", cmd])
|
2018-02-08 03:13:51 +00:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
def getaddr(self, ifname, rescan=False):
|
2017-05-03 21:20:56 +01:00
|
|
|
"""
|
|
|
|
Get address for interface on node.
|
|
|
|
|
|
|
|
:param str ifname: interface name to get address for
|
|
|
|
:param bool rescan: rescan flag
|
|
|
|
:return: interface information
|
|
|
|
:rtype: dict
|
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
if ifname in self._addr and not rescan:
|
|
|
|
return self._addr[ifname]
|
2018-02-27 18:48:01 +00:00
|
|
|
|
|
|
|
interface = {"ether": [], "inet": [], "inet6": [], "inet6link": []}
|
2018-03-02 00:23:58 +00:00
|
|
|
args = [constants.IP_BIN, "addr", "show", "dev", ifname]
|
|
|
|
p, stdin, stdout, stderr = self.popen(args)
|
2018-02-27 18:48:01 +00:00
|
|
|
stdin.close()
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-02-27 18:48:01 +00:00
|
|
|
for line in stdout:
|
2013-08-29 15:21:13 +01:00
|
|
|
line = line.strip().split()
|
|
|
|
if line[0] == "link/ether":
|
2018-02-27 18:48:01 +00:00
|
|
|
interface["ether"].append(line[1])
|
2013-08-29 15:21:13 +01:00
|
|
|
elif line[0] == "inet":
|
2018-02-27 18:48:01 +00:00
|
|
|
interface["inet"].append(line[1])
|
2013-08-29 15:21:13 +01:00
|
|
|
elif line[0] == "inet6":
|
|
|
|
if line[3] == "global":
|
2018-02-27 18:48:01 +00:00
|
|
|
interface["inet6"].append(line[1])
|
2013-08-29 15:21:13 +01:00
|
|
|
elif line[3] == "link":
|
2018-02-27 18:48:01 +00:00
|
|
|
interface["inet6link"].append(line[1])
|
2013-08-29 15:21:13 +01:00
|
|
|
else:
|
2019-06-03 22:36:21 +01:00
|
|
|
logging.warning("unknown scope: %s" % line[3])
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-02-27 18:48:01 +00:00
|
|
|
err = stderr.read()
|
|
|
|
stdout.close()
|
|
|
|
stderr.close()
|
|
|
|
status = p.wait()
|
2013-08-29 15:21:13 +01:00
|
|
|
if status:
|
2019-06-03 22:36:21 +01:00
|
|
|
logging.warning("nonzero exist status (%s) for cmd: %s", status, args)
|
2013-08-29 15:21:13 +01:00
|
|
|
if err:
|
2019-06-03 22:36:21 +01:00
|
|
|
logging.warning("error output: %s", err)
|
2018-02-27 18:48:01 +00:00
|
|
|
self._addr[ifname] = interface
|
|
|
|
return interface
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
def netifstats(self, ifname=None):
|
2017-05-03 21:20:56 +01:00
|
|
|
"""
|
|
|
|
Retrieve network interface state.
|
|
|
|
|
|
|
|
:param str ifname: name of interface to get state for
|
|
|
|
:return: interface state information
|
|
|
|
:rtype: dict
|
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
stats = {}
|
2018-03-02 00:23:58 +00:00
|
|
|
args = ["cat", "/proc/net/dev"]
|
|
|
|
p, stdin, stdout, stderr = self.popen(args)
|
2018-02-27 18:48:01 +00:00
|
|
|
stdin.close()
|
2013-08-29 15:21:13 +01:00
|
|
|
# ignore first line
|
2018-02-27 18:48:01 +00:00
|
|
|
stdout.readline()
|
2013-08-29 15:21:13 +01:00
|
|
|
# second line has count names
|
2019-06-03 22:36:21 +01:00
|
|
|
tmp = stdout.readline().decode("utf-8").strip().split("|")
|
2013-08-29 15:21:13 +01:00
|
|
|
rxkeys = tmp[1].split()
|
|
|
|
txkeys = tmp[2].split()
|
2018-02-27 18:48:01 +00:00
|
|
|
for line in stdout:
|
2019-06-03 22:36:21 +01:00
|
|
|
line = line.decode("utf-8").strip().split()
|
2013-08-29 15:21:13 +01:00
|
|
|
devname, tmp = line[0].split(":")
|
|
|
|
if tmp:
|
|
|
|
line.insert(1, tmp)
|
|
|
|
stats[devname] = {"rx": {}, "tx": {}}
|
|
|
|
field = 1
|
|
|
|
for count in rxkeys:
|
|
|
|
stats[devname]["rx"][count] = int(line[field])
|
|
|
|
field += 1
|
|
|
|
for count in txkeys:
|
|
|
|
stats[devname]["tx"][count] = int(line[field])
|
|
|
|
field += 1
|
2018-02-27 18:48:01 +00:00
|
|
|
err = stderr.read()
|
|
|
|
stdout.close()
|
|
|
|
stderr.close()
|
|
|
|
status = p.wait()
|
2013-08-29 15:21:13 +01:00
|
|
|
if status:
|
2019-06-03 22:36:21 +01:00
|
|
|
logging.warning("nonzero exist status (%s) for cmd: %s", status, args)
|
2013-08-29 15:21:13 +01:00
|
|
|
if err:
|
2019-06-03 22:36:21 +01:00
|
|
|
logging.warning("error output: %s", err)
|
2013-08-29 15:21:13 +01:00
|
|
|
if ifname is not None:
|
|
|
|
return stats[ifname]
|
|
|
|
else:
|
|
|
|
return stats
|