From 226e28c114bcdf945ddbbd3acd9dde211c79c232 Mon Sep 17 00:00:00 2001 From: Michael Buesch Date: Fri, 25 Jan 2019 20:35:44 +0100 Subject: [PATCH] Add option to shutdown or reboot coreserver system Signed-off-by: Michael Buesch --- awlsim-client | 12 ++++- awlsim-server | 43 ++++++++++-------- awlsim-server.service.in | 2 +- awlsim/coreclient/client.py | 31 +++++++++++-- awlsim/coreserver/messages.py | 82 ++++++++++++++++++++++++++++++++++- awlsim/coreserver/server.py | 29 +++++++++++-- 6 files changed, 173 insertions(+), 26 deletions(-) diff --git a/awlsim-client b/awlsim-client index 52ee1e2f9..f76119dd1 100755 --- a/awlsim-client +++ b/awlsim-client @@ -96,6 +96,8 @@ def usage(): print("Actions to be performed on the server:") print(" -r|--runstate RUN/STOP Set the run state of the CPU.") print(" -S|--stats Fetch and display CPU statistics.") + print(" --shutdown Shutdown the core server system.") + print(" --reboot Reboot the core server system.") def main(): opt_connect = (AwlSimServer.DEFAULT_HOST, AwlSimServer.DEFAULT_PORT) @@ -114,7 +116,7 @@ def main(): "hc:t:L:sP:r:S", [ "help", "connect=", "timeout=", "loglevel=", "ssh-tunnel", "ssh-passphrase=", "ssh-user=", "ssh-port=", "ssh-localport=", "ssh-exe=", - "runstate=", "stats", ]) + "runstate=", "stats", "shutdown", "reboot", ]) except getopt.GetoptError as e: printError(str(e)) usage() @@ -177,6 +179,10 @@ def main(): sys.exit(1) if o in ("-S", "--stats"): actions.append(("stats", None)) + if o == "--shutdown": + actions.append(("shutdown", None)) + if o == "--reboot": + actions.append(("reboot", None)) if args: usage() return ExitCodes.EXIT_ERR_CMDLINE @@ -213,6 +219,10 @@ def main(): client.setRunState(actionValue) elif action == "stats": printCpuStats(client.getCpuStats(sync=True)) + elif action == "shutdown": + client.shutdownCoreServerSystem() + elif action == "reboot": + client.rebootCoreServerSystem() else: assert(0) except AwlSimError as e: diff --git a/awlsim-server b/awlsim-server index 8e28b2ae2..699216d73 100755 --- a/awlsim-server +++ b/awlsim-server @@ -3,7 +3,7 @@ # # AWL simulator - Server interface # -# Copyright 2013-2016 Michael Buesch +# Copyright 2013-2019 Michael Buesch # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -51,6 +51,7 @@ def usage(): print(" -6|--force-ipv6 Force the use of IPv6.") print(" -B|--background Fork a background process") print(" -w|--rw-project Enable project file writing") + print(" -S|--allow-shutdown Allow remote system shutdown") print(" -L|--loglevel LVL Set the log level:") print(" 0: Log nothing") print(" 1: Log errors") @@ -68,13 +69,14 @@ def main(): opt_listen = (AwlSimServer.DEFAULT_HOST, AwlSimServer.DEFAULT_PORT) opt_family = None opt_background = False + opt_allowShutdown = False opt_loglevel = Logging.LOG_INFO try: (opts, args) = getopt.getopt(sys.argv[1:], - "hl:46BwL:", + "hl:46BwSL:", [ "help", "listen=", "force-ipv4", "force-ipv6", - "background", "rw-project", + "background", "rw-project", "allow-shutdown", "loglevel=", ]) except getopt.GetoptError as e: printError(str(e)) @@ -104,6 +106,8 @@ def main(): opt_background = True if o in ("-w", "--rw-project"): opt_rwProject = True + if o in ("-S", "--allow-shutdown"): + opt_allowShutdown = True if o in ("-L", "--loglevel"): try: opt_loglevel = int(v) @@ -119,16 +123,21 @@ def main(): exitCode = ExitCodes.EXIT_OK try: Logging.setLoglevel(opt_loglevel) + + commandMask = 0 + if opt_allowShutdown: + commandMask |= AwlSimServer.CMDMSK_SHUTDOWN + if opt_background: interpreter = sys.executable assert(interpreter) - serverProcess = AwlSimServer.start(listenHost = opt_listen[0], - listenPort = opt_listen[1], - listenFamily = opt_family, - forkInterpreter = interpreter, - commandMask = 0, - projectFile = opt_project, - projectWriteBack = opt_rwProject) + serverProcess = AwlSimServer.start(listenHost=opt_listen[0], + listenPort=opt_listen[1], + listenFamily=opt_family, + forkInterpreter=interpreter, + commandMask=commandMask, + projectFile=opt_project, + projectWriteBack=opt_rwProject) printInfo("Started awlsim server process (PID: %d)" %\ serverProcess.pid) else: @@ -136,13 +145,13 @@ def main(): printInfo("*** Using accelerated CYTHON core " "(AWLSIM_CYTHON environment variable is set)") - exitCode = AwlSimServer.start(listenHost = opt_listen[0], - listenPort = opt_listen[1], - listenFamily = opt_family, - forkInterpreter = None, - commandMask = 0, - projectFile = opt_project, - projectWriteBack = opt_rwProject) + exitCode = AwlSimServer.start(listenHost=opt_listen[0], + listenPort=opt_listen[1], + listenFamily=opt_family, + forkInterpreter=None, + commandMask=commandMask, + projectFile=opt_project, + projectWriteBack=opt_rwProject) except AwlSimError as e: printError(e.getReport()) return ExitCodes.EXIT_ERR_SIM diff --git a/awlsim-server.service.in b/awlsim-server.service.in index 58d564147..09b54b220 100644 --- a/awlsim-server.service.in +++ b/awlsim-server.service.in @@ -12,7 +12,7 @@ User=@USER@ Group=@GROUP@ Nice=-15 -ExecStart=@PYTHON@ @PREFIX@/bin/awlsim-server -l localhost -4 -w @PROJECT@ +ExecStart=@PYTHON@ @PREFIX@/bin/awlsim-server -l localhost -4 -S -w @PROJECT@ ExecStartPost=-@PYTHON@ @PREFIX@/bin/awlsim-client -c localhost -r RUN Environment=PYTHONPATH=@PYTHON_SITE@ PYTHONHASHSEED=0 PYTHONOPTIMIZE=2 PYTHONDONTWRITEBYTECODE=1 AWLSIM_CYTHON=1 AWLSIM_SCHED=realtime AWLSIM_PRIO=50 AWLSIM_AFFINITY=-1,-2,-3 diff --git a/awlsim/coreclient/client.py b/awlsim/coreclient/client.py index 7070b630d..ee0d8d3ea 100644 --- a/awlsim/coreclient/client.py +++ b/awlsim/coreclient/client.py @@ -188,9 +188,7 @@ def killSpawnedServer(self): if self.__transceiver: with contextlib.suppress(AwlSimError, MaintenanceRequest): - msg = AwlSimMessage_SHUTDOWN() - status = self.__sendAndWaitFor_REPLY(msg) - if status != AwlSimMessage_REPLY.STAT_OK: + if not self.shutdownCoreServer(): printError("AwlSimClient: Failed to shut " "down server via message") @@ -860,3 +858,30 @@ def getCpuStats(self, sync=False): else: self.__send(msg) return True + + def shutdownCoreServer(self): + """Shut down the core server. + """ + if not self.__transceiver: + return False + msg = AwlSimMessage_SHUTDOWN(AwlSimMessage_SHUTDOWN.SHUTDOWN_CORE) + status = self.__sendAndWaitFor_REPLY(msg) + return status == AwlSimMessage_REPLY.STAT_OK + + def shutdownCoreServerSystem(self): + """Shut down the core server and the system it runs on. + """ + if not self.__transceiver: + return False + msg = AwlSimMessage_SHUTDOWN(AwlSimMessage_SHUTDOWN.SHUTDOWN_SYSTEM_HALT) + self.__send(msg) + return True + + def rebootCoreServerSystem(self): + """Reboot the core server and the system it runs on. + """ + if not self.__transceiver: + return False + msg = AwlSimMessage_SHUTDOWN(AwlSimMessage_SHUTDOWN.SHUTDOWN_SYSTEM_REBOOT) + self.__send(msg) + return True diff --git a/awlsim/coreserver/messages.py b/awlsim/coreserver/messages.py index d25c6fa15..59f84e3ad 100644 --- a/awlsim/coreserver/messages.py +++ b/awlsim/coreserver/messages.py @@ -277,9 +277,78 @@ class AwlSimMessage_PONG(AwlSimMessage): class AwlSimMessage_RESET(AwlSimMessage): msgId = AwlSimMessage.MSG_ID_RESET + # Payload struct: + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + + plStruct = struct.Struct(str(">IIIIIIII")) + + def __init__(self): + pass + + def toBytes(self): + pl = self.plStruct.pack(0, 0, 0, 0, 0, 0, 0, 0) + return AwlSimMessage.toBytes(self, len(pl)) + pl + + @classmethod + def fromBytes(cls, payload): + try: + _, _, _, _, _, _, _, _ =\ + cls.plStruct.unpack(payload) + except struct.error as e: + raise TransferError("RESET: Invalid data format") + return cls() + class AwlSimMessage_SHUTDOWN(AwlSimMessage): msgId = AwlSimMessage.MSG_ID_SHUTDOWN + EnumGen.start + SHUTDOWN_CORE = EnumGen.item + SHUTDOWN_SYSTEM_HALT = EnumGen.item + SHUTDOWN_SYSTEM_REBOOT = EnumGen.item + EnumGen.end + + SHUTDOWN_MAGIC = 0x7B8F + + # Payload struct: + # magic number (16 bit) + # shutdownType (16 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + + plStruct = struct.Struct(str(">HHIIIIIII")) + + def __init__(self, shutdownType): + self.shutdownType = shutdownType & 0xFFFF + + def toBytes(self): + pl = self.plStruct.pack(self.SHUTDOWN_MAGIC, + self.shutdownType, + 0, 0, 0, 0, 0, 0, 0) + return AwlSimMessage.toBytes(self, len(pl)) + pl + + @classmethod + def fromBytes(cls, payload): + try: + magic, shutdownType, _, _, _, _, _, _, _ =\ + cls.plStruct.unpack(payload) + if magic != cls.SHUTDOWN_MAGIC: + raise TransferError("SHUTDOWN: Incorrect magic number") + except struct.error as e: + raise TransferError("SHUTDOWN: Invalid data format") + return cls(shutdownType) + class AwlSimMessage_RUNSTATE(AwlSimMessage): msgId = AwlSimMessage.MSG_ID_RUNSTATE @@ -288,6 +357,17 @@ class AwlSimMessage_RUNSTATE(AwlSimMessage): STATE_RUN = EnumGen.item EnumGen.end + # Payload struct: + # runState (16 bit) + # reserved (16 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + # reserved (32 bit) + plStruct = struct.Struct(str(">HHIIIIIII")) def __init__(self, runState): @@ -620,7 +700,7 @@ def fromBytes(cls, payload): class AwlSimMessage_BUILD(AwlSimMessage): msgId = AwlSimMessage.MSG_ID_BUILD - plStruct = struct.Struct(str(">16x")) + plStruct = struct.Struct(str(">32x")) def toBytes(self): try: diff --git a/awlsim/coreserver/server.py b/awlsim/coreserver/server.py index 31c375158..3534ce7dc 100644 --- a/awlsim/coreserver/server.py +++ b/awlsim/coreserver/server.py @@ -956,9 +956,32 @@ def __rx_SHUTDOWN(self, client, msg): printDebug("Received message: SHUTDOWN") status = AwlSimMessage_REPLY.STAT_FAIL if self.__commandMask & AwlSimServer.CMDMSK_SHUTDOWN: - printInfo("Exiting due to shutdown command") - self.setRunState(self.STATE_EXIT) - status = AwlSimMessage_REPLY.STAT_OK + if msg.shutdownType == msg.SHUTDOWN_CORE: + printInfo("Exiting due to shutdown command") + self.setRunState(self.STATE_EXIT) + status = AwlSimMessage_REPLY.STAT_OK + elif msg.shutdownType == msg.SHUTDOWN_SYSTEM_HALT: + if osIsLinux: + printInfo("Halting system due to shutdown command") + self.setRunState(self.STATE_EXIT) + process = PopenWrapper(["/sbin/shutdown"], + AwlSimEnv.getEnv()) + process.wait() + status = AwlSimMessage_REPLY.STAT_OK + else: + printError("Halting system is not supported.") + elif msg.shutdownType == msg.SHUTDOWN_SYSTEM_REBOOT: + if osIsLinux: + printInfo("Rebooting system due to shutdown command") + self.setRunState(self.STATE_EXIT) + process = PopenWrapper(["/sbin/reboot"], + AwlSimEnv.getEnv()) + process.wait() + status = AwlSimMessage_REPLY.STAT_OK + else: + printError("Rebooting system is not supported.") + else: + printError("Unknown shutdown command") client.transceiver.send(AwlSimMessage_REPLY.make(msg, status)) def __rx_RUNSTATE(self, client, msg):