Merge lp:~evarlast/charms/trusty/logstash-forwarder/add-logs-relation into lp:~canonical-is-sa/charms/trusty/logstash-forwarder/trunk

Proposed by Jay R. Wren
Status: Merged
Merged at revision: 16
Proposed branch: lp:~evarlast/charms/trusty/logstash-forwarder/add-logs-relation
Merge into: lp:~canonical-is-sa/charms/trusty/logstash-forwarder/trunk
Diff against target: 222 lines (+142/-3)
4 files modified
config.yaml (+3/-0)
hooks/hooks.py (+41/-2)
hooks/test_hooks.py (+94/-0)
metadata.yaml (+4/-1)
To merge this branch: bzr merge lp:~evarlast/charms/trusty/logstash-forwarder/add-logs-relation
Reviewer Review Type Date Requested Status
Greg Mason (community) Approve
Michael Nelson (community) Approve
Review via email: mp+277493@code.launchpad.net

Description of the change

add logs relation

To post a comment you must log in.
Revision history for this message
Michael Nelson (michael.nelson) wrote :

Thanks Jay. This generally looks good. I'm not sure, but did you forget to commit the hooks/logs-relation-* symlinks, or are they no longer required?

I've a few other comments below. My main concern is the format that you're using for the relation data - in that I think it will restrict our future options (ie. if in the future we want to have two files for one type, or have extra fields for a file type). See below for more.

Let me know what you think.

Revision history for this message
Jay R. Wren (evarlast) wrote :

I addressed most the code issues.

Regarding the relation data: IMO to keep things simple on the other side of the relation, this makes the most sense. It turns out that logstash-forwarder is quite OK with single files configured with the same type. The simplicity of file, type pair is intentional. No need for added complexity. The restriction on an extra field is also by design. Parallel arrays were used for files and lists, an additional parallel array could be given by any charm implementing the logs interface, and this charm consuming the interface would ignore it. This is flexible and simple.

Can it land now?

17. By Jay R. Wren

cleanup

18. By Jay R. Wren

add missing symlinks

Revision history for this message
Michael Nelson (michael.nelson) wrote :

Great - thanks Jay.

review: Approve
Revision history for this message
Michael Nelson (michael.nelson) wrote :

BTW, you'll need someone from IS (#webops) to land it for you, afaik.

