2013-08-29 15:21:13 +01:00
|
|
|
#!/usr/bin/env python
|
|
|
|
#
|
|
|
|
# CORE
|
|
|
|
# Copyright (c)2010-2013 the Boeing Company.
|
|
|
|
# See the LICENSE file included in this distribution.
|
|
|
|
#
|
|
|
|
# authors: Tom Goff <thomas.goff@boeing.com>
|
|
|
|
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
|
|
|
|
#
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
"""
|
|
|
|
core-daemon: the CORE daemon is a server process that receives CORE API
|
2013-08-29 15:21:13 +01:00
|
|
|
messages and instantiates emulated nodes and networks within the kernel. Various
|
|
|
|
message handlers are defined and some support for sending messages.
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
import ConfigParser
|
2013-08-29 15:21:13 +01:00
|
|
|
import atexit
|
2016-09-06 23:46:54 +01:00
|
|
|
import importlib
|
2017-04-25 16:45:34 +01:00
|
|
|
import logging
|
|
|
|
import optparse
|
|
|
|
import os
|
|
|
|
import signal
|
|
|
|
import socket
|
|
|
|
import sys
|
|
|
|
import threading
|
|
|
|
import time
|
|
|
|
|
|
|
|
from core import constants
|
|
|
|
from core import corehandlers
|
|
|
|
from core import coreserver
|
|
|
|
from core import enumerations
|
2016-08-12 19:58:30 +01:00
|
|
|
from core.api import coreapi
|
2017-04-25 16:45:34 +01:00
|
|
|
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
|
2013-08-29 15:21:13 +01:00
|
|
|
|
|
|
|
DEFAULT_MAXFD = 1024
|
|
|
|
|
|
|
|
|
2016-09-15 01:15:16 +01:00
|
|
|
#
|
|
|
|
# UDP server startup
|
2017-04-25 16:45:34 +01:00
|
|
|
#
|
2016-09-06 23:46:54 +01:00
|
|
|
def startudp(mainserver, server_address):
|
2017-04-25 16:45:34 +01:00
|
|
|
""" Start a thread running a UDP server on the same host,port for
|
2016-09-06 23:46:54 +01:00
|
|
|
connectionless requests.
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
mainserver.udpserver = coreserver.CoreUdpServer(
|
|
|
|
server_address,
|
|
|
|
corehandlers.CoreDatagramRequestHandler,
|
|
|
|
mainserver)
|
|
|
|
mainserver.udpthread = threading.Thread(target=mainserver.udpserver.start)
|
2016-09-06 23:46:54 +01:00
|
|
|
mainserver.udpthread.daemon = True
|
|
|
|
mainserver.udpthread.start()
|
|
|
|
return mainserver.udpserver
|
2013-08-29 15:21:13 +01:00
|
|
|
|
|
|
|
|
2016-09-06 23:46:54 +01:00
|
|
|
#
|
|
|
|
# Auxiliary server startup
|
2017-04-25 16:45:34 +01:00
|
|
|
#
|
2016-09-06 23:46:54 +01:00
|
|
|
def startaux(mainserver, aux_address, aux_handler):
|
2017-04-25 16:45:34 +01:00
|
|
|
""" Start a thread running an auxiliary TCP server on the given address.
|
2016-09-06 23:46:54 +01:00
|
|
|
This server will communicate with client requests using a handler
|
2017-04-25 16:45:34 +01:00
|
|
|
using the aux_handler class. The aux_handler can provide an alternative
|
2016-09-06 23:46:54 +01:00
|
|
|
API to CORE.
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
handlermodname, dot, handlerclassname = aux_handler.rpartition(".")
|
2016-09-06 23:46:54 +01:00
|
|
|
handlermod = importlib.import_module(handlermodname)
|
|
|
|
handlerclass = getattr(handlermod, handlerclassname)
|
2017-04-25 16:45:34 +01:00
|
|
|
mainserver.auxserver = coreserver.CoreAuxServer(aux_address, handlerclass, mainserver)
|
|
|
|
mainserver.auxthread = threading.Thread(target=mainserver.auxserver.start)
|
2016-09-06 23:46:54 +01:00
|
|
|
mainserver.auxthread.daemon = True
|
|
|
|
mainserver.auxthread.start()
|
|
|
|
return mainserver.auxserver
|
2016-07-27 01:54:11 +01:00
|
|
|
|
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def banner():
|
2017-04-25 16:45:34 +01:00
|
|
|
""" Output the program banner printed to the terminal or log file.
|
|
|
|
"""
|
|
|
|
sys.stdout.write("CORE daemon v.%s started %s\n" % (constants.COREDPY_VERSION, time.ctime()))
|
2013-08-29 15:21:13 +01:00
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
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:
|
2013-08-29 15:21:13 +01:00
|
|
|
host = "localhost"
|
|
|
|
try:
|
2017-04-25 16:45:34 +01:00
|
|
|
server = coreserver.CoreServer((host, port), corehandlers.CoreRequestHandler, cfg)
|
2013-08-29 15:21:13 +01:00
|
|
|
except Exception, e:
|
2017-04-25 16:45:34 +01:00
|
|
|
sys.stderr.write("error starting main server on: %s:%s\n\t%s\n" % (host, port, e))
|
2013-08-29 15:21:13 +01:00
|
|
|
sys.stderr.flush()
|
|
|
|
sys.exit(1)
|
|
|
|
closeonexec(server.fileno())
|
2016-09-06 23:48:41 +01:00
|
|
|
sys.stdout.write("main server started, listening on: %s:%s\n" % (host, port))
|
2013-08-29 15:21:13 +01:00
|
|
|
sys.stdout.flush()
|
2016-09-15 01:15:16 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
udpserver = startudp(server, (host, port))
|
2016-09-06 23:46:54 +01:00
|
|
|
closeonexec(udpserver.fileno())
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
auxreqhandler = cfg["aux_request_handler"]
|
2016-09-06 23:46:54 +01:00
|
|
|
if auxreqhandler:
|
|
|
|
try:
|
2017-04-25 16:45:34 +01:00
|
|
|
handler, auxport = auxreqhandler.rsplit(":")
|
|
|
|
auxserver = startaux(server, (host, int(auxport)), handler)
|
2016-09-06 23:46:54 +01:00
|
|
|
closeonexec(auxserver.fileno())
|
|
|
|
except Exception as e:
|
|
|
|
raise ValueError, "invalid auxreqhandler:(%s)\nError: %s" % (auxreqhandler, e)
|
2016-07-27 01:54:11 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
server.serve_forever()
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def cleanup():
|
2017-04-25 16:45:34 +01:00
|
|
|
while coreserver.CoreServer.servers:
|
|
|
|
server = coreserver.CoreServer.servers.pop()
|
2013-08-29 15:21:13 +01:00
|
|
|
server.shutdown()
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
atexit.register(cleanup)
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def sighandler(signum, stackframe):
|
|
|
|
print >> sys.stderr, "terminated by signal:", signum
|
|
|
|
sys.exit(signum)
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2015-12-29 18:50:05 +00:00
|
|
|
signal.signal(signal.SIGHUP, sighandler)
|
|
|
|
signal.signal(signal.SIGINT, sighandler)
|
|
|
|
signal.signal(signal.SIGPIPE, sighandler)
|
2013-08-29 15:21:13 +01:00
|
|
|
signal.signal(signal.SIGTERM, sighandler)
|
2015-12-29 18:50:05 +00:00
|
|
|
signal.signal(signal.SIGUSR1, sighandler)
|
|
|
|
signal.signal(signal.SIGUSR2, sighandler)
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
def logrotate(stdout, stderr, stdoutmode=0644, stderrmode=0644):
|
2015-12-29 18:50:18 +00:00
|
|
|
def reopen(fileno, filename, mode):
|
|
|
|
err = 0
|
|
|
|
fd = -1
|
|
|
|
try:
|
|
|
|
fd = os.open(filename,
|
|
|
|
os.O_WRONLY | os.O_CREAT | os.O_APPEND, mode)
|
|
|
|
os.dup2(fd, fileno)
|
|
|
|
except OSError as e:
|
|
|
|
err = e.errno
|
|
|
|
finally:
|
|
|
|
if fd >= 0:
|
|
|
|
os.close(fd)
|
|
|
|
return err
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2015-12-29 18:50:18 +00:00
|
|
|
if stdout:
|
|
|
|
err = reopen(1, stdout, stdoutmode)
|
|
|
|
if stderr:
|
|
|
|
if stderr == stdout and not err:
|
|
|
|
try:
|
|
|
|
os.dup2(1, 2)
|
|
|
|
except OSError as e:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
reopen(2, stderr, stderrmode)
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
def get_merged_config(filename):
|
|
|
|
""" Return a configuration after merging config file and command-line
|
2013-08-29 15:21:13 +01:00
|
|
|
arguments.
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
# these are the defaults used in the config file
|
2017-04-25 16:45:34 +01:00
|
|
|
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,
|
|
|
|
}
|
2013-08-29 15:21:13 +01:00
|
|
|
|
|
|
|
usagestr = "usage: %prog [-h] [options] [args]\n\n" + \
|
|
|
|
"CORE daemon v.%s instantiates Linux network namespace " \
|
2017-04-25 16:45:34 +01:00
|
|
|
"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",
|
2013-08-29 15:21:13 +01:00
|
|
|
action="store_true",
|
2017-04-25 16:45:34 +01:00
|
|
|
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"])
|
2013-08-29 15:21:13 +01:00
|
|
|
|
|
|
|
# parse command line options
|
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
|
|
|
|
# read the config file
|
|
|
|
if options.configfile is not None:
|
|
|
|
filename = options.configfile
|
|
|
|
del options.configfile
|
|
|
|
cfg = ConfigParser.SafeConfigParser(defaults)
|
|
|
|
cfg.read(filename)
|
|
|
|
|
|
|
|
section = "core-daemon"
|
|
|
|
if not cfg.has_section(section):
|
|
|
|
cfg.add_section(section)
|
|
|
|
# gracefully support legacy configs (cored.py/cored now core-daemon)
|
|
|
|
if cfg.has_section("cored.py"):
|
|
|
|
for name, val in cfg.items("cored.py"):
|
2017-04-25 16:45:34 +01:00
|
|
|
if name == "pidfile" or name == "logfile":
|
|
|
|
bn = os.path.basename(val).replace("coredpy", "core-daemon")
|
2013-08-29 15:21:13 +01:00
|
|
|
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"):
|
2017-04-25 16:45:34 +01:00
|
|
|
if name == "pidfile" or name == "logfile":
|
|
|
|
bn = os.path.basename(val).replace("cored", "core-daemon")
|
2013-08-29 15:21:13 +01:00
|
|
|
val = os.path.join(os.path.dirname(val), bn)
|
|
|
|
cfg.set(section, name, val)
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# merge command line with config file
|
|
|
|
for opt in options.__dict__:
|
|
|
|
val = options.__dict__[opt]
|
|
|
|
if val is not None:
|
|
|
|
cfg.set(section, opt, val.__str__())
|
|
|
|
|
|
|
|
return dict(cfg.items(section)), args
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def exec_file(cfg):
|
2017-04-25 16:45:34 +01:00
|
|
|
""" Send a Register Message to execute a new session based on XML or Python
|
2013-08-29 15:21:13 +01:00
|
|
|
script file.
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
filename = cfg["execfile"]
|
|
|
|
sys.stdout.write("Telling daemon to execute file: %s...\n" % filename)
|
2013-08-29 15:21:13 +01:00
|
|
|
sys.stdout.flush()
|
2017-04-25 16:45:34 +01:00
|
|
|
tlvdata = coreapi.CoreRegisterTlv.pack(RegisterTlvs.EXECUTE_SERVER.value, filename)
|
|
|
|
msg = coreapi.CoreRegMessage.pack(MessageFlags.ADD.value, tlvdata)
|
2013-08-29 15:21:13 +01:00
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
2017-04-25 16:45:34 +01:00
|
|
|
sock.connect(("localhost", int(cfg["port"]))) # TODO: connect address option
|
2013-08-29 15:21:13 +01:00
|
|
|
sock.sendall(msg)
|
|
|
|
return 0
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
def main():
|
2017-04-25 16:45:34 +01:00
|
|
|
""" Main program startup.
|
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
# get a configuration merged from config file and command-line arguments
|
2017-04-25 16:45:34 +01:00
|
|
|
cfg, args = get_merged_config("%s/core.conf" % constants.CORE_CONF_DIR)
|
2013-08-29 15:21:13 +01:00
|
|
|
for a in args:
|
2017-04-25 16:45:34 +01:00
|
|
|
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)
|
2015-12-29 18:50:18 +00:00
|
|
|
signal.signal(signal.SIGUSR1, lambda signum, stackframe:
|
2017-04-25 16:45:34 +01:00
|
|
|
logrotate(stdout=cfg["logfile"],
|
|
|
|
stderr=cfg["logfile"]))
|
2013-08-29 15:21:13 +01:00
|
|
|
|
|
|
|
banner()
|
2017-04-25 16:45:34 +01:00
|
|
|
if cfg["execfile"]:
|
|
|
|
cfg["execfile"] = os.path.abspath(cfg["execfile"])
|
2013-08-29 15:21:13 +01:00
|
|
|
sys.exit(exec_file(cfg))
|
|
|
|
try:
|
|
|
|
cored(cfg)
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
pass
|
|
|
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2017-04-25 16:45:34 +01:00
|
|
|
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()
|
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
main()
|