Merge lp:~billy-olsen/charms/trusty/ceph-radosgw/public-endpoint-host into lp:~openstack-charmers-archive/charms/trusty/ceph-radosgw/next
- Trusty Tahr (14.04)
- public-endpoint-host
- Merge into next
Proposed by
Billy Olsen
Status: | Merged |
---|---|
Merged at revision: | 39 |
Proposed branch: | lp:~billy-olsen/charms/trusty/ceph-radosgw/public-endpoint-host |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/ceph-radosgw/next |
Diff against target: |
1126 lines (+460/-135) 16 files modified
config.yaml (+12/-0) hooks/charmhelpers/contrib/hahelpers/cluster.py (+25/-0) hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+15/-6) hooks/charmhelpers/contrib/openstack/ip.py (+49/-44) hooks/charmhelpers/contrib/openstack/neutron.py (+10/-5) hooks/charmhelpers/contrib/openstack/utils.py (+65/-18) hooks/charmhelpers/contrib/python/packages.py (+28/-5) hooks/charmhelpers/core/hookenv.py (+147/-10) hooks/charmhelpers/core/host.py (+1/-1) hooks/charmhelpers/core/services/base.py (+32/-11) hooks/charmhelpers/fetch/__init__.py (+1/-1) hooks/charmhelpers/fetch/giturl.py (+7/-5) hooks/hooks.py (+4/-15) tests/charmhelpers/contrib/amulet/utils.py (+8/-1) tests/charmhelpers/contrib/openstack/amulet/deployment.py (+15/-6) unit_tests/test_hooks.py (+41/-7) |
To merge this branch: | bzr merge lp:~billy-olsen/charms/trusty/ceph-radosgw/public-endpoint-host |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Corey Bryant (community) | Approve | ||
Review via email: mp+261011@code.launchpad.net |
Commit message
Description of the change
Provides a config option which allows the user to specify the public hostname used to advertise to keystone when creating endpoints.
To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #4729 ceph-radosgw-next for billy-olsen mp261011
UNIT OK: passed
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #4455 ceph-radosgw-next for billy-olsen mp261011
AMULET OK: passed
Build: http://
- 42. By Billy Olsen
-
c-h sync. unit test updates for sync
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_lint_check #5073 ceph-radosgw-next for billy-olsen mp261011
LINT OK: passed
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #4752 ceph-radosgw-next for billy-olsen mp261011
UNIT OK: passed
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #4480 ceph-radosgw-next for billy-olsen mp261011
AMULET OK: passed
Build: http://
Revision history for this message
Corey Bryant (corey.bryant) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'config.yaml' |
2 | --- config.yaml 2015-01-14 13:27:02 +0000 |
3 | +++ config.yaml 2015-06-04 23:07:06 +0000 |
4 | @@ -87,3 +87,15 @@ |
5 | description: | |
6 | Default multicast port number that will be used to communicate between |
7 | HA Cluster nodes. |
8 | + os-public-hostname: |
9 | + type: string |
10 | + default: |
11 | + description: | |
12 | + The hostname or address of the public endpoints created for ceph-radosgw |
13 | + in the keystone identity provider. |
14 | + . |
15 | + This value will be used for public endpoints. For example, an |
16 | + os-public-hostname set to 'files.example.com' with will create |
17 | + the following public endpoint for the ceph-radosgw. |
18 | + . |
19 | + https://files.example.com:80/swift/v1 |
20 | \ No newline at end of file |
21 | |
22 | === modified file 'hooks/charmhelpers/contrib/hahelpers/cluster.py' |
23 | --- hooks/charmhelpers/contrib/hahelpers/cluster.py 2015-02-24 11:02:02 +0000 |
24 | +++ hooks/charmhelpers/contrib/hahelpers/cluster.py 2015-06-04 23:07:06 +0000 |
25 | @@ -52,6 +52,8 @@ |
26 | bool_from_string, |
27 | ) |
28 | |
29 | +DC_RESOURCE_NAME = 'DC' |
30 | + |
31 | |
32 | class HAIncompleteConfig(Exception): |
33 | pass |
34 | @@ -95,6 +97,27 @@ |
35 | return False |
36 | |
37 | |
38 | +def is_crm_dc(): |
39 | + """ |
40 | + Determine leadership by querying the pacemaker Designated Controller |
41 | + """ |
42 | + cmd = ['crm', 'status'] |
43 | + try: |
44 | + status = subprocess.check_output(cmd, stderr=subprocess.STDOUT) |
45 | + if not isinstance(status, six.text_type): |
46 | + status = six.text_type(status, "utf-8") |
47 | + except subprocess.CalledProcessError: |
48 | + return False |
49 | + current_dc = '' |
50 | + for line in status.split('\n'): |
51 | + if line.startswith('Current DC'): |
52 | + # Current DC: juju-lytrusty-machine-2 (168108163) - partition with quorum |
53 | + current_dc = line.split(':')[1].split()[0] |
54 | + if current_dc == get_unit_hostname(): |
55 | + return True |
56 | + return False |
57 | + |
58 | + |
59 | @retry_on_exception(5, base_delay=2, exc_type=CRMResourceNotFound) |
60 | def is_crm_leader(resource, retry=False): |
61 | """ |
62 | @@ -104,6 +127,8 @@ |
63 | We allow this operation to be retried to avoid the possibility of getting a |
64 | false negative. See LP #1396246 for more info. |
65 | """ |
66 | + if resource == DC_RESOURCE_NAME: |
67 | + return is_crm_dc() |
68 | cmd = ['crm', 'resource', 'show', resource] |
69 | try: |
70 | status = subprocess.check_output(cmd, stderr=subprocess.STDOUT) |
71 | |
72 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py' |
73 | --- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-04-16 21:32:59 +0000 |
74 | +++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-06-04 23:07:06 +0000 |
75 | @@ -46,15 +46,22 @@ |
76 | stable or next branches for the other_services.""" |
77 | base_charms = ['mysql', 'mongodb'] |
78 | |
79 | + if self.series in ['precise', 'trusty']: |
80 | + base_series = self.series |
81 | + else: |
82 | + base_series = self.current_next |
83 | + |
84 | if self.stable: |
85 | for svc in other_services: |
86 | - temp = 'lp:charms/{}' |
87 | - svc['location'] = temp.format(svc['name']) |
88 | + temp = 'lp:charms/{}/{}' |
89 | + svc['location'] = temp.format(base_series, |
90 | + svc['name']) |
91 | else: |
92 | for svc in other_services: |
93 | if svc['name'] in base_charms: |
94 | - temp = 'lp:charms/{}' |
95 | - svc['location'] = temp.format(svc['name']) |
96 | + temp = 'lp:charms/{}/{}' |
97 | + svc['location'] = temp.format(base_series, |
98 | + svc['name']) |
99 | else: |
100 | temp = 'lp:~openstack-charmers/charms/{}/{}/next' |
101 | svc['location'] = temp.format(self.current_next, |
102 | @@ -99,10 +106,12 @@ |
103 | Return an integer representing the enum value of the openstack |
104 | release. |
105 | """ |
106 | + # Must be ordered by OpenStack release (not by Ubuntu release): |
107 | (self.precise_essex, self.precise_folsom, self.precise_grizzly, |
108 | self.precise_havana, self.precise_icehouse, |
109 | - self.trusty_icehouse, self.trusty_juno, self.trusty_kilo, |
110 | - self.utopic_juno, self.vivid_kilo) = range(10) |
111 | + self.trusty_icehouse, self.trusty_juno, self.utopic_juno, |
112 | + self.trusty_kilo, self.vivid_kilo) = range(10) |
113 | + |
114 | releases = { |
115 | ('precise', None): self.precise_essex, |
116 | ('precise', 'cloud:precise-folsom'): self.precise_folsom, |
117 | |
118 | === modified file 'hooks/charmhelpers/contrib/openstack/ip.py' |
119 | --- hooks/charmhelpers/contrib/openstack/ip.py 2015-02-24 11:02:02 +0000 |
120 | +++ hooks/charmhelpers/contrib/openstack/ip.py 2015-06-04 23:07:06 +0000 |
121 | @@ -17,6 +17,7 @@ |
122 | from charmhelpers.core.hookenv import ( |
123 | config, |
124 | unit_get, |
125 | + service_name, |
126 | ) |
127 | from charmhelpers.contrib.network.ip import ( |
128 | get_address_in_network, |
129 | @@ -26,8 +27,6 @@ |
130 | ) |
131 | from charmhelpers.contrib.hahelpers.cluster import is_clustered |
132 | |
133 | -from functools import partial |
134 | - |
135 | PUBLIC = 'public' |
136 | INTERNAL = 'int' |
137 | ADMIN = 'admin' |
138 | @@ -35,15 +34,18 @@ |
139 | ADDRESS_MAP = { |
140 | PUBLIC: { |
141 | 'config': 'os-public-network', |
142 | - 'fallback': 'public-address' |
143 | + 'fallback': 'public-address', |
144 | + 'override': 'os-public-hostname', |
145 | }, |
146 | INTERNAL: { |
147 | 'config': 'os-internal-network', |
148 | - 'fallback': 'private-address' |
149 | + 'fallback': 'private-address', |
150 | + 'override': 'os-internal-hostname', |
151 | }, |
152 | ADMIN: { |
153 | 'config': 'os-admin-network', |
154 | - 'fallback': 'private-address' |
155 | + 'fallback': 'private-address', |
156 | + 'override': 'os-admin-hostname', |
157 | } |
158 | } |
159 | |
160 | @@ -57,15 +59,50 @@ |
161 | :param endpoint_type: str endpoint type to resolve. |
162 | :param returns: str base URL for services on the current service unit. |
163 | """ |
164 | - scheme = 'http' |
165 | - if 'https' in configs.complete_contexts(): |
166 | - scheme = 'https' |
167 | + scheme = _get_scheme(configs) |
168 | + |
169 | address = resolve_address(endpoint_type) |
170 | if is_ipv6(address): |
171 | address = "[{}]".format(address) |
172 | + |
173 | return '%s://%s' % (scheme, address) |
174 | |
175 | |
176 | +def _get_scheme(configs): |
177 | + """Returns the scheme to use for the url (either http or https) |
178 | + depending upon whether https is in the configs value. |
179 | + |
180 | + :param configs: OSTemplateRenderer config templating object to inspect |
181 | + for a complete https context. |
182 | + :returns: either 'http' or 'https' depending on whether https is |
183 | + configured within the configs context. |
184 | + """ |
185 | + scheme = 'http' |
186 | + if configs and 'https' in configs.complete_contexts(): |
187 | + scheme = 'https' |
188 | + return scheme |
189 | + |
190 | + |
191 | +def _get_address_override(endpoint_type=PUBLIC): |
192 | + """Returns any address overrides that the user has defined based on the |
193 | + endpoint type. |
194 | + |
195 | + Note: this function allows for the service name to be inserted into the |
196 | + address if the user specifies {service_name}.somehost.org. |
197 | + |
198 | + :param endpoint_type: the type of endpoint to retrieve the override |
199 | + value for. |
200 | + :returns: any endpoint address or hostname that the user has overridden |
201 | + or None if an override is not present. |
202 | + """ |
203 | + override_key = ADDRESS_MAP[endpoint_type]['override'] |
204 | + addr_override = config(override_key) |
205 | + if not addr_override: |
206 | + return None |
207 | + else: |
208 | + return addr_override.format(service_name=service_name()) |
209 | + |
210 | + |
211 | def resolve_address(endpoint_type=PUBLIC): |
212 | """Return unit address depending on net config. |
213 | |
214 | @@ -77,7 +114,10 @@ |
215 | |
216 | :param endpoint_type: Network endpoing type |
217 | """ |
218 | - resolved_address = None |
219 | + resolved_address = _get_address_override(endpoint_type) |
220 | + if resolved_address: |
221 | + return resolved_address |
222 | + |
223 | vips = config('vip') |
224 | if vips: |
225 | vips = vips.split() |
226 | @@ -109,38 +149,3 @@ |
227 | "clustered=%s)" % (net_type, clustered)) |
228 | |
229 | return resolved_address |
230 | - |
231 | - |
232 | -def endpoint_url(configs, url_template, port, endpoint_type=PUBLIC, |
233 | - override=None): |
234 | - """Returns the correct endpoint URL to advertise to Keystone. |
235 | - |
236 | - This method provides the correct endpoint URL which should be advertised to |
237 | - the keystone charm for endpoint creation. This method allows for the url to |
238 | - be overridden to force a keystone endpoint to have specific URL for any of |
239 | - the defined scopes (admin, internal, public). |
240 | - |
241 | - :param configs: OSTemplateRenderer config templating object to inspect |
242 | - for a complete https context. |
243 | - :param url_template: str format string for creating the url template. Only |
244 | - two values will be passed - the scheme+hostname |
245 | - returned by the canonical_url and the port. |
246 | - :param endpoint_type: str endpoint type to resolve. |
247 | - :param override: str the name of the config option which overrides the |
248 | - endpoint URL defined by the charm itself. None will |
249 | - disable any overrides (default). |
250 | - """ |
251 | - if override: |
252 | - # Return any user-defined overrides for the keystone endpoint URL. |
253 | - user_value = config(override) |
254 | - if user_value: |
255 | - return user_value.strip() |
256 | - |
257 | - return url_template % (canonical_url(configs, endpoint_type), port) |
258 | - |
259 | - |
260 | -public_endpoint = partial(endpoint_url, endpoint_type=PUBLIC) |
261 | - |
262 | -internal_endpoint = partial(endpoint_url, endpoint_type=INTERNAL) |
263 | - |
264 | -admin_endpoint = partial(endpoint_url, endpoint_type=ADMIN) |
265 | |
266 | === modified file 'hooks/charmhelpers/contrib/openstack/neutron.py' |
267 | --- hooks/charmhelpers/contrib/openstack/neutron.py 2015-04-16 21:32:59 +0000 |
268 | +++ hooks/charmhelpers/contrib/openstack/neutron.py 2015-06-04 23:07:06 +0000 |
269 | @@ -256,11 +256,14 @@ |
270 | def parse_mappings(mappings): |
271 | parsed = {} |
272 | if mappings: |
273 | - mappings = mappings.split(' ') |
274 | + mappings = mappings.split() |
275 | for m in mappings: |
276 | p = m.partition(':') |
277 | - if p[1] == ':': |
278 | - parsed[p[0].strip()] = p[2].strip() |
279 | + key = p[0].strip() |
280 | + if p[1]: |
281 | + parsed[key] = p[2].strip() |
282 | + else: |
283 | + parsed[key] = '' |
284 | |
285 | return parsed |
286 | |
287 | @@ -283,13 +286,13 @@ |
288 | Returns dict of the form {bridge:port}. |
289 | """ |
290 | _mappings = parse_mappings(mappings) |
291 | - if not _mappings: |
292 | + if not _mappings or list(_mappings.values()) == ['']: |
293 | if not mappings: |
294 | return {} |
295 | |
296 | # For backwards-compatibility we need to support port-only provided in |
297 | # config. |
298 | - _mappings = {default_bridge: mappings.split(' ')[0]} |
299 | + _mappings = {default_bridge: mappings.split()[0]} |
300 | |
301 | bridges = _mappings.keys() |
302 | ports = _mappings.values() |
303 | @@ -309,6 +312,8 @@ |
304 | |
305 | Mappings must be a space-delimited list of provider:start:end mappings. |
306 | |
307 | + The start:end range is optional and may be omitted. |
308 | + |
309 | Returns dict of the form {provider: (start, end)}. |
310 | """ |
311 | _mappings = parse_mappings(mappings) |
312 | |
313 | === modified file 'hooks/charmhelpers/contrib/openstack/utils.py' |
314 | --- hooks/charmhelpers/contrib/openstack/utils.py 2015-04-16 21:32:59 +0000 |
315 | +++ hooks/charmhelpers/contrib/openstack/utils.py 2015-06-04 23:07:06 +0000 |
316 | @@ -53,9 +53,13 @@ |
317 | get_ipv6_addr |
318 | ) |
319 | |
320 | +from charmhelpers.contrib.python.packages import ( |
321 | + pip_create_virtualenv, |
322 | + pip_install, |
323 | +) |
324 | + |
325 | from charmhelpers.core.host import lsb_release, mounts, umount |
326 | from charmhelpers.fetch import apt_install, apt_cache, install_remote |
327 | -from charmhelpers.contrib.python.packages import pip_install |
328 | from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk |
329 | from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device |
330 | |
331 | @@ -497,7 +501,17 @@ |
332 | requirements_dir = None |
333 | |
334 | |
335 | -def git_clone_and_install(projects_yaml, core_project): |
336 | +def _git_yaml_load(projects_yaml): |
337 | + """ |
338 | + Load the specified yaml into a dictionary. |
339 | + """ |
340 | + if not projects_yaml: |
341 | + return None |
342 | + |
343 | + return yaml.load(projects_yaml) |
344 | + |
345 | + |
346 | +def git_clone_and_install(projects_yaml, core_project, depth=1): |
347 | """ |
348 | Clone/install all specified OpenStack repositories. |
349 | |
350 | @@ -510,23 +524,22 @@ |
351 | repository: 'git://git.openstack.org/openstack/requirements.git', |
352 | branch: 'stable/icehouse'} |
353 | directory: /mnt/openstack-git |
354 | - http_proxy: http://squid.internal:3128 |
355 | - https_proxy: https://squid.internal:3128 |
356 | + http_proxy: squid-proxy-url |
357 | + https_proxy: squid-proxy-url |
358 | |
359 | The directory, http_proxy, and https_proxy keys are optional. |
360 | """ |
361 | global requirements_dir |
362 | parent_dir = '/mnt/openstack-git' |
363 | - |
364 | - if not projects_yaml: |
365 | - return |
366 | - |
367 | - projects = yaml.load(projects_yaml) |
368 | + http_proxy = None |
369 | + |
370 | + projects = _git_yaml_load(projects_yaml) |
371 | _git_validate_projects_yaml(projects, core_project) |
372 | |
373 | old_environ = dict(os.environ) |
374 | |
375 | if 'http_proxy' in projects.keys(): |
376 | + http_proxy = projects['http_proxy'] |
377 | os.environ['http_proxy'] = projects['http_proxy'] |
378 | if 'https_proxy' in projects.keys(): |
379 | os.environ['https_proxy'] = projects['https_proxy'] |
380 | @@ -534,15 +547,19 @@ |
381 | if 'directory' in projects.keys(): |
382 | parent_dir = projects['directory'] |
383 | |
384 | + pip_create_virtualenv(os.path.join(parent_dir, 'venv')) |
385 | + |
386 | for p in projects['repositories']: |
387 | repo = p['repository'] |
388 | branch = p['branch'] |
389 | if p['name'] == 'requirements': |
390 | - repo_dir = _git_clone_and_install_single(repo, branch, parent_dir, |
391 | + repo_dir = _git_clone_and_install_single(repo, branch, depth, |
392 | + parent_dir, http_proxy, |
393 | update_requirements=False) |
394 | requirements_dir = repo_dir |
395 | else: |
396 | - repo_dir = _git_clone_and_install_single(repo, branch, parent_dir, |
397 | + repo_dir = _git_clone_and_install_single(repo, branch, depth, |
398 | + parent_dir, http_proxy, |
399 | update_requirements=True) |
400 | |
401 | os.environ = old_environ |
402 | @@ -574,7 +591,8 @@ |
403 | error_out('openstack-origin-git key \'{}\' is missing'.format(key)) |
404 | |
405 | |
406 | -def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements): |
407 | +def _git_clone_and_install_single(repo, branch, depth, parent_dir, http_proxy, |
408 | + update_requirements): |
409 | """ |
410 | Clone and install a single git repository. |
411 | """ |
412 | @@ -587,7 +605,8 @@ |
413 | |
414 | if not os.path.exists(dest_dir): |
415 | juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch)) |
416 | - repo_dir = install_remote(repo, dest=parent_dir, branch=branch) |
417 | + repo_dir = install_remote(repo, dest=parent_dir, branch=branch, |
418 | + depth=depth) |
419 | else: |
420 | repo_dir = dest_dir |
421 | |
422 | @@ -598,7 +617,12 @@ |
423 | _git_update_requirements(repo_dir, requirements_dir) |
424 | |
425 | juju_log('Installing git repo from dir: {}'.format(repo_dir)) |
426 | - pip_install(repo_dir) |
427 | + if http_proxy: |
428 | + pip_install(repo_dir, proxy=http_proxy, |
429 | + venv=os.path.join(parent_dir, 'venv')) |
430 | + else: |
431 | + pip_install(repo_dir, |
432 | + venv=os.path.join(parent_dir, 'venv')) |
433 | |
434 | return repo_dir |
435 | |
436 | @@ -621,16 +645,27 @@ |
437 | os.chdir(orig_dir) |
438 | |
439 | |
440 | +def git_pip_venv_dir(projects_yaml): |
441 | + """ |
442 | + Return the pip virtualenv path. |
443 | + """ |
444 | + parent_dir = '/mnt/openstack-git' |
445 | + |
446 | + projects = _git_yaml_load(projects_yaml) |
447 | + |
448 | + if 'directory' in projects.keys(): |
449 | + parent_dir = projects['directory'] |
450 | + |
451 | + return os.path.join(parent_dir, 'venv') |
452 | + |
453 | + |
454 | def git_src_dir(projects_yaml, project): |
455 | """ |
456 | Return the directory where the specified project's source is located. |
457 | """ |
458 | parent_dir = '/mnt/openstack-git' |
459 | |
460 | - if not projects_yaml: |
461 | - return |
462 | - |
463 | - projects = yaml.load(projects_yaml) |
464 | + projects = _git_yaml_load(projects_yaml) |
465 | |
466 | if 'directory' in projects.keys(): |
467 | parent_dir = projects['directory'] |
468 | @@ -640,3 +675,15 @@ |
469 | return os.path.join(parent_dir, os.path.basename(p['repository'])) |
470 | |
471 | return None |
472 | + |
473 | + |
474 | +def git_yaml_value(projects_yaml, key): |
475 | + """ |
476 | + Return the value in projects_yaml for the specified key. |
477 | + """ |
478 | + projects = _git_yaml_load(projects_yaml) |
479 | + |
480 | + if key in projects.keys(): |
481 | + return projects[key] |
482 | + |
483 | + return None |
484 | |
485 | === modified file 'hooks/charmhelpers/contrib/python/packages.py' |
486 | --- hooks/charmhelpers/contrib/python/packages.py 2015-02-24 11:02:02 +0000 |
487 | +++ hooks/charmhelpers/contrib/python/packages.py 2015-06-04 23:07:06 +0000 |
488 | @@ -17,8 +17,11 @@ |
489 | # You should have received a copy of the GNU Lesser General Public License |
490 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
491 | |
492 | +import os |
493 | +import subprocess |
494 | + |
495 | from charmhelpers.fetch import apt_install, apt_update |
496 | -from charmhelpers.core.hookenv import log |
497 | +from charmhelpers.core.hookenv import charm_dir, log |
498 | |
499 | try: |
500 | from pip import main as pip_execute |
501 | @@ -51,11 +54,15 @@ |
502 | pip_execute(command) |
503 | |
504 | |
505 | -def pip_install(package, fatal=False, upgrade=False, **options): |
506 | +def pip_install(package, fatal=False, upgrade=False, venv=None, **options): |
507 | """Install a python package""" |
508 | - command = ["install"] |
509 | + if venv: |
510 | + venv_python = os.path.join(venv, 'bin/pip') |
511 | + command = [venv_python, "install"] |
512 | + else: |
513 | + command = ["install"] |
514 | |
515 | - available_options = ('proxy', 'src', 'log', "index-url", ) |
516 | + available_options = ('proxy', 'src', 'log', 'index-url', ) |
517 | for option in parse_options(options, available_options): |
518 | command.append(option) |
519 | |
520 | @@ -69,7 +76,10 @@ |
521 | |
522 | log("Installing {} package with options: {}".format(package, |
523 | command)) |
524 | - pip_execute(command) |
525 | + if venv: |
526 | + subprocess.check_call(command) |
527 | + else: |
528 | + pip_execute(command) |
529 | |
530 | |
531 | def pip_uninstall(package, **options): |
532 | @@ -94,3 +104,16 @@ |
533 | """Returns the list of current python installed packages |
534 | """ |
535 | return pip_execute(["list"]) |
536 | + |
537 | + |
538 | +def pip_create_virtualenv(path=None): |
539 | + """Create an isolated Python environment.""" |
540 | + apt_install('python-virtualenv') |
541 | + |
542 | + if path: |
543 | + venv_path = path |
544 | + else: |
545 | + venv_path = os.path.join(charm_dir(), 'venv') |
546 | + |
547 | + if not os.path.exists(venv_path): |
548 | + subprocess.check_call(['virtualenv', venv_path]) |
549 | |
550 | === modified file 'hooks/charmhelpers/core/hookenv.py' |
551 | --- hooks/charmhelpers/core/hookenv.py 2015-04-16 21:32:59 +0000 |
552 | +++ hooks/charmhelpers/core/hookenv.py 2015-06-04 23:07:06 +0000 |
553 | @@ -21,12 +21,14 @@ |
554 | # Charm Helpers Developers <juju@lists.ubuntu.com> |
555 | |
556 | from __future__ import print_function |
557 | +from functools import wraps |
558 | import os |
559 | import json |
560 | import yaml |
561 | import subprocess |
562 | import sys |
563 | import errno |
564 | +import tempfile |
565 | from subprocess import CalledProcessError |
566 | |
567 | import six |
568 | @@ -58,15 +60,17 @@ |
569 | |
570 | will cache the result of unit_get + 'test' for future calls. |
571 | """ |
572 | + @wraps(func) |
573 | def wrapper(*args, **kwargs): |
574 | global cache |
575 | key = str((func, args, kwargs)) |
576 | try: |
577 | return cache[key] |
578 | except KeyError: |
579 | - res = func(*args, **kwargs) |
580 | - cache[key] = res |
581 | - return res |
582 | + pass # Drop out of the exception handler scope. |
583 | + res = func(*args, **kwargs) |
584 | + cache[key] = res |
585 | + return res |
586 | return wrapper |
587 | |
588 | |
589 | @@ -178,7 +182,7 @@ |
590 | |
591 | def remote_unit(): |
592 | """The remote unit for the current relation hook""" |
593 | - return os.environ['JUJU_REMOTE_UNIT'] |
594 | + return os.environ.get('JUJU_REMOTE_UNIT', None) |
595 | |
596 | |
597 | def service_name(): |
598 | @@ -250,6 +254,12 @@ |
599 | except KeyError: |
600 | return (self._prev_dict or {})[key] |
601 | |
602 | + def get(self, key, default=None): |
603 | + try: |
604 | + return self[key] |
605 | + except KeyError: |
606 | + return default |
607 | + |
608 | def keys(self): |
609 | prev_keys = [] |
610 | if self._prev_dict is not None: |
611 | @@ -353,18 +363,49 @@ |
612 | """Set relation information for the current unit""" |
613 | relation_settings = relation_settings if relation_settings else {} |
614 | relation_cmd_line = ['relation-set'] |
615 | + accepts_file = "--file" in subprocess.check_output( |
616 | + relation_cmd_line + ["--help"], universal_newlines=True) |
617 | if relation_id is not None: |
618 | relation_cmd_line.extend(('-r', relation_id)) |
619 | - for k, v in (list(relation_settings.items()) + list(kwargs.items())): |
620 | - if v is None: |
621 | - relation_cmd_line.append('{}='.format(k)) |
622 | - else: |
623 | - relation_cmd_line.append('{}={}'.format(k, v)) |
624 | - subprocess.check_call(relation_cmd_line) |
625 | + settings = relation_settings.copy() |
626 | + settings.update(kwargs) |
627 | + for key, value in settings.items(): |
628 | + # Force value to be a string: it always should, but some call |
629 | + # sites pass in things like dicts or numbers. |
630 | + if value is not None: |
631 | + settings[key] = "{}".format(value) |
632 | + if accepts_file: |
633 | + # --file was introduced in Juju 1.23.2. Use it by default if |
634 | + # available, since otherwise we'll break if the relation data is |
635 | + # too big. Ideally we should tell relation-set to read the data from |
636 | + # stdin, but that feature is broken in 1.23.2: Bug #1454678. |
637 | + with tempfile.NamedTemporaryFile(delete=False) as settings_file: |
638 | + settings_file.write(yaml.safe_dump(settings).encode("utf-8")) |
639 | + subprocess.check_call( |
640 | + relation_cmd_line + ["--file", settings_file.name]) |
641 | + os.remove(settings_file.name) |
642 | + else: |
643 | + for key, value in settings.items(): |
644 | + if value is None: |
645 | + relation_cmd_line.append('{}='.format(key)) |
646 | + else: |
647 | + relation_cmd_line.append('{}={}'.format(key, value)) |
648 | + subprocess.check_call(relation_cmd_line) |
649 | # Flush cache of any relation-gets for local unit |
650 | flush(local_unit()) |
651 | |
652 | |
653 | +def relation_clear(r_id=None): |
654 | + ''' Clears any relation data already set on relation r_id ''' |
655 | + settings = relation_get(rid=r_id, |
656 | + unit=local_unit()) |
657 | + for setting in settings: |
658 | + if setting not in ['public-address', 'private-address']: |
659 | + settings[setting] = None |
660 | + relation_set(relation_id=r_id, |
661 | + **settings) |
662 | + |
663 | + |
664 | @cached |
665 | def relation_ids(reltype=None): |
666 | """A list of relation_ids""" |
667 | @@ -509,6 +550,11 @@ |
668 | return None |
669 | |
670 | |
671 | +def unit_public_ip(): |
672 | + """Get this unit's public IP address""" |
673 | + return unit_get('public-address') |
674 | + |
675 | + |
676 | def unit_private_ip(): |
677 | """Get this unit's private IP address""" |
678 | return unit_get('private-address') |
679 | @@ -605,3 +651,94 @@ |
680 | |
681 | The results set by action_set are preserved.""" |
682 | subprocess.check_call(['action-fail', message]) |
683 | + |
684 | + |
685 | +def status_set(workload_state, message): |
686 | + """Set the workload state with a message |
687 | + |
688 | + Use status-set to set the workload state with a message which is visible |
689 | + to the user via juju status. If the status-set command is not found then |
690 | + assume this is juju < 1.23 and juju-log the message unstead. |
691 | + |
692 | + workload_state -- valid juju workload state. |
693 | + message -- status update message |
694 | + """ |
695 | + valid_states = ['maintenance', 'blocked', 'waiting', 'active'] |
696 | + if workload_state not in valid_states: |
697 | + raise ValueError( |
698 | + '{!r} is not a valid workload state'.format(workload_state) |
699 | + ) |
700 | + cmd = ['status-set', workload_state, message] |
701 | + try: |
702 | + ret = subprocess.call(cmd) |
703 | + if ret == 0: |
704 | + return |
705 | + except OSError as e: |
706 | + if e.errno != errno.ENOENT: |
707 | + raise |
708 | + log_message = 'status-set failed: {} {}'.format(workload_state, |
709 | + message) |
710 | + log(log_message, level='INFO') |
711 | + |
712 | + |
713 | +def status_get(): |
714 | + """Retrieve the previously set juju workload state |
715 | + |
716 | + If the status-set command is not found then assume this is juju < 1.23 and |
717 | + return 'unknown' |
718 | + """ |
719 | + cmd = ['status-get'] |
720 | + try: |
721 | + raw_status = subprocess.check_output(cmd, universal_newlines=True) |
722 | + status = raw_status.rstrip() |
723 | + return status |
724 | + except OSError as e: |
725 | + if e.errno == errno.ENOENT: |
726 | + return 'unknown' |
727 | + else: |
728 | + raise |
729 | + |
730 | + |
731 | +def translate_exc(from_exc, to_exc): |
732 | + def inner_translate_exc1(f): |
733 | + def inner_translate_exc2(*args, **kwargs): |
734 | + try: |
735 | + return f(*args, **kwargs) |
736 | + except from_exc: |
737 | + raise to_exc |
738 | + |
739 | + return inner_translate_exc2 |
740 | + |
741 | + return inner_translate_exc1 |
742 | + |
743 | + |
744 | +@translate_exc(from_exc=OSError, to_exc=NotImplementedError) |
745 | +def is_leader(): |
746 | + """Does the current unit hold the juju leadership |
747 | + |
748 | + Uses juju to determine whether the current unit is the leader of its peers |
749 | + """ |
750 | + cmd = ['is-leader', '--format=json'] |
751 | + return json.loads(subprocess.check_output(cmd).decode('UTF-8')) |
752 | + |
753 | + |
754 | +@translate_exc(from_exc=OSError, to_exc=NotImplementedError) |
755 | +def leader_get(attribute=None): |
756 | + """Juju leader get value(s)""" |
757 | + cmd = ['leader-get', '--format=json'] + [attribute or '-'] |
758 | + return json.loads(subprocess.check_output(cmd).decode('UTF-8')) |
759 | + |
760 | + |
761 | +@translate_exc(from_exc=OSError, to_exc=NotImplementedError) |
762 | +def leader_set(settings=None, **kwargs): |
763 | + """Juju leader set value(s)""" |
764 | + log("Juju leader-set '%s'" % (settings), level=DEBUG) |
765 | + cmd = ['leader-set'] |
766 | + settings = settings or {} |
767 | + settings.update(kwargs) |
768 | + for k, v in settings.iteritems(): |
769 | + if v is None: |
770 | + cmd.append('{}='.format(k)) |
771 | + else: |
772 | + cmd.append('{}={}'.format(k, v)) |
773 | + subprocess.check_call(cmd) |
774 | |
775 | === modified file 'hooks/charmhelpers/core/host.py' |
776 | --- hooks/charmhelpers/core/host.py 2015-04-16 21:32:59 +0000 |
777 | +++ hooks/charmhelpers/core/host.py 2015-06-04 23:07:06 +0000 |
778 | @@ -90,7 +90,7 @@ |
779 | ['service', service_name, 'status'], |
780 | stderr=subprocess.STDOUT).decode('UTF-8') |
781 | except subprocess.CalledProcessError as e: |
782 | - return 'unrecognized service' not in e.output |
783 | + return b'unrecognized service' not in e.output |
784 | else: |
785 | return True |
786 | |
787 | |
788 | === modified file 'hooks/charmhelpers/core/services/base.py' |
789 | --- hooks/charmhelpers/core/services/base.py 2015-01-26 11:53:19 +0000 |
790 | +++ hooks/charmhelpers/core/services/base.py 2015-06-04 23:07:06 +0000 |
791 | @@ -15,9 +15,9 @@ |
792 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
793 | |
794 | import os |
795 | -import re |
796 | import json |
797 | -from collections import Iterable |
798 | +from inspect import getargspec |
799 | +from collections import Iterable, OrderedDict |
800 | |
801 | from charmhelpers.core import host |
802 | from charmhelpers.core import hookenv |
803 | @@ -119,7 +119,7 @@ |
804 | """ |
805 | self._ready_file = os.path.join(hookenv.charm_dir(), 'READY-SERVICES.json') |
806 | self._ready = None |
807 | - self.services = {} |
808 | + self.services = OrderedDict() |
809 | for service in services or []: |
810 | service_name = service['service'] |
811 | self.services[service_name] = service |
812 | @@ -132,8 +132,8 @@ |
813 | if hook_name == 'stop': |
814 | self.stop_services() |
815 | else: |
816 | + self.reconfigure_services() |
817 | self.provide_data() |
818 | - self.reconfigure_services() |
819 | cfg = hookenv.config() |
820 | if cfg.implicit_save: |
821 | cfg.save() |
822 | @@ -145,15 +145,36 @@ |
823 | A provider must have a `name` attribute, which indicates which relation |
824 | to set data on, and a `provide_data()` method, which returns a dict of |
825 | data to set. |
826 | + |
827 | + The `provide_data()` method can optionally accept two parameters: |
828 | + |
829 | + * ``remote_service`` The name of the remote service that the data will |
830 | + be provided to. The `provide_data()` method will be called once |
831 | + for each connected service (not unit). This allows the method to |
832 | + tailor its data to the given service. |
833 | + * ``service_ready`` Whether or not the service definition had all of |
834 | + its requirements met, and thus the ``data_ready`` callbacks run. |
835 | + |
836 | + Note that the ``provided_data`` methods are now called **after** the |
837 | + ``data_ready`` callbacks are run. This gives the ``data_ready`` callbacks |
838 | + a chance to generate any data necessary for the providing to the remote |
839 | + services. |
840 | """ |
841 | - hook_name = hookenv.hook_name() |
842 | - for service in self.services.values(): |
843 | + for service_name, service in self.services.items(): |
844 | + service_ready = self.is_ready(service_name) |
845 | for provider in service.get('provided_data', []): |
846 | - if re.match(r'{}-relation-(joined|changed)'.format(provider.name), hook_name): |
847 | - data = provider.provide_data() |
848 | - _ready = provider._is_ready(data) if hasattr(provider, '_is_ready') else data |
849 | - if _ready: |
850 | - hookenv.relation_set(None, data) |
851 | + for relid in hookenv.relation_ids(provider.name): |
852 | + units = hookenv.related_units(relid) |
853 | + if not units: |
854 | + continue |
855 | + remote_service = units[0].split('/')[0] |
856 | + argspec = getargspec(provider.provide_data) |
857 | + if len(argspec.args) > 1: |
858 | + data = provider.provide_data(remote_service, service_ready) |
859 | + else: |
860 | + data = provider.provide_data() |
861 | + if data: |
862 | + hookenv.relation_set(relid, data) |
863 | |
864 | def reconfigure_services(self, *service_names): |
865 | """ |
866 | |
867 | === modified file 'hooks/charmhelpers/fetch/__init__.py' |
868 | --- hooks/charmhelpers/fetch/__init__.py 2015-01-26 11:53:19 +0000 |
869 | +++ hooks/charmhelpers/fetch/__init__.py 2015-06-04 23:07:06 +0000 |
870 | @@ -158,7 +158,7 @@ |
871 | |
872 | def apt_cache(in_memory=True): |
873 | """Build and return an apt cache""" |
874 | - import apt_pkg |
875 | + from apt import apt_pkg |
876 | apt_pkg.init() |
877 | if in_memory: |
878 | apt_pkg.config.set("Dir::Cache::pkgcache", "") |
879 | |
880 | === modified file 'hooks/charmhelpers/fetch/giturl.py' |
881 | --- hooks/charmhelpers/fetch/giturl.py 2015-02-24 11:02:02 +0000 |
882 | +++ hooks/charmhelpers/fetch/giturl.py 2015-06-04 23:07:06 +0000 |
883 | @@ -45,14 +45,16 @@ |
884 | else: |
885 | return True |
886 | |
887 | - def clone(self, source, dest, branch): |
888 | + def clone(self, source, dest, branch, depth=None): |
889 | if not self.can_handle(source): |
890 | raise UnhandledSource("Cannot handle {}".format(source)) |
891 | |
892 | - repo = Repo.clone_from(source, dest) |
893 | - repo.git.checkout(branch) |
894 | + if depth: |
895 | + Repo.clone_from(source, dest, branch=branch, depth=depth) |
896 | + else: |
897 | + Repo.clone_from(source, dest, branch=branch) |
898 | |
899 | - def install(self, source, branch="master", dest=None): |
900 | + def install(self, source, branch="master", dest=None, depth=None): |
901 | url_parts = self.parse_url(source) |
902 | branch_name = url_parts.path.strip("/").split("/")[-1] |
903 | if dest: |
904 | @@ -63,7 +65,7 @@ |
905 | if not os.path.exists(dest_dir): |
906 | mkdir(dest_dir, perms=0o755) |
907 | try: |
908 | - self.clone(source, dest_dir, branch) |
909 | + self.clone(source, dest_dir, branch, depth) |
910 | except GitCommandError as e: |
911 | raise UnhandledSource(e.message) |
912 | except OSError as e: |
913 | |
914 | === modified file 'hooks/hooks.py' |
915 | --- hooks/hooks.py 2015-04-28 21:18:24 +0000 |
916 | +++ hooks/hooks.py 2015-06-04 23:07:06 +0000 |
917 | @@ -50,10 +50,9 @@ |
918 | from charmhelpers.contrib.network.ip import ( |
919 | get_iface_for_address, |
920 | get_netmask_for_address, |
921 | - is_ipv6, |
922 | ) |
923 | from charmhelpers.contrib.openstack.ip import ( |
924 | - resolve_address, |
925 | + canonical_url, |
926 | PUBLIC, INTERNAL, ADMIN, |
927 | ) |
928 | |
929 | @@ -273,16 +272,6 @@ |
930 | open_port(port=80) |
931 | |
932 | |
933 | -# XXX Define local canonical_url until charm has been updated to use the |
934 | -# standard context architecture. |
935 | -def canonical_url(configs, endpoint_type=PUBLIC): |
936 | - scheme = 'http' |
937 | - address = resolve_address(endpoint_type) |
938 | - if is_ipv6(address): |
939 | - address = "[{}]".format(address) |
940 | - return '%s://%s' % (scheme, address) |
941 | - |
942 | - |
943 | @hooks.hook('identity-service-relation-joined') |
944 | def identity_joined(relid=None): |
945 | if cmp_pkgrevno('radosgw', '0.55') < 0: |
946 | @@ -290,11 +279,11 @@ |
947 | sys.exit(1) |
948 | |
949 | port = 80 |
950 | - admin_url = '%s:%i/swift' % (canonical_url(ADMIN), port) |
951 | + admin_url = '%s:%i/swift' % (canonical_url(None, ADMIN), port) |
952 | internal_url = '%s:%s/swift/v1' % \ |
953 | - (canonical_url(INTERNAL), port) |
954 | + (canonical_url(None, INTERNAL), port) |
955 | public_url = '%s:%s/swift/v1' % \ |
956 | - (canonical_url(PUBLIC), port) |
957 | + (canonical_url(None, PUBLIC), port) |
958 | relation_set(service='swift', |
959 | region=config('region'), |
960 | public_url=public_url, internal_url=internal_url, |
961 | |
962 | === modified file 'tests/charmhelpers/contrib/amulet/utils.py' |
963 | --- tests/charmhelpers/contrib/amulet/utils.py 2015-04-16 21:32:59 +0000 |
964 | +++ tests/charmhelpers/contrib/amulet/utils.py 2015-06-04 23:07:06 +0000 |
965 | @@ -79,6 +79,9 @@ |
966 | for k, v in six.iteritems(commands): |
967 | for cmd in v: |
968 | output, code = k.run(cmd) |
969 | + self.log.debug('{} `{}` returned ' |
970 | + '{}'.format(k.info['unit_name'], |
971 | + cmd, code)) |
972 | if code != 0: |
973 | return "command `{}` returned {}".format(cmd, str(code)) |
974 | return None |
975 | @@ -86,7 +89,11 @@ |
976 | def _get_config(self, unit, filename): |
977 | """Get a ConfigParser object for parsing a unit's config file.""" |
978 | file_contents = unit.file_contents(filename) |
979 | - config = ConfigParser.ConfigParser() |
980 | + |
981 | + # NOTE(beisner): by default, ConfigParser does not handle options |
982 | + # with no value, such as the flags used in the mysql my.cnf file. |
983 | + # https://bugs.python.org/issue7005 |
984 | + config = ConfigParser.ConfigParser(allow_no_value=True) |
985 | config.readfp(io.StringIO(file_contents)) |
986 | return config |
987 | |
988 | |
989 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py' |
990 | --- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-04-16 21:32:59 +0000 |
991 | +++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-06-04 23:07:06 +0000 |
992 | @@ -46,15 +46,22 @@ |
993 | stable or next branches for the other_services.""" |
994 | base_charms = ['mysql', 'mongodb'] |
995 | |
996 | + if self.series in ['precise', 'trusty']: |
997 | + base_series = self.series |
998 | + else: |
999 | + base_series = self.current_next |
1000 | + |
1001 | if self.stable: |
1002 | for svc in other_services: |
1003 | - temp = 'lp:charms/{}' |
1004 | - svc['location'] = temp.format(svc['name']) |
1005 | + temp = 'lp:charms/{}/{}' |
1006 | + svc['location'] = temp.format(base_series, |
1007 | + svc['name']) |
1008 | else: |
1009 | for svc in other_services: |
1010 | if svc['name'] in base_charms: |
1011 | - temp = 'lp:charms/{}' |
1012 | - svc['location'] = temp.format(svc['name']) |
1013 | + temp = 'lp:charms/{}/{}' |
1014 | + svc['location'] = temp.format(base_series, |
1015 | + svc['name']) |
1016 | else: |
1017 | temp = 'lp:~openstack-charmers/charms/{}/{}/next' |
1018 | svc['location'] = temp.format(self.current_next, |
1019 | @@ -99,10 +106,12 @@ |
1020 | Return an integer representing the enum value of the openstack |
1021 | release. |
1022 | """ |
1023 | + # Must be ordered by OpenStack release (not by Ubuntu release): |
1024 | (self.precise_essex, self.precise_folsom, self.precise_grizzly, |
1025 | self.precise_havana, self.precise_icehouse, |
1026 | - self.trusty_icehouse, self.trusty_juno, self.trusty_kilo, |
1027 | - self.utopic_juno, self.vivid_kilo) = range(10) |
1028 | + self.trusty_icehouse, self.trusty_juno, self.utopic_juno, |
1029 | + self.trusty_kilo, self.vivid_kilo) = range(10) |
1030 | + |
1031 | releases = { |
1032 | ('precise', None): self.precise_essex, |
1033 | ('precise', 'cloud:precise-folsom'): self.precise_folsom, |
1034 | |
1035 | === modified file 'unit_tests/test_hooks.py' |
1036 | --- unit_tests/test_hooks.py 2015-04-28 21:18:24 +0000 |
1037 | +++ unit_tests/test_hooks.py 2015-06-04 23:07:06 +0000 |
1038 | @@ -8,6 +8,7 @@ |
1039 | CharmTestCase, |
1040 | patch_open |
1041 | ) |
1042 | +from charmhelpers.contrib.openstack.ip import PUBLIC |
1043 | |
1044 | dnsmock = MagicMock() |
1045 | modules = { |
1046 | @@ -45,7 +46,6 @@ |
1047 | 'relation_set', |
1048 | 'relation_get', |
1049 | 'render_template', |
1050 | - 'resolve_address', |
1051 | 'shutil', |
1052 | 'subprocess', |
1053 | 'sys', |
1054 | @@ -323,14 +323,22 @@ |
1055 | cmd = ['service', 'radosgw', 'restart'] |
1056 | self.subprocess.call.assert_called_with(cmd) |
1057 | |
1058 | - def test_identity_joined_early_version(self): |
1059 | + @patch('charmhelpers.contrib.openstack.ip.service_name', |
1060 | + lambda *args: 'ceph-radosgw') |
1061 | + @patch('charmhelpers.contrib.openstack.ip.config') |
1062 | + def test_identity_joined_early_version(self, _config): |
1063 | self.cmp_pkgrevno.return_value = -1 |
1064 | ceph_hooks.identity_joined() |
1065 | self.sys.exit.assert_called_with(1) |
1066 | |
1067 | - def test_identity_joined(self): |
1068 | + @patch('charmhelpers.contrib.openstack.ip.service_name', |
1069 | + lambda *args: 'ceph-radosgw') |
1070 | + @patch('charmhelpers.contrib.openstack.ip.resolve_address') |
1071 | + @patch('charmhelpers.contrib.openstack.ip.config') |
1072 | + def test_identity_joined(self, _config, _resolve_address): |
1073 | self.cmp_pkgrevno.return_value = 1 |
1074 | - self.resolve_address.return_value = 'myserv' |
1075 | + _resolve_address.return_value = 'myserv' |
1076 | + _config.side_effect = self.test_config.get |
1077 | self.test_config.set('region', 'region1') |
1078 | self.test_config.set('operator-roles', 'admin') |
1079 | self.unit_get.return_value = 'myserv' |
1080 | @@ -344,6 +352,27 @@ |
1081 | relation_id='rid', |
1082 | admin_url='http://myserv:80/swift') |
1083 | |
1084 | + @patch('charmhelpers.contrib.openstack.ip.service_name', |
1085 | + lambda *args: 'ceph-radosgw') |
1086 | + @patch('charmhelpers.contrib.openstack.ip.is_clustered') |
1087 | + @patch('charmhelpers.contrib.openstack.ip.unit_get') |
1088 | + @patch('charmhelpers.contrib.openstack.ip.config') |
1089 | + def test_identity_joined_public_name(self, _config, _unit_get, |
1090 | + _is_clustered): |
1091 | + _config.side_effect = self.test_config.get |
1092 | + self.test_config.set('os-public-hostname', 'files.example.com') |
1093 | + _unit_get.return_value = 'myserv' |
1094 | + _is_clustered.return_value = False |
1095 | + ceph_hooks.identity_joined(relid='rid') |
1096 | + self.relation_set.assert_called_with( |
1097 | + service='swift', |
1098 | + region='RegionOne', |
1099 | + public_url='http://files.example.com:80/swift/v1', |
1100 | + internal_url='http://myserv:80/swift/v1', |
1101 | + requested_roles='Member,Admin', |
1102 | + relation_id='rid', |
1103 | + admin_url='http://myserv:80/swift') |
1104 | + |
1105 | def test_identity_changed(self): |
1106 | _emit_cephconf = self.patch('emit_cephconf') |
1107 | _restart = self.patch('restart') |
1108 | @@ -351,10 +380,15 @@ |
1109 | _emit_cephconf.assert_called() |
1110 | _restart.assert_called() |
1111 | |
1112 | - def test_canonical_url_ipv6(self): |
1113 | + @patch('charmhelpers.contrib.openstack.ip.is_clustered') |
1114 | + @patch('charmhelpers.contrib.openstack.ip.unit_get') |
1115 | + @patch('charmhelpers.contrib.openstack.ip.config') |
1116 | + def test_canonical_url_ipv6(self, _config, _unit_get, _is_clustered): |
1117 | ipv6_addr = '2001:db8:85a3:8d3:1319:8a2e:370:7348' |
1118 | - self.resolve_address.return_value = ipv6_addr |
1119 | - self.assertEquals(ceph_hooks.canonical_url({}), |
1120 | + _config.side_effect = self.test_config.get |
1121 | + _unit_get.return_value = ipv6_addr |
1122 | + _is_clustered.return_value = False |
1123 | + self.assertEquals(ceph_hooks.canonical_url({}, PUBLIC), |
1124 | 'http://[%s]' % ipv6_addr) |
1125 | |
1126 | @patch.object(ceph_hooks, 'CONFIGS') |
charm_lint_check #5049 ceph-radosgw-next for billy-olsen mp261011
LINT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_lint_ check/5049/