quick pass to try and provide documentation within core.netns
This commit is contained in:
parent
4ae7958a63
commit
2b866e1b3f
5 changed files with 824 additions and 78 deletions
|
@ -1,6 +1,6 @@
|
|||
"""
|
||||
nodes.py: definition of an LxcNode and CoreNode classes, and other node classes
|
||||
that inherit from the CoreNode, implementing specific node types.
|
||||
Definition of LxcNode, CoreNode, and other node classes that inherit from the CoreNode,
|
||||
implementing specific node types.
|
||||
"""
|
||||
|
||||
import socket
|
||||
|
@ -28,6 +28,9 @@ logger = log.get_logger(__name__)
|
|||
|
||||
|
||||
class CtrlNet(LxBrNet):
|
||||
"""
|
||||
Control network functionality.
|
||||
"""
|
||||
policy = "ACCEPT"
|
||||
# base control interface index
|
||||
CTRLIF_IDX_BASE = 99
|
||||
|
@ -41,6 +44,20 @@ class CtrlNet(LxBrNet):
|
|||
def __init__(self, session, objid="ctrlnet", name=None, prefix=None,
|
||||
hostid=None, start=True, assign_address=True,
|
||||
updown_script=None, serverintf=None):
|
||||
"""
|
||||
Creates a CtrlNet instance.
|
||||
|
||||
:param core.session.Session session: core session instance
|
||||
:param int objid: node id
|
||||
:param str name: node namee
|
||||
:param prefix: control network ipv4 prefix
|
||||
:param hostid: host id
|
||||
:param bool start: start flag
|
||||
:param str assign_address: assigned address
|
||||
:param str updown_script: updown script
|
||||
:param serverintf: server interface
|
||||
:return:
|
||||
"""
|
||||
self.prefix = ipaddress.Ipv4Prefix(prefix)
|
||||
self.hostid = hostid
|
||||
self.assign_address = assign_address
|
||||
|
@ -49,6 +66,11 @@ class CtrlNet(LxBrNet):
|
|||
LxBrNet.__init__(self, session, objid=objid, name=name, start=start)
|
||||
|
||||
def startup(self):
|
||||
"""
|
||||
Startup functionality for the control network.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if self.detectoldbridge():
|
||||
return
|
||||
|
||||
|
@ -80,6 +102,9 @@ class CtrlNet(LxBrNet):
|
|||
"""
|
||||
Occassionally, control net bridges from previously closed sessions are not cleaned up.
|
||||
Check if there are old control net bridges and delete them
|
||||
|
||||
:return: True if an old bridge was detected, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
retstat, retstr = utils.cmdresult([constants.BRCTL_BIN, 'show'])
|
||||
if retstat != 0:
|
||||
|
@ -110,6 +135,11 @@ class CtrlNet(LxBrNet):
|
|||
return False
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Control network shutdown.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if self.serverintf is not None:
|
||||
try:
|
||||
subprocess.check_call([constants.BRCTL_BIN, "delif", self.brname, self.serverintf])
|
||||
|
@ -125,27 +155,42 @@ class CtrlNet(LxBrNet):
|
|||
def all_link_data(self, flags):
|
||||
"""
|
||||
Do not include CtrlNet in link messages describing this session.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
class CoreNode(LxcNode):
|
||||
"""
|
||||
Basic core node class for nodes to extend.
|
||||
"""
|
||||
apitype = NodeTypes.DEFAULT.value
|
||||
|
||||
|
||||
class PtpNet(LxBrNet):
|
||||
"""
|
||||
Peer to peer network node.
|
||||
"""
|
||||
policy = "ACCEPT"
|
||||
|
||||
def attach(self, netif):
|
||||
if len(self._netif) > 1:
|
||||
raise ValueError, \
|
||||
"Point-to-point links support at most 2 network interfaces"
|
||||
"""
|
||||
Attach a network interface, but limit attachment to two interfaces.
|
||||
|
||||
:param core.coreobj.PyCoreNetIf netif: network interface
|
||||
:return: nothing
|
||||
"""
|
||||
if len(self._netif) >= 2:
|
||||
raise ValueError("Point-to-point links support at most 2 network interfaces")
|
||||
LxBrNet.attach(self, netif)
|
||||
|
||||
def data(self, message_type):
|
||||
"""
|
||||
Do not generate a Node Message for point-to-point links. They are
|
||||
built using a link message instead.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -153,6 +198,9 @@ class PtpNet(LxBrNet):
|
|||
"""
|
||||
Build CORE API TLVs for a point-to-point link. One Link message
|
||||
describes this network.
|
||||
|
||||
:return: all link data
|
||||
:rtype: list[LinkData]
|
||||
"""
|
||||
|
||||
all_links = []
|
||||
|
@ -253,20 +301,31 @@ class PtpNet(LxBrNet):
|
|||
|
||||
|
||||
class SwitchNode(LxBrNet):
|
||||
"""
|
||||
Provides switch functionality within a core node.
|
||||
"""
|
||||
apitype = NodeTypes.SWITCH.value
|
||||
policy = "ACCEPT"
|
||||
type = "lanswitch"
|
||||
|
||||
|
||||
class HubNode(LxBrNet):
|
||||
"""
|
||||
Provides hub functionality within a core node, forwards packets to all bridge
|
||||
ports by turning off MAC address learning.
|
||||
"""
|
||||
apitype = NodeTypes.HUB.value
|
||||
policy = "ACCEPT"
|
||||
type = "hub"
|
||||
|
||||
def __init__(self, session, objid=None, name=None, start=True):
|
||||
"""
|
||||
the Hub node forwards packets to all bridge ports by turning off
|
||||
the MAC address learning
|
||||
Creates a HubNode instance.
|
||||
|
||||
:param core.session.Session session: core session instance
|
||||
:param int objid: node id
|
||||
:param str name: node namee
|
||||
:param bool start: start flag
|
||||
"""
|
||||
LxBrNet.__init__(self, session, objid, name, start)
|
||||
if start:
|
||||
|
@ -274,12 +333,24 @@ class HubNode(LxBrNet):
|
|||
|
||||
|
||||
class WlanNode(LxBrNet):
|
||||
"""
|
||||
Provides wireless lan functionality within a core node.
|
||||
"""
|
||||
apitype = NodeTypes.WIRELESS_LAN.value
|
||||
linktype = LinkTypes.WIRELESS.value
|
||||
policy = "DROP"
|
||||
type = "wlan"
|
||||
|
||||
def __init__(self, session, objid=None, name=None, start=True, policy=None):
|
||||
"""
|
||||
Create a WlanNode instance.
|
||||
|
||||
:param core.session.Session session: core session instance
|
||||
:param int objid: node id
|
||||
:param str name: node name
|
||||
:param bool start: start flag
|
||||
:param policy: wlan policy
|
||||
"""
|
||||
LxBrNet.__init__(self, session, objid, name, start, policy)
|
||||
# wireless model such as basic range
|
||||
self.model = None
|
||||
|
@ -287,12 +358,18 @@ class WlanNode(LxBrNet):
|
|||
self.mobility = None
|
||||
|
||||
def attach(self, netif):
|
||||
"""
|
||||
Attach a network interface.
|
||||
|
||||
:param core.coreobj.PyCoreNetIf netif: network interface
|
||||
:return: nothing
|
||||
"""
|
||||
LxBrNet.attach(self, netif)
|
||||
if self.model:
|
||||
netif.poshook = self.model.position_callback
|
||||
if netif.node is None:
|
||||
return
|
||||
(x, y, z) = netif.node.position.get()
|
||||
x, y, z = netif.node.position.get()
|
||||
# invokes any netif.poshook
|
||||
netif.setposition(x, y, z)
|
||||
# self.model.setlinkparams()
|
||||
|
@ -302,8 +379,8 @@ class WlanNode(LxBrNet):
|
|||
Sets the mobility and wireless model.
|
||||
|
||||
:param core.mobility.WirelessModel.cls model: wireless model to set to
|
||||
:param config:
|
||||
:return:
|
||||
:param config: model configuration
|
||||
:return: nothing
|
||||
"""
|
||||
logger.info("adding model %s" % model.name)
|
||||
if model.config_type == RegisterTlvs.WIRELESS.value:
|
||||
|
@ -321,6 +398,10 @@ class WlanNode(LxBrNet):
|
|||
def updatemodel(self, model_name, values):
|
||||
"""
|
||||
Allow for model updates during runtime (similar to setmodel().)
|
||||
|
||||
:param model_name: model name to update
|
||||
:param values: values to update model with
|
||||
:return: nothing
|
||||
"""
|
||||
logger.info("updating model %s" % model_name)
|
||||
if self.model is None or self.model.name != model_name:
|
||||
|
@ -338,6 +419,13 @@ class WlanNode(LxBrNet):
|
|||
self.model.setlinkparams()
|
||||
|
||||
def all_link_data(self, flags):
|
||||
"""
|
||||
Retrieve all link data.
|
||||
|
||||
:param flags: link flags
|
||||
:return: all link data
|
||||
:rtype: list[LinkData]
|
||||
"""
|
||||
all_links = LxBrNet.all_link_data(self, flags)
|
||||
|
||||
if self.model:
|
||||
|
@ -355,6 +443,16 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
|
|||
type = "rj45"
|
||||
|
||||
def __init__(self, session, objid=None, name=None, mtu=1500, start=True):
|
||||
"""
|
||||
Create an RJ45Node instance.
|
||||
|
||||
:param core.session.Session session: core session instance
|
||||
:param int objid: node id
|
||||
:param str name: node name
|
||||
:param mtu: rj45 mtu
|
||||
:param bool start: start flag
|
||||
:return:
|
||||
"""
|
||||
PyCoreNode.__init__(self, session, objid, name, start=start)
|
||||
# this initializes net, params, poshook
|
||||
PyCoreNetIf.__init__(self, node=self, name=name, mtu=mtu)
|
||||
|
@ -370,20 +468,24 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
|
|||
def startup(self):
|
||||
"""
|
||||
Set the interface in the up state.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
# interface will also be marked up during net.attach()
|
||||
self.savestate()
|
||||
|
||||
try:
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
|
||||
self.up = True
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("failed to run command: %s link set %s up", constants.IP_BIN, self.localname)
|
||||
return
|
||||
self.up = True
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Bring the interface down. Remove any addresses and queuing
|
||||
disciplines.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if not self.up:
|
||||
return
|
||||
|
@ -393,43 +495,72 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
|
|||
self.up = False
|
||||
self.restorestate()
|
||||
|
||||
# TODO: issue in that both classes inherited from provide the same method with different signatures
|
||||
def attachnet(self, net):
|
||||
"""
|
||||
Attach a network.
|
||||
|
||||
:param core.coreobj.PyCoreNet net: network to attach
|
||||
:return: nothing
|
||||
"""
|
||||
PyCoreNetIf.attachnet(self, net)
|
||||
|
||||
def detachnet(self):
|
||||
"""
|
||||
Detach a network.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
PyCoreNetIf.detachnet(self)
|
||||
|
||||
def newnetif(self, net=None, addrlist=[], hwaddr=None,
|
||||
ifindex=None, ifname=None):
|
||||
# TODO: parameters are not used
|
||||
def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None):
|
||||
"""
|
||||
This is called when linking with another node. Since this node
|
||||
represents an interface, we do not create another object here,
|
||||
but attach ourselves to the given network.
|
||||
|
||||
:param core.coreobj.PyCoreNet net: new network instance
|
||||
:param list[str] addrlist: address list
|
||||
:param str hwaddr: hardware address
|
||||
:param int ifindex: interface index
|
||||
:param str ifname: interface name
|
||||
:return:
|
||||
"""
|
||||
self.lock.acquire()
|
||||
try:
|
||||
with self.lock:
|
||||
if ifindex is None:
|
||||
ifindex = 0
|
||||
|
||||
if self.net is not None:
|
||||
raise ValueError, \
|
||||
"RJ45 nodes support at most 1 network interface"
|
||||
raise ValueError("RJ45 nodes support at most 1 network interface")
|
||||
|
||||
self._netif[ifindex] = self
|
||||
# PyCoreNetIf.node is self
|
||||
self.node = self
|
||||
self.ifindex = ifindex
|
||||
|
||||
if net is not None:
|
||||
self.attachnet(net)
|
||||
for addr in utils.maketuple(addrlist):
|
||||
self.addaddr(addr)
|
||||
|
||||
if addrlist:
|
||||
for addr in utils.maketuple(addrlist):
|
||||
self.addaddr(addr)
|
||||
|
||||
return ifindex
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def delnetif(self, ifindex):
|
||||
"""
|
||||
Delete a network interface.
|
||||
|
||||
:param int ifindex: interface index to delete
|
||||
:return: nothing
|
||||
"""
|
||||
if ifindex is None:
|
||||
ifindex = 0
|
||||
|
||||
if ifindex not in self._netif:
|
||||
raise ValueError, "ifindex %s does not exist" % ifindex
|
||||
|
||||
self._netif.pop(ifindex)
|
||||
if ifindex == self.ifindex:
|
||||
self.shutdown()
|
||||
|
@ -441,26 +572,54 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
|
|||
This object is considered the network interface, so we only
|
||||
return self here. This keeps the RJ45Node compatible with
|
||||
real nodes.
|
||||
|
||||
:param int ifindex: interface index to retrieve
|
||||
:param net: network to retrieve
|
||||
:return: a network interface
|
||||
:rtype: core.coreobj.PyCoreNetIf
|
||||
"""
|
||||
if net is not None and net == self.net:
|
||||
return self
|
||||
|
||||
if ifindex is None:
|
||||
ifindex = 0
|
||||
|
||||
if ifindex == self.ifindex:
|
||||
return self
|
||||
|
||||
return None
|
||||
|
||||
def getifindex(self, netif):
|
||||
"""
|
||||
Retrieve network interface index.
|
||||
|
||||
:param core.coreobj.PyCoreNetIf netif: network interface to retrieve index for
|
||||
:return: interface index, None otherwise
|
||||
:rtype: int
|
||||
"""
|
||||
if netif != self:
|
||||
return None
|
||||
|
||||
return self.ifindex
|
||||
|
||||
def addaddr(self, addr):
|
||||
"""
|
||||
Add address to to network interface.
|
||||
|
||||
:param str addr: address to add
|
||||
:return: nothing
|
||||
"""
|
||||
if self.up:
|
||||
subprocess.check_call([constants.IP_BIN, "addr", "add", str(addr), "dev", self.name])
|
||||
PyCoreNetIf.addaddr(self, addr)
|
||||
|
||||
def deladdr(self, addr):
|
||||
"""
|
||||
Delete address from network interface.
|
||||
|
||||
:param str addr: address to delete
|
||||
:return: nothing
|
||||
"""
|
||||
if self.up:
|
||||
subprocess.check_call([constants.IP_BIN, "addr", "del", str(addr), "dev", self.name])
|
||||
PyCoreNetIf.deladdr(self, addr)
|
||||
|
@ -469,6 +628,8 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
|
|||
"""
|
||||
Save the addresses and other interface state before using the
|
||||
interface for emulation purposes. TODO: save/restore the PROMISC flag
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.old_up = False
|
||||
self.old_addrs = []
|
||||
|
@ -500,6 +661,8 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
|
|||
def restorestate(self):
|
||||
"""
|
||||
Restore the addresses and other interface state after using it.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
for addr in self.old_addrs:
|
||||
if addr[1] is None:
|
||||
|
@ -512,6 +675,8 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
|
|||
def setposition(self, x=None, y=None, z=None):
|
||||
"""
|
||||
Use setposition() from both parent classes.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
PyCoreObj.setposition(self, x, y, z)
|
||||
# invoke any poshook
|
||||
|
@ -519,6 +684,9 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
|
|||
|
||||
|
||||
class TunnelNode(GreTapBridge):
|
||||
"""
|
||||
Provides tunnel functionality in a core node.
|
||||
"""
|
||||
apitype = NodeTypes.TUNNEL.value
|
||||
policy = "ACCEPT"
|
||||
type = "tunnel"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
"""
|
||||
PyCoreNetIf classes that implement the interfaces available
|
||||
under Linux.
|
||||
virtual ethernet classes that implement the interfaces available under Linux.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
|
@ -19,7 +18,23 @@ utils.check_executables([constants.IP_BIN])
|
|||
|
||||
|
||||
class VEth(PyCoreNetIf):
|
||||
"""
|
||||
Provides virtual ethernet functionality for core nodes.
|
||||
"""
|
||||
|
||||
# TODO: network is not used, why was it needed?
|
||||
def __init__(self, node, name, localname, mtu=1500, net=None, start=True):
|
||||
"""
|
||||
Creates a VEth instance.
|
||||
|
||||
:param core.netns.nodes.CoreNode node: related core node
|
||||
:param str name: interface name
|
||||
:param str localname: interface local name
|
||||
:param mtu: interface mtu
|
||||
:param net: network
|
||||
:param bool start: start flag
|
||||
:return:
|
||||
"""
|
||||
# note that net arg is ignored
|
||||
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
|
||||
self.localname = localname
|
||||
|
@ -28,12 +43,22 @@ class VEth(PyCoreNetIf):
|
|||
self.startup()
|
||||
|
||||
def startup(self):
|
||||
"""
|
||||
Interface startup logic.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
subprocess.check_call([constants.IP_BIN, "link", "add", "name", self.localname,
|
||||
"type", "veth", "peer", "name", self.name])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
|
||||
self.up = True
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Interface shutdown logic.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if not self.up:
|
||||
return
|
||||
if self.node:
|
||||
|
@ -48,7 +73,18 @@ class TunTap(PyCoreNetIf):
|
|||
TUN/TAP virtual device in TAP mode
|
||||
"""
|
||||
|
||||
# TODO: network is not used, why was it needed?
|
||||
def __init__(self, node, name, localname, mtu=1500, net=None, start=True):
|
||||
"""
|
||||
Create a TunTap instance.
|
||||
|
||||
:param core.netns.nodes.CoreNode node: related core node
|
||||
:param str name: interface name
|
||||
:param str localname: local interface name
|
||||
:param mtu: interface mtu
|
||||
:param net: related network
|
||||
:param bool start: start flag
|
||||
"""
|
||||
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
|
||||
self.localname = localname
|
||||
self.up = False
|
||||
|
@ -57,6 +93,11 @@ class TunTap(PyCoreNetIf):
|
|||
self.startup()
|
||||
|
||||
def startup(self):
|
||||
"""
|
||||
Startup logic for a tunnel tap.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
# TODO: more sophisticated TAP creation here
|
||||
# Debian does not support -p (tap) option, RedHat does.
|
||||
# For now, this is disabled to allow the TAP to be created by another
|
||||
|
@ -66,6 +107,11 @@ class TunTap(PyCoreNetIf):
|
|||
self.up = True
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Shutdown functionality for a tunnel tap.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if not self.up:
|
||||
return
|
||||
self.node.cmd([constants.IP_BIN, "-6", "addr", "flush", "dev", self.name])
|
||||
|
@ -75,7 +121,12 @@ class TunTap(PyCoreNetIf):
|
|||
|
||||
def waitfor(self, func, attempts=10, maxretrydelay=0.25):
|
||||
"""
|
||||
Wait for func() to return zero with exponential backoff
|
||||
Wait for func() to return zero with exponential backoff.
|
||||
|
||||
:param func: function to wait for a result of zero
|
||||
:param int attempts: number of attempts to wait for a zero result
|
||||
:param float maxretrydelay: maximum retry delay
|
||||
:return: nothing
|
||||
"""
|
||||
delay = 0.01
|
||||
for i in xrange(1, attempts + 1):
|
||||
|
@ -100,6 +151,9 @@ class TunTap(PyCoreNetIf):
|
|||
"""
|
||||
Check for presence of a local device - tap device may not
|
||||
appear right away waits
|
||||
|
||||
:return: wait for device local response
|
||||
:rtype: int
|
||||
"""
|
||||
|
||||
def localdevexists():
|
||||
|
@ -110,8 +164,9 @@ class TunTap(PyCoreNetIf):
|
|||
|
||||
def waitfordevicenode(self):
|
||||
"""
|
||||
Check for presence of a node device - tap device may not
|
||||
appear right away waits
|
||||
Check for presence of a node device - tap device may not appear right away waits.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
|
||||
def nodedevexists():
|
||||
|
@ -139,9 +194,12 @@ class TunTap(PyCoreNetIf):
|
|||
startup() method but called at a later time when a userspace
|
||||
program (running on the host) has had a chance to open the socket
|
||||
end of the TAP.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.waitfordevicelocal()
|
||||
netns = str(self.node.pid)
|
||||
|
||||
try:
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "netns", netns])
|
||||
except subprocess.CalledProcessError:
|
||||
|
@ -149,12 +207,15 @@ class TunTap(PyCoreNetIf):
|
|||
msg += "ip link set %s netns %s" % (self.localname, netns)
|
||||
logger.exception(msg)
|
||||
return
|
||||
|
||||
self.node.cmd([constants.IP_BIN, "link", "set", self.localname, "name", self.name])
|
||||
self.node.cmd([constants.IP_BIN, "link", "set", self.name, "up"])
|
||||
|
||||
def setaddrs(self):
|
||||
"""
|
||||
Set interface addresses based on self.addrlist.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.waitfordevicenode()
|
||||
for addr in self.addrlist:
|
||||
|
@ -171,6 +232,20 @@ class GreTap(PyCoreNetIf):
|
|||
def __init__(self, node=None, name=None, session=None, mtu=1458,
|
||||
remoteip=None, objid=None, localip=None, ttl=255,
|
||||
key=None, start=True):
|
||||
"""
|
||||
Creates a GreTap instance.
|
||||
|
||||
:param core.netns.nodes.CoreNode node: related core node
|
||||
:param str name: interface name
|
||||
:param core.session.Session session: core session instance
|
||||
:param mtu: interface mtu
|
||||
:param str remoteip: remote address
|
||||
:param int objid: object id
|
||||
:param str localip: local address
|
||||
:param ttl: ttl value
|
||||
:param key: gre tap key
|
||||
:param bool start: start flag
|
||||
"""
|
||||
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
|
||||
self.session = session
|
||||
if objid is None:
|
||||
|
@ -201,6 +276,11 @@ class GreTap(PyCoreNetIf):
|
|||
self.up = True
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Shutdown logic for a GreTap.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if self.localname:
|
||||
cmd = ("ip", "link", "set", self.localname, "down")
|
||||
subprocess.check_call(cmd)
|
||||
|
@ -209,7 +289,20 @@ class GreTap(PyCoreNetIf):
|
|||
self.localname = None
|
||||
|
||||
def data(self, message_type):
|
||||
"""
|
||||
Data for a gre tap.
|
||||
|
||||
:param message_type: message type for data
|
||||
:return: None
|
||||
"""
|
||||
return None
|
||||
|
||||
def all_link_data(self, flags):
|
||||
"""
|
||||
Retrieve link data.
|
||||
|
||||
:param flags: link flags
|
||||
:return: link data
|
||||
:rtype: list[core.data.LinkData]
|
||||
"""
|
||||
return []
|
||||
|
|
|
@ -58,6 +58,8 @@ class EbtablesQueue(object):
|
|||
def startupdateloop(self, wlan):
|
||||
"""
|
||||
Kick off the update loop; only needs to be invoked once.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.updatelock.acquire()
|
||||
self.last_update_time[wlan] = time.time()
|
||||
|
@ -72,6 +74,8 @@ class EbtablesQueue(object):
|
|||
def stopupdateloop(self, wlan):
|
||||
"""
|
||||
Kill the update loop thread if there are no more WLANs using it.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.updatelock.acquire()
|
||||
try:
|
||||
|
@ -90,6 +94,10 @@ class EbtablesQueue(object):
|
|||
def ebatomiccmd(self, cmd):
|
||||
"""
|
||||
Helper for building ebtables atomic file command list.
|
||||
|
||||
:param list[str] cmd: ebtable command
|
||||
:return: ebtable atomic command
|
||||
:rtype: list[str]
|
||||
"""
|
||||
r = [constants.EBTABLES_BIN, "--atomic-file", self.atomic_file]
|
||||
if cmd:
|
||||
|
@ -99,17 +107,25 @@ class EbtablesQueue(object):
|
|||
def lastupdate(self, wlan):
|
||||
"""
|
||||
Return the time elapsed since this WLAN was last updated.
|
||||
|
||||
:param wlan: wlan entity
|
||||
:return: elpased time
|
||||
:rtype: float
|
||||
"""
|
||||
try:
|
||||
elapsed = time.time() - self.last_update_time[wlan]
|
||||
except KeyError:
|
||||
self.last_update_time[wlan] = time.time()
|
||||
elapsed = 0.0
|
||||
|
||||
return elapsed
|
||||
|
||||
def updated(self, wlan):
|
||||
"""
|
||||
Keep track of when this WLAN was last updated.
|
||||
|
||||
:param wlan: wlan entity
|
||||
:return: nothing
|
||||
"""
|
||||
self.last_update_time[wlan] = time.time()
|
||||
self.updates.remove(wlan)
|
||||
|
@ -119,6 +135,8 @@ class EbtablesQueue(object):
|
|||
Thread target that looks for WLANs needing update, and
|
||||
rate limits the amount of ebtables activity. Only one userspace program
|
||||
should use ebtables at any given time, or results can be unpredictable.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
while self.doupdateloop:
|
||||
self.updatelock.acquire()
|
||||
|
@ -144,8 +162,9 @@ class EbtablesQueue(object):
|
|||
|
||||
def ebcommit(self, wlan):
|
||||
"""
|
||||
Perform ebtables atomic commit using commands built in the
|
||||
self.cmds list.
|
||||
Perform ebtables atomic commit using commands built in the self.cmds list.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
# save kernel ebtables snapshot to a file
|
||||
cmd = self.ebatomiccmd(["--atomic-save", ])
|
||||
|
@ -177,6 +196,8 @@ class EbtablesQueue(object):
|
|||
"""
|
||||
Flag a change to the given WLAN's _linked dict, so the ebtables
|
||||
chain will be rebuilt at the next interval.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.updatelock.acquire()
|
||||
if wlan not in self.updates:
|
||||
|
@ -185,8 +206,9 @@ class EbtablesQueue(object):
|
|||
|
||||
def buildcmds(self, wlan):
|
||||
"""
|
||||
Inspect a _linked dict from a wlan, and rebuild the ebtables chain
|
||||
for that WLAN.
|
||||
Inspect a _linked dict from a wlan, and rebuild the ebtables chain for that WLAN.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
wlan._linked_lock.acquire()
|
||||
# flush the chain
|
||||
|
@ -213,18 +235,34 @@ ebq = EbtablesQueue()
|
|||
|
||||
|
||||
def ebtablescmds(call, cmds):
|
||||
ebtables_lock.acquire()
|
||||
try:
|
||||
"""
|
||||
Run ebtable commands.
|
||||
|
||||
:param func call: function to call commands
|
||||
:param list cmds: commands to call
|
||||
:return: nothing
|
||||
"""
|
||||
with ebtables_lock:
|
||||
for cmd in cmds:
|
||||
call(cmd)
|
||||
finally:
|
||||
ebtables_lock.release()
|
||||
|
||||
|
||||
class LxBrNet(PyCoreNet):
|
||||
"""
|
||||
Provides linux bridge network functionlity for core nodes.
|
||||
"""
|
||||
policy = "DROP"
|
||||
|
||||
def __init__(self, session, objid=None, name=None, start=True, policy=None):
|
||||
"""
|
||||
Creates a LxBrNet instance.
|
||||
|
||||
:param core.session.Session session: core session instance
|
||||
:param int objid: object id
|
||||
:param str name: object name
|
||||
:param bool start: start flag
|
||||
:param policy: network policy
|
||||
"""
|
||||
PyCoreNet.__init__(self, session, objid, name, start)
|
||||
if name is None:
|
||||
name = str(self.objid)
|
||||
|
@ -239,6 +277,11 @@ class LxBrNet(PyCoreNet):
|
|||
ebq.startupdateloop(self)
|
||||
|
||||
def startup(self):
|
||||
"""
|
||||
Linux bridge starup logic.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
try:
|
||||
subprocess.check_call([constants.BRCTL_BIN, "addbr", self.brname])
|
||||
except subprocess.CalledProcessError:
|
||||
|
@ -264,6 +307,11 @@ class LxBrNet(PyCoreNet):
|
|||
self.up = True
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Linux bridge shutdown logic.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if not self.up:
|
||||
return
|
||||
ebq.stopupdateloop(self)
|
||||
|
@ -282,6 +330,12 @@ class LxBrNet(PyCoreNet):
|
|||
self.up = False
|
||||
|
||||
def attach(self, netif):
|
||||
"""
|
||||
Attach a network interface.
|
||||
|
||||
:param core.netns.vif.VEth netif: network interface to attach
|
||||
:return: nothing
|
||||
"""
|
||||
if self.up:
|
||||
try:
|
||||
subprocess.check_call([constants.BRCTL_BIN, "addif", self.brname, netif.localname])
|
||||
|
@ -292,6 +346,12 @@ class LxBrNet(PyCoreNet):
|
|||
PyCoreNet.attach(self, netif)
|
||||
|
||||
def detach(self, netif):
|
||||
"""
|
||||
Detach a network interface.
|
||||
|
||||
:param core.netns.vif.Veth netif: network interface to detach
|
||||
:return: nothing
|
||||
"""
|
||||
if self.up:
|
||||
try:
|
||||
subprocess.check_call([constants.BRCTL_BIN, "delif", self.brname, netif.localname])
|
||||
|
@ -301,11 +361,21 @@ class LxBrNet(PyCoreNet):
|
|||
PyCoreNet.detach(self, netif)
|
||||
|
||||
def linked(self, netif1, netif2):
|
||||
"""
|
||||
Determine if the provided network interfaces are linked.
|
||||
|
||||
:param core.netns.vif.Veth netif1: interface one
|
||||
:param core.netns.vif.Veth netif2: interface two
|
||||
:return: True if interfaces are linked, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
# check if the network interfaces are attached to this network
|
||||
if self._netif[netif1.netifi] != netif1:
|
||||
raise ValueError, "inconsistency for netif %s" % netif1.name
|
||||
raise ValueError("inconsistency for netif %s" % netif1.name)
|
||||
|
||||
if self._netif[netif2.netifi] != netif2:
|
||||
raise ValueError, "inconsistency for netif %s" % netif2.name
|
||||
raise ValueError("inconsistency for netif %s" % netif2.name)
|
||||
|
||||
try:
|
||||
linked = self._linked[netif1][netif2]
|
||||
except KeyError:
|
||||
|
@ -314,14 +384,19 @@ class LxBrNet(PyCoreNet):
|
|||
elif self.policy == "DROP":
|
||||
linked = False
|
||||
else:
|
||||
raise Exception, "unknown policy: %s" % self.policy
|
||||
raise Exception("unknown policy: %s" % self.policy)
|
||||
self._linked[netif1][netif2] = linked
|
||||
|
||||
return linked
|
||||
|
||||
def unlink(self, netif1, netif2):
|
||||
"""
|
||||
Unlink two PyCoreNetIfs, resulting in adding or removing ebtables
|
||||
filtering rules.
|
||||
|
||||
:param core.netns.vif.Veth netif1: interface one
|
||||
:param core.netns.vif.Veth netif2: interface two
|
||||
:return: nothing
|
||||
"""
|
||||
self._linked_lock.acquire()
|
||||
if not self.linked(netif1, netif2):
|
||||
|
@ -335,6 +410,10 @@ class LxBrNet(PyCoreNet):
|
|||
"""
|
||||
Link two PyCoreNetIfs together, resulting in adding or removing
|
||||
ebtables filtering rules.
|
||||
|
||||
:param core.netns.vif.Veth netif1: interface one
|
||||
:param core.netns.vif.Veth netif2: interface two
|
||||
:return: nothing
|
||||
"""
|
||||
self._linked_lock.acquire()
|
||||
if self.linked(netif1, netif2):
|
||||
|
@ -347,8 +426,17 @@ class LxBrNet(PyCoreNet):
|
|||
def linkconfig(self, netif, bw=None, delay=None, loss=None, duplicate=None,
|
||||
jitter=None, netif2=None, devname=None):
|
||||
"""
|
||||
Configure link parameters by applying tc queuing disciplines on the
|
||||
interface.
|
||||
Configure link parameters by applying tc queuing disciplines on the interface.
|
||||
|
||||
:param core.netns.vif.Veth netif: interface one
|
||||
:param bw: bandwidth to set to
|
||||
:param delay: packet delay to set to
|
||||
:param loss: packet loss to set to
|
||||
:param duplicate: duplicate percentage to set to
|
||||
:param jitter: jitter to set to
|
||||
:param core.netns.vif.Veth netif2: interface two
|
||||
:param devname: device name
|
||||
:return: nothing
|
||||
"""
|
||||
if devname is None:
|
||||
devname = netif.localname
|
||||
|
@ -359,7 +447,8 @@ class LxBrNet(PyCoreNet):
|
|||
# from tc-tbf(8): minimum value for burst is rate / kernel_hz
|
||||
if bw is not None:
|
||||
burst = max(2 * netif.mtu, bw / 1000)
|
||||
limit = 0xffff # max IP payload
|
||||
# max IP payload
|
||||
limit = 0xffff
|
||||
tbf = ["tbf", "rate", str(bw),
|
||||
"burst", str(burst), "limit", str(limit)]
|
||||
if bw > 0:
|
||||
|
@ -422,22 +511,26 @@ class LxBrNet(PyCoreNet):
|
|||
"""
|
||||
Link this bridge with another by creating a veth pair and installing
|
||||
each device into each bridge.
|
||||
|
||||
:param core.netns.vnet.LxBrNet net: network to link with
|
||||
:return: created interface
|
||||
:rtype: Veth
|
||||
"""
|
||||
sessionid = self.session.short_session_id()
|
||||
try:
|
||||
self_objid = '%x' % self.objid
|
||||
self_objid = "%x" % self.objid
|
||||
except TypeError:
|
||||
self_objid = '%s' % self.objid
|
||||
self_objid = "%s" % self.objid
|
||||
try:
|
||||
net_objid = '%x' % net.objid
|
||||
net_objid = "%x" % net.objid
|
||||
except TypeError:
|
||||
net_objid = '%s' % net.objid
|
||||
localname = 'veth%s.%s.%s' % (self_objid, net_objid, sessionid)
|
||||
net_objid = "%s" % net.objid
|
||||
localname = "veth%s.%s.%s" % (self_objid, net_objid, sessionid)
|
||||
if len(localname) >= 16:
|
||||
raise ValueError("interface local name '%s' too long" % localname)
|
||||
name = 'veth%s.%s.%s' % (net_objid, self_objid, sessionid)
|
||||
raise ValueError("interface local name %s too long" % localname)
|
||||
name = "veth%s.%s.%s" % (net_objid, self_objid, sessionid)
|
||||
if len(name) >= 16:
|
||||
raise ValueError("interface name '%s' too long" % name)
|
||||
raise ValueError("interface name %s too long" % name)
|
||||
netif = VEth(node=None, name=name, localname=localname,
|
||||
mtu=1500, net=self, start=self.up)
|
||||
self.attach(netif)
|
||||
|
@ -458,15 +551,22 @@ class LxBrNet(PyCoreNet):
|
|||
"""
|
||||
Return the interface of that links this net with another net
|
||||
(that were linked using linknet()).
|
||||
|
||||
:param core.netns.vnet.LxBrNet net: interface to get link for
|
||||
:return: interface the provided network is linked to
|
||||
:rtype: core.netns.vnet.LxBrNet
|
||||
"""
|
||||
for netif in self.netifs():
|
||||
if hasattr(netif, 'othernet') and netif.othernet == net:
|
||||
if hasattr(netif, "othernet") and netif.othernet == net:
|
||||
return netif
|
||||
return None
|
||||
|
||||
def addrconfig(self, addrlist):
|
||||
"""
|
||||
Set addresses on the bridge.
|
||||
|
||||
:param list[str] addrlist: address list
|
||||
:return: nothing
|
||||
"""
|
||||
if not self.up:
|
||||
return
|
||||
|
@ -485,6 +585,20 @@ class GreTapBridge(LxBrNet):
|
|||
|
||||
def __init__(self, session, remoteip=None, objid=None, name=None,
|
||||
policy="ACCEPT", localip=None, ttl=255, key=None, start=True):
|
||||
"""
|
||||
Create a GreTapBridge instance.
|
||||
|
||||
:param core.session.Session session: core session instance
|
||||
:param str remoteip: remote address
|
||||
:param int objid: object id
|
||||
:param str name: object name
|
||||
:param policy: network policy
|
||||
:param str localip: local address
|
||||
:param ttl: ttl value
|
||||
:param key: gre tap key
|
||||
:param bool start: start flag
|
||||
:return:
|
||||
"""
|
||||
LxBrNet.__init__(self, session=session, objid=objid, name=name, policy=policy, start=False)
|
||||
self.grekey = key
|
||||
if self.grekey is None:
|
||||
|
@ -497,15 +611,16 @@ class GreTapBridge(LxBrNet):
|
|||
if remoteip is None:
|
||||
self.gretap = None
|
||||
else:
|
||||
self.gretap = GreTap(node=self, name=None, session=session,
|
||||
remoteip=remoteip, objid=None, localip=localip, ttl=ttl,
|
||||
key=self.grekey)
|
||||
self.gretap = GreTap(node=self, name=None, session=session, remoteip=remoteip,
|
||||
objid=None, localip=localip, ttl=ttl, key=self.grekey)
|
||||
if start:
|
||||
self.startup()
|
||||
|
||||
def startup(self):
|
||||
"""
|
||||
Creates a bridge and adds the gretap device to it.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
LxBrNet.startup(self)
|
||||
if self.gretap:
|
||||
|
@ -514,6 +629,8 @@ class GreTapBridge(LxBrNet):
|
|||
def shutdown(self):
|
||||
"""
|
||||
Detach the gretap device and remove the bridge.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if self.gretap:
|
||||
self.detach(self.gretap)
|
||||
|
@ -527,15 +644,17 @@ class GreTapBridge(LxBrNet):
|
|||
creating the GreTap device, which requires the remoteip at startup.
|
||||
The 1st address in the provided list is remoteip, 2nd optionally
|
||||
specifies localip.
|
||||
|
||||
:param list addrlist: address list
|
||||
:return: nothing
|
||||
"""
|
||||
if self.gretap:
|
||||
raise ValueError, "gretap already exists for %s" % self.name
|
||||
raise ValueError("gretap already exists for %s" % self.name)
|
||||
remoteip = addrlist[0].split('/')[0]
|
||||
localip = None
|
||||
if len(addrlist) > 1:
|
||||
localip = addrlist[1].split('/')[0]
|
||||
self.gretap = GreTap(session=self.session, remoteip=remoteip,
|
||||
objid=None, name=None,
|
||||
self.gretap = GreTap(session=self.session, remoteip=remoteip, objid=None, name=None,
|
||||
localip=localip, ttl=self.ttl, key=self.grekey)
|
||||
self.attach(self.gretap)
|
||||
|
||||
|
@ -543,5 +662,8 @@ class GreTapBridge(LxBrNet):
|
|||
"""
|
||||
Set the GRE key used for the GreTap device. This needs to be set
|
||||
prior to instantiating the GreTap device (before addrconfig).
|
||||
|
||||
:param key: gre key
|
||||
:return: nothing
|
||||
"""
|
||||
self.grekey = key
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
"""
|
||||
vnode.py: PyCoreNode and LxcNode classes that implement the network namespace
|
||||
virtual node.
|
||||
PyCoreNode and LxcNode classes that implement the network namespac virtual node.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
@ -28,7 +27,21 @@ utils.check_executables([constants.IP_BIN])
|
|||
|
||||
|
||||
class SimpleLxcNode(PyCoreNode):
|
||||
"""
|
||||
Provides simple lxc functionality for core nodes.
|
||||
"""
|
||||
valid_deladdrtype = ("inet", "inet6", "inet6link")
|
||||
|
||||
def __init__(self, session, objid=None, name=None, nodedir=None, start=True):
|
||||
"""
|
||||
Create a SimpleLxcNode instance.
|
||||
|
||||
:param core.session.Session session: core session instance
|
||||
:param int objid: object id
|
||||
:param str name: object name
|
||||
:param str nodedir: node directory
|
||||
:param bool start: start flag
|
||||
"""
|
||||
PyCoreNode.__init__(self, session, objid, name, start=start)
|
||||
self.nodedir = nodedir
|
||||
self.ctrlchnlname = os.path.abspath(os.path.join(self.session.session_dir, self.name))
|
||||
|
@ -39,10 +52,17 @@ class SimpleLxcNode(PyCoreNode):
|
|||
self._mounts = []
|
||||
|
||||
def alive(self):
|
||||
"""
|
||||
Check if the node is alive.
|
||||
|
||||
:return: True if node is alive, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
try:
|
||||
os.kill(self.pid, 0)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def startup(self):
|
||||
|
@ -50,6 +70,8 @@ class SimpleLxcNode(PyCoreNode):
|
|||
Start a new namespace node by invoking the vnoded process that
|
||||
allocates a new namespace. Bring up the loopback device and set
|
||||
the hostname.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if self.up:
|
||||
raise Exception("already up")
|
||||
|
@ -88,6 +110,11 @@ class SimpleLxcNode(PyCoreNode):
|
|||
self.up = True
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Shutdown logic for simple lxc nodes.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
# nothing to do if node is not up
|
||||
if not self.up:
|
||||
return
|
||||
|
@ -120,56 +147,143 @@ class SimpleLxcNode(PyCoreNode):
|
|||
self.vnodeclient.close()
|
||||
self.up = False
|
||||
|
||||
# TODO: potentially remove all these wrapper methods, just make use of object itself.
|
||||
def cmd(self, args, wait=True):
|
||||
"""
|
||||
Wrapper around vnodeclient cmd.
|
||||
|
||||
:param args: arguments for ocmmand
|
||||
:param wait: wait or not
|
||||
:return:
|
||||
"""
|
||||
return self.vnodeclient.cmd(args, wait)
|
||||
|
||||
def cmdresult(self, args):
|
||||
"""
|
||||
Wrapper around vnodeclient cmdresult.
|
||||
|
||||
:param args: arguments for ocmmand
|
||||
:return:
|
||||
"""
|
||||
return self.vnodeclient.cmdresult(args)
|
||||
|
||||
def popen(self, args):
|
||||
"""
|
||||
Wrapper around vnodeclient popen.
|
||||
|
||||
:param args: arguments for ocmmand
|
||||
:return:
|
||||
"""
|
||||
return self.vnodeclient.popen(args)
|
||||
|
||||
def icmd(self, args):
|
||||
"""
|
||||
Wrapper around vnodeclient icmd.
|
||||
|
||||
:param args: arguments for ocmmand
|
||||
:return:
|
||||
"""
|
||||
return self.vnodeclient.icmd(args)
|
||||
|
||||
def redircmd(self, infd, outfd, errfd, args, wait=True):
|
||||
"""
|
||||
Wrapper around vnodeclient redircmd.
|
||||
|
||||
:param infd: input file descriptor
|
||||
:param outfd: output file descriptor
|
||||
:param errfd: err file descriptor
|
||||
:param args: command arguments
|
||||
:param wait: wait or not
|
||||
:return:
|
||||
"""
|
||||
return self.vnodeclient.redircmd(infd, outfd, errfd, args, wait)
|
||||
|
||||
def term(self, sh="/bin/sh"):
|
||||
"""
|
||||
Wrapper around vnodeclient term.
|
||||
|
||||
:param sh: shell to create terminal for
|
||||
:return:
|
||||
"""
|
||||
return self.vnodeclient.term(sh=sh)
|
||||
|
||||
def termcmdstring(self, sh="/bin/sh"):
|
||||
"""
|
||||
Wrapper around vnodeclient termcmdstring.
|
||||
|
||||
:param sh: shell to run command in
|
||||
:return:
|
||||
"""
|
||||
return self.vnodeclient.termcmdstring(sh=sh)
|
||||
|
||||
def shcmd(self, cmdstr, sh="/bin/sh"):
|
||||
"""
|
||||
Wrapper around vnodeclient shcmd.
|
||||
|
||||
:param str cmdstr: command string
|
||||
:param sh: shell to run command in
|
||||
:return:
|
||||
"""
|
||||
return self.vnodeclient.shcmd(cmdstr, sh=sh)
|
||||
|
||||
def boot(self):
|
||||
"""
|
||||
Boot logic.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
pass
|
||||
|
||||
def mount(self, source, target):
|
||||
"""
|
||||
Create and mount a directory.
|
||||
|
||||
:param str source: source directory to mount
|
||||
:param str target: target directory to create
|
||||
:return: nothing
|
||||
"""
|
||||
source = os.path.abspath(source)
|
||||
logger.info("mounting %s at %s" % (source, target))
|
||||
try:
|
||||
shcmd = "mkdir -p '%s' && %s -n --bind '%s' '%s'" % \
|
||||
(target, constants.MOUNT_BIN, source, target)
|
||||
shcmd = "mkdir -p '%s' && %s -n --bind '%s' '%s'" % (
|
||||
target, constants.MOUNT_BIN, source, target)
|
||||
self.shcmd(shcmd)
|
||||
self._mounts.append((source, target))
|
||||
except:
|
||||
except IOError:
|
||||
logger.exception("mounting failed for %s at %s", source, target)
|
||||
|
||||
def umount(self, target):
|
||||
"""
|
||||
Unmount a target directory.
|
||||
|
||||
:param str target: target directory to unmount
|
||||
:return: nothing
|
||||
"""
|
||||
logger.info("unmounting '%s'" % target)
|
||||
try:
|
||||
self.cmd([constants.UMOUNT_BIN, "-n", "-l", target])
|
||||
except:
|
||||
except IOError:
|
||||
logger.exception("unmounting failed for %s" % target)
|
||||
|
||||
def newifindex(self):
|
||||
"""
|
||||
Retrieve a new interface index.
|
||||
|
||||
:return: new interface index
|
||||
:rtype: int
|
||||
"""
|
||||
with self.lock:
|
||||
return PyCoreNode.newifindex(self)
|
||||
return super(SimpleLxcNode, self).newifindex()
|
||||
|
||||
def newveth(self, ifindex=None, ifname=None, net=None):
|
||||
"""
|
||||
Create a new interface.
|
||||
|
||||
:param int ifindex: index for the new interface
|
||||
:param str ifname: name for the new interface
|
||||
:param net: network to associate interface with
|
||||
:return: nothing
|
||||
"""
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if ifindex is None:
|
||||
|
@ -187,8 +301,7 @@ class SimpleLxcNode(PyCoreNode):
|
|||
|
||||
localname = 'veth' + suffix
|
||||
if len(localname) >= 16:
|
||||
raise ValueError, "interface local name '%s' too long" % \
|
||||
localname
|
||||
raise ValueError("interface local name '%s' too long" % localname)
|
||||
name = localname + 'p'
|
||||
if len(name) >= 16:
|
||||
raise ValueError, "interface name '%s' too long" % name
|
||||
|
@ -221,6 +334,15 @@ class SimpleLxcNode(PyCoreNode):
|
|||
self.lock.release()
|
||||
|
||||
def newtuntap(self, ifindex=None, ifname=None, net=None):
|
||||
"""
|
||||
Create a new tunnel tap.
|
||||
|
||||
:param int ifindex: interface index
|
||||
:param str ifname: interface name
|
||||
:param net: network to associate with
|
||||
:return: interface index
|
||||
:rtype: int
|
||||
"""
|
||||
self.lock.acquire()
|
||||
try:
|
||||
if ifindex is None:
|
||||
|
@ -235,15 +357,22 @@ class SimpleLxcNode(PyCoreNode):
|
|||
mtu=1500, net=net, start=self.up)
|
||||
try:
|
||||
self.addnetif(tuntap, ifindex)
|
||||
except:
|
||||
except Exception as e:
|
||||
tuntap.shutdown()
|
||||
del tuntap
|
||||
raise
|
||||
raise e
|
||||
return ifindex
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def sethwaddr(self, ifindex, addr):
|
||||
"""
|
||||
Set hardware addres for an interface.
|
||||
|
||||
:param int ifindex: index of interface to set hardware address for
|
||||
:param str addr: hardware address to set
|
||||
:return: mothing
|
||||
"""
|
||||
self._netif[ifindex].sethwaddr(addr)
|
||||
if self.up:
|
||||
(status, result) = self.cmdresult([constants.IP_BIN, "link", "set", "dev",
|
||||
|
@ -252,12 +381,26 @@ class SimpleLxcNode(PyCoreNode):
|
|||
logger.error("error setting MAC address %s", str(addr))
|
||||
|
||||
def addaddr(self, ifindex, addr):
|
||||
"""
|
||||
Add interface address.
|
||||
|
||||
:param int ifindex: index of interface to add address to
|
||||
:param str addr: address to add to interface
|
||||
:return: nothing
|
||||
"""
|
||||
if self.up:
|
||||
self.cmd([constants.IP_BIN, "addr", "add", str(addr),
|
||||
"dev", self.ifname(ifindex)])
|
||||
self._netif[ifindex].addaddr(addr)
|
||||
|
||||
def deladdr(self, ifindex, addr):
|
||||
"""
|
||||
Delete address from an interface.
|
||||
|
||||
:param int ifindex: index of interface to delete address from
|
||||
:param str addr: address to delete from interface
|
||||
:return: nothing
|
||||
"""
|
||||
try:
|
||||
self._netif[ifindex].deladdr(addr)
|
||||
except ValueError:
|
||||
|
@ -266,9 +409,14 @@ class SimpleLxcNode(PyCoreNode):
|
|||
if self.up:
|
||||
self.cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)])
|
||||
|
||||
valid_deladdrtype = ("inet", "inet6", "inet6link")
|
||||
|
||||
def delalladdr(self, ifindex, addrtypes=valid_deladdrtype):
|
||||
"""
|
||||
Delete all addresses from an interface.
|
||||
|
||||
:param int ifindex: index of interface to delete all addresses from
|
||||
:param tuple addrtypes: address types to delete
|
||||
:return: nothing
|
||||
"""
|
||||
addr = self.getaddr(self.ifname(ifindex), rescan=True)
|
||||
for t in addrtypes:
|
||||
if t not in self.valid_deladdrtype:
|
||||
|
@ -279,16 +427,32 @@ class SimpleLxcNode(PyCoreNode):
|
|||
self.getaddr(self.ifname(ifindex), rescan=True)
|
||||
|
||||
def ifup(self, ifindex):
|
||||
"""
|
||||
Bring an interface up.
|
||||
|
||||
:param int ifindex: index of interface to bring up
|
||||
:return: nothing
|
||||
"""
|
||||
if self.up:
|
||||
self.cmd([constants.IP_BIN, "link", "set", self.ifname(ifindex), "up"])
|
||||
|
||||
def newnetif(self, net=None, addrlist=[], hwaddr=None,
|
||||
ifindex=None, ifname=None):
|
||||
def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None):
|
||||
"""
|
||||
Create a new network interface.
|
||||
|
||||
:param net: network to associate with
|
||||
:param list addrlist: addresses to add on the interface
|
||||
:param str hwaddr: hardware address to set for interface
|
||||
:param int ifindex: index of interface to create
|
||||
:param str ifname: name for interface
|
||||
:return: interface index
|
||||
:rtype: int
|
||||
"""
|
||||
self.lock.acquire()
|
||||
try:
|
||||
# TODO: see if you can move this to emane specific code
|
||||
if nodeutils.is_node(net, NodeTypes.EMANE):
|
||||
ifindex = self.newtuntap(ifindex=ifindex, ifname=ifname,
|
||||
net=net)
|
||||
ifindex = self.newtuntap(ifindex=ifindex, ifname=ifname, net=net)
|
||||
# TUN/TAP is not ready for addressing yet; the device may
|
||||
# take some time to appear, and installing it into a
|
||||
# namespace after it has been bound removes addressing;
|
||||
|
@ -308,8 +472,9 @@ class SimpleLxcNode(PyCoreNode):
|
|||
if hwaddr:
|
||||
self.sethwaddr(ifindex, hwaddr)
|
||||
|
||||
for addr in utils.maketuple(addrlist):
|
||||
self.addaddr(ifindex, addr)
|
||||
if addrlist:
|
||||
for addr in utils.maketuple(addrlist):
|
||||
self.addaddr(ifindex, addr)
|
||||
|
||||
self.ifup(ifindex)
|
||||
return ifindex
|
||||
|
@ -317,6 +482,14 @@ class SimpleLxcNode(PyCoreNode):
|
|||
self.lock.release()
|
||||
|
||||
def connectnode(self, ifname, othernode, otherifname):
|
||||
"""
|
||||
Connect a node.
|
||||
|
||||
:param str ifname: name of interface to connect
|
||||
:param core.netns.nodes.LxcNode othernode: node to connect to
|
||||
:param str otherifname: interface name to connect to
|
||||
:return: nothing
|
||||
"""
|
||||
tmplen = 8
|
||||
tmp1 = "tmp." + "".join([random.choice(string.ascii_lowercase)
|
||||
for x in xrange(tmplen)])
|
||||
|
@ -335,20 +508,53 @@ class SimpleLxcNode(PyCoreNode):
|
|||
othernode.newifindex())
|
||||
|
||||
def addfile(self, srcname, filename):
|
||||
shcmd = "mkdir -p $(dirname '%s') && mv '%s' '%s' && sync" % \
|
||||
(filename, srcname, filename)
|
||||
"""
|
||||
Add a file.
|
||||
|
||||
:param str srcname: source file name
|
||||
:param str filename: file name to add
|
||||
:return: nothing
|
||||
"""
|
||||
shcmd = "mkdir -p $(dirname '%s') && mv '%s' '%s' && sync" % (filename, srcname, filename)
|
||||
self.shcmd(shcmd)
|
||||
|
||||
def getaddr(self, ifname, rescan=False):
|
||||
"""
|
||||
Wrapper around vnodeclient getaddr.
|
||||
|
||||
:param str ifname: interface name to get address for
|
||||
:param bool rescan: rescan flag
|
||||
:return:
|
||||
"""
|
||||
return self.vnodeclient.getaddr(ifname=ifname, rescan=rescan)
|
||||
|
||||
def netifstats(self, ifname=None):
|
||||
"""
|
||||
Wrapper around vnodeclient netifstate.
|
||||
|
||||
:param str ifname: interface name to get state for
|
||||
:return:
|
||||
"""
|
||||
return self.vnodeclient.netifstats(ifname=ifname)
|
||||
|
||||
|
||||
class LxcNode(SimpleLxcNode):
|
||||
"""
|
||||
Provides lcx node functionality for core nodes.
|
||||
"""
|
||||
|
||||
def __init__(self, session, objid=None, name=None,
|
||||
nodedir=None, bootsh="boot.sh", start=True):
|
||||
"""
|
||||
Create a LxcNode instance.
|
||||
|
||||
:param core.session.Session session: core session instance
|
||||
:param int objid: object id
|
||||
:param str name: object name
|
||||
:param str nodedir: node directory
|
||||
:param bootsh: boot shell
|
||||
:param bool start: start flag
|
||||
"""
|
||||
super(LxcNode, self).__init__(session=session, objid=objid,
|
||||
name=name, nodedir=nodedir, start=start)
|
||||
self.bootsh = bootsh
|
||||
|
@ -356,12 +562,27 @@ class LxcNode(SimpleLxcNode):
|
|||
self.startup()
|
||||
|
||||
def boot(self):
|
||||
"""
|
||||
Boot the node.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.session.services.bootnodeservices(self)
|
||||
|
||||
def validate(self):
|
||||
"""
|
||||
Validate the node.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.session.services.validatenodeservices(self)
|
||||
|
||||
def startup(self):
|
||||
"""
|
||||
Startup logic for the node.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.lock.acquire()
|
||||
try:
|
||||
self.makenodedir()
|
||||
|
@ -374,6 +595,11 @@ class LxcNode(SimpleLxcNode):
|
|||
self.lock.release()
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Shutdown logic for the node.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if not self.up:
|
||||
return
|
||||
self.lock.acquire()
|
||||
|
@ -381,11 +607,19 @@ class LxcNode(SimpleLxcNode):
|
|||
# self.session.services.stopnodeservices(self)
|
||||
try:
|
||||
super(LxcNode, self).shutdown()
|
||||
except:
|
||||
logger.exception("error during shutdown")
|
||||
finally:
|
||||
self.rmnodedir()
|
||||
self.lock.release()
|
||||
|
||||
def privatedir(self, path):
|
||||
"""
|
||||
Create a private directory.
|
||||
|
||||
:param str path: path to create
|
||||
:return: nothing
|
||||
"""
|
||||
if path[0] != "/":
|
||||
raise ValueError("path not fully qualified: %s" % path)
|
||||
hostpath = os.path.join(self.nodedir, os.path.normpath(path).strip('/').replace('/', '.'))
|
||||
|
@ -400,10 +634,13 @@ class LxcNode(SimpleLxcNode):
|
|||
def hostfilename(self, filename):
|
||||
"""
|
||||
Return the name of a node's file on the host filesystem.
|
||||
|
||||
:param str filename: host file name
|
||||
:return: path to file
|
||||
"""
|
||||
dirname, basename = os.path.split(filename)
|
||||
if not basename:
|
||||
raise ValueError, "no basename for filename: " + filename
|
||||
raise ValueError("no basename for filename: " + filename)
|
||||
if dirname and dirname[0] == "/":
|
||||
dirname = dirname[1:]
|
||||
dirname = dirname.replace("/", ".")
|
||||
|
@ -411,6 +648,14 @@ class LxcNode(SimpleLxcNode):
|
|||
return os.path.join(dirname, basename)
|
||||
|
||||
def opennodefile(self, filename, mode="w"):
|
||||
"""
|
||||
Open a node file, within it's directory.
|
||||
|
||||
:param str filename: file name to open
|
||||
:param str mode: mode to open file in
|
||||
:return: open file
|
||||
:rtype: file
|
||||
"""
|
||||
hostfilename = self.hostfilename(filename)
|
||||
dirname, basename = os.path.split(hostfilename)
|
||||
if not os.path.isdir(dirname):
|
||||
|
@ -418,6 +663,14 @@ class LxcNode(SimpleLxcNode):
|
|||
return open(hostfilename, mode)
|
||||
|
||||
def nodefile(self, filename, contents, mode=0644):
|
||||
"""
|
||||
Create a node file with a given mode.
|
||||
|
||||
:param str filename: name of file to create
|
||||
:param contents: contents of file
|
||||
:param int mode: mode for file
|
||||
:return: nothing
|
||||
"""
|
||||
f = self.opennodefile(filename, "w")
|
||||
f.write(contents)
|
||||
os.chmod(f.name, mode)
|
||||
|
@ -428,6 +681,11 @@ class LxcNode(SimpleLxcNode):
|
|||
"""
|
||||
Copy a file to a node, following symlinks and preserving metadata.
|
||||
Change file mode if specified.
|
||||
|
||||
:param str filename: file name to copy file to
|
||||
:param str srcfilename: file to copy
|
||||
:param int mode: mode to copy to
|
||||
:return: nothing
|
||||
"""
|
||||
hostfilename = self.hostfilename(filename)
|
||||
shutil.copy2(srcfilename, hostfilename)
|
||||
|
|
|
@ -24,7 +24,17 @@ VCMD = os.path.join(constants.CORE_SBIN_DIR, "vcmd")
|
|||
|
||||
|
||||
class VnodeClient(object):
|
||||
"""
|
||||
Provides client functionality for interacting with a virtual node.
|
||||
"""
|
||||
|
||||
def __init__(self, name, ctrlchnlname):
|
||||
"""
|
||||
Create a VnodeClient instance.
|
||||
|
||||
:param str name: name for client
|
||||
:param str ctrlchnlname: control channel name
|
||||
"""
|
||||
self.name = name
|
||||
self.ctrlchnlname = ctrlchnlname
|
||||
if USE_VCMD_MODULE:
|
||||
|
@ -34,18 +44,34 @@ class VnodeClient(object):
|
|||
self._addr = {}
|
||||
|
||||
def connected(self):
|
||||
"""
|
||||
Check if node is connected or not.
|
||||
|
||||
:return: True if connected, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
if USE_VCMD_MODULE:
|
||||
return self.cmdchnl.connected()
|
||||
else:
|
||||
return True
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the client connection.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if USE_VCMD_MODULE:
|
||||
self.cmdchnl.close()
|
||||
|
||||
def cmd(self, args, wait=True):
|
||||
"""
|
||||
Execute a command on a node and return the status (return code).
|
||||
|
||||
:param list args: command arguments
|
||||
:param bool wait: wait for command to end or not
|
||||
:return: command status
|
||||
:rtype: int
|
||||
"""
|
||||
if USE_VCMD_MODULE:
|
||||
if not self.cmdchnl.connected():
|
||||
|
@ -62,8 +88,10 @@ class VnodeClient(object):
|
|||
tmp = os.spawnlp(mode, VCMD, VCMD, "-c", self.ctrlchnlname, "-q", "--", *args)
|
||||
if not wait:
|
||||
return tmp
|
||||
|
||||
if tmp:
|
||||
logger.warn("cmd exited with status %s: %s" % (tmp, str(args)))
|
||||
|
||||
return tmp
|
||||
|
||||
def cmdresult(self, args):
|
||||
|
@ -71,6 +99,10 @@ class VnodeClient(object):
|
|||
Execute a command on a node and return a tuple containing the
|
||||
exit status and result string. stderr output
|
||||
is folded into the stdout result string.
|
||||
|
||||
:param list args: command arguments
|
||||
:return: command status and combined stdout and stderr output
|
||||
:rtype: tuple[int, str]
|
||||
"""
|
||||
cmdid, cmdin, cmdout, cmderr = self.popen(args)
|
||||
result = cmdout.read()
|
||||
|
@ -82,6 +114,13 @@ class VnodeClient(object):
|
|||
return status, result
|
||||
|
||||
def popen(self, args):
|
||||
"""
|
||||
Execute a popen command against the node.
|
||||
|
||||
:param list args: command arguments
|
||||
:return: popen object, stdin, stdout, and stderr
|
||||
:rtype: tuple
|
||||
"""
|
||||
if USE_VCMD_MODULE:
|
||||
if not self.cmdchnl.connected():
|
||||
raise ValueError("self.cmdchnl not connected")
|
||||
|
@ -93,12 +132,27 @@ class VnodeClient(object):
|
|||
return tmp, tmp.stdin, tmp.stdout, tmp.stderr
|
||||
|
||||
def icmd(self, args):
|
||||
"""
|
||||
Execute an icmd against a node.
|
||||
|
||||
:param list args: command arguments
|
||||
:return: command result
|
||||
:rtype: int
|
||||
"""
|
||||
return os.spawnlp(os.P_WAIT, VCMD, VCMD, "-c", self.ctrlchnlname, "--", *args)
|
||||
|
||||
def redircmd(self, infd, outfd, errfd, args, wait=True):
|
||||
"""
|
||||
Execute a command on a node with standard input, output, and
|
||||
error redirected according to the given file descriptors.
|
||||
|
||||
:param infd: stdin file descriptor
|
||||
:param outfd: stdout file descriptor
|
||||
:param errfd: stderr file descriptor
|
||||
:param list args: command arguments
|
||||
:param bool wait: wait flag
|
||||
:return: command status
|
||||
:rtype: int
|
||||
"""
|
||||
if not USE_VCMD_MODULE:
|
||||
raise NotImplementedError
|
||||
|
@ -113,6 +167,13 @@ class VnodeClient(object):
|
|||
return tmp
|
||||
|
||||
def term(self, sh="/bin/sh"):
|
||||
"""
|
||||
Open a terminal on a node.
|
||||
|
||||
:param str sh: shell to open terminal with
|
||||
:return: terminal command result
|
||||
:rtype: int
|
||||
"""
|
||||
cmd = ("xterm", "-ut", "-title", self.name, "-e",
|
||||
VCMD, "-c", self.ctrlchnlname, "--", sh)
|
||||
if "SUDO_USER" in os.environ:
|
||||
|
@ -122,12 +183,34 @@ class VnodeClient(object):
|
|||
return os.spawnvp(os.P_NOWAIT, cmd[0], cmd)
|
||||
|
||||
def termcmdstring(self, sh="/bin/sh"):
|
||||
"""
|
||||
Create a terminal command string.
|
||||
|
||||
:param str sh: shell to execute command in
|
||||
:return: str
|
||||
"""
|
||||
return "%s -c %s -- %s" % (VCMD, self.ctrlchnlname, sh)
|
||||
|
||||
def shcmd(self, cmdstr, sh="/bin/sh"):
|
||||
"""
|
||||
Execute a shell command.
|
||||
|
||||
:param str cmdstr: command string
|
||||
:param str sh: shell to run command in
|
||||
:return: command result
|
||||
:rtype: int
|
||||
"""
|
||||
return self.cmd([sh, "-c", cmdstr])
|
||||
|
||||
def getaddr(self, ifname, rescan=False):
|
||||
"""
|
||||
Get address for interface on node.
|
||||
|
||||
:param str ifname: interface name to get address for
|
||||
:param bool rescan: rescan flag
|
||||
:return: interface information
|
||||
:rtype: dict
|
||||
"""
|
||||
if ifname in self._addr and not rescan:
|
||||
return self._addr[ifname]
|
||||
tmp = {"ether": [], "inet": [], "inet6": [], "inet6link": []}
|
||||
|
@ -161,6 +244,13 @@ class VnodeClient(object):
|
|||
return tmp
|
||||
|
||||
def netifstats(self, ifname=None):
|
||||
"""
|
||||
Retrieve network interface state.
|
||||
|
||||
:param str ifname: name of interface to get state for
|
||||
:return: interface state information
|
||||
:rtype: dict
|
||||
"""
|
||||
stats = {}
|
||||
cmd = ["cat", "/proc/net/dev"]
|
||||
cmdid, cmdin, cmdout, cmderr = self.popen(cmd)
|
||||
|
@ -199,6 +289,15 @@ class VnodeClient(object):
|
|||
|
||||
|
||||
def createclients(sessiondir, clientcls=VnodeClient, cmdchnlfilterfunc=None):
|
||||
"""
|
||||
Create clients
|
||||
|
||||
:param str sessiondir: session directory to create clients
|
||||
:param class clientcls: class to create clients from
|
||||
:param func cmdchnlfilterfunc: command channel filter function
|
||||
:return: list of created clients
|
||||
:rtype: list
|
||||
"""
|
||||
direntries = map(lambda x: os.path.join(sessiondir, x), os.listdir(sessiondir))
|
||||
cmdchnls = filter(lambda x: stat.S_ISSOCK(os.stat(x).st_mode), direntries)
|
||||
if cmdchnlfilterfunc:
|
||||
|
@ -211,6 +310,12 @@ def createremoteclients(sessiondir, clientcls=VnodeClient, filterfunc=None):
|
|||
"""
|
||||
Creates remote VnodeClients, for nodes emulated on other machines. The
|
||||
session.Broker writes a n1.conf/server file having the server's info.
|
||||
|
||||
:param str sessiondir: session directory to create clients
|
||||
:param class clientcls: class to create clients from
|
||||
:param func filterfunc: filter function
|
||||
:return: list of remove clients
|
||||
:rtype: list
|
||||
"""
|
||||
direntries = map(lambda x: os.path.join(sessiondir, x), os.listdir(sessiondir))
|
||||
nodedirs = filter(lambda x: stat.S_ISDIR(os.stat(x).st_mode), direntries)
|
||||
|
|
Loading…
Reference in a new issue