blob: 046387a388618aaa2e2f84b6f2a665054db43c2d [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
Joel Kitching3e72e8b2016-07-04 17:03:00 +080014
Peter Shihfdf17682017-05-26 11:38:39 +080015import instalog_common # pylint: disable=unused-import
Joel Kitching3e72e8b2016-07-04 17:03:00 +080016from instalog import core
17from instalog import daemon_utils
Joel Kitchingd01336a2016-08-03 08:14:59 +080018from instalog import log_utils
Peter Shihb4e49352017-05-25 17:35:11 +080019from instalog.utils import file_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
Joel Kitching3e72e8b2016-07-04 17:03:00 +080039 super(InstalogService, self).__init__(
40 pidfile=config['instalog']['pid_file'])
41
42 def _SignalHandler(self, signal_num, frame):
Joel Kitching8e514ce2017-03-29 14:04:33 +080043 """Signal handler to stop Instalog on SIGINT or SIGTERM."""
Joel Kitching3e72e8b2016-07-04 17:03:00 +080044 del frame
Joel Kitching8e514ce2017-03-29 14:04:33 +080045 logging.debug('_SignalHandler called with signalnum=%s', signal_num)
46 if signal_num not in [signal.SIGINT, signal.SIGTERM]:
Joel Kitching3e72e8b2016-07-04 17:03:00 +080047 return
Joel Kitching3e72e8b2016-07-04 17:03:00 +080048 if self._core:
Joel Kitching8e514ce2017-03-29 14:04:33 +080049 # No need for a lock since _SignalHandler will only ever be called from
50 # Instalog's main thread.
51 signal_string = 'SIGINT' if signal_num == signal.SIGINT else 'SIGTERM'
52 logging.warning('%s detected, stopping', signal_string)
Joel Kitching3e72e8b2016-07-04 17:03:00 +080053 self._core.Stop()
54 self._core = None
Joel Kitching3e72e8b2016-07-04 17:03:00 +080055
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.
Peter Shihb4e49352017-05-25 17:35:11 +080073 log_file = self._config['instalog']['log_file']
74 file_utils.TryMakeDirs(os.path.dirname(log_file))
75 fh = logging.FileHandler(log_file)
Joel Kitching3e72e8b2016-07-04 17:03:00 +080076 fh.setFormatter(formatter)
77 fh.setLevel(self._logging_level)
78 logger.addHandler(fh)
79
80 # Output logging calls to console (for when foreground=True).
81 sh = logging.StreamHandler()
82 sh.setFormatter(formatter)
83 sh.setLevel(self._logging_level)
84 logger.addHandler(sh)
85
86 def Run(self, foreground):
87 """Starts Instalog."""
88 self._InitLogging()
89
Joel Kitching8e514ce2017-03-29 14:04:33 +080090 signal.signal(signal.SIGINT, self._SignalHandler)
91 signal.signal(signal.SIGTERM, self._SignalHandler)
Joel Kitching3e72e8b2016-07-04 17:03:00 +080092
93 self._core = core.Instalog(
94 node_id=self._config['instalog']['node_id'],
Joel Kitching9edac3b2016-10-23 10:40:24 +070095 data_dir=self._config['instalog']['data_dir'],
Joel Kitching3e72e8b2016-07-04 17:03:00 +080096 cli_hostname=self._config['instalog']['cli_hostname'],
97 cli_port=self._config['instalog']['cli_port'],
98 buffer_plugin=self._config['buffer'],
99 input_plugins=self._config['input'],
100 output_plugins=self._config['output'])
101 self._core.Run()
102
103
104class InstalogCLI(object):
105 """Represents the CLI interface used to control Instalog."""
106
107 def __init__(self, args):
108 # Read config file.
109 config_path = self._LocateConfigFile(args.config)
110 if config_path is None:
111 exit('No config file found')
112 with open(config_path) as f:
113 config = yaml.load(f)
114
115 # logging.WARNING = 30, logging.INFO = 20, logging.DEBUG = 10
116 logging_level = logging.INFO - ((args.verbose - args.quiet) * 10)
117
118 self._service = InstalogService(config, logging_level)
119 self._core = jsonrpclib.Server(
120 'http://%s:%s' % (config['instalog']['cli_hostname'],
121 config['instalog']['cli_port']))
122
123 if args.cmd == 'start':
124 self.Start(args.foreground)
125 elif args.cmd == 'stop':
chuntsendc33ae82017-01-17 15:22:16 +0800126 self.Stop(args.timeout)
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800127 elif args.cmd == 'restart':
128 self.Restart()
129 elif args.cmd == 'status':
130 self.Status()
Joel Kitching23293972016-11-18 17:24:47 +0800131 elif args.cmd == 'inspect':
132 self.Inspect(args.plugin_id, args.json_path)
Joel Kitching860649d2016-11-21 14:53:02 +0800133 elif args.cmd == 'flush':
134 self.Flush(args.plugin_id, args.timeout)
Joel Kitching23293972016-11-18 17:24:47 +0800135
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800136
137 def _LocateConfigFile(self, user_path):
138 """Locates the config file that should be used by Instalog."""
139 if user_path:
140 return user_path
141 paths = [
142 os.path.join(os.getcwd(), 'instalog.yaml'),
143 os.path.join(os.path.dirname(os.path.realpath(__file__)),
144 'instalog.yaml'),
145 os.path.join(os.path.expanduser('~'), '.instalog.yaml'),
Joel Kitching24e01242016-11-25 15:17:29 +0800146 os.path.join(os.sep, 'etc', 'instalog.yaml'),
Hung-Te Linb0a32bb2017-01-05 15:11:06 +0800147 os.path.join(os.sep, 'run', 'instalog.yaml')]
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800148 for path in paths:
149 logging.debug('Checking %s for config file...', path)
150 if os.path.exists(path):
151 logging.info('Config file found at %s', path)
152 return path
153
154 def Restart(self):
155 """Restarts the daemon."""
chuntsendc33ae82017-01-17 15:22:16 +0800156 self.Stop(_DEFAULT_STOP_TIMEOUT)
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800157 self.Start(False)
158
159 def Start(self, foreground):
160 """Starts the daemon.
161
162 Args:
163 foreground: Does not detach the daemon.
164 """
165 print('Starting...')
166 self._service.Start(foreground)
167 if foreground:
168 return
Joel Kitching11bbbe72016-12-07 11:43:38 +0800169
170 # First, wait for the daemon process to start.
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800171 try:
172 sync_utils.WaitFor(self._service.IsRunning, 10)
173 except type_utils.TimeoutError:
Joel Kitching11bbbe72016-12-07 11:43:38 +0800174 print('Daemon could not be brought up, check the logs')
175 sys.exit(1)
176
177 def TryIsUp():
178 try:
179 # Perform the real check to see if Instalog is up internally.
180 return self._core.IsUp()
181 except Exception:
182 raise type_utils.TimeoutError('Could not call core IsUp')
183
184 try:
185 if sync_utils.WaitFor(TryIsUp, 10):
186 print('DONE')
187 return
188 except type_utils.TimeoutError:
189 pass
190 print('Daemon could not be brought up, check the logs')
191 sys.exit(1)
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800192
chuntsendc33ae82017-01-17 15:22:16 +0800193 def Stop(self, timeout):
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800194 """Stops the daemon."""
195 # First, send the "stop" instruction to the daemon.
196 print('Stopping...')
197 try:
198 self._core.Stop()
199 except Exception:
200 print('Could not connect to daemon, is it running?')
201 sys.exit(1)
202
203 # Then, wait for the process to come down.
204 try:
chuntsendc33ae82017-01-17 15:22:16 +0800205 sync_utils.WaitFor(self._service.IsStopped, timeout)
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800206 except type_utils.TimeoutError:
207 print('Still shutting down?')
208 sys.exit(1)
209 else:
210 print('DONE')
211
212 def Status(self):
213 """Prints the status of the daemon."""
214 running = self._service.IsRunning()
Joel Kitchingbed7d452017-04-01 00:21:08 -0700215 if running:
216 up = self._core.IsUp()
217 print('UP' if up else 'STARTING')
218 else:
219 print('DOWN')
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800220
Joel Kitching23293972016-11-18 17:24:47 +0800221 def Inspect(self, plugin_id, json_path):
222 """Inspects the store of a given plugin."""
223 success, value = self._core.Inspect(plugin_id, json_path)
224 print(value)
225 if not success:
226 sys.exit(1)
227
Joel Kitching860649d2016-11-21 14:53:02 +0800228 def Flush(self, plugin_id, timeout):
229 """Flushes the given plugin with given timeout."""
230 success, value = self._core.Flush(plugin_id, timeout)
231 print(value)
232 if not success:
233 sys.exit(1)
234
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800235
Peter Shihb4e49352017-05-25 17:35:11 +0800236def main():
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800237 parser = argparse.ArgumentParser()
238 parser.add_argument(
239 '--config', '-c',
240 help='config file path; by default, searches: \n'
241 '$PWD/instalog.yaml py/instalog/instalog.yaml '
Hung-Te Linb0a32bb2017-01-05 15:11:06 +0800242 '~/.instalog.yaml /etc/instalog.yaml /run/instalog.yaml')
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800243 parser.add_argument(
244 '--verbose', '-v', action='count', default=0,
245 help='increase verbosity')
246 parser.add_argument(
247 '--quiet', '-q', action='count', default=0,
248 help='decrease verbosity')
249
250 subparsers = parser.add_subparsers(title='commands')
251
252 start_parser = subparsers.add_parser('start', help='start Instalog')
253 start_parser.set_defaults(cmd='start')
254 start_parser.add_argument(
255 '--no-daemon', '-n', dest='foreground', action='store_true',
256 help='keep in foreground')
257
258 stop_parser = subparsers.add_parser('stop', help='stop Instalog')
259 stop_parser.set_defaults(cmd='stop')
chuntsendc33ae82017-01-17 15:22:16 +0800260 stop_parser.add_argument(
261 '--timeout', '-w', type=float,
262 required=False, default=_DEFAULT_STOP_TIMEOUT,
263 help='time to wait before giving up')
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800264
265 restart_parser = subparsers.add_parser('restart', help='restart Instalog')
266 restart_parser.set_defaults(cmd='restart')
267
268 status_parser = subparsers.add_parser('status', help='print Instalog status')
269 status_parser.set_defaults(cmd='status')
270
Joel Kitching23293972016-11-18 17:24:47 +0800271 inspect_parser = subparsers.add_parser('inspect', help='inspect plugin store')
272 inspect_parser.set_defaults(cmd='inspect')
273 inspect_parser.add_argument(
274 'plugin_id', type=str, help='ID of plugin to inspect')
275 inspect_parser.add_argument(
276 'json_path', type=str, nargs='?', default='.',
277 help='path of store JSON to print')
278
Joel Kitching860649d2016-11-21 14:53:02 +0800279 flush_parser = subparsers.add_parser('flush', help='flush plugin')
280 flush_parser.set_defaults(cmd='flush')
281 flush_parser.add_argument(
282 '--timeout', '-w', type=float,
chuntsendc33ae82017-01-17 15:22:16 +0800283 required=False, default=_DEFAULT_FLUSH_TIMEOUT,
Joel Kitching860649d2016-11-21 14:53:02 +0800284 help='time to wait before giving up')
285 flush_parser.add_argument(
286 'plugin_id', type=str, nargs='?', default=None,
287 help='ID of plugin to flush')
288
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800289 args = parser.parse_args()
290
291 InstalogCLI(args)
Peter Shihb4e49352017-05-25 17:35:11 +0800292
293if __name__ == '__main__':
294 main()