quick pass to try and provide documentation within core.netns

This commit is contained in:
Blake J. Harnden 2017-05-03 13:20:56 -07:00
parent 4ae7958a63
commit 2b866e1b3f
5 changed files with 824 additions and 78 deletions

View file

@ -1,6 +1,6 @@
"""
nodes.py: definition of an LxcNode and CoreNode classes, and other node classes
that inherit from the CoreNode, implementing specific node types.
Definition of LxcNode, CoreNode, and other node classes that inherit from the CoreNode,
implementing specific node types.
"""
import socket
@ -28,6 +28,9 @@ logger = log.get_logger(__name__)
class CtrlNet(LxBrNet):
"""
Control network functionality.
"""
policy = "ACCEPT"
# base control interface index
CTRLIF_IDX_BASE = 99
@ -41,6 +44,20 @@ class CtrlNet(LxBrNet):
def __init__(self, session, objid="ctrlnet", name=None, prefix=None,
hostid=None, start=True, assign_address=True,
updown_script=None, serverintf=None):
"""
Creates a CtrlNet instance.
:param core.session.Session session: core session instance
:param int objid: node id
:param str name: node namee
:param prefix: control network ipv4 prefix
:param hostid: host id
:param bool start: start flag
:param str assign_address: assigned address
:param str updown_script: updown script
:param serverintf: server interface
:return:
"""
self.prefix = ipaddress.Ipv4Prefix(prefix)
self.hostid = hostid
self.assign_address = assign_address
@ -49,6 +66,11 @@ class CtrlNet(LxBrNet):
LxBrNet.__init__(self, session, objid=objid, name=name, start=start)
def startup(self):
"""
Startup functionality for the control network.
:return: nothing
"""
if self.detectoldbridge():
return
@ -80,6 +102,9 @@ class CtrlNet(LxBrNet):
"""
Occassionally, control net bridges from previously closed sessions are not cleaned up.
Check if there are old control net bridges and delete them
:return: True if an old bridge was detected, False otherwise
:rtype: bool
"""
retstat, retstr = utils.cmdresult([constants.BRCTL_BIN, 'show'])
if retstat != 0:
@ -110,6 +135,11 @@ class CtrlNet(LxBrNet):
return False
def shutdown(self):
"""
Control network shutdown.
:return: nothing
"""
if self.serverintf is not None:
try:
subprocess.check_call([constants.BRCTL_BIN, "delif", self.brname, self.serverintf])
@ -125,27 +155,42 @@ class CtrlNet(LxBrNet):
def all_link_data(self, flags):
"""
Do not include CtrlNet in link messages describing this session.
:return: nothing
"""
return []
class CoreNode(LxcNode):
"""
Basic core node class for nodes to extend.
"""
apitype = NodeTypes.DEFAULT.value
class PtpNet(LxBrNet):
"""
Peer to peer network node.
"""
policy = "ACCEPT"
def attach(self, netif):
if len(self._netif) > 1:
raise ValueError, \
"Point-to-point links support at most 2 network interfaces"
"""
Attach a network interface, but limit attachment to two interfaces.
:param core.coreobj.PyCoreNetIf netif: network interface
:return: nothing
"""
if len(self._netif) >= 2:
raise ValueError("Point-to-point links support at most 2 network interfaces")
LxBrNet.attach(self, netif)
def data(self, message_type):
"""
Do not generate a Node Message for point-to-point links. They are
built using a link message instead.
:return: nothing
"""
pass
@ -153,6 +198,9 @@ class PtpNet(LxBrNet):
"""
Build CORE API TLVs for a point-to-point link. One Link message
describes this network.
:return: all link data
:rtype: list[LinkData]
"""
all_links = []
@ -253,20 +301,31 @@ class PtpNet(LxBrNet):
class SwitchNode(LxBrNet):
"""
Provides switch functionality within a core node.
"""
apitype = NodeTypes.SWITCH.value
policy = "ACCEPT"
type = "lanswitch"
class HubNode(LxBrNet):
"""
Provides hub functionality within a core node, forwards packets to all bridge
ports by turning off MAC address learning.
"""
apitype = NodeTypes.HUB.value
policy = "ACCEPT"
type = "hub"
def __init__(self, session, objid=None, name=None, start=True):
"""
the Hub node forwards packets to all bridge ports by turning off
the MAC address learning
Creates a HubNode instance.
:param core.session.Session session: core session instance
:param int objid: node id
:param str name: node namee
:param bool start: start flag
"""
LxBrNet.__init__(self, session, objid, name, start)
if start:
@ -274,12 +333,24 @@ class HubNode(LxBrNet):
class WlanNode(LxBrNet):
"""
Provides wireless lan functionality within a core node.
"""
apitype = NodeTypes.WIRELESS_LAN.value
linktype = LinkTypes.WIRELESS.value
policy = "DROP"
type = "wlan"
def __init__(self, session, objid=None, name=None, start=True, policy=None):
"""
Create a WlanNode instance.
:param core.session.Session session: core session instance
:param int objid: node id
:param str name: node name
:param bool start: start flag
:param policy: wlan policy
"""
LxBrNet.__init__(self, session, objid, name, start, policy)
# wireless model such as basic range
self.model = None
@ -287,12 +358,18 @@ class WlanNode(LxBrNet):
self.mobility = None
def attach(self, netif):
"""
Attach a network interface.
:param core.coreobj.PyCoreNetIf netif: network interface
:return: nothing
"""
LxBrNet.attach(self, netif)
if self.model:
netif.poshook = self.model.position_callback
if netif.node is None:
return
(x, y, z) = netif.node.position.get()
x, y, z = netif.node.position.get()
# invokes any netif.poshook
netif.setposition(x, y, z)
# self.model.setlinkparams()
@ -302,8 +379,8 @@ class WlanNode(LxBrNet):
Sets the mobility and wireless model.
:param core.mobility.WirelessModel.cls model: wireless model to set to
:param config:
:return:
:param config: model configuration
:return: nothing
"""
logger.info("adding model %s" % model.name)
if model.config_type == RegisterTlvs.WIRELESS.value:
@ -321,6 +398,10 @@ class WlanNode(LxBrNet):
def updatemodel(self, model_name, values):
"""
Allow for model updates during runtime (similar to setmodel().)
:param model_name: model name to update
:param values: values to update model with
:return: nothing
"""
logger.info("updating model %s" % model_name)
if self.model is None or self.model.name != model_name:
@ -338,6 +419,13 @@ class WlanNode(LxBrNet):
self.model.setlinkparams()
def all_link_data(self, flags):
"""
Retrieve all link data.
:param flags: link flags
:return: all link data
:rtype: list[LinkData]
"""
all_links = LxBrNet.all_link_data(self, flags)
if self.model:
@ -355,6 +443,16 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
type = "rj45"
def __init__(self, session, objid=None, name=None, mtu=1500, start=True):
"""
Create an RJ45Node instance.
:param core.session.Session session: core session instance
:param int objid: node id
:param str name: node name
:param mtu: rj45 mtu
:param bool start: start flag
:return:
"""
PyCoreNode.__init__(self, session, objid, name, start=start)
# this initializes net, params, poshook
PyCoreNetIf.__init__(self, node=self, name=name, mtu=mtu)
@ -370,20 +468,24 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
def startup(self):
"""
Set the interface in the up state.
:return: nothing
"""
# interface will also be marked up during net.attach()
self.savestate()
try:
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
self.up = True
except subprocess.CalledProcessError:
logger.exception("failed to run command: %s link set %s up", constants.IP_BIN, self.localname)
return
self.up = True
def shutdown(self):
"""
Bring the interface down. Remove any addresses and queuing
disciplines.
:return: nothing
"""
if not self.up:
return
@ -393,43 +495,72 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
self.up = False
self.restorestate()
# TODO: issue in that both classes inherited from provide the same method with different signatures
def attachnet(self, net):
"""
Attach a network.
:param core.coreobj.PyCoreNet net: network to attach
:return: nothing
"""
PyCoreNetIf.attachnet(self, net)
def detachnet(self):
"""
Detach a network.
:return: nothing
"""
PyCoreNetIf.detachnet(self)
def newnetif(self, net=None, addrlist=[], hwaddr=None,
ifindex=None, ifname=None):
# TODO: parameters are not used
def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None):
"""
This is called when linking with another node. Since this node
represents an interface, we do not create another object here,
but attach ourselves to the given network.
:param core.coreobj.PyCoreNet net: new network instance
:param list[str] addrlist: address list
:param str hwaddr: hardware address
:param int ifindex: interface index
:param str ifname: interface name
:return:
"""
self.lock.acquire()
try:
with self.lock:
if ifindex is None:
ifindex = 0
if self.net is not None:
raise ValueError, \
"RJ45 nodes support at most 1 network interface"
raise ValueError("RJ45 nodes support at most 1 network interface")
self._netif[ifindex] = self
# PyCoreNetIf.node is self
self.node = self
self.ifindex = ifindex
if net is not None:
self.attachnet(net)
for addr in utils.maketuple(addrlist):
self.addaddr(addr)
if addrlist:
for addr in utils.maketuple(addrlist):
self.addaddr(addr)
return ifindex
finally:
self.lock.release()
def delnetif(self, ifindex):
"""
Delete a network interface.
:param int ifindex: interface index to delete
:return: nothing
"""
if ifindex is None:
ifindex = 0
if ifindex not in self._netif:
raise ValueError, "ifindex %s does not exist" % ifindex
self._netif.pop(ifindex)
if ifindex == self.ifindex:
self.shutdown()
@ -441,26 +572,54 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
This object is considered the network interface, so we only
return self here. This keeps the RJ45Node compatible with
real nodes.
:param int ifindex: interface index to retrieve
:param net: network to retrieve
:return: a network interface
:rtype: core.coreobj.PyCoreNetIf
"""
if net is not None and net == self.net:
return self
if ifindex is None:
ifindex = 0
if ifindex == self.ifindex:
return self
return None
def getifindex(self, netif):
"""
Retrieve network interface index.
:param core.coreobj.PyCoreNetIf netif: network interface to retrieve index for
:return: interface index, None otherwise
:rtype: int
"""
if netif != self:
return None
return self.ifindex
def addaddr(self, addr):
"""
Add address to to network interface.
:param str addr: address to add
:return: nothing
"""
if self.up:
subprocess.check_call([constants.IP_BIN, "addr", "add", str(addr), "dev", self.name])
PyCoreNetIf.addaddr(self, addr)
def deladdr(self, addr):
"""
Delete address from network interface.
:param str addr: address to delete
:return: nothing
"""
if self.up:
subprocess.check_call([constants.IP_BIN, "addr", "del", str(addr), "dev", self.name])
PyCoreNetIf.deladdr(self, addr)
@ -469,6 +628,8 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
"""
Save the addresses and other interface state before using the
interface for emulation purposes. TODO: save/restore the PROMISC flag
:return: nothing
"""
self.old_up = False
self.old_addrs = []
@ -500,6 +661,8 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
def restorestate(self):
"""
Restore the addresses and other interface state after using it.
:return: nothing
"""
for addr in self.old_addrs:
if addr[1] is None:
@ -512,6 +675,8 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
def setposition(self, x=None, y=None, z=None):
"""
Use setposition() from both parent classes.
:return: nothing
"""
PyCoreObj.setposition(self, x, y, z)
# invoke any poshook
@ -519,6 +684,9 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
class TunnelNode(GreTapBridge):
"""
Provides tunnel functionality in a core node.
"""
apitype = NodeTypes.TUNNEL.value
policy = "ACCEPT"
type = "tunnel"

