changes to have DockerNode based off a CoreNode instead, elminating the need for a lot of boiler plate

This commit is contained in:
Blake Harnden 2019-06-28 08:17:11 -07:00
parent 9825706e03
commit 5971950523
4 changed files with 17 additions and 340 deletions

View file

@ -1,16 +1,10 @@
import logging import logging
import os import os
import random
import shutil
import string
import threading import threading
from core import utils, CoreCommandError, constants from core import utils, CoreCommandError
from core.emulator.enumerations import NodeTypes from core.emulator.enumerations import NodeTypes
from core.nodes.base import CoreNodeBase from core.nodes.base import CoreNode
from core.nodes.interface import Veth, TunTap, CoreInterface
_DEFAULT_MTU = 1500
class DockerClient(object): class DockerClient(object):
@ -24,8 +18,10 @@ class DockerClient(object):
name=self.name, name=self.name,
image=image image=image
)) ))
self.pid = self.get_pid()
return self.pid
def is_container_alive(self): def is_alive(self):
status, output = utils.cmd_output("docker containers ls -f name={name}".format( status, output = utils.cmd_output("docker containers ls -f name={name}".format(
name=self.name name=self.name
)) ))
@ -47,13 +43,10 @@ class DockerClient(object):
def ns_cmd(self, cmd): def ns_cmd(self, cmd):
if isinstance(cmd, list): if isinstance(cmd, list):
cmd = " ".join(cmd) cmd = " ".join(cmd)
status, output = utils.cmd_output("nsenter -t {pid} -m -u -i -p -n {cmd}".format( return utils.cmd_output("nsenter -t {pid} -m -u -i -p -n {cmd}".format(
pid=self.pid, pid=self.pid,
cmd=cmd cmd=cmd
)) ))
if status:
raise CoreCommandError(status, output)
return output
def get_pid(self): def get_pid(self):
status, output = utils.cmd_output("docker inspect -f '{{{{.State.Pid}}}}' {name}".format(name=self.name)) status, output = utils.cmd_output("docker inspect -f '{{{{.State.Pid}}}}' {name}".format(name=self.name))
@ -98,7 +91,7 @@ class DockerClient(object):
return interface return interface
class DockerNode(CoreNodeBase): class DockerNode(CoreNode):
apitype = NodeTypes.DOCKER.value apitype = NodeTypes.DOCKER.value
valid_address_types = {"inet", "inet6", "inet6link"} valid_address_types = {"inet", "inet6", "inet6link"}
@ -113,7 +106,7 @@ class DockerNode(CoreNodeBase):
:param str bootsh: boot shell to use :param str bootsh: boot shell to use
:param bool start: start flag :param bool start: start flag
""" """
super(CoreNodeBase, self).__init__(session, _id, name, start=start) super(CoreNode, self).__init__(session, _id, name, start=start)
self.nodedir = nodedir self.nodedir = nodedir
self.ctrlchnlname = os.path.abspath(os.path.join(self.session.session_dir, self.name)) self.ctrlchnlname = os.path.abspath(os.path.join(self.session.session_dir, self.name))
self.client = DockerClient(self.name) self.client = DockerClient(self.name)
@ -122,6 +115,7 @@ class DockerNode(CoreNodeBase):
self.lock = threading.RLock() self.lock = threading.RLock()
self._mounts = [] self._mounts = []
self.bootsh = bootsh self.bootsh = bootsh
logging.debug("docker services: %s", self.services)
if start: if start:
self.startup() self.startup()
@ -132,7 +126,7 @@ class DockerNode(CoreNodeBase):
:return: True if node is alive, False otherwise :return: True if node is alive, False otherwise
:rtype: bool :rtype: bool
""" """
return self.client.is_container_alive() return self.client.is_alive()
def startup(self): def startup(self):
""" """
@ -145,8 +139,7 @@ class DockerNode(CoreNodeBase):
with self.lock: with self.lock:
if self.up: if self.up:
raise ValueError("starting a node that is already up") raise ValueError("starting a node that is already up")
self.client.create_container("ubuntu:ifconfig") self.pid = self.client.create_container("ubuntu:ifconfig")
self.pid = self.client.get_pid()
self.up = True self.up = True
def shutdown(self): def shutdown(self):
@ -173,7 +166,7 @@ class DockerNode(CoreNodeBase):
:return: exit status for command :return: exit status for command
:rtype: int :rtype: int
""" """
status, _ = self.client.run_cmd(args) status, _ = self.client.ns_cmd(args)
return status return status
def cmd_output(self, args): def cmd_output(self, args):
@ -184,7 +177,7 @@ class DockerNode(CoreNodeBase):
:return: exit status and combined stdout and stderr :return: exit status and combined stdout and stderr
:rtype: tuple[int, str] :rtype: tuple[int, str]
""" """
return self.client.run_cmd(args) return self.client.ns_cmd(args)
def check_cmd(self, args): def check_cmd(self, args):
""" """
@ -195,7 +188,7 @@ class DockerNode(CoreNodeBase):
:rtype: str :rtype: str
:raises CoreCommandError: when a non-zero exit status occurs :raises CoreCommandError: when a non-zero exit status occurs
""" """
status, output = self.client.run_cmd(args) status, output = self.client.ns_cmd(args)
if status: if status:
raise CoreCommandError(status, output) raise CoreCommandError(status, output)
return output return output
@ -228,322 +221,3 @@ class DockerNode(CoreNodeBase):
:raises CoreCommandError: when a non-zero exit status occurs :raises CoreCommandError: when a non-zero exit status occurs
""" """
pass pass
def newifindex(self):
"""
Retrieve a new interface index.
:return: new interface index
:rtype: int
"""
with self.lock:
return super(DockerNode, 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 core.nodes.base.CoreNetworkBase net: network to associate interface with
:return: nothing
"""
with self.lock:
if ifindex is None:
ifindex = self.newifindex()
if ifname is None:
ifname = "eth%d" % ifindex
sessionid = self.session.short_session_id()
try:
suffix = "%x.%s.%s" % (self.id, ifindex, sessionid)
except TypeError:
suffix = "%s.%s.%s" % (self.id, ifindex, sessionid)
localname = "veth" + suffix
if len(localname) >= 16:
raise ValueError("interface local name (%s) too long" % localname)
name = localname + "p"
if len(name) >= 16:
raise ValueError("interface name (%s) too long" % name)
veth = Veth(node=self, name=name, localname=localname, net=net, start=self.up)
if self.up:
utils.check_cmd([constants.IP_BIN, "link", "set", veth.name, "netns", str(self.pid)])
self.client.ns_cmd(["ip", "link", "set", veth.name, "name", ifname])
self.client.ns_cmd(["ethtool", "-K", ifname, "rx", "off", "tx", "off"])
veth.name = ifname
if self.up:
# TODO: potentially find better way to query interface ID
# retrieve interface information
output = self.client.ns_cmd([constants.IP_BIN, "link", "show", veth.name])
logging.debug("interface command output: %s", output)
output = output.split("\n")
veth.flow_id = int(output[0].strip().split(":")[0]) + 1
logging.debug("interface flow index: %s - %s", veth.name, veth.flow_id)
# TODO: mimic packed hwaddr
# veth.hwaddr = MacAddress.from_string(output[1].strip().split()[1])
logging.debug("interface mac: %s - %s", veth.name, veth.hwaddr)
try:
self.addnetif(veth, ifindex)
except ValueError as e:
veth.shutdown()
del veth
raise e
return ifindex
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
"""
with self.lock:
if ifindex is None:
ifindex = self.newifindex()
if ifname is None:
ifname = "eth%d" % ifindex
sessionid = self.session.short_session_id()
localname = "tap%s.%s.%s" % (self.id, ifindex, sessionid)
name = ifname
tuntap = TunTap(node=self, name=name, localname=localname, net=net, start=self.up)
try:
self.addnetif(tuntap, ifindex)
except ValueError as e:
tuntap.shutdown()
del tuntap
raise e
return ifindex
def sethwaddr(self, ifindex, addr):
"""
Set hardware addres for an interface.
:param int ifindex: index of interface to set hardware address for
:param core.nodes.ipaddress.MacAddress addr: hardware address to set
:return: nothing
:raises CoreCommandError: when a non-zero exit status occurs
"""
self._netif[ifindex].sethwaddr(addr)
if self.up:
args = ["ip", "link", "set", "dev", self.ifname(ifindex), "address", str(addr)]
self.client.ns_cmd(args)
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:
# check if addr is ipv6
if ":" in str(addr):
args = ["ip", "addr", "add", str(addr), "dev", self.ifname(ifindex)]
self.client.ns_cmd(args)
else:
args = ["ip", "addr", "add", str(addr), "broadcast", "+", "dev", self.ifname(ifindex)]
self.client.ns_cmd(args)
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
:raises CoreCommandError: when a non-zero exit status occurs
"""
try:
self._netif[ifindex].deladdr(addr)
except ValueError:
logging.exception("trying to delete unknown address: %s" % addr)
if self.up:
self.check_cmd(["ip", "addr", "del", str(addr), "dev", self.ifname(ifindex)])
def delalladdr(self, ifindex, address_types=None):
"""
Delete all addresses from an interface.
:param int ifindex: index of interface to delete address types from
:param tuple[str] address_types: address types to delete
:return: nothing
:raises CoreCommandError: when a non-zero exit status occurs
"""
if not address_types:
address_types = self.valid_address_types
interface_name = self.ifname(ifindex)
addresses = self.client.getaddr(interface_name, rescan=True)
for address_type in address_types:
if address_type not in self.valid_address_types:
raise ValueError("addr type must be in: %s" % " ".join(self.valid_address_types))
for address in addresses[address_type]:
self.deladdr(ifindex, address)
# update cached information
self.client.getaddr(interface_name, 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.check_cmd(["ip", "link", "set", self.ifname(ifindex), "up"])
self.client.ns_cmd(["ip", "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 core.nodes.base.CoreNetworkBase net: network to associate with
:param list addrlist: addresses to add on the interface
:param core.nodes.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
"""
if not addrlist:
addrlist = []
with self.lock:
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 address in utils.make_tuple(addrlist):
self.addaddr(ifindex, address)
self.ifup(ifindex)
return ifindex
def connectnode(self, ifname, othernode, otherifname):
"""
Connect a node.
:param str ifname: name of interface to connect
:param core.nodes.CoreNodeBase 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 _ in range(tmplen)])
tmp2 = "tmp." + "".join([random.choice(string.ascii_lowercase) for _ in range(tmplen)])
utils.check_cmd([constants.IP_BIN, "link", "add", "name", tmp1, "type", "veth", "peer", "name", tmp2])
utils.check_cmd([constants.IP_BIN, "link", "set", tmp1, "netns", str(self.pid)])
self.check_cmd(["ip", "link", "set", tmp1, "name", ifname])
interface = CoreInterface(node=self, name=ifname, mtu=_DEFAULT_MTU)
self.addnetif(interface, self.newifindex())
utils.check_cmd([constants.IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid)])
othernode.check_cmd([constants.IP_BIN, "link", "set", tmp2, "name", otherifname])
other_interface = CoreInterface(node=othernode, name=otherifname, mtu=_DEFAULT_MTU)
othernode.addnetif(other_interface, othernode.newifindex())
def addfile(self, srcname, filename):
"""
Add a file.
:param str srcname: source file name
:param str filename: file name to add
:return: nothing
:raises CoreCommandError: when a non-zero exit status occurs
"""
logging.info("adding file from %s to %s", srcname, filename)
directory = os.path.dirname(filename)
cmd = 'mkdir -p "%s" && mv "%s" "%s" && sync' % (directory, srcname, filename)
status, output = self.client.run_cmd(cmd)
if status:
raise CoreCommandError(status, cmd, output)
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: %s" % 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"):
"""
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=0o755)
return open(hostfilename, mode)
def nodefile(self, filename, contents, mode=0o644):
"""
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
"""
with self.opennodefile(filename, "w") as open_file:
open_file.write(contents)
os.chmod(open_file.name, mode)
logging.info("node(%s) added file: %s; mode: 0%o", self.name, open_file.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)
logging.info("node(%s) copied file: %s; mode: %s", self.name, hostfilename, mode)

