merged cleanup branch with master

This commit is contained in:
Rod A Santiago 2017-06-19 18:09:28 -07:00
parent 0a91fe7a3e
commit 55a6e2dcef
81 changed files with 11596 additions and 15021 deletions

View file

@ -1,80 +1,114 @@
#
# CORE
# Copyright (c)2010-2013 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# authors: Tom Goff <thomas.goff@boeing.com>
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
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.
"""
from vnode import *
from vnet import *
from core.misc.ipaddr import *
from core.api import coreapi
import socket
import subprocess
import threading
from socket import AF_INET
from socket import AF_INET6
from core import constants
from core.coreobj import PyCoreNetIf
from core.coreobj import PyCoreNode
from core.coreobj import PyCoreObj
from core.data import LinkData
from core.enumerations import LinkTypes
from core.enumerations import NodeTypes
from core.enumerations import RegisterTlvs
from core.misc import ipaddress
from core.misc import log
from core.misc import utils
from core.netns.vnet import GreTapBridge
from core.netns.vnet import LxBrNet
from core.netns.vnode import LxcNode
logger = log.get_logger(__name__)
class CtrlNet(LxBrNet):
"""
Control network functionality.
"""
policy = "ACCEPT"
CTRLIF_IDX_BASE = 99 # base control interface index
DEFAULT_PREFIX_LIST = ["172.16.0.0/24 172.16.1.0/24 172.16.2.0/24 172.16.3.0/24 172.16.4.0/24",
"172.17.0.0/24 172.17.1.0/24 172.17.2.0/24 172.17.3.0/24 172.17.4.0/24",
"172.18.0.0/24 172.18.1.0/24 172.18.2.0/24 172.18.3.0/24 172.18.4.0/24",
"172.19.0.0/24 172.19.1.0/24 172.19.2.0/24 172.19.3.0/24 172.19.4.0/24"]
def __init__(self, session, objid = "ctrlnet", name = None,
verbose = False, prefix = None,
hostid = None, start = True, assign_address = True,
updown_script = None, serverintf = None):
self.prefix = IPv4Prefix(prefix)
# base control interface index
CTRLIF_IDX_BASE = 99
DEFAULT_PREFIX_LIST = [
"172.16.0.0/24 172.16.1.0/24 172.16.2.0/24 172.16.3.0/24 172.16.4.0/24",
"172.17.0.0/24 172.17.1.0/24 172.17.2.0/24 172.17.3.0/24 172.17.4.0/24",
"172.18.0.0/24 172.18.1.0/24 172.18.2.0/24 172.18.3.0/24 172.18.4.0/24",
"172.19.0.0/24 172.19.1.0/24 172.19.2.0/24 172.19.3.0/24 172.19.4.0/24"
]
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
self.updown_script = updown_script
self.serverintf = serverintf
LxBrNet.__init__(self, session, objid = objid, name = name,
verbose = verbose, start = start)
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
LxBrNet.startup(self)
if self.hostid:
addr = self.prefix.addr(self.hostid)
else:
addr = self.prefix.maxaddr()
addr = self.prefix.max_addr()
msg = "Added control network bridge: %s %s" % \
(self.brname, self.prefix)
(self.brname, self.prefix)
addrlist = ["%s/%s" % (addr, self.prefix.prefixlen)]
if self.assign_address:
self.addrconfig(addrlist = addrlist)
self.addrconfig(addrlist=addrlist)
msg += " address %s" % addr
self.session.info(msg)
logger.info(msg)
if self.updown_script is not None:
self.info("interface %s updown script '%s startup' called" % \
(self.brname, self.updown_script))
check_call([self.updown_script, self.brname, "startup"])
logger.info("interface %s updown script '%s startup' called" % \
(self.brname, self.updown_script))
subprocess.check_call([self.updown_script, self.brname, "startup"])
if self.serverintf is not None:
try:
check_call([BRCTL_BIN, "addif", self.brname, self.serverintf])
check_call([IP_BIN, "link", "set", self.serverintf, "up"])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, self.brname,
"Error joining server interface %s to controlnet bridge %s: %s" % \
(self.serverintf, self.brname, e))
subprocess.check_call([constants.BRCTL_BIN, "addif", self.brname, self.serverintf])
subprocess.check_call([constants.IP_BIN, "link", "set", self.serverintf, "up"])
except subprocess.CalledProcessError:
logger.exception("Error joining server interface %s to controlnet bridge %s",
self.serverintf, self.brname)
def detectoldbridge(self):
''' Occassionally, control net bridges from previously closed sessions are not cleaned up.
"""
Occassionally, control net bridges from previously closed sessions are not cleaned up.
Check if there are old control net bridges and delete them
'''
retstat, retstr = cmdresult([BRCTL_BIN,'show'])
:return: True if an old bridge was detected, False otherwise
:rtype: bool
"""
retstat, retstr = utils.cmdresult([constants.BRCTL_BIN, 'show'])
if retstat != 0:
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, None,
"Unable to retrieve list of installed bridges")
logger.error("Unable to retrieve list of installed bridges")
lines = retstr.split('\n')
for line in lines[1:]:
cols = line.split('\t')
@ -82,248 +116,346 @@ class CtrlNet(LxBrNet):
flds = cols[0].split('.')
if len(flds) == 3:
if flds[0] == 'b' and flds[1] == self.objid:
self.session.exception(coreapi.CORE_EXCP_LEVEL_FATAL, "CtrlNet.startup()", None,
"Error: An active control net bridge (%s) found. "\
"An older session might still be running. " \
"Stop all sessions and, if needed, delete %s to continue." % \
(oldbr, oldbr))
logger.error(
"Error: An active control net bridge (%s) found. " \
"An older session might still be running. " \
"Stop all sessions and, if needed, delete %s to continue." % \
(oldbr, oldbr)
)
return True
'''
"""
# Do this if we want to delete the old bridge
self.warn("Warning: Old %s bridge found: %s" % (self.objid, oldbr))
logger.warn("Warning: Old %s bridge found: %s" % (self.objid, oldbr))
try:
check_call([BRCTL_BIN, 'delbr', oldbr])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, oldbr,
"Error deleting old bridge %s" % oldbr)
self.info("Deleted %s" % oldbr)
'''
except subprocess.CalledProcessError as e:
logger.exception("Error deleting old bridge %s", oldbr, e)
logger.info("Deleted %s", oldbr)
"""
return False
def shutdown(self):
"""
Control network shutdown.
:return: nothing
"""
if self.serverintf is not None:
try:
check_call([BRCTL_BIN, "delif", self.brname, self.serverintf])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname,
"Error deleting server interface %s to controlnet bridge %s: %s" % \
(self.serverintf, self.brname, e))
subprocess.check_call([constants.BRCTL_BIN, "delif", self.brname, self.serverintf])
except subprocess.CalledProcessError:
logger.exception("Error deleting server interface %s to controlnet bridge %s",
self.serverintf, self.brname)
if self.updown_script is not None:
self.info("interface %s updown script '%s shutdown' called" % \
(self.brname, self.updown_script))
check_call([self.updown_script, self.brname, "shutdown"])
logger.info("interface %s updown script '%s shutdown' called" % (self.brname, self.updown_script))
subprocess.check_call([self.updown_script, self.brname, "shutdown"])
LxBrNet.shutdown(self)
def tolinkmsgs(self, flags):
''' Do not include CtrlNet in link messages describing this session.
'''
def all_link_data(self, flags):
"""
Do not include CtrlNet in link messages describing this session.
:return: nothing
"""
return []
class CoreNode(LxcNode):
apitype = coreapi.CORE_NODE_DEF
"""
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 tonodemsg(self, flags):
''' Do not generate a Node Message for point-to-point links. They are
built using a link message instead.
'''
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
def tolinkmsgs(self, flags):
''' Build CORE API TLVs for a point-to-point link. One Link message
describes this network.
'''
tlvdata = ""
def all_link_data(self, flags):
"""
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 = []
if len(self._netif) != 2:
return tlvdata
(if1, if2) = self._netif.items()
return all_links
if1, if2 = self._netif.items()
if1 = if1[1]
if2 = if2[1]
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER,
if1.node.objid)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER,
if2.node.objid)
uni = False
unidirectional = 0
if if1.getparams() != if2.getparams():
uni = True
tlvdata += self.netifparamstolink(if1)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_TYPE,
self.linktype)
if uni:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_UNI, 1)
unidirectional = 1
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1NUM, \
if1.node.getifindex(if1))
if if1.hwaddr:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1MAC,
if1.hwaddr)
for addr in if1.addrlist:
(ip, sep, mask) = addr.partition('/')
interface1_ip4 = None
interface1_ip4_mask = None
interface1_ip6 = None
interface1_ip6_mask = None
for address in if1.addrlist:
ip, sep, mask = address.partition('/')
mask = int(mask)
if isIPv4Address(ip):
if ipaddress.is_ipv4_address(ip):
family = AF_INET
tlvtypeip = coreapi.CORE_TLV_LINK_IF1IP4
tlvtypemask = coreapi.CORE_TLV_LINK_IF1IP4MASK
ipl = socket.inet_pton(family, ip)
interface1_ip4 = ipaddress.IpAddress(af=family, address=ipl)
interface1_ip4_mask = mask
else:
family = AF_INET6
tlvtypeip = coreapi.CORE_TLV_LINK_IF1IP6
tlvtypemask = coreapi.CORE_TLV_LINK_IF1IP6MASK
ipl = socket.inet_pton(family, ip)
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip,
IPAddr(af=family, addr=ipl))
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
ipl = socket.inet_pton(family, ip)
interface1_ip6 = ipaddress.IpAddress(af=family, address=ipl)
interface1_ip6_mask = mask
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM, \
if2.node.getifindex(if2))
if if2.hwaddr:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2MAC,
if2.hwaddr)
for addr in if2.addrlist:
(ip, sep, mask) = addr.partition('/')
interface2_ip4 = None
interface2_ip4_mask = None
interface2_ip6 = None
interface2_ip6_mask = None
for address in if2.addrlist:
ip, sep, mask = address.partition('/')
mask = int(mask)
if isIPv4Address(ip):
if ipaddress.is_ipv4_address(ip):
family = AF_INET
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP4
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP4MASK
ipl = socket.inet_pton(family, ip)
interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl)
interface2_ip4_mask = mask
else:
family = AF_INET6
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP6
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP6MASK
ipl = socket.inet_pton(family, ip)
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip,
IPAddr(af=family, addr=ipl))
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
msg = coreapi.CoreLinkMessage.pack(flags, tlvdata)
if not uni:
return [msg,]
ipl = socket.inet_pton(family, ip)
interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl)
interface2_ip6_mask = mask
# TODO: not currently used
# loss=netif.getparam('loss')
link_data = LinkData(
message_type=flags,
node1_id=if1.node.objid,
node2_id=if2.node.objid,
link_type=self.linktype,
unidirectional=unidirectional,
delay=if1.getparam("delay"),
bandwidth=if1.getparam("bw"),
dup=if1.getparam("duplicate"),
jitter=if1.getparam("jitter"),
interface1_id=if1.node.getifindex(if1),
interface1_mac=if1.hwaddr,
interface1_ip4=interface1_ip4,
interface1_ip4_mask=interface1_ip4_mask,
interface1_ip6=interface1_ip6,
interface1_ip6_mask=interface1_ip6_mask,
interface2_id=if2.node.getifindex(if2),
interface2_mac=if2.hwaddr,
interface2_ip4=interface2_ip4,
interface2_ip4_mask=interface2_ip4_mask,
interface2_ip6=interface2_ip6,
interface2_ip6_mask=interface2_ip6_mask,
)
all_links.append(link_data)
# build a 2nd link message for the upstream link parameters
# (swap if1 and if2)
tlvdata = ""
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER,
if2.node.objid)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER,
if1.node.objid)
tlvdata += self.netifparamstolink(if2)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_UNI, 1)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1NUM, \
if2.node.getifindex(if2))
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM, \
if1.node.getifindex(if1))
msg2 = coreapi.CoreLinkMessage.pack(0, tlvdata)
return [msg, msg2]
if unidirectional:
link_data = LinkData(
message_type=0,
node1_id=if2.node.objid,
node2_id=if1.node.objid,
delay=if1.getparam("delay"),
bandwidth=if1.getparam("bw"),
dup=if1.getparam("duplicate"),
jitter=if1.getparam("jitter"),
unidirectional=1,
interface1_id=if2.node.getifindex(if2),
interface2_id=if1.node.getifindex(if1)
)
all_links.append(link_data)
return all_links
class SwitchNode(LxBrNet):
apitype = coreapi.CORE_NODE_SWITCH
"""
Provides switch functionality within a core node.
"""
apitype = NodeTypes.SWITCH.value
policy = "ACCEPT"
type = "lanswitch"
class HubNode(LxBrNet):
apitype = coreapi.CORE_NODE_HUB
"""
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, verbose = False,
start = True):
''' the Hub node forwards packets to all bridge ports by turning off
the MAC address learning
'''
LxBrNet.__init__(self, session, objid, name, verbose, start)
def __init__(self, session, objid=None, name=None, start=True):
"""
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:
check_call([BRCTL_BIN, "setageing", self.brname, "0"])
subprocess.check_call([constants.BRCTL_BIN, "setageing", self.brname, "0"])
class WlanNode(LxBrNet):
apitype = coreapi.CORE_NODE_WLAN
linktype = coreapi.CORE_LINK_WIRELESS
"""
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, verbose = False,
start = True, policy = None):
LxBrNet.__init__(self, session, objid, name, verbose, start, policy)
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
# mobility model such as scripted
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._positioncallback
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()
# self.model.setlinkparams()
def setmodel(self, model, config):
''' Mobility and wireless model.
'''
if (self.verbose):
self.info("adding model %s" % model._name)
if model._type == coreapi.CORE_TLV_REG_WIRELESS:
self.model = model(session=self.session, objid=self.objid,
verbose=self.verbose, values=config)
if self.model._positioncallback:
"""
Sets the mobility and wireless model.
:param core.mobility.WirelessModel.cls model: wireless model to set to
:param config: model configuration
:return: nothing
"""
logger.info("adding model %s" % model.name)
if model.config_type == RegisterTlvs.WIRELESS.value:
self.model = model(session=self.session, object_id=self.objid, values=config)
if self.model.position_callback:
for netif in self.netifs():
netif.poshook = self.model._positioncallback
netif.poshook = self.model.position_callback
if netif.node is not None:
(x,y,z) = netif.node.position.get()
x, y, z = netif.node.position.get()
netif.poshook(netif, x, y, z)
self.model.setlinkparams()
elif model._type == coreapi.CORE_TLV_REG_MOBILITY:
self.mobility = model(session=self.session, objid=self.objid,
verbose=self.verbose, values=config)
elif model.config_type == RegisterTlvs.MOBILITY.value:
self.mobility = model(session=self.session, object_id=self.objid, values=config)
def updatemodel(self, model_name, values):
''' Allow for model updates during runtime (similar to setmodel().)
'''
if (self.verbose):
self.info("updating model %s" % model_name)
if self.model is None or self.model._name != model_name:
"""
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:
return
model = self.model
if model._type == coreapi.CORE_TLV_REG_WIRELESS:
if model.config_type == RegisterTlvs.WIRELESS.value:
if not model.updateconfig(values):
return
if self.model._positioncallback:
if self.model.position_callback:
for netif in self.netifs():
netif.poshook = self.model._positioncallback
netif.poshook = self.model.position_callback
if netif.node is not None:
(x,y,z) = netif.node.position.get()
(x, y, z) = netif.node.position.get()
netif.poshook(netif, x, y, z)
self.model.setlinkparams()
def tolinkmsgs(self, flags):
msgs = LxBrNet.tolinkmsgs(self, flags)
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:
msgs += self.model.tolinkmsgs(flags)
return msgs
all_links.extend(self.model.all_link_data(flags))
return all_links
class RJ45Node(PyCoreNode, PyCoreNetIf):
''' RJ45Node is a physical interface on the host linked to the emulated
network.
'''
apitype = coreapi.CORE_NODE_RJ45
"""
RJ45Node is a physical interface on the host linked to the emulated
network.
"""
apitype = NodeTypes.RJ45.value
type = "rj45"
def __init__(self, session, objid = None, name = None, mtu = 1500,
verbose = False, start = True):
PyCoreNode.__init__(self, session, objid, name, verbose=verbose,
start=start)
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)
PyCoreNetIf.__init__(self, node=self, name=name, mtu=mtu)
self.up = False
self.lock = threading.RLock()
self.ifindex = None
@ -334,65 +466,101 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
self.startup()
def startup(self):
''' Set the interface in the up state.
'''
"""
Set the interface in the up state.
:return: nothing
"""
# interface will also be marked up during net.attach()
self.savestate()
try:
check_call([IP_BIN, "link", "set", self.localname, "up"])
except:
self.warn("Failed to run command: %s link set %s up" % \
(IP_BIN, self.localname))
return
self.up = True
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)
def shutdown(self):
''' Bring the interface down. Remove any addresses and queuing
disciplines.
'''
"""
Bring the interface down. Remove any addresses and queuing
disciplines.
:return: nothing
"""
if not self.up:
return
check_call([IP_BIN, "link", "set", self.localname, "down"])
check_call([IP_BIN, "addr", "flush", "dev", self.localname])
mutecall([TC_BIN, "qdisc", "del", "dev", self.localname, "root"])
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "down"])
subprocess.check_call([constants.IP_BIN, "addr", "flush", "dev", self.localname])
utils.mutecall([constants.TC_BIN, "qdisc", "del", "dev", self.localname, "root"])
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):
''' 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.
'''
self.lock.acquire()
try:
# 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:
"""
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
self.node = self # PyCoreNetIf.node is self
# PyCoreNetIf.node is self
self.node = self
self.ifindex = ifindex
if net is not None:
self.attachnet(net)
for addr in 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()
@ -400,46 +568,78 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
raise ValueError, "ifindex %s does not exist" % ifindex
def netif(self, ifindex, net=None):
''' This object is considered the network interface, so we only
return self here. This keeps the RJ45Node compatible with
real nodes.
'''
"""
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:
check_call([IP_BIN, "addr", "add", str(addr), "dev", self.name])
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:
check_call([IP_BIN, "addr", "del", str(addr), "dev", self.name])
subprocess.check_call([constants.IP_BIN, "addr", "del", str(addr), "dev", self.name])
PyCoreNetIf.deladdr(self, addr)
def savestate(self):
''' Save the addresses and other interface state before using the
"""
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 = []
cmd = [IP_BIN, "addr", "show", "dev", self.localname]
cmd = [constants.IP_BIN, "addr", "show", "dev", self.localname]
try:
tmp = subprocess.Popen(cmd, stdout = subprocess.PIPE)
tmp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
except OSError:
self.warn("Failed to run %s command: %s" % (IP_BIN, cmd))
logger.exception("Failed to run %s command: %s", constants.IP_BIN, cmd)
if tmp.wait():
self.warn("Command failed: %s" % cmd)
logger.warn("Command failed: %s", cmd)
return
lines = tmp.stdout.read()
tmp.stdout.close()
@ -459,31 +659,34 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
self.old_addrs.append((items[1], None))
def restorestate(self):
''' Restore the addresses and other interface state after using it.
'''
"""
Restore the addresses and other interface state after using it.
:return: nothing
"""
for addr in self.old_addrs:
if addr[1] is None:
check_call([IP_BIN, "addr", "add", addr[0], "dev",
self.localname])
subprocess.check_call([constants.IP_BIN, "addr", "add", addr[0], "dev", self.localname])
else:
check_call([IP_BIN, "addr", "add", addr[0], "brd", addr[1],
"dev", self.localname])
subprocess.check_call([constants.IP_BIN, "addr", "add", addr[0], "brd", addr[1], "dev", self.localname])
if self.old_up:
check_call([IP_BIN, "link", "set", self.localname, "up"])
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
def setposition(self, x=None, y=None, z=None):
''' Use setposition() from both parent classes.
'''
"""
Use setposition() from both parent classes.
:return: nothing
"""
PyCoreObj.setposition(self, x, y, z)
# invoke any poshook
PyCoreNetIf.setposition(self, x, y, z)
class TunnelNode(GreTapBridge):
apitype = coreapi.CORE_NODE_TUNNEL
"""
Provides tunnel functionality in a core node.
"""
apitype = NodeTypes.TUNNEL.value
policy = "ACCEPT"
type = "tunnel"

View file

@ -1,58 +1,91 @@
#
# CORE
# Copyright (c)2011-2014 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# authors: Tom Goff <thomas.goff@boeing.com>
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
vif.py: PyCoreNetIf classes that implement the interfaces available
under Linux.
'''
"""
virtual ethernet classes that implement the interfaces available under Linux.
"""
import os, signal, shutil, sys, subprocess, vnodeclient, threading, string
import random, time
from core.api import coreapi
from core.misc.utils import *
from core.constants import *
from core.coreobj import PyCoreObj, PyCoreNode, PyCoreNetIf, Position
from core.emane.nodes import EmaneNode
import subprocess
import time
from core import constants
from core.coreobj import PyCoreNetIf
from core.enumerations import NodeTypes
from core.misc import log
from core.misc import nodeutils
from core.misc import utils
logger = log.get_logger(__name__)
utils.check_executables([constants.IP_BIN])
checkexec([IP_BIN])
class VEth(PyCoreNetIf):
def __init__(self, node, name, localname, mtu = 1500, net = None,
start = True):
"""
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)
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
self.localname = localname
self.up = False
if start:
self.startup()
def startup(self):
check_call([IP_BIN, "link", "add", "name", self.localname,
"type", "veth", "peer", "name", self.name])
check_call([IP_BIN, "link", "set", self.localname, "up"])
"""
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:
self.node.cmd([IP_BIN, "-6", "addr", "flush", "dev", self.name])
self.node.cmd([constants.IP_BIN, "-6", "addr", "flush", "dev", self.name])
if self.localname:
mutedetach([IP_BIN, "link", "delete", self.localname])
utils.mutedetach([constants.IP_BIN, "link", "delete", self.localname])
self.up = False
class TunTap(PyCoreNetIf):
''' TUN/TAP virtual device in TAP mode
'''
def __init__(self, node, name, localname, mtu = 1500, net = None,
start = True):
PyCoreNetIf.__init__(self, node = node, name = name, mtu = mtu)
"""
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
self.transport_type = "virtual"
@ -60,26 +93,41 @@ 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
# For now, this is disabled to allow the TAP to be created by another
# system (e.g. EMANE's emanetransportd)
#check_call(["tunctl", "-t", self.name])
# check_call(["tunctl", "-t", self.name])
# self.install()
self.up = True
def shutdown(self):
"""
Shutdown functionality for a tunnel tap.
:return: nothing
"""
if not self.up:
return
self.node.cmd([IP_BIN, "-6", "addr", "flush", "dev", self.name])
#if self.name:
self.node.cmd([constants.IP_BIN, "-6", "addr", "flush", "dev", self.name])
# if self.name:
# mutedetach(["tunctl", "-d", self.localname])
self.up = False
def waitfor(self, func, attempts = 10, maxretrydelay = 0.25):
'''\
Wait for func() to return zero with exponential backoff
'''
def waitfor(self, func, attempts=10, maxretrydelay=0.25):
"""
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):
r = func()
@ -88,98 +136,130 @@ class TunTap(PyCoreNetIf):
msg = 'attempt %s failed with nonzero exit status %s' % (i, r)
if i < attempts + 1:
msg += ', retrying...'
self.node.info(msg)
logger.info(msg)
time.sleep(delay)
delay = delay + delay
if delay > maxretrydelay:
delay = maxretrydelay
else:
msg += ', giving up'
self.node.info(msg)
raise RuntimeError, 'command failed after %s attempts' % attempts
logger.info(msg)
raise RuntimeError('command failed after %s attempts' % attempts)
def waitfordevicelocal(self):
'''\
"""
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():
cmd = (IP_BIN, 'link', 'show', self.localname)
return mutecall(cmd)
cmd = (constants.IP_BIN, 'link', 'show', self.localname)
return utils.mutecall(cmd)
self.waitfor(localdevexists)
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():
cmd = (IP_BIN, 'link', 'show', self.name)
cmd = (constants.IP_BIN, 'link', 'show', self.name)
return self.node.cmd(cmd)
count = 0
while True:
try:
self.waitfor(nodedevexists)
break
except RuntimeError:
except RuntimeError as e:
# check if this is an EMANE interface; if so, continue
# waiting if EMANE is still running
if count < 5 and isinstance(self.net, EmaneNode) and \
self.node.session.emane.emanerunning(self.node):
# TODO: remove emane code
if count < 5 and nodeutils.is_node(self.net, NodeTypes.EMANE) and \
self.node.session.emane.emanerunning(self.node):
count += 1
else:
raise
raise e
def install(self):
''' Install this TAP into its namespace. This is not done from the
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.
'''
"""
Install this TAP into its namespace. This is not done from the
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:
check_call([IP_BIN, "link", "set", self.localname, "netns", netns])
except Exception, e:
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "netns", netns])
except subprocess.CalledProcessError:
msg = "error installing TAP interface %s, command:" % self.localname
msg += "ip link set %s netns %s" % (self.localname, netns)
self.node.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.localname, msg)
self.node.warn(msg)
logger.exception(msg)
return
self.node.cmd([IP_BIN, "link", "set", self.localname,
"name", self.name])
self.node.cmd([IP_BIN, "link", "set", self.name, "up"])
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.
'''
"""
Set interface addresses based on self.addrlist.
:return: nothing
"""
self.waitfordevicenode()
for addr in self.addrlist:
self.node.cmd([IP_BIN, "addr", "add", str(addr),
"dev", self.name])
self.node.cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.name])
class GreTap(PyCoreNetIf):
''' GRE TAP device for tunneling between emulation servers.
Uses the "gretap" tunnel device type from Linux which is a GRE device
having a MAC address. The MAC address is required for bridging.
'''
def __init__(self, node = None, name = None, session = None, mtu = 1458,
remoteip = None, objid = None, localip = None, ttl = 255,
key = None, start = True):
PyCoreNetIf.__init__(self, node = node, name = name, mtu = mtu)
"""
GRE TAP device for tunneling between emulation servers.
Uses the "gretap" tunnel device type from Linux which is a GRE device
having a MAC address. The MAC address is required for bridging.
"""
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:
# from PyCoreObj
objid = (((id(self) >> 16) ^ (id(self) & 0xffff)) & 0xffff)
objid = ((id(self) >> 16) ^ (id(self) & 0xffff)) & 0xffff
self.objid = objid
sessionid = self.session.shortsessionid()
sessionid = self.session.short_session_id()
# interface name on the local host machine
self.localname = "gt.%s.%s" % (self.objid, sessionid)
self.transport_type = "raw"
if not start:
self.up = False
return
if remoteip is None:
raise ValueError, "missing remote IP required for GRE TAP device"
cmd = ("ip", "link", "add", self.localname, "type", "gretap",
@ -190,21 +270,39 @@ class GreTap(PyCoreNetIf):
cmd += ("ttl", str(ttl))
if key:
cmd += ("key", str(key))
check_call(cmd)
subprocess.check_call(cmd)
cmd = ("ip", "link", "set", self.localname, "up")
check_call(cmd)
subprocess.check_call(cmd)
self.up = True
def shutdown(self):
"""
Shutdown logic for a GreTap.
:return: nothing
"""
if self.localname:
cmd = ("ip", "link", "set", self.localname, "down")
check_call(cmd)
subprocess.check_call(cmd)
cmd = ("ip", "link", "del", self.localname)
check_call(cmd)
subprocess.check_call(cmd)
self.localname = None
def tonodemsg(self, flags):
def data(self, message_type):
"""
Data for a gre tap.
:param message_type: message type for data
:return: None
"""
return None
def tolinkmsgs(self, flags):
def all_link_data(self, flags):
"""
Retrieve link data.
:param flags: link flags
:return: link data
:rtype: list[core.data.LinkData]
"""
return []

View file

@ -1,42 +1,48 @@
#
# CORE
# Copyright (c)2010-2016 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# authors: Tom Goff <thomas.goff@boeing.com>
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
vnet.py: PyCoreNet and LxBrNet classes that implement virtual networks using
"""
PyCoreNet and LxBrNet classes that implement virtual networks using
Linux Ethernet bridging and ebtables rules.
'''
"""
import os, sys, threading, time, subprocess
import os
import subprocess
import threading
import time
from core.api import coreapi
from core.misc.utils import *
from core.constants import *
from core.coreobj import PyCoreNet, PyCoreObj
from core.netns.vif import VEth, GreTap
from core import constants
from core.coreobj import PyCoreNet
from core.misc import log
from core.misc import utils
from core.netns.vif import GreTap
from core.netns.vif import VEth
checkexec([BRCTL_BIN, IP_BIN, EBTABLES_BIN, TC_BIN])
logger = log.get_logger(__name__)
utils.check_executables([
constants.BRCTL_BIN,
constants.IP_BIN,
constants.EBTABLES_BIN,
constants.TC_BIN
])
ebtables_lock = threading.Lock()
class EbtablesQueue(object):
''' Helper class for queuing up ebtables commands into rate-limited
"""
Helper class for queuing up ebtables commands into rate-limited
atomic commits. This improves performance and reliability when there are
many WLAN link updates.
'''
"""
# update rate is every 300ms
rate = 0.3
# ebtables
atomic_file = "/tmp/pycore.ebtables.atomic"
def __init__(self):
''' Initialize the helper class, but don't start the update thread
"""
Initialize the helper class, but don't start the update thread
until a WLAN is instantiated.
'''
"""
self.doupdateloop = False
self.updatethread = None
# this lock protects cmds and updates lists
@ -48,28 +54,35 @@ class EbtablesQueue(object):
# timestamps of last WLAN update; this keeps track of WLANs that are
# using this queue
self.last_update_time = {}
def startupdateloop(self, wlan):
''' Kick off the update loop; only needs to be invoked once.
'''
"""
Kick off the update loop; only needs to be invoked once.
:return: nothing
"""
self.updatelock.acquire()
self.last_update_time[wlan] = time.time()
self.updatelock.release()
if self.doupdateloop:
return
self.doupdateloop = True
self.updatethread = threading.Thread(target = self.updateloop)
self.updatethread = threading.Thread(target=self.updateloop)
self.updatethread.daemon = True
self.updatethread.start()
def stopupdateloop(self, wlan):
''' Kill the update loop thread if there are no more WLANs using it.
'''
"""
Kill the update loop thread if there are no more WLANs using it.
:return: nothing
"""
self.updatelock.acquire()
try:
del self.last_update_time[wlan]
except KeyError:
pass
logger.exception("error deleting last update time for wlan: %s", wlan)
self.updatelock.release()
if len(self.last_update_time) > 0:
return
@ -77,152 +90,186 @@ class EbtablesQueue(object):
if self.updatethread:
self.updatethread.join()
self.updatethread = None
def ebatomiccmd(self, cmd):
''' Helper for building ebtables atomic file command list.
'''
r = [EBTABLES_BIN, "--atomic-file", self.atomic_file]
"""
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:
r.extend(cmd)
return r
def lastupdate(self, wlan):
''' Return the time elapsed since this WLAN was last updated.
'''
"""
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.
'''
"""
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)
def updateloop(self):
''' Thread target that looks for WLANs needing update, and
"""
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()
for wlan in self.updates:
'''
Check if wlan is from a previously closed session. Because of the
rate limiting scheme employed here, this may happen if a new session
"""
Check if wlan is from a previously closed session. Because of the
rate limiting scheme employed here, this may happen if a new session
is started soon after closing a previous session.
'''
"""
try:
wlan.session
except:
# Just mark as updated to remove from self.updates.
# Just mark as updated to remove from self.updates.
self.updated(wlan)
continue
if self.lastupdate(wlan) > self.rate:
self.buildcmds(wlan)
#print "ebtables commit %d rules" % len(self.cmds)
# print "ebtables commit %d rules" % len(self.cmds)
self.ebcommit(wlan)
self.updated(wlan)
self.updatelock.release()
time.sleep(self.rate)
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",])
cmd = self.ebatomiccmd(["--atomic-save", ])
try:
check_call(cmd)
except Exception, e:
self.eberror(wlan, "atomic-save (%s)" % cmd, e)
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
logger.exception("atomic-save (%s)", cmd)
# no atomic file, exit
return
# modify the table file using queued ebtables commands
for c in self.cmds:
cmd = self.ebatomiccmd(c)
try:
check_call(cmd)
except Exception, e:
self.eberror(wlan, "cmd=%s" % cmd, e)
pass
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
logger.exception("cmd=%s", cmd)
self.cmds = []
# commit the table file to the kernel
cmd = self.ebatomiccmd(["--atomic-commit",])
cmd = self.ebatomiccmd(["--atomic-commit", ])
try:
check_call(cmd)
subprocess.check_call(cmd)
os.unlink(self.atomic_file)
except Exception, e:
self.eberror(wlan, "atomic-commit (%s)" % cmd, e)
except OSError:
logger.exception("atomic-commit (%s)", cmd)
def ebchange(self, wlan):
''' Flag a change to the given WLAN's _linked dict, so the ebtables
"""
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:
self.updates.append(wlan)
self.updatelock.release()
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
self.cmds.extend([["-F", wlan.brname],])
self.cmds.extend([["-F", wlan.brname], ])
# rebuild the chain
for (netif1, v) in wlan._linked.items():
for (netif2, linked) in v.items():
for netif1, v in wlan._linked.items():
for netif2, linked in v.items():
if wlan.policy == "DROP" and linked:
self.cmds.extend([["-A", wlan.brname, "-i", netif1.localname,
"-o", netif2.localname, "-j", "ACCEPT"],
["-A", wlan.brname, "-o", netif1.localname,
"-i", netif2.localname, "-j", "ACCEPT"]])
"-o", netif2.localname, "-j", "ACCEPT"],
["-A", wlan.brname, "-o", netif1.localname,
"-i", netif2.localname, "-j", "ACCEPT"]])
elif wlan.policy == "ACCEPT" and not linked:
self.cmds.extend([["-A", wlan.brname, "-i", netif1.localname,
"-o", netif2.localname, "-j", "DROP"],
["-A", wlan.brname, "-o", netif1.localname,
"-i", netif2.localname, "-j", "DROP"]])
"-o", netif2.localname, "-j", "DROP"],
["-A", wlan.brname, "-o", netif1.localname,
"-i", netif2.localname, "-j", "DROP"]])
wlan._linked_lock.release()
def eberror(self, wlan, source, error):
''' Log an ebtables command error and send an exception.
'''
if not wlan:
return
wlan.exception(coreapi.CORE_EXCP_LEVEL_ERROR, wlan.brname,
"ebtables command error: %s\n%s\n" % (source, error))
# a global object because all WLANs share the same queue
# cannot have multiple threads invoking the ebtables commnd
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, verbose = False,
start = True, policy = None):
PyCoreNet.__init__(self, session, objid, name, verbose, start)
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)
if policy is not None:
self.policy = policy
self.name = name
sessionid = self.session.shortsessionid()
sessionid = self.session.short_session_id()
self.brname = "b.%s.%s" % (str(self.objid), sessionid)
self.up = False
if start:
@ -230,42 +277,50 @@ class LxBrNet(PyCoreNet):
ebq.startupdateloop(self)
def startup(self):
"""
Linux bridge starup logic.
:return: nothing
"""
try:
check_call([BRCTL_BIN, "addbr", self.brname])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, self.brname,
"Error adding bridge: %s" % e)
subprocess.check_call([constants.BRCTL_BIN, "addbr", self.brname])
except subprocess.CalledProcessError:
logger.exception("Error adding bridge")
try:
# turn off spanning tree protocol and forwarding delay
check_call([BRCTL_BIN, "stp", self.brname, "off"])
check_call([BRCTL_BIN, "setfd", self.brname, "0"])
check_call([IP_BIN, "link", "set", self.brname, "up"])
subprocess.check_call([constants.BRCTL_BIN, "stp", self.brname, "off"])
subprocess.check_call([constants.BRCTL_BIN, "setfd", self.brname, "0"])
subprocess.check_call([constants.IP_BIN, "link", "set", self.brname, "up"])
# create a new ebtables chain for this bridge
ebtablescmds(check_call, [
[EBTABLES_BIN, "-N", self.brname, "-P", self.policy],
[EBTABLES_BIN, "-A", "FORWARD",
"--logical-in", self.brname, "-j", self.brname]])
ebtablescmds(subprocess.check_call, [
[constants.EBTABLES_BIN, "-N", self.brname, "-P", self.policy],
[constants.EBTABLES_BIN, "-A", "FORWARD", "--logical-in", self.brname, "-j", self.brname]
])
# turn off multicast snooping so mcast forwarding occurs w/o IGMP joins
snoop = "/sys/devices/virtual/net/%s/bridge/multicast_snooping" % \
self.brname
snoop = "/sys/devices/virtual/net/%s/bridge/multicast_snooping" % self.brname
if os.path.exists(snoop):
open(snoop, "w").write('0')
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_WARNING, self.brname,
"Error setting bridge parameters: %s" % e)
except subprocess.CalledProcessError:
logger.exception("Error setting bridge parameters")
self.up = True
def shutdown(self):
"""
Linux bridge shutdown logic.
:return: nothing
"""
if not self.up:
return
ebq.stopupdateloop(self)
mutecall([IP_BIN, "link", "set", self.brname, "down"])
mutecall([BRCTL_BIN, "delbr", self.brname])
ebtablescmds(mutecall, [
[EBTABLES_BIN, "-D", "FORWARD",
utils.mutecall([constants.IP_BIN, "link", "set", self.brname, "down"])
utils.mutecall([constants.BRCTL_BIN, "delbr", self.brname])
ebtablescmds(utils.mutecall, [
[constants.EBTABLES_BIN, "-D", "FORWARD",
"--logical-in", self.brname, "-j", self.brname],
[EBTABLES_BIN, "-X", self.brname]])
[constants.EBTABLES_BIN, "-X", self.brname]])
for netif in self.netifs():
# removes veth pairs used for bridge-to-bridge connections
netif.shutdown()
@ -275,34 +330,52 @@ 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:
check_call([BRCTL_BIN, "addif", self.brname, netif.localname])
check_call([IP_BIN, "link", "set", netif.localname, "up"])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname,
"Error joining interface %s to bridge %s: %s" % \
(netif.localname, self.brname, e))
subprocess.check_call([constants.BRCTL_BIN, "addif", self.brname, netif.localname])
subprocess.check_call([constants.IP_BIN, "link", "set", netif.localname, "up"])
except subprocess.CalledProcessError:
logger.exception("Error joining interface %s to bridge %s", netif.localname, self.brname)
return
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:
check_call([BRCTL_BIN, "delif", self.brname, netif.localname])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname,
"Error removing interface %s from bridge %s: %s" % \
(netif.localname, self.brname, e))
subprocess.check_call([constants.BRCTL_BIN, "delif", self.brname, netif.localname])
except subprocess.CalledProcessError:
logger.exception("Error removing interface %s from bridge %s", netif.localname, self.brname)
return
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:
@ -311,14 +384,20 @@ 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
"""
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):
self._linked_lock.release()
@ -328,9 +407,14 @@ class LxBrNet(PyCoreNet):
ebq.ebchange(self)
def link(self, netif1, netif2):
''' Link two PyCoreNetIfs together, resulting in adding or removing
"""
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):
self._linked_lock.release()
@ -339,37 +423,45 @@ class LxBrNet(PyCoreNet):
self._linked_lock.release()
ebq.ebchange(self)
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.
'''
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.
: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
tc = [TC_BIN, "qdisc", "replace", "dev", devname]
tc = [constants.TC_BIN, "qdisc", "replace", "dev", devname]
parent = ["root"]
changed = False
if netif.setparam('bw', bw):
# 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:
if self.up:
if (self.verbose):
self.info("linkconfig: %s" % \
([tc + parent + ["handle", "1:"] + tbf],))
check_call(tc + parent + ["handle", "1:"] + tbf)
logger.info("linkconfig: %s" % ([tc + parent + ["handle", "1:"] + tbf],))
subprocess.check_call(tc + parent + ["handle", "1:"] + tbf)
netif.setparam('has_tbf', True)
changed = True
elif netif.getparam('has_tbf') and bw <= 0:
tcd = [] + tc
tcd[2] = "delete"
if self.up:
check_call(tcd + parent)
subprocess.check_call(tcd + parent)
netif.setparam('has_tbf', False)
# removing the parent removes the child
netif.setparam('has_netem', False)
@ -395,7 +487,7 @@ class LxBrNet(PyCoreNet):
netem += ["delay", "0us", "%sus" % jitter, "25%"]
else:
netem += ["%sus" % jitter, "25%"]
if loss is not None:
netem += ["loss", "%s%%" % min(loss, 100)]
if duplicate is not None:
@ -406,47 +498,47 @@ class LxBrNet(PyCoreNet):
return
tc[2] = "delete"
if self.up:
if self.verbose:
self.info("linkconfig: %s" % \
([tc + parent + ["handle", "10:"]],))
check_call(tc + parent + ["handle", "10:"])
logger.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"]],))
subprocess.check_call(tc + parent + ["handle", "10:"])
netif.setparam('has_netem', False)
elif len(netem) > 1:
if self.up:
if self.verbose:
self.info("linkconfig: %s" % \
([tc + parent + ["handle", "10:"] + netem],))
check_call(tc + parent + ["handle", "10:"] + netem)
logger.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"] + netem],))
subprocess.check_call(tc + parent + ["handle", "10:"] + netem)
netif.setparam('has_netem', True)
def linknet(self, net):
''' Link this bridge with another by creating a veth pair and installing
each device into each bridge.
'''
sessionid = self.session.shortsessionid()
"""
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
netif = VEth(node = None, name = name, localname = localname,
mtu = 1500, net = self, start = self.up)
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)
if net.up:
# this is similar to net.attach() but uses netif.name instead
# this is similar to net.attach() but uses netif.name instead
# of localname
check_call([BRCTL_BIN, "addif", net.brname, netif.name])
check_call([IP_BIN, "link", "set", netif.name, "up"])
subprocess.check_call([constants.BRCTL_BIN, "addif", net.brname, netif.name])
subprocess.check_call([constants.IP_BIN, "link", "set", netif.name, "up"])
i = net.newifindex()
net._netif[i] = netif
with net._linked_lock:
@ -454,38 +546,60 @@ class LxBrNet(PyCoreNet):
netif.net = self
netif.othernet = net
return netif
def getlinknetif(self, net):
''' Return the interface of that links this net with another net
"""
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.
'''
"""
Set addresses on the bridge.
:param list[str] addrlist: address list
:return: nothing
"""
if not self.up:
return
for addr in addrlist:
try:
check_call([IP_BIN, "addr", "add", str(addr), "dev", self.brname])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname,
"Error adding IP address: %s" % e)
subprocess.check_call([constants.IP_BIN, "addr", "add", str(addr), "dev", self.brname])
except subprocess.CalledProcessError:
logger.exception("Error adding IP address")
class GreTapBridge(LxBrNet):
''' A network consisting of a bridge with a gretap device for tunneling to
another system.
'''
def __init__(self, session, remoteip = None, objid = None, name = None,
policy = "ACCEPT", localip = None, ttl = 255, key = None,
verbose = False, start = True):
LxBrNet.__init__(self, session = session, objid = objid,
name = name, verbose = verbose, policy = policy,
start = False)
"""
A network consisting of a bridge with a gretap device for tunneling to
another system.
"""
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:
self.grekey = self.session.sessionid ^ self.objid
@ -497,47 +611,59 @@ 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.
'''
"""
Creates a bridge and adds the gretap device to it.
:return: nothing
"""
LxBrNet.startup(self)
if self.gretap:
self.attach(self.gretap)
def shutdown(self):
''' Detach the gretap device and remove the bridge.
'''
"""
Detach the gretap device and remove the bridge.
:return: nothing
"""
if self.gretap:
self.detach(self.gretap)
self.gretap.shutdown()
self.gretap = None
LxBrNet.shutdown(self)
def addrconfig(self, addrlist):
''' Set the remote tunnel endpoint. This is a one-time method for
creating the GreTap device, which requires the remoteip at startup.
The 1st address in the provided list is remoteip, 2nd optionally
specifies localip.
'''
"""
Set the remote tunnel endpoint. This is a one-time method for
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,
localip = localip, ttl = self.ttl, key = self.grekey)
self.gretap = GreTap(session=self.session, remoteip=remoteip, objid=None, name=None,
localip=localip, ttl=self.ttl, key=self.grekey)
self.attach(self.gretap)
def setkey(self, key):
''' Set the GRE key used for the GreTap device. This needs to be set
prior to instantiating the GreTap device (before addrconfig).
'''
"""
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,35 +1,50 @@
#
# CORE
# Copyright (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# authors: Tom Goff <thomas.goff@boeing.com>
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
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, signal, sys, subprocess, vnodeclient, threading, string, shutil
import random, time
from core.api import coreapi
from core.misc.utils import *
from core.constants import *
from core.coreobj import PyCoreObj, PyCoreNode, PyCoreNetIf, Position
from core.netns.vif import VEth, TunTap
from core.emane.nodes import EmaneNode
import os
import random
import shutil
import signal
import string
import subprocess
import threading
from core import constants
from core.coreobj import PyCoreNetIf
from core.coreobj import PyCoreNode
from core.enumerations import NodeTypes
from core.misc import log
from core.misc import nodeutils
from core.misc import utils
from core.netns import vnodeclient
from core.netns.vif import TunTap
from core.netns.vif import VEth
logger = log.get_logger(__name__)
utils.check_executables([constants.IP_BIN])
checkexec([IP_BIN])
class SimpleLxcNode(PyCoreNode):
def __init__(self, session, objid = None, name = None, nodedir = None,
verbose = False, start = True):
PyCoreNode.__init__(self, session, objid, name, verbose=verbose,
start=start)
"""
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.sessiondir, self.name))
self.ctrlchnlname = os.path.abspath(os.path.join(self.session.session_dir, self.name))
self.vnodeclient = None
self.pid = None
self.up = False
@ -37,234 +52,411 @@ 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):
''' Start a new namespace node by invoking the vnoded process that
allocates a new namespace. Bring up the loopback device and set
the hostname.
'''
"""
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"
vnoded = ["%s/vnoded" % CORE_SBIN_DIR, "-v", "-c", self.ctrlchnlname,
raise Exception("already up")
vnoded = ["%s/vnoded" % constants.CORE_SBIN_DIR, "-v", "-c", self.ctrlchnlname,
"-l", self.ctrlchnlname + ".log",
"-p", self.ctrlchnlname + ".pid"]
if self.nodedir:
vnoded += ["-C", self.nodedir]
env = self.session.getenviron(state=False)
env = self.session.get_environment(state=False)
env['NODE_NUMBER'] = str(self.objid)
env['NODE_NAME'] = str(self.name)
try:
tmp = subprocess.Popen(vnoded, stdout = subprocess.PIPE, env = env)
except OSError, e:
msg = "error running vnoded command: %s (%s)" % (vnoded, e)
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL,
"SimpleLxcNode.startup()", msg)
raise Exception, msg
tmp = subprocess.Popen(vnoded, stdout=subprocess.PIPE, env=env)
except OSError:
msg = "error running vnoded command: %s" % vnoded
logger.exception("SimpleLxcNode.startup(): %s", msg)
raise Exception(msg)
try:
self.pid = int(tmp.stdout.read())
tmp.stdout.close()
except Exception:
except ValueError:
msg = "vnoded failed to create a namespace; "
msg += "check kernel support and user priveleges"
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL,
"SimpleLxcNode.startup()", msg)
logger.exception("SimpleLxcNode.startup(): %s", msg)
if tmp.wait():
raise Exception, ("command failed: %s" % vnoded)
self.vnodeclient = vnodeclient.VnodeClient(self.name,
self.ctrlchnlname)
self.info("bringing up loopback interface")
self.cmd([IP_BIN, "link", "set", "lo", "up"])
self.info("setting hostname: %s" % self.name)
raise Exception("command failed: %s" % vnoded)
self.vnodeclient = vnodeclient.VnodeClient(self.name, self.ctrlchnlname)
logger.info("bringing up loopback interface")
self.cmd([constants.IP_BIN, "link", "set", "lo", "up"])
logger.info("setting hostname: %s" % self.name)
self.cmd(["hostname", self.name])
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
# unmount all targets
while self._mounts:
source, target = self._mounts.pop(-1)
self.umount(target)
# shutdown all interfaces
for netif in self.netifs():
netif.shutdown()
# attempt to kill node process and wait for termination of children
try:
os.kill(self.pid, signal.SIGTERM)
os.waitpid(self.pid, 0)
except OSError:
pass
logger.exception("error killing process")
# remove node directory if present
try:
os.unlink(self.ctrlchnlname)
if os.path.exists(self.ctrlchnlname):
os.unlink(self.ctrlchnlname)
except OSError:
pass
logger.exception("error removing file")
# clear interface data, close client, and mark self and not up
self._netif.clear()
self.vnodeclient.close()
self.up = False
def cmd(self, args, wait = True):
# 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):
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"):
return self.vnodeclient.term(sh = sh)
def term(self, sh="/bin/sh"):
"""
Wrapper around vnodeclient term.
def termcmdstring(self, sh = "/bin/sh"):
return self.vnodeclient.termcmdstring(sh = sh)
:param sh: shell to create terminal for
:return:
"""
return self.vnodeclient.term(sh=sh)
def shcmd(self, cmdstr, sh = "/bin/sh"):
return self.vnodeclient.shcmd(cmdstr, 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)
self.info("mounting %s at %s" % (source, target))
logger.info("mounting %s at %s" % (source, target))
try:
shcmd = "mkdir -p '%s' && %s -n --bind '%s' '%s'" % \
(target, 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:
self.warn("mounting failed for %s at %s" % (source, target))
except IOError:
logger.exception("mounting failed for %s at %s", source, target)
def umount(self, target):
self.info("unmounting '%s'" % target)
"""
Unmount a target directory.
:param str target: target directory to unmount
:return: nothing
"""
logger.info("unmounting '%s'" % target)
try:
self.cmd([UMOUNT_BIN, "-n", "-l", target])
except:
self.warn("unmounting failed for %s" % target)
self.cmd([constants.UMOUNT_BIN, "-n", "-l", target])
except IOError:
logger.exception("unmounting failed for %s" % target)
def newifindex(self):
with self.lock:
return PyCoreNode.newifindex(self)
"""
Retrieve a new interface index.
def newveth(self, ifindex = None, ifname = None, net = None):
:return: new interface index
:rtype: int
"""
with self.lock:
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:
ifindex = self.newifindex()
if ifname is None:
ifname = "eth%d" % ifindex
sessionid = self.session.shortsessionid()
sessionid = self.session.short_session_id()
try:
suffix = '%x.%s.%s' % (self.objid, ifindex, sessionid)
except TypeError:
suffix = '%s.%s.%s' % (self.objid, ifindex, sessionid)
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
ifclass = VEth
veth = ifclass(node = self, name = name, localname = localname,
mtu = 1500, net = net, start = self.up)
veth = VEth(node=self, name=name, localname=localname, mtu=1500, net=net, start=self.up)
if self.up:
check_call([IP_BIN, "link", "set", veth.name,
"netns", str(self.pid)])
self.cmd([IP_BIN, "link", "set", veth.name, "name", ifname])
subprocess.check_call([constants.IP_BIN, "link", "set", veth.name, "netns", str(self.pid)])
self.cmd([constants.IP_BIN, "link", "set", veth.name, "name", ifname])
veth.name = ifname
# retrieve interface information
result, output = self.cmdresult(["ip", "link", "show", veth.name])
logger.info("interface command output: %s", output)
output = output.split("\n")
veth.flow_id = int(output[0].strip().split(":")[0]) + 1
logger.info("interface flow index: %s - %s", veth.name, veth.flow_id)
veth.hwaddr = output[1].strip().split()[1]
logger.info("interface mac: %s - %s", veth.name, veth.hwaddr)
try:
self.addnetif(veth, ifindex)
except:
veth.shutdown()
del veth
raise
return ifindex
finally:
self.lock.release()
def newtuntap(self, ifindex = None, ifname = None, net = None):
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:
ifindex = self.newifindex()
if ifname is None:
ifname = "eth%d" % ifindex
sessionid = self.session.shortsessionid()
sessionid = self.session.short_session_id()
localname = "tap%s.%s.%s" % (self.objid, ifindex, sessionid)
name = ifname
ifclass = TunTap
tuntap = ifclass(node = self, name = name, localname = localname,
mtu = 1500, net = net, start = self.up)
tuntap = ifclass(node=self, name=name, localname=localname,
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 core.misc.ipaddress.MacAddress addr: hardware address to set
:return: mothing
"""
self._netif[ifindex].sethwaddr(addr)
if self.up:
(status, result) = self.cmdresult([IP_BIN, "link", "set", "dev",
self.ifname(ifindex), "address", str(addr)])
(status, result) = self.cmdresult([constants.IP_BIN, "link", "set", "dev",
self.ifname(ifindex), "address", str(addr)])
if status:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR,
"SimpleLxcNode.sethwaddr()",
"error setting MAC address %s" % str(addr))
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:
if ":" in str(addr): # check if addr is ipv6
self.cmd([IP_BIN, "addr", "add", str(addr),
self.cmd([constants.IP_BIN, "addr", "add", str(addr),
"dev", self.ifname(ifindex)])
else:
self.cmd([IP_BIN, "addr", "add", str(addr), "broadcast", "+",
self.cmd([constants.IP_BIN, "addr", "add", str(addr), "broadcast", "+",
"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:
self.warn("trying to delete unknown address: %s" % addr)
if self.up:
self.cmd([IP_BIN, "addr", "del", str(addr),
"dev", self.ifname(ifindex)])
logger.exception("trying to delete unknown address: %s" % addr)
valid_deladdrtype = ("inet", "inet6", "inet6link")
def delalladdr(self, ifindex, addrtypes = valid_deladdrtype):
addr = self.getaddr(self.ifname(ifindex), rescan = True)
if self.up:
self.cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)])
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:
raise ValueError, "addr type must be in: " + \
" ".join(self.valid_deladdrtype)
raise ValueError("addr type must be in: " + " ".join(self.valid_deladdrtype))
for a in addr[t]:
self.deladdr(ifindex, a)
# update cached information
self.getaddr(self.ifname(ifindex), rescan = True)
self.getaddr(self.ifname(ifindex), rescan=True)
def ifup(self, ifindex):
if self.up:
self.cmd([IP_BIN, "link", "set", self.ifname(ifindex), "up"])
"""
Bring an interface up.
def newnetif(self, net = None, addrlist = [], hwaddr = None,
ifindex = None, ifname = None):
: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=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 core.misc.ipaddress.MacAddress 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:
if isinstance(net, EmaneNode):
ifindex = self.newtuntap(ifindex = ifindex, ifname = ifname,
net = net)
# 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)
# 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;
@ -272,143 +464,235 @@ class SimpleLxcNode(PyCoreNode):
self.attachnet(ifindex, net)
netif = self.netif(ifindex)
netif.sethwaddr(hwaddr)
for addr in maketuple(addrlist):
for addr in utils.maketuple(addrlist):
netif.addaddr(addr)
return ifindex
else:
ifindex = self.newveth(ifindex = ifindex, ifname = ifname,
net = net)
ifindex = self.newveth(ifindex=ifindex, ifname=ifname, net=net)
if net is not None:
self.attachnet(ifindex, net)
if hwaddr:
self.sethwaddr(ifindex, hwaddr)
for addr in maketuple(addrlist):
self.addaddr(ifindex, addr)
if addrlist:
for addr in utils.maketuple(addrlist):
self.addaddr(ifindex, addr)
self.ifup(ifindex)
return ifindex
finally:
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)])
tmp2 = "tmp." + "".join([random.choice(string.ascii_lowercase)
for x in xrange(tmplen)])
check_call([IP_BIN, "link", "add", "name", tmp1,
"type", "veth", "peer", "name", tmp2])
subprocess.check_call([constants.IP_BIN, "link", "add", "name", tmp1,
"type", "veth", "peer", "name", tmp2])
check_call([IP_BIN, "link", "set", tmp1, "netns", str(self.pid)])
self.cmd([IP_BIN, "link", "set", tmp1, "name", ifname])
subprocess.call([constants.IP_BIN, "link", "set", tmp1, "netns", str(self.pid)])
self.cmd([constants.IP_BIN, "link", "set", tmp1, "name", ifname])
self.addnetif(PyCoreNetIf(self, ifname), self.newifindex())
check_call([IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid)])
othernode.cmd([IP_BIN, "link", "set", tmp2, "name", otherifname])
subprocess.check_call([constants.IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid)])
othernode.cmd([constants.IP_BIN, "link", "set", tmp2, "name", otherifname])
othernode.addnetif(PyCoreNetIf(othernode, otherifname),
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):
return self.vnodeclient.getaddr(ifname = ifname, rescan = rescan)
def getaddr(self, ifname, rescan=False):
"""
Wrapper around vnodeclient getaddr.
def netifstats(self, ifname = None):
return self.vnodeclient.netifstats(ifname = ifname)
: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):
def __init__(self, session, objid = None, name = None,
nodedir = None, bootsh = "boot.sh", verbose = False,
start = True):
super(LxcNode, self).__init__(session = session, objid = objid,
name = name, nodedir = nodedir,
verbose = verbose, start = start)
"""
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
if start:
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()
super(LxcNode, self).startup()
self.privatedir("/var/run")
self.privatedir("/var/log")
except OSError, e:
self.warn("Error with LxcNode.startup(): %s" % e)
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR,
"LxcNode.startup()", "%s" % e)
except OSError:
logger.exception("error during LxcNode.startup()")
finally:
self.lock.release()
def shutdown(self):
"""
Shutdown logic for the node.
:return: nothing
"""
if not self.up:
return
self.lock.acquire()
# services are instead stopped when session enters datacollect state
#self.session.services.stopnodeservices(self)
# 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: " + path
hostpath = os.path.join(self.nodedir,
os.path.normpath(path).strip('/').replace('/', '.'))
raise ValueError("path not fully qualified: %s" % path)
hostpath = os.path.join(self.nodedir, os.path.normpath(path).strip('/').replace('/', '.'))
try:
os.mkdir(hostpath)
except OSError:
pass
except Exception, e:
raise Exception, e
logger.exception("error creating directory: %s", hostpath)
self.mount(hostpath, path)
def hostfilename(self, filename):
''' Return the name of a node's file on the host filesystem.
'''
"""
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("/", ".")
dirname = os.path.join(self.nodedir, dirname)
return os.path.join(dirname, basename)
def opennodefile(self, filename, mode = "w"):
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):
os.makedirs(dirname, mode = 0755)
os.makedirs(dirname, mode=0755)
return open(hostfilename, mode)
def nodefile(self, filename, contents, mode = 0644):
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)
f.close()
self.info("created nodefile: '%s'; mode: 0%o" % (f.name, mode))
def nodefilecopy(self, filename, srcfilename, mode = None):
''' Copy a file to a node, following symlinks and preserving metadata.
logger.info("created nodefile: '%s'; mode: 0%o" % (f.name, mode))
def nodefilecopy(self, filename, srcfilename, mode=None):
"""
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)
if mode is not None:
os.chmod(hostfilename, mode)
self.info("copied nodefile: '%s'; mode: %s" % (hostfilename, mode))
logger.info("copied nodefile: '%s'; mode: %s" % (hostfilename, mode))

View file

@ -1,19 +1,17 @@
#
# CORE
# Copyright (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Tom Goff <thomas.goff@boeing.com>
#
'''
"""
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 *
import os
import stat
from core import constants
from core.misc import log
logger = log.get_logger(__name__)
USE_VCMD_MODULE = True
@ -22,10 +20,21 @@ if USE_VCMD_MODULE:
else:
import subprocess
VCMD = os.path.join(CORE_SBIN_DIR, "vcmd")
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,25 +43,39 @@ class VnodeClient(object):
self.cmdchnl = None
self._addr = {}
def warn(self, msg):
print >> sys.stderr, "%s: %s" % (self.name, msg)
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).
'''
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():
raise ValueError, "self.cmdchnl not connected"
raise ValueError("self.cmdchnl not connected")
tmp = self.cmdchnl.qcmd(args)
if not wait:
return tmp
@ -62,19 +85,25 @@ class VnodeClient(object):
mode = os.P_WAIT
else:
mode = os.P_NOWAIT
tmp = os.spawnlp(mode, VCMD, VCMD, "-c",
self.ctrlchnlname, "-q", "--", *args)
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)))
logger.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.
'''
"""
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()
result += cmderr.read()
@ -82,43 +111,69 @@ class VnodeClient(object):
cmdout.close()
cmderr.close()
status = cmdid.wait()
return (status, result)
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"
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)
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)
"""
Execute an icmd against a node.
def redircmd(self, infd, outfd, errfd, args, wait = True):
'''
: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
if not self.cmdchnl.connected():
raise ValueError, "self.cmdchnl not 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)))
logger.warn("cmd exited with status %s: %s" % (tmp, str(args)))
return tmp
def term(self, sh = "/bin/sh"):
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:
@ -127,19 +182,42 @@ class VnodeClient(object):
os.environ["SUDO_USER"])
return os.spawnvp(os.P_NOWAIT, cmd[0], cmd)
def termcmdstring(self, sh = "/bin/sh"):
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"):
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):
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": []}
cmd = [IP_BIN, "addr", "show", "dev", ifname]
cmd = [constants.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":
@ -152,21 +230,27 @@ class VnodeClient(object):
elif line[3] == "link":
tmp["inet6link"].append(line[1])
else:
self.warn("unknown scope: %s" % line[3])
else:
pass
logger.warn("unknown scope: %s" % line[3])
err = cmderr.read()
cmdout.close()
cmderr.close()
status = cmdid.wait()
if status:
self.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd))
logger.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd))
if err:
self.warn("error output: %s" % err)
logger.warn("error output: %s" % err)
self._addr[ifname] = tmp
return tmp
def netifstats(self, ifname = None):
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)
@ -195,34 +279,47 @@ class VnodeClient(object):
cmderr.close()
status = cmdid.wait()
if status:
self.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd))
logger.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd))
if err:
self.warn("error output: %s" % err)
logger.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))
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:
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
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))
: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)
nodedirs = filter(lambda x: os.path.exists(os.path.join(x, "server")),
nodedirs)
nodedirs = filter(lambda x: os.path.exists(os.path.join(x, "server")), nodedirs)
if filterfunc:
nodedirs = filter(filterfunc, nodedirs)
nodedirs.sort()