Merge lp:~gnuoy/charm-helpers/custom-restart into lp:charm-helpers

Proposed by Liam Young
Status: Merged
Merged at revision: 564
Proposed branch: lp:~gnuoy/charm-helpers/custom-restart
Merge into: lp:charm-helpers
Diff against target: 131 lines (+52/-8)
3 files modified
charmhelpers/contrib/openstack/utils.py (+4/-2)
charmhelpers/core/host.py (+17/-6)
tests/core/test_host.py (+31/-0)
To merge this branch: bzr merge lp:~gnuoy/charm-helpers/custom-restart
Reviewer Review Type Date Requested Status
charmers Pending
Review via email: mp+291372@code.launchpad.net

Description of the change

The charms sometimes have to deal with badly behaved init scripts which may incorrectly assert the state of a service. Sometimes a workaround is needed while upstream bugs are filed and fixed.

This change allows a charm to pass in custom function to be used for restarting a specific service. For example the code below will result in pgrep_restart being called when svc1 needs restarting.

def pgrep_restart(service):
    service_stop(service)
    while pgrep(service) != []:
        time.sleep(1)
    service_start(service)

@restart_on_change({'/etc/my.conf': ['svc1', 'svc2'], restart_functions={'svc1': pgrep_restart})

To post a comment you must log in.
Revision history for this message
Alex Kavanagh (ajkavanagh) wrote :

A corner case is that if called with stopstart=True, then the custom restart_functions will be called twice, once for stop and once for start. I've added a comment below to with some thoughts on how to handle this.

Revision history for this message
Liam Young (gnuoy) wrote :

Thanks, update to follow

565. By Liam Young

Fix double call to restart function and other fixes from tinwoods review

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/openstack/utils.py'
2--- charmhelpers/contrib/openstack/utils.py 2016-04-04 18:34:36 +0000
3+++ charmhelpers/contrib/openstack/utils.py 2016-04-08 15:09:22 +0000
4@@ -1535,7 +1535,8 @@
5 return _assess_status_func
6
7
8-def pausable_restart_on_change(restart_map, stopstart=False):
9+def pausable_restart_on_change(restart_map, stopstart=False,
10+ restart_functions=None):
11 """A restart_on_change decorator that checks to see if the unit is
12 paused. If it is paused then the decorated function doesn't fire.
13
14@@ -1568,6 +1569,7 @@
15 return f(*args, **kwargs)
16 # otherwise, normal restart_on_change functionality
17 return restart_on_change_helper(
18- (lambda: f(*args, **kwargs)), restart_map, stopstart)
19+ (lambda: f(*args, **kwargs)), restart_map, stopstart,
20+ restart_functions)
21 return wrapped_f
22 return wrap
23
24=== modified file 'charmhelpers/core/host.py'
25--- charmhelpers/core/host.py 2016-04-01 13:10:03 +0000
26+++ charmhelpers/core/host.py 2016-04-08 15:09:22 +0000
27@@ -423,7 +423,7 @@
28 pass
29
30
31-def restart_on_change(restart_map, stopstart=False):
32+def restart_on_change(restart_map, stopstart=False, restart_functions=None):
33 """Restart services based on configuration files changing
34
35 This function is used a decorator, for example::
36@@ -444,18 +444,22 @@
37
38 @param restart_map: {path_file_name: [service_name, ...]
39 @param stopstart: DEFAULT false; whether to stop, start OR restart
40+ @param restart_functions: nonstandard functions to use to restart services
41+ {svc: func, ...}
42 @returns result from decorated function
43 """
44 def wrap(f):
45 @functools.wraps(f)
46 def wrapped_f(*args, **kwargs):
47 return restart_on_change_helper(
48- (lambda: f(*args, **kwargs)), restart_map, stopstart)
49+ (lambda: f(*args, **kwargs)), restart_map, stopstart,
50+ restart_functions)
51 return wrapped_f
52 return wrap
53
54
55-def restart_on_change_helper(lambda_f, restart_map, stopstart=False):
56+def restart_on_change_helper(lambda_f, restart_map, stopstart=False,
57+ restart_functions=None):
58 """Helper function to perform the restart_on_change function.
59
60 This is provided for decorators to restart services if files described
61@@ -464,8 +468,12 @@
62 @param lambda_f: function to call.
63 @param restart_map: {file: [service, ...]}
64 @param stopstart: whether to stop, start or restart a service
65+ @param restart_functions: nonstandard functions to use to restart services
66+ {svc: func, ...}
67 @returns result of lambda_f()
68 """
69+ if restart_functions is None:
70+ restart_functions = {}
71 checksums = {path: path_hash(path) for path in restart_map}
72 r = lambda_f()
73 # create a list of lists of the services to restart
74@@ -476,9 +484,12 @@
75 services_list = list(OrderedDict.fromkeys(itertools.chain(*restarts)))
76 if services_list:
77 actions = ('stop', 'start') if stopstart else ('restart',)
78- for action in actions:
79- for service_name in services_list:
80- service(action, service_name)
81+ for service_name in services_list:
82+ if service_name in restart_functions:
83+ restart_functions[service_name](service_name)
84+ else:
85+ for action in actions:
86+ service(action, service_name)
87 return r
88
89
90
91=== modified file 'tests/core/test_host.py'
92--- tests/core/test_host.py 2016-04-01 13:10:03 +0000
93+++ tests/core/test_host.py 2016-04-08 15:09:22 +0000
94@@ -1281,6 +1281,37 @@
95
96 self.assertEquals([call('restart', 'service')], service.call_args_list)
97
98+ @patch.object(host, 'service_reload')
99+ @patch.object(host, 'service')
100+ @patch('os.path.exists')
101+ @patch('glob.iglob')
102+ def test_restart_on_change_restart_functs(self, iglob, exists, service,
103+ service_reload):
104+ file_name_one = '/etc/cinder/cinder.conf'
105+ file_name_two = '/etc/haproxy/haproxy.conf'
106+ restart_map = OrderedDict([
107+ (file_name_one, ['some-api']),
108+ (file_name_two, ['haproxy'])
109+ ])
110+ iglob.side_effect = [[], [file_name_two],
111+ [file_name_one], [file_name_two]]
112+ exists.return_value = True
113+
114+ restart_funcs = {
115+ 'some-api': service_reload,
116+ }
117+
118+ @host.restart_on_change(restart_map, restart_functions=restart_funcs)
119+ def make_some_changes():
120+ pass
121+
122+ with patch_open() as (mock_open, mock_file):
123+ mock_file.read.side_effect = [b'exists', b'missing', b'exists2']
124+ make_some_changes()
125+
126+ self.assertEquals([call('restart', 'haproxy')], service.call_args_list)
127+ self.assertEquals([call('some-api')], service_reload.call_args_list)
128+
129 def test_lsb_release(self):
130 result = {
131 "DISTRIB_ID": "Ubuntu",

Subscribers

People subscribed via source and target branches