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

@ -7,160 +7,177 @@
# authors: Tom Goff <thomas.goff@boeing.com>
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
core-daemon: the CORE daemon is a server process that receives CORE API
"""
core-daemon: the CORE daemon is a server process that receives CORE API
messages and instantiates emulated nodes and networks within the kernel. Various
message handlers are defined and some support for sending messages.
'''
"""
import os, optparse, ConfigParser, gc, shlex, socket, shutil
import ConfigParser
import atexit
import signal
import importlib
import logging
import optparse
import os
import signal
import socket
import sys
import threading
import time
try:
from core import pycore
except ImportError:
# hack for Fedora autoconf that uses the following pythondir:
if "/usr/lib/python2.6/site-packages" in sys.path:
sys.path.append("/usr/local/lib/python2.6/site-packages")
if "/usr/lib64/python2.6/site-packages" in sys.path:
sys.path.append("/usr/local/lib64/python2.6/site-packages")
if "/usr/lib/python2.7/site-packages" in sys.path:
sys.path.append("/usr/local/lib/python2.7/site-packages")
if "/usr/lib64/python2.7/site-packages" in sys.path:
sys.path.append("/usr/local/lib64/python2.7/site-packages")
from core import pycore
from core.coreserver import *
from core.constants import *
from core import constants
from core import corehandlers
from core import coreserver
from core import enumerations
from core.api import coreapi
from core.misc.utils import daemonize, closeonexec
from core.corehandlers import CoreDatagramRequestHandler
from core.enumerations import MessageFlags
from core.enumerations import RegisterTlvs
from core.misc import log
from core.misc import nodemaps
from core.misc import nodeutils
from core.misc.utils import closeonexec
from core.misc.utils import daemonize
from core.services import bird
from core.services import dockersvc
from core.services import nrl
from core.services import quagga
from core.services import security
from core.services import startup
from core.services import ucarp
from core.services import utility
from core.services import xorp
logger = log.get_logger(__name__)
DEFAULT_MAXFD = 1024
# garbage collection debugging
# gc.set_debug(gc.DEBUG_STATS | gc.DEBUG_LEAK)
def startudp(core_server, server_address):
"""
Start a thread running a UDP server on the same host,port for connectionless requests.
:param core.coreserver.CoreServer core_server: core server instance
:param tuple[str, int] server_address: server address
:return: created core udp server
:rtype: core.coreserver.CoreUdpServer
"""
core_server.udpserver = coreserver.CoreUdpServer(server_address, CoreDatagramRequestHandler, core_server)
core_server.udpthread = threading.Thread(target=core_server.udpserver.start)
core_server.udpthread.daemon = True
core_server.udpthread.start()
return core_server.udpserver
coreapi.add_node_class("CORE_NODE_DEF",
coreapi.CORE_NODE_DEF, pycore.nodes.CoreNode)
coreapi.add_node_class("CORE_NODE_PHYS",
coreapi.CORE_NODE_PHYS, pycore.pnodes.PhysicalNode)
try:
coreapi.add_node_class("CORE_NODE_XEN",
coreapi.CORE_NODE_XEN, pycore.xen.XenNode)
except Exception:
#print "XenNode class unavailable."
pass
coreapi.add_node_class("CORE_NODE_TBD",
coreapi.CORE_NODE_TBD, None)
coreapi.add_node_class("CORE_NODE_SWITCH",
coreapi.CORE_NODE_SWITCH, pycore.nodes.SwitchNode)
coreapi.add_node_class("CORE_NODE_HUB",
coreapi.CORE_NODE_HUB, pycore.nodes.HubNode)
coreapi.add_node_class("CORE_NODE_WLAN",
coreapi.CORE_NODE_WLAN, pycore.nodes.WlanNode)
coreapi.add_node_class("CORE_NODE_RJ45",
coreapi.CORE_NODE_RJ45, pycore.nodes.RJ45Node)
coreapi.add_node_class("CORE_NODE_TUNNEL",
coreapi.CORE_NODE_TUNNEL, pycore.nodes.TunnelNode)
coreapi.add_node_class("CORE_NODE_EMANE",
coreapi.CORE_NODE_EMANE, pycore.nodes.EmaneNode)
#
# UDP server startup
#
def startudp(mainserver, server_address):
''' Start a thread running a UDP server on the same host,port for
connectionless requests.
'''
mainserver.udpserver = CoreUdpServer(server_address,
CoreDatagramRequestHandler, mainserver)
mainserver.udpthread = threading.Thread(target = mainserver.udpserver.start)
mainserver.udpthread.daemon = True
mainserver.udpthread.start()
return mainserver.udpserver
#
# Auxiliary server startup
#
def startaux(mainserver, aux_address, aux_handler):
''' Start a thread running an auxiliary TCP server on the given address.
def startaux(core_server, aux_address, aux_handler):
"""
Start a thread running an auxiliary TCP server on the given address.
This server will communicate with client requests using a handler
using the aux_handler class. The aux_handler can provide an alternative
using the aux_handler class. The aux_handler can provide an alternative
API to CORE.
'''
handlermodname,dot,handlerclassname = aux_handler.rpartition('.')
:param core.coreserver.CoreServer core_server: core server instance
:param tuple[str, int] aux_address: auxiliary server address
:param str aux_handler: auxiliary handler string to import
:return: auxiliary server
"""
handlermodname, dot, handlerclassname = aux_handler.rpartition(".")
handlermod = importlib.import_module(handlermodname)
handlerclass = getattr(handlermod, handlerclassname)
mainserver.auxserver = CoreAuxServer(aux_address,
handlerclass,
mainserver)
mainserver.auxthread = threading.Thread(target = mainserver.auxserver.start)
mainserver.auxthread.daemon = True
mainserver.auxthread.start()
return mainserver.auxserver
core_server.auxserver = coreserver.CoreAuxServer(aux_address, handlerclass, core_server)
core_server.auxthread = threading.Thread(target=core_server.auxserver.start)
core_server.auxthread.daemon = True
core_server.auxthread.start()
return core_server.auxserver
def banner():
''' Output the program banner printed to the terminal or log file.
'''
sys.stdout.write("CORE daemon v.%s started %s\n" % \
(COREDPY_VERSION, time.ctime()))
sys.stdout.flush()
"""
Output the program banner printed to the terminal or log file.
:return: nothing
"""
logger.info("CORE daemon v.%s started %s\n" % (constants.COREDPY_VERSION, time.ctime()))
def cored(cfg = None):
''' Start the CoreServer object and enter the server loop.
'''
host = cfg['listenaddr']
port = int(cfg['port'])
if host == '' or host is None:
def cored(cfg=None):
"""
Start the CoreServer object and enter the server loop.
:param dict cfg: core configuration
:return: nothing
"""
host = cfg["listenaddr"]
port = int(cfg["port"])
if host == "" or host is None:
host = "localhost"
try:
server = CoreServer((host, port), CoreRequestHandler, cfg)
except Exception, e:
sys.stderr.write("error starting main server on: %s:%s\n\t%s\n" % \
(host, port, e))
sys.stderr.flush()
server = coreserver.CoreServer((host, port), corehandlers.CoreRequestHandler, cfg)
except:
logger.exception("error starting main server on: %s:%s", host, port)
sys.exit(1)
closeonexec(server.fileno())
sys.stdout.write("main server started, listening on: %s:%s\n" % (host, port))
sys.stdout.flush()
udpserver = startudp(server, (host,port))
closeonexec(server.fileno())
logger.info("main server started, listening on: %s:%s\n" % (host, port))
udpserver = startudp(server, (host, port))
closeonexec(udpserver.fileno())
auxreqhandler = cfg['aux_request_handler']
auxreqhandler = cfg["aux_request_handler"]
if auxreqhandler:
try:
handler, auxport = auxreqhandler.rsplit(':')
auxserver = startaux(server, (host,int(auxport)), handler)
closeonexec(auxserver.fileno())
except Exception as e:
raise ValueError, "invalid auxreqhandler:(%s)\nError: %s" % (auxreqhandler, e)
handler, auxport = auxreqhandler.rsplit(":")
auxserver = startaux(server, (host, int(auxport)), handler)
closeonexec(auxserver.fileno())
server.serve_forever()
# TODO: should sessions and the main core daemon both catch at exist to shutdown independently?
def cleanup():
while CoreServer.servers:
server = CoreServer.servers.pop()
"""
Runs server shutdown and cleanup when catching an exit signal.
:return: nothing
"""
while coreserver.CoreServer.servers:
server = coreserver.CoreServer.servers.pop()
server.shutdown()
atexit.register(cleanup)
def sighandler(signum, stackframe):
print >> sys.stderr, "terminated by signal:", signum
"""
Signal handler when different signals are sent.
:param int signum: singal number sent
:param stackframe: stack frame sent
:return: nothing
"""
logger.error("terminated by signal: %s", signum)
sys.exit(signum)
signal.signal(signal.SIGHUP, sighandler)
signal.signal(signal.SIGINT, sighandler)
signal.signal(signal.SIGTERM, sighandler)
signal.signal(signal.SIGUSR1, sighandler)
signal.signal(signal.SIGUSR2, sighandler)
def logrotate(stdout, stderr, stdoutmode = 0644, stderrmode = 0644):
def logrotate(stdout, stderr, stdoutmode=0644, stderrmode=0644):
"""
Log rotation method.
:param stdout: stdout
:param stderr: stderr
:param int stdoutmode: stdout mode
:param int stderrmode: stderr mode
:return:
"""
def reopen(fileno, filename, mode):
err = 0
fd = -1
@ -174,6 +191,7 @@ def logrotate(stdout, stderr, stdoutmode = 0644, stderrmode = 0644):
if fd >= 0:
os.close(fd)
return err
if stdout:
err = reopen(1, stdout, stdoutmode)
if stderr:
@ -185,59 +203,64 @@ def logrotate(stdout, stderr, stdoutmode = 0644, stderrmode = 0644):
else:
reopen(2, stderr, stderrmode)
def getMergedConfig(filename):
''' Return a configuration after merging config file and command-line
arguments.
'''
def get_merged_config(filename):
"""
Return a configuration after merging config file and command-line arguments.
:param str filename: file name to merge configuration settings with
:return: merged configuration
:rtype: dict
"""
# these are the defaults used in the config file
defaults = { 'port' : '%d' % coreapi.CORE_API_PORT,
'listenaddr' : 'localhost',
'pidfile' : '%s/run/core-daemon.pid' % CORE_STATE_DIR,
'logfile' : '%s/log/core-daemon.log' % CORE_STATE_DIR,
'xmlfilever' : '1.0',
'numthreads' : '1',
'verbose' : 'False',
'daemonize' : 'False',
'debug' : 'False',
'execfile' : None,
'aux_request_handler' : None,
}
defaults = {"port": "%d" % enumerations.CORE_API_PORT,
"listenaddr": "localhost",
"pidfile": "%s/run/core-daemon.pid" % constants.CORE_STATE_DIR,
"logfile": "%s/log/core-daemon.log" % constants.CORE_STATE_DIR,
"xmlfilever": "1.0",
"numthreads": "1",
"verbose": "False",
"daemonize": "False",
"debug": "False",
"execfile": None,
"aux_request_handler": None,
}
usagestr = "usage: %prog [-h] [options] [args]\n\n" + \
"CORE daemon v.%s instantiates Linux network namespace " \
"nodes." % COREDPY_VERSION
parser = optparse.OptionParser(usage = usagestr)
parser.add_option("-f", "--configfile", dest = "configfile",
type = "string",
help = "read config from specified file; default = %s" %
filename)
parser.add_option("-d", "--daemonize", dest = "daemonize",
"nodes." % constants.COREDPY_VERSION
parser = optparse.OptionParser(usage=usagestr)
parser.add_option("-f", "--configfile", dest="configfile",
type="string",
help="read config from specified file; default = %s" %
filename)
parser.add_option("-d", "--daemonize", dest="daemonize",
action="store_true",
help = "run in background as daemon; default=%s" % \
defaults["daemonize"])
parser.add_option("-e", "--execute", dest = "execfile", type = "string",
help = "execute a Python/XML-based session")
parser.add_option("-l", "--logfile", dest = "logfile", type = "string",
help = "log output to specified file; default = %s" %
defaults["logfile"])
parser.add_option("-p", "--port", dest = "port", type = int,
help = "port number to listen on; default = %s" % \
defaults["port"])
parser.add_option("-i", "--pidfile", dest = "pidfile",
help = "filename to write pid to; default = %s" % \
defaults["pidfile"])
parser.add_option("-t", "--numthreads", dest = "numthreads", type = int,
help = "number of server threads; default = %s" % \
defaults["numthreads"])
parser.add_option("-v", "--verbose", dest = "verbose", action="store_true",
help = "enable verbose logging; default = %s" % \
defaults["verbose"])
parser.add_option("-g", "--debug", dest = "debug", action="store_true",
help = "enable debug logging; default = %s" % \
defaults["debug"])
help="run in background as daemon; default=%s" % \
defaults["daemonize"])
parser.add_option("-e", "--execute", dest="execfile", type="string",
help="execute a Python/XML-based session")
parser.add_option("-l", "--logfile", dest="logfile", type="string",
help="log output to specified file; default = %s" %
defaults["logfile"])
parser.add_option("-p", "--port", dest="port", type=int,
help="port number to listen on; default = %s" % \
defaults["port"])
parser.add_option("-i", "--pidfile", dest="pidfile",
help="filename to write pid to; default = %s" % \
defaults["pidfile"])
parser.add_option("-t", "--numthreads", dest="numthreads", type=int,
help="number of server threads; default = %s" % \
defaults["numthreads"])
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
help="enable verbose logging; default = %s" % \
defaults["verbose"])
parser.add_option("-g", "--debug", dest="debug", action="store_true",
help="enable debug logging; default = %s" % \
defaults["debug"])
# parse command line options
(options, args) = parser.parse_args()
options, args = parser.parse_args()
# read the config file
if options.configfile is not None:
@ -252,17 +275,17 @@ def getMergedConfig(filename):
# gracefully support legacy configs (cored.py/cored now core-daemon)
if cfg.has_section("cored.py"):
for name, val in cfg.items("cored.py"):
if name == 'pidfile' or name == 'logfile':
bn = os.path.basename(val).replace('coredpy', 'core-daemon')
if name == "pidfile" or name == "logfile":
bn = os.path.basename(val).replace("coredpy", "core-daemon")
val = os.path.join(os.path.dirname(val), bn)
cfg.set(section, name, val)
if cfg.has_section("cored"):
for name, val in cfg.items("cored"):
if name == 'pidfile' or name == 'logfile':
bn = os.path.basename(val).replace('cored', 'core-daemon')
if name == "pidfile" or name == "logfile":
bn = os.path.basename(val).replace("cored", "core-daemon")
val = os.path.join(os.path.dirname(val), bn)
cfg.set(section, name, val)
# merge command line with config file
for opt in options.__dict__:
val = options.__dict__[opt]
@ -271,49 +294,74 @@ def getMergedConfig(filename):
return dict(cfg.items(section)), args
def exec_file(cfg):
''' Send a Register Message to execute a new session based on XML or Python
script file.
'''
filename = cfg['execfile']
sys.stdout.write("Telling daemon to execute file: '%s'...\n" % filename)
sys.stdout.flush()
tlvdata = coreapi.CoreRegTlv.pack(coreapi.CORE_TLV_REG_EXECSRV, filename)
msg = coreapi.CoreRegMessage.pack(coreapi.CORE_API_ADD_FLAG, tlvdata)
"""
Send a Register Message to execute a new session based on XML or Python script file.
:param dict cfg: configuration settings
:return: 0
"""
filename = cfg["execfile"]
logger.info("Telling daemon to execute file: %s...", filename)
tlvdata = coreapi.CoreRegisterTlv.pack(RegisterTlvs.EXECUTE_SERVER.value, filename)
msg = coreapi.CoreRegMessage.pack(MessageFlags.ADD.value, tlvdata)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(("localhost", int(cfg['port']))) # TODO: connect address option
sock.connect(("localhost", int(cfg["port"]))) # TODO: connect address option
sock.sendall(msg)
return 0
def main():
''' Main program startup.
'''
# get a configuration merged from config file and command-line arguments
cfg, args = getMergedConfig("%s/core.conf" % CORE_CONF_DIR)
for a in args:
sys.stderr.write("ignoring command line argument: '%s'\n" % a)
if cfg['daemonize'] == 'True':
daemonize(rootdir = None, umask = 0, close_fds = False,
stdin = os.devnull,
stdout = cfg['logfile'], stderr = cfg['logfile'],
pidfilename = cfg['pidfile'],
defaultmaxfd = DEFAULT_MAXFD)
def main():
"""
Main program startup.
:return: nothing
"""
# get a configuration merged from config file and command-line arguments
cfg, args = get_merged_config("%s/core.conf" % constants.CORE_CONF_DIR)
for a in args:
logger.error("ignoring command line argument: %s", a)
if cfg["daemonize"] == "True":
daemonize(rootdir=None, umask=0, close_fds=False,
stdin=os.devnull,
stdout=cfg["logfile"], stderr=cfg["logfile"],
pidfilename=cfg["pidfile"],
defaultmaxfd=DEFAULT_MAXFD)
signal.signal(signal.SIGUSR1, lambda signum, stackframe:
logrotate(stdout = cfg['logfile'],
stderr = cfg['logfile']))
logrotate(stdout=cfg["logfile"], stderr=cfg["logfile"]))
banner()
if cfg['execfile']:
cfg['execfile'] = os.path.abspath(cfg['execfile'])
if cfg["execfile"]:
cfg["execfile"] = os.path.abspath(cfg["execfile"])
sys.exit(exec_file(cfg))
try:
cored(cfg)
except KeyboardInterrupt:
pass
logger.info("keyboard interrupt, stopping core daemon")
sys.exit(0)
if __name__ == "__main__":
log.setup(level=logging.INFO)
# configure nodes to use
node_map = nodemaps.CLASSIC_NODES
if len(sys.argv) == 2 and sys.argv[1] == "ovs":
node_map = nodemaps.OVS_NODES
nodeutils.set_node_map(node_map)
# load default services
quagga.load_services()
nrl.load_services()
xorp.load_services()
bird.load_services()
utility.load_services()
security.load_services()
ucarp.load_services()
dockersvc.load_services()
startup.load_services()
main()