# # CORE # Copyright (c)2010-2012 the Boeing Company. # See the LICENSE file included in this distribution. # # author: Tom Goff # ''' vnodeclient.py: implementation of the VnodeClient class for issuing commands 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. ''' import os, stat, sys from core.constants import * USE_VCMD_MODULE = True if USE_VCMD_MODULE: import vcmd else: import subprocess VCMD = os.path.join(CORE_SBIN_DIR, "vcmd") class VnodeClient(object): def __init__(self, name, ctrlchnlname): self.name = name self.ctrlchnlname = ctrlchnlname if USE_VCMD_MODULE: self.cmdchnl = vcmd.VCmd(self.ctrlchnlname) else: self.cmdchnl = None self._addr = {} def warn(self, msg): print >> sys.stderr, "%s: %s" % (self.name, msg) def connected(self): if USE_VCMD_MODULE: return self.cmdchnl.connected() else: return True def close(self): if USE_VCMD_MODULE: self.cmdchnl.close() def cmd(self, args, wait = True): ''' Execute a command on a node and return the status (return code). ''' if USE_VCMD_MODULE: if not self.cmdchnl.connected(): raise ValueError, "self.cmdchnl not connected" tmp = self.cmdchnl.qcmd(args) if not wait: return tmp tmp = tmp.wait() else: if wait: mode = os.P_WAIT else: mode = os.P_NOWAIT tmp = os.spawnlp(mode, VCMD, VCMD, "-c", self.ctrlchnlname, "-q", "--", *args) if not wait: return tmp if tmp: self.warn("cmd exited with status %s: %s" % (tmp, str(args))) return tmp def cmdresult(self, args): ''' 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. ''' cmdid, cmdin, cmdout, cmderr = self.popen(args) result = cmdout.read() result += cmderr.read() cmdin.close() cmdout.close() cmderr.close() status = cmdid.wait() return (status, result) def popen(self, args): if USE_VCMD_MODULE: if not self.cmdchnl.connected(): raise ValueError, "self.cmdchnl not connected" return self.cmdchnl.popen(args) else: cmd = [VCMD, "-c", self.ctrlchnlname, "--"] cmd.extend(args) tmp = subprocess.Popen(cmd, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE) return tmp, tmp.stdin, tmp.stdout, tmp.stderr def icmd(self, args): return os.spawnlp(os.P_WAIT, VCMD, VCMD, "-c", self.ctrlchnlname, "--", *args) def redircmd(self, infd, outfd, errfd, args, wait = True): ''' Execute a command on a node with standard input, output, and error redirected according to the given file descriptors. ''' if not USE_VCMD_MODULE: raise NotImplementedError if not self.cmdchnl.connected(): raise ValueError, "self.cmdchnl not connected" tmp = self.cmdchnl.redircmd(infd, outfd, errfd, args) if not wait: return tmp tmp = tmp.wait() if tmp: self.warn("cmd exited with status %s: %s" % (tmp, str(args))) return tmp def term(self, sh = "/bin/sh"): cmd = ("xterm", "-ut", "-title", self.name, "-e", VCMD, "-c", self.ctrlchnlname, "--", sh) if "SUDO_USER" in os.environ: cmd = ("su", "-s", "/bin/sh", "-c", "exec " + " ".join(map(lambda x: "'%s'" % x, cmd)), os.environ["SUDO_USER"]) return os.spawnvp(os.P_NOWAIT, cmd[0], cmd) def termcmdstring(self, sh = "/bin/sh"): return "%s -c %s -- %s" % (VCMD, self.ctrlchnlname, sh) def shcmd(self, cmdstr, sh = "/bin/sh"): return self.cmd([sh, "-c", cmdstr]) def getaddr(self, ifname, rescan = False): if ifname in self._addr and not rescan: return self._addr[ifname] tmp = {"ether": [], "inet": [], "inet6": [], "inet6link": []} cmd = [IP_BIN, "addr", "show", "dev", ifname] cmdid, cmdin, cmdout, cmderr = self.popen(cmd) cmdin.close() for line in cmdout: line = line.strip().split() if line[0] == "link/ether": tmp["ether"].append(line[1]) elif line[0] == "inet": tmp["inet"].append(line[1]) elif line[0] == "inet6": if line[3] == "global": tmp["inet6"].append(line[1]) elif line[3] == "link": tmp["inet6link"].append(line[1]) else: self.warn("unknown scope: %s" % line[3]) else: pass err = cmderr.read() cmdout.close() cmderr.close() status = cmdid.wait() if status: self.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd)) if err: self.warn("error output: %s" % err) self._addr[ifname] = tmp return tmp def netifstats(self, ifname = None): stats = {} cmd = ["cat", "/proc/net/dev"] cmdid, cmdin, cmdout, cmderr = self.popen(cmd) cmdin.close() # ignore first line cmdout.readline() # second line has count names tmp = cmdout.readline().strip().split("|") rxkeys = tmp[1].split() txkeys = tmp[2].split() for line in cmdout: line = line.strip().split() 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 err = cmderr.read() cmdout.close() cmderr.close() status = cmdid.wait() if status: self.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd)) if err: self.warn("error output: %s" % err) if ifname is not None: return stats[ifname] else: return stats def createclients(sessiondir, clientcls = VnodeClient, cmdchnlfilterfunc = None): direntries = map(lambda x: os.path.join(sessiondir, x), os.listdir(sessiondir)) cmdchnls = filter(lambda x: stat.S_ISSOCK(os.stat(x).st_mode), direntries) if cmdchnlfilterfunc: cmdchnls = filter(cmdchnlfilterfunc, cmdchnls) cmdchnls.sort() return map(lambda x: clientcls(os.path.basename(x), x), cmdchnls) def createremoteclients(sessiondir, clientcls = VnodeClient, filterfunc = None): ''' Creates remote VnodeClients, for nodes emulated on other machines. The session.Broker writes a n1.conf/server file having the server's info. ''' direntries = map(lambda x: os.path.join(sessiondir, x), os.listdir(sessiondir)) nodedirs = filter(lambda x: stat.S_ISDIR(os.stat(x).st_mode), direntries) nodedirs = filter(lambda x: os.path.exists(os.path.join(x, "server")), nodedirs) if filterfunc: nodedirs = filter(filterfunc, nodedirs) nodedirs.sort() return map(lambda x: clientcls(x), nodedirs)