blob: 5960a85feea94e0487e87a0ddc5f608687314420 [file] [log] [blame]
Joel Kitching3e72e8b2016-07-04 17:03:00 +08001#!/usr/bin/python2
2#
3# Copyright 2016 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7from __future__ import print_function
8
9import argparse
10import logging
11import os
12import signal
13import sys
14import threading
Joel Kitching3e72e8b2016-07-04 17:03:00 +080015
16import instalog_common # pylint: disable=W0611
17from instalog import core
18from instalog import daemon_utils
Joel Kitchingd01336a2016-08-03 08:14:59 +080019from instalog import log_utils
Joel Kitching3e72e8b2016-07-04 17:03:00 +080020from instalog.utils import sync_utils
21from instalog.utils import type_utils
22
Joel Kitching3a5e76c2016-12-21 13:22:22 +080023from instalog.external import jsonrpclib
24from instalog.external import yaml
25
Joel Kitching3e72e8b2016-07-04 17:03:00 +080026
Joel Kitching860649d2016-11-21 14:53:02 +080027# The default number of seconds to wait before giving up on a flush.
chuntsendc33ae82017-01-17 15:22:16 +080028_DEFAULT_FLUSH_TIMEOUT = 30
29_DEFAULT_STOP_TIMEOUT = 10
Joel Kitching860649d2016-11-21 14:53:02 +080030
31
Joel Kitching3e72e8b2016-07-04 17:03:00 +080032class InstalogService(daemon_utils.Daemon):
33 """Represents the Instalog daemon service."""
34
35 def __init__(self, config, logging_level):
36 self._config = config
37 self._logging_level = logging_level
38 self._core = None
39 self._signal_handler_lock = threading.Lock()
40 super(InstalogService, self).__init__(
41 pidfile=config['instalog']['pid_file'])
42
43 def _SignalHandler(self, signal_num, frame):
44 """Signal handler to stop Instalog on SIGINT."""
45 logging.debug('_SignalHandler called with signalnum=%s', signal_num)
46 del frame
47 if signal_num is not signal.SIGINT:
48 return
49 self._signal_handler_lock.acquire(False)
50 if self._core:
51 logging.warning('SIGINT detected, stopping')
52 self._core.Stop()
53 self._core = None
54 self._signal_handler_lock.release()
55
56 def _InitLogging(self):
57 """Sets up logging."""
Joel Kitchingd01336a2016-08-03 08:14:59 +080058 # TODO(kitching): Refactor (some of) this code to log_utils module.
Joel Kitching3e72e8b2016-07-04 17:03:00 +080059 # Get logger.
Joel Kitching3e72e8b2016-07-04 17:03:00 +080060 logger = logging.getLogger()
61 logger.setLevel(logging.DEBUG)
62
Joel Kitching24e01242016-11-25 15:17:29 +080063 # In certain situations, a StreamHandler to stdout is created implicitly.
64 # Since we want to create our own, we need to remove the default one if it
65 # exists.
66 if logger.handlers:
67 logger.removeHandler(logger.handlers[0])
Joel Kitching3e72e8b2016-07-04 17:03:00 +080068
69 # Create formatter.
Joel Kitchingd01336a2016-08-03 08:14:59 +080070 formatter = logging.Formatter(log_utils.LOG_FORMAT)
Joel Kitching3e72e8b2016-07-04 17:03:00 +080071
72 # Save logging calls to log file.
73 fh = logging.FileHandler(self._config['instalog']['log_file'])
74 fh.setFormatter(formatter)
75 fh.setLevel(self._logging_level)
76 logger.addHandler(fh)
77
78 # Output logging calls to console (for when foreground=True).
79 sh = logging.StreamHandler()
80 sh.setFormatter(formatter)
81 sh.setLevel(self._logging_level)
82 logger.addHandler(sh)
83
84 def Run(self, foreground):
85 """Starts Instalog."""
86 self._InitLogging()
87
88 if foreground:
89 signal.signal(signal.SIGINT, self._SignalHandler)
90
91 self._core = core.Instalog(
92 node_id=self._config['instalog']['node_id'],
Joel Kitching9edac3b2016-10-23 10:40:24 +070093 data_dir=self._config['instalog']['data_dir'],
Joel Kitching3e72e8b2016-07-04 17:03:00 +080094 cli_hostname=self._config['instalog']['cli_hostname'],
95 cli_port=self._config['instalog']['cli_port'],
96 buffer_plugin=self._config['buffer'],
97 input_plugins=self._config['input'],
98 output_plugins=self._config['output'])
99 self._core.Run()
100
101
102class InstalogCLI(object):
103 """Represents the CLI interface used to control Instalog."""
104
105 def __init__(self, args):
106 # Read config file.
107 config_path = self._LocateConfigFile(args.config)
108 if config_path is None:
109 exit('No config file found')
110 with open(config_path) as f:
111 config = yaml.load(f)
112
113 # logging.WARNING = 30, logging.INFO = 20, logging.DEBUG = 10
114 logging_level = logging.INFO - ((args.verbose - args.quiet) * 10)
115
116 self._service = InstalogService(config, logging_level)
117 self._core = jsonrpclib.Server(
118 'http://%s:%s' % (config['instalog']['cli_hostname'],
119 config['instalog']['cli_port']))
120
121 if args.cmd == 'start':
122 self.Start(args.foreground)
123 elif args.cmd == 'stop':
chuntsendc33ae82017-01-17 15:22:16 +0800124 self.Stop(args.timeout)
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800125 elif args.cmd == 'restart':
126 self.Restart()
127 elif args.cmd == 'status':
128 self.Status()
Joel Kitching23293972016-11-18 17:24:47 +0800129 elif args.cmd == 'inspect':
130 self.Inspect(args.plugin_id, args.json_path)
Joel Kitching860649d2016-11-21 14:53:02 +0800131 elif args.cmd == 'flush':
132 self.Flush(args.plugin_id, args.timeout)
Joel Kitching23293972016-11-18 17:24:47 +0800133
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800134
135 def _LocateConfigFile(self, user_path):
136 """Locates the config file that should be used by Instalog."""
137 if user_path:
138 return user_path
139 paths = [
140 os.path.join(os.getcwd(), 'instalog.yaml'),
141 os.path.join(os.path.dirname(os.path.realpath(__file__)),
142 'instalog.yaml'),
143 os.path.join(os.path.expanduser('~'), '.instalog.yaml'),
Joel Kitching24e01242016-11-25 15:17:29 +0800144 os.path.join(os.sep, 'etc', 'instalog.yaml'),
Hung-Te Linb0a32bb2017-01-05 15:11:06 +0800145 os.path.join(os.sep, 'run', 'instalog.yaml')]
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800146 for path in paths:
147 logging.debug('Checking %s for config file...', path)
148 if os.path.exists(path):
149 logging.info('Config file found at %s', path)
150 return path
151
152 def Restart(self):
153 """Restarts the daemon."""
chuntsendc33ae82017-01-17 15:22:16 +0800154 self.Stop(_DEFAULT_STOP_TIMEOUT)
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800155 self.Start(False)
156
157 def Start(self, foreground):
158 """Starts the daemon.
159
160 Args:
161 foreground: Does not detach the daemon.
162 """
163 print('Starting...')
164 self._service.Start(foreground)
165 if foreground:
166 return
Joel Kitching11bbbe72016-12-07 11:43:38 +0800167
168 # First, wait for the daemon process to start.
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800169 try:
170 sync_utils.WaitFor(self._service.IsRunning, 10)
171 except type_utils.TimeoutError:
Joel Kitching11bbbe72016-12-07 11:43:38 +0800172 print('Daemon could not be brought up, check the logs')
173 sys.exit(1)
174
175 def TryIsUp():
176 try:
177 # Perform the real check to see if Instalog is up internally.
178 return self._core.IsUp()
179 except Exception:
180 raise type_utils.TimeoutError('Could not call core IsUp')
181
182 try:
183 if sync_utils.WaitFor(TryIsUp, 10):
184 print('DONE')
185 return
186 except type_utils.TimeoutError:
187 pass
188 print('Daemon could not be brought up, check the logs')
189 sys.exit(1)
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800190
chuntsendc33ae82017-01-17 15:22:16 +0800191 def Stop(self, timeout):
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800192 """Stops the daemon."""
193 # First, send the "stop" instruction to the daemon.
194 print('Stopping...')
195 try:
196 self._core.Stop()
197 except Exception:
198 print('Could not connect to daemon, is it running?')
199 sys.exit(1)
200
201 # Then, wait for the process to come down.
202 try:
chuntsendc33ae82017-01-17 15:22:16 +0800203 sync_utils.WaitFor(self._service.IsStopped, timeout)
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800204 except type_utils.TimeoutError:
205 print('Still shutting down?')
206 sys.exit(1)
207 else:
208 print('DONE')
209
210 def Status(self):
211 """Prints the status of the daemon."""
212 running = self._service.IsRunning()
213 print('UP' if running else 'DOWN')
214
Joel Kitching23293972016-11-18 17:24:47 +0800215 def Inspect(self, plugin_id, json_path):
216 """Inspects the store of a given plugin."""
217 success, value = self._core.Inspect(plugin_id, json_path)
218 print(value)
219 if not success:
220 sys.exit(1)
221
Joel Kitching860649d2016-11-21 14:53:02 +0800222 def Flush(self, plugin_id, timeout):
223 """Flushes the given plugin with given timeout."""
224 success, value = self._core.Flush(plugin_id, timeout)
225 print(value)
226 if not success:
227 sys.exit(1)
228
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800229
230if __name__ == '__main__':
231 parser = argparse.ArgumentParser()
232 parser.add_argument(
233 '--config', '-c',
234 help='config file path; by default, searches: \n'
235 '$PWD/instalog.yaml py/instalog/instalog.yaml '
Hung-Te Linb0a32bb2017-01-05 15:11:06 +0800236 '~/.instalog.yaml /etc/instalog.yaml /run/instalog.yaml')
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800237 parser.add_argument(
238 '--verbose', '-v', action='count', default=0,
239 help='increase verbosity')
240 parser.add_argument(
241 '--quiet', '-q', action='count', default=0,
242 help='decrease verbosity')
243
244 subparsers = parser.add_subparsers(title='commands')
245
246 start_parser = subparsers.add_parser('start', help='start Instalog')
247 start_parser.set_defaults(cmd='start')
248 start_parser.add_argument(
249 '--no-daemon', '-n', dest='foreground', action='store_true',
250 help='keep in foreground')
251
252 stop_parser = subparsers.add_parser('stop', help='stop Instalog')
253 stop_parser.set_defaults(cmd='stop')
chuntsendc33ae82017-01-17 15:22:16 +0800254 stop_parser.add_argument(
255 '--timeout', '-w', type=float,
256 required=False, default=_DEFAULT_STOP_TIMEOUT,
257 help='time to wait before giving up')
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800258
259 restart_parser = subparsers.add_parser('restart', help='restart Instalog')
260 restart_parser.set_defaults(cmd='restart')
261
262 status_parser = subparsers.add_parser('status', help='print Instalog status')
263 status_parser.set_defaults(cmd='status')
264
Joel Kitching23293972016-11-18 17:24:47 +0800265 inspect_parser = subparsers.add_parser('inspect', help='inspect plugin store')
266 inspect_parser.set_defaults(cmd='inspect')
267 inspect_parser.add_argument(
268 'plugin_id', type=str, help='ID of plugin to inspect')
269 inspect_parser.add_argument(
270 'json_path', type=str, nargs='?', default='.',
271 help='path of store JSON to print')
272
Joel Kitching860649d2016-11-21 14:53:02 +0800273 flush_parser = subparsers.add_parser('flush', help='flush plugin')
274 flush_parser.set_defaults(cmd='flush')
275 flush_parser.add_argument(
276 '--timeout', '-w', type=float,
chuntsendc33ae82017-01-17 15:22:16 +0800277 required=False, default=_DEFAULT_FLUSH_TIMEOUT,
Joel Kitching860649d2016-11-21 14:53:02 +0800278 help='time to wait before giving up')
279 flush_parser.add_argument(
280 'plugin_id', type=str, nargs='?', default=None,
281 help='ID of plugin to flush')
282
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800283 args = parser.parse_args()
284
285 InstalogCLI(args)