openrvdas.server.server_api_command_line
Command line interface for server API.
Includes a script that maintains an InMemoryServerAPI instance and allows you to modify it. Run via
server/server_api_command_line.py
and type "help" for list of valid commands.
Also see server/server_api.py for full documentation on the ServerAPI.
1#!/usr/bin/env python3 2"""Command line interface for server API. 3 4Includes a script that maintains an InMemoryServerAPI instance and 5allows you to modify it. Run via 6``` 7 server/server_api_command_line.py 8``` 9and type "help" for list of valid commands. 10 11Also see server/server_api.py for full documentation on the ServerAPI. 12""" 13 14import argparse 15import atexit 16import getpass # to get username 17import json 18import logging 19import os 20import pprint 21import readline 22import signal 23import socket # to get hostname 24 25from logger.utils.read_config import read_config, expand_cruise_definition # noqa: E402 26from server.server_api import ServerAPI # noqa: E402 27 28LOGGING_FORMAT = '%(asctime)-15s %(filename)s:%(lineno)d %(message)s' 29LOG_LEVELS = {0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG} 30 31SOURCE_NAME = 'CommandLine' 32USER = getpass.getuser() 33HOSTNAME = socket.gethostname() 34 35 36############################ 37def kill_handler(self, signum): 38 """Translate an external signal (such as we'd get from os.kill) into a 39 KeyboardInterrupt, which will signal the start() loop to exit nicely.""" 40 raise KeyboardInterrupt('Received external kill signal') 41 42 43################################################################################ 44# Definitions for running from command line 45class ServerAPICommandLine: 46 def __init__(self, api): 47 """Argument api is a ServerAPI subclass instance, either an 48 InMemoryServerAPI or DjangoServerAPI as of this writing. 49 """ 50 if not issubclass(type(api), ServerAPI): 51 raise ValueError('Passed api "%s" must be subclass of ServerAPI' % api) 52 self.api = api 53 self.quit_requested = False 54 55 try: 56 signal.signal(signal.SIGTERM, kill_handler) 57 except ValueError: 58 logging.info('ServerAPICommandLine not running in main thread; ' 59 'shutting down with Ctl-C may not work.') 60 61 ############################ 62 def quit(self): 63 logging.info('ServerAPICommandLine - quit requested') 64 self.quit_requested = True 65 self.api.quit() 66 67 ############################ 68 def run(self): 69 """Iterate, reading commands and processing them.""" 70 try: 71 self.api.message_log(source=SOURCE_NAME, 72 user='(%s@%s)' % (USER, HOSTNAME), 73 log_level=self.api.INFO, 74 message='started') 75 while not self.quit_requested: 76 command = input('command? ') 77 if command: 78 command = command.strip() 79 self.process_command(command) 80 81 except (KeyboardInterrupt, EOFError): 82 logging.warning('ServerAPICommandLine.run() received Keyboard Interrupt') 83 except Exception as e: 84 logging.error(str(e)) 85 86 # Signal cleanup 87 self.quit() 88 89 ############################ 90 def show_commands(self): 91 """Print summary of commands we can send to API.""" 92 commands = [ 93 # ('cruises', 'Show list of loaded cruises'), 94 ('load_configuration <configuration file name>', 95 'Load a new config from file and set to default mode'), 96 ('reload_configuration', 97 'Reload the current configuration file and update any loggers whose ' 98 'that configurations have changed'), 99 ('set_configuration <JSON encoding of a configuration>', 100 'Load a new configuration from passed JSON encoding'), 101 ('delete_configuration', 102 'Delete the current configuration from the server\n'), 103 104 ('get_active_mode', 'Get currently active mode'), 105 ('get_modes', 'Get list of all defined modes'), 106 ('set_active_mode <name of mode>', 'Set new current mode\n'), 107 108 ('get_loggers', 'Get list of all defined loggers'), 109 ('get_active_logger_configs', 'Get names of active logger configurations\n'), 110 111 ('get_logger_configs <logger>', 112 'Get names of all configurations for specified logger'), 113 ('set_active_logger_config <logger name> <name of logger config>', 114 'Set logger to named configuration\n'), 115 116 ('get_status', 117 'Print most recent status for each logger'), 118 ('get_status_since <timestamp>', 119 'Get all logger status updates since specified timestamp\n'), 120 121 ('get_server_log [timestamp]', 122 'Print most recent log message for server, optionally all messages ' 123 'since specified timestamp\n'), 124 125 ('quit', 'Quit gracefully') 126 ] 127 print('Valid commands:') 128 for command, desc in commands: 129 print(' %s\n %s' % (command, desc)) 130 131 ############################ 132 def process_command(self, command): 133 """Parse and execute the command string we've received.""" 134 try: 135 if not command: 136 logging.info('Empty command received') 137 138 # elif command == 'cruises': 139 # cruises = self.api.get_cruises() 140 # if cruises: 141 # print('Loaded cruises: ' + ', '.join(cruises)) 142 # else: 143 # print('No cruises loaded') 144 145 # load_configuration <cruise config file name> 146 elif command == 'load_configuration': 147 raise ValueError('format: load_configuration <config file name>') 148 elif command.find('load_configuration ') == 0: 149 (load_cmd, filename) = command.split(maxsplit=1) 150 logging.info('Loading config from %s', filename) 151 try: 152 # Load the file to memory and parse to a dict. Add the name 153 # of the file we've just loaded to the dict. 154 config = read_config(filename) 155 config = expand_cruise_definition(config) 156 157 if 'cruise' in config: 158 config['cruise']['config_filename'] = filename 159 self.api.load_configuration(config) 160 default_mode = self.api.get_default_mode() 161 if default_mode: 162 self.api.set_active_mode(default_mode) 163 except FileNotFoundError: 164 logging.error('Unable to find file "%s"', filename) 165 166 # reload_configuration <cruise config file name> 167 # Main difference here is that we already have the filename, and 168 # we *don't* reset everything to the default mode after loading. 169 elif command == 'reload_configuration': 170 try: 171 # Look up the filename of the current cruise_definition. 172 cruise = self.api.get_configuration() 173 filename = cruise['config_filename'] 174 175 # Load the file to memory and parse to a dict. Add the name 176 # of the file we've just loaded to the dict. 177 config = read_config(filename) 178 if 'cruise' in config: 179 config['config_filename'] = filename 180 self.api.load_configuration(config) 181 except FileNotFoundError: 182 logging.error('Unable to find file "%s"', filename) 183 184 # set_cruise <JSON encoding of a cruise> 185 elif command == 'set_configuration': 186 raise ValueError('format: set_configuration <JSON encoding of config>') 187 elif command.find('set_configuration ') == 0: 188 (cruise_cmd, config_json) = command.split(maxsplit=1) 189 logging.info('Setting config to %s', config_json) 190 self.api.load_configuration(json.loads(config_json)) 191 default_mode = self.api.get_default_mode() 192 if default_mode: 193 self.api.set_active_mode(default_mode) 194 195 # delete_cruise <cruise_id> 196 # elif command == 'delete_configuration': 197 # raise ValueError('format: delete_configuration') 198 elif command.find('delete_configuration') == 0: 199 logging.info('Deleting config') 200 self.api.delete_configuration() 201 202 # modes <cruise_id> 203 # elif command == 'modes': 204 # raise ValueError('format: modes') 205 elif command.find('get_modes') == 0: 206 (mode_cmd) = command.split(maxsplit=1) 207 modes = self.api.get_modes() 208 if len(modes) > 0: 209 print('Available Modes: %s' % (', '.join(modes))) 210 else: 211 print('Available Modes: n/a') 212 213 ############################ 214 # mode <cruise_id> 215 # elif command == 'mode': 216 # raise ValueError('format: mode') 217 elif command.find('get_active_mode') == 0: 218 (mode_cmd) = command.split(maxsplit=1) 219 mode = self.api.get_active_mode() 220 print('Current mode: %s' % (mode)) 221 222 # set_active_mode <mode> 223 elif command == 'set_active_mode': 224 raise ValueError('format: set_active_mode <mode>') 225 elif command.find('set_active_mode ') == 0: 226 (mode_cmd, mode_name) = command.split(maxsplit=1) 227 logging.info('Setting mode to %s', mode_name) 228 self.api.set_active_mode(mode_name) 229 230 ############################ 231 # loggers <cruise_id> 232 # elif command == 'loggers': 233 # raise ValueError('format: loggers') 234 elif command.find('get_loggers') == 0: 235 loggers = self.api.get_loggers() 236 if len(loggers) > 0: 237 print('Loggers: %s' % (', '.join(loggers))) 238 else: 239 print('Loggers: n/a') 240 241 ############################ 242 # logger_configs <cruise_id> <logger> 243 elif command == 'get_logger_configs': 244 raise ValueError('format: get_logger_configs <logger name>') 245 elif command.find('get_logger_configs ') == 0: 246 (logger_cmd, logger_name) = command.split(maxsplit=1) 247 logger_configs = self.api.get_logger_config_names(logger_name) 248 print('Configs for %s: %s' % 249 (logger_name, ', '.join(logger_configs))) 250 251 ############################ 252 # set_logger_config_name <cruise_id> <logger name> <name of logger config> 253 elif command == 'set_active_logger_config': 254 raise ValueError( 255 'format: set_active_logger_config <logger name> <name of logger config>') 256 elif command.find('set_active_logger_config ') == 0: 257 (logger_cmd, 258 logger_name, config_name) = command.split(maxsplit=2) 259 logging.info('Setting logger %s to config %s', logger_name, config_name) 260 261 # Is this a valid config for this logger? 262 if config_name not in self.api.get_logger_config_names(logger_name): 263 raise ValueError('Config "%s" is not valid for logger "%s"' 264 % (config_name, logger_name)) 265 self.api.set_active_logger_config(logger_name, config_name) 266 267 ############################ 268 # configs <cruise_id> 269 # elif command == 'configs': 270 # raise ValueError('format: configs') 271 elif command.find('get_active_logger_configs') == 0: 272 config_names = {logger_id: self.api.get_logger_config_name(logger_id) 273 for logger_id in self.api.get_loggers()} 274 if len(config_names) > 0: 275 for logger_id, config_name in config_names.items(): 276 print('%s: %s' % (logger_id, config_name)) 277 else: 278 print("No configs found!") 279 280 ############################ 281 # status 282 # elif command == 'status': 283 # raise ValueError('format: status') 284 elif command.find('get_status') == 0: 285 (status_cmd) = command.split(maxsplit=1) 286 status_dict = self.api.get_status() 287 print('%s' % pprint.pformat(status_dict)) 288 289 ############################ 290 # status_since 291 elif command == 'get_status_since': 292 raise ValueError('format: get_status_since <timestamp>') 293 elif command.find('get_status_since ') == 0: 294 (status_cmd, since_timestamp) = command.split(maxsplit=1) 295 status_dict = self.api.get_status(float(since_timestamp)) 296 print('%s' % pprint.pformat(status_dict)) 297 298 ############################ 299 # server_log 300 elif command == 'get_server_log': 301 server_log = self.api.get_message_log(source=SOURCE_NAME) 302 print('%s' % pprint.pformat(server_log)) 303 304 ############################ 305 # server_log timestamp 306 elif command.find('get_server_log') == 0: 307 (log_cmd, since_timestamp) = command.split(maxsplit=1) 308 server_log = self.api.get_message_log(source=SOURCE_NAME, user=None, 309 log_level=self.api.DEBUG, 310 since_timestamp=float(since_timestamp)) 311 print('%s' % pprint.pformat(server_log)) 312 313 ############################ 314 # Quit gracefully 315 elif command == 'quit': 316 logging.info('Got quit command') 317 self.quit() 318 319 ############################ 320 elif command == 'help': 321 self.show_commands() 322 323 ############################ 324 else: 325 print('Got unknown command: "{}"'.format(command)) 326 print('Type "help" for help') 327 328 except ValueError as e: 329 logging.error('%s', e) 330 finally: 331 self.api.message_log(source=SOURCE_NAME, 332 user='(%s@%s)' % (USER, HOSTNAME), 333 log_level=self.api.INFO, 334 message='command: ' + command) 335 336 337################################################################################ 338if __name__ == '__main__': 339 340 parser = argparse.ArgumentParser() 341 parser.add_argument('--database', dest='database', action='store', 342 choices=['memory', 'django', 'sqlite'], 343 default='memory', help='What backing store database ' 344 'to use. Currently-implemented options are "memory" ' 345 'and "django".') 346 parser.add_argument('-v', '--verbosity', dest='verbosity', 347 default=0, action='count', 348 help='Increase output verbosity') 349 parser.add_argument('-V', '--logger_verbosity', dest='logger_verbosity', 350 default=0, action='count', 351 help='Increase output verbosity of component loggers') 352 args = parser.parse_args() 353 354 # Set logging verbosity 355 LOGGING_FORMAT = '%(asctime)-15s %(filename)s:%(lineno)d %(message)s' 356 LOG_LEVELS = {0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG} 357 args.verbosity = min(args.verbosity, max(LOG_LEVELS)) 358 logging.getLogger().setLevel(LOG_LEVELS[args.verbosity]) 359 360 # Enable command line editing and history 361 histfile = '.openrvdas_command_line_history' 362 histpath = os.path.join(os.path.expanduser('~'), histfile) 363 try: 364 readline.read_history_file(histpath) 365 # default history len is -1 (infinite), which may grow unruly 366 readline.set_history_length(1000) 367 except (FileNotFoundError, PermissionError): 368 pass 369 atexit.register(readline.write_history_file, histpath) 370 371 ############################ 372 # Instantiate API - a Are we using an in-memory store or Django 373 # database as our backing store? Do our imports conditionally, so 374 # they don't actually have to have Django if they're not using it. 375 if args.database == 'django': 376 from django_gui.django_server_api import DjangoServerAPI 377 api = DjangoServerAPI() 378 elif args.database == 'memory': 379 from server.in_memory_server_api import InMemoryServerAPI 380 api = InMemoryServerAPI() 381 elif args.database == 'sqlite': 382 from server.sqlite_server_api import SQLiteServerAPI 383 api = SQLiteServerAPI() 384 else: 385 raise ValueError('Illegal arg for --database: "%s"' % args.database) 386 command_line_reader = ServerAPICommandLine(api) 387 command_line_reader.run()
LOGGING_FORMAT =
'%(asctime)-15s %(filename)s:%(lineno)d %(message)s'
LOG_LEVELS =
{0: 30, 1: 20, 2: 10}
SOURCE_NAME =
'CommandLine'
USER =
'runner'
HOSTNAME =
'runnervmeorf1'
def
kill_handler(self, signum):
38def kill_handler(self, signum): 39 """Translate an external signal (such as we'd get from os.kill) into a 40 KeyboardInterrupt, which will signal the start() loop to exit nicely.""" 41 raise KeyboardInterrupt('Received external kill signal')
Translate an external signal (such as we'd get from os.kill) into a KeyboardInterrupt, which will signal the start() loop to exit nicely.
class
ServerAPICommandLine:
46class ServerAPICommandLine: 47 def __init__(self, api): 48 """Argument api is a ServerAPI subclass instance, either an 49 InMemoryServerAPI or DjangoServerAPI as of this writing. 50 """ 51 if not issubclass(type(api), ServerAPI): 52 raise ValueError('Passed api "%s" must be subclass of ServerAPI' % api) 53 self.api = api 54 self.quit_requested = False 55 56 try: 57 signal.signal(signal.SIGTERM, kill_handler) 58 except ValueError: 59 logging.info('ServerAPICommandLine not running in main thread; ' 60 'shutting down with Ctl-C may not work.') 61 62 ############################ 63 def quit(self): 64 logging.info('ServerAPICommandLine - quit requested') 65 self.quit_requested = True 66 self.api.quit() 67 68 ############################ 69 def run(self): 70 """Iterate, reading commands and processing them.""" 71 try: 72 self.api.message_log(source=SOURCE_NAME, 73 user='(%s@%s)' % (USER, HOSTNAME), 74 log_level=self.api.INFO, 75 message='started') 76 while not self.quit_requested: 77 command = input('command? ') 78 if command: 79 command = command.strip() 80 self.process_command(command) 81 82 except (KeyboardInterrupt, EOFError): 83 logging.warning('ServerAPICommandLine.run() received Keyboard Interrupt') 84 except Exception as e: 85 logging.error(str(e)) 86 87 # Signal cleanup 88 self.quit() 89 90 ############################ 91 def show_commands(self): 92 """Print summary of commands we can send to API.""" 93 commands = [ 94 # ('cruises', 'Show list of loaded cruises'), 95 ('load_configuration <configuration file name>', 96 'Load a new config from file and set to default mode'), 97 ('reload_configuration', 98 'Reload the current configuration file and update any loggers whose ' 99 'that configurations have changed'), 100 ('set_configuration <JSON encoding of a configuration>', 101 'Load a new configuration from passed JSON encoding'), 102 ('delete_configuration', 103 'Delete the current configuration from the server\n'), 104 105 ('get_active_mode', 'Get currently active mode'), 106 ('get_modes', 'Get list of all defined modes'), 107 ('set_active_mode <name of mode>', 'Set new current mode\n'), 108 109 ('get_loggers', 'Get list of all defined loggers'), 110 ('get_active_logger_configs', 'Get names of active logger configurations\n'), 111 112 ('get_logger_configs <logger>', 113 'Get names of all configurations for specified logger'), 114 ('set_active_logger_config <logger name> <name of logger config>', 115 'Set logger to named configuration\n'), 116 117 ('get_status', 118 'Print most recent status for each logger'), 119 ('get_status_since <timestamp>', 120 'Get all logger status updates since specified timestamp\n'), 121 122 ('get_server_log [timestamp]', 123 'Print most recent log message for server, optionally all messages ' 124 'since specified timestamp\n'), 125 126 ('quit', 'Quit gracefully') 127 ] 128 print('Valid commands:') 129 for command, desc in commands: 130 print(' %s\n %s' % (command, desc)) 131 132 ############################ 133 def process_command(self, command): 134 """Parse and execute the command string we've received.""" 135 try: 136 if not command: 137 logging.info('Empty command received') 138 139 # elif command == 'cruises': 140 # cruises = self.api.get_cruises() 141 # if cruises: 142 # print('Loaded cruises: ' + ', '.join(cruises)) 143 # else: 144 # print('No cruises loaded') 145 146 # load_configuration <cruise config file name> 147 elif command == 'load_configuration': 148 raise ValueError('format: load_configuration <config file name>') 149 elif command.find('load_configuration ') == 0: 150 (load_cmd, filename) = command.split(maxsplit=1) 151 logging.info('Loading config from %s', filename) 152 try: 153 # Load the file to memory and parse to a dict. Add the name 154 # of the file we've just loaded to the dict. 155 config = read_config(filename) 156 config = expand_cruise_definition(config) 157 158 if 'cruise' in config: 159 config['cruise']['config_filename'] = filename 160 self.api.load_configuration(config) 161 default_mode = self.api.get_default_mode() 162 if default_mode: 163 self.api.set_active_mode(default_mode) 164 except FileNotFoundError: 165 logging.error('Unable to find file "%s"', filename) 166 167 # reload_configuration <cruise config file name> 168 # Main difference here is that we already have the filename, and 169 # we *don't* reset everything to the default mode after loading. 170 elif command == 'reload_configuration': 171 try: 172 # Look up the filename of the current cruise_definition. 173 cruise = self.api.get_configuration() 174 filename = cruise['config_filename'] 175 176 # Load the file to memory and parse to a dict. Add the name 177 # of the file we've just loaded to the dict. 178 config = read_config(filename) 179 if 'cruise' in config: 180 config['config_filename'] = filename 181 self.api.load_configuration(config) 182 except FileNotFoundError: 183 logging.error('Unable to find file "%s"', filename) 184 185 # set_cruise <JSON encoding of a cruise> 186 elif command == 'set_configuration': 187 raise ValueError('format: set_configuration <JSON encoding of config>') 188 elif command.find('set_configuration ') == 0: 189 (cruise_cmd, config_json) = command.split(maxsplit=1) 190 logging.info('Setting config to %s', config_json) 191 self.api.load_configuration(json.loads(config_json)) 192 default_mode = self.api.get_default_mode() 193 if default_mode: 194 self.api.set_active_mode(default_mode) 195 196 # delete_cruise <cruise_id> 197 # elif command == 'delete_configuration': 198 # raise ValueError('format: delete_configuration') 199 elif command.find('delete_configuration') == 0: 200 logging.info('Deleting config') 201 self.api.delete_configuration() 202 203 # modes <cruise_id> 204 # elif command == 'modes': 205 # raise ValueError('format: modes') 206 elif command.find('get_modes') == 0: 207 (mode_cmd) = command.split(maxsplit=1) 208 modes = self.api.get_modes() 209 if len(modes) > 0: 210 print('Available Modes: %s' % (', '.join(modes))) 211 else: 212 print('Available Modes: n/a') 213 214 ############################ 215 # mode <cruise_id> 216 # elif command == 'mode': 217 # raise ValueError('format: mode') 218 elif command.find('get_active_mode') == 0: 219 (mode_cmd) = command.split(maxsplit=1) 220 mode = self.api.get_active_mode() 221 print('Current mode: %s' % (mode)) 222 223 # set_active_mode <mode> 224 elif command == 'set_active_mode': 225 raise ValueError('format: set_active_mode <mode>') 226 elif command.find('set_active_mode ') == 0: 227 (mode_cmd, mode_name) = command.split(maxsplit=1) 228 logging.info('Setting mode to %s', mode_name) 229 self.api.set_active_mode(mode_name) 230 231 ############################ 232 # loggers <cruise_id> 233 # elif command == 'loggers': 234 # raise ValueError('format: loggers') 235 elif command.find('get_loggers') == 0: 236 loggers = self.api.get_loggers() 237 if len(loggers) > 0: 238 print('Loggers: %s' % (', '.join(loggers))) 239 else: 240 print('Loggers: n/a') 241 242 ############################ 243 # logger_configs <cruise_id> <logger> 244 elif command == 'get_logger_configs': 245 raise ValueError('format: get_logger_configs <logger name>') 246 elif command.find('get_logger_configs ') == 0: 247 (logger_cmd, logger_name) = command.split(maxsplit=1) 248 logger_configs = self.api.get_logger_config_names(logger_name) 249 print('Configs for %s: %s' % 250 (logger_name, ', '.join(logger_configs))) 251 252 ############################ 253 # set_logger_config_name <cruise_id> <logger name> <name of logger config> 254 elif command == 'set_active_logger_config': 255 raise ValueError( 256 'format: set_active_logger_config <logger name> <name of logger config>') 257 elif command.find('set_active_logger_config ') == 0: 258 (logger_cmd, 259 logger_name, config_name) = command.split(maxsplit=2) 260 logging.info('Setting logger %s to config %s', logger_name, config_name) 261 262 # Is this a valid config for this logger? 263 if config_name not in self.api.get_logger_config_names(logger_name): 264 raise ValueError('Config "%s" is not valid for logger "%s"' 265 % (config_name, logger_name)) 266 self.api.set_active_logger_config(logger_name, config_name) 267 268 ############################ 269 # configs <cruise_id> 270 # elif command == 'configs': 271 # raise ValueError('format: configs') 272 elif command.find('get_active_logger_configs') == 0: 273 config_names = {logger_id: self.api.get_logger_config_name(logger_id) 274 for logger_id in self.api.get_loggers()} 275 if len(config_names) > 0: 276 for logger_id, config_name in config_names.items(): 277 print('%s: %s' % (logger_id, config_name)) 278 else: 279 print("No configs found!") 280 281 ############################ 282 # status 283 # elif command == 'status': 284 # raise ValueError('format: status') 285 elif command.find('get_status') == 0: 286 (status_cmd) = command.split(maxsplit=1) 287 status_dict = self.api.get_status() 288 print('%s' % pprint.pformat(status_dict)) 289 290 ############################ 291 # status_since 292 elif command == 'get_status_since': 293 raise ValueError('format: get_status_since <timestamp>') 294 elif command.find('get_status_since ') == 0: 295 (status_cmd, since_timestamp) = command.split(maxsplit=1) 296 status_dict = self.api.get_status(float(since_timestamp)) 297 print('%s' % pprint.pformat(status_dict)) 298 299 ############################ 300 # server_log 301 elif command == 'get_server_log': 302 server_log = self.api.get_message_log(source=SOURCE_NAME) 303 print('%s' % pprint.pformat(server_log)) 304 305 ############################ 306 # server_log timestamp 307 elif command.find('get_server_log') == 0: 308 (log_cmd, since_timestamp) = command.split(maxsplit=1) 309 server_log = self.api.get_message_log(source=SOURCE_NAME, user=None, 310 log_level=self.api.DEBUG, 311 since_timestamp=float(since_timestamp)) 312 print('%s' % pprint.pformat(server_log)) 313 314 ############################ 315 # Quit gracefully 316 elif command == 'quit': 317 logging.info('Got quit command') 318 self.quit() 319 320 ############################ 321 elif command == 'help': 322 self.show_commands() 323 324 ############################ 325 else: 326 print('Got unknown command: "{}"'.format(command)) 327 print('Type "help" for help') 328 329 except ValueError as e: 330 logging.error('%s', e) 331 finally: 332 self.api.message_log(source=SOURCE_NAME, 333 user='(%s@%s)' % (USER, HOSTNAME), 334 log_level=self.api.INFO, 335 message='command: ' + command)
ServerAPICommandLine(api)
47 def __init__(self, api): 48 """Argument api is a ServerAPI subclass instance, either an 49 InMemoryServerAPI or DjangoServerAPI as of this writing. 50 """ 51 if not issubclass(type(api), ServerAPI): 52 raise ValueError('Passed api "%s" must be subclass of ServerAPI' % api) 53 self.api = api 54 self.quit_requested = False 55 56 try: 57 signal.signal(signal.SIGTERM, kill_handler) 58 except ValueError: 59 logging.info('ServerAPICommandLine not running in main thread; ' 60 'shutting down with Ctl-C may not work.')
Argument api is a ServerAPI subclass instance, either an InMemoryServerAPI or DjangoServerAPI as of this writing.
def
run(self):
69 def run(self): 70 """Iterate, reading commands and processing them.""" 71 try: 72 self.api.message_log(source=SOURCE_NAME, 73 user='(%s@%s)' % (USER, HOSTNAME), 74 log_level=self.api.INFO, 75 message='started') 76 while not self.quit_requested: 77 command = input('command? ') 78 if command: 79 command = command.strip() 80 self.process_command(command) 81 82 except (KeyboardInterrupt, EOFError): 83 logging.warning('ServerAPICommandLine.run() received Keyboard Interrupt') 84 except Exception as e: 85 logging.error(str(e)) 86 87 # Signal cleanup 88 self.quit()
Iterate, reading commands and processing them.
def
show_commands(self):
91 def show_commands(self): 92 """Print summary of commands we can send to API.""" 93 commands = [ 94 # ('cruises', 'Show list of loaded cruises'), 95 ('load_configuration <configuration file name>', 96 'Load a new config from file and set to default mode'), 97 ('reload_configuration', 98 'Reload the current configuration file and update any loggers whose ' 99 'that configurations have changed'), 100 ('set_configuration <JSON encoding of a configuration>', 101 'Load a new configuration from passed JSON encoding'), 102 ('delete_configuration', 103 'Delete the current configuration from the server\n'), 104 105 ('get_active_mode', 'Get currently active mode'), 106 ('get_modes', 'Get list of all defined modes'), 107 ('set_active_mode <name of mode>', 'Set new current mode\n'), 108 109 ('get_loggers', 'Get list of all defined loggers'), 110 ('get_active_logger_configs', 'Get names of active logger configurations\n'), 111 112 ('get_logger_configs <logger>', 113 'Get names of all configurations for specified logger'), 114 ('set_active_logger_config <logger name> <name of logger config>', 115 'Set logger to named configuration\n'), 116 117 ('get_status', 118 'Print most recent status for each logger'), 119 ('get_status_since <timestamp>', 120 'Get all logger status updates since specified timestamp\n'), 121 122 ('get_server_log [timestamp]', 123 'Print most recent log message for server, optionally all messages ' 124 'since specified timestamp\n'), 125 126 ('quit', 'Quit gracefully') 127 ] 128 print('Valid commands:') 129 for command, desc in commands: 130 print(' %s\n %s' % (command, desc))
Print summary of commands we can send to API.
def
process_command(self, command):
133 def process_command(self, command): 134 """Parse and execute the command string we've received.""" 135 try: 136 if not command: 137 logging.info('Empty command received') 138 139 # elif command == 'cruises': 140 # cruises = self.api.get_cruises() 141 # if cruises: 142 # print('Loaded cruises: ' + ', '.join(cruises)) 143 # else: 144 # print('No cruises loaded') 145 146 # load_configuration <cruise config file name> 147 elif command == 'load_configuration': 148 raise ValueError('format: load_configuration <config file name>') 149 elif command.find('load_configuration ') == 0: 150 (load_cmd, filename) = command.split(maxsplit=1) 151 logging.info('Loading config from %s', filename) 152 try: 153 # Load the file to memory and parse to a dict. Add the name 154 # of the file we've just loaded to the dict. 155 config = read_config(filename) 156 config = expand_cruise_definition(config) 157 158 if 'cruise' in config: 159 config['cruise']['config_filename'] = filename 160 self.api.load_configuration(config) 161 default_mode = self.api.get_default_mode() 162 if default_mode: 163 self.api.set_active_mode(default_mode) 164 except FileNotFoundError: 165 logging.error('Unable to find file "%s"', filename) 166 167 # reload_configuration <cruise config file name> 168 # Main difference here is that we already have the filename, and 169 # we *don't* reset everything to the default mode after loading. 170 elif command == 'reload_configuration': 171 try: 172 # Look up the filename of the current cruise_definition. 173 cruise = self.api.get_configuration() 174 filename = cruise['config_filename'] 175 176 # Load the file to memory and parse to a dict. Add the name 177 # of the file we've just loaded to the dict. 178 config = read_config(filename) 179 if 'cruise' in config: 180 config['config_filename'] = filename 181 self.api.load_configuration(config) 182 except FileNotFoundError: 183 logging.error('Unable to find file "%s"', filename) 184 185 # set_cruise <JSON encoding of a cruise> 186 elif command == 'set_configuration': 187 raise ValueError('format: set_configuration <JSON encoding of config>') 188 elif command.find('set_configuration ') == 0: 189 (cruise_cmd, config_json) = command.split(maxsplit=1) 190 logging.info('Setting config to %s', config_json) 191 self.api.load_configuration(json.loads(config_json)) 192 default_mode = self.api.get_default_mode() 193 if default_mode: 194 self.api.set_active_mode(default_mode) 195 196 # delete_cruise <cruise_id> 197 # elif command == 'delete_configuration': 198 # raise ValueError('format: delete_configuration') 199 elif command.find('delete_configuration') == 0: 200 logging.info('Deleting config') 201 self.api.delete_configuration() 202 203 # modes <cruise_id> 204 # elif command == 'modes': 205 # raise ValueError('format: modes') 206 elif command.find('get_modes') == 0: 207 (mode_cmd) = command.split(maxsplit=1) 208 modes = self.api.get_modes() 209 if len(modes) > 0: 210 print('Available Modes: %s' % (', '.join(modes))) 211 else: 212 print('Available Modes: n/a') 213 214 ############################ 215 # mode <cruise_id> 216 # elif command == 'mode': 217 # raise ValueError('format: mode') 218 elif command.find('get_active_mode') == 0: 219 (mode_cmd) = command.split(maxsplit=1) 220 mode = self.api.get_active_mode() 221 print('Current mode: %s' % (mode)) 222 223 # set_active_mode <mode> 224 elif command == 'set_active_mode': 225 raise ValueError('format: set_active_mode <mode>') 226 elif command.find('set_active_mode ') == 0: 227 (mode_cmd, mode_name) = command.split(maxsplit=1) 228 logging.info('Setting mode to %s', mode_name) 229 self.api.set_active_mode(mode_name) 230 231 ############################ 232 # loggers <cruise_id> 233 # elif command == 'loggers': 234 # raise ValueError('format: loggers') 235 elif command.find('get_loggers') == 0: 236 loggers = self.api.get_loggers() 237 if len(loggers) > 0: 238 print('Loggers: %s' % (', '.join(loggers))) 239 else: 240 print('Loggers: n/a') 241 242 ############################ 243 # logger_configs <cruise_id> <logger> 244 elif command == 'get_logger_configs': 245 raise ValueError('format: get_logger_configs <logger name>') 246 elif command.find('get_logger_configs ') == 0: 247 (logger_cmd, logger_name) = command.split(maxsplit=1) 248 logger_configs = self.api.get_logger_config_names(logger_name) 249 print('Configs for %s: %s' % 250 (logger_name, ', '.join(logger_configs))) 251 252 ############################ 253 # set_logger_config_name <cruise_id> <logger name> <name of logger config> 254 elif command == 'set_active_logger_config': 255 raise ValueError( 256 'format: set_active_logger_config <logger name> <name of logger config>') 257 elif command.find('set_active_logger_config ') == 0: 258 (logger_cmd, 259 logger_name, config_name) = command.split(maxsplit=2) 260 logging.info('Setting logger %s to config %s', logger_name, config_name) 261 262 # Is this a valid config for this logger? 263 if config_name not in self.api.get_logger_config_names(logger_name): 264 raise ValueError('Config "%s" is not valid for logger "%s"' 265 % (config_name, logger_name)) 266 self.api.set_active_logger_config(logger_name, config_name) 267 268 ############################ 269 # configs <cruise_id> 270 # elif command == 'configs': 271 # raise ValueError('format: configs') 272 elif command.find('get_active_logger_configs') == 0: 273 config_names = {logger_id: self.api.get_logger_config_name(logger_id) 274 for logger_id in self.api.get_loggers()} 275 if len(config_names) > 0: 276 for logger_id, config_name in config_names.items(): 277 print('%s: %s' % (logger_id, config_name)) 278 else: 279 print("No configs found!") 280 281 ############################ 282 # status 283 # elif command == 'status': 284 # raise ValueError('format: status') 285 elif command.find('get_status') == 0: 286 (status_cmd) = command.split(maxsplit=1) 287 status_dict = self.api.get_status() 288 print('%s' % pprint.pformat(status_dict)) 289 290 ############################ 291 # status_since 292 elif command == 'get_status_since': 293 raise ValueError('format: get_status_since <timestamp>') 294 elif command.find('get_status_since ') == 0: 295 (status_cmd, since_timestamp) = command.split(maxsplit=1) 296 status_dict = self.api.get_status(float(since_timestamp)) 297 print('%s' % pprint.pformat(status_dict)) 298 299 ############################ 300 # server_log 301 elif command == 'get_server_log': 302 server_log = self.api.get_message_log(source=SOURCE_NAME) 303 print('%s' % pprint.pformat(server_log)) 304 305 ############################ 306 # server_log timestamp 307 elif command.find('get_server_log') == 0: 308 (log_cmd, since_timestamp) = command.split(maxsplit=1) 309 server_log = self.api.get_message_log(source=SOURCE_NAME, user=None, 310 log_level=self.api.DEBUG, 311 since_timestamp=float(since_timestamp)) 312 print('%s' % pprint.pformat(server_log)) 313 314 ############################ 315 # Quit gracefully 316 elif command == 'quit': 317 logging.info('Got quit command') 318 self.quit() 319 320 ############################ 321 elif command == 'help': 322 self.show_commands() 323 324 ############################ 325 else: 326 print('Got unknown command: "{}"'.format(command)) 327 print('Type "help" for help') 328 329 except ValueError as e: 330 logging.error('%s', e) 331 finally: 332 self.api.message_log(source=SOURCE_NAME, 333 user='(%s@%s)' % (USER, HOSTNAME), 334 log_level=self.api.INFO, 335 message='command: ' + command)
Parse and execute the command string we've received.