Merge lp:~cprov/charms/trusty/logstash/heap_size into lp:~tanuki/charms/trusty/logstash/trunk
- Trusty Tahr (14.04)
- heap_size
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~cprov/charms/trusty/logstash/heap_size |
Merge into: | lp:~tanuki/charms/trusty/logstash/trunk |
Diff against target: |
736 lines (+451/-85) 13 files modified
README.md (+0/-21) config.yaml (+34/-4) files/nrpe/check_e2e_output.py (+59/-0) files/nrpe/check_lumberjack_e2e.py (+265/-0) files/upstart/logstash-indexer.conf (+2/-1) hooks/client-relation-changed (+13/-29) hooks/config-changed (+27/-10) hooks/nrpe-external-master-relation-changed (+43/-2) templates/e2e-cron.tmpl (+4/-0) templates/input-file-syslog.conf (+1/-1) templates/input-python-logging.conf (+0/-14) templates/input-redis.conf (+1/-1) templates/output-elasticsearch.conf (+2/-2) |
To merge this branch: | bzr merge lp:~cprov/charms/trusty/logstash/heap_size |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Nelson (community) | Approve | ||
Guillermo Gonzalez | Needs Information | ||
Review via email:
|
This proposal has been superseded by a proposal from 2016-11-23.
Commit message
Allowing changing the logstash maximum heap size as a configuration.
Description of the change
Allowing changing the logstash maximum heap size as a configuration.
Defaults to 1000m instead of the application 500m default value and allow adjustments.
- 59. By Celso Providelo
-
Adding heap_size config option.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michael Nelson (michael.nelson) wrote : | # |
LGTM. Note that in the ES charm, we're explicitly giving unlimited memlock. See the task at:
which installs this template:
- 60. By Celso Providelo
-
merge right branch (lp:~canonical-is-sa/charms/trusty/logstash/logstash2).
- 61. By Celso Providelo
-
Special (no-) limits configuration for logstash, so it can eat as much memory it is configured too.
Unmerged revisions
- 61. By Celso Providelo
-
Special (no-) limits configuration for logstash, so it can eat as much memory it is configured too.
- 60. By Celso Providelo
-
merge right branch (lp:~canonical-is-sa/charms/trusty/logstash/logstash2).
- 59. By Celso Providelo
-
Adding heap_size config option.
Preview Diff
1 | === modified file 'README.md' |
2 | --- README.md 2015-06-30 16:42:53 +0000 |
3 | +++ README.md 2016-11-23 22:25:27 +0000 |
4 | @@ -54,27 +54,6 @@ |
5 | juju add-relation logstash-agent logstash-indexer:input |
6 | |
7 | |
8 | -example 4 - python-logstash + Indexer + 2x ElasticSearch + Kibana |
9 | -================================================================== |
10 | - |
11 | - juju deploy cs:trusty/elasticsearch |
12 | - juju add-unit elasticsearch |
13 | - juju deploy cs:trusty/logstash-indexer |
14 | - juju add-relation elasticsearch:cluster logstash-indexer |
15 | - juju deploy cs:trusty/kibana |
16 | - juju add-relation elasticsearch:rest kibana |
17 | - juju expose kibana |
18 | - |
19 | - juju expose logstash-indexer |
20 | - $ python3 |
21 | - >>> import logging, logstash |
22 | - >>> logger = logging.getLogger() |
23 | - >>> logger.addHandler( |
24 | - ... logstash.LogstashHandler('<ip-of-indexer>', port=5959, version=1)) |
25 | - >>> logger.info('Hello Logstash!!!') |
26 | - |
27 | -http://ip-of-kibana |
28 | - |
29 | ### Caveats |
30 | |
31 | The charm will fetch the logstash complete archive every time. |
32 | |
33 | === modified file 'config.yaml' |
34 | --- config.yaml 2015-09-29 19:30:00 +0000 |
35 | +++ config.yaml 2016-11-23 22:25:27 +0000 |
36 | @@ -1,11 +1,11 @@ |
37 | options: |
38 | logstash-source: |
39 | type: string |
40 | - default: "https://download.elasticsearch.org/logstash/logstash/logstash-1.4.2.tar.gz" |
41 | + default: "https://download.elastic.co/logstash/logstash/logstash-2.0.0.tar.gz" |
42 | description: The logstash binary file to install to this charm. |
43 | logstash-sum: |
44 | type: string |
45 | - default: "d59ef579c7614c5df9bd69cfdce20ed371f728ff" |
46 | + default: "f0961520dd9590d3b600c877be66f79f94a05f80" |
47 | description: The checksum value for the logstash file. |
48 | extra-packages: |
49 | type: string |
50 | @@ -22,7 +22,29 @@ |
51 | extra-config: |
52 | type: string |
53 | default: '' |
54 | - description: "Base64-encoded custom configuration content." |
55 | + description: | |
56 | + Base64-encoded custom configuration content. If you have a directory of |
57 | + logstash filters, you can set this with: |
58 | + juju set logstash extra-config="$(cat filters/* | base64)" |
59 | + extra-patterns: |
60 | + type: string |
61 | + default: '' |
62 | + description: | |
63 | + Base64-encoded custom grok patterns for your logstash filters. If you |
64 | + have a directory of patterns, you can set this with: |
65 | + juju set logstash extra-patterns="$(cat patterns/* | base64)" |
66 | + These will be placed into the /opt/logstash/patterns directory and so can be |
67 | + referenced in a grok filter with: |
68 | + grok { |
69 | + patterns_dir => "./patterns" |
70 | + match => { ... } |
71 | + } |
72 | + More info at https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html#_custom_patterns |
73 | + heap_size: |
74 | + default: "1000m" |
75 | + type: string |
76 | + description: | |
77 | + Maximum Java heap size for the logstash process. |
78 | nagios_context: |
79 | default: "juju" |
80 | type: string |
81 | @@ -40,7 +62,7 @@ |
82 | A comma-separated list of nagios servicegroups. |
83 | If left empty, the nagios_context will be used as the servicegroup |
84 | nagios_check_procs_params: |
85 | - default: "-a /opt/logstash/lib/logstash/runner.rb -c 1:1" |
86 | + default: "-a logstash/runner.rb -c 1:1" |
87 | type: string |
88 | description: The parameters to pass to the nrpe plugin check_procs. |
89 | nagios_check_tcp_params: |
90 | @@ -51,3 +73,11 @@ |
91 | default: "-D 30,14 -H 127.0.0.1 -p 5043" |
92 | type: string |
93 | description: The parameters to pass to the nrpe plugin "check_tcp --ssl" to check certificate expiration date. |
94 | + nagios_check_e2e_params: |
95 | + default: "-w 30 -c 60" |
96 | + type: string |
97 | + description: The parameters to pass to the nrpe plugin "check_lumberjack_e2e.py" to do a E2E check. |
98 | + nagios_check_e2e_cron: |
99 | + default: "" |
100 | + type: string |
101 | + description: cron interval |
102 | |
103 | === added directory 'files/nrpe' |
104 | === added file 'files/nrpe/check_e2e_output.py' |
105 | --- files/nrpe/check_e2e_output.py 1970-01-01 00:00:00 +0000 |
106 | +++ files/nrpe/check_e2e_output.py 2016-11-23 22:25:27 +0000 |
107 | @@ -0,0 +1,59 @@ |
108 | +#!/usr/bin/env python2 |
109 | +"""Script to check the output of check_lumberjack_e2e.py""" |
110 | + |
111 | +# Copyright (C) 2016 Guillermo Gonzalez <guillermo.gonzalez@canonical.com> |
112 | + |
113 | +from __future__ import unicode_literals |
114 | +from __future__ import print_function |
115 | + |
116 | +import os |
117 | +import sys |
118 | +import time |
119 | +import traceback |
120 | + |
121 | + |
122 | +def main(): |
123 | + import argparse |
124 | + parser = argparse.ArgumentParser() |
125 | + parser.add_argument('-f', dest="e2e_file", required=True) |
126 | + parser.add_argument('-w', dest="warn", type=int, default=450, |
127 | + help="file age warning threshold.") |
128 | + parser.add_argument('-c', dest="critical", type=int, default=600, |
129 | + help="file age critical threshold") |
130 | + args = parser.parse_args() |
131 | + |
132 | + if os.path.exists(args.e2e_file): |
133 | + # check file age |
134 | + file_age = time.time() - os.path.getmtime(args.e2e_file) |
135 | + if file_age > args.critical: |
136 | + print("ERROR: e2e output file is too old (%s)" % (file_age)) |
137 | + return 2 |
138 | + elif file_age < args.critical and file_age > args.warn: |
139 | + print("WARNING: e2e output file is old (%s)" % (file_age)) |
140 | + return 1 |
141 | + # the file is recent enough |
142 | + else: |
143 | + with open(args.e2e_file, 'r') as fd: |
144 | + lines = fd.readlines() |
145 | + output = lines[-1] |
146 | + if output.startswith("OK"): |
147 | + print(output) |
148 | + return 0 |
149 | + elif output.startswith("WARNING"): |
150 | + print(output) |
151 | + return 1 |
152 | + elif output.startswith("ERROR"): |
153 | + print(output) |
154 | + return 2 |
155 | + else: |
156 | + print("ERROR: e2e output file (%s) is missing" % (args.e2e_file,)) |
157 | + return 2 |
158 | + |
159 | + |
160 | +if __name__ == "__main__": |
161 | + try: |
162 | + sys.exit(main()) |
163 | + except Exception, e: |
164 | + print("ERROR: Unhandled error: %s" % (str(e),)) |
165 | + traceback.print_exc() |
166 | + sys.exit(3) |
167 | |
168 | === added file 'files/nrpe/check_lumberjack_e2e.py' |
169 | --- files/nrpe/check_lumberjack_e2e.py 1970-01-01 00:00:00 +0000 |
170 | +++ files/nrpe/check_lumberjack_e2e.py 2016-11-23 22:25:27 +0000 |
171 | @@ -0,0 +1,265 @@ |
172 | +#!/usr/bin/env python3 |
173 | +""" |
174 | +This is very simple lumbjerjack client to run a e2e check against logstash+elasticsearch. |
175 | + |
176 | +All the lumberjack protocol code was adapted from Matt Johnson: https://pypi.python.org/pypi/stashward/0.1.4 |
177 | +""" |
178 | + |
179 | +import traceback |
180 | +import datetime |
181 | +import struct |
182 | +import errno |
183 | +import socket |
184 | +import sys |
185 | +import urllib.request |
186 | +import json |
187 | +import time |
188 | +import uuid |
189 | +import random |
190 | + |
191 | +from logging import INFO |
192 | + |
193 | +from ssl import wrap_socket, CERT_NONE, CERT_REQUIRED, SSLError |
194 | +from ssl import match_hostname, CertificateError |
195 | + |
196 | + |
197 | +class ErrorCode: |
198 | + """ |
199 | + We use some random numbers for error codes to make unit testing a little |
200 | + easier, and for greppability |
201 | + """ |
202 | + GENERIC_SSL_FAILURE = 42218 |
203 | + CN_MISMATCH = 34624 |
204 | + FILE_NOT_FOUND = 17253 |
205 | + |
206 | + |
207 | +class LumberjackClient(object): |
208 | + """Single threaded Lumberjack client to send events""" |
209 | + |
210 | + # The sequence number is 32 bits, so it should rollover to 0 when it gets there |
211 | + MAX_SEQUENCE_NUMBER = 2**32-1 |
212 | + |
213 | + def __init__(self, host, port, ca_certs=None, message_type="logstash-e2e"): |
214 | + """ |
215 | + Specify the host, port, and file path to the concatenated list of CAs |
216 | + """ |
217 | + self.host = host |
218 | + self.port = port |
219 | + self.ca_certs = ca_certs |
220 | + self.sequence_number = 0 |
221 | + self.message_type = message_type |
222 | + self.sock = None |
223 | + |
224 | + # immediately send a packet setting the windows size to something huge |
225 | + # since we don't care about ACKs |
226 | + self.send(struct.pack(b"!ssI", b"1", b"W", self.MAX_SEQUENCE_NUMBER)) |
227 | + |
228 | + def format_message(self, record): |
229 | + """Create a dict from the logging record""" |
230 | + message = { |
231 | + '@version': 1, |
232 | + 'message': record['message'], |
233 | + |
234 | + # Extra Fields |
235 | + 'levelname': record['levelname'], |
236 | + 'logger': record['name'], |
237 | + 'type': self.message_type, |
238 | + 'path': '', |
239 | + |
240 | + # required for logstash forwarder to work |
241 | + 'host': self.host, |
242 | + "line": 0, |
243 | + "offset": 0, # this is completely meaningless, but required |
244 | + "request_id": record['request_id'] |
245 | + } |
246 | + |
247 | + return message |
248 | + |
249 | + def make_socket(self, timeout=1): |
250 | + """Make the socket and wrap it with SSL. A valid certificate is required from a trusted CA""" |
251 | + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
252 | + if hasattr(s, 'settimeout'): |
253 | + s.settimeout(timeout) |
254 | + s.connect((self.host, self.port)) |
255 | + |
256 | + try: |
257 | + if self.host != "127.0.0.1" and self.ca_certs: |
258 | + s = wrap_socket(s, cert_reqs=CERT_REQUIRED, ca_certs=self.ca_certs) |
259 | + else: |
260 | + s = wrap_socket(s, cert_reqs=CERT_NONE) |
261 | + except SSLError as e: |
262 | + # the ssl certificate was probably bad |
263 | + # since logging doesn't work, print to stderr |
264 | + traceback.print_exc() |
265 | + print("ERROR %d: This is likely caused by a CN mismatch, an invalid certificate file at %s, or a verification failure based on your CA file" % (ErrorCode.GENERIC_SSL_FAILURE, self.ca_certs), file=sys.stderr) |
266 | + s.close() |
267 | + raise |
268 | + except OSError as e: |
269 | + s.close() |
270 | + # if we get an ENOENT, that's because the ca_file is bad |
271 | + if e.errno == errno.ENOENT: |
272 | + traceback.print_exc() |
273 | + print("ERROR %d: This error is probably caused by a bad CA file at %s" % (ErrorCode.FILE_NOT_FOUND, self.ca_certs), file=sys.stderr) |
274 | + s.close() |
275 | + raise |
276 | + |
277 | + # some python versions do not do a CN match check, so we have to do it |
278 | + try: |
279 | + if self.host != "127.0.0.1": |
280 | + # only check if this isn't executed from logstash server |
281 | + match_hostname(s.getpeercert(), self.host) |
282 | + except CertificateError as e: |
283 | + traceback.print_exc() |
284 | + print("ERROR %d: The CN provided by the server didn't match the host you connected to" % ErrorCode.CN_MISMATCH, file=sys.stderr) |
285 | + s.close() |
286 | + raise socket.error("CN Mismatch") |
287 | + |
288 | + return s |
289 | + |
290 | + def send(self, s): |
291 | + """ |
292 | + Send a pickled string to the socket. |
293 | + |
294 | + This function allows for partial sends which can happen when the |
295 | + network is busy. |
296 | + """ |
297 | + if self.sock is None: |
298 | + self.sock = self.make_socket() |
299 | + try: |
300 | + if hasattr(self.sock, "sendall"): |
301 | + self.sock.sendall(s) |
302 | + else: |
303 | + sentsofar = 0 |
304 | + left = len(s) |
305 | + while left > 0: |
306 | + sent = self.sock.send(s[sentsofar:]) |
307 | + sentsofar = sentsofar + sent |
308 | + left = left - sent |
309 | + except socket.error: |
310 | + self.sock.close() |
311 | + self.sock = None # so we can call make_socket next time |
312 | + |
313 | + def emit(self, record): |
314 | + """Format the record, and turn it into a logstash compatible packet""" |
315 | + packet = self.packetize(self.format_message(record), self.sequence_number) |
316 | + # critical section here for incremeting the sequence_number. Notice, we |
317 | + # don't have to wrap a lock around this, as this is single threaded |
318 | + self.sequence_number = self.sequence_number + 1 if self.sequence_number < self.MAX_SEQUENCE_NUMBER else 0 |
319 | + self.send(packet) |
320 | + |
321 | + def packetize(self, data, sequence_number): |
322 | + """ |
323 | + Turn a dict into a byte stream that is compatible with logstash-forwarder's protocol |
324 | + |
325 | + The packet looks like this: |
326 | + |
327 | + Header "1D" - the version and frame type (data) in ascii |
328 | + 32 bit unsigned, big-endian, integer sequence number |
329 | + 32 bit unsigned, big-endian, integer count of the number of (key, value) pairs |
330 | + For each key, value pair: |
331 | + 32 bit unsigned, big-endian, integer number for the key length |
332 | + the key (raw bytes) |
333 | + 32 bit unsigned, big-endian, integer number for the value length |
334 | + the value (raw bytes) |
335 | + |
336 | + |
337 | + The data *MUST* include a key/value pair for "host", "line" and |
338 | + "offset". That fact isn't documented anywhere but here. You're welcome. |
339 | + """ |
340 | + format_string = [b"!ssII"] |
341 | + string = [b"1", b"D", sequence_number, len(data)] |
342 | + |
343 | + for key, value in data.items(): |
344 | + # encode the keys and values as utf8-encoded bytestrings |
345 | + key = key.encode('utf-8') |
346 | + value = str(value).encode('utf-8') |
347 | + |
348 | + # tack on the key length |
349 | + format_string.append(b"I") |
350 | + string.append(len(key)) |
351 | + # tack on the key itself |
352 | + format_string.append(("%ds" % (len(key))).encode('utf-8')) |
353 | + string.append(key) |
354 | + # tack on the value length |
355 | + format_string.append(b"I") |
356 | + string.append(len(value)) |
357 | + # tack on the value |
358 | + format_string.append(("%ds" % (len(value))).encode('utf-8')) |
359 | + string.append(value) |
360 | + |
361 | + return struct.pack(b"".join(format_string), *string) |
362 | + |
363 | + |
364 | +def main(args): |
365 | + req_id = str(uuid.uuid4()) |
366 | + c = LumberjackClient(args.host, args.port, ca_certs=args.ca_certs, |
367 | + message_type=args.message_type) |
368 | + c.emit(dict(name="logstash-e2e", levelname=INFO, |
369 | + message='testing logstash e2e {}'.format(req_id), |
370 | + request_id=req_id)) |
371 | + ts = time.time() |
372 | + |
373 | + index = "logstash-%s" % datetime.datetime.utcnow().strftime("%Y.%m.%d") |
374 | + get_body = {"query": {"match": {"request_id": req_id}}, |
375 | + "sort": [{"_timestamp": {"order": "desc"}}] |
376 | + } |
377 | + |
378 | + ok = False |
379 | + elapsed = 0 |
380 | + retry = 0 |
381 | + es_hosts = args.es_host_port.split(',') |
382 | + while not ok or elapsed >= args.critical: |
383 | + es_host = es_hosts[random.randint(0, len(es_hosts)-1)] |
384 | + try: |
385 | + resp = urllib.request.urlopen("http://%s/%s/_search?size=1" % (es_host, index), |
386 | + data=json.dumps(get_body).encode('utf-8'), |
387 | + timeout=args.es_timeout) |
388 | + except Exception as e: |
389 | + print("Elasticsearch unreachable: %s" % str(e)) |
390 | + return 2 |
391 | + results = json.loads(resp.read().decode('utf-8')) |
392 | + elapsed = time.time()-ts |
393 | + for result in results['hits']['hits']: |
394 | + if 'request_id' in result['_source'] and req_id == result['_source']['request_id']: |
395 | + ok = True |
396 | + break |
397 | + if not ok: |
398 | + # trivial retry with backoff |
399 | + time.sleep((retry**0.5)+(random.randint(0, 1000) / 1000)) |
400 | + retry = retry + 1 |
401 | + |
402 | + with open(args.e2e_file, 'w') as fd: |
403 | + if not ok or elapsed >= args.critical: |
404 | + fd.write("ERROR: Taking longer than %ss to reach ES, aborting\n" % (args.critical,)) |
405 | + elif elapsed >= args.warn and elapsed < args.critical: |
406 | + fd.write("WARNING: Took longer than %ss to reach ES\n" % (args.warn,)) |
407 | + else: |
408 | + fd.write("OK: took: %s\n" % (elapsed,)) |
409 | + fd.flush() |
410 | + |
411 | + |
412 | +if __name__ == "__main__": |
413 | + import argparse |
414 | + parser = argparse.ArgumentParser() |
415 | + parser.add_argument('-H', dest="host", default="127.0.0.1") |
416 | + parser.add_argument('-p', dest="port", default=5043, type=int) |
417 | + parser.add_argument('--ca-certs', dest="ca_certs", default=None) |
418 | + parser.add_argument('-m', dest="message_type", default="logstash-e2e") |
419 | + parser.add_argument('-w', dest="warn", type=int, default=60) |
420 | + parser.add_argument('-c', dest="critical", type=int, default=120) |
421 | + parser.add_argument('--es', dest="es_host_port", required=True, |
422 | + help="Comma separated list of elasticsearch " + \ |
423 | + "host:port, e.g: host:port,host1:port1..") |
424 | + parser.add_argument('-t', dest="es_timeout", type=int, default=5, |
425 | + help="timeout for ES http request") |
426 | + parser.add_argument('-f', dest="e2e_file", required=True, |
427 | + help="path to the output file") |
428 | + args = parser.parse_args() |
429 | + try: |
430 | + sys.exit(main(args)) |
431 | + except Exception as e: |
432 | + with open(args.e2e_file, 'w') as fd: |
433 | + fd.write("ERROR: Unhandled error: %s\n" % (str(e),)) |
434 | + fd.flush() |
435 | + traceback.print_exc() |
436 | + sys.exit(1) |
437 | |
438 | === modified file 'files/upstart/logstash-indexer.conf' |
439 | --- files/upstart/logstash-indexer.conf 2014-09-23 12:11:08 +0000 |
440 | +++ files/upstart/logstash-indexer.conf 2016-11-23 22:25:27 +0000 |
441 | @@ -11,6 +11,7 @@ |
442 | respawn |
443 | respawn limit 5 30 |
444 | env HOME={{BASEPATH}} |
445 | +env LS_HEAP_SIZE={{HEAP_SIZE}} |
446 | chdir {{BASEPATH}} |
447 | setuid logstash |
448 | setgid adm |
449 | @@ -18,4 +19,4 @@ |
450 | |
451 | script |
452 | {{BASEPATH}}/bin/logstash agent -f {{BASEPATH}}/conf.d/ |
453 | -end script |
454 | \ No newline at end of file |
455 | +end script |
456 | |
457 | === modified file 'hooks/client-relation-changed' |
458 | --- hooks/client-relation-changed 2015-09-08 19:45:13 +0000 |
459 | +++ hooks/client-relation-changed 2016-11-23 22:25:27 +0000 |
460 | @@ -1,6 +1,9 @@ |
461 | #!/usr/bin/python |
462 | |
463 | +import json |
464 | import os |
465 | +import shlex |
466 | +import subprocess |
467 | import sys |
468 | |
469 | sys.path.insert(0, os.path.join(os.environ['CHARM_DIR'], 'lib')) |
470 | @@ -19,45 +22,26 @@ |
471 | BASEPATH = os.path.join(os.path.sep, 'opt', 'logstash') |
472 | |
473 | |
474 | -@hooks.hook('client-relation-changed') |
475 | +@hooks.hook('client-relation-changed', 'client-relation-departed') |
476 | def rest_changed(): |
477 | - cache_hosts() |
478 | write_config() |
479 | host.service_restart(SERVICE) or host.service_start(SERVICE) |
480 | |
481 | |
482 | def write_config(): |
483 | - with open('host_cache', 'r') as f: |
484 | - hosts = f.readlines() |
485 | - if not hosts: |
486 | - sys.exit(0) |
487 | - |
488 | - # Use last host in list as it will be the most recently added |
489 | - # and first host in list may not exist anymore! TODO fix that. |
490 | - opts = {'hosts': hosts[-1].rstrip()} |
491 | - |
492 | - out = os.path.join(BASEPATH, 'conf.d', 'output-elasticsearch.conf') |
493 | - with open(out, 'w') as p: |
494 | - p.write(render(os.path.basename(out), opts)) |
495 | - |
496 | - |
497 | -def cache_hosts(): |
498 | rels = hookenv.relations_of_type("client") |
499 | if not rels: |
500 | log('No client relations. Assuming nothing to do.') |
501 | sys.exit(0) |
502 | - if not os.path.exists('host_cache'): |
503 | - open('host_cache', 'a').close() |
504 | - for rel in rels: |
505 | - host = rel.get('host') |
506 | - if not host: |
507 | - log('No host received for relation: {}.'.format(rel)) |
508 | - continue |
509 | - with open('host_cache', 'r') as f: |
510 | - hosts = f.readlines() |
511 | - if host not in hosts: |
512 | - with open('host_cache', 'a') as f: |
513 | - f.write('{}\n'.format(host)) |
514 | + hosts = json.dumps( |
515 | + ["%s:%s" % (client['host'], client['port']) for client in rels |
516 | + if 'host' in client]) |
517 | + |
518 | + out = os.path.join(BASEPATH, 'conf.d', 'output-elasticsearch.conf') |
519 | + with open(out, 'w') as p: |
520 | + p.write(render(os.path.basename(out), dict(hosts=hosts))) |
521 | + # update nrpe now that we have client relations |
522 | + subprocess.check_output(shlex.split('hooks/nrpe-external-master-relation-changed')) |
523 | |
524 | |
525 | if __name__ == "__main__": |
526 | |
527 | === added symlink 'hooks/client-relation-departed' |
528 | === target is u'client-relation-changed' |
529 | === modified file 'hooks/config-changed' |
530 | --- hooks/config-changed 2015-09-08 19:05:34 +0000 |
531 | +++ hooks/config-changed 2016-11-23 22:25:27 +0000 |
532 | @@ -38,15 +38,20 @@ |
533 | |
534 | # This only actually opens the port if we've exposed the service in juju |
535 | hookenv.open_port(5043) |
536 | - hookenv.open_port(5959, protocol='UDP') |
537 | - hookenv.open_port(5959, protocol='TCP') |
538 | - |
539 | - # The install hook is idempotent, so re-run it. |
540 | - # XXX: no, it's not idempotent. it fails if the tarball was already |
541 | - # extracted |
542 | - # subprocess.check_output(shlex.split('hooks/install')) |
543 | - |
544 | - # Restart the service when configuration has changed. |
545 | + |
546 | + # Verify the config to get a decent error message for bad configuration. |
547 | + try: |
548 | + subprocess.check_output([ |
549 | + '/opt/logstash/bin/logstash', 'agent', '-f', |
550 | + '/opt/logstash/conf.d/', '--configtest' |
551 | + ]) |
552 | + except subprocess.CalledProcessError, e: |
553 | + # The exception doesn't print the output, by default. |
554 | + print(e.output) |
555 | + raise |
556 | + |
557 | + # Restart the service when configuration has changed |
558 | + # and the config is valid. |
559 | subprocess.check_output(shlex.split('hooks/start')) |
560 | |
561 | subprocess.check_output(shlex.split('hooks/nrpe-external-master-relation-changed')) |
562 | @@ -61,6 +66,8 @@ |
563 | key_file = os.path.join(cert_dir, 'logstash.key') |
564 | |
565 | for f in files: |
566 | + if not f.endswith(".conf"): |
567 | + continue |
568 | # skip output-elasticsearch.conf, is managed by |
569 | # hooks/client-relation-changed |
570 | if os.path.basename(f) == "output-elasticsearch.conf": |
571 | @@ -75,6 +82,12 @@ |
572 | if config_data['extra-config']: |
573 | with open(os.path.join(BASEPATH, 'conf.d', 'extra.conf'), 'w') as f: |
574 | f.write(str(base64.b64decode(config_data['extra-config']))) |
575 | + if config_data['extra-patterns']: |
576 | + patterns_dir = os.path.join(BASEPATH, 'patterns') |
577 | + if not os.path.exists(patterns_dir): |
578 | + os.mkdir(patterns_dir) |
579 | + with open(os.path.join(patterns_dir, 'extra-patterns'), 'w') as f: |
580 | + f.write(str(base64.b64decode(config_data['extra-patterns']))) |
581 | |
582 | # Only setup lumberjack protocol if ssl cert and key are configured |
583 | if config_data['ssl_cert'] and config_data['ssl_key']: |
584 | @@ -100,7 +113,11 @@ |
585 | def place_upstart_template(): |
586 | out = os.path.join(os.path.sep, 'etc', 'init', '{}.conf'.format(SERVICE)) |
587 | templ = os.path.join('files', 'upstart') |
588 | - opts = {'BASEPATH': BASEPATH} |
589 | + config_data = hookenv.config() |
590 | + opts = { |
591 | + 'BASEPATH': BASEPATH, |
592 | + 'HEAP_SIZE': config_data['heap_size'], |
593 | + } |
594 | |
595 | with open(out, 'w') as p: |
596 | p.write(render('{}.conf'.format(SERVICE), opts, template_dir=templ)) |
597 | |
598 | === modified file 'hooks/nrpe-external-master-relation-changed' |
599 | --- hooks/nrpe-external-master-relation-changed 2015-09-29 19:43:29 +0000 |
600 | +++ hooks/nrpe-external-master-relation-changed 2016-11-23 22:25:27 +0000 |
601 | @@ -1,13 +1,14 @@ |
602 | #!/usr/bin/python |
603 | |
604 | +import glob |
605 | import os |
606 | +import shutil |
607 | import sys |
608 | |
609 | sys.path.insert(0, os.path.join(os.environ['CHARM_DIR'], 'lib')) |
610 | |
611 | -from charmhelpers.core import hookenv |
612 | +from charmhelpers.core import hookenv, templating |
613 | from charmhelpers.contrib.charmsupport import nrpe |
614 | -from charmhelpers.contrib.charmsupport.nrpe import NRPE |
615 | |
616 | hooks = hookenv.Hooks() |
617 | log = hookenv.log |
618 | @@ -47,8 +48,21 @@ |
619 | self.service_template = CustomIntervalCheck.service_template % intervals_config |
620 | |
621 | |
622 | +def install_nrpe_scripts(): |
623 | + # copy the scripts |
624 | + scripts_src = os.path.join(os.environ["CHARM_DIR"], "files", |
625 | + "nrpe") |
626 | + scripts_dst = "/usr/lib/nagios/plugins" |
627 | + if not os.path.exists(scripts_dst): |
628 | + os.makedirs(scripts_dst) |
629 | + for fname in glob.glob(os.path.join(scripts_src, "*.py")): |
630 | + shutil.copy2(fname, |
631 | + os.path.join(scripts_dst, os.path.basename(fname))) |
632 | + |
633 | + |
634 | @hooks.hook('nrpe-external-master-relation-changed') |
635 | def update_nrpe_checks(): |
636 | + install_nrpe_scripts() |
637 | nrpe_compat = nrpe.NRPE() |
638 | conf = nrpe_compat.config |
639 | check_procs_params = conf.get('nagios_check_procs_params') |
640 | @@ -60,6 +74,8 @@ |
641 | ) |
642 | check_tcp_params = conf.get('nagios_check_tcp_params') |
643 | check_cert_params = conf.get('nagios_check_cert_params') |
644 | + check_e2e_params = conf.get('nagios_check_e2e_params') |
645 | + check_e2e_cron = conf.get('nagios_check_e2e_cron') |
646 | config_data = hookenv.config() |
647 | # Only setup lumberjack protocol if ssl cert and key are configured |
648 | if config_data['ssl_cert'] and config_data['ssl_key']: |
649 | @@ -79,6 +95,31 @@ |
650 | retry_check_interval=120, # minutes |
651 | ) |
652 | nrpe_compat.checks.append(cert_check) |
653 | + # add e2e check using lumberjack protocol |
654 | + rels = hookenv.relations_of_type("client") |
655 | + if rels and check_e2e_cron: |
656 | + # delete old check |
657 | + if os.path.exists('/etc/nagios/nrpe.d/check_e2e-check.cfg'): |
658 | + os.unlink('/etc/nagios/nrpe.d/check_e2e-check.cfg') |
659 | + es_ips = ','.join(["%s:%s" % (client['host'], client['port']) |
660 | + for client in rels if 'host' in client]) |
661 | + e2e_output_file = "/var/lib/nagios/logstash-e2e-check.output" |
662 | + charm_dir = hookenv.charm_dir() |
663 | + # write cronjob to run e2e script every 5mins |
664 | + context = {'es_ips': es_ips, |
665 | + 'e2e_file': e2e_output_file, |
666 | + 'e2e_params': check_e2e_params, |
667 | + 'cron_expression': check_e2e_cron, |
668 | + 'charm_dir': charm_dir} |
669 | + templating.render('e2e-cron.tmpl', '/etc/cron.d/e2e-check', |
670 | + context=context, perms=0554) |
671 | + nrpe_compat.add_check( |
672 | + shortname='e2e_output', |
673 | + description='Check E2E script output', |
674 | + check_cmd='check_e2e_output.py -f {}'.format(e2e_output_file) |
675 | + ) |
676 | + else: |
677 | + log('No client relations. Not adding e2e check.') |
678 | |
679 | nrpe_compat.write() |
680 | |
681 | |
682 | === added file 'templates/e2e-cron.tmpl' |
683 | --- templates/e2e-cron.tmpl 1970-01-01 00:00:00 +0000 |
684 | +++ templates/e2e-cron.tmpl 2016-11-23 22:25:27 +0000 |
685 | @@ -0,0 +1,4 @@ |
686 | +# Cron job to run e2e check |
687 | +MAILTO="" |
688 | +{{ cron_expression }} nagios run-this-one python3 {{ charm_dir }}/files/nrpe/check_lumberjack_e2e.py --es {{ es_ips }} -f {{ e2e_file }} {{ e2e_params }} |
689 | + |
690 | |
691 | === modified file 'templates/input-file-syslog.conf' |
692 | --- templates/input-file-syslog.conf 2014-09-23 12:12:45 +0000 |
693 | +++ templates/input-file-syslog.conf 2016-11-23 22:25:27 +0000 |
694 | @@ -1,1 +1,1 @@ |
695 | -input { file { type => 'syslog' path => ['/var/log/syslog','/var/log/kern.log','/var/log/auth.log','/var/log/dpkg.log'] sincedb_path => '{{BASEPATH}}' } } |
696 | \ No newline at end of file |
697 | +input { file { type => 'syslog' path => ['/var/log/syslog','/var/log/kern.log','/var/log/auth.log','/var/log/dpkg.log'] } } |
698 | |
699 | === removed file 'templates/input-python-logging.conf' |
700 | --- templates/input-python-logging.conf 2015-06-30 16:42:53 +0000 |
701 | +++ templates/input-python-logging.conf 1970-01-01 00:00:00 +0000 |
702 | @@ -1,14 +0,0 @@ |
703 | -# Input for python-logstash [[https://pypi.python.org/pypi/python-logstash]] |
704 | -# via udp:5959. |
705 | -input { |
706 | - udp { |
707 | - # The port to listen on. |
708 | - port => 5959 |
709 | - |
710 | - # Receives plain JSON objects. |
711 | - codec => "json" |
712 | - |
713 | - # Just an identifier, change it to whatever makes more sense. |
714 | - type => "python-logstash" |
715 | - } |
716 | -} |
717 | |
718 | === modified file 'templates/input-redis.conf' |
719 | --- templates/input-redis.conf 2014-09-23 12:12:45 +0000 |
720 | +++ templates/input-redis.conf 2016-11-23 22:25:27 +0000 |
721 | @@ -1,1 +1,1 @@ |
722 | -input { redis { host => "127.0.0.1" data_type => "list" type => "redis" tags => ["redis"] key => "logstash" message_format => "json_event" } } |
723 | \ No newline at end of file |
724 | +input { redis { host => "127.0.0.1" data_type => "list" type => "redis" tags => ["redis"] key => "logstash" } } |
725 | |
726 | === modified file 'templates/output-elasticsearch.conf' |
727 | --- templates/output-elasticsearch.conf 2014-09-23 16:59:13 +0000 |
728 | +++ templates/output-elasticsearch.conf 2016-11-23 22:25:27 +0000 |
729 | @@ -1,5 +1,5 @@ |
730 | output { |
731 | - elasticsearch_http { |
732 | - host => "{{hosts}}" |
733 | + elasticsearch { |
734 | + hosts => {{ hosts }} |
735 | } |
736 | } |
Celso,
If this is to apply it to the OLS ELK, it's using a a different charm:
lp:~canonical-is-sa/charms/trusty/logstash/logstash2;revno=61,overwrite=true
the spec in use (for both staging and production) is: is/mojo- is-logging- kibana/ @ lp:canonical-mojo-specs.