View file

@ -1,6 +1,5 @@
"""
PyCoreNetIf classes that implement the interfaces available
under Linux.
virtual ethernet classes that implement the interfaces available under Linux.
"""
import subprocess
@ -19,7 +18,23 @@ utils.check_executables([constants.IP_BIN])
class VEth(PyCoreNetIf):
"""
Provides virtual ethernet functionality for core nodes.
"""
# TODO: network is not used, why was it needed?
def __init__(self, node, name, localname, mtu=1500, net=None, start=True):
"""
Creates a VEth instance.
:param core.netns.nodes.CoreNode node: related core node
:param str name: interface name
:param str localname: interface local name
:param mtu: interface mtu
:param net: network
:param bool start: start flag
:return:
"""
# note that net arg is ignored
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
self.localname = localname
@ -28,12 +43,22 @@ class VEth(PyCoreNetIf):
self.startup()
def startup(self):
"""
Interface startup logic.
:return: nothing
"""
subprocess.check_call([constants.IP_BIN, "link", "add", "name", self.localname,
"type", "veth", "peer", "name", self.name])
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
self.up = True
def shutdown(self):
"""
Interface shutdown logic.
:return: nothing
"""
if not self.up:
return
if self.node:
@ -48,7 +73,18 @@ class TunTap(PyCoreNetIf):
TUN/TAP virtual device in TAP mode
"""
# TODO: network is not used, why was it needed?
def __init__(self, node, name, localname, mtu=1500, net=None, start=True):
"""
Create a TunTap instance.
:param core.netns.nodes.CoreNode node: related core node
:param str name: interface name
:param str localname: local interface name
:param mtu: interface mtu
:param net: related network
:param bool start: start flag
"""
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
self.localname = localname
self.up = False
@ -57,6 +93,11 @@ class TunTap(PyCoreNetIf):
self.startup()
def startup(self):
"""
Startup logic for a tunnel tap.
:return: nothing
"""
# TODO: more sophisticated TAP creation here
# Debian does not support -p (tap) option, RedHat does.
# For now, this is disabled to allow the TAP to be created by another
@ -66,6 +107,11 @@ class TunTap(PyCoreNetIf):
self.up = True
def shutdown(self):
"""
Shutdown functionality for a tunnel tap.
:return: nothing
"""
if not self.up:
return
self.node.cmd([constants.IP_BIN, "-6", "addr", "flush", "dev", self.name])
@ -75,7 +121,12 @@ class TunTap(PyCoreNetIf):
def waitfor(self, func, attempts=10, maxretrydelay=0.25):
"""
Wait for func() to return zero with exponential backoff
Wait for func() to return zero with exponential backoff.
:param func: function to wait for a result of zero
:param int attempts: number of attempts to wait for a zero result
:param float maxretrydelay: maximum retry delay
:return: nothing
"""
delay = 0.01
for i in xrange(1, attempts + 1):
@ -100,6 +151,9 @@ class TunTap(PyCoreNetIf):
"""
Check for presence of a local device - tap device may not
appear right away waits
:return: wait for device local response
:rtype: int
"""
def localdevexists():
@ -110,8 +164,9 @@ class TunTap(PyCoreNetIf):
def waitfordevicenode(self):
"""
Check for presence of a node device - tap device may not
appear right away waits
Check for presence of a node device - tap device may not appear right away waits.
:return: nothing
"""
def nodedevexists():
@ -139,9 +194,12 @@ class TunTap(PyCoreNetIf):
startup() method but called at a later time when a userspace
program (running on the host) has had a chance to open the socket
end of the TAP.
:return: nothing
"""
self.waitfordevicelocal()
netns = str(self.node.pid)
try:
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "netns", netns])
except subprocess.CalledProcessError:
@ -149,12 +207,15 @@ class TunTap(PyCoreNetIf):
msg += "ip link set %s netns %s" % (self.localname, netns)
logger.exception(msg)
return
self.node.cmd([constants.IP_BIN, "link", "set", self.localname, "name", self.name])
self.node.cmd([constants.IP_BIN, "link", "set", self.name, "up"])
def setaddrs(self):
"""
Set interface addresses based on self.addrlist.
:return: nothing
"""
self.waitfordevicenode()
for addr in self.addrlist:
@ -171,6 +232,20 @@ class GreTap(PyCoreNetIf):
def __init__(self, node=None, name=None, session=None, mtu=1458,
remoteip=None, objid=None, localip=None, ttl=255,
key=None, start=True):
"""
Creates a GreTap instance.
:param core.netns.nodes.CoreNode node: related core node
:param str name: interface name
:param core.session.Session session: core session instance
:param mtu: interface mtu
:param str remoteip: remote address
:param int objid: object id
:param str localip: local address
:param ttl: ttl value
:param key: gre tap key
:param bool start: start flag
"""
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
self.session = session
if objid is None:
@ -201,6 +276,11 @@ class GreTap(PyCoreNetIf):
self.up = True
def shutdown(self):
"""
Shutdown logic for a GreTap.
:return: nothing
"""
if self.localname:
cmd = ("ip", "link", "set", self.localname, "down")
subprocess.check_call(cmd)
@ -209,7 +289,20 @@ class GreTap(PyCoreNetIf):
self.localname = None
def data(self, message_type):
"""
Data for a gre tap.
:param message_type: message type for data
:return: None
"""
return None
def all_link_data(self, flags):
"""
Retrieve link data.
:param flags: link flags
:return: link data
:rtype: list[core.data.LinkData]
"""
return []

