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.

api
quit_requested
def quit(self):
63    def quit(self):
64        logging.info('ServerAPICommandLine - quit requested')
65        self.quit_requested = True
66        self.api.quit()
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.