View file

@ -26,6 +26,7 @@ if __name__ == "__main__":
print(node_one.cmd_output("ifconfig")) print(node_one.cmd_output("ifconfig"))
print(node_two.cmd_output("ifconfig")) print(node_two.cmd_output("ifconfig"))
input("press key to continue") input("press key to continue")
session.instantiate()
finally: finally:
input("continue to shutdown") input("continue to shutdown")
coreemu.shutdown() coreemu.shutdown()

View file

@ -26,6 +26,7 @@ if __name__ == "__main__":
print(node_one.cmd_output("ifconfig")) print(node_one.cmd_output("ifconfig"))
print(node_two.cmd_output("ifconfig")) print(node_two.cmd_output("ifconfig"))
input("press key to continue") input("press key to continue")
session.instantiate()
finally: finally:
input("continue to shutdown") input("continue to shutdown")
coreemu.shutdown() coreemu.shutdown()

View file

@ -31,6 +31,7 @@ if __name__ == "__main__":
print(node_one.cmd_output("ifconfig")) print(node_one.cmd_output("ifconfig"))
print(node_two.cmd_output("ifconfig")) print(node_two.cmd_output("ifconfig"))
input("press key to continue") input("press key to continue")
session.instantiate()
finally: finally:
input("continue to shutdown") input("continue to shutdown")
coreemu.shutdown() coreemu.shutdown()