Completed
Push — master ( d058c9...ca4b9c )
by Kenny
45s
created

plumd.initialize()   B

Complexity

Conditions 5

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 5
dl 0
loc 28
rs 8.0894
1
# -*- coding: utf-8 -*-
2
3
__author__ = 'Kenny Freeman'
4
__email__ = '[email protected]'
5
__license__     = "ISCL"
6
__docformat__   = 'reStructuredText'
7
8
import os
9
import sys
10
import time
11
import signal
12
import logging
13
import argparse
14
import threading
15
16
import structlog  # pip install structlog
17
18
import plumd
19
import plumd.util
20
import plumd.config
21
import plumd.plugins.load
22
23
24
descr = """plumd: measures system and service metrics."""
25
ext_event = threading.Event() # use this to block until exit signal
26
27
28
def get_config():
29
    """Returns a conf object - either from the default file or sys.argv.
30
31
    :rtype: plumd.config.conf
32
    """
33
    # get cli options - currently just the path to this instances config file
34
    argp = argparse.ArgumentParser(description=descr)
35
    argp.add_argument('--config', metavar='config', type=str,
36
                      nargs='?', default=plumd.DEFAULT_CONFIG['config.file'],
37
                      help='main configuration file in yaml format')
38
    args = argp.parse_args()
39
40
    # get a simple configuration helper object
41
    config = None
42
    try:
43
        config = plumd.config.Conf(args.config).defaults(plumd.DEFAULT_CONFIG)
44
    except plumd.ConfigError as e:
45
        sys.stderr.write("configuration failed to load: {0}\n".format(e))
46
        sys.exit(1)
47
48
    # ensure log level is known
49
    llevel = config.get('log.level')
50
    if not llevel in plumd.LOG_LEVELS:
51
        llevels =" ".join(plumd.LOG_LEVELS.keys())
52
        sys.stderr.write("unkonwn log level: {0}\n".format(llevel))
53
        sys.stderr.write("valid log levels are: {0}\n".format(llevels))
54
        sys.exit(1)
55
56
    # all done, return our config
57
    return config
58
59
60
def initialize(config):
61
    """Do various initilization things.
62
63
    :param config: a :class:`plumd.config.conf` instance
64
    :type config: plumd.config.conf
65
    """
66
    # set the configured timezone, from:
67
    # http://stackoverflow.com/questions/6321160/python-logging-how-to-set-time-to-gmt
68
    tz = config.get('tz')
69
    if tz:
70
        try:
71
            os.environ['TZ'] = tz
72
            time.tzset()
73
        except Exception as e:
74
            err = "unable to set timezone: {0} : {1}\n"
75
            sys.stderr.write(err.format(tz, e))
76
            sys.exit(1)
77
78
    # add hostname metadata
79
    if config.get('auto.hostname'):
80
        meta = config.get('meta')
81
        if 'hostname' not in meta:
82
            meta['hostname'] = plumd.util.get_hostname()
83
            config.set('meta', meta)
84
        else:
85
            err = "auto.hostname: hostname already set in config: {0} : {1}\n"
86
            sys.stderr.write(err.format(meta['hostname'], config.path))
87
            sys.exit(1)
88
89
90
def get_log(config, src):
91
    """Returns a configured logger.
92
93
    :param config: a :class:`plumd.config.conf` instance
94
    :type config: plumd.config.conf
95
    :param src: all log entries generated are tagged with src=<src>
96
    :type src: str
97
98
    :rtype: structlog.logger
99
    """
100
    # setup logging, use structlog
101
    handler = logging.StreamHandler(sys.stdout)
102
    stdlog = logging.getLogger()
103
    stdlog.addHandler(handler)
104
    stdlog.setLevel(plumd.LOG_LEVELS[config.get('log.level')])
105
    order=['timestamp', 'src', 'level', 'event']
106
    structlog.configure(
107
        processors=[
108
            structlog.stdlib.filter_by_level,
109
            structlog.stdlib.add_log_level,
110
            structlog.processors.TimeStamper(fmt="iso"),
111
            structlog.processors.KeyValueRenderer(sort_keys=False,
112
                                                  key_order=order)
113
        ],
114
        context_class=dict,
115
        logger_factory=structlog.stdlib.LoggerFactory(),
116
        wrapper_class=structlog.stdlib.BoundLogger,
117
        cache_logger_on_first_use=True,
118
    )
119
    return structlog.get_logger().bind(src=src)
120
121
122
def get_plugins(config, log):
123
    """Returns a plugin loader object with plugins already started.
124
125
    :param config: a :class:`plumd.config.conf` instance
126
    :type config: plumd.config.conf
127
    :param log: a structlog logging instance
128
    :type log: structlog
129
130
    :rtype: plumd.load.PluginLoader"""
131
    # load reader and writer plugins and start
132
    log.info("starting", component="plugins")
133
    psyslog = structlog.get_logger().bind(src="PluginLoader")
134
    psys = None
135
    # several things can go wrong loading plugins
136
    try:
137
        psys = plumd.plugins.load.PluginLoader(psyslog, config)
138
    except plumd.PluginLoadError as e:
139
        log.critical("plugin load failed", exception=e)
140
        sys.exit(1)
141
    except plumd.ConfigError as e:
142
        log.critical("invalid plugin config", exception=e)
143
        sys.exit(1)
144
    except plumd.DuplicatePlugin as e:
145
        log.critical("duplicate plugin", exception=e)
146
        sys.exit(1)
147
    else:
148
        time.sleep(config.get('delay.startup'))
149
        psys.start()
150
151
    # all done, return the PluginLoader object
152
    return psys
153
154
155
def signal_handler():
156
    """signal handler - just set the exit event."""
157
    ext_event.set()
158
159
160
def main():
161
    """Main entry point for plumd."""
162
163
    # setup signal handling
164
    sig_wait = [ signal.SIGINT, signal.SIGTERM, signal.SIGQUIT ]
165
    sig_ignore = [ signal.SIGUSR1, signal.SIGUSR2, signal.SIGHUP ]
166
    swait = plumd.util.SignalWaiter(sig_wait=sig_wait, sig_ignore=sig_ignore)
167
168
    # get a configuration helper object
169
    config = get_config()
170
171
    # create a logger
172
    log = get_log(config=config, src="main")
173
    log.info("starting")
174
175
    # get the plugin loader with all plugins started
176
    psys = get_plugins(log=log, config=config)
177
178
    # check to ensure we've loaded a sane configuration
179
    # is at least one reader/writer plugin loaded?
180
    if psys.nplugins < 1:
181
        log.error("exiting", error="no plugins loaded")
182
        sys.exit(1)
183
    # writer plugin?
184
    elif psys.nwriters < 1:
185
        log.error("exiting", error="no writers loaded")
186
        sys.exit(1)
187
    # reader plugin?
188
    elif psys.nreaders < 1:
189
        log.error("exiting", error="no readers loaded")
190
        sys.exit(1)
191
192
    # all set, now wait for the exit signal
193
    log.info("running")
194
    swait.wait()
195
196
    # stop readers/writers/bridge
197
    log.info("stopping")
198
    psys.stop()
199
200
    # all done, exit
201
    log.info("exiting")
202
    sys.exit(0)
203
204
205
if __name__ == "__main__":
206
    main()
207