Merge lp:~junaidali/charms/trusty/plumgrid-director/oil-sapi-changes into lp:charms/trusty/plumgrid-director
- Trusty Tahr (14.04)
- oil-sapi-changes
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~junaidali/charms/trusty/plumgrid-director/oil-sapi-changes |
Merge into: | lp:charms/trusty/plumgrid-director |
Diff against target: |
1050 lines (+649/-94) 12 files modified
Makefile (+2/-3) README.md (+3/-2) actions.yaml (+8/-2) actions/actions.py (+36/-7) bin/charm_helpers_sync.py (+253/-0) config.yaml (+24/-0) hooks/install (+20/-0) hooks/pg_dir_context.py (+24/-0) hooks/pg_dir_hooks.py (+37/-17) hooks/pg_dir_utils.py (+235/-62) metadata.yaml (+3/-0) unit_tests/test_pg_dir_hooks.py (+4/-1) |
To merge this branch: | bzr merge lp:~junaidali/charms/trusty/plumgrid-director/oil-sapi-changes |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Page | Pending | ||
Review via email: mp+308654@code.launchpad.net |
Commit message
Description of the change
Added charm series in metadata.yaml file
Updated README file
Added Solutions API support for PG-ONS 6.X.X
Added actions
Wrapper to deal with Ubuntu versions don't have py2 installed
Edge and gateway relations will also be used to get node's IPs
Catalyst OPSVM changes
Unmerged revisions
- 22. By Junaid Ali
-
Changes:
Added charm series in metadata.yaml file
Updated README file
Added Solutions API support for PG-ONS 6.X.X
Added actions
Wrapper to deal with Ubuntu versions don't have py2 installed
Edge and gateway relations will also be used to get node's IPs
Catalyst OPSVM changes - 21. By Junaid Ali
-
Changes:
Added charm series in metadata.yaml file
Updated README file
Added Solutions API support for PG-ONS 6.X.X
Added actions
Wrapper to deal with Ubuntu versions don't have py2 installed
Edge and gateway relations will also be used to get node's IPs
Catalyst OPSVM changes
Preview Diff
1 | === modified file 'Makefile' |
2 | --- Makefile 2016-05-25 16:30:35 +0000 |
3 | +++ Makefile 2016-10-17 17:28:49 +0000 |
4 | @@ -4,12 +4,11 @@ |
5 | virtualenv: |
6 | virtualenv .venv |
7 | .venv/bin/pip install flake8 nose coverage mock pyyaml netifaces \ |
8 | - netaddr jinja2 pyflakes pep8 six pbr funcsigs psutil charm-tools \ |
9 | - simplejson |
10 | + netaddr jinja2 pyflakes pep8 six pbr funcsigs psutil |
11 | |
12 | lint: virtualenv |
13 | .venv/bin/flake8 --exclude hooks/charmhelpers hooks unit_tests tests --ignore E402 |
14 | - @.venv/bin/charm-proof |
15 | + @charm proof |
16 | |
17 | unit_test: virtualenv |
18 | @echo Starting tests... |
19 | |
20 | === modified file 'README.md' |
21 | --- README.md 2016-03-03 20:56:40 +0000 |
22 | +++ README.md 2016-10-17 17:28:49 +0000 |
23 | @@ -26,7 +26,7 @@ |
24 | |
25 | # Known Limitations and Issues |
26 | |
27 | -This is an early access version of the PLUMgrid Director charm and it is not meant for production deployments. The charm only supports Kilo Openstack Release. |
28 | +This charm currently is in an initial phase in supporting Ubuntu 16.04. |
29 | |
30 | # Configuration |
31 | |
32 | @@ -58,4 +58,5 @@ |
33 | # Contact Information |
34 | |
35 | Bilal Baqar <bbaqar@plumgrid.com> |
36 | -Bilal Ahmad <bilal@plumgrid.com> |
37 | +Javeria Khan <javeriak@plumgrid.com> |
38 | +Junaid Ali <junaidali@plumgrid.com> |
39 | |
40 | === modified file 'actions.yaml' |
41 | --- actions.yaml 2016-07-27 09:39:41 +0000 |
42 | +++ actions.yaml 2016-10-17 17:28:49 +0000 |
43 | @@ -1,2 +1,8 @@ |
44 | -restart: |
45 | - description: Restart the plumgrid-director unit. This action will restart related services. |
46 | +restart-pg-service: |
47 | + description: Restart the plumgrid-director unit's service. |
48 | +sapi-post-ips: |
49 | + description: Post PLUMgrid nodes IPs to Solutions API server. |
50 | +sapi-post-zone-info: |
51 | + description: Post PLUMgrid Zone info to Solutions API server. |
52 | +sapi-post-license: |
53 | + description: Post PLUMgrid License to Solutions API server. |
54 | |
55 | === modified file 'actions/actions.py' |
56 | --- actions/actions.py 2016-07-27 09:39:41 +0000 |
57 | +++ actions/actions.py 2016-10-17 17:28:49 +0000 |
58 | @@ -7,20 +7,49 @@ |
59 | |
60 | from charmhelpers.core.hookenv import action_fail |
61 | from pg_dir_utils import ( |
62 | - restart_pg |
63 | + restart_pg, |
64 | + sapi_post_zone_info, |
65 | + sapi_post_license, |
66 | + sapi_post_ips |
67 | ) |
68 | |
69 | |
70 | -def restart(args): |
71 | - """Pause the Ceilometer services. |
72 | - @raises Exception should the service fail to stop. |
73 | - """ |
74 | - restart_pg('lxc') |
75 | +def restart_pg_service(args): |
76 | + """ |
77 | + Restart PLUMgrid services. |
78 | + """ |
79 | + restart_pg() |
80 | + |
81 | + |
82 | +def post_ips(args): |
83 | + """ |
84 | + POST PLUMgrid nodes IPs to solutions api server. |
85 | + """ |
86 | + sapi_post_ips() |
87 | + |
88 | + |
89 | +def post_zone_info(args): |
90 | + """ |
91 | + POST PLUMgrid zone information to solutions api server |
92 | + """ |
93 | + sapi_post_zone_info() |
94 | + |
95 | + |
96 | +def post_license(args): |
97 | + """ |
98 | + POST PLUMgrid License key to solutions api server |
99 | + """ |
100 | + sapi_post_license() |
101 | |
102 | |
103 | # A dictionary of all the defined actions to callables (which take |
104 | # parsed arguments). |
105 | -ACTIONS = {"restart": restart} |
106 | +ACTIONS = { |
107 | + "restart-pg-service": restart_pg_service, |
108 | + "sapi-post-ips": post_ips, |
109 | + "sapi-post-zone-info": post_zone_info, |
110 | + "sapi-post-license": post_license |
111 | +} |
112 | |
113 | |
114 | def main(args): |
115 | |
116 | === removed symlink 'actions/restart' |
117 | === target was u'actions.py' |
118 | === added symlink 'actions/restart-pg-service' |
119 | === target is u'actions.py' |
120 | === added symlink 'actions/sapi-post-ips' |
121 | === target is u'actions.py' |
122 | === added symlink 'actions/sapi-post-license' |
123 | === target is u'actions.py' |
124 | === added symlink 'actions/sapi-post-zone-info' |
125 | === target is u'actions.py' |
126 | === added directory 'bin' |
127 | === added file 'bin/charm_helpers_sync.py' |
128 | --- bin/charm_helpers_sync.py 1970-01-01 00:00:00 +0000 |
129 | +++ bin/charm_helpers_sync.py 2016-10-17 17:28:49 +0000 |
130 | @@ -0,0 +1,253 @@ |
131 | +#!/usr/bin/python |
132 | + |
133 | +# Copyright 2014-2015 Canonical Limited. |
134 | +# |
135 | +# This file is part of charm-helpers. |
136 | +# |
137 | +# charm-helpers is free software: you can redistribute it and/or modify |
138 | +# it under the terms of the GNU Lesser General Public License version 3 as |
139 | +# published by the Free Software Foundation. |
140 | +# |
141 | +# charm-helpers is distributed in the hope that it will be useful, |
142 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
143 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
144 | +# GNU Lesser General Public License for more details. |
145 | +# |
146 | +# You should have received a copy of the GNU Lesser General Public License |
147 | +# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
148 | + |
149 | +# Authors: |
150 | +# Adam Gandelman <adamg@ubuntu.com> |
151 | + |
152 | +import logging |
153 | +import optparse |
154 | +import os |
155 | +import subprocess |
156 | +import shutil |
157 | +import sys |
158 | +import tempfile |
159 | +import yaml |
160 | +from fnmatch import fnmatch |
161 | + |
162 | +import six |
163 | + |
164 | +CHARM_HELPERS_BRANCH = 'lp:charm-helpers' |
165 | + |
166 | + |
167 | +def parse_config(conf_file): |
168 | + if not os.path.isfile(conf_file): |
169 | + logging.error('Invalid config file: %s.' % conf_file) |
170 | + return False |
171 | + return yaml.load(open(conf_file).read()) |
172 | + |
173 | + |
174 | +def clone_helpers(work_dir, branch): |
175 | + dest = os.path.join(work_dir, 'charm-helpers') |
176 | + logging.info('Checking out %s to %s.' % (branch, dest)) |
177 | + cmd = ['bzr', 'checkout', '--lightweight', branch, dest] |
178 | + subprocess.check_call(cmd) |
179 | + return dest |
180 | + |
181 | + |
182 | +def _module_path(module): |
183 | + return os.path.join(*module.split('.')) |
184 | + |
185 | + |
186 | +def _src_path(src, module): |
187 | + return os.path.join(src, 'charmhelpers', _module_path(module)) |
188 | + |
189 | + |
190 | +def _dest_path(dest, module): |
191 | + return os.path.join(dest, _module_path(module)) |
192 | + |
193 | + |
194 | +def _is_pyfile(path): |
195 | + return os.path.isfile(path + '.py') |
196 | + |
197 | + |
198 | +def ensure_init(path): |
199 | + ''' |
200 | + ensure directories leading up to path are importable, omitting |
201 | + parent directory, eg path='/hooks/helpers/foo'/: |
202 | + hooks/ |
203 | + hooks/helpers/__init__.py |
204 | + hooks/helpers/foo/__init__.py |
205 | + ''' |
206 | + for d, dirs, files in os.walk(os.path.join(*path.split('/')[:2])): |
207 | + _i = os.path.join(d, '__init__.py') |
208 | + if not os.path.exists(_i): |
209 | + logging.info('Adding missing __init__.py: %s' % _i) |
210 | + open(_i, 'wb').close() |
211 | + |
212 | + |
213 | +def sync_pyfile(src, dest): |
214 | + src = src + '.py' |
215 | + src_dir = os.path.dirname(src) |
216 | + logging.info('Syncing pyfile: %s -> %s.' % (src, dest)) |
217 | + if not os.path.exists(dest): |
218 | + os.makedirs(dest) |
219 | + shutil.copy(src, dest) |
220 | + if os.path.isfile(os.path.join(src_dir, '__init__.py')): |
221 | + shutil.copy(os.path.join(src_dir, '__init__.py'), |
222 | + dest) |
223 | + ensure_init(dest) |
224 | + |
225 | + |
226 | +def get_filter(opts=None): |
227 | + opts = opts or [] |
228 | + if 'inc=*' in opts: |
229 | + # do not filter any files, include everything |
230 | + return None |
231 | + |
232 | + def _filter(dir, ls): |
233 | + incs = [opt.split('=').pop() for opt in opts if 'inc=' in opt] |
234 | + _filter = [] |
235 | + for f in ls: |
236 | + _f = os.path.join(dir, f) |
237 | + |
238 | + if not os.path.isdir(_f) and not _f.endswith('.py') and incs: |
239 | + if True not in [fnmatch(_f, inc) for inc in incs]: |
240 | + logging.debug('Not syncing %s, does not match include ' |
241 | + 'filters (%s)' % (_f, incs)) |
242 | + _filter.append(f) |
243 | + else: |
244 | + logging.debug('Including file, which matches include ' |
245 | + 'filters (%s): %s' % (incs, _f)) |
246 | + elif (os.path.isfile(_f) and not _f.endswith('.py')): |
247 | + logging.debug('Not syncing file: %s' % f) |
248 | + _filter.append(f) |
249 | + elif (os.path.isdir(_f) and not |
250 | + os.path.isfile(os.path.join(_f, '__init__.py'))): |
251 | + logging.debug('Not syncing directory: %s' % f) |
252 | + _filter.append(f) |
253 | + return _filter |
254 | + return _filter |
255 | + |
256 | + |
257 | +def sync_directory(src, dest, opts=None): |
258 | + if os.path.exists(dest): |
259 | + logging.debug('Removing existing directory: %s' % dest) |
260 | + shutil.rmtree(dest) |
261 | + logging.info('Syncing directory: %s -> %s.' % (src, dest)) |
262 | + |
263 | + shutil.copytree(src, dest, ignore=get_filter(opts)) |
264 | + ensure_init(dest) |
265 | + |
266 | + |
267 | +def sync(src, dest, module, opts=None): |
268 | + |
269 | + # Sync charmhelpers/__init__.py for bootstrap code. |
270 | + sync_pyfile(_src_path(src, '__init__'), dest) |
271 | + |
272 | + # Sync other __init__.py files in the path leading to module. |
273 | + m = [] |
274 | + steps = module.split('.')[:-1] |
275 | + while steps: |
276 | + m.append(steps.pop(0)) |
277 | + init = '.'.join(m + ['__init__']) |
278 | + sync_pyfile(_src_path(src, init), |
279 | + os.path.dirname(_dest_path(dest, init))) |
280 | + |
281 | + # Sync the module, or maybe a .py file. |
282 | + if os.path.isdir(_src_path(src, module)): |
283 | + sync_directory(_src_path(src, module), _dest_path(dest, module), opts) |
284 | + elif _is_pyfile(_src_path(src, module)): |
285 | + sync_pyfile(_src_path(src, module), |
286 | + os.path.dirname(_dest_path(dest, module))) |
287 | + else: |
288 | + logging.warn('Could not sync: %s. Neither a pyfile or directory, ' |
289 | + 'does it even exist?' % module) |
290 | + |
291 | + |
292 | +def parse_sync_options(options): |
293 | + if not options: |
294 | + return [] |
295 | + return options.split(',') |
296 | + |
297 | + |
298 | +def extract_options(inc, global_options=None): |
299 | + global_options = global_options or [] |
300 | + if global_options and isinstance(global_options, six.string_types): |
301 | + global_options = [global_options] |
302 | + if '|' not in inc: |
303 | + return (inc, global_options) |
304 | + inc, opts = inc.split('|') |
305 | + return (inc, parse_sync_options(opts) + global_options) |
306 | + |
307 | + |
308 | +def sync_helpers(include, src, dest, options=None): |
309 | + if not os.path.isdir(dest): |
310 | + os.makedirs(dest) |
311 | + |
312 | + global_options = parse_sync_options(options) |
313 | + |
314 | + for inc in include: |
315 | + if isinstance(inc, str): |
316 | + inc, opts = extract_options(inc, global_options) |
317 | + sync(src, dest, inc, opts) |
318 | + elif isinstance(inc, dict): |
319 | + # could also do nested dicts here. |
320 | + for k, v in six.iteritems(inc): |
321 | + if isinstance(v, list): |
322 | + for m in v: |
323 | + inc, opts = extract_options(m, global_options) |
324 | + sync(src, dest, '%s.%s' % (k, inc), opts) |
325 | + |
326 | +if __name__ == '__main__': |
327 | + parser = optparse.OptionParser() |
328 | + parser.add_option('-c', '--config', action='store', dest='config', |
329 | + default=None, help='helper config file') |
330 | + parser.add_option('-D', '--debug', action='store_true', dest='debug', |
331 | + default=False, help='debug') |
332 | + parser.add_option('-b', '--branch', action='store', dest='branch', |
333 | + help='charm-helpers bzr branch (overrides config)') |
334 | + parser.add_option('-d', '--destination', action='store', dest='dest_dir', |
335 | + help='sync destination dir (overrides config)') |
336 | + (opts, args) = parser.parse_args() |
337 | + |
338 | + if opts.debug: |
339 | + logging.basicConfig(level=logging.DEBUG) |
340 | + else: |
341 | + logging.basicConfig(level=logging.INFO) |
342 | + |
343 | + if opts.config: |
344 | + logging.info('Loading charm helper config from %s.' % opts.config) |
345 | + config = parse_config(opts.config) |
346 | + if not config: |
347 | + logging.error('Could not parse config from %s.' % opts.config) |
348 | + sys.exit(1) |
349 | + else: |
350 | + config = {} |
351 | + |
352 | + if 'branch' not in config: |
353 | + config['branch'] = CHARM_HELPERS_BRANCH |
354 | + if opts.branch: |
355 | + config['branch'] = opts.branch |
356 | + if opts.dest_dir: |
357 | + config['destination'] = opts.dest_dir |
358 | + |
359 | + if 'destination' not in config: |
360 | + logging.error('No destination dir. specified as option or config.') |
361 | + sys.exit(1) |
362 | + |
363 | + if 'include' not in config: |
364 | + if not args: |
365 | + logging.error('No modules to sync specified as option or config.') |
366 | + sys.exit(1) |
367 | + config['include'] = [] |
368 | + [config['include'].append(a) for a in args] |
369 | + |
370 | + sync_options = None |
371 | + if 'options' in config: |
372 | + sync_options = config['options'] |
373 | + tmpd = tempfile.mkdtemp() |
374 | + try: |
375 | + checkout = clone_helpers(tmpd, config['branch']) |
376 | + sync_helpers(config['include'], checkout, config['destination'], |
377 | + options=sync_options) |
378 | + except Exception as e: |
379 | + logging.error("Could not sync: %s" % e) |
380 | + raise e |
381 | + finally: |
382 | + logging.debug('Cleaning up %s' % tmpd) |
383 | + shutil.rmtree(tmpd) |
384 | |
385 | === modified file 'config.yaml' |
386 | --- config.yaml 2016-07-23 14:21:58 +0000 |
387 | +++ config.yaml 2016-10-17 17:28:49 +0000 |
388 | @@ -17,6 +17,7 @@ |
389 | description: Public SSH key of PLUMgrid LCM which is running PG-Tools. |
390 | mgmt-interface: |
391 | type: string |
392 | + default: |
393 | description: The interface connected to PLUMgrid Managment network. |
394 | fabric-interfaces: |
395 | default: 'MANAGEMENT' |
396 | @@ -56,3 +57,26 @@ |
397 | default: 127.0.0.1 |
398 | type: string |
399 | description: IP address of the PLUMgrid Operations VM Management interface. |
400 | + lcm-ip: |
401 | + type: string |
402 | + default: 127.0.0.1 |
403 | + description: IP used by Solutions API to get/post cloud information. |
404 | + sapi-port: |
405 | + default: 8099 |
406 | + type: int |
407 | + description: Port used by Solutions API to get/post cloud information. |
408 | + sapi-zone: |
409 | + default: pgzone |
410 | + type: string |
411 | + description: Zone name used by Solutions API to get/post cloud information. |
412 | + openstack-release: |
413 | + default: kilo |
414 | + type: string |
415 | + description: | |
416 | + OpenStack release to determine solution version that will be posted to |
417 | + Solutions API server. |
418 | + enable-sapi: |
419 | + default: false |
420 | + type: boolean |
421 | + description: | |
422 | + Enable or disable Solutions API support. |
423 | |
424 | === modified symlink 'hooks/install' (properties changed: -x to +x) |
425 | === target was u'pg_dir_hooks.py' |
426 | --- hooks/install 1970-01-01 00:00:00 +0000 |
427 | +++ hooks/install 2016-10-17 17:28:49 +0000 |
428 | @@ -0,0 +1,20 @@ |
429 | +#!/bin/bash |
430 | +# Wrapper to deal with newer Ubuntu versions that don't have py2 installed |
431 | +# by default. |
432 | + |
433 | +declare -a DEPS=('apt' 'netaddr' 'netifaces' 'pip' 'yaml') |
434 | + |
435 | +check_and_install() { |
436 | + pkg="${1}-${2}" |
437 | + if ! dpkg -s ${pkg} 2>&1 > /dev/null; then |
438 | + apt-get -y install ${pkg} |
439 | + fi |
440 | +} |
441 | + |
442 | +PYTHON="python" |
443 | + |
444 | +for dep in ${DEPS[@]}; do |
445 | + check_and_install ${PYTHON} ${dep} |
446 | +done |
447 | + |
448 | +exec ./hooks/install.real |
449 | |
450 | === added symlink 'hooks/install.real' |
451 | === target is u'pg_dir_hooks.py' |
452 | === modified file 'hooks/pg_dir_context.py' |
453 | --- hooks/pg_dir_context.py 2016-03-26 22:04:58 +0000 |
454 | +++ hooks/pg_dir_context.py 2016-10-17 17:28:49 +0000 |
455 | @@ -26,6 +26,30 @@ |
456 | ) |
457 | |
458 | |
459 | +def _pg_edge_ips(): |
460 | + ''' |
461 | + Inspects edge-peer relation and returns the |
462 | + ips of the edge nodes |
463 | + ''' |
464 | + return [get_host_ip(rdata['private-address']) |
465 | + for rid in relation_ids("plumgrid") |
466 | + for rdata in |
467 | + (relation_get(rid=rid, unit=unit) for unit in related_units(rid)) |
468 | + if 'edge-peer' in rdata] |
469 | + |
470 | + |
471 | +def _pg_gateway_ips(): |
472 | + ''' |
473 | + Inspects gateway-peer relation and returns the |
474 | + ips of the gateway nodes |
475 | + ''' |
476 | + return [get_host_ip(rdata['private-address']) |
477 | + for rid in relation_ids("plumgrid") |
478 | + for rdata in |
479 | + (relation_get(rid=rid, unit=unit) for unit in related_units(rid)) |
480 | + if 'gateway-peer' in rdata] |
481 | + |
482 | + |
483 | def _pg_dir_ips(): |
484 | ''' |
485 | Inspects plumgrid-director peer relation and returns the |
486 | |
487 | === modified file 'hooks/pg_dir_hooks.py' |
488 | --- hooks/pg_dir_hooks.py 2016-07-28 19:27:47 +0000 |
489 | +++ hooks/pg_dir_hooks.py 2016-10-17 17:28:49 +0000 |
490 | @@ -9,7 +9,6 @@ |
491 | import time |
492 | from charmhelpers.core.host import service_running |
493 | from charmhelpers.contrib.network.ip import is_ip |
494 | - |
495 | from charmhelpers.core.hookenv import ( |
496 | Hooks, |
497 | UnregisteredHookError, |
498 | @@ -41,15 +40,19 @@ |
499 | load_iptables, |
500 | restart_on_change, |
501 | director_cluster_ready, |
502 | - disable_apparmor_libvirt, |
503 | - configure_pg_sources |
504 | + configure_pg_sources, |
505 | + configure_analyst_opsvm, |
506 | + sapi_post_ips, |
507 | + sapi_post_license, |
508 | + sapi_post_zone_info, |
509 | + disable_apparmor_libvirt |
510 | ) |
511 | |
512 | hooks = Hooks() |
513 | CONFIGS = register_configs() |
514 | |
515 | |
516 | -@hooks.hook() |
517 | +@hooks.hook('install.real') |
518 | def install(): |
519 | ''' |
520 | Install hook is run when the charm is first deployed on a node. |
521 | @@ -62,12 +65,13 @@ |
522 | for pkg in pkgs: |
523 | apt_install(pkg, options=['--force-yes'], fatal=True) |
524 | load_iovisor() |
525 | - ensure_mtu() |
526 | disable_apparmor_libvirt() |
527 | + ensure_mtu() |
528 | CONFIGS.write_all() |
529 | |
530 | |
531 | @hooks.hook('director-relation-joined') |
532 | +@hooks.hook('director-relation-changed') |
533 | @restart_on_change(restart_map()) |
534 | def dir_joined(): |
535 | ''' |
536 | @@ -76,19 +80,22 @@ |
537 | if director_cluster_ready(): |
538 | ensure_mtu() |
539 | CONFIGS.write_all() |
540 | - restart_pg('lxc') |
541 | - |
542 | - |
543 | -@hooks.hook('plumgrid-relation-joined') |
544 | + |
545 | + |
546 | +@hooks.hook('plumgrid-relation-joined', |
547 | + 'plumgrid-relation-changed', |
548 | + 'plumgrid-relation-departed') |
549 | def plumgrid_joined(relation_id=None): |
550 | ''' |
551 | This hook is run when relation with edge or gateway is created. |
552 | ''' |
553 | opsvm_ip = config('opsvm-ip') |
554 | if not is_ip(opsvm_ip): |
555 | - raise ValueError('Incorrect OPSVM IP specified') |
556 | + raise ValueError('Invalid OPSVM IP specified!') |
557 | else: |
558 | relation_set(relation_id=relation_id, opsvm_ip=opsvm_ip) |
559 | + if is_leader(): |
560 | + sapi_post_ips() |
561 | |
562 | |
563 | @hooks.hook('plumgrid-configs-relation-joined') |
564 | @@ -119,6 +126,8 @@ |
565 | if charm_config.changed('plumgrid-license-key'): |
566 | if is_leader() and post_pg_license(): |
567 | log("PLUMgrid License Posted") |
568 | + # Post PG license to Sol-API |
569 | + sapi_post_license() |
570 | if charm_config.changed('fabric-interfaces'): |
571 | if not fabric_interface_changed(): |
572 | log("Fabric interface already set") |
573 | @@ -126,6 +135,8 @@ |
574 | stop_pg() |
575 | if charm_config.changed('plumgrid-virtual-ip'): |
576 | CONFIGS.write_all() |
577 | + for rid in relation_ids('plumgrid'): |
578 | + plumgrid_joined(rid) |
579 | stop_pg() |
580 | for rid in relation_ids('plumgrid-configs'): |
581 | plumgrid_configs_joined(rid) |
582 | @@ -151,10 +162,21 @@ |
583 | for rid in relation_ids('plumgrid'): |
584 | plumgrid_joined(rid) |
585 | stop_pg() |
586 | + if (charm_config.changed('sapi-port') or |
587 | + charm_config.changed('lcm-ip') or |
588 | + charm_config.changed('sapi-zone') or |
589 | + charm_config.changed('enable-sapi')): |
590 | + if is_leader(): |
591 | + if is_ip(config('lcm-ip')): |
592 | + sapi_post_zone_info() |
593 | + else: |
594 | + raise ValueError('Invalid LCM IP specified!') |
595 | + for rid in relation_ids('plumgrid'): |
596 | + plumgrid_joined(rid) |
597 | ensure_mtu() |
598 | CONFIGS.write_all() |
599 | if not service_running('plumgrid'): |
600 | - restart_pg('lxc') |
601 | + restart_pg() |
602 | |
603 | |
604 | @hooks.hook('start') |
605 | @@ -162,16 +184,15 @@ |
606 | ''' |
607 | This hook is run when the charm is started. |
608 | ''' |
609 | - restart_pg('lxc') |
610 | - time.sleep(15) |
611 | + configure_analyst_opsvm() |
612 | if config('plumgrid-license-key') is not None: |
613 | count = 0 |
614 | - while (count < 15): |
615 | + while (count < 10): |
616 | + time.sleep(15) |
617 | if post_pg_license(): |
618 | break |
619 | count += 1 |
620 | - time.sleep(15) |
621 | - if count == 15: |
622 | + if count == 10: |
623 | raise ValueError("Error occurred while posting plumgrid license" |
624 | "key. Please check plumgrid services.") |
625 | |
626 | @@ -184,7 +205,6 @@ |
627 | ''' |
628 | ensure_mtu() |
629 | CONFIGS.write_all() |
630 | - restart_pg('lxc') |
631 | |
632 | |
633 | @hooks.hook('stop') |
634 | |
635 | === modified file 'hooks/pg_dir_utils.py' |
636 | --- hooks/pg_dir_utils.py 2016-08-29 10:03:28 +0000 |
637 | +++ hooks/pg_dir_utils.py 2016-10-17 17:28:49 +0000 |
638 | @@ -7,7 +7,6 @@ |
639 | import time |
640 | import os |
641 | import json |
642 | -import shlex |
643 | from collections import OrderedDict |
644 | from socket import gethostname as get_unit_hostname |
645 | from copy import deepcopy |
646 | @@ -25,14 +24,16 @@ |
647 | get_bridges, |
648 | get_bridge_nics, |
649 | is_ip, |
650 | + get_iface_addr, |
651 | + get_host_ip |
652 | ) |
653 | from charmhelpers.core.host import ( |
654 | service_start, |
655 | - service_restart, |
656 | service_stop, |
657 | service_running, |
658 | path_hash, |
659 | - set_nic_mtu |
660 | + set_nic_mtu, |
661 | + service_restart |
662 | ) |
663 | from charmhelpers.fetch import ( |
664 | apt_cache, |
665 | @@ -41,6 +42,11 @@ |
666 | from charmhelpers.contrib.openstack.utils import ( |
667 | os_release, |
668 | ) |
669 | +from pg_dir_context import ( |
670 | + _pg_dir_ips, |
671 | + _pg_edge_ips, |
672 | + _pg_gateway_ips |
673 | +) |
674 | |
675 | SOURCES_LIST = '/etc/apt/sources.list' |
676 | LXC_CONF = '/etc/libvirt/lxc.conf' |
677 | @@ -57,6 +63,14 @@ |
678 | AUTH_KEY_PATH = '%s/root/.ssh/authorized_keys' % PG_LXC_DATA_PATH |
679 | TEMP_LICENSE_FILE = '/tmp/license' |
680 | |
681 | +# Constant values for OpenStack releases as Canonical-Ubuntu |
682 | +# doesn't have any specific solution version associated |
683 | +OPENSTACK_RELEASE_VERS = { |
684 | + 'kilo': '10', |
685 | + 'liberty': '11', |
686 | + 'mitaka': '12' |
687 | +} |
688 | + |
689 | BASE_RESOURCE_MAP = OrderedDict([ |
690 | (PG_KA_CONF, { |
691 | 'services': ['plumgrid'], |
692 | @@ -105,6 +119,31 @@ |
693 | log('Unable to update /etc/apt/sources.list') |
694 | |
695 | |
696 | +def configure_analyst_opsvm(): |
697 | + ''' |
698 | + Configures Anaylyst for OPSVM |
699 | + ''' |
700 | + if not service_running('plumgrid'): |
701 | + restart_pg() |
702 | + NS_ENTER = ('/opt/local/bin/nsenter -t $(ps ho pid --ppid $(cat ' |
703 | + '/var/run/libvirt/lxc/plumgrid.pid)) -m -n -u -i -p ') |
704 | + sigmund_stop = NS_ENTER + '/usr/bin/service plumgrid-sigmund stop' |
705 | + sigmund_status = NS_ENTER \ |
706 | + + '/usr/bin/service plumgrid-sigmund status' |
707 | + sigmund_autoboot = NS_ENTER \ |
708 | + + '/usr/bin/sigmund-configure --ip {0} --start --autoboot' \ |
709 | + .format(config('opsvm-ip')) |
710 | + try: |
711 | + status = subprocess.check_output(sigmund_status, shell=True) |
712 | + if 'start/running' in status: |
713 | + if subprocess.call(sigmund_stop, shell=True): |
714 | + log('plumgrid-sigmund couldn\'t be stopped!') |
715 | + return |
716 | + subprocess.check_call(sigmund_autoboot, shell=True) |
717 | + except: |
718 | + log('plumgrid-sigmund couldn\'t be started!') |
719 | + |
720 | + |
721 | def determine_packages(): |
722 | ''' |
723 | Returns list of packages required by PLUMgrid director as specified |
724 | @@ -131,6 +170,36 @@ |
725 | return pkgs |
726 | |
727 | |
728 | +def disable_apparmor_libvirt(): |
729 | + ''' |
730 | + Disables Apparmor profile of libvirtd. |
731 | + ''' |
732 | + apt_install('apparmor-utils') |
733 | + apt_install('cgroup-bin') |
734 | + _exec_cmd(['sudo', 'aa-disable', '/usr/sbin/libvirtd'], |
735 | + error_msg='Error disabling AppArmor profile of libvirtd') |
736 | + disable_apparmor() |
737 | + service_restart('libvirt-bin') |
738 | + |
739 | + |
740 | +def disable_apparmor(): |
741 | + ''' |
742 | + Disables Apparmor security for lxc. |
743 | + ''' |
744 | + try: |
745 | + f = open(LXC_CONF, 'r') |
746 | + except IOError: |
747 | + log('Libvirt not installed yet') |
748 | + return 0 |
749 | + filedata = f.read() |
750 | + f.close() |
751 | + newdata = filedata.replace("security_driver = \"apparmor\"", |
752 | + "#security_driver = \"apparmor\"") |
753 | + f = open(LXC_CONF, 'w') |
754 | + f.write(newdata) |
755 | + f.close() |
756 | + |
757 | + |
758 | def register_configs(release=None): |
759 | ''' |
760 | Returns an object of the Openstack Tempating Class which contains the |
761 | @@ -161,31 +230,7 @@ |
762 | return {cfg: rscs['services'] for cfg, rscs in resource_map().iteritems()} |
763 | |
764 | |
765 | -def start_gateway(): |
766 | - ''' |
767 | - Brings up PE-gateway interface. Initial hack but will be solved when docker |
768 | - plumgrid-director package will be used. |
769 | - ''' |
770 | - count = 0 |
771 | - while (count < 7): |
772 | - cmd = 'ps aux | grep launch_metadata_helper' |
773 | - output = subprocess.check_output([cmd], shell=True) |
774 | - roots = 0 |
775 | - v = shlex.split(output) |
776 | - for i in v: |
777 | - if i == 'root': |
778 | - roots += 1 |
779 | - if roots < 3: |
780 | - stop_pg() |
781 | - time.sleep(3) |
782 | - service_start('plumgrid') |
783 | - else: |
784 | - break |
785 | - count += 1 |
786 | - time.sleep(20) |
787 | - |
788 | - |
789 | -def restart_pg(gateway=None): |
790 | +def restart_pg(): |
791 | ''' |
792 | Stops and Starts PLUMgrid service after flushing iptables. |
793 | ''' |
794 | @@ -203,8 +248,6 @@ |
795 | raise ValueError("plumgrid service couldn't be started") |
796 | else: |
797 | raise ValueError("libvirt-bin service couldn't be started") |
798 | - if gateway: |
799 | - start_gateway() |
800 | status_set('active', 'Unit is ready') |
801 | |
802 | |
803 | @@ -251,8 +294,14 @@ |
804 | ''' |
805 | mgmt_interface = config('mgmt-interface') |
806 | if not mgmt_interface: |
807 | - return get_iface_from_addr(unit_get('private-address')) |
808 | - elif mgmt_interface and interface_exists(mgmt_interface): |
809 | + try: |
810 | + return get_iface_from_addr(unit_get('private-address')) |
811 | + except: |
812 | + for bridge_interface in get_bridges(): |
813 | + if (get_host_ip(unit_get('private-address')) |
814 | + in get_iface_addr(bridge_interface)): |
815 | + return bridge_interface |
816 | + elif interface_exists(mgmt_interface): |
817 | return mgmt_interface |
818 | else: |
819 | log('Provided managment interface %s does not exist' |
820 | @@ -317,36 +366,6 @@ |
821 | set_nic_mtu(fabric_interface, interface_mtu) |
822 | |
823 | |
824 | -def disable_apparmor_libvirt(): |
825 | - ''' |
826 | - Disables Apparmor profile of libvirtd. |
827 | - ''' |
828 | - apt_install('apparmor-utils') |
829 | - apt_install('cgroup-bin') |
830 | - _exec_cmd(['sudo', 'aa-disable', '/usr/sbin/libvirtd'], |
831 | - error_msg='Error disabling AppArmor profile of libvirtd') |
832 | - disable_apparmor() |
833 | - service_restart('libvirt-bin') |
834 | - |
835 | - |
836 | -def disable_apparmor(): |
837 | - ''' |
838 | - Disables Apparmor security for lxc. |
839 | - ''' |
840 | - try: |
841 | - f = open(LXC_CONF, 'r') |
842 | - except IOError: |
843 | - log('Libvirt not installed yet') |
844 | - return 0 |
845 | - filedata = f.read() |
846 | - f.close() |
847 | - newdata = filedata.replace("security_driver = \"apparmor\"", |
848 | - "#security_driver = \"apparmor\"") |
849 | - f = open(LXC_CONF, 'w') |
850 | - f.write(newdata) |
851 | - f.close() |
852 | - |
853 | - |
854 | def _exec_cmd(cmd=None, error_msg='Command exited with ERRORs', fatal=False): |
855 | ''' |
856 | Function to execute any bash command on the node. |
857 | @@ -431,6 +450,160 @@ |
858 | return 1 |
859 | |
860 | |
861 | +def sapi_post_ips(): |
862 | + """ |
863 | + Posts PLUMgrid nodes IPs to solutions api server. |
864 | + """ |
865 | + if not config('enable-sapi'): |
866 | + log('Solutions API support is disabled!') |
867 | + return 1 |
868 | + pg_edge_ips = _pg_edge_ips() |
869 | + pg_dir_ips = _pg_dir_ips() |
870 | + pg_gateway_ips = _pg_gateway_ips() |
871 | + pg_dir_ips.append(get_host_ip(unit_get('private-address'))) |
872 | + pg_edge_ips = '"edge_ips"' + ':' \ |
873 | + + '"{}"'.format(','.join(str(i) for i in pg_edge_ips)) |
874 | + pg_dir_ips = '"director_ips"' + ':' \ |
875 | + + '"{}"'.format(','.join(str(i) for i in pg_dir_ips)) |
876 | + pg_gateway_ips = '"gateway_ips"' + ':' \ |
877 | + + '"{}"'.format(','.join(str(i) for i in pg_gateway_ips)) |
878 | + opsvm_ip = '"opsvm_ip"' + ':' + '"{}"'.format(config('opsvm-ip')) |
879 | + virtual_ip = '"virtual_ip"' + ':' \ |
880 | + + '"{}"'.format(config('plumgrid-virtual-ip')) |
881 | + JSON_IPS = ','.join([pg_dir_ips, pg_edge_ips, pg_gateway_ips, |
882 | + opsvm_ip, virtual_ip]) |
883 | + status = ( |
884 | + 'curl -H \'Content-Type: application/json\' -X ' |
885 | + 'PUT -d \'{{{0}}}\' http://{1}' + ':' + '{2}/v1/zones/{3}/allIps' |
886 | + ).format(JSON_IPS, config('lcm-ip'), config('sapi-port'), |
887 | + config('sapi-zone')) |
888 | + POST_ZONE_IPs = _exec_cmd_output( |
889 | + status, |
890 | + 'Posting Zone IPs to Solutions API server failed!') |
891 | + if POST_ZONE_IPs: |
892 | + if 'success' in POST_ZONE_IPs: |
893 | + log('Successfully posted Zone IPs to Solutions API server!') |
894 | + log(POST_ZONE_IPs) |
895 | + |
896 | + |
897 | +def _exec_cmd_output(cmd=None, error_msg='Command exited with ERRORs', |
898 | + fatal=False): |
899 | + ''' |
900 | + Function to get output from bash command executed on the node. |
901 | + ''' |
902 | + if cmd is None: |
903 | + log("No command specified") |
904 | + else: |
905 | + if fatal: |
906 | + return subprocess.check_output(cmd, shell=True) |
907 | + else: |
908 | + try: |
909 | + return subprocess.check_output(cmd, shell=True) |
910 | + except subprocess.CalledProcessError: |
911 | + log(error_msg) |
912 | + return None |
913 | + |
914 | + |
915 | +def sapi_post_license(): |
916 | + ''' |
917 | + Posts PLUMgrid License to solutions api server |
918 | + ''' |
919 | + if not config('enable-sapi'): |
920 | + log('Solutions API support is disabled!') |
921 | + return 1 |
922 | + username = '"user_name":' + '"{}"'.format(config('plumgrid-username')) |
923 | + password = '"password":' + '"{}"'.format(config('plumgrid-password')) |
924 | + license = '"license":' + '"{}"'.format(config('plumgrid-license-key')) |
925 | + JSON_LICENSE = ','.join([username, password, license]) |
926 | + status = ( |
927 | + 'curl -H \'Content-Type: application/json\' -X ' |
928 | + 'PUT -d \'{{{0}}}\' http://{1}' + ':' + '{2}/v1/zones/{3}/pgLicense' |
929 | + ).format(JSON_LICENSE, config('lcm-ip'), config('sapi-port'), |
930 | + config('sapi-zone')) |
931 | + POST_LICENSE = _exec_cmd_output( |
932 | + status, |
933 | + 'Posting PLUMgrid License to Solutions API server failed!') |
934 | + if POST_LICENSE: |
935 | + if 'success' in POST_LICENSE: |
936 | + log('Successfully posted license file for zone "{}"!' |
937 | + .format(config('sapi-zone'))) |
938 | + log(POST_LICENSE) |
939 | + |
940 | + |
941 | +def sapi_post_zone_info(): |
942 | + ''' |
943 | + Posts zone information to solutions api server |
944 | + ''' |
945 | + if not config('enable-sapi'): |
946 | + log('Solutions API support is disabled!') |
947 | + return 1 |
948 | + sol_name = '"solution_name":"Ubuntu OpenStack"' |
949 | + release = config('openstack-release') |
950 | + for key, value in OPENSTACK_RELEASE_VERS.iteritems(): |
951 | + if release == value: |
952 | + sol_version = value |
953 | + else: |
954 | + sol_version = 10 |
955 | + sol_version = '"solution_version":"{}"'.format(sol_version) |
956 | + pg_ons_version = _exec_cmd_output( |
957 | + 'dpkg -l | grep plumgrid | awk \'{print $3}\' | ' |
958 | + 'sed \'s/-/./\' | cut -f1 -d"-"', |
959 | + 'Unable to obtain PG ONS version' |
960 | + ).replace('\n', '') |
961 | + pg_ons_version = \ |
962 | + '"pg_ons_version":"{}"'.format(pg_ons_version) |
963 | + hypervisor = '"hypervisor":"Ubuntu"' |
964 | + hypervisor_version = \ |
965 | + _exec_cmd_output('lsb_release -r | awk \'{print $2}\'', |
966 | + 'Unable to obtain solution version' |
967 | + ).replace('\n', '') |
968 | + hypervisor_version = '"hypervisor_version":"{}"' \ |
969 | + .format(hypervisor_version) |
970 | + kernel_version = _exec_cmd_output( |
971 | + 'uname -r', |
972 | + 'Unable to obtain kernal version').replace('\n', '') |
973 | + kernel_version = \ |
974 | + '"kernel_version":"{}"'.format(kernel_version) |
975 | + cloudapex_path = '/var/lib/libvirt/filesystems/plumgrid/' \ |
976 | + 'opt/pg/web/cloudApex/modules/appCloudApex' \ |
977 | + '/appCloudApex.js' |
978 | + if os.path.isfile(cloudapex_path): |
979 | + pg_cloudapex_version = 'cat ' \ |
980 | + + '{}'.format(cloudapex_path) \ |
981 | + + ' | grep -i appversion | awk \'{print $2}\'' |
982 | + pg_cloudapex_version = \ |
983 | + _exec_cmd_output(pg_cloudapex_version, |
984 | + 'Unable to retrieve CloudApex version' |
985 | + ).replace('\n', '') |
986 | + else: |
987 | + log('CloudApex not installed!') |
988 | + pg_cloudapex_version = '' |
989 | + pg_cloudapex_version = \ |
990 | + '"pg_cloudapex_version":"{}"'.format(pg_cloudapex_version) |
991 | + JSON_ZONE_INFO = ','.join([ |
992 | + sol_name, |
993 | + sol_version, |
994 | + pg_ons_version, |
995 | + hypervisor, |
996 | + hypervisor_version, |
997 | + kernel_version, |
998 | + pg_cloudapex_version, |
999 | + ]) |
1000 | + status = ( |
1001 | + 'curl -H \'Content-Type: application/json\' -X ' |
1002 | + 'PUT -d \'{{{0}}}\' http://{1}:{2}/v1/zones/{3}/zoneinfo' |
1003 | + ).format(JSON_ZONE_INFO, config('lcm-ip'), config('sapi-port'), |
1004 | + config('sapi-zone')) |
1005 | + POST_ZONE_INFO = _exec_cmd_output( |
1006 | + status, |
1007 | + 'Posting Zone Information to Solutions API server failed!') |
1008 | + if POST_ZONE_INFO: |
1009 | + if 'success' in POST_ZONE_INFO: |
1010 | + log('Successfully posted Zone information to Solutions API' |
1011 | + ' server!') |
1012 | + log(POST_ZONE_INFO) |
1013 | + |
1014 | + |
1015 | def load_iptables(): |
1016 | ''' |
1017 | Loads iptables rules to allow all PLUMgrid communication. |
1018 | |
1019 | === added symlink 'hooks/plumgrid-relation-changed' |
1020 | === target is u'pg_dir_hooks.py' |
1021 | === added symlink 'hooks/plumgrid-relation-departed' |
1022 | === target is u'pg_dir_hooks.py' |
1023 | === modified file 'metadata.yaml' |
1024 | --- metadata.yaml 2016-05-04 06:47:43 +0000 |
1025 | +++ metadata.yaml 2016-10-17 17:28:49 +0000 |
1026 | @@ -7,6 +7,9 @@ |
1027 | The configuration of the virtual network infrastructure for tenants is |
1028 | done through the PLUMgrid Director. The PLUMgrid Director is typically |
1029 | co-located on the OpenStack controller nodes. |
1030 | +series: |
1031 | + - xenial |
1032 | + - trusty |
1033 | tags: |
1034 | - openstack |
1035 | requires: |
1036 | |
1037 | === modified file 'unit_tests/test_pg_dir_hooks.py' |
1038 | --- unit_tests/test_pg_dir_hooks.py 2016-05-01 02:16:59 +0000 |
1039 | +++ unit_tests/test_pg_dir_hooks.py 2016-10-17 17:28:49 +0000 |
1040 | @@ -32,7 +32,10 @@ |
1041 | 'post_pg_license', |
1042 | 'config', |
1043 | 'load_iptables', |
1044 | - 'status_set' |
1045 | + 'status_set', |
1046 | + 'configure_analyst_opsvm', |
1047 | + 'sapi_post_zone_info', |
1048 | + 'disable_apparmor_libvirt' |
1049 | ] |
1050 | NEUTRON_CONF_DIR = "/etc/neutron" |
1051 |