Merge lp:~saviq/charm-helpers/glob-restart into lp:charm-helpers

Proposed by Michał Sawicz
Status: Merged
Merged at revision: 387
Proposed branch: lp:~saviq/charm-helpers/glob-restart
Merge into: lp:charm-helpers
Diff against target: 259 lines (+146/-20)
2 files modified
charmhelpers/core/host.py (+24/-6)
tests/core/test_host.py (+122/-14)
To merge this branch: bzr merge lp:~saviq/charm-helpers/glob-restart
Reviewer Review Type Date Requested Status
Corey Bryant (community) Approve
charmers Pending
Review via email: mp+260535@code.launchpad.net

Commit message

[saviq] Add support for globs in restart_on_changed

To post a comment you must log in.
lp:~saviq/charm-helpers/glob-restart updated
382. By Michał Sawicz

Abstract path_hash for easier mocking

383. By Michał Sawicz

Add mention of 'glob' module to restart_on_change

Revision history for this message
Corey Bryant (corey.bryant) wrote :

Hi Michal, Looks good to me except for the lint errors.

review: Needs Fixing
lp:~saviq/charm-helpers/glob-restart updated
384. By Michał Sawicz

Fix lint

Revision history for this message
Corey Bryant (corey.bryant) wrote :

Confirmed lint fixes and approved. Although I can't land to charm-helpers.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/core/host.py'
2--- charmhelpers/core/host.py 2015-04-29 12:52:18 +0000
3+++ charmhelpers/core/host.py 2015-06-01 17:16:53 +0000
4@@ -24,6 +24,7 @@
5 import os
6 import re
7 import pwd
8+import glob
9 import grp
10 import random
11 import string
12@@ -269,6 +270,21 @@
13 return None
14
15
16+def path_hash(path):
17+ """
18+ Generate a hash checksum of all files matching 'path'. Standard wildcards
19+ like '*' and '?' are supported, see documentation for the 'glob' module for
20+ more information.
21+
22+ :return: dict: A { filename: hash } dictionary for all matched files.
23+ Empty if none found.
24+ """
25+ return {
26+ filename: file_hash(filename)
27+ for filename in glob.iglob(path)
28+ }
29+
30+
31 def check_hash(path, checksum, hash_type='md5'):
32 """
33 Validate a file using a cryptographic checksum.
34@@ -296,23 +312,25 @@
35
36 @restart_on_change({
37 '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
38+ '/etc/apache/sites-enabled/*': [ 'apache2' ]
39 })
40- def ceph_client_changed():
41+ def config_changed():
42 pass # your code here
43
44 In this example, the cinder-api and cinder-volume services
45 would be restarted if /etc/ceph/ceph.conf is changed by the
46- ceph_client_changed function.
47+ ceph_client_changed function. The apache2 service would be
48+ restarted if any file matching the pattern got changed, created
49+ or removed. Standard wildcards are supported, see documentation
50+ for the 'glob' module for more information.
51 """
52 def wrap(f):
53 def wrapped_f(*args, **kwargs):
54- checksums = {}
55- for path in restart_map:
56- checksums[path] = file_hash(path)
57+ checksums = {path: path_hash(path) for path in restart_map}
58 f(*args, **kwargs)
59 restarts = []
60 for path in restart_map:
61- if checksums[path] != file_hash(path):
62+ if path_hash(path) != checksums[path]:
63 restarts += restart_map[path]
64 services_list = list(OrderedDict.fromkeys(restarts))
65 if not stopstart:
66
67=== modified file 'tests/core/test_host.py'
68--- tests/core/test_host.py 2015-04-29 12:52:18 +0000
69+++ tests/core/test_host.py 2015-06-01 17:16:53 +0000
70@@ -679,12 +679,13 @@
71
72 @patch.object(host, 'service')
73 @patch('os.path.exists')
74- def test_restart_no_changes(self, exists, service):
75+ @patch('glob.iglob')
76+ def test_restart_no_changes(self, iglob, exists, service):
77 file_name = '/etc/missing.conf'
78 restart_map = {
79 file_name: ['test-service']
80 }
81- exists.side_effect = [False, False]
82+ iglob.return_value = []
83
84 @host.restart_on_change(restart_map)
85 def make_no_changes():
86@@ -693,19 +694,18 @@
87 make_no_changes()
88
89 assert not service.called
90-
91- exists.assert_has_calls([
92- call(file_name),
93- ])
94+ assert not exists.called
95
96 @patch.object(host, 'service')
97 @patch('os.path.exists')
98- def test_restart_on_change(self, exists, service):
99+ @patch('glob.iglob')
100+ def test_restart_on_change(self, iglob, exists, service):
101 file_name = '/etc/missing.conf'
102 restart_map = {
103 file_name: ['test-service']
104 }
105- exists.side_effect = [False, True]
106+ iglob.side_effect = [[], [file_name]]
107+ exists.return_value = True
108
109 @host.restart_on_change(restart_map)
110 def make_some_changes(mock_file):
111@@ -723,14 +723,17 @@
112
113 @patch.object(host, 'service')
114 @patch('os.path.exists')
115- def test_multiservice_restart_on_change(self, exists, service):
116+ @patch('glob.iglob')
117+ def test_multiservice_restart_on_change(self, iglob, exists, service):
118 file_name_one = '/etc/missing.conf'
119 file_name_two = '/etc/exists.conf'
120 restart_map = {
121 file_name_one: ['test-service'],
122 file_name_two: ['test-service', 'test-service2']
123 }
124- exists.side_effect = [False, True, True, True]
125+ iglob.side_effect = [[], [file_name_two],
126+ [file_name_one], [file_name_two]]
127+ exists.return_value = True
128
129 @host.restart_on_change(restart_map)
130 def make_some_changes():
131@@ -752,12 +755,17 @@
132
133 @patch.object(host, 'service')
134 @patch('os.path.exists')
135- def test_multiservice_restart_on_change_in_order(self, exists, service):
136+ @patch('glob.iglob')
137+ def test_multiservice_restart_on_change_in_order(self, iglob, exists, service):
138+ file_name_one = '/etc/cinder/cinder.conf'
139+ file_name_two = '/etc/haproxy/haproxy.conf'
140 restart_map = OrderedDict([
141- ('/etc/cinder/cinder.conf', ['some-api']),
142- ('/etc/haproxy/haproxy.conf', ['haproxy'])
143+ (file_name_one, ['some-api']),
144+ (file_name_two, ['haproxy'])
145 ])
146- exists.side_effect = [False, True, True, True]
147+ iglob.side_effect = [[], [file_name_two],
148+ [file_name_one], [file_name_two]]
149+ exists.return_value = True
150
151 @host.restart_on_change(restart_map)
152 def make_some_changes():
153@@ -775,6 +783,106 @@
154 ]
155 self.assertEquals(expected, service.call_args_list)
156
157+ @patch.object(host, 'service')
158+ @patch('os.path.exists')
159+ @patch('glob.iglob')
160+ def test_glob_no_restart(self, iglob, exists, service):
161+ glob_path = '/etc/service/*.conf'
162+ file_name_one = '/etc/service/exists.conf'
163+ file_name_two = '/etc/service/exists2.conf'
164+ restart_map = {
165+ glob_path: ['service']
166+ }
167+ iglob.side_effect = [[file_name_one, file_name_two],
168+ [file_name_one, file_name_two]]
169+ exists.return_value = True
170+
171+ @host.restart_on_change(restart_map)
172+ def make_some_changes():
173+ pass
174+
175+ with patch_open() as (mock_open, mock_file):
176+ mock_file.read.side_effect = [b'content', b'content2',
177+ b'content', b'content2']
178+ make_some_changes()
179+
180+ self.assertEquals([], service.call_args_list)
181+
182+ @patch.object(host, 'service')
183+ @patch('os.path.exists')
184+ @patch('glob.iglob')
185+ def test_glob_restart_on_change(self, iglob, exists, service):
186+ glob_path = '/etc/service/*.conf'
187+ file_name_one = '/etc/service/exists.conf'
188+ file_name_two = '/etc/service/exists2.conf'
189+ restart_map = {
190+ glob_path: ['service']
191+ }
192+ iglob.side_effect = [[file_name_one, file_name_two],
193+ [file_name_one, file_name_two]]
194+ exists.return_value = True
195+
196+ @host.restart_on_change(restart_map)
197+ def make_some_changes():
198+ pass
199+
200+ with patch_open() as (mock_open, mock_file):
201+ mock_file.read.side_effect = [b'content', b'content2',
202+ b'changed', b'content2']
203+ make_some_changes()
204+
205+ self.assertEquals([call('restart', 'service')], service.call_args_list)
206+
207+ @patch.object(host, 'service')
208+ @patch('os.path.exists')
209+ @patch('glob.iglob')
210+ def test_glob_restart_on_create(self, iglob, exists, service):
211+ glob_path = '/etc/service/*.conf'
212+ file_name_one = '/etc/service/exists.conf'
213+ file_name_two = '/etc/service/missing.conf'
214+ restart_map = {
215+ glob_path: ['service']
216+ }
217+ iglob.side_effect = [[file_name_one],
218+ [file_name_one, file_name_two]]
219+ exists.return_value = True
220+
221+ @host.restart_on_change(restart_map)
222+ def make_some_changes():
223+ pass
224+
225+ with patch_open() as (mock_open, mock_file):
226+ mock_file.read.side_effect = [b'exists',
227+ b'exists', b'created']
228+ make_some_changes()
229+
230+ self.assertEquals([call('restart', 'service')], service.call_args_list)
231+
232+ @patch.object(host, 'service')
233+ @patch('os.path.exists')
234+ @patch('glob.iglob')
235+ def test_glob_restart_on_delete(self, iglob, exists, service):
236+ glob_path = '/etc/service/*.conf'
237+ file_name_one = '/etc/service/exists.conf'
238+ file_name_two = '/etc/service/exists2.conf'
239+ restart_map = {
240+ glob_path: ['service']
241+ }
242+ iglob.side_effect = [[file_name_one, file_name_two],
243+ [file_name_two]]
244+ exists.return_value = True
245+
246+ @host.restart_on_change(restart_map)
247+ def make_some_changes():
248+ pass
249+
250+ with patch_open() as (mock_open, mock_file):
251+ mock_file.read.side_effect = [b'exists', b'exists2',
252+ b'exists2']
253+ make_some_changes()
254+
255+ self.assertEquals([call('restart', 'service')], service.call_args_list)
256+
257 def test_lsb_release(self):
258 result = {
259 "DISTRIB_ID": "Ubuntu",

Subscribers

People subscribed via source and target branches