blob: 32e1e684829fc86458d11cc91bb746b794ef7814 [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.
28_FLUSH_DEFAULT_TIMEOUT = 30
29
30
Joel Kitching3e72e8b2016-07-04 17:03:00 +080031class InstalogService(daemon_utils.Daemon):
32 """Represents the Instalog daemon service."""
33
34 def __init__(self, config, logging_level):
35 self._config = config
36 self._logging_level = logging_level
37 self._core = None
38 self._signal_handler_lock = threading.Lock()
39 super(InstalogService, self).__init__(
40 pidfile=config['instalog']['pid_file'])
41
42 def _SignalHandler(self, signal_num, frame):
43 """Signal handler to stop Instalog on SIGINT."""
44 logging.debug('_SignalHandler called with signalnum=%s', signal_num)
45 del frame
46 if signal_num is not signal.SIGINT:
47 return
48 self._signal_handler_lock.acquire(False)
49 if self._core:
50 logging.warning('SIGINT detected, stopping')
51 self._core.Stop()
52 self._core = None
53 self._signal_handler_lock.release()
54
55 def _InitLogging(self):
56 """Sets up logging."""
Joel Kitchingd01336a2016-08-03 08:14:59 +080057 # TODO(kitching): Refactor (some of) this code to log_utils module.
Joel Kitching3e72e8b2016-07-04 17:03:00 +080058 # Get logger.
Joel Kitching3e72e8b2016-07-04 17:03:00 +080059 logger = logging.getLogger()
60 logger.setLevel(logging.DEBUG)
61
Joel Kitching24e01242016-11-25 15:17:29 +080062 # In certain situations, a StreamHandler to stdout is created implicitly.
63 # Since we want to create our own, we need to remove the default one if it
64 # exists.
65 if logger.handlers:
66 logger.removeHandler(logger.handlers[0])
Joel Kitching3e72e8b2016-07-04 17:03:00 +080067
68 # Create formatter.
Joel Kitchingd01336a2016-08-03 08:14:59 +080069 formatter = logging.Formatter(log_utils.LOG_FORMAT)
Joel Kitching3e72e8b2016-07-04 17:03:00 +080070
71 # Save logging calls to log file.
72 fh = logging.FileHandler(self._config['instalog']['log_file'])
73 fh.setFormatter(formatter)
74 fh.setLevel(self._logging_level)
75 logger.addHandler(fh)
76
77 # Output logging calls to console (for when foreground=True).
78 sh = logging.StreamHandler()
79 sh.setFormatter(formatter)
80 sh.setLevel(self._logging_level)
81 logger.addHandler(sh)
82
83 def Run(self, foreground):
84 """Starts Instalog."""
85 self._InitLogging()
86
87 if foreground:
88 signal.signal(signal.SIGINT, self._SignalHandler)
89
90 self._core = core.Instalog(
91 node_id=self._config['instalog']['node_id'],
Joel Kitching9edac3b2016-10-23 10:40:24 +070092 data_dir=self._config['instalog']['data_dir'],
Joel Kitching3e72e8b2016-07-04 17:03:00 +080093 cli_hostname=self._config['instalog']['cli_hostname'],
94 cli_port=self._config['instalog']['cli_port'],
95 buffer_plugin=self._config['buffer'],
96 input_plugins=self._config['input'],
97 output_plugins=self._config['output'])
98 self._core.Run()
99
100
101class InstalogCLI(object):
102 """Represents the CLI interface used to control Instalog."""
103
104 def __init__(self, args):
105 # Read config file.
106 config_path = self._LocateConfigFile(args.config)
107 if config_path is None:
108 exit('No config file found')
109 with open(config_path) as f:
110 config = yaml.load(f)
111
112 # logging.WARNING = 30, logging.INFO = 20, logging.DEBUG = 10
113 logging_level = logging.INFO - ((args.verbose - args.quiet) * 10)
114
115 self._service = InstalogService(config, logging_level)
116 self._core = jsonrpclib.Server(
117 'http://%s:%s' % (config['instalog']['cli_hostname'],
118 config['instalog']['cli_port']))
119
120 if args.cmd == 'start':
121 self.Start(args.foreground)
122 elif args.cmd == 'stop':
123 self.Stop()
124 elif args.cmd == 'restart':
125 self.Restart()
126 elif args.cmd == 'status':
127 self.Status()
Joel Kitching23293972016-11-18 17:24:47 +0800128 elif args.cmd == 'inspect':
129 self.Inspect(args.plugin_id, args.json_path)
Joel Kitching860649d2016-11-21 14:53:02 +0800130 elif args.cmd == 'flush':
131 self.Flush(args.plugin_id, args.timeout)
Joel Kitching23293972016-11-18 17:24:47 +0800132
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800133
134 def _LocateConfigFile(self, user_path):
135 """Locates the config file that should be used by Instalog."""
136 if user_path:
137 return user_path
138 paths = [
139 os.path.join(os.getcwd(), 'instalog.yaml'),
140 os.path.join(os.path.dirname(os.path.realpath(__file__)),
141 'instalog.yaml'),
142 os.path.join(os.path.expanduser('~'), '.instalog.yaml'),
Joel Kitching24e01242016-11-25 15:17:29 +0800143 os.path.join(os.sep, 'etc', 'instalog.yaml'),
Hung-Te Linb0a32bb2017-01-05 15:11:06 +0800144 os.path.join(os.sep, 'run', 'instalog.yaml')]
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800145 for path in paths:
146 logging.debug('Checking %s for config file...', path)
147 if os.path.exists(path):
148 logging.info('Config file found at %s', path)
149 return path
150
151 def Restart(self):
152 """Restarts the daemon."""
153 self.Stop()
154 self.Start(False)
155
156 def Start(self, foreground):
157 """Starts the daemon.
158
159 Args:
160 foreground: Does not detach the daemon.
161 """
162 print('Starting...')
163 self._service.Start(foreground)
164 if foreground:
165 return
Joel Kitching11bbbe72016-12-07 11:43:38 +0800166
167 # First, wait for the daemon process to start.
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800168 try:
169 sync_utils.WaitFor(self._service.IsRunning, 10)
170 except type_utils.TimeoutError:
Joel Kitching11bbbe72016-12-07 11:43:38 +0800171 print('Daemon could not be brought up, check the logs')
172 sys.exit(1)
173
174 def TryIsUp():
175 try:
176 # Perform the real check to see if Instalog is up internally.
177 return self._core.IsUp()
178 except Exception:
179 raise type_utils.TimeoutError('Could not call core IsUp')
180
181 try:
182 if sync_utils.WaitFor(TryIsUp, 10):
183 print('DONE')
184 return
185 except type_utils.TimeoutError:
186 pass
187 print('Daemon could not be brought up, check the logs')
188 sys.exit(1)
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800189
190 def Stop(self):
191 """Stops the daemon."""
192 # First, send the "stop" instruction to the daemon.
193 print('Stopping...')
194 try:
195 self._core.Stop()
196 except Exception:
197 print('Could not connect to daemon, is it running?')
198 sys.exit(1)
199
200 # Then, wait for the process to come down.
201 try:
202 sync_utils.WaitFor(self._service.IsStopped, 10)
203 except type_utils.TimeoutError:
204 print('Still shutting down?')
205 sys.exit(1)
206 else:
207 print('DONE')
208
209 def Status(self):
210 """Prints the status of the daemon."""
211 running = self._service.IsRunning()
212 print('UP' if running else 'DOWN')
213
Joel Kitching23293972016-11-18 17:24:47 +0800214 def Inspect(self, plugin_id, json_path):
215 """Inspects the store of a given plugin."""
216 success, value = self._core.Inspect(plugin_id, json_path)
217 print(value)
218 if not success:
219 sys.exit(1)
220
Joel Kitching860649d2016-11-21 14:53:02 +0800221 def Flush(self, plugin_id, timeout):
222 """Flushes the given plugin with given timeout."""
223 success, value = self._core.Flush(plugin_id, timeout)
224 print(value)
225 if not success:
226 sys.exit(1)
227
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800228
229if __name__ == '__main__':
230 parser = argparse.ArgumentParser()
231 parser.add_argument(
232 '--config', '-c',
233 help='config file path; by default, searches: \n'
234 '$PWD/instalog.yaml py/instalog/instalog.yaml '
Hung-Te Linb0a32bb2017-01-05 15:11:06 +0800235 '~/.instalog.yaml /etc/instalog.yaml /run/instalog.yaml')
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800236 parser.add_argument(
237 '--verbose', '-v', action='count', default=0,
238 help='increase verbosity')
239 parser.add_argument(
240 '--quiet', '-q', action='count', default=0,
241 help='decrease verbosity')
242
243 subparsers = parser.add_subparsers(title='commands')
244
245 start_parser = subparsers.add_parser('start', help='start Instalog')
246 start_parser.set_defaults(cmd='start')
247 start_parser.add_argument(
248 '--no-daemon', '-n', dest='foreground', action='store_true',
249 help='keep in foreground')
250
251 stop_parser = subparsers.add_parser('stop', help='stop Instalog')
252 stop_parser.set_defaults(cmd='stop')
253
254 restart_parser = subparsers.add_parser('restart', help='restart Instalog')
255 restart_parser.set_defaults(cmd='restart')
256
257 status_parser = subparsers.add_parser('status', help='print Instalog status')
258 status_parser.set_defaults(cmd='status')
259
Joel Kitching23293972016-11-18 17:24:47 +0800260 inspect_parser = subparsers.add_parser('inspect', help='inspect plugin store')
261 inspect_parser.set_defaults(cmd='inspect')
262 inspect_parser.add_argument(
263 'plugin_id', type=str, help='ID of plugin to inspect')
264 inspect_parser.add_argument(
265 'json_path', type=str, nargs='?', default='.',
266 help='path of store JSON to print')
267
Joel Kitching860649d2016-11-21 14:53:02 +0800268 flush_parser = subparsers.add_parser('flush', help='flush plugin')
269 flush_parser.set_defaults(cmd='flush')
270 flush_parser.add_argument(
271 '--timeout', '-w', type=float,
272 required=False, default=_FLUSH_DEFAULT_TIMEOUT,
273 help='time to wait before giving up')
274 flush_parser.add_argument(
275 'plugin_id', type=str, nargs='?', default=None,
276 help='ID of plugin to flush')
277
Joel Kitching3e72e8b2016-07-04 17:03:00 +0800278 args = parser.parse_args()
279
280 InstalogCLI(args)