View file

@ -58,6 +58,8 @@ class EbtablesQueue(object):
def startupdateloop(self, wlan):
"""
Kick off the update loop; only needs to be invoked once.
:return: nothing
"""
self.updatelock.acquire()
self.last_update_time[wlan] = time.time()
@ -72,6 +74,8 @@ class EbtablesQueue(object):
def stopupdateloop(self, wlan):
"""
Kill the update loop thread if there are no more WLANs using it.
:return: nothing
"""
self.updatelock.acquire()
try:
@ -90,6 +94,10 @@ class EbtablesQueue(object):
def ebatomiccmd(self, cmd):
"""
Helper for building ebtables atomic file command list.
:param list[str] cmd: ebtable command
:return: ebtable atomic command
:rtype: list[str]
"""
r = [constants.EBTABLES_BIN, "--atomic-file", self.atomic_file]
if cmd:
@ -99,17 +107,25 @@ class EbtablesQueue(object):
def lastupdate(self, wlan):
"""
Return the time elapsed since this WLAN was last updated.
:param wlan: wlan entity
:return: elpased time
:rtype: float
"""
try:
elapsed = time.time() - self.last_update_time[wlan]
except KeyError:
self.last_update_time[wlan] = time.time()
elapsed = 0.0
return elapsed
def updated(self, wlan):
"""
Keep track of when this WLAN was last updated.
:param wlan: wlan entity
:return: nothing
"""
self.last_update_time[wlan] = time.time()
self.updates.remove(wlan)
@ -119,6 +135,8 @@ class EbtablesQueue(object):
Thread target that looks for WLANs needing update, and
rate limits the amount of ebtables activity. Only one userspace program
should use ebtables at any given time, or results can be unpredictable.
:return: nothing
"""
while self.doupdateloop:
self.updatelock.acquire()
@ -144,8 +162,9 @@ class EbtablesQueue(object):
def ebcommit(self, wlan):
"""
Perform ebtables atomic commit using commands built in the
self.cmds list.
Perform ebtables atomic commit using commands built in the self.cmds list.
:return: nothing
"""
# save kernel ebtables snapshot to a file
cmd = self.ebatomiccmd(["--atomic-save", ])
@ -177,6 +196,8 @@ class EbtablesQueue(object):
"""
Flag a change to the given WLAN's _linked dict, so the ebtables
chain will be rebuilt at the next interval.
:return: nothing
"""
self.updatelock.acquire()
if wlan not in self.updates:
@ -185,8 +206,9 @@ class EbtablesQueue(object):
def buildcmds(self, wlan):
"""
Inspect a _linked dict from a wlan, and rebuild the ebtables chain
for that WLAN.
Inspect a _linked dict from a wlan, and rebuild the ebtables chain for that WLAN.
:return: nothing
"""
wlan._linked_lock.acquire()
# flush the chain
@ -213,18 +235,34 @@ ebq = EbtablesQueue()
def ebtablescmds(call, cmds):
ebtables_lock.acquire()
try:
"""
Run ebtable commands.
:param func call: function to call commands
:param list cmds: commands to call
:return: nothing
"""
with ebtables_lock:
for cmd in cmds:
call(cmd)
finally:
ebtables_lock.release()
class LxBrNet(PyCoreNet):
"""
Provides linux bridge network functionlity for core nodes.
"""
policy = "DROP"
def __init__(self, session, objid=None, name=None, start=True, policy=None):
"""
Creates a LxBrNet instance.
:param core.session.Session session: core session instance
:param int objid: object id
:param str name: object name
:param bool start: start flag
:param policy: network policy
"""
PyCoreNet.__init__(self, session, objid, name, start)
if name is None:
name = str(self.objid)
@ -239,6 +277,11 @@ class LxBrNet(PyCoreNet):
ebq.startupdateloop(self)
def startup(self):
"""
Linux bridge starup logic.
:return: nothing
"""
try:
subprocess.check_call([constants.BRCTL_BIN, "addbr", self.brname])
except subprocess.CalledProcessError:
@ -264,6 +307,11 @@ class LxBrNet(PyCoreNet):
self.up = True
def shutdown(self):
"""
Linux bridge shutdown logic.
:return: nothing
"""
if not self.up:
return
ebq.stopupdateloop(self)
@ -282,6 +330,12 @@ class LxBrNet(PyCoreNet):
self.up = False
def attach(self, netif):
"""
Attach a network interface.
:param core.netns.vif.VEth netif: network interface to attach
:return: nothing
"""
if self.up:
try:
subprocess.check_call([constants.BRCTL_BIN, "addif", self.brname, netif.localname])
@ -292,6 +346,12 @@ class LxBrNet(PyCoreNet):
PyCoreNet.attach(self, netif)
def detach(self, netif):
"""
Detach a network interface.
:param core.netns.vif.Veth netif: network interface to detach
:return: nothing
"""
if self.up:
try:
subprocess.check_call([constants.BRCTL_BIN, "delif", self.brname, netif.localname])
@ -301,11 +361,21 @@ class LxBrNet(PyCoreNet):
PyCoreNet.detach(self, netif)
def linked(self, netif1, netif2):
"""
Determine if the provided network interfaces are linked.
:param core.netns.vif.Veth netif1: interface one
:param core.netns.vif.Veth netif2: interface two
:return: True if interfaces are linked, False otherwise
:rtype: bool
"""
# check if the network interfaces are attached to this network
if self._netif[netif1.netifi] != netif1:
raise ValueError, "inconsistency for netif %s" % netif1.name
raise ValueError("inconsistency for netif %s" % netif1.name)
if self._netif[netif2.netifi] != netif2:
raise ValueError, "inconsistency for netif %s" % netif2.name
raise ValueError("inconsistency for netif %s" % netif2.name)
try:
linked = self._linked[netif1][netif2]
except KeyError:
@ -314,14 +384,19 @@ class LxBrNet(PyCoreNet):
elif self.policy == "DROP":
linked = False
else:
raise Exception, "unknown policy: %s" % self.policy
raise Exception("unknown policy: %s" % self.policy)
self._linked[netif1][netif2] = linked
return linked
def unlink(self, netif1, netif2):
"""
Unlink two PyCoreNetIfs, resulting in adding or removing ebtables
filtering rules.
:param core.netns.vif.Veth netif1: interface one
:param core.netns.vif.Veth netif2: interface two
:return: nothing
"""
self._linked_lock.acquire()
if not self.linked(netif1, netif2):
@ -335,6 +410,10 @@ class LxBrNet(PyCoreNet):
"""
Link two PyCoreNetIfs together, resulting in adding or removing
ebtables filtering rules.
:param core.netns.vif.Veth netif1: interface one
:param core.netns.vif.Veth netif2: interface two
:return: nothing
"""
self._linked_lock.acquire()
if self.linked(netif1, netif2):
@ -347,8 +426,17 @@ class LxBrNet(PyCoreNet):
def linkconfig(self, netif, bw=None, delay=None, loss=None, duplicate=None,
jitter=None, netif2=None, devname=None):
"""
Configure link parameters by applying tc queuing disciplines on the
interface.
Configure link parameters by applying tc queuing disciplines on the interface.
:param core.netns.vif.Veth netif: interface one
:param bw: bandwidth to set to
:param delay: packet delay to set to
:param loss: packet loss to set to
:param duplicate: duplicate percentage to set to
:param jitter: jitter to set to
:param core.netns.vif.Veth netif2: interface two
:param devname: device name
:return: nothing
"""
if devname is None:
devname = netif.localname
@ -359,7 +447,8 @@ class LxBrNet(PyCoreNet):
# from tc-tbf(8): minimum value for burst is rate / kernel_hz
if bw is not None:
burst = max(2 * netif.mtu, bw / 1000)
limit = 0xffff # max IP payload
# max IP payload
limit = 0xffff
tbf = ["tbf", "rate", str(bw),
"burst", str(burst), "limit", str(limit)]
if bw > 0:
@ -422,22 +511,26 @@ class LxBrNet(PyCoreNet):
"""
Link this bridge with another by creating a veth pair and installing
each device into each bridge.
:param core.netns.vnet.LxBrNet net: network to link with
:return: created interface
:rtype: Veth
"""
sessionid = self.session.short_session_id()
try:
self_objid = '%x' % self.objid
self_objid = "%x" % self.objid
except TypeError:
self_objid = '%s' % self.objid
self_objid = "%s" % self.objid
try:
net_objid = '%x' % net.objid
net_objid = "%x" % net.objid
except TypeError:
net_objid = '%s' % net.objid
localname = 'veth%s.%s.%s' % (self_objid, net_objid, sessionid)
net_objid = "%s" % net.objid
localname = "veth%s.%s.%s" % (self_objid, net_objid, sessionid)
if len(localname) >= 16:
raise ValueError("interface local name '%s' too long" % localname)
name = 'veth%s.%s.%s' % (net_objid, self_objid, sessionid)
raise ValueError("interface local name %s too long" % localname)
name = "veth%s.%s.%s" % (net_objid, self_objid, sessionid)
if len(name) >= 16:
raise ValueError("interface name '%s' too long" % name)
raise ValueError("interface name %s too long" % name)
netif = VEth(node=None, name=name, localname=localname,
mtu=1500, net=self, start=self.up)
self.attach(netif)
@ -458,15 +551,22 @@ class LxBrNet(PyCoreNet):
"""
Return the interface of that links this net with another net
(that were linked using linknet()).
:param core.netns.vnet.LxBrNet net: interface to get link for
:return: interface the provided network is linked to
:rtype: core.netns.vnet.LxBrNet
"""
for netif in self.netifs():
if hasattr(netif, 'othernet') and netif.othernet == net:
if hasattr(netif, "othernet") and netif.othernet == net:
return netif
return None
def addrconfig(self, addrlist):
"""
Set addresses on the bridge.
:param list[str] addrlist: address list
:return: nothing
"""
if not self.up:
return
@ -485,6 +585,20 @@ class GreTapBridge(LxBrNet):
def __init__(self, session, remoteip=None, objid=None, name=None,
policy="ACCEPT", localip=None, ttl=255, key=None, start=True):
"""
Create a GreTapBridge instance.
:param core.session.Session session: core session instance
:param str remoteip: remote address
:param int objid: object id
:param str name: object name
:param policy: network policy
:param str localip: local address
:param ttl: ttl value
:param key: gre tap key
:param bool start: start flag
:return:
"""
LxBrNet.__init__(self, session=session, objid=objid, name=name, policy=policy, start=False)
self.grekey = key
if self.grekey is None:
@ -497,15 +611,16 @@ class GreTapBridge(LxBrNet):
if remoteip is None:
self.gretap = None
else:
self.gretap = GreTap(node=self, name=None, session=session,
remoteip=remoteip, objid=None, localip=localip, ttl=ttl,
key=self.grekey)
self.gretap = GreTap(node=self, name=None, session=session, remoteip=remoteip,
objid=None, localip=localip, ttl=ttl, key=self.grekey)
if start:
self.startup()
def startup(self):
"""
Creates a bridge and adds the gretap device to it.
:return: nothing
"""
LxBrNet.startup(self)
if self.gretap:
@ -514,6 +629,8 @@ class GreTapBridge(LxBrNet):
def shutdown(self):
"""
Detach the gretap device and remove the bridge.
:return: nothing
"""
if self.gretap:
self.detach(self.gretap)
@ -527,15 +644,17 @@ class GreTapBridge(LxBrNet):
creating the GreTap device, which requires the remoteip at startup.
The 1st address in the provided list is remoteip, 2nd optionally
specifies localip.
:param list addrlist: address list
:return: nothing
"""
if self.gretap:
raise ValueError, "gretap already exists for %s" % self.name
raise ValueError("gretap already exists for %s" % self.name)
remoteip = addrlist[0].split('/')[0]
localip = None
if len(addrlist) > 1:
localip = addrlist[1].split('/')[0]
self.gretap = GreTap(session=self.session, remoteip=remoteip,
objid=None, name=None,
self.gretap = GreTap(session=self.session, remoteip=remoteip, objid=None, name=None,
localip=localip, ttl=self.ttl, key=self.grekey)
self.attach(self.gretap)
@ -543,5 +662,8 @@ class GreTapBridge(LxBrNet):
"""
Set the GRE key used for the GreTap device. This needs to be set
prior to instantiating the GreTap device (before addrconfig).
:param key: gre key
:return: nothing
"""
self.grekey = key

View file

@ -1,6 +1,5 @@
"""
vnode.py: PyCoreNode and LxcNode classes that implement the network namespace
virtual node.
PyCoreNode and LxcNode classes that implement the network namespac virtual node.
"""
import os
@ -28,7 +27,21 @@ utils.check_executables([constants.IP_BIN])
class SimpleLxcNode(PyCoreNode):
"""
Provides simple lxc functionality for core nodes.
"""
valid_deladdrtype = ("inet", "inet6", "inet6link")
def __init__(self, session, objid=None, name=None, nodedir=None, start=True):
"""
Create a SimpleLxcNode instance.
:param core.session.Session session: core session instance
:param int objid: object id
:param str name: object name
:param str nodedir: node directory
:param bool start: start flag
"""
PyCoreNode.__init__(self, session, objid, name, start=start)
self.nodedir = nodedir
self.ctrlchnlname = os.path.abspath(os.path.join(self.session.session_dir, self.name))
@ -39,10 +52,17 @@ class SimpleLxcNode(PyCoreNode):
self._mounts = []
def alive(self):
"""
Check if the node is alive.
:return: True if node is alive, False otherwise
:rtype: bool
"""
try:
os.kill(self.pid, 0)
except OSError:
return False
return True
def startup(self):
@ -50,6 +70,8 @@ class SimpleLxcNode(PyCoreNode):
Start a new namespace node by invoking the vnoded process that
allocates a new namespace. Bring up the loopback device and set
the hostname.
:return: nothing
"""
if self.up:
raise Exception("already up")
@ -88,6 +110,11 @@ class SimpleLxcNode(PyCoreNode):
self.up = True
def shutdown(self):
"""
Shutdown logic for simple lxc nodes.
:return: nothing
"""
# nothing to do if node is not up
if not self.up:
return
@ -120,56 +147,143 @@ class SimpleLxcNode(PyCoreNode):
self.vnodeclient.close()
self.up = False
# TODO: potentially remove all these wrapper methods, just make use of object itself.
def cmd(self, args, wait=True):
"""
Wrapper around vnodeclient cmd.
:param args: arguments for ocmmand
:param wait: wait or not
:return:
"""
return self.vnodeclient.cmd(args, wait)
def cmdresult(self, args):
"""
Wrapper around vnodeclient cmdresult.
:param args: arguments for ocmmand
:return:
"""
return self.vnodeclient.cmdresult(args)
def popen(self, args):
"""
Wrapper around vnodeclient popen.
:param args: arguments for ocmmand
:return:
"""
return self.vnodeclient.popen(args)
def icmd(self, args):
"""
Wrapper around vnodeclient icmd.
:param args: arguments for ocmmand
:return:
"""
return self.vnodeclient.icmd(args)
def redircmd(self, infd, outfd, errfd, args, wait=True):
"""
Wrapper around vnodeclient redircmd.
:param infd: input file descriptor
:param outfd: output file descriptor
:param errfd: err file descriptor
:param args: command arguments
:param wait: wait or not
:return:
"""
return self.vnodeclient.redircmd(infd, outfd, errfd, args, wait)
def term(self, sh="/bin/sh"):
"""
Wrapper around vnodeclient term.
:param sh: shell to create terminal for
:return:
"""
return self.vnodeclient.term(sh=sh)
def termcmdstring(self, sh="/bin/sh"):
"""
Wrapper around vnodeclient termcmdstring.
:param sh: shell to run command in
:return:
"""
return self.vnodeclient.termcmdstring(sh=sh)
def shcmd(self, cmdstr, sh="/bin/sh"):
"""
Wrapper around vnodeclient shcmd.
:param str cmdstr: command string
:param sh: shell to run command in
:return:
"""
return self.vnodeclient.shcmd(cmdstr, sh=sh)
def boot(self):
"""
Boot logic.
:return: nothing
"""
pass
def mount(self, source, target):
"""
Create and mount a directory.
:param str source: source directory to mount
:param str target: target directory to create
:return: nothing
"""
source = os.path.abspath(source)
logger.info("mounting %s at %s" % (source, target))
try:
shcmd = "mkdir -p '%s' && %s -n --bind '%s' '%s'" % \
(target, constants.MOUNT_BIN, source, target)
shcmd = "mkdir -p '%s' && %s -n --bind '%s' '%s'" % (
target, constants.MOUNT_BIN, source, target)
self.shcmd(shcmd)
self._mounts.append((source, target))
except:
except IOError:
logger.exception("mounting failed for %s at %s", source, target)
def umount(self, target):
"""
Unmount a target directory.
:param str target: target directory to unmount
:return: nothing
"""
logger.info("unmounting '%s'" % target)
try:
self.cmd([constants.UMOUNT_BIN, "-n", "-l", target])
except:
except IOError:
logger.exception("unmounting failed for %s" % target)
def newifindex(self):
"""
Retrieve a new interface index.
:return: new interface index
:rtype: int
"""
with self.lock:
return PyCoreNode.newifindex(self)
return super(SimpleLxcNode, self).newifindex()
def newveth(self, ifindex=None, ifname=None, net=None):
"""
Create a new interface.
:param int ifindex: index for the new interface
:param str ifname: name for the new interface
:param net: network to associate interface with
:return: nothing
"""
self.lock.acquire()
try:
if ifindex is None:
@ -187,8 +301,7 @@ class SimpleLxcNode(PyCoreNode):
localname = 'veth' + suffix
if len(localname) >= 16:
raise ValueError, "interface local name '%s' too long" % \
localname
raise ValueError("interface local name '%s' too long" % localname)
name = localname + 'p'
if len(name) >= 16:
raise ValueError, "interface name '%s' too long" % name
@ -221,6 +334,15 @@ class SimpleLxcNode(PyCoreNode):
self.lock.release()
def newtuntap(self, ifindex=None, ifname=None, net=None):
"""
Create a new tunnel tap.
:param int ifindex: interface index
:param str ifname: interface name
:param net: network to associate with
:return: interface index
:rtype: int
"""
self.lock.acquire()
try:
if ifindex is None:
@ -235,15 +357,22 @@ class SimpleLxcNode(PyCoreNode):
mtu=1500, net=net, start=self.up)
try:
self.addnetif(tuntap, ifindex)
except:
except Exception as e:
tuntap.shutdown()
del tuntap
raise
raise e
return ifindex
finally:
self.lock.release()
def sethwaddr(self, ifindex, addr):
"""
Set hardware addres for an interface.
:param int ifindex: index of interface to set hardware address for
:param str addr: hardware address to set
:return: mothing
"""
self._netif[ifindex].sethwaddr(addr)
if self.up:
(status, result) = self.cmdresult([constants.IP_BIN, "link", "set", "dev",
@ -252,12 +381,26 @@ class SimpleLxcNode(PyCoreNode):
logger.error("error setting MAC address %s", str(addr))
def addaddr(self, ifindex, addr):
"""
Add interface address.
:param int ifindex: index of interface to add address to
:param str addr: address to add to interface
:return: nothing
"""
if self.up:
self.cmd([constants.IP_BIN, "addr", "add", str(addr),
"dev", self.ifname(ifindex)])
self._netif[ifindex].addaddr(addr)
def deladdr(self, ifindex, addr):
"""
Delete address from an interface.
:param int ifindex: index of interface to delete address from
:param str addr: address to delete from interface
:return: nothing
"""
try:
self._netif[ifindex].deladdr(addr)
except ValueError:
@ -266,9 +409,14 @@ class SimpleLxcNode(PyCoreNode):
if self.up:
self.cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)])
valid_deladdrtype = ("inet", "inet6", "inet6link")
def delalladdr(self, ifindex, addrtypes=valid_deladdrtype):
"""
Delete all addresses from an interface.
:param int ifindex: index of interface to delete all addresses from
:param tuple addrtypes: address types to delete
:return: nothing
"""
addr = self.getaddr(self.ifname(ifindex), rescan=True)
for t in addrtypes:
if t not in self.valid_deladdrtype:
@ -279,16 +427,32 @@ class SimpleLxcNode(PyCoreNode):
self.getaddr(self.ifname(ifindex), rescan=True)
def ifup(self, ifindex):
"""
Bring an interface up.
:param int ifindex: index of interface to bring up
:return: nothing
"""
if self.up:
self.cmd([constants.IP_BIN, "link", "set", self.ifname(ifindex), "up"])
def newnetif(self, net=None, addrlist=[], hwaddr=None,
ifindex=None, ifname=None):
def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None):
"""
Create a new network interface.
:param net: network to associate with
:param list addrlist: addresses to add on the interface
:param str hwaddr: hardware address to set for interface
:param int ifindex: index of interface to create
:param str ifname: name for interface
:return: interface index
:rtype: int
"""
self.lock.acquire()
try:
# TODO: see if you can move this to emane specific code
if nodeutils.is_node(net, NodeTypes.EMANE):
ifindex = self.newtuntap(ifindex=ifindex, ifname=ifname,
net=net)
ifindex = self.newtuntap(ifindex=ifindex, ifname=ifname, net=net)
# TUN/TAP is not ready for addressing yet; the device may
# take some time to appear, and installing it into a
# namespace after it has been bound removes addressing;
@ -308,8 +472,9 @@ class SimpleLxcNode(PyCoreNode):
if hwaddr:
self.sethwaddr(ifindex, hwaddr)
for addr in utils.maketuple(addrlist):
self.addaddr(ifindex, addr)
if addrlist:
for addr in utils.maketuple(addrlist):
self.addaddr(ifindex, addr)
self.ifup(ifindex)
return ifindex
@ -317,6 +482,14 @@ class SimpleLxcNode(PyCoreNode):
self.lock.release()
def connectnode(self, ifname, othernode, otherifname):
"""
Connect a node.
:param str ifname: name of interface to connect
:param core.netns.nodes.LxcNode othernode: node to connect to
:param str otherifname: interface name to connect to
:return: nothing
"""
tmplen = 8
tmp1 = "tmp." + "".join([random.choice(string.ascii_lowercase)
for x in xrange(tmplen)])
@ -335,20 +508,53 @@ class SimpleLxcNode(PyCoreNode):
othernode.newifindex())
def addfile(self, srcname, filename):
shcmd = "mkdir -p $(dirname '%s') && mv '%s' '%s' && sync" % \
(filename, srcname, filename)
"""
Add a file.
:param str srcname: source file name
:param str filename: file name to add
:return: nothing
"""
shcmd = "mkdir -p $(dirname '%s') && mv '%s' '%s' && sync" % (filename, srcname, filename)
self.shcmd(shcmd)
def getaddr(self, ifname, rescan=False):
"""
Wrapper around vnodeclient getaddr.
:param str ifname: interface name to get address for
:param bool rescan: rescan flag
:return:
"""
return self.vnodeclient.getaddr(ifname=ifname, rescan=rescan)
def netifstats(self, ifname=None):
"""
Wrapper around vnodeclient netifstate.
:param str ifname: interface name to get state for
:return:
"""
return self.vnodeclient.netifstats(ifname=ifname)
class LxcNode(SimpleLxcNode):
"""
Provides lcx node functionality for core nodes.
"""
def __init__(self, session, objid=None, name=None,
nodedir=None, bootsh="boot.sh", start=True):
"""
Create a LxcNode instance.
:param core.session.Session session: core session instance
:param int objid: object id
:param str name: object name
:param str nodedir: node directory
:param bootsh: boot shell
:param bool start: start flag
"""
super(LxcNode, self).__init__(session=session, objid=objid,
name=name, nodedir=nodedir, start=start)
self.bootsh = bootsh
@ -356,12 +562,27 @@ class LxcNode(SimpleLxcNode):
self.startup()
def boot(self):
"""
Boot the node.
:return: nothing
"""
self.session.services.bootnodeservices(self)
def validate(self):
"""
Validate the node.
:return: nothing
"""
self.session.services.validatenodeservices(self)
def startup(self):
"""
Startup logic for the node.
:return: nothing
"""
self.lock.acquire()
try:
self.makenodedir()
@ -374,6 +595,11 @@ class LxcNode(SimpleLxcNode):
self.lock.release()
def shutdown(self):
"""
Shutdown logic for the node.
:return: nothing
"""
if not self.up:
return
self.lock.acquire()
@ -381,11 +607,19 @@ class LxcNode(SimpleLxcNode):
# self.session.services.stopnodeservices(self)
try:
super(LxcNode, self).shutdown()
except:
logger.exception("error during shutdown")
finally:
self.rmnodedir()
self.lock.release()
def privatedir(self, path):
"""
Create a private directory.
:param str path: path to create
:return: nothing
"""
if path[0] != "/":
raise ValueError("path not fully qualified: %s" % path)
hostpath = os.path.join(self.nodedir, os.path.normpath(path).strip('/').replace('/', '.'))
@ -400,10 +634,13 @@ class LxcNode(SimpleLxcNode):
def hostfilename(self, filename):
"""
Return the name of a node's file on the host filesystem.
:param str filename: host file name
:return: path to file
"""
dirname, basename = os.path.split(filename)
if not basename:
raise ValueError, "no basename for filename: " + filename
raise ValueError("no basename for filename: " + filename)
if dirname and dirname[0] == "/":
dirname = dirname[1:]
dirname = dirname.replace("/", ".")
@ -411,6 +648,14 @@ class LxcNode(SimpleLxcNode):
return os.path.join(dirname, basename)
def opennodefile(self, filename, mode="w"):
"""
Open a node file, within it's directory.
:param str filename: file name to open
:param str mode: mode to open file in
:return: open file
:rtype: file
"""
hostfilename = self.hostfilename(filename)
dirname, basename = os.path.split(hostfilename)
if not os.path.isdir(dirname):
@ -418,6 +663,14 @@ class LxcNode(SimpleLxcNode):
return open(hostfilename, mode)
def nodefile(self, filename, contents, mode=0644):
"""
Create a node file with a given mode.
:param str filename: name of file to create
:param contents: contents of file
:param int mode: mode for file
:return: nothing
"""
f = self.opennodefile(filename, "w")
f.write(contents)
os.chmod(f.name, mode)
@ -428,6 +681,11 @@ class LxcNode(SimpleLxcNode):
"""
Copy a file to a node, following symlinks and preserving metadata.
Change file mode if specified.
:param str filename: file name to copy file to
:param str srcfilename: file to copy
:param int mode: mode to copy to
:return: nothing
"""
hostfilename = self.hostfilename(filename)
shutil.copy2(srcfilename, hostfilename)

View file

@ -24,7 +24,17 @@ VCMD = os.path.join(constants.CORE_SBIN_DIR, "vcmd")
class VnodeClient(object):
"""
Provides client functionality for interacting with a virtual node.
"""
def __init__(self, name, ctrlchnlname):
"""
Create a VnodeClient instance.
:param str name: name for client
:param str ctrlchnlname: control channel name
"""
self.name = name
self.ctrlchnlname = ctrlchnlname
if USE_VCMD_MODULE:
@ -34,18 +44,34 @@ class VnodeClient(object):
self._addr = {}
def connected(self):
"""
Check if node is connected or not.
:return: True if connected, False otherwise
:rtype: bool
"""
if USE_VCMD_MODULE:
return self.cmdchnl.connected()
else:
return True
def close(self):
"""
Close the client connection.
:return: nothing
"""
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).
:param list args: command arguments
:param bool wait: wait for command to end or not
:return: command status
:rtype: int
"""
if USE_VCMD_MODULE:
if not self.cmdchnl.connected():
@ -62,8 +88,10 @@ class VnodeClient(object):
tmp = os.spawnlp(mode, VCMD, VCMD, "-c", self.ctrlchnlname, "-q", "--", *args)
if not wait:
return tmp
if tmp:
logger.warn("cmd exited with status %s: %s" % (tmp, str(args)))
return tmp
def cmdresult(self, args):
@ -71,6 +99,10 @@ class VnodeClient(object):
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.
:param list args: command arguments
:return: command status and combined stdout and stderr output
:rtype: tuple[int, str]
"""
cmdid, cmdin, cmdout, cmderr = self.popen(args)
result = cmdout.read()
@ -82,6 +114,13 @@ class VnodeClient(object):
return status, result
def popen(self, args):
"""
Execute a popen command against the node.
:param list args: command arguments
:return: popen object, stdin, stdout, and stderr
:rtype: tuple
"""
if USE_VCMD_MODULE:
if not self.cmdchnl.connected():
raise ValueError("self.cmdchnl not connected")
@ -93,12 +132,27 @@ class VnodeClient(object):
return tmp, tmp.stdin, tmp.stdout, tmp.stderr
def icmd(self, args):
"""
Execute an icmd against a node.
:param list args: command arguments
:return: command result
:rtype: int
"""
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.
:param infd: stdin file descriptor
:param outfd: stdout file descriptor
:param errfd: stderr file descriptor
:param list args: command arguments
:param bool wait: wait flag
:return: command status
:rtype: int
"""
if not USE_VCMD_MODULE:
raise NotImplementedError
@ -113,6 +167,13 @@ class VnodeClient(object):
return tmp
def term(self, sh="/bin/sh"):
"""
Open a terminal on a node.
:param str sh: shell to open terminal with
:return: terminal command result
:rtype: int
"""
cmd = ("xterm", "-ut", "-title", self.name, "-e",
VCMD, "-c", self.ctrlchnlname, "--", sh)
if "SUDO_USER" in os.environ:
@ -122,12 +183,34 @@ class VnodeClient(object):
return os.spawnvp(os.P_NOWAIT, cmd[0], cmd)
def termcmdstring(self, sh="/bin/sh"):
"""
Create a terminal command string.
:param str sh: shell to execute command in
:return: str
"""
return "%s -c %s -- %s" % (VCMD, self.ctrlchnlname, sh)
def shcmd(self, cmdstr, sh="/bin/sh"):
"""
Execute a shell command.
:param str cmdstr: command string
:param str sh: shell to run command in
:return: command result
:rtype: int
"""
return self.cmd([sh, "-c", cmdstr])
def getaddr(self, ifname, rescan=False):
"""
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
"""
if ifname in self._addr and not rescan:
return self._addr[ifname]
tmp = {"ether": [], "inet": [], "inet6": [], "inet6link": []}
@ -161,6 +244,13 @@ class VnodeClient(object):
return tmp
def netifstats(self, ifname=None):
"""
Retrieve network interface state.
:param str ifname: name of interface to get state for
:return: interface state information
:rtype: dict
"""
stats = {}
cmd = ["cat", "/proc/net/dev"]
cmdid, cmdin, cmdout, cmderr = self.popen(cmd)
@ -199,6 +289,15 @@ class VnodeClient(object):
def createclients(sessiondir, clientcls=VnodeClient, cmdchnlfilterfunc=None):
"""
Create clients
:param str sessiondir: session directory to create clients
:param class clientcls: class to create clients from
:param func cmdchnlfilterfunc: command channel filter function
:return: list of created clients
:rtype: list
"""
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:
@ -211,6 +310,12 @@ 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.
:param str sessiondir: session directory to create clients
:param class clientcls: class to create clients from
:param func filterfunc: filter function
:return: list of remove clients
:rtype: list
"""
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)