Merge lp:~ajkavanagh/charm-helpers/support-maintenance-mode into lp:charm-helpers
- support-maintenance-mode
- Merge into devel
Proposed by
Alex Kavanagh
Status: | Merged |
---|---|
Merged at revision: | 539 |
Proposed branch: | lp:~ajkavanagh/charm-helpers/support-maintenance-mode |
Merge into: | lp:charm-helpers |
Diff against target: |
1459 lines (+1050/-191) 3 files modified
charmhelpers/contrib/openstack/utils.py (+539/-118) charmhelpers/core/host.py (+36/-14) tests/contrib/openstack/test_openstack_utils.py (+475/-59) |
To merge this branch: | bzr merge lp:~ajkavanagh/charm-helpers/support-maintenance-mode |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Liam Young (community) | Approve | ||
Review via email: mp+287907@code.launchpad.net |
Commit message
Description of the change
Support pause/resume functionality for OpenStack charms. Pause and resume are baked into set_os_
Also includes a @pausable_
To post a comment you must log in.
Revision history for this message
Alex Kavanagh (ajkavanagh) wrote : | # |
Thanks for looking through it; I know it's a big patch. I'll simplify the list comprehensions, and I've commented inline/responses.
- 542. By Alex Kavanagh
-
Simplify the list comprehensions around message generation as per gnuoy's
suggestions. They were a little unreadable.
Revision history for this message
Liam Young (gnuoy) wrote : | # |
Looks good to me, thanks for the mp!
review:
Approve
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-02-17 16:29:13 +0000 | |||
3 | +++ charmhelpers/contrib/openstack/utils.py 2016-03-07 16:45:36 +0000 | |||
4 | @@ -24,6 +24,7 @@ | |||
5 | 24 | import sys | 24 | import sys |
6 | 25 | import re | 25 | import re |
7 | 26 | import itertools | 26 | import itertools |
8 | 27 | import functools | ||
9 | 27 | 28 | ||
10 | 28 | import six | 29 | import six |
11 | 29 | import tempfile | 30 | import tempfile |
12 | @@ -69,7 +70,15 @@ | |||
13 | 69 | pip_install, | 70 | pip_install, |
14 | 70 | ) | 71 | ) |
15 | 71 | 72 | ||
17 | 72 | from charmhelpers.core.host import lsb_release, mounts, umount, service_running | 73 | from charmhelpers.core.host import ( |
18 | 74 | lsb_release, | ||
19 | 75 | mounts, | ||
20 | 76 | umount, | ||
21 | 77 | service_running, | ||
22 | 78 | service_pause, | ||
23 | 79 | service_resume, | ||
24 | 80 | restart_on_change_helper, | ||
25 | 81 | ) | ||
26 | 73 | from charmhelpers.fetch import apt_install, apt_cache, install_remote | 82 | from charmhelpers.fetch import apt_install, apt_cache, install_remote |
27 | 74 | from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk | 83 | from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk |
28 | 75 | from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device | 84 | from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device |
29 | @@ -862,66 +871,155 @@ | |||
30 | 862 | return wrap | 871 | return wrap |
31 | 863 | 872 | ||
32 | 864 | 873 | ||
55 | 865 | def set_os_workload_status(configs, required_interfaces, charm_func=None, services=None, ports=None): | 874 | def set_os_workload_status(configs, required_interfaces, charm_func=None, |
56 | 866 | """ | 875 | services=None, ports=None): |
57 | 867 | Set workload status based on complete contexts. | 876 | """Set the state of the workload status for the charm. |
58 | 868 | status-set missing or incomplete contexts | 877 | |
59 | 869 | and juju-log details of missing required data. | 878 | This calls _determine_os_workload_status() to get the new state, message |
60 | 870 | charm_func is a charm specific function to run checking | 879 | and sets the status using status_set() |
61 | 871 | for charm specific requirements such as a VIP setting. | 880 | |
62 | 872 | 881 | @param configs: a templating.OSConfigRenderer() object | |
63 | 873 | This function also checks for whether the services defined are ACTUALLY | 882 | @param required_interfaces: {generic: [specific, specific2, ...]} |
64 | 874 | running and that the ports they advertise are open and being listened to. | 883 | @param charm_func: a callable function that returns state, message. The |
65 | 875 | 884 | signature is charm_func(configs) -> (state, message) | |
66 | 876 | @param services - OPTIONAL: a [{'service': <string>, 'ports': [<int>]] | 885 | @param services: list of strings OR dictionary specifying services/ports |
67 | 877 | The ports are optional. | 886 | @param ports: OPTIONAL list of port numbers. |
68 | 878 | If services is a [<string>] then ports are ignored. | 887 | @returns state, message: the new workload status, user message |
69 | 879 | @param ports - OPTIONAL: an [<int>] representing ports that shoudl be | 888 | """ |
70 | 880 | open. | 889 | state, message = _determine_os_workload_status( |
71 | 881 | @returns None | 890 | configs, required_interfaces, charm_func, services, ports) |
72 | 882 | """ | 891 | status_set(state, message) |
73 | 883 | incomplete_rel_data = incomplete_relation_data(configs, required_interfaces) | 892 | |
74 | 884 | state = 'active' | 893 | |
75 | 885 | missing_relations = [] | 894 | def _determine_os_workload_status( |
76 | 886 | incomplete_relations = [] | 895 | configs, required_interfaces, charm_func=None, |
77 | 896 | services=None, ports=None): | ||
78 | 897 | """Determine the state of the workload status for the charm. | ||
79 | 898 | |||
80 | 899 | This function returns the new workload status for the charm based | ||
81 | 900 | on the state of the interfaces, the paused state and whether the | ||
82 | 901 | services are actually running and any specified ports are open. | ||
83 | 902 | |||
84 | 903 | This checks: | ||
85 | 904 | |||
86 | 905 | 1. if the unit should be paused, that it is actually paused. If so the | ||
87 | 906 | state is 'maintenance' + message, else 'broken'. | ||
88 | 907 | 2. that the interfaces/relations are complete. If they are not then | ||
89 | 908 | it sets the state to either 'broken' or 'waiting' and an appropriate | ||
90 | 909 | message. | ||
91 | 910 | 3. If all the relation data is set, then it checks that the actual | ||
92 | 911 | services really are running. If not it sets the state to 'broken'. | ||
93 | 912 | |||
94 | 913 | If everything is okay then the state returns 'active'. | ||
95 | 914 | |||
96 | 915 | @param configs: a templating.OSConfigRenderer() object | ||
97 | 916 | @param required_interfaces: {generic: [specific, specific2, ...]} | ||
98 | 917 | @param charm_func: a callable function that returns state, message. The | ||
99 | 918 | signature is charm_func(configs) -> (state, message) | ||
100 | 919 | @param services: list of strings OR dictionary specifying services/ports | ||
101 | 920 | @param ports: OPTIONAL list of port numbers. | ||
102 | 921 | @returns state, message: the new workload status, user message | ||
103 | 922 | """ | ||
104 | 923 | state, message = _ows_check_if_paused(services, ports) | ||
105 | 924 | |||
106 | 925 | if state is None: | ||
107 | 926 | state, message = _ows_check_generic_interfaces( | ||
108 | 927 | configs, required_interfaces) | ||
109 | 928 | |||
110 | 929 | if state != 'maintenance' and charm_func: | ||
111 | 930 | # _ows_check_charm_func() may modify the state, message | ||
112 | 931 | state, message = _ows_check_charm_func( | ||
113 | 932 | state, message, lambda: charm_func(configs)) | ||
114 | 933 | |||
115 | 934 | if state is None: | ||
116 | 935 | state, message = _ows_check_services_running(services, ports) | ||
117 | 936 | |||
118 | 937 | if state is None: | ||
119 | 938 | state = 'active' | ||
120 | 939 | message = "Unit is ready" | ||
121 | 940 | juju_log(message, 'INFO') | ||
122 | 941 | |||
123 | 942 | return state, message | ||
124 | 943 | |||
125 | 944 | |||
126 | 945 | def _ows_check_if_paused(services=None, ports=None): | ||
127 | 946 | """Check if the unit is supposed to be paused, and if so check that the | ||
128 | 947 | services/ports (if passed) are actually stopped/not being listened to. | ||
129 | 948 | |||
130 | 949 | if the unit isn't supposed to be paused, just return None, None | ||
131 | 950 | |||
132 | 951 | @param services: OPTIONAL services spec or list of service names. | ||
133 | 952 | @param ports: OPTIONAL list of port numbers. | ||
134 | 953 | @returns state, message or None, None | ||
135 | 954 | """ | ||
136 | 955 | if is_unit_paused_set(): | ||
137 | 956 | state, message = check_actually_paused(services=services, | ||
138 | 957 | ports=ports) | ||
139 | 958 | if state is None: | ||
140 | 959 | # we're paused okay, so set maintenance and return | ||
141 | 960 | state = "maintenance" | ||
142 | 961 | message = "Paused. Use 'resume' action to resume normal service." | ||
143 | 962 | return state, message | ||
144 | 963 | return None, None | ||
145 | 964 | |||
146 | 965 | |||
147 | 966 | def _ows_check_generic_interfaces(configs, required_interfaces): | ||
148 | 967 | """Check the complete contexts to determine the workload status. | ||
149 | 968 | |||
150 | 969 | - Checks for missing or incomplete contexts | ||
151 | 970 | - juju log details of missing required data. | ||
152 | 971 | - determines the correct workload status | ||
153 | 972 | - creates an appropriate message for status_set(...) | ||
154 | 973 | |||
155 | 974 | if there are no problems then the function returns None, None | ||
156 | 975 | |||
157 | 976 | @param configs: a templating.OSConfigRenderer() object | ||
158 | 977 | @params required_interfaces: {generic_interface: [specific_interface], } | ||
159 | 978 | @returns state, message or None, None | ||
160 | 979 | """ | ||
161 | 980 | incomplete_rel_data = incomplete_relation_data(configs, | ||
162 | 981 | required_interfaces) | ||
163 | 982 | state = None | ||
164 | 887 | message = None | 983 | message = None |
167 | 888 | charm_state = None | 984 | missing_relations = set() |
168 | 889 | charm_message = None | 985 | incomplete_relations = set() |
169 | 890 | 986 | ||
171 | 891 | for generic_interface in incomplete_rel_data.keys(): | 987 | for generic_interface, relations_states in incomplete_rel_data.items(): |
172 | 892 | related_interface = None | 988 | related_interface = None |
173 | 893 | missing_data = {} | 989 | missing_data = {} |
174 | 894 | # Related or not? | 990 | # Related or not? |
177 | 895 | for interface in incomplete_rel_data[generic_interface]: | 991 | for interface, relation_state in relations_states.items(): |
178 | 896 | if incomplete_rel_data[generic_interface][interface].get('related'): | 992 | if relation_state.get('related'): |
179 | 897 | related_interface = interface | 993 | related_interface = interface |
182 | 898 | missing_data = incomplete_rel_data[generic_interface][interface].get('missing_data') | 994 | missing_data = relation_state.get('missing_data') |
183 | 899 | # No relation ID for the generic_interface | 995 | break |
184 | 996 | # No relation ID for the generic_interface? | ||
185 | 900 | if not related_interface: | 997 | if not related_interface: |
186 | 901 | juju_log("{} relation is missing and must be related for " | 998 | juju_log("{} relation is missing and must be related for " |
187 | 902 | "functionality. ".format(generic_interface), 'WARN') | 999 | "functionality. ".format(generic_interface), 'WARN') |
188 | 903 | state = 'blocked' | 1000 | state = 'blocked' |
191 | 904 | if generic_interface not in missing_relations: | 1001 | missing_relations.add(generic_interface) |
190 | 905 | missing_relations.append(generic_interface) | ||
192 | 906 | else: | 1002 | else: |
194 | 907 | # Relation ID exists but no related unit | 1003 | # Relation ID eists but no related unit |
195 | 908 | if not missing_data: | 1004 | if not missing_data: |
199 | 909 | # Edge case relation ID exists but departing | 1005 | # Edge case - relation ID exists but departings |
200 | 910 | if ('departed' in hook_name() or 'broken' in hook_name()) \ | 1006 | _hook_name = hook_name() |
201 | 911 | and related_interface in hook_name(): | 1007 | if (('departed' in _hook_name or 'broken' in _hook_name) and |
202 | 1008 | related_interface in _hook_name): | ||
203 | 912 | state = 'blocked' | 1009 | state = 'blocked' |
206 | 913 | if generic_interface not in missing_relations: | 1010 | missing_relations.add(generic_interface) |
205 | 914 | missing_relations.append(generic_interface) | ||
207 | 915 | juju_log("{} relation's interface, {}, " | 1011 | juju_log("{} relation's interface, {}, " |
208 | 916 | "relationship is departed or broken " | 1012 | "relationship is departed or broken " |
209 | 917 | "and is required for functionality." | 1013 | "and is required for functionality." |
211 | 918 | "".format(generic_interface, related_interface), "WARN") | 1014 | "".format(generic_interface, related_interface), |
212 | 1015 | "WARN") | ||
213 | 919 | # Normal case relation ID exists but no related unit | 1016 | # Normal case relation ID exists but no related unit |
214 | 920 | # (joining) | 1017 | # (joining) |
215 | 921 | else: | 1018 | else: |
216 | 922 | juju_log("{} relations's interface, {}, is related but has " | 1019 | juju_log("{} relations's interface, {}, is related but has " |
217 | 923 | "no units in the relation." | 1020 | "no units in the relation." |
219 | 924 | "".format(generic_interface, related_interface), "INFO") | 1021 | "".format(generic_interface, related_interface), |
220 | 1022 | "INFO") | ||
221 | 925 | # Related unit exists and data missing on the relation | 1023 | # Related unit exists and data missing on the relation |
222 | 926 | else: | 1024 | else: |
223 | 927 | juju_log("{} relation's interface, {}, is related awaiting " | 1025 | juju_log("{} relation's interface, {}, is related awaiting " |
224 | @@ -930,9 +1028,8 @@ | |||
225 | 930 | ", ".join(missing_data)), "INFO") | 1028 | ", ".join(missing_data)), "INFO") |
226 | 931 | if state != 'blocked': | 1029 | if state != 'blocked': |
227 | 932 | state = 'waiting' | 1030 | state = 'waiting' |
231 | 933 | if generic_interface not in incomplete_relations \ | 1031 | if generic_interface not in missing_relations: |
232 | 934 | and generic_interface not in missing_relations: | 1032 | incomplete_relations.add(generic_interface) |
230 | 935 | incomplete_relations.append(generic_interface) | ||
233 | 936 | 1033 | ||
234 | 937 | if missing_relations: | 1034 | if missing_relations: |
235 | 938 | message = "Missing relations: {}".format(", ".join(missing_relations)) | 1035 | message = "Missing relations: {}".format(", ".join(missing_relations)) |
236 | @@ -945,9 +1042,22 @@ | |||
237 | 945 | "".format(", ".join(incomplete_relations)) | 1042 | "".format(", ".join(incomplete_relations)) |
238 | 946 | state = 'waiting' | 1043 | state = 'waiting' |
239 | 947 | 1044 | ||
243 | 948 | # Run charm specific checks | 1045 | return state, message |
244 | 949 | if charm_func: | 1046 | |
245 | 950 | charm_state, charm_message = charm_func(configs) | 1047 | |
246 | 1048 | def _ows_check_charm_func(state, message, charm_func_with_configs): | ||
247 | 1049 | """Run a custom check function for the charm to see if it wants to | ||
248 | 1050 | change the state. This is only run if not in 'maintenance' and | ||
249 | 1051 | tests to see if the new state is more important that the previous | ||
250 | 1052 | one determined by the interfaces/relations check. | ||
251 | 1053 | |||
252 | 1054 | @param state: the previously determined state so far. | ||
253 | 1055 | @param message: the user orientated message so far. | ||
254 | 1056 | @param charm_func: a callable function that returns state, message | ||
255 | 1057 | @returns state, message strings. | ||
256 | 1058 | """ | ||
257 | 1059 | if charm_func_with_configs: | ||
258 | 1060 | charm_state, charm_message = charm_func_with_configs() | ||
259 | 951 | if charm_state != 'active' and charm_state != 'unknown': | 1061 | if charm_state != 'active' and charm_state != 'unknown': |
260 | 952 | state = workload_state_compare(state, charm_state) | 1062 | state = workload_state_compare(state, charm_state) |
261 | 953 | if message: | 1063 | if message: |
262 | @@ -956,72 +1066,151 @@ | |||
263 | 956 | message = "{}, {}".format(message, charm_message) | 1066 | message = "{}, {}".format(message, charm_message) |
264 | 957 | else: | 1067 | else: |
265 | 958 | message = charm_message | 1068 | message = charm_message |
287 | 959 | 1069 | return state, message | |
288 | 960 | # If the charm thinks the unit is active, check that the actual services | 1070 | |
289 | 961 | # really are active. | 1071 | |
290 | 962 | if services is not None and state == 'active': | 1072 | def _ows_check_services_running(services, ports): |
291 | 963 | # if we're passed the dict() then just grab the values as a list. | 1073 | """Check that the services that should be running are actually running |
292 | 964 | if isinstance(services, dict): | 1074 | and that any ports specified are being listened to. |
293 | 965 | services = services.values() | 1075 | |
294 | 966 | # either extract the list of services from the dictionary, or if | 1076 | @param services: list of strings OR dictionary specifying services/ports |
295 | 967 | # it is a simple string, use that. i.e. works with mixed lists. | 1077 | @param ports: list of ports |
296 | 968 | _s = [] | 1078 | @returns state, message: strings or None, None |
297 | 969 | for s in services: | 1079 | """ |
298 | 970 | if isinstance(s, dict) and 'service' in s: | 1080 | messages = [] |
299 | 971 | _s.append(s['service']) | 1081 | state = None |
300 | 972 | if isinstance(s, str): | 1082 | if services is not None: |
301 | 973 | _s.append(s) | 1083 | services = _extract_services_list_helper(services) |
302 | 974 | services_running = [service_running(s) for s in _s] | 1084 | services_running, running = _check_running_services(services) |
303 | 975 | if not all(services_running): | 1085 | if not all(running): |
304 | 976 | not_running = [s for s, running in zip(_s, services_running) | 1086 | messages.append( |
305 | 977 | if not running] | 1087 | "Services not running that should be: {}" |
306 | 978 | message = ("Services not running that should be: {}" | 1088 | .format(", ".join(_filter_tuples(services_running, False)))) |
286 | 979 | .format(", ".join(not_running))) | ||
307 | 980 | state = 'blocked' | 1089 | state = 'blocked' |
308 | 981 | # also verify that the ports that should be open are open | 1090 | # also verify that the ports that should be open are open |
309 | 982 | # NB, that ServiceManager objects only OPTIONALLY have ports | 1091 | # NB, that ServiceManager objects only OPTIONALLY have ports |
335 | 983 | port_map = OrderedDict([(s['service'], s['ports']) | 1092 | map_not_open, ports_open = ( |
336 | 984 | for s in services if 'ports' in s]) | 1093 | _check_listening_on_services_ports(services)) |
337 | 985 | if state == 'active' and port_map: | 1094 | if not all(ports_open): |
338 | 986 | all_ports = list(itertools.chain(*port_map.values())) | 1095 | # find which service has missing ports. They are in service |
339 | 987 | ports_open = [port_has_listener('0.0.0.0', p) | 1096 | # order which makes it a bit easier. |
340 | 988 | for p in all_ports] | 1097 | message_parts = {service: ", ".join([str(v) for v in open_ports]) |
341 | 989 | if not all(ports_open): | 1098 | for service, open_ports in map_not_open.items()} |
342 | 990 | not_opened = [p for p, opened in zip(all_ports, ports_open) | 1099 | message = ", ".join( |
343 | 991 | if not opened] | 1100 | ["{}: [{}]".format(s, sp) for s, sp in message_parts.items()]) |
344 | 992 | map_not_open = OrderedDict() | 1101 | messages.append( |
345 | 993 | for service, ports in port_map.items(): | 1102 | "Services with ports not open that should be: {}" |
346 | 994 | closed_ports = set(ports).intersection(not_opened) | 1103 | .format(message)) |
347 | 995 | if closed_ports: | 1104 | state = 'blocked' |
323 | 996 | map_not_open[service] = closed_ports | ||
324 | 997 | # find which service has missing ports. They are in service | ||
325 | 998 | # order which makes it a bit easier. | ||
326 | 999 | message = ( | ||
327 | 1000 | "Services with ports not open that should be: {}" | ||
328 | 1001 | .format( | ||
329 | 1002 | ", ".join([ | ||
330 | 1003 | "{}: [{}]".format( | ||
331 | 1004 | service, | ||
332 | 1005 | ", ".join([str(v) for v in ports])) | ||
333 | 1006 | for service, ports in map_not_open.items()]))) | ||
334 | 1007 | state = 'blocked' | ||
348 | 1008 | 1105 | ||
350 | 1009 | if ports is not None and state == 'active': | 1106 | if ports is not None: |
351 | 1010 | # and we can also check ports which we don't know the service for | 1107 | # and we can also check ports which we don't know the service for |
355 | 1011 | ports_open = [port_has_listener('0.0.0.0', p) for p in ports] | 1108 | ports_open, ports_open_bools = _check_listening_on_ports_list(ports) |
356 | 1012 | if not all(ports_open): | 1109 | if not all(ports_open_bools): |
357 | 1013 | message = ( | 1110 | messages.append( |
358 | 1014 | "Ports which should be open, but are not: {}" | 1111 | "Ports which should be open, but are not: {}" |
360 | 1015 | .format(", ".join([str(p) for p, v in zip(ports, ports_open) | 1112 | .format(", ".join([str(p) for p, v in ports_open |
361 | 1016 | if not v]))) | 1113 | if not v]))) |
362 | 1017 | state = 'blocked' | 1114 | state = 'blocked' |
363 | 1018 | 1115 | ||
370 | 1019 | # Set to active if all requirements have been met | 1116 | if state is not None: |
371 | 1020 | if state == 'active': | 1117 | message = "; ".join(messages) |
372 | 1021 | message = "Unit is ready" | 1118 | return state, message |
373 | 1022 | juju_log(message, "INFO") | 1119 | |
374 | 1023 | 1120 | return None, None | |
375 | 1024 | status_set(state, message) | 1121 | |
376 | 1122 | |||
377 | 1123 | def _extract_services_list_helper(services): | ||
378 | 1124 | """Extract a OrderedDict of {service: [ports]} of the supplied services | ||
379 | 1125 | for use by the other functions. | ||
380 | 1126 | |||
381 | 1127 | The services object can either be: | ||
382 | 1128 | - None : no services were passed (an empty dict is returned) | ||
383 | 1129 | - a list of strings | ||
384 | 1130 | - A dictionary (optionally OrderedDict) {service_name: {'service': ..}} | ||
385 | 1131 | - An array of [{'service': service_name, ...}, ...] | ||
386 | 1132 | |||
387 | 1133 | @param services: see above | ||
388 | 1134 | @returns OrderedDict(service: [ports], ...) | ||
389 | 1135 | """ | ||
390 | 1136 | if services is None: | ||
391 | 1137 | return {} | ||
392 | 1138 | if isinstance(services, dict): | ||
393 | 1139 | services = services.values() | ||
394 | 1140 | # either extract the list of services from the dictionary, or if | ||
395 | 1141 | # it is a simple string, use that. i.e. works with mixed lists. | ||
396 | 1142 | _s = OrderedDict() | ||
397 | 1143 | for s in services: | ||
398 | 1144 | if isinstance(s, dict) and 'service' in s: | ||
399 | 1145 | _s[s['service']] = s.get('ports', []) | ||
400 | 1146 | if isinstance(s, str): | ||
401 | 1147 | _s[s] = [] | ||
402 | 1148 | return _s | ||
403 | 1149 | |||
404 | 1150 | |||
405 | 1151 | def _check_running_services(services): | ||
406 | 1152 | """Check that the services dict provided is actually running and provide | ||
407 | 1153 | a list of (service, boolean) tuples for each service. | ||
408 | 1154 | |||
409 | 1155 | Returns both a zipped list of (service, boolean) and a list of booleans | ||
410 | 1156 | in the same order as the services. | ||
411 | 1157 | |||
412 | 1158 | @param services: OrderedDict of strings: [ports], one for each service to | ||
413 | 1159 | check. | ||
414 | 1160 | @returns [(service, boolean), ...], : results for checks | ||
415 | 1161 | [boolean] : just the result of the service checks | ||
416 | 1162 | """ | ||
417 | 1163 | services_running = [service_running(s) for s in services] | ||
418 | 1164 | return list(zip(services, services_running)), services_running | ||
419 | 1165 | |||
420 | 1166 | |||
421 | 1167 | def _check_listening_on_services_ports(services, test=False): | ||
422 | 1168 | """Check that the unit is actually listening (has the port open) on the | ||
423 | 1169 | ports that the service specifies are open. If test is True then the | ||
424 | 1170 | function returns the services with ports that are open rather than | ||
425 | 1171 | closed. | ||
426 | 1172 | |||
427 | 1173 | Returns an OrderedDict of service: ports and a list of booleans | ||
428 | 1174 | |||
429 | 1175 | @param services: OrderedDict(service: [port, ...], ...) | ||
430 | 1176 | @param test: default=False, if False, test for closed, otherwise open. | ||
431 | 1177 | @returns OrderedDict(service: [port-not-open, ...]...), [boolean] | ||
432 | 1178 | """ | ||
433 | 1179 | test = not(not(test)) # ensure test is True or False | ||
434 | 1180 | all_ports = list(itertools.chain(*services.values())) | ||
435 | 1181 | ports_states = [port_has_listener('0.0.0.0', p) for p in all_ports] | ||
436 | 1182 | map_ports = OrderedDict() | ||
437 | 1183 | matched_ports = [p for p, opened in zip(all_ports, ports_states) | ||
438 | 1184 | if opened == test] # essentially opened xor test | ||
439 | 1185 | for service, ports in services.items(): | ||
440 | 1186 | set_ports = set(ports).intersection(matched_ports) | ||
441 | 1187 | if set_ports: | ||
442 | 1188 | map_ports[service] = set_ports | ||
443 | 1189 | return map_ports, ports_states | ||
444 | 1190 | |||
445 | 1191 | |||
446 | 1192 | def _check_listening_on_ports_list(ports): | ||
447 | 1193 | """Check that the ports list given are being listened to | ||
448 | 1194 | |||
449 | 1195 | Returns a list of ports being listened to and a list of the | ||
450 | 1196 | booleans. | ||
451 | 1197 | |||
452 | 1198 | @param ports: LIST or port numbers. | ||
453 | 1199 | @returns [(port_num, boolean), ...], [boolean] | ||
454 | 1200 | """ | ||
455 | 1201 | ports_open = [port_has_listener('0.0.0.0', p) for p in ports] | ||
456 | 1202 | return zip(ports, ports_open), ports_open | ||
457 | 1203 | |||
458 | 1204 | |||
459 | 1205 | def _filter_tuples(services_states, state): | ||
460 | 1206 | """Return a simple list from a list of tuples according to the condition | ||
461 | 1207 | |||
462 | 1208 | @param services_states: LIST of (string, boolean): service and running | ||
463 | 1209 | state. | ||
464 | 1210 | @param state: Boolean to match the tuple against. | ||
465 | 1211 | @returns [LIST of strings] that matched the tuple RHS. | ||
466 | 1212 | """ | ||
467 | 1213 | return [s for s, b in services_states if b == state] | ||
468 | 1025 | 1214 | ||
469 | 1026 | 1215 | ||
470 | 1027 | def workload_state_compare(current_workload_state, workload_state): | 1216 | def workload_state_compare(current_workload_state, workload_state): |
471 | @@ -1046,8 +1235,7 @@ | |||
472 | 1046 | 1235 | ||
473 | 1047 | 1236 | ||
474 | 1048 | def incomplete_relation_data(configs, required_interfaces): | 1237 | def incomplete_relation_data(configs, required_interfaces): |
477 | 1049 | """ | 1238 | """Check complete contexts against required_interfaces |
476 | 1050 | Check complete contexts against required_interfaces | ||
478 | 1051 | Return dictionary of incomplete relation data. | 1239 | Return dictionary of incomplete relation data. |
479 | 1052 | 1240 | ||
480 | 1053 | configs is an OSConfigRenderer object with configs registered | 1241 | configs is an OSConfigRenderer object with configs registered |
481 | @@ -1072,19 +1260,13 @@ | |||
482 | 1072 | 'shared-db': {'related': True}}} | 1260 | 'shared-db': {'related': True}}} |
483 | 1073 | """ | 1261 | """ |
484 | 1074 | complete_ctxts = configs.complete_contexts() | 1262 | complete_ctxts = configs.complete_contexts() |
498 | 1075 | incomplete_relations = [] | 1263 | incomplete_relations = [ |
499 | 1076 | for svc_type in required_interfaces.keys(): | 1264 | svc_type |
500 | 1077 | # Avoid duplicates | 1265 | for svc_type, interfaces in required_interfaces.items() |
501 | 1078 | found_ctxt = False | 1266 | if not set(interfaces).intersection(complete_ctxts)] |
502 | 1079 | for interface in required_interfaces[svc_type]: | 1267 | return { |
503 | 1080 | if interface in complete_ctxts: | 1268 | i: configs.get_incomplete_context_data(required_interfaces[i]) |
504 | 1081 | found_ctxt = True | 1269 | for i in incomplete_relations} |
492 | 1082 | if not found_ctxt: | ||
493 | 1083 | incomplete_relations.append(svc_type) | ||
494 | 1084 | incomplete_context_data = {} | ||
495 | 1085 | for i in incomplete_relations: | ||
496 | 1086 | incomplete_context_data[i] = configs.get_incomplete_context_data(required_interfaces[i]) | ||
497 | 1087 | return incomplete_context_data | ||
505 | 1088 | 1270 | ||
506 | 1089 | 1271 | ||
507 | 1090 | def do_action_openstack_upgrade(package, upgrade_callback, configs): | 1272 | def do_action_openstack_upgrade(package, upgrade_callback, configs): |
508 | @@ -1145,3 +1327,242 @@ | |||
509 | 1145 | relation_set(relation_id=rid, | 1327 | relation_set(relation_id=rid, |
510 | 1146 | relation_settings=trigger, | 1328 | relation_settings=trigger, |
511 | 1147 | ) | 1329 | ) |
512 | 1330 | |||
513 | 1331 | |||
514 | 1332 | def check_actually_paused(services=None, ports=None): | ||
515 | 1333 | """Check that services listed in the services object and and ports | ||
516 | 1334 | are actually closed (not listened to), to verify that the unit is | ||
517 | 1335 | properly paused. | ||
518 | 1336 | |||
519 | 1337 | @param services: See _extract_services_list_helper | ||
520 | 1338 | @returns status, : string for status (None if okay) | ||
521 | 1339 | message : string for problem for status_set | ||
522 | 1340 | """ | ||
523 | 1341 | state = None | ||
524 | 1342 | message = None | ||
525 | 1343 | messages = [] | ||
526 | 1344 | if services is not None: | ||
527 | 1345 | services = _extract_services_list_helper(services) | ||
528 | 1346 | services_running, services_states = _check_running_services(services) | ||
529 | 1347 | if any(services_states): | ||
530 | 1348 | # there shouldn't be any running so this is a problem | ||
531 | 1349 | messages.append("these services running: {}" | ||
532 | 1350 | .format(", ".join( | ||
533 | 1351 | _filter_tuples(services_running, True)))) | ||
534 | 1352 | state = "blocked" | ||
535 | 1353 | ports_open, ports_open_bools = ( | ||
536 | 1354 | _check_listening_on_services_ports(services, True)) | ||
537 | 1355 | if any(ports_open_bools): | ||
538 | 1356 | message_parts = {service: ", ".join([str(v) for v in open_ports]) | ||
539 | 1357 | for service, open_ports in ports_open.items()} | ||
540 | 1358 | message = ", ".join( | ||
541 | 1359 | ["{}: [{}]".format(s, sp) for s, sp in message_parts.items()]) | ||
542 | 1360 | messages.append( | ||
543 | 1361 | "these service:ports are open: {}".format(message)) | ||
544 | 1362 | state = 'blocked' | ||
545 | 1363 | if ports is not None: | ||
546 | 1364 | ports_open, bools = _check_listening_on_ports_list(ports) | ||
547 | 1365 | if any(bools): | ||
548 | 1366 | messages.append( | ||
549 | 1367 | "these ports which should be closed, but are open: {}" | ||
550 | 1368 | .format(", ".join([str(p) for p, v in ports_open if v]))) | ||
551 | 1369 | state = 'blocked' | ||
552 | 1370 | if messages: | ||
553 | 1371 | message = ("Services should be paused but {}" | ||
554 | 1372 | .format(", ".join(messages))) | ||
555 | 1373 | return state, message | ||
556 | 1374 | |||
557 | 1375 | |||
558 | 1376 | def set_unit_paused(): | ||
559 | 1377 | """Set the unit to a paused state in the local kv() store. | ||
560 | 1378 | This does NOT actually pause the unit | ||
561 | 1379 | """ | ||
562 | 1380 | with unitdata.HookData()() as kv: | ||
563 | 1381 | kv.set('unit-paused', True) | ||
564 | 1382 | |||
565 | 1383 | |||
566 | 1384 | def clear_unit_paused(): | ||
567 | 1385 | """Clear the unit from a paused state in the local kv() store | ||
568 | 1386 | This does NOT actually restart any services - it only clears the | ||
569 | 1387 | local state. | ||
570 | 1388 | """ | ||
571 | 1389 | with unitdata.HookData()() as kv: | ||
572 | 1390 | kv.set('unit-paused', False) | ||
573 | 1391 | |||
574 | 1392 | |||
575 | 1393 | def is_unit_paused_set(): | ||
576 | 1394 | """Return the state of the kv().get('unit-paused'). | ||
577 | 1395 | This does NOT verify that the unit really is paused. | ||
578 | 1396 | |||
579 | 1397 | To help with units that don't have HookData() (testing) | ||
580 | 1398 | if it excepts, return False | ||
581 | 1399 | """ | ||
582 | 1400 | try: | ||
583 | 1401 | with unitdata.HookData()() as kv: | ||
584 | 1402 | # transform something truth-y into a Boolean. | ||
585 | 1403 | return not(not(kv.get('unit-paused'))) | ||
586 | 1404 | except: | ||
587 | 1405 | return False | ||
588 | 1406 | |||
589 | 1407 | |||
590 | 1408 | def pause_unit(assess_status_func, services=None, ports=None, | ||
591 | 1409 | charm_func=None): | ||
592 | 1410 | """Pause a unit by stopping the services and setting 'unit-paused' | ||
593 | 1411 | in the local kv() store. | ||
594 | 1412 | |||
595 | 1413 | Also checks that the services have stopped and ports are no longer | ||
596 | 1414 | being listened to. | ||
597 | 1415 | |||
598 | 1416 | An optional charm_func() can be called that can either raise an | ||
599 | 1417 | Exception or return non None, None to indicate that the unit | ||
600 | 1418 | didn't pause cleanly. | ||
601 | 1419 | |||
602 | 1420 | The signature for charm_func is: | ||
603 | 1421 | charm_func() -> message: string | ||
604 | 1422 | |||
605 | 1423 | charm_func() is executed after any services are stopped, if supplied. | ||
606 | 1424 | |||
607 | 1425 | The services object can either be: | ||
608 | 1426 | - None : no services were passed (an empty dict is returned) | ||
609 | 1427 | - a list of strings | ||
610 | 1428 | - A dictionary (optionally OrderedDict) {service_name: {'service': ..}} | ||
611 | 1429 | - An array of [{'service': service_name, ...}, ...] | ||
612 | 1430 | |||
613 | 1431 | @param assess_status_func: (f() -> message: string | None) or None | ||
614 | 1432 | @param services: OPTIONAL see above | ||
615 | 1433 | @param ports: OPTIONAL list of port | ||
616 | 1434 | @param charm_func: function to run for custom charm pausing. | ||
617 | 1435 | @returns None | ||
618 | 1436 | @raises Exception(message) on an error for action_fail(). | ||
619 | 1437 | """ | ||
620 | 1438 | services = _extract_services_list_helper(services) | ||
621 | 1439 | messages = [] | ||
622 | 1440 | if services: | ||
623 | 1441 | for service in services.keys(): | ||
624 | 1442 | stopped = service_pause(service) | ||
625 | 1443 | if not stopped: | ||
626 | 1444 | messages.append("{} didn't stop cleanly.".format(service)) | ||
627 | 1445 | if charm_func: | ||
628 | 1446 | try: | ||
629 | 1447 | message = charm_func() | ||
630 | 1448 | if message: | ||
631 | 1449 | messages.append(message) | ||
632 | 1450 | except Exception as e: | ||
633 | 1451 | message.append(str(e)) | ||
634 | 1452 | set_unit_paused() | ||
635 | 1453 | if assess_status_func: | ||
636 | 1454 | message = assess_status_func() | ||
637 | 1455 | if message: | ||
638 | 1456 | messages.append(message) | ||
639 | 1457 | if messages: | ||
640 | 1458 | raise Exception("Couldn't pause: {}".format("; ".join(messages))) | ||
641 | 1459 | |||
642 | 1460 | |||
643 | 1461 | def resume_unit(assess_status_func, services=None, ports=None, | ||
644 | 1462 | charm_func=None): | ||
645 | 1463 | """Resume a unit by starting the services and clearning 'unit-paused' | ||
646 | 1464 | in the local kv() store. | ||
647 | 1465 | |||
648 | 1466 | Also checks that the services have started and ports are being listened to. | ||
649 | 1467 | |||
650 | 1468 | An optional charm_func() can be called that can either raise an | ||
651 | 1469 | Exception or return non None to indicate that the unit | ||
652 | 1470 | didn't resume cleanly. | ||
653 | 1471 | |||
654 | 1472 | The signature for charm_func is: | ||
655 | 1473 | charm_func() -> message: string | ||
656 | 1474 | |||
657 | 1475 | charm_func() is executed after any services are started, if supplied. | ||
658 | 1476 | |||
659 | 1477 | The services object can either be: | ||
660 | 1478 | - None : no services were passed (an empty dict is returned) | ||
661 | 1479 | - a list of strings | ||
662 | 1480 | - A dictionary (optionally OrderedDict) {service_name: {'service': ..}} | ||
663 | 1481 | - An array of [{'service': service_name, ...}, ...] | ||
664 | 1482 | |||
665 | 1483 | @param assess_status_func: (f() -> message: string | None) or None | ||
666 | 1484 | @param services: OPTIONAL see above | ||
667 | 1485 | @param ports: OPTIONAL list of port | ||
668 | 1486 | @param charm_func: function to run for custom charm resuming. | ||
669 | 1487 | @returns None | ||
670 | 1488 | @raises Exception(message) on an error for action_fail(). | ||
671 | 1489 | """ | ||
672 | 1490 | services = _extract_services_list_helper(services) | ||
673 | 1491 | messages = [] | ||
674 | 1492 | if services: | ||
675 | 1493 | for service in services.keys(): | ||
676 | 1494 | started = service_resume(service) | ||
677 | 1495 | if not started: | ||
678 | 1496 | messages.append("{} didn't start cleanly.".format(service)) | ||
679 | 1497 | if charm_func: | ||
680 | 1498 | try: | ||
681 | 1499 | message = charm_func() | ||
682 | 1500 | if message: | ||
683 | 1501 | messages.append(message) | ||
684 | 1502 | except Exception as e: | ||
685 | 1503 | message.append(str(e)) | ||
686 | 1504 | clear_unit_paused() | ||
687 | 1505 | if assess_status_func: | ||
688 | 1506 | message = assess_status_func() | ||
689 | 1507 | if message: | ||
690 | 1508 | messages.append(message) | ||
691 | 1509 | if messages: | ||
692 | 1510 | raise Exception("Couldn't resume: {}".format("; ".join(messages))) | ||
693 | 1511 | |||
694 | 1512 | |||
695 | 1513 | def make_assess_status_func(*args, **kwargs): | ||
696 | 1514 | """Creates an assess_status_func() suitable for handing to pause_unit() | ||
697 | 1515 | and resume_unit(). | ||
698 | 1516 | |||
699 | 1517 | This uses the _determine_os_workload_status(...) function to determine | ||
700 | 1518 | what the workload_status should be for the unit. If the unit is | ||
701 | 1519 | not in maintenance or active states, then the message is returned to | ||
702 | 1520 | the caller. This is so an action that doesn't result in either a | ||
703 | 1521 | complete pause or complete resume can signal failure with an action_fail() | ||
704 | 1522 | """ | ||
705 | 1523 | def _assess_status_func(): | ||
706 | 1524 | state, message = _determine_os_workload_status(*args, **kwargs) | ||
707 | 1525 | status_set(state, message) | ||
708 | 1526 | if state not in ['maintenance', 'active']: | ||
709 | 1527 | return message | ||
710 | 1528 | return None | ||
711 | 1529 | |||
712 | 1530 | return _assess_status_func | ||
713 | 1531 | |||
714 | 1532 | |||
715 | 1533 | def pausable_restart_on_change(restart_map, stopstart=False): | ||
716 | 1534 | """A restart_on_change decorator that checks to see if the unit is | ||
717 | 1535 | paused. If it is paused then the decorated function doesn't fire. | ||
718 | 1536 | |||
719 | 1537 | This is provided as a helper, as the @restart_on_change(...) decorator | ||
720 | 1538 | is in core.host, yet the openstack specific helpers are in this file | ||
721 | 1539 | (contrib.openstack.utils). Thus, this needs to be an optional feature | ||
722 | 1540 | for openstack charms (or charms that wish to use the openstack | ||
723 | 1541 | pause/resume type features). | ||
724 | 1542 | |||
725 | 1543 | It is used as follows: | ||
726 | 1544 | |||
727 | 1545 | from contrib.openstack.utils import ( | ||
728 | 1546 | pausable_restart_on_change as restart_on_change) | ||
729 | 1547 | |||
730 | 1548 | @restart_on_change(restart_map, stopstart=<boolean>) | ||
731 | 1549 | def some_hook(...): | ||
732 | 1550 | pass | ||
733 | 1551 | |||
734 | 1552 | see core.utils.restart_on_change() for more details. | ||
735 | 1553 | |||
736 | 1554 | @param f: the function to decorate | ||
737 | 1555 | @param restart_map: the restart map {conf_file: [services]} | ||
738 | 1556 | @param stopstart: DEFAULT false; whether to stop, start or just restart | ||
739 | 1557 | @returns decorator to use a restart_on_change with pausability | ||
740 | 1558 | """ | ||
741 | 1559 | def wrap(f): | ||
742 | 1560 | @functools.wraps(f) | ||
743 | 1561 | def wrapped_f(*args, **kwargs): | ||
744 | 1562 | if is_unit_paused_set(): | ||
745 | 1563 | return f(*args, **kwargs) | ||
746 | 1564 | # otherwise, normal restart_on_change functionality | ||
747 | 1565 | return restart_on_change_helper( | ||
748 | 1566 | (lambda: f(*args, **kwargs)), restart_map, stopstart) | ||
749 | 1567 | return wrapped_f | ||
750 | 1568 | return wrap | ||
751 | 1148 | 1569 | ||
752 | === modified file 'charmhelpers/core/host.py' | |||
753 | --- charmhelpers/core/host.py 2016-01-19 21:53:13 +0000 | |||
754 | +++ charmhelpers/core/host.py 2016-03-07 16:45:36 +0000 | |||
755 | @@ -30,6 +30,8 @@ | |||
756 | 30 | import string | 30 | import string |
757 | 31 | import subprocess | 31 | import subprocess |
758 | 32 | import hashlib | 32 | import hashlib |
759 | 33 | import functools | ||
760 | 34 | import itertools | ||
761 | 33 | from contextlib import contextmanager | 35 | from contextlib import contextmanager |
762 | 34 | from collections import OrderedDict | 36 | from collections import OrderedDict |
763 | 35 | 37 | ||
764 | @@ -428,27 +430,47 @@ | |||
765 | 428 | restarted if any file matching the pattern got changed, created | 430 | restarted if any file matching the pattern got changed, created |
766 | 429 | or removed. Standard wildcards are supported, see documentation | 431 | or removed. Standard wildcards are supported, see documentation |
767 | 430 | for the 'glob' module for more information. | 432 | for the 'glob' module for more information. |
768 | 433 | |||
769 | 434 | @param restart_map: {path_file_name: [service_name, ...] | ||
770 | 435 | @param stopstart: DEFAULT false; whether to stop, start OR restart | ||
771 | 436 | @returns result from decorated function | ||
772 | 431 | """ | 437 | """ |
773 | 432 | def wrap(f): | 438 | def wrap(f): |
774 | 439 | @functools.wraps(f) | ||
775 | 433 | def wrapped_f(*args, **kwargs): | 440 | def wrapped_f(*args, **kwargs): |
790 | 434 | checksums = {path: path_hash(path) for path in restart_map} | 441 | return restart_on_change_helper( |
791 | 435 | f(*args, **kwargs) | 442 | (lambda: f(*args, **kwargs)), restart_map, stopstart) |
778 | 436 | restarts = [] | ||
779 | 437 | for path in restart_map: | ||
780 | 438 | if path_hash(path) != checksums[path]: | ||
781 | 439 | restarts += restart_map[path] | ||
782 | 440 | services_list = list(OrderedDict.fromkeys(restarts)) | ||
783 | 441 | if not stopstart: | ||
784 | 442 | for service_name in services_list: | ||
785 | 443 | service('restart', service_name) | ||
786 | 444 | else: | ||
787 | 445 | for action in ['stop', 'start']: | ||
788 | 446 | for service_name in services_list: | ||
789 | 447 | service(action, service_name) | ||
792 | 448 | return wrapped_f | 443 | return wrapped_f |
793 | 449 | return wrap | 444 | return wrap |
794 | 450 | 445 | ||
795 | 451 | 446 | ||
796 | 447 | def restart_on_change_helper(lambda_f, restart_map, stopstart=False): | ||
797 | 448 | """Helper function to perform the restart_on_change function. | ||
798 | 449 | |||
799 | 450 | This is provided for decorators to restart services if files described | ||
800 | 451 | in the restart_map have changed after an invocation of lambda_f(). | ||
801 | 452 | |||
802 | 453 | @param lambda_f: function to call. | ||
803 | 454 | @param restart_map: {file: [service, ...]} | ||
804 | 455 | @param stopstart: whether to stop, start or restart a service | ||
805 | 456 | @returns result of lambda_f() | ||
806 | 457 | """ | ||
807 | 458 | checksums = {path: path_hash(path) for path in restart_map} | ||
808 | 459 | r = lambda_f() | ||
809 | 460 | # create a list of lists of the services to restart | ||
810 | 461 | restarts = [restart_map[path] | ||
811 | 462 | for path in restart_map | ||
812 | 463 | if path_hash(path) != checksums[path]] | ||
813 | 464 | # create a flat list of ordered services without duplicates from lists | ||
814 | 465 | services_list = list(OrderedDict.fromkeys(itertools.chain(*restarts))) | ||
815 | 466 | if services_list: | ||
816 | 467 | actions = ('stop', 'start') if stopstart else ('restart',) | ||
817 | 468 | for action in actions: | ||
818 | 469 | for service_name in services_list: | ||
819 | 470 | service(action, service_name) | ||
820 | 471 | return r | ||
821 | 472 | |||
822 | 473 | |||
823 | 452 | def lsb_release(): | 474 | def lsb_release(): |
824 | 453 | """Return /etc/lsb-release in a dict""" | 475 | """Return /etc/lsb-release in a dict""" |
825 | 454 | d = {} | 476 | d = {} |
826 | 455 | 477 | ||
827 | === modified file 'tests/contrib/openstack/test_openstack_utils.py' | |||
828 | --- tests/contrib/openstack/test_openstack_utils.py 2016-02-17 16:29:13 +0000 | |||
829 | +++ tests/contrib/openstack/test_openstack_utils.py 2016-03-07 16:45:36 +0000 | |||
830 | @@ -919,7 +919,10 @@ | |||
831 | 919 | 919 | ||
832 | 920 | @patch.object(openstack, 'juju_log') | 920 | @patch.object(openstack, 'juju_log') |
833 | 921 | @patch('charmhelpers.contrib.openstack.utils.status_set') | 921 | @patch('charmhelpers.contrib.openstack.utils.status_set') |
835 | 922 | def test_set_os_workload_status_complete(self, status_set, log): | 922 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', |
836 | 923 | return_value=False) | ||
837 | 924 | def test_set_os_workload_status_complete( | ||
838 | 925 | self, is_unit_paused_set, status_set, log): | ||
839 | 923 | configs = MagicMock() | 926 | configs = MagicMock() |
840 | 924 | configs.complete_contexts.return_value = ['shared-db', | 927 | configs.complete_contexts.return_value = ['shared-db', |
841 | 925 | 'amqp', | 928 | 'amqp', |
842 | @@ -936,9 +939,11 @@ | |||
843 | 936 | @patch('charmhelpers.contrib.openstack.utils.incomplete_relation_data', | 939 | @patch('charmhelpers.contrib.openstack.utils.incomplete_relation_data', |
844 | 937 | return_value={'identity': {'identity-service': {'related': True}}}) | 940 | return_value={'identity': {'identity-service': {'related': True}}}) |
845 | 938 | @patch('charmhelpers.contrib.openstack.utils.status_set') | 941 | @patch('charmhelpers.contrib.openstack.utils.status_set') |
849 | 939 | def test_set_os_workload_status_related_incomplete(self, status_set, | 942 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', |
850 | 940 | incomplete_relation_data, | 943 | return_value=False) |
851 | 941 | log): | 944 | def test_set_os_workload_status_related_incomplete( |
852 | 945 | self, is_unit_paused_set, status_set, | ||
853 | 946 | incomplete_relation_data, log): | ||
854 | 942 | configs = MagicMock() | 947 | configs = MagicMock() |
855 | 943 | configs.complete_contexts.return_value = ['shared-db', 'amqp'] | 948 | configs.complete_contexts.return_value = ['shared-db', 'amqp'] |
856 | 944 | required_interfaces = { | 949 | required_interfaces = { |
857 | @@ -954,8 +959,11 @@ | |||
858 | 954 | @patch('charmhelpers.contrib.openstack.utils.incomplete_relation_data', | 959 | @patch('charmhelpers.contrib.openstack.utils.incomplete_relation_data', |
859 | 955 | return_value={'identity': {'identity-service': {'related': False}}}) | 960 | return_value={'identity': {'identity-service': {'related': False}}}) |
860 | 956 | @patch('charmhelpers.contrib.openstack.utils.status_set') | 961 | @patch('charmhelpers.contrib.openstack.utils.status_set') |
863 | 957 | def test_set_os_workload_status_absent(self, status_set, | 962 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', |
864 | 958 | incomplete_relation_data, log): | 963 | return_value=False) |
865 | 964 | def test_set_os_workload_status_absent( | ||
866 | 965 | self, is_unit_paused_set, status_set, | ||
867 | 966 | incomplete_relation_data, log): | ||
868 | 959 | configs = MagicMock() | 967 | configs = MagicMock() |
869 | 960 | configs.complete_contexts.return_value = ['shared-db', 'amqp'] | 968 | configs.complete_contexts.return_value = ['shared-db', 'amqp'] |
870 | 961 | required_interfaces = { | 969 | required_interfaces = { |
871 | @@ -973,9 +981,11 @@ | |||
872 | 973 | @patch('charmhelpers.contrib.openstack.utils.incomplete_relation_data', | 981 | @patch('charmhelpers.contrib.openstack.utils.incomplete_relation_data', |
873 | 974 | return_value={'identity': {'identity-service': {'related': True}}}) | 982 | return_value={'identity': {'identity-service': {'related': True}}}) |
874 | 975 | @patch('charmhelpers.contrib.openstack.utils.status_set') | 983 | @patch('charmhelpers.contrib.openstack.utils.status_set') |
878 | 976 | def test_set_os_workload_status_related_broken(self, status_set, | 984 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', |
879 | 977 | incomplete_relation_data, | 985 | return_value=False) |
880 | 978 | hook_name, log): | 986 | def test_set_os_workload_status_related_broken( |
881 | 987 | self, is_unit_paused_set, status_set, | ||
882 | 988 | incomplete_relation_data, hook_name, log): | ||
883 | 979 | configs = MagicMock() | 989 | configs = MagicMock() |
884 | 980 | configs.complete_contexts.return_value = ['shared-db', 'amqp'] | 990 | configs.complete_contexts.return_value = ['shared-db', 'amqp'] |
885 | 981 | required_interfaces = { | 991 | required_interfaces = { |
886 | @@ -1000,8 +1010,11 @@ | |||
887 | 1000 | {'shared-db': {'related': False}} | 1010 | {'shared-db': {'related': False}} |
888 | 1001 | }) | 1011 | }) |
889 | 1002 | @patch('charmhelpers.contrib.openstack.utils.status_set') | 1012 | @patch('charmhelpers.contrib.openstack.utils.status_set') |
892 | 1003 | def test_set_os_workload_status_mixed(self, status_set, incomplete_relation_data, | 1013 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', |
893 | 1004 | log): | 1014 | return_value=False) |
894 | 1015 | def test_set_os_workload_status_mixed( | ||
895 | 1016 | self, is_unit_paused_set, status_set, | ||
896 | 1017 | incomplete_relation_data, log): | ||
897 | 1005 | configs = MagicMock() | 1018 | configs = MagicMock() |
898 | 1006 | configs.complete_contexts.return_value = ['shared-db', 'amqp'] | 1019 | configs.complete_contexts.return_value = ['shared-db', 'amqp'] |
899 | 1007 | required_interfaces = { | 1020 | required_interfaces = { |
900 | @@ -1025,16 +1038,14 @@ | |||
901 | 1025 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | 1038 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') |
902 | 1026 | @patch.object(openstack, 'juju_log') | 1039 | @patch.object(openstack, 'juju_log') |
903 | 1027 | @patch('charmhelpers.contrib.openstack.utils.status_set') | 1040 | @patch('charmhelpers.contrib.openstack.utils.status_set') |
904 | 1041 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', | ||
905 | 1042 | return_value=False) | ||
906 | 1028 | def test_set_os_workload_status_complete_with_services_list( | 1043 | def test_set_os_workload_status_complete_with_services_list( |
908 | 1029 | self, status_set, log, port_has_listener, service_running): | 1044 | self, is_unit_paused_set, status_set, log, |
909 | 1045 | port_has_listener, service_running): | ||
910 | 1030 | configs = MagicMock() | 1046 | configs = MagicMock() |
918 | 1031 | configs.complete_contexts.return_value = ['shared-db', | 1047 | configs.complete_contexts.return_value = [] |
919 | 1032 | 'amqp', | 1048 | required_interfaces = {} |
913 | 1033 | 'identity-service'] | ||
914 | 1034 | required_interfaces = { | ||
915 | 1035 | 'database': ['shared-db', 'pgsql-db'], | ||
916 | 1036 | 'message': ['amqp', 'zeromq-configuration'], | ||
917 | 1037 | 'identity': ['identity-service']} | ||
920 | 1038 | 1049 | ||
921 | 1039 | services = ['database', 'identity'] | 1050 | services = ['database', 'identity'] |
922 | 1040 | # Assume that the service and ports are open. | 1051 | # Assume that the service and ports are open. |
923 | @@ -1049,16 +1060,14 @@ | |||
924 | 1049 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | 1060 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') |
925 | 1050 | @patch.object(openstack, 'juju_log') | 1061 | @patch.object(openstack, 'juju_log') |
926 | 1051 | @patch('charmhelpers.contrib.openstack.utils.status_set') | 1062 | @patch('charmhelpers.contrib.openstack.utils.status_set') |
927 | 1063 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', | ||
928 | 1064 | return_value=False) | ||
929 | 1052 | def test_set_os_workload_status_complete_services_list_not_running( | 1065 | def test_set_os_workload_status_complete_services_list_not_running( |
931 | 1053 | self, status_set, log, port_has_listener, service_running): | 1066 | self, is_unit_paused_set, status_set, log, |
932 | 1067 | port_has_listener, service_running): | ||
933 | 1054 | configs = MagicMock() | 1068 | configs = MagicMock() |
941 | 1055 | configs.complete_contexts.return_value = ['shared-db', | 1069 | configs.complete_contexts.return_value = [] |
942 | 1056 | 'amqp', | 1070 | required_interfaces = {} |
936 | 1057 | 'identity-service'] | ||
937 | 1058 | required_interfaces = { | ||
938 | 1059 | 'database': ['shared-db', 'pgsql-db'], | ||
939 | 1060 | 'message': ['amqp', 'zeromq-configuration'], | ||
940 | 1061 | 'identity': ['identity-service']} | ||
943 | 1062 | 1071 | ||
944 | 1063 | services = ['database', 'identity'] | 1072 | services = ['database', 'identity'] |
945 | 1064 | port_has_listener.return_value = True | 1073 | port_has_listener.return_value = True |
946 | @@ -1075,16 +1084,14 @@ | |||
947 | 1075 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | 1084 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') |
948 | 1076 | @patch.object(openstack, 'juju_log') | 1085 | @patch.object(openstack, 'juju_log') |
949 | 1077 | @patch('charmhelpers.contrib.openstack.utils.status_set') | 1086 | @patch('charmhelpers.contrib.openstack.utils.status_set') |
950 | 1087 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', | ||
951 | 1088 | return_value=False) | ||
952 | 1078 | def test_set_os_workload_status_complete_with_services( | 1089 | def test_set_os_workload_status_complete_with_services( |
954 | 1079 | self, status_set, log, port_has_listener, service_running): | 1090 | self, is_unit_paused_set, status_set, log, |
955 | 1091 | port_has_listener, service_running): | ||
956 | 1080 | configs = MagicMock() | 1092 | configs = MagicMock() |
964 | 1081 | configs.complete_contexts.return_value = ['shared-db', | 1093 | configs.complete_contexts.return_value = [] |
965 | 1082 | 'amqp', | 1094 | required_interfaces = {} |
959 | 1083 | 'identity-service'] | ||
960 | 1084 | required_interfaces = { | ||
961 | 1085 | 'database': ['shared-db', 'pgsql-db'], | ||
962 | 1086 | 'message': ['amqp', 'zeromq-configuration'], | ||
963 | 1087 | 'identity': ['identity-service']} | ||
966 | 1088 | 1095 | ||
967 | 1089 | services = [ | 1096 | services = [ |
968 | 1090 | {'service': 'database', 'ports': [10, 20]}, | 1097 | {'service': 'database', 'ports': [10, 20]}, |
969 | @@ -1102,16 +1109,14 @@ | |||
970 | 1102 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | 1109 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') |
971 | 1103 | @patch.object(openstack, 'juju_log') | 1110 | @patch.object(openstack, 'juju_log') |
972 | 1104 | @patch('charmhelpers.contrib.openstack.utils.status_set') | 1111 | @patch('charmhelpers.contrib.openstack.utils.status_set') |
973 | 1112 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', | ||
974 | 1113 | return_value=False) | ||
975 | 1105 | def test_set_os_workload_status_complete_service_not_running( | 1114 | def test_set_os_workload_status_complete_service_not_running( |
977 | 1106 | self, status_set, log, port_has_listener, service_running): | 1115 | self, is_unit_paused_set, status_set, log, |
978 | 1116 | port_has_listener, service_running): | ||
979 | 1107 | configs = MagicMock() | 1117 | configs = MagicMock() |
987 | 1108 | configs.complete_contexts.return_value = ['shared-db', | 1118 | configs.complete_contexts.return_value = [] |
988 | 1109 | 'amqp', | 1119 | required_interfaces = {} |
982 | 1110 | 'identity-service'] | ||
983 | 1111 | required_interfaces = { | ||
984 | 1112 | 'database': ['shared-db', 'pgsql-db'], | ||
985 | 1113 | 'message': ['amqp', 'zeromq-configuration'], | ||
986 | 1114 | 'identity': ['identity-service']} | ||
989 | 1115 | 1120 | ||
990 | 1116 | services = [ | 1121 | services = [ |
991 | 1117 | {'service': 'database', 'ports': [10, 20]}, | 1122 | {'service': 'database', 'ports': [10, 20]}, |
992 | @@ -1131,16 +1136,14 @@ | |||
993 | 1131 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | 1136 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') |
994 | 1132 | @patch.object(openstack, 'juju_log') | 1137 | @patch.object(openstack, 'juju_log') |
995 | 1133 | @patch('charmhelpers.contrib.openstack.utils.status_set') | 1138 | @patch('charmhelpers.contrib.openstack.utils.status_set') |
996 | 1139 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', | ||
997 | 1140 | return_value=False) | ||
998 | 1134 | def test_set_os_workload_status_complete_port_not_open( | 1141 | def test_set_os_workload_status_complete_port_not_open( |
1000 | 1135 | self, status_set, log, port_has_listener, service_running): | 1142 | self, is_unit_paused_set, status_set, log, |
1001 | 1143 | port_has_listener, service_running): | ||
1002 | 1136 | configs = MagicMock() | 1144 | configs = MagicMock() |
1010 | 1137 | configs.complete_contexts.return_value = ['shared-db', | 1145 | configs.complete_contexts.return_value = [] |
1011 | 1138 | 'amqp', | 1146 | required_interfaces = {} |
1005 | 1139 | 'identity-service'] | ||
1006 | 1140 | required_interfaces = { | ||
1007 | 1141 | 'database': ['shared-db', 'pgsql-db'], | ||
1008 | 1142 | 'message': ['amqp', 'zeromq-configuration'], | ||
1009 | 1143 | 'identity': ['identity-service']} | ||
1012 | 1144 | 1147 | ||
1013 | 1145 | services = [ | 1148 | services = [ |
1014 | 1146 | {'service': 'database', 'ports': [10, 20]}, | 1149 | {'service': 'database', 'ports': [10, 20]}, |
1015 | @@ -1160,16 +1163,13 @@ | |||
1016 | 1160 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | 1163 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') |
1017 | 1161 | @patch.object(openstack, 'juju_log') | 1164 | @patch.object(openstack, 'juju_log') |
1018 | 1162 | @patch('charmhelpers.contrib.openstack.utils.status_set') | 1165 | @patch('charmhelpers.contrib.openstack.utils.status_set') |
1019 | 1166 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', | ||
1020 | 1167 | return_value=False) | ||
1021 | 1163 | def test_set_os_workload_status_complete_ports_not_open( | 1168 | def test_set_os_workload_status_complete_ports_not_open( |
1023 | 1164 | self, status_set, log, port_has_listener): | 1169 | self, is_unit_paused_set, status_set, log, port_has_listener): |
1024 | 1165 | configs = MagicMock() | 1170 | configs = MagicMock() |
1032 | 1166 | configs.complete_contexts.return_value = ['shared-db', | 1171 | configs.complete_contexts.return_value = [] |
1033 | 1167 | 'amqp', | 1172 | required_interfaces = {} |
1027 | 1168 | 'identity-service'] | ||
1028 | 1169 | required_interfaces = { | ||
1029 | 1170 | 'database': ['shared-db', 'pgsql-db'], | ||
1030 | 1171 | 'message': ['amqp', 'zeromq-configuration'], | ||
1031 | 1172 | 'identity': ['identity-service']} | ||
1034 | 1173 | 1173 | ||
1035 | 1174 | ports = [50, 60, 70] | 1174 | ports = [50, 60, 70] |
1036 | 1175 | port_has_listener.side_effect = [True, False, True] | 1175 | port_has_listener.side_effect = [True, False, True] |
1037 | @@ -1181,6 +1181,422 @@ | |||
1038 | 1181 | 'Ports which should be open, but are not: 60') | 1181 | 'Ports which should be open, but are not: 60') |
1039 | 1182 | 1182 | ||
1040 | 1183 | @patch.object(openstack, 'juju_log') | 1183 | @patch.object(openstack, 'juju_log') |
1041 | 1184 | @patch('charmhelpers.contrib.openstack.utils.status_set') | ||
1042 | 1185 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', | ||
1043 | 1186 | return_value=True) | ||
1044 | 1187 | def test_set_os_workload_status_paused_simple( | ||
1045 | 1188 | self, is_unit_paused_set, status_set, log): | ||
1046 | 1189 | configs = MagicMock() | ||
1047 | 1190 | configs.complete_contexts.return_value = [] | ||
1048 | 1191 | required_interfaces = {} | ||
1049 | 1192 | |||
1050 | 1193 | openstack.set_os_workload_status(configs, required_interfaces) | ||
1051 | 1194 | status_set.assert_called_with( | ||
1052 | 1195 | 'maintenance', | ||
1053 | 1196 | "Paused. Use 'resume' action to resume normal service.") | ||
1054 | 1197 | |||
1055 | 1198 | @patch('charmhelpers.contrib.openstack.utils.service_running') | ||
1056 | 1199 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | ||
1057 | 1200 | @patch.object(openstack, 'juju_log') | ||
1058 | 1201 | @patch('charmhelpers.contrib.openstack.utils.status_set') | ||
1059 | 1202 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', | ||
1060 | 1203 | return_value=True) | ||
1061 | 1204 | def test_set_os_workload_status_paused_services_check( | ||
1062 | 1205 | self, is_unit_paused_set, status_set, log, | ||
1063 | 1206 | port_has_listener, service_running): | ||
1064 | 1207 | configs = MagicMock() | ||
1065 | 1208 | configs.complete_contexts.return_value = [] | ||
1066 | 1209 | required_interfaces = {} | ||
1067 | 1210 | |||
1068 | 1211 | services = [ | ||
1069 | 1212 | {'service': 'database', 'ports': [10, 20]}, | ||
1070 | 1213 | {'service': 'identity', 'ports': [30]}, | ||
1071 | 1214 | ] | ||
1072 | 1215 | port_has_listener.return_value = False | ||
1073 | 1216 | service_running.side_effect = [False, False] | ||
1074 | 1217 | |||
1075 | 1218 | openstack.set_os_workload_status( | ||
1076 | 1219 | configs, required_interfaces, services=services) | ||
1077 | 1220 | status_set.assert_called_with( | ||
1078 | 1221 | 'maintenance', | ||
1079 | 1222 | "Paused. Use 'resume' action to resume normal service.") | ||
1080 | 1223 | |||
1081 | 1224 | @patch('charmhelpers.contrib.openstack.utils.service_running') | ||
1082 | 1225 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | ||
1083 | 1226 | @patch.object(openstack, 'juju_log') | ||
1084 | 1227 | @patch('charmhelpers.contrib.openstack.utils.status_set') | ||
1085 | 1228 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', | ||
1086 | 1229 | return_value=True) | ||
1087 | 1230 | def test_set_os_workload_status_paused_services_fail( | ||
1088 | 1231 | self, is_unit_paused_set, status_set, log, | ||
1089 | 1232 | port_has_listener, service_running): | ||
1090 | 1233 | configs = MagicMock() | ||
1091 | 1234 | configs.complete_contexts.return_value = [] | ||
1092 | 1235 | required_interfaces = {} | ||
1093 | 1236 | |||
1094 | 1237 | services = [ | ||
1095 | 1238 | {'service': 'database', 'ports': [10, 20]}, | ||
1096 | 1239 | {'service': 'identity', 'ports': [30]}, | ||
1097 | 1240 | ] | ||
1098 | 1241 | port_has_listener.return_value = False | ||
1099 | 1242 | # Fail the identity service | ||
1100 | 1243 | service_running.side_effect = [False, True] | ||
1101 | 1244 | |||
1102 | 1245 | openstack.set_os_workload_status( | ||
1103 | 1246 | configs, required_interfaces, services=services) | ||
1104 | 1247 | status_set.assert_called_with( | ||
1105 | 1248 | 'blocked', | ||
1106 | 1249 | "Services should be paused but these services running: identity") | ||
1107 | 1250 | |||
1108 | 1251 | @patch('charmhelpers.contrib.openstack.utils.service_running') | ||
1109 | 1252 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | ||
1110 | 1253 | @patch.object(openstack, 'juju_log') | ||
1111 | 1254 | @patch('charmhelpers.contrib.openstack.utils.status_set') | ||
1112 | 1255 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', | ||
1113 | 1256 | return_value=True) | ||
1114 | 1257 | def test_set_os_workload_status_paused_services_ports_fail( | ||
1115 | 1258 | self, is_unit_paused_set, status_set, log, | ||
1116 | 1259 | port_has_listener, service_running): | ||
1117 | 1260 | configs = MagicMock() | ||
1118 | 1261 | configs.complete_contexts.return_value = [] | ||
1119 | 1262 | required_interfaces = {} | ||
1120 | 1263 | |||
1121 | 1264 | services = [ | ||
1122 | 1265 | {'service': 'database', 'ports': [10, 20]}, | ||
1123 | 1266 | {'service': 'identity', 'ports': [30]}, | ||
1124 | 1267 | ] | ||
1125 | 1268 | # make the service 20 port be still listening. | ||
1126 | 1269 | port_has_listener.side_effect = [False, True, False] | ||
1127 | 1270 | service_running.return_value = False | ||
1128 | 1271 | |||
1129 | 1272 | openstack.set_os_workload_status( | ||
1130 | 1273 | configs, required_interfaces, services=services) | ||
1131 | 1274 | status_set.assert_called_with( | ||
1132 | 1275 | 'blocked', | ||
1133 | 1276 | "Services should be paused but these service:ports are open:" | ||
1134 | 1277 | " database: [20]") | ||
1135 | 1278 | |||
1136 | 1279 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | ||
1137 | 1280 | @patch.object(openstack, 'juju_log') | ||
1138 | 1281 | @patch('charmhelpers.contrib.openstack.utils.status_set') | ||
1139 | 1282 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', | ||
1140 | 1283 | return_value=True) | ||
1141 | 1284 | def test_set_os_workload_status_paused_ports_check( | ||
1142 | 1285 | self, is_unit_paused_set, status_set, log, | ||
1143 | 1286 | port_has_listener): | ||
1144 | 1287 | configs = MagicMock() | ||
1145 | 1288 | configs.complete_contexts.return_value = [] | ||
1146 | 1289 | required_interfaces = {} | ||
1147 | 1290 | |||
1148 | 1291 | ports = [50, 60, 70] | ||
1149 | 1292 | port_has_listener.side_effect = [False, False, False] | ||
1150 | 1293 | |||
1151 | 1294 | openstack.set_os_workload_status( | ||
1152 | 1295 | configs, required_interfaces, ports=ports) | ||
1153 | 1296 | status_set.assert_called_with( | ||
1154 | 1297 | 'maintenance', | ||
1155 | 1298 | "Paused. Use 'resume' action to resume normal service.") | ||
1156 | 1299 | |||
1157 | 1300 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | ||
1158 | 1301 | @patch.object(openstack, 'juju_log') | ||
1159 | 1302 | @patch('charmhelpers.contrib.openstack.utils.status_set') | ||
1160 | 1303 | @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', | ||
1161 | 1304 | return_value=True) | ||
1162 | 1305 | def test_set_os_workload_status_paused_ports_fail( | ||
1163 | 1306 | self, is_unit_paused_set, status_set, log, | ||
1164 | 1307 | port_has_listener): | ||
1165 | 1308 | configs = MagicMock() | ||
1166 | 1309 | configs.complete_contexts.return_value = [] | ||
1167 | 1310 | required_interfaces = {} | ||
1168 | 1311 | |||
1169 | 1312 | # fail port 70 to make it seem to be running | ||
1170 | 1313 | ports = [50, 60, 70] | ||
1171 | 1314 | port_has_listener.side_effect = [False, False, True] | ||
1172 | 1315 | |||
1173 | 1316 | openstack.set_os_workload_status( | ||
1174 | 1317 | configs, required_interfaces, ports=ports) | ||
1175 | 1318 | status_set.assert_called_with( | ||
1176 | 1319 | 'blocked', | ||
1177 | 1320 | "Services should be paused but " | ||
1178 | 1321 | "these ports which should be closed, but are open: 70") | ||
1179 | 1322 | |||
1180 | 1323 | @patch('charmhelpers.contrib.openstack.utils.service_running') | ||
1181 | 1324 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | ||
1182 | 1325 | def test_check_actually_paused_simple_services( | ||
1183 | 1326 | self, port_has_listener, service_running): | ||
1184 | 1327 | services = ['database', 'identity'] | ||
1185 | 1328 | port_has_listener.return_value = False | ||
1186 | 1329 | service_running.return_value = False | ||
1187 | 1330 | |||
1188 | 1331 | state, message = openstack.check_actually_paused( | ||
1189 | 1332 | services) | ||
1190 | 1333 | self.assertEquals(state, None) | ||
1191 | 1334 | self.assertEquals(message, None) | ||
1192 | 1335 | |||
1193 | 1336 | @patch('charmhelpers.contrib.openstack.utils.service_running') | ||
1194 | 1337 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | ||
1195 | 1338 | def test_check_actually_paused_simple_services_fail( | ||
1196 | 1339 | self, port_has_listener, service_running): | ||
1197 | 1340 | services = ['database', 'identity'] | ||
1198 | 1341 | port_has_listener.return_value = False | ||
1199 | 1342 | service_running.side_effect = [False, True] | ||
1200 | 1343 | |||
1201 | 1344 | state, message = openstack.check_actually_paused( | ||
1202 | 1345 | services) | ||
1203 | 1346 | self.assertEquals(state, 'blocked') | ||
1204 | 1347 | self.assertEquals( | ||
1205 | 1348 | message, | ||
1206 | 1349 | "Services should be paused but these services running: identity") | ||
1207 | 1350 | |||
1208 | 1351 | @patch('charmhelpers.contrib.openstack.utils.service_running') | ||
1209 | 1352 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | ||
1210 | 1353 | def test_check_actually_paused_services_dict( | ||
1211 | 1354 | self, port_has_listener, service_running): | ||
1212 | 1355 | services = [ | ||
1213 | 1356 | {'service': 'database', 'ports': [10, 20]}, | ||
1214 | 1357 | {'service': 'identity', 'ports': [30]}, | ||
1215 | 1358 | ] | ||
1216 | 1359 | # Assume that the service and ports are open. | ||
1217 | 1360 | port_has_listener.return_value = False | ||
1218 | 1361 | service_running.return_value = False | ||
1219 | 1362 | |||
1220 | 1363 | state, message = openstack.check_actually_paused( | ||
1221 | 1364 | services) | ||
1222 | 1365 | self.assertEquals(state, None) | ||
1223 | 1366 | self.assertEquals(message, None) | ||
1224 | 1367 | |||
1225 | 1368 | @patch('charmhelpers.contrib.openstack.utils.service_running') | ||
1226 | 1369 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | ||
1227 | 1370 | def test_check_actually_paused_services_dict_fail( | ||
1228 | 1371 | self, port_has_listener, service_running): | ||
1229 | 1372 | services = [ | ||
1230 | 1373 | {'service': 'database', 'ports': [10, 20]}, | ||
1231 | 1374 | {'service': 'identity', 'ports': [30]}, | ||
1232 | 1375 | ] | ||
1233 | 1376 | # Assume that the service and ports are open. | ||
1234 | 1377 | port_has_listener.return_value = False | ||
1235 | 1378 | service_running.side_effect = [False, True] | ||
1236 | 1379 | |||
1237 | 1380 | state, message = openstack.check_actually_paused( | ||
1238 | 1381 | services) | ||
1239 | 1382 | self.assertEquals(state, 'blocked') | ||
1240 | 1383 | self.assertEquals( | ||
1241 | 1384 | message, | ||
1242 | 1385 | "Services should be paused but these services running: identity") | ||
1243 | 1386 | |||
1244 | 1387 | @patch('charmhelpers.contrib.openstack.utils.service_running') | ||
1245 | 1388 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | ||
1246 | 1389 | def test_check_actually_paused_services_dict_ports_fail( | ||
1247 | 1390 | self, port_has_listener, service_running): | ||
1248 | 1391 | services = [ | ||
1249 | 1392 | {'service': 'database', 'ports': [10, 20]}, | ||
1250 | 1393 | {'service': 'identity', 'ports': [30]}, | ||
1251 | 1394 | ] | ||
1252 | 1395 | # Assume that the service and ports are open. | ||
1253 | 1396 | port_has_listener.side_effect = [False, True, False] | ||
1254 | 1397 | service_running.return_value = False | ||
1255 | 1398 | |||
1256 | 1399 | state, message = openstack.check_actually_paused( | ||
1257 | 1400 | services) | ||
1258 | 1401 | self.assertEquals(state, 'blocked') | ||
1259 | 1402 | self.assertEquals(message, | ||
1260 | 1403 | 'Services should be paused but these service:ports' | ||
1261 | 1404 | ' are open: database: [20]') | ||
1262 | 1405 | |||
1263 | 1406 | @patch('charmhelpers.contrib.openstack.utils.service_running') | ||
1264 | 1407 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | ||
1265 | 1408 | def test_check_actually_paused_ports_okay( | ||
1266 | 1409 | self, port_has_listener, service_running): | ||
1267 | 1410 | port_has_listener.side_effect = [False, False, False] | ||
1268 | 1411 | service_running.return_value = False | ||
1269 | 1412 | ports = [50, 60, 70] | ||
1270 | 1413 | |||
1271 | 1414 | state, message = openstack.check_actually_paused( | ||
1272 | 1415 | ports=ports) | ||
1273 | 1416 | self.assertEquals(state, None) | ||
1274 | 1417 | self.assertEquals(state, None) | ||
1275 | 1418 | |||
1276 | 1419 | @patch('charmhelpers.contrib.openstack.utils.service_running') | ||
1277 | 1420 | @patch('charmhelpers.contrib.openstack.utils.port_has_listener') | ||
1278 | 1421 | def test_check_actually_paused_ports_fail( | ||
1279 | 1422 | self, port_has_listener, service_running): | ||
1280 | 1423 | port_has_listener.side_effect = [False, True, False] | ||
1281 | 1424 | service_running.return_value = False | ||
1282 | 1425 | ports = [50, 60, 70] | ||
1283 | 1426 | |||
1284 | 1427 | state, message = openstack.check_actually_paused( | ||
1285 | 1428 | ports=ports) | ||
1286 | 1429 | self.assertEquals(state, 'blocked') | ||
1287 | 1430 | self.assertEquals(message, | ||
1288 | 1431 | 'Services should be paused but these ports ' | ||
1289 | 1432 | 'which should be closed, but are open: 60') | ||
1290 | 1433 | |||
1291 | 1434 | @patch('charmhelpers.contrib.openstack.utils.service_pause') | ||
1292 | 1435 | @patch('charmhelpers.contrib.openstack.utils.set_unit_paused') | ||
1293 | 1436 | def test_pause_unit_okay(self, set_unit_paused, service_pause): | ||
1294 | 1437 | services = ['service1', 'service2'] | ||
1295 | 1438 | service_pause.side_effect = [True, True] | ||
1296 | 1439 | openstack.pause_unit(None, services=services) | ||
1297 | 1440 | set_unit_paused.assert_called_once_with() | ||
1298 | 1441 | self.assertEquals(service_pause.call_count, 2) | ||
1299 | 1442 | |||
1300 | 1443 | @patch('charmhelpers.contrib.openstack.utils.service_pause') | ||
1301 | 1444 | @patch('charmhelpers.contrib.openstack.utils.set_unit_paused') | ||
1302 | 1445 | def test_pause_unit_service_fails(self, set_unit_paused, service_pause): | ||
1303 | 1446 | services = ['service1', 'service2'] | ||
1304 | 1447 | service_pause.side_effect = [True, True] | ||
1305 | 1448 | openstack.pause_unit(None, services=services) | ||
1306 | 1449 | set_unit_paused.assert_called_once_with() | ||
1307 | 1450 | self.assertEquals(service_pause.call_count, 2) | ||
1308 | 1451 | # Fail the 2nd service | ||
1309 | 1452 | service_pause.side_effect = [True, False] | ||
1310 | 1453 | try: | ||
1311 | 1454 | openstack.pause_unit(None, services=services) | ||
1312 | 1455 | raise Exception("pause_unit should have raised Exception") | ||
1313 | 1456 | except Exception as e: | ||
1314 | 1457 | self.assertEquals(e.args[0], | ||
1315 | 1458 | "Couldn't pause: service2 didn't stop cleanly.") | ||
1316 | 1459 | |||
1317 | 1460 | @patch('charmhelpers.contrib.openstack.utils.service_pause') | ||
1318 | 1461 | @patch('charmhelpers.contrib.openstack.utils.set_unit_paused') | ||
1319 | 1462 | def test_pausee_unit_service_charm_func( | ||
1320 | 1463 | self, set_unit_paused, service_pause): | ||
1321 | 1464 | services = ['service1', 'service2'] | ||
1322 | 1465 | service_pause.return_value = True | ||
1323 | 1466 | charm_func = MagicMock() | ||
1324 | 1467 | charm_func.return_value = None | ||
1325 | 1468 | openstack.pause_unit(None, services=services, charm_func=charm_func) | ||
1326 | 1469 | charm_func.assert_called_once_with() | ||
1327 | 1470 | # fail the charm_func | ||
1328 | 1471 | charm_func.return_value = "Custom charm failed" | ||
1329 | 1472 | try: | ||
1330 | 1473 | openstack.pause_unit( | ||
1331 | 1474 | None, services=services, charm_func=charm_func) | ||
1332 | 1475 | raise Exception("pause_unit should have raised Exception") | ||
1333 | 1476 | except Exception as e: | ||
1334 | 1477 | self.assertEquals(e.args[0], | ||
1335 | 1478 | "Couldn't pause: Custom charm failed") | ||
1336 | 1479 | |||
1337 | 1480 | @patch('charmhelpers.contrib.openstack.utils.service_pause') | ||
1338 | 1481 | @patch('charmhelpers.contrib.openstack.utils.set_unit_paused') | ||
1339 | 1482 | def test_pause_unit_assess_status_func( | ||
1340 | 1483 | self, set_unit_paused, service_pause): | ||
1341 | 1484 | services = ['service1', 'service2'] | ||
1342 | 1485 | service_pause.return_value = True | ||
1343 | 1486 | assess_status_func = MagicMock() | ||
1344 | 1487 | assess_status_func.return_value = None | ||
1345 | 1488 | openstack.pause_unit(assess_status_func, services=services) | ||
1346 | 1489 | assess_status_func.assert_called_once_with() | ||
1347 | 1490 | # fail the assess_status_func | ||
1348 | 1491 | assess_status_func.return_value = "assess_status_func failed" | ||
1349 | 1492 | try: | ||
1350 | 1493 | openstack.pause_unit(assess_status_func, services=services) | ||
1351 | 1494 | raise Exception("pause_unit should have raised Exception") | ||
1352 | 1495 | except Exception as e: | ||
1353 | 1496 | self.assertEquals(e.args[0], | ||
1354 | 1497 | "Couldn't pause: assess_status_func failed") | ||
1355 | 1498 | |||
1356 | 1499 | @patch('charmhelpers.contrib.openstack.utils.service_resume') | ||
1357 | 1500 | @patch('charmhelpers.contrib.openstack.utils.clear_unit_paused') | ||
1358 | 1501 | def test_resume_unit_okay(self, clear_unit_paused, service_resume): | ||
1359 | 1502 | services = ['service1', 'service2'] | ||
1360 | 1503 | service_resume.side_effect = [True, True] | ||
1361 | 1504 | openstack.resume_unit(None, services=services) | ||
1362 | 1505 | clear_unit_paused.assert_called_once_with() | ||
1363 | 1506 | self.assertEquals(service_resume.call_count, 2) | ||
1364 | 1507 | |||
1365 | 1508 | @patch('charmhelpers.contrib.openstack.utils.service_resume') | ||
1366 | 1509 | @patch('charmhelpers.contrib.openstack.utils.clear_unit_paused') | ||
1367 | 1510 | def test_resume_unit_service_fails(self, clear_unit_paused, service_resume): | ||
1368 | 1511 | services = ['service1', 'service2'] | ||
1369 | 1512 | service_resume.side_effect = [True, True] | ||
1370 | 1513 | openstack.resume_unit(None, services=services) | ||
1371 | 1514 | clear_unit_paused.assert_called_once_with() | ||
1372 | 1515 | self.assertEquals(service_resume.call_count, 2) | ||
1373 | 1516 | # Fail the 2nd service | ||
1374 | 1517 | service_resume.side_effect = [True, False] | ||
1375 | 1518 | try: | ||
1376 | 1519 | openstack.resume_unit(None, services=services) | ||
1377 | 1520 | raise Exception("resume_unit should have raised Exception") | ||
1378 | 1521 | except Exception as e: | ||
1379 | 1522 | self.assertEquals(e.args[0], | ||
1380 | 1523 | "Couldn't resume: service2 didn't start cleanly.") | ||
1381 | 1524 | |||
1382 | 1525 | @patch('charmhelpers.contrib.openstack.utils.service_resume') | ||
1383 | 1526 | @patch('charmhelpers.contrib.openstack.utils.clear_unit_paused') | ||
1384 | 1527 | def test_resume_unit_service_charm_func( | ||
1385 | 1528 | self, clear_unit_paused, service_resume): | ||
1386 | 1529 | services = ['service1', 'service2'] | ||
1387 | 1530 | service_resume.return_value = True | ||
1388 | 1531 | charm_func = MagicMock() | ||
1389 | 1532 | charm_func.return_value = None | ||
1390 | 1533 | openstack.resume_unit(None, services=services, charm_func=charm_func) | ||
1391 | 1534 | charm_func.assert_called_once_with() | ||
1392 | 1535 | # fail the charm_func | ||
1393 | 1536 | charm_func.return_value = "Custom charm failed" | ||
1394 | 1537 | try: | ||
1395 | 1538 | openstack.resume_unit( | ||
1396 | 1539 | None, services=services, charm_func=charm_func) | ||
1397 | 1540 | raise Exception("resume_unit should have raised Exception") | ||
1398 | 1541 | except Exception as e: | ||
1399 | 1542 | self.assertEquals(e.args[0], | ||
1400 | 1543 | "Couldn't resume: Custom charm failed") | ||
1401 | 1544 | |||
1402 | 1545 | @patch('charmhelpers.contrib.openstack.utils.service_resume') | ||
1403 | 1546 | @patch('charmhelpers.contrib.openstack.utils.clear_unit_paused') | ||
1404 | 1547 | def test_resume_unit_assess_status_func( | ||
1405 | 1548 | self, clear_unit_paused, service_resume): | ||
1406 | 1549 | services = ['service1', 'service2'] | ||
1407 | 1550 | service_resume.return_value = True | ||
1408 | 1551 | assess_status_func = MagicMock() | ||
1409 | 1552 | assess_status_func.return_value = None | ||
1410 | 1553 | openstack.resume_unit(assess_status_func, services=services) | ||
1411 | 1554 | assess_status_func.assert_called_once_with() | ||
1412 | 1555 | # fail the assess_status_func | ||
1413 | 1556 | assess_status_func.return_value = "assess_status_func failed" | ||
1414 | 1557 | try: | ||
1415 | 1558 | openstack.resume_unit(assess_status_func, services=services) | ||
1416 | 1559 | raise Exception("resume_unit should have raised Exception") | ||
1417 | 1560 | except Exception as e: | ||
1418 | 1561 | self.assertEquals(e.args[0], | ||
1419 | 1562 | "Couldn't resume: assess_status_func failed") | ||
1420 | 1563 | |||
1421 | 1564 | @patch('charmhelpers.contrib.openstack.utils.status_set') | ||
1422 | 1565 | @patch('charmhelpers.contrib.openstack.utils.' | ||
1423 | 1566 | '_determine_os_workload_status') | ||
1424 | 1567 | def test_make_assess_status_func(self, _determine_os_workload_status, | ||
1425 | 1568 | status_set): | ||
1426 | 1569 | _determine_os_workload_status.return_value = ('active', 'fine') | ||
1427 | 1570 | f = openstack.make_assess_status_func('one', 'two', three='three') | ||
1428 | 1571 | r = f() | ||
1429 | 1572 | self.assertEquals(r, None) | ||
1430 | 1573 | _determine_os_workload_status.assert_called_once_with( | ||
1431 | 1574 | 'one', 'two', three='three') | ||
1432 | 1575 | status_set.assert_called_once_with('active', 'fine') | ||
1433 | 1576 | # return something other than 'active' or 'maintenance' | ||
1434 | 1577 | _determine_os_workload_status.return_value = ('broken', 'damaged') | ||
1435 | 1578 | r = f() | ||
1436 | 1579 | self.assertEquals(r, 'damaged') | ||
1437 | 1580 | |||
1438 | 1581 | @patch.object(openstack, 'restart_on_change_helper') | ||
1439 | 1582 | @patch.object(openstack, 'is_unit_paused_set') | ||
1440 | 1583 | def test_pausable_restart_on_change( | ||
1441 | 1584 | self, is_unit_paused_set, restart_on_change_helper): | ||
1442 | 1585 | @openstack.pausable_restart_on_change({}) | ||
1443 | 1586 | def test_func(): | ||
1444 | 1587 | pass | ||
1445 | 1588 | |||
1446 | 1589 | # test with pause: restart_on_change_helper should not be called. | ||
1447 | 1590 | is_unit_paused_set.return_value = True | ||
1448 | 1591 | test_func() | ||
1449 | 1592 | self.assertEquals(restart_on_change_helper.call_count, 0) | ||
1450 | 1593 | |||
1451 | 1594 | # test without pause: restart_on_change_helper should be called. | ||
1452 | 1595 | is_unit_paused_set.return_value = False | ||
1453 | 1596 | test_func() | ||
1454 | 1597 | self.assertEquals(restart_on_change_helper.call_count, 1) | ||
1455 | 1598 | |||
1456 | 1599 | @patch.object(openstack, 'juju_log') | ||
1457 | 1184 | @patch.object(openstack, 'action_set') | 1600 | @patch.object(openstack, 'action_set') |
1458 | 1185 | @patch.object(openstack, 'action_fail') | 1601 | @patch.object(openstack, 'action_fail') |
1459 | 1186 | @patch.object(openstack, 'openstack_upgrade_available') | 1602 | @patch.object(openstack, 'openstack_upgrade_available') |
It looks good to me, I like the rejig. A minor nitpick but could you expand some of the list comprehensions, I find them hard to parse tbh. I've pointed them out in line.