Revision history for this message
Greg Mason (gmason) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'config.yaml'
2--- config.yaml 2015-11-05 04:16:47 +0000
3+++ config.yaml 2015-12-10 18:17:52 +0000
4@@ -26,6 +26,9 @@
5 Alternatively, this can also be the full list of hashes, as per the
6 definition of the files attribute of the logstash-forwarder config at
7 https://github.com/elastic/logstash-forwarder
8+ If this service is related to another services via the logs relation,
9+ the field type and files from the logs relation are appened to this
10+ value.
11 package_name:
12 default: "logstash-forwarder_0.4.0_amd64.deb"
13 type: string
14
15=== modified file 'hooks/hooks.py'
16--- hooks/hooks.py 2015-11-05 04:16:47 +0000
17+++ hooks/hooks.py 2015-12-10 18:17:52 +0000
18@@ -12,7 +12,9 @@
19 import shutil
20 sys.path.insert(0, os.path.join(os.environ['CHARM_DIR'], 'lib'))
21 from charmhelpers.core.hookenv import Hooks, log, charm_dir, relations_of_type
22+from charmhelpers.core import host
23 from charmhelpers.core.host import mkdir, service_start, service_stop, service_restart
24+from charmhelpers.core.services import RelationContext
25 from charmhelpers.fetch import apt_install, apt_update, add_source
26 from Cheetah.Template import Template
27 from Config import Config
28@@ -81,6 +83,7 @@
29 config = {}
30 servers = []
31 files = []
32+ logs_relation_data = logs_relation()
33 if not os.path.exists(conf.configDir()):
34 mkdir(conf.configDir())
35 serverPort = conf.serverPort()
36@@ -97,6 +100,7 @@
37 if conf.timeout():
38 config["network"]["timeout"] = conf.timeout()
39
40+ config["files"] = []
41 fileSpec = conf.files()
42 if fileSpec is not None:
43 if isinstance(fileSpec, list):
44@@ -112,8 +116,12 @@
45 })
46 config["files"] = files
47
48- with open(conf.configFile(), "w") as f:
49- f.write(json.dumps(config, sort_keys=True, indent=4))
50+ if logs_relation_data is not None:
51+ for typ, fil in logs_relation_data:
52+ config["files"].append({"paths": [fil], "fields": {"type": typ}})
53+
54+ host.write_file(conf.configFile(),
55+ json.dumps(config, sort_keys=True, indent=4))
56
57
58 def writeEtcHosts():
59@@ -224,5 +232,36 @@
60 service_stop("logstash-forwarder")
61
62
63+@hooks.hook("logs-relation-joined")
64+@hooks.hook('logs-relation-changed')
65+@hooks.hook('logs-relation-departed')
66+@hooks.hook('logs-relation-broken')
67+def logs_relation_hooks():
68+ log(os.environ['JUJU_HOOK_NAME'])
69+ writeConfig()
70+ service_restart("logstash-forwarder")
71+
72+
73+def logs_relation():
74+ lsr = LogsRelation()
75+ log("LogsRelation: {}".format(lsr))
76+ if 'logs' not in lsr:
77+ return
78+ logs_relation_data = lsr['logs']
79+ if (not logs_relation_data or
80+ 'types' not in logs_relation_data[0] or
81+ 'files' not in logs_relation_data[0]):
82+ return None
83+ types = logs_relation_data[0]['types'].split()
84+ files = logs_relation_data[0]['files'].split()
85+ return zip(types, files)
86+
87+
88+class LogsRelation(RelationContext):
89+ name = 'logs'
90+ interface = 'logs'
91+ required_keys = ['types', 'files']
92+
93+
94 if __name__ == "__main__":
95 hooks.execute(sys.argv)
96
97=== added symlink 'hooks/logs-relation-broken'
98=== target is u'hooks'
99=== added symlink 'hooks/logs-relation-changed'
100=== target is u'hooks'
101=== added symlink 'hooks/logs-relation-departed'
102=== target is u'hooks'
103=== added symlink 'hooks/logs-relation-joined'
104=== target is u'hooks'
105=== added file 'hooks/test_hooks.py'
106--- hooks/test_hooks.py 1970-01-01 00:00:00 +0000
107+++ hooks/test_hooks.py 2015-12-10 18:17:52 +0000
108@@ -0,0 +1,94 @@
109+#!/usr/bin/python
110+
111+import json
112+import mock
113+import os
114+import shutil
115+import subprocess
116+import unittest
117+
118+os.environ['CHARM_DIR'] = os.path.join(os.path.dirname(__file__), '..')
119+os.environ['JUJU_UNIT_NAME'] = 'logstash-forwarder/0'
120+
121+from charmhelpers.core import hookenv, services
122+from charmhelpers.core import host
123+import charmhelpers.fetch
124+subprocess.check_output = mock.MagicMock()
125+subprocess.check_call = mock.MagicMock()
126+subprocess.call = mock.MagicMock(return_value=0)
127+config = hookenv.Config({"application_name": "logstash_forwarder",
128+ "config_dir": "/etc/logstash-forwarder",
129+ "package_name": "logstash-forwarder_0.4.0_amd64.deb",
130+ })
131+hookenv.config = lambda k: config.get(k)
132+shutil.copyfile = mock.MagicMock()
133+os.chmod = mock.MagicMock()
134+
135+host.mkdir = mock.MagicMock()
136+host.service_start = mock.MagicMock()
137+host.service_stop = mock.MagicMock()
138+host.service_restart = mock.MagicMock()
139+host.write_file = mock.MagicMock()
140+hookenv.log = mock.MagicMock()
141+
142+charmhelpers.fetch.apt_install = mock.MagicMock()
143+import hooks
144+hooks.conf.config = config
145+hooks.log = mock.MagicMock()
146+
147+
148+class TestInstall(unittest.TestCase):
149+ '''Testing that there are no exceptions in hooks.install'''
150+
151+ def test_install_does_not_raise(self):
152+ with mock.patch('__builtin__.open'):
153+ hooks.install()
154+
155+ def test_installs_packages(self):
156+ subprocess.call.reset()
157+ with mock.patch('__builtin__.open'):
158+ hooks.install()
159+ subprocess.call.assert_called_with(["dpkg", "-i",
160+ os.path.join(
161+ hookenv.charm_dir(),
162+ "files",
163+ hookenv.config('package_name'))
164+ ])
165+
166+ def test_package_file_exist(self):
167+ pkgFile = os.path.join(hookenv.charm_dir(), "files",
168+ hookenv.config('package_name'))
169+ if not os.path.exists(pkgFile):
170+ self.assert_fail()
171+
172+
173+class TestLogsRelation(unittest.TestCase):
174+
175+ def setUp(self):
176+ os.environ['JUJU_HOOK_NAME'] = 'logs-relation-joined'
177+ self.phookenv = mock.patch.object(services.helpers, 'hookenv')
178+ self.mhookenv = self.phookenv.start()
179+ self.mhookenv.relation_ids.return_value = ['baz']
180+ self.mhookenv.related_units.side_effect = lambda i: [i + '/0']
181+ self.mhookenv.relation_get.side_effect = [
182+ {'files': '\n'.join(['file1', 'file2']),
183+ 'types': '\n'.join(['type1', 'type2'])}
184+ ]
185+
186+ def tearDown(self):
187+ self.phookenv.stop()
188+ del os.environ['JUJU_HOOK_NAME']
189+
190+ def test_logs_hooks(self):
191+ conf_path = hookenv.config("config_dir") + "/logstash_forwarder.conf"
192+ host.write_file.reset_mock()
193+ hooks.logs_relation_hooks()
194+ self.assertTrue(host.write_file.called)
195+ self.assertEqual(conf_path, host.write_file.call_args[0][0])
196+ conf = json.loads(host.write_file.call_args[0][1])
197+ self.assertEqual("file1", conf["files"][0]["paths"][0])
198+ host.service_restart.assert_called_once_with('logstash-forwarder')
199+
200+ def test_logs_realtion(self):
201+ data = hooks.logs_relation()
202+ self.assertEqual([('type1', 'file1'), ('type2', 'file2')], data)
203
204=== modified file 'metadata.yaml'
205--- metadata.yaml 2015-05-29 04:52:33 +0000
206+++ metadata.yaml 2015-12-10 18:17:52 +0000
207@@ -4,7 +4,7 @@
208 description: |
209 This charm installs and configures logstash-forwarder, a lightweight
210 alternative to logstash-agent.
211-categories:
212+tags:
213 - miscellaneous
214 subordinate: true
215 provides:
216@@ -15,3 +15,6 @@
217 juju-info:
218 interface: juju-info
219 scope: container
220+ logs:
221+ interface: logs
222+ scope: container

Subscribers

People subscribed via source and target branches

to all changes: