diff --git a/Makefile.am b/Makefile.am index 83645c1b..2b5f29e2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -21,6 +21,7 @@ ACLOCAL_AMFLAGS = -I config # extra files to include with distribution tarball EXTRA_DIST = bootstrap.sh \ + package \ LICENSE \ README.md \ ASSIGNMENT_OF_COPYRIGHT.pdf \ @@ -47,7 +48,7 @@ fpm -s dir -t deb -n core-distributed \ --description "Common Open Research Emulator Distributed Package" \ --url https://github.com/coreemu/core \ --vendor "$(PACKAGE_VENDOR)" \ - -p core_distributed_VERSION_ARCH.deb \ + -p core-distributed_VERSION_ARCH.deb \ -v $(PACKAGE_VERSION) \ -d "ethtool" \ -d "procps" \ @@ -58,7 +59,8 @@ fpm -s dir -t deb -n core-distributed \ -d "libev4" \ -d "openssh-server" \ -d "xterm" \ - -C $(DESTDIR) + netns/vnoded=/usr/bin/ \ + netns/vcmd=/usr/bin/ endef define fpm-distributed-rpm = @@ -68,7 +70,7 @@ fpm -s dir -t rpm -n core-distributed \ --description "Common Open Research Emulator Distributed Package" \ --url https://github.com/coreemu/core \ --vendor "$(PACKAGE_VENDOR)" \ - -p core_distributed_VERSION_ARCH.rpm \ + -p core-distributed_VERSION_ARCH.rpm \ -v $(PACKAGE_VERSION) \ -d "ethtool" \ -d "procps-ng" \ @@ -79,12 +81,75 @@ fpm -s dir -t rpm -n core-distributed \ -d "net-tools" \ -d "openssh-server" \ -d "xterm" \ - -C $(DESTDIR) + netns/vnoded=/usr/bin/ \ + netns/vcmd=/usr/bin/ endef -.PHONY: fpm-distributed -fpm-distributed: clean-local-fpm - $(MAKE) -C netns install DESTDIR=$(DESTDIR) +define fpm-rpm = +fpm -s dir -t rpm -n core \ + -m "$(PACKAGE_MAINTAINERS)" \ + --license "BSD" \ + --description "core vnoded/vcmd and system dependencies" \ + --url https://github.com/coreemu/core \ + --vendor "$(PACKAGE_VENDOR)" \ + -p core_VERSION_ARCH.rpm \ + -v $(PACKAGE_VERSION) \ + --rpm-init package/core-daemon \ + --after-install package/after-install.sh \ + --after-remove package/after-remove.sh \ + -d "ethtool" \ + -d "tk" \ + -d "procps-ng" \ + -d "bash >= 3.0" \ + -d "ebtables" \ + -d "iproute" \ + -d "libev" \ + -d "net-tools" \ + -d "nftables" \ + netns/vnoded=/usr/bin/ \ + netns/vcmd=/usr/bin/ \ + package/etc/core.conf=/etc/core/ \ + package/etc/logging.conf=/etc/core/ \ + package/examples=/opt/core/ \ + daemon/dist/core-$(PACKAGE_VERSION)-py3-none-any.whl=/opt/core/ +endef + +define fpm-deb = +fpm -s dir -t deb -n core \ + -m "$(PACKAGE_MAINTAINERS)" \ + --license "BSD" \ + --description "core vnoded/vcmd and system dependencies" \ + --url https://github.com/coreemu/core \ + --vendor "$(PACKAGE_VENDOR)" \ + -p core_VERSION_ARCH.deb \ + -v $(PACKAGE_VERSION) \ + --deb-systemd package/core-daemon.service \ + --deb-no-default-config-files \ + --after-install package/after-install.sh \ + --after-remove package/after-remove.sh \ + -d "ethtool" \ + -d "tk" \ + -d "libtk-img" \ + -d "procps" \ + -d "libc6 >= 2.14" \ + -d "bash >= 3.0" \ + -d "ebtables" \ + -d "iproute2" \ + -d "libev4" \ + -d "nftables" \ + netns/vnoded=/usr/bin/ \ + netns/vcmd=/usr/bin/ \ + package/etc/core.conf=/etc/core/ \ + package/etc/logging.conf=/etc/core/ \ + package/examples=/opt/core/ \ + daemon/dist/core-$(PACKAGE_VERSION)-py3-none-any.whl=/opt/core/ +endef + +.PHONY: fpm +fpm: clean-local-fpm + cd daemon && poetry build -f wheel + $(call fpm-deb) + $(call fpm-rpm) $(call fpm-distributed-deb) $(call fpm-distributed-rpm) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 79459b80..2585ea1a 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -21,10 +21,7 @@ DISTCLEANFILES = Makefile.in # files to include with distribution tarball EXTRA_DIST = core \ - data \ doc/conf.py.in \ - examples \ - scripts \ tests \ setup.cfg \ poetry.lock \ diff --git a/daemon/examples/grpc/__init__.py b/daemon/core/scripts/__init__.py similarity index 100% rename from daemon/examples/grpc/__init__.py rename to daemon/core/scripts/__init__.py diff --git a/daemon/core/scripts/cleanup.py b/daemon/core/scripts/cleanup.py new file mode 100755 index 00000000..080cd62b --- /dev/null +++ b/daemon/core/scripts/cleanup.py @@ -0,0 +1,103 @@ +import argparse +import os +import subprocess +import sys +import time + + +def check_root() -> None: + if os.geteuid() != 0: + print("permission denied, run this script as root") + sys.exit(1) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="helps cleanup lingering core processes and files", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "-d", "--daemon", action="store_true", help="also kill core-daemon" + ) + return parser.parse_args() + + +def cleanup_daemon() -> None: + print("killing core-daemon process ... ", end="") + result = subprocess.call("pkill -9 core-daemon", shell=True) + if result: + print("not found") + else: + print("done") + + +def cleanup_nodes() -> None: + print("killing vnoded processes ... ", end="") + result = subprocess.call("pkill -KILL vnoded", shell=True) + if result: + print("none found") + else: + time.sleep(1) + print("done") + + +def cleanup_emane() -> None: + print("killing emane processes ... ", end="") + result = subprocess.call("pkill emane", shell=True) + if result: + print("none found") + else: + print("done") + + +def cleanup_sessions() -> None: + print("removing session directories ... ", end="") + result = subprocess.call("rm -rf /tmp/pycore*", shell=True) + if result: + print("none found") + else: + print("done") + + +def cleanup_interfaces() -> None: + print("cleaning up devices") + output = subprocess.check_output("ip -o -br link show", shell=True) + lines = output.decode().strip().split("\n") + for line in lines: + values = line.split() + name = values[0] + if ( + name.startswith("veth") + or name.startswith("gt.") + or name.startswith("b.") + or name.startswith("ctrl") + ): + result = subprocess.call(f"ip link delete {name}", shell=True) + if result: + print(f"failed to remove {name}") + else: + print(f"removed {name}") + if name.startswith("b."): + result = subprocess.call( + f"nft delete table bridge {name}", + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + shell=True, + ) + if not result: + print(f"cleared nft rules for {name}") + + +def main() -> None: + check_root() + args = parse_args() + if args.daemon: + cleanup_daemon() + cleanup_nodes() + cleanup_emane() + cleanup_interfaces() + cleanup_sessions() + + +if __name__ == "__main__": + main() diff --git a/daemon/scripts/core-cli b/daemon/core/scripts/cli.py similarity index 88% rename from daemon/scripts/core-cli rename to daemon/core/scripts/cli.py index b9de2615..31ad086e 100755 --- a/daemon/scripts/core-cli +++ b/daemon/core/scripts/cli.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import json import sys from argparse import ( @@ -32,7 +31,9 @@ NODE_TYPES = [x.name for x in NodeType if x != NodeType.PEER_TO_PEER] def protobuf_to_json(message: Any) -> Dict[str, Any]: - return MessageToDict(message, including_default_value_fields=True, preserving_proto_field_name=True) + return MessageToDict( + message, including_default_value_fields=True, preserving_proto_field_name=True + ) def print_json(data: Any) -> None: @@ -122,18 +123,15 @@ def get_current_session(core: CoreGrpcClient, session_id: Optional[int]) -> int: return sessions[0].id -def create_iface(iface_id: int, mac: str, ip4_net: IPNetwork, ip6_net: IPNetwork) -> Interface: +def create_iface( + iface_id: int, mac: str, ip4_net: IPNetwork, ip6_net: IPNetwork +) -> Interface: ip4 = str(ip4_net.ip) if ip4_net else None ip4_mask = ip4_net.prefixlen if ip4_net else None ip6 = str(ip6_net.ip) if ip6_net else None ip6_mask = ip6_net.prefixlen if ip6_net else None return Interface( - id=iface_id, - mac=mac, - ip4=ip4, - ip4_mask=ip4_mask, - ip6=ip6, - ip6_mask=ip6_mask, + id=iface_id, mac=mac, ip4=ip4, ip4_mask=ip4_mask, ip6=ip6, ip6_mask=ip6_mask ) @@ -216,12 +214,14 @@ def query_session(core: CoreGrpcClient, args: Namespace) -> None: for node in session.nodes.values(): xy_pos = f"{int(node.position.x)},{int(node.position.y)}" geo_pos = f"{node.geo.lon:.7f},{node.geo.lat:.7f},{node.geo.alt:f}" - print(f"{node.id:<7} | {node.name[:7]:<7} | {node.type.name[:7]:<7} | {xy_pos:<9} | {geo_pos}") + print( + f"{node.id:<7} | {node.name[:7]:<7} | {node.type.name[:7]:<7} | {xy_pos:<9} | {geo_pos}" + ) print("\nLinks") for link in session.links: n1 = session.nodes[link.node1_id].name n2 = session.nodes[link.node2_id].name - print(f"Node | ", end="") + print("Node | ", end="") print_iface_header() print(f"{n1:<6} | ", end="") if link.iface1: @@ -248,7 +248,9 @@ def query_node(core: CoreGrpcClient, args: Namespace) -> None: print("ID | Name | Type | XY | Geo") xy_pos = f"{int(node.position.x)},{int(node.position.y)}" geo_pos = f"{node.geo.lon:.7f},{node.geo.lat:.7f},{node.geo.alt:f}" - print(f"{node.id:<7} | {node.name[:7]:<7} | {node.type.name[:7]:<7} | {xy_pos:<9} | {geo_pos}") + print( + f"{node.id:<7} | {node.name[:7]:<7} | {node.type.name[:7]:<7} | {xy_pos:<9} | {geo_pos}" + ) if ifaces: print("Interfaces") print("Connected To | ", end="") @@ -348,10 +350,14 @@ def add_link(core: CoreGrpcClient, args: Namespace) -> None: session_id = get_current_session(core, args.session) iface1 = None if args.iface1_id is not None: - iface1 = create_iface(args.iface1_id, args.iface1_mac, args.iface1_ip4, args.iface1_ip6) + iface1 = create_iface( + args.iface1_id, args.iface1_mac, args.iface1_ip4, args.iface1_ip6 + ) iface2 = None if args.iface2_id is not None: - iface2 = create_iface(args.iface2_id, args.iface2_mac, args.iface2_ip4, args.iface2_ip6) + iface2 = create_iface( + args.iface2_id, args.iface2_mac, args.iface2_ip4, args.iface2_ip6 + ) options = LinkOptions( bandwidth=args.bandwidth, loss=args.loss, @@ -432,13 +438,17 @@ def setup_node_parser(parent) -> None: add_parser.add_argument( "-t", "--type", choices=NODE_TYPES, default="DEFAULT", help="type of node" ) - add_parser.add_argument("-m", "--model", help="used to determine services, optional") + add_parser.add_argument( + "-m", "--model", help="used to determine services, optional" + ) group = add_parser.add_mutually_exclusive_group(required=True) group.add_argument("-p", "--pos", type=position_type, help="x,y position") group.add_argument("-g", "--geo", type=geo_type, help="lon,lat,alt position") add_parser.add_argument("-ic", "--icon", help="icon to use, optional") add_parser.add_argument("-im", "--image", help="container image, optional") - add_parser.add_argument("-e", "--emane", help="emane model, only required for emane nodes") + add_parser.add_argument( + "-e", "--emane", help="emane model, only required for emane nodes" + ) add_parser.set_defaults(func=add_node) edit_parser = subparsers.add_parser("edit", help="edit a node") @@ -449,7 +459,9 @@ def setup_node_parser(parent) -> None: move_parser = subparsers.add_parser("move", help="move a node") move_parser.formatter_class = ArgumentDefaultsHelpFormatter - move_parser.add_argument("-i", "--id", type=int, help="id to use, optional", required=True) + move_parser.add_argument( + "-i", "--id", type=int, help="id to use, optional", required=True + ) group = move_parser.add_mutually_exclusive_group(required=True) group.add_argument("-p", "--pos", type=position_type, help="x,y position") group.add_argument("-g", "--geo", type=geo_type, help="lon,lat,alt position") @@ -474,19 +486,33 @@ def setup_link_parser(parent) -> None: add_parser.add_argument("-n1", "--node1", type=int, help="node1 id", required=True) add_parser.add_argument("-n2", "--node2", type=int, help="node2 id", required=True) add_parser.add_argument("-i1-i", "--iface1-id", type=int, help="node1 interface id") - add_parser.add_argument("-i1-m", "--iface1-mac", type=mac_type, help="node1 interface mac") - add_parser.add_argument("-i1-4", "--iface1-ip4", type=ip4_type, help="node1 interface ip4") - add_parser.add_argument("-i1-6", "--iface1-ip6", type=ip6_type, help="node1 interface ip6") + add_parser.add_argument( + "-i1-m", "--iface1-mac", type=mac_type, help="node1 interface mac" + ) + add_parser.add_argument( + "-i1-4", "--iface1-ip4", type=ip4_type, help="node1 interface ip4" + ) + add_parser.add_argument( + "-i1-6", "--iface1-ip6", type=ip6_type, help="node1 interface ip6" + ) add_parser.add_argument("-i2-i", "--iface2-id", type=int, help="node2 interface id") - add_parser.add_argument("-i2-m", "--iface2-mac", type=mac_type, help="node2 interface mac") - add_parser.add_argument("-i2-4", "--iface2-ip4", type=ip4_type, help="node2 interface ip4") - add_parser.add_argument("-i2-6", "--iface2-ip6", type=ip6_type, help="node2 interface ip6") + add_parser.add_argument( + "-i2-m", "--iface2-mac", type=mac_type, help="node2 interface mac" + ) + add_parser.add_argument( + "-i2-4", "--iface2-ip4", type=ip4_type, help="node2 interface ip4" + ) + add_parser.add_argument( + "-i2-6", "--iface2-ip6", type=ip6_type, help="node2 interface ip6" + ) add_parser.add_argument("-b", "--bandwidth", type=int, help="bandwidth (bps)") add_parser.add_argument("-l", "--loss", type=float, help="loss (%%)") add_parser.add_argument("-j", "--jitter", type=int, help="jitter (us)") add_parser.add_argument("-de", "--delay", type=int, help="delay (us)") add_parser.add_argument("-du", "--duplicate", type=int, help="duplicate (%%)") - add_parser.add_argument("-u", "--uni", action="store_true", help="is link unidirectional?") + add_parser.add_argument( + "-u", "--uni", action="store_true", help="is link unidirectional?" + ) add_parser.set_defaults(func=add_link) edit_parser = subparsers.add_parser("edit", help="edit a link") @@ -507,8 +533,12 @@ def setup_link_parser(parent) -> None: delete_parser = subparsers.add_parser("delete", help="delete a link") delete_parser.formatter_class = ArgumentDefaultsHelpFormatter - delete_parser.add_argument("-n1", "--node1", type=int, help="node1 id", required=True) - delete_parser.add_argument("-n2", "--node2", type=int, help="node1 id", required=True) + delete_parser.add_argument( + "-n1", "--node1", type=int, help="node1 id", required=True + ) + delete_parser.add_argument( + "-n2", "--node2", type=int, help="node1 id", required=True + ) delete_parser.add_argument("-i1", "--iface1", type=int, help="node1 interface id") delete_parser.add_argument("-i2", "--iface2", type=int, help="node2 interface id") delete_parser.set_defaults(func=delete_link) @@ -526,20 +556,28 @@ def setup_query_parser(parent) -> None: session_parser = subparsers.add_parser("session", help="query session") session_parser.formatter_class = ArgumentDefaultsHelpFormatter - session_parser.add_argument("-i", "--id", type=int, help="session to query", required=True) + session_parser.add_argument( + "-i", "--id", type=int, help="session to query", required=True + ) session_parser.set_defaults(func=query_session) node_parser = subparsers.add_parser("node", help="query node") node_parser.formatter_class = ArgumentDefaultsHelpFormatter - node_parser.add_argument("-i", "--id", type=int, help="session to query", required=True) - node_parser.add_argument("-n", "--node", type=int, help="node to query", required=True) + node_parser.add_argument( + "-i", "--id", type=int, help="session to query", required=True + ) + node_parser.add_argument( + "-n", "--node", type=int, help="node to query", required=True + ) node_parser.set_defaults(func=query_node) def setup_xml_parser(parent) -> None: parser = parent.add_parser("xml", help="open session xml") parser.formatter_class = ArgumentDefaultsHelpFormatter - parser.add_argument("-f", "--file", type=file_type, help="xml file to open", required=True) + parser.add_argument( + "-f", "--file", type=file_type, help="xml file to open", required=True + ) parser.add_argument("-s", "--start", action="store_true", help="start the session?") parser.set_defaults(func=open_xml) diff --git a/daemon/scripts/core-daemon b/daemon/core/scripts/daemon.py similarity index 78% rename from daemon/scripts/core-daemon rename to daemon/core/scripts/daemon.py index a2ae3343..6b9caa54 100755 --- a/daemon/scripts/core-daemon +++ b/daemon/core/scripts/daemon.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ core-daemon: the CORE daemon is a server process that receives CORE API messages and instantiates emulated nodes and networks within the kernel. Various @@ -61,18 +60,35 @@ def get_merged_config(filename): defaults = { "grpcport": default_grpc_port, "grpcaddress": default_address, - "logfile": default_log + "logfile": default_log, } parser = argparse.ArgumentParser( - description=f"CORE daemon v.{COREDPY_VERSION} instantiates Linux network namespace nodes.") - parser.add_argument("-f", "--configfile", dest="configfile", - help=f"read config from specified file; default = {filename}") - parser.add_argument("--ovs", action="store_true", help="enable experimental ovs mode, default is false") - parser.add_argument("--grpc-port", dest="grpcport", - help=f"grpc port to listen on; default {default_grpc_port}") - parser.add_argument("--grpc-address", dest="grpcaddress", - help=f"grpc address to listen on; default {default_address}") - parser.add_argument("-l", "--logfile", help=f"core logging configuration; default {default_log}") + description=f"CORE daemon v.{COREDPY_VERSION} instantiates Linux network namespace nodes." + ) + parser.add_argument( + "-f", + "--configfile", + dest="configfile", + help=f"read config from specified file; default = {filename}", + ) + parser.add_argument( + "--ovs", + action="store_true", + help="enable experimental ovs mode, default is false", + ) + parser.add_argument( + "--grpc-port", + dest="grpcport", + help=f"grpc port to listen on; default {default_grpc_port}", + ) + parser.add_argument( + "--grpc-address", + dest="grpcaddress", + help=f"grpc address to listen on; default {default_address}", + ) + parser.add_argument( + "-l", "--logfile", help=f"core logging configuration; default {default_log}" + ) # parse command line options args = parser.parse_args() # convert ovs to internal format diff --git a/daemon/scripts/core-gui b/daemon/core/scripts/gui.py similarity index 60% rename from daemon/scripts/core-gui rename to daemon/core/scripts/gui.py index ff7795a3..9c0560b2 100755 --- a/daemon/scripts/core-gui +++ b/daemon/core/scripts/gui.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import argparse import logging from logging.handlers import TimedRotatingFileHandler @@ -9,12 +8,19 @@ from core.gui.app import Application def main() -> None: # parse flags - parser = argparse.ArgumentParser(description=f"CORE Python GUI") - parser.add_argument("-l", "--level", choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], default="INFO", - help="logging level") + parser = argparse.ArgumentParser(description="CORE Python GUI") + parser.add_argument( + "-l", + "--level", + choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], + default="INFO", + help="logging level", + ) parser.add_argument("-p", "--proxy", action="store_true", help="enable proxy") parser.add_argument("-s", "--session", type=int, help="session id to join") - parser.add_argument("--create-dir", action="store_true", help="create gui directory and exit") + parser.add_argument( + "--create-dir", action="store_true", help="create gui directory and exit" + ) args = parser.parse_args() # check home directory exists and create if necessary @@ -25,9 +31,13 @@ def main() -> None: # setup logging log_format = "%(asctime)s - %(levelname)s - %(module)s:%(funcName)s - %(message)s" stream_handler = logging.StreamHandler() - file_handler = TimedRotatingFileHandler(filename=appconfig.LOG_PATH, when="D", backupCount=5) + file_handler = TimedRotatingFileHandler( + filename=appconfig.LOG_PATH, when="D", backupCount=5 + ) log_level = logging.getLevelName(args.level) - logging.basicConfig(level=log_level, format=log_format, handlers=[stream_handler, file_handler]) + logging.basicConfig( + level=log_level, format=log_format, handlers=[stream_handler, file_handler] + ) logging.getLogger("PIL").setLevel(logging.ERROR) # start app diff --git a/daemon/scripts/core-player b/daemon/core/scripts/player.py similarity index 90% rename from daemon/scripts/core-player rename to daemon/core/scripts/player.py index c7942b37..07728939 100755 --- a/daemon/scripts/core-player +++ b/daemon/core/scripts/player.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - import argparse import logging import sys @@ -31,7 +29,10 @@ def parse_args() -> argparse.Namespace: "-f", "--file", required=True, type=path_type, help="core file to play" ) parser.add_argument( - "-s", "--session", type=int, help="session to play to, first found session otherwise" + "-s", + "--session", + type=int, + help="session to play to, first found session otherwise", ) return parser.parse_args() diff --git a/daemon/scripts/core-route-monitor b/daemon/core/scripts/routemonitor.py similarity index 97% rename from daemon/scripts/core-route-monitor rename to daemon/core/scripts/routemonitor.py index bc61f6fa..2ebfdfad 100755 --- a/daemon/scripts/core-route-monitor +++ b/daemon/core/scripts/routemonitor.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import argparse import enum import select @@ -60,15 +59,15 @@ class SdtClient: class RouterMonitor: def __init__( - self, - session: int, - src: str, - dst: str, - pkt: str, - rate: int, - dead: int, - sdt_host: str, - sdt_port: int, + self, + session: int, + src: str, + dst: str, + pkt: str, + rate: int, + dead: int, + sdt_host: str, + sdt_port: int, ) -> None: self.queue = Queue() self.core = CoreGrpcClient() diff --git a/daemon/scripts/core-service-update b/daemon/core/scripts/serviceupdate.py similarity index 50% rename from daemon/scripts/core-service-update rename to daemon/core/scripts/serviceupdate.py index d0ca863f..50ada96d 100755 --- a/daemon/scripts/core-service-update +++ b/daemon/core/scripts/serviceupdate.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import argparse import re from io import TextIOWrapper @@ -6,9 +5,15 @@ from io import TextIOWrapper def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( - description=f"Helps transition older CORE services to work with newer versions") - parser.add_argument("-f", "--file", dest="file", type=argparse.FileType("r"), - help=f"service file to update") + description="Helps transition older CORE services to work with newer versions" + ) + parser.add_argument( + "-f", + "--file", + dest="file", + type=argparse.FileType("r"), + help="service file to update", + ) return parser.parse_args() @@ -20,17 +25,32 @@ def update_service(service_file: TextIOWrapper) -> None: # rename dirs to directories line = re.sub(r"^(\s+)dirs", r"\1directories", line) # fix import states for service - line = re.sub(r"^.+import.+CoreService.+$", - r"from core.services.coreservices import CoreService", line) + line = re.sub( + r"^.+import.+CoreService.+$", + r"from core.services.coreservices import CoreService", + line, + ) # fix method signatures - line = re.sub(r"def generateconfig\(cls, node, filename, services\)", - r"def generate_config(cls, node, filename)", line) - line = re.sub(r"def getvalidate\(cls, node, services\)", - r"def get_validate(cls, node)", line) - line = re.sub(r"def getstartup\(cls, node, services\)", - r"def get_startup(cls, node)", line) - line = re.sub(r"def getconfigfilenames\(cls, nodenum, services\)", - r"def get_configs(cls, node)", line) + line = re.sub( + r"def generateconfig\(cls, node, filename, services\)", + r"def generate_config(cls, node, filename)", + line, + ) + line = re.sub( + r"def getvalidate\(cls, node, services\)", + r"def get_validate(cls, node)", + line, + ) + line = re.sub( + r"def getstartup\(cls, node, services\)", + r"def get_startup(cls, node)", + line, + ) + line = re.sub( + r"def getconfigfilenames\(cls, nodenum, services\)", + r"def get_configs(cls, node)", + line, + ) # remove unwanted lines if re.search(r"addservice\(", line): continue diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index 91b5dbad..70d684dd 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -14,6 +14,14 @@ include = [ ] exclude = ["core/constants.py.in"] +[tool.poetry.scripts] +core-daemon = "core.scripts.daemon:main" +core-cli = "core.scripts.cli:main" +core-gui = "core.scripts.gui:main" +core-player = "core.scripts.player:main" +core-route-monitor = "core.scripts.routemonitor:main" +core-service-update = "core.scripts.serviceupdate:main" +core-cleanup = "core.scripts.cleanup:main" [tool.poetry.dependencies] python = "^3.6" diff --git a/daemon/scripts/core-cleanup b/daemon/scripts/core-cleanup deleted file mode 100755 index ced76634..00000000 --- a/daemon/scripts/core-cleanup +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/sh - -if [ "z$1" = "z-h" -o "z$1" = "z--help" ]; then - echo "usage: $0 [-d [-l]]" - echo -n " Clean up all CORE namespaces processes, bridges, interfaces, " - echo "and session\n directories. Options:" - echo " -h show this help message and exit" - echo " -d also kill the Python daemon" - echo " -l remove the core-daemon.log file" - exit 0 -fi - -if [ `id -u` != 0 ]; then - echo "Permission denied. Re-run this script as root." - exit 1 -fi - -PATH="/sbin:/bin:/usr/sbin:/usr/bin" -export PATH - -if [ "z$1" = "z-d" ]; then - pypids=`pidof python3 python` - for p in $pypids; do - grep -q core-daemon /proc/$p/cmdline - if [ $? = 0 ]; then - echo "cleaning up core-daemon process: $p" - kill -9 $p - fi - done -fi - -if [ "z$2" = "z-l" ]; then - rm -f /var/log/core-daemon.log -fi - -kaopts="-v" -killall --help 2>&1 | grep -q namespace -if [ $? = 0 ]; then - kaopts="$kaopts --ns 0" -fi - -vnodedpids=`pidof vnoded` -if [ "z$vnodedpids" != "z" ]; then - echo "cleaning up old vnoded processes: $vnodedpids" - killall $kaopts -KILL vnoded - # pause for 1 second for interfaces to disappear - sleep 1 -fi -killall -q emane -killall -q emanetransportd -killall -q emaneeventservice - -if [ -d /sys/class/net ]; then - ifcommand="ls -1 /sys/class/net" -else - ifcommand="ip -o link show | sed -r -e 's/[0-9]+: ([^[:space:]]+): .*/\1/'" -fi - -eval "$ifcommand" | awk ' - /^veth[0-9]+\./ {print "removing interface " $1; system("ip link del " $1);} - /tmp\./ {print "removing interface " $1; system("ip link del " $1);} - /gt\./ {print "removing interface " $1; system("ip link del " $1);} - /b\./ {print "removing bridge " $1; system("ip link set " $1 " down; ip link del " $1);} - /ctrl[0-9]+\./ {print "removing bridge " $1; system("ip link set " $1 " down; ip link del " $1);} -' - -nft list ruleset | awk ' - $3 ~ /^b\./ {print "removing nftables " $3; system("nft delete table bridge " $3);} -' - -rm -rf /tmp/pycore* diff --git a/package/Dockerfile.centos b/package/Dockerfile.centos new file mode 100644 index 00000000..6cbb3610 --- /dev/null +++ b/package/Dockerfile.centos @@ -0,0 +1,15 @@ +# syntax=docker/dockerfile:1 +FROM centos:7 +LABEL Description="CORE CentOS Image" + +# define environment +ENV DEBIAN_FRONTEND=noninteractive + +# install basic dependencies +RUN yum update -y && yum install -y python3 python3-pip python3-tkinter +RUN python3 -m pip install --upgrade pip + +# install core +WORKDIR /opt/core +COPY core_*.rpm . +RUN NO_VENV=1 yum install -y ./core_*.rpm diff --git a/package/Dockerfile.ubuntu b/package/Dockerfile.ubuntu new file mode 100644 index 00000000..5a19e326 --- /dev/null +++ b/package/Dockerfile.ubuntu @@ -0,0 +1,15 @@ +# syntax=docker/dockerfile:1 +FROM ubuntu:20.04 +LABEL Description="CORE Docker Ubuntu Image" + +# define environment +ENV DEBIAN_FRONTEND=noninteractive + +# install basic dependencies +RUN apt-get update && apt-get install -y python3 python3-tk python3-pip python3-venv +RUN python3 -m pip install --upgrade pip + +# install core +WORKDIR /opt/core +COPY core_*.deb . +RUN apt-get install -y ./core_*.deb diff --git a/package/after-install.sh b/package/after-install.sh new file mode 100644 index 00000000..341e909d --- /dev/null +++ b/package/after-install.sh @@ -0,0 +1,16 @@ +#!/bin/sh +if [ ! -z "${NO_PYTHON}" ]; then + exit 0 +fi + +PYTHON="${PYTHON:=python3}" +if [ ! -z "${NO_VENV}" ]; then + ${PYTHON} -m pip install /opt/core/core-*.whl + echo "DAEMON=/usr/local/bin/core-daemon" > /opt/core/service +else + ${PYTHON} -m venv /opt/core/venv + . /opt/core/venv/bin/activate + pip install --upgrade pip + pip install /opt/core/core-*.whl + echo "DAEMON=/opt/core/venv/bin/core-daemon" > /opt/core/service +fi diff --git a/package/after-remove.sh b/package/after-remove.sh new file mode 100644 index 00000000..e67a0a59 --- /dev/null +++ b/package/after-remove.sh @@ -0,0 +1,13 @@ +#!/bin/sh +if [ -v NO_PYTHON ]; then + exit 0 +fi + +PYTHON="${PYTHON:=python3}" +if [ -v NO_VENV ]; then + ${PYTHON} -m pip uninstall core +else + ${PYTHON} -m venv /opt/core/venv + . /opt/core/venv/bin/activate + pip uninstall core +fi diff --git a/package/core-daemon b/package/core-daemon new file mode 100644 index 00000000..8cb57e4c --- /dev/null +++ b/package/core-daemon @@ -0,0 +1,112 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: core-daemon +# Required-Start: $network $remote_fs +# Required-Stop: $network $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start the core-daemon CORE daemon at boot time +# Description: Starts and stops the core-daemon CORE daemon used to +# provide network emulation services for the CORE GUI +# or scripts. +### END INIT INFO +# +# chkconfig: 35 90 03 +# description: Starts and stops the CORE daemon \ +# used to provide network emulation services. +# +# config: /etc/core/ + +. /opt/core/service +NAME=`basename $0` +PIDFILE="/var/$NAME.pid" +LOG="/var/log/$NAME.log" +CMD="$DAEMON" + +get_pid() { + cat "$PIDFILE" +} + +is_alive() { + [ -f "$PIDFILE" ] && ps -p `get_pid` > /dev/null 2>&1 +} + +corestart() { + if is_alive; then + echo "$NAME already started" + else + echo "starting $NAME" + $CMD 2>&1 >> "$LOG" & + fi + + echo $! > "$PIDFILE" + if ! is_alive; then + echo "unable to start $NAME, see $LOG" + exit 1 + fi +} + +corestop() { + if is_alive; then + echo -n "stopping $NAME.." + kill `get_pid` + for i in 1 2 3 4 5; do + sleep 1 + if ! is_alive; then + break + fi + echo -n "." + done + echo + + if is_alive; then + echo "not stopped; may still be shutting down" + exit 1 + else + echo "stopped" + if [ -f "$PIDFILE" ]; then + rm -f "$PIDFILE" + fi + fi + else + echo "$NAME not running" + fi +} + +corerestart() { + corestop + corestart +} + +corestatus() { + if is_alive; then + echo "$NAME is running" + else + echo "$NAME is stopped" + exit 1 + fi +} + + +case "$1" in + start) + corestart + ;; + stop) + corestop + ;; + restart) + corerestart + ;; + force-reload) + corerestart + ;; + status) + corestatus + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 +esac + +exit $? diff --git a/package/core-daemon.service b/package/core-daemon.service new file mode 100644 index 00000000..ede52c63 --- /dev/null +++ b/package/core-daemon.service @@ -0,0 +1,12 @@ +[Unit] +Description=Common Open Research Emulator Service +After=network.target + +[Service] +Type=simple +EnvironmentFile=/opt/core/service +ExecStart=$DAEMON +TasksMax=infinity + +[Install] +WantedBy=multi-user.target diff --git a/daemon/data/core.conf b/package/etc/core.conf similarity index 100% rename from daemon/data/core.conf rename to package/etc/core.conf diff --git a/daemon/data/logging.conf b/package/etc/logging.conf similarity index 100% rename from daemon/data/logging.conf rename to package/etc/logging.conf diff --git a/daemon/examples/configservices/switch.py b/package/examples/configservices/switch.py similarity index 100% rename from daemon/examples/configservices/switch.py rename to package/examples/configservices/switch.py diff --git a/daemon/examples/controlnet_updown b/package/examples/controlnet_updown similarity index 100% rename from daemon/examples/controlnet_updown rename to package/examples/controlnet_updown diff --git a/daemon/examples/docker/docker2core.py b/package/examples/docker/docker2core.py similarity index 100% rename from daemon/examples/docker/docker2core.py rename to package/examples/docker/docker2core.py diff --git a/daemon/examples/docker/docker2docker.py b/package/examples/docker/docker2docker.py similarity index 100% rename from daemon/examples/docker/docker2docker.py rename to package/examples/docker/docker2docker.py diff --git a/daemon/examples/docker/switch.py b/package/examples/docker/switch.py similarity index 100% rename from daemon/examples/docker/switch.py rename to package/examples/docker/switch.py diff --git a/daemon/examples/myemane/__init__.py b/package/examples/grpc/__init__.py similarity index 100% rename from daemon/examples/myemane/__init__.py rename to package/examples/grpc/__init__.py diff --git a/daemon/examples/grpc/distributed_switch.py b/package/examples/grpc/distributed_switch.py similarity index 100% rename from daemon/examples/grpc/distributed_switch.py rename to package/examples/grpc/distributed_switch.py diff --git a/daemon/examples/grpc/emane80211.py b/package/examples/grpc/emane80211.py similarity index 100% rename from daemon/examples/grpc/emane80211.py rename to package/examples/grpc/emane80211.py diff --git a/daemon/examples/grpc/peertopeer.py b/package/examples/grpc/peertopeer.py similarity index 100% rename from daemon/examples/grpc/peertopeer.py rename to package/examples/grpc/peertopeer.py diff --git a/daemon/examples/grpc/switch.py b/package/examples/grpc/switch.py similarity index 100% rename from daemon/examples/grpc/switch.py rename to package/examples/grpc/switch.py diff --git a/daemon/examples/grpc/wireless.py b/package/examples/grpc/wireless.py similarity index 100% rename from daemon/examples/grpc/wireless.py rename to package/examples/grpc/wireless.py diff --git a/daemon/examples/grpc/wlan.py b/package/examples/grpc/wlan.py similarity index 100% rename from daemon/examples/grpc/wlan.py rename to package/examples/grpc/wlan.py diff --git a/daemon/examples/lxd/lxd2core.py b/package/examples/lxd/lxd2core.py similarity index 100% rename from daemon/examples/lxd/lxd2core.py rename to package/examples/lxd/lxd2core.py diff --git a/daemon/examples/lxd/lxd2lxd.py b/package/examples/lxd/lxd2lxd.py similarity index 100% rename from daemon/examples/lxd/lxd2lxd.py rename to package/examples/lxd/lxd2lxd.py diff --git a/daemon/examples/lxd/switch.py b/package/examples/lxd/switch.py similarity index 100% rename from daemon/examples/lxd/switch.py rename to package/examples/lxd/switch.py diff --git a/package/examples/myemane/__init__.py b/package/examples/myemane/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/daemon/examples/myemane/examplemodel.py b/package/examples/myemane/examplemodel.py similarity index 100% rename from daemon/examples/myemane/examplemodel.py rename to package/examples/myemane/examplemodel.py diff --git a/daemon/examples/myservices/__init__.py b/package/examples/myservices/__init__.py similarity index 100% rename from daemon/examples/myservices/__init__.py rename to package/examples/myservices/__init__.py diff --git a/daemon/examples/myservices/exampleservice.py b/package/examples/myservices/exampleservice.py similarity index 100% rename from daemon/examples/myservices/exampleservice.py rename to package/examples/myservices/exampleservice.py diff --git a/daemon/examples/python/distributed_emane.py b/package/examples/python/distributed_emane.py similarity index 100% rename from daemon/examples/python/distributed_emane.py rename to package/examples/python/distributed_emane.py diff --git a/daemon/examples/python/distributed_lxd.py b/package/examples/python/distributed_lxd.py similarity index 100% rename from daemon/examples/python/distributed_lxd.py rename to package/examples/python/distributed_lxd.py diff --git a/daemon/examples/python/distributed_ptp.py b/package/examples/python/distributed_ptp.py similarity index 100% rename from daemon/examples/python/distributed_ptp.py rename to package/examples/python/distributed_ptp.py diff --git a/daemon/examples/python/distributed_switch.py b/package/examples/python/distributed_switch.py similarity index 100% rename from daemon/examples/python/distributed_switch.py rename to package/examples/python/distributed_switch.py diff --git a/daemon/examples/python/emane80211.py b/package/examples/python/emane80211.py similarity index 100% rename from daemon/examples/python/emane80211.py rename to package/examples/python/emane80211.py diff --git a/daemon/examples/python/peertopeer.py b/package/examples/python/peertopeer.py similarity index 100% rename from daemon/examples/python/peertopeer.py rename to package/examples/python/peertopeer.py diff --git a/daemon/examples/python/switch.py b/package/examples/python/switch.py similarity index 100% rename from daemon/examples/python/switch.py rename to package/examples/python/switch.py diff --git a/daemon/examples/python/wireless.py b/package/examples/python/wireless.py similarity index 100% rename from daemon/examples/python/wireless.py rename to package/examples/python/wireless.py diff --git a/daemon/examples/python/wlan.py b/package/examples/python/wlan.py similarity index 100% rename from daemon/examples/python/wlan.py rename to package/examples/python/wlan.py diff --git a/daemon/examples/services/sampleFirewall b/package/examples/services/sampleFirewall similarity index 100% rename from daemon/examples/services/sampleFirewall rename to package/examples/services/sampleFirewall diff --git a/daemon/examples/services/sampleIPsec b/package/examples/services/sampleIPsec similarity index 100% rename from daemon/examples/services/sampleIPsec rename to package/examples/services/sampleIPsec diff --git a/daemon/examples/services/sampleVPNClient b/package/examples/services/sampleVPNClient similarity index 100% rename from daemon/examples/services/sampleVPNClient rename to package/examples/services/sampleVPNClient diff --git a/daemon/examples/services/sampleVPNServer b/package/examples/services/sampleVPNServer similarity index 100% rename from daemon/examples/services/sampleVPNServer rename to package/examples/services/sampleVPNServer diff --git a/daemon/examples/tdma/schedule.xml b/package/examples/tdma/schedule.xml similarity index 100% rename from daemon/examples/tdma/schedule.xml rename to package/examples/tdma/schedule.xml diff --git a/tasks.py b/tasks.py index 0cf4613f..adb9dfde 100644 --- a/tasks.py +++ b/tasks.py @@ -291,25 +291,6 @@ def install_core_files(c, local=False, verbose=False, prefix=DEFAULT_PREFIX): hide = not verbose python = get_python(c) bin_dir = Path(prefix).joinpath("bin") - # install scripts - for script in Path("daemon/scripts").iterdir(): - dest = bin_dir.joinpath(script.name) - with open(script, "r") as f: - lines = f.readlines() - first = lines[0].strip() - # modify python scripts to point to virtual environment - if not local and first == "#!/usr/bin/env python3": - lines[0] = f"#!{python}\n" - temp = NamedTemporaryFile("w", delete=False) - for line in lines: - temp.write(line) - temp.close() - c.run(f"sudo cp {temp.name} {dest}", hide=hide) - c.run(f"sudo chmod 755 {dest}", hide=hide) - os.unlink(temp.name) - # copy normal links - else: - c.run(f"sudo cp {script} {dest}", hide=hide) # setup core python helper if not local: core_python = bin_dir.joinpath("core-python") @@ -325,12 +306,12 @@ def install_core_files(c, local=False, verbose=False, prefix=DEFAULT_PREFIX): # install core configuration file config_dir = "/etc/core" c.run(f"sudo mkdir -p {config_dir}", hide=hide) - c.run(f"sudo cp -n daemon/data/core.conf {config_dir}", hide=hide) - c.run(f"sudo cp -n daemon/data/logging.conf {config_dir}", hide=hide) + c.run(f"sudo cp -n package/etc/core.conf {config_dir}", hide=hide) + c.run(f"sudo cp -n package/etc/logging.conf {config_dir}", hide=hide) # install examples examples_dir = f"{prefix}/share/core" c.run(f"sudo mkdir -p {examples_dir}", hide=hide) - c.run(f"sudo cp -r daemon/examples {examples_dir}", hide=hide) + c.run(f"sudo cp -r package/examples {examples_dir}", hide=hide) @task( @@ -467,11 +448,9 @@ def uninstall( c.run("sudo -v", hide=True) with p.start("uninstalling core"): c.run("sudo make uninstall", hide=hide) - with p.start("cleaning build directory"): c.run("make clean", hide=hide) c.run("./bootstrap.sh clean", hide=hide) - if local: with p.start("uninstalling core"): python_bin = get_env_python() @@ -485,22 +464,15 @@ def uninstall( c.run("poetry run pre-commit uninstall", hide=hide) with p.start("uninstalling poetry virtual environment"): c.run(f"poetry env remove {python}", hide=hide) - # remove installed files bin_dir = Path(prefix).joinpath("bin") - with p.start("uninstalling script files"): - for script in Path("daemon/scripts").iterdir(): - dest = bin_dir.joinpath(script.name) - c.run(f"sudo rm -f {dest}", hide=hide) with p.start("uninstalling examples"): examples_dir = Path(prefix).joinpath("share/core") c.run(f"sudo rm -rf {examples_dir}") - # remove core-python symlink if not local: core_python = bin_dir.joinpath("core-python") c.run(f"sudo rm -f {core_python}", hide=hide) - # remove service systemd_dir = Path("/lib/systemd/system/") service_name = "core-daemon.service"