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