Merge lp:~junaidali/charms/trusty/neutron-api-plumgrid/oil-networking-pg into lp:charms/trusty/neutron-api-plumgrid

Proposed by Junaid Ali
Status: Merged
Merged at revision: 9
Proposed branch: lp:~junaidali/charms/trusty/neutron-api-plumgrid/oil-networking-pg
Merge into: lp:charms/trusty/neutron-api-plumgrid
Diff against target: 868 lines (+375/-136)
13 files modified
config.yaml (+8/-0)
hooks/charmhelpers/contrib/network/ip.py (+22/-5)
hooks/charmhelpers/contrib/openstack/context.py (+5/-83)
hooks/charmhelpers/contrib/openstack/exceptions.py (+6/-0)
hooks/charmhelpers/contrib/openstack/utils.py (+184/-21)
hooks/charmhelpers/contrib/storage/linux/ceph.py (+41/-0)
hooks/charmhelpers/core/host.py (+70/-23)
hooks/charmhelpers/fetch/__init__.py (+8/-0)
hooks/neutron_plumgrid_context.py (+2/-0)
hooks/neutron_plumgrid_hooks.py (+3/-0)
hooks/neutron_plumgrid_utils.py (+20/-4)
templates/kilo/plumlib.ini (+2/-0)
unit_tests/test_neutron_plumgrid_plugin_context.py (+4/-0)
To merge this branch: bzr merge lp:~junaidali/charms/trusty/neutron-api-plumgrid/oil-networking-pg
Reviewer Review Type Date Requested Status
Review Queue (community) automated testing Needs Fixing
David Ames Pending
Review via email: mp+304248@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Review Queue (review-queue) wrote :

This item has failed automated testing! Results available here http://juju-ci.vapour.ws/job/charm-bundle-test-lxc/5470/

review: Needs Fixing (automated testing)
Revision history for this message
Review Queue (review-queue) wrote :

This item has failed automated testing! Results available here http://juju-ci.vapour.ws/job/charm-bundle-test-aws/5571/

review: Needs Fixing (automated testing)

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 2016-05-17 07:37:13 +0000
3+++ config.yaml 2016-08-29 12:04:53 +0000
4@@ -49,3 +49,11 @@
5 default: None
6 description: |
7 Proxy address to install python modules behind a proxy
8+ user-domain-name:
9+ type: string
10+ default: Default
11+ description: Keystone user domain name
12+ project-domain-name:
13+ type: string
14+ default: Default
15+ description: Keystone project domain name
16
17=== modified file 'hooks/charmhelpers/contrib/network/ip.py'
18--- hooks/charmhelpers/contrib/network/ip.py 2016-04-22 04:35:32 +0000
19+++ hooks/charmhelpers/contrib/network/ip.py 2016-08-29 12:04:53 +0000
20@@ -214,7 +214,16 @@
21
22 def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False,
23 fatal=True, exc_list=None):
24- """Return the assigned IP address for a given interface, if any."""
25+ """Return the assigned IP address for a given interface, if any.
26+
27+ :param iface: network interface on which address(es) are expected to
28+ be found.
29+ :param inet_type: inet address family
30+ :param inc_aliases: include alias interfaces in search
31+ :param fatal: if True, raise exception if address not found
32+ :param exc_list: list of addresses to ignore
33+ :return: list of ip addresses
34+ """
35 # Extract nic if passed /dev/ethX
36 if '/' in iface:
37 iface = iface.split('/')[-1]
38@@ -315,6 +324,14 @@
39 We currently only support scope global IPv6 addresses i.e. non-temporary
40 addresses. If no global IPv6 address is found, return the first one found
41 in the ipv6 address list.
42+
43+ :param iface: network interface on which ipv6 address(es) are expected to
44+ be found.
45+ :param inc_aliases: include alias interfaces in search
46+ :param fatal: if True, raise exception if address not found
47+ :param exc_list: list of addresses to ignore
48+ :param dynamic_only: only recognise dynamic addresses
49+ :return: list of ipv6 addresses
50 """
51 addresses = get_iface_addr(iface=iface, inet_type='AF_INET6',
52 inc_aliases=inc_aliases, fatal=fatal,
53@@ -336,7 +353,7 @@
54 cmd = ['ip', 'addr', 'show', iface]
55 out = subprocess.check_output(cmd).decode('UTF-8')
56 if dynamic_only:
57- key = re.compile("inet6 (.+)/[0-9]+ scope global dynamic.*")
58+ key = re.compile("inet6 (.+)/[0-9]+ scope global.* dynamic.*")
59 else:
60 key = re.compile("inet6 (.+)/[0-9]+ scope global.*")
61
62@@ -388,10 +405,10 @@
63 Returns True if address is a valid IP address.
64 """
65 try:
66- # Test to see if already an IPv4 address
67- socket.inet_aton(address)
68+ # Test to see if already an IPv4/IPv6 address
69+ address = netaddr.IPAddress(address)
70 return True
71- except socket.error:
72+ except netaddr.AddrFormatError:
73 return False
74
75
76
77=== modified file 'hooks/charmhelpers/contrib/openstack/context.py'
78--- hooks/charmhelpers/contrib/openstack/context.py 2016-04-22 04:35:32 +0000
79+++ hooks/charmhelpers/contrib/openstack/context.py 2016-08-29 12:04:53 +0000
80@@ -23,7 +23,6 @@
81 from subprocess import check_call, CalledProcessError
82
83 import six
84-import yaml
85
86 from charmhelpers.fetch import (
87 apt_install,
88@@ -50,6 +49,7 @@
89
90 from charmhelpers.core.sysctl import create as sysctl_create
91 from charmhelpers.core.strutils import bool_from_string
92+from charmhelpers.contrib.openstack.exceptions import OSContextError
93
94 from charmhelpers.core.host import (
95 get_bond_master,
96@@ -88,7 +88,10 @@
97 is_address_in_network,
98 is_bridge_member,
99 )
100-from charmhelpers.contrib.openstack.utils import get_host_ip
101+from charmhelpers.contrib.openstack.utils import (
102+ config_flags_parser,
103+ get_host_ip,
104+)
105 from charmhelpers.core.unitdata import kv
106
107 try:
108@@ -101,10 +104,6 @@
109 ADDRESS_TYPES = ['admin', 'internal', 'public']
110
111
112-class OSContextError(Exception):
113- pass
114-
115-
116 def ensure_packages(packages):
117 """Install but do not upgrade required plugin packages."""
118 required = filter_installed_packages(packages)
119@@ -125,83 +124,6 @@
120 return True
121
122
123-def config_flags_parser(config_flags):
124- """Parses config flags string into dict.
125-
126- This parsing method supports a few different formats for the config
127- flag values to be parsed:
128-
129- 1. A string in the simple format of key=value pairs, with the possibility
130- of specifying multiple key value pairs within the same string. For
131- example, a string in the format of 'key1=value1, key2=value2' will
132- return a dict of:
133-
134- {'key1': 'value1',
135- 'key2': 'value2'}.
136-
137- 2. A string in the above format, but supporting a comma-delimited list
138- of values for the same key. For example, a string in the format of
139- 'key1=value1, key2=value3,value4,value5' will return a dict of:
140-
141- {'key1', 'value1',
142- 'key2', 'value2,value3,value4'}
143-
144- 3. A string containing a colon character (:) prior to an equal
145- character (=) will be treated as yaml and parsed as such. This can be
146- used to specify more complex key value pairs. For example,
147- a string in the format of 'key1: subkey1=value1, subkey2=value2' will
148- return a dict of:
149-
150- {'key1', 'subkey1=value1, subkey2=value2'}
151-
152- The provided config_flags string may be a list of comma-separated values
153- which themselves may be comma-separated list of values.
154- """
155- # If we find a colon before an equals sign then treat it as yaml.
156- # Note: limit it to finding the colon first since this indicates assignment
157- # for inline yaml.
158- colon = config_flags.find(':')
159- equals = config_flags.find('=')
160- if colon > 0:
161- if colon < equals or equals < 0:
162- return yaml.safe_load(config_flags)
163-
164- if config_flags.find('==') >= 0:
165- log("config_flags is not in expected format (key=value)", level=ERROR)
166- raise OSContextError
167-
168- # strip the following from each value.
169- post_strippers = ' ,'
170- # we strip any leading/trailing '=' or ' ' from the string then
171- # split on '='.
172- split = config_flags.strip(' =').split('=')
173- limit = len(split)
174- flags = {}
175- for i in range(0, limit - 1):
176- current = split[i]
177- next = split[i + 1]
178- vindex = next.rfind(',')
179- if (i == limit - 2) or (vindex < 0):
180- value = next
181- else:
182- value = next[:vindex]
183-
184- if i == 0:
185- key = current
186- else:
187- # if this not the first entry, expect an embedded key.
188- index = current.rfind(',')
189- if index < 0:
190- log("Invalid config value(s) at index %s" % (i), level=ERROR)
191- raise OSContextError
192- key = current[index + 1:]
193-
194- # Add to collection.
195- flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
196-
197- return flags
198-
199-
200 class OSContextGenerator(object):
201 """Base class for all context generators."""
202 interfaces = []
203
204=== added file 'hooks/charmhelpers/contrib/openstack/exceptions.py'
205--- hooks/charmhelpers/contrib/openstack/exceptions.py 1970-01-01 00:00:00 +0000
206+++ hooks/charmhelpers/contrib/openstack/exceptions.py 2016-08-29 12:04:53 +0000
207@@ -0,0 +1,6 @@
208+class OSContextError(Exception):
209+ """Raised when an error occurs during context generation.
210+
211+ This exception is principally used in contrib.openstack.context
212+ """
213+ pass
214
215=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
216--- hooks/charmhelpers/contrib/openstack/utils.py 2016-04-22 04:35:32 +0000
217+++ hooks/charmhelpers/contrib/openstack/utils.py 2016-08-29 12:04:53 +0000
218@@ -25,6 +25,7 @@
219 import re
220 import itertools
221 import functools
222+import shutil
223
224 import six
225 import tempfile
226@@ -46,6 +47,7 @@
227 charm_dir,
228 DEBUG,
229 INFO,
230+ ERROR,
231 related_units,
232 relation_ids,
233 relation_set,
234@@ -82,6 +84,7 @@
235 from charmhelpers.fetch import apt_install, apt_cache, install_remote
236 from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
237 from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
238+from charmhelpers.contrib.openstack.exceptions import OSContextError
239
240 CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
241 CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
242@@ -100,6 +103,8 @@
243 ('vivid', 'kilo'),
244 ('wily', 'liberty'),
245 ('xenial', 'mitaka'),
246+ ('yakkety', 'newton'),
247+ ('zebra', 'ocata'), # TODO: upload with real Z name
248 ])
249
250
251@@ -114,6 +119,8 @@
252 ('2015.1', 'kilo'),
253 ('2015.2', 'liberty'),
254 ('2016.1', 'mitaka'),
255+ ('2016.2', 'newton'),
256+ ('2017.1', 'ocata'),
257 ])
258
259 # The ugly duckling - must list releases oldest to newest
260@@ -138,46 +145,65 @@
261 ['2.3.0', '2.4.0', '2.5.0']),
262 ('mitaka',
263 ['2.5.0', '2.6.0', '2.7.0']),
264+ ('newton',
265+ ['2.8.0']),
266 ])
267
268 # >= Liberty version->codename mapping
269 PACKAGE_CODENAMES = {
270 'nova-common': OrderedDict([
271- ('12.0', 'liberty'),
272- ('13.0', 'mitaka'),
273+ ('12', 'liberty'),
274+ ('13', 'mitaka'),
275+ ('14', 'newton'),
276+ ('15', 'ocata'),
277 ]),
278 'neutron-common': OrderedDict([
279- ('7.0', 'liberty'),
280- ('8.0', 'mitaka'),
281+ ('7', 'liberty'),
282+ ('8', 'mitaka'),
283+ ('9', 'newton'),
284+ ('10', 'ocata'),
285 ]),
286 'cinder-common': OrderedDict([
287- ('7.0', 'liberty'),
288- ('8.0', 'mitaka'),
289+ ('7', 'liberty'),
290+ ('8', 'mitaka'),
291+ ('9', 'newton'),
292+ ('10', 'ocata'),
293 ]),
294 'keystone': OrderedDict([
295- ('8.0', 'liberty'),
296- ('8.1', 'liberty'),
297- ('9.0', 'mitaka'),
298+ ('8', 'liberty'),
299+ ('9', 'mitaka'),
300+ ('10', 'newton'),
301+ ('11', 'ocata'),
302 ]),
303 'horizon-common': OrderedDict([
304- ('8.0', 'liberty'),
305- ('9.0', 'mitaka'),
306+ ('8', 'liberty'),
307+ ('9', 'mitaka'),
308+ ('10', 'newton'),
309+ ('11', 'ocata'),
310 ]),
311 'ceilometer-common': OrderedDict([
312- ('5.0', 'liberty'),
313- ('6.0', 'mitaka'),
314+ ('5', 'liberty'),
315+ ('6', 'mitaka'),
316+ ('7', 'newton'),
317+ ('8', 'ocata'),
318 ]),
319 'heat-common': OrderedDict([
320- ('5.0', 'liberty'),
321- ('6.0', 'mitaka'),
322+ ('5', 'liberty'),
323+ ('6', 'mitaka'),
324+ ('7', 'newton'),
325+ ('8', 'ocata'),
326 ]),
327 'glance-common': OrderedDict([
328- ('11.0', 'liberty'),
329- ('12.0', 'mitaka'),
330+ ('11', 'liberty'),
331+ ('12', 'mitaka'),
332+ ('13', 'newton'),
333+ ('14', 'ocata'),
334 ]),
335 'openstack-dashboard': OrderedDict([
336- ('8.0', 'liberty'),
337- ('9.0', 'mitaka'),
338+ ('8', 'liberty'),
339+ ('9', 'mitaka'),
340+ ('10', 'newton'),
341+ ('11', 'ocata'),
342 ]),
343 }
344
345@@ -253,6 +279,7 @@
346 def get_swift_codename(version):
347 '''Determine OpenStack codename that corresponds to swift version.'''
348 codenames = [k for k, v in six.iteritems(SWIFT_CODENAMES) if version in v]
349+
350 if len(codenames) > 1:
351 # If more than one release codename contains this version we determine
352 # the actual codename based on the highest available install source.
353@@ -264,6 +291,16 @@
354 return codename
355 elif len(codenames) == 1:
356 return codenames[0]
357+
358+ # NOTE: fallback - attempt to match with just major.minor version
359+ match = re.match('^(\d+)\.(\d+)', version)
360+ if match:
361+ major_minor_version = match.group(0)
362+ for codename, versions in six.iteritems(SWIFT_CODENAMES):
363+ for release_version in versions:
364+ if release_version.startswith(major_minor_version):
365+ return codename
366+
367 return None
368
369
370@@ -302,10 +339,13 @@
371 if match:
372 vers = match.group(0)
373
374+ # Generate a major version number for newer semantic
375+ # versions of openstack projects
376+ major_vers = vers.split('.')[0]
377 # >= Liberty independent project versions
378 if (package in PACKAGE_CODENAMES and
379- vers in PACKAGE_CODENAMES[package]):
380- return PACKAGE_CODENAMES[package][vers]
381+ major_vers in PACKAGE_CODENAMES[package]):
382+ return PACKAGE_CODENAMES[package][major_vers]
383 else:
384 # < Liberty co-ordinated project versions
385 try:
386@@ -465,6 +505,9 @@
387 'mitaka': 'trusty-updates/mitaka',
388 'mitaka/updates': 'trusty-updates/mitaka',
389 'mitaka/proposed': 'trusty-proposed/mitaka',
390+ 'newton': 'xenial-updates/newton',
391+ 'newton/updates': 'xenial-updates/newton',
392+ 'newton/proposed': 'xenial-proposed/newton',
393 }
394
395 try:
396@@ -857,6 +900,47 @@
397 return None
398
399
400+def git_generate_systemd_init_files(templates_dir):
401+ """
402+ Generate systemd init files.
403+
404+ Generates and installs systemd init units and script files based on the
405+ *.init.in files contained in the templates_dir directory.
406+
407+ This code is based on the openstack-pkg-tools package and its init
408+ script generation, which is used by the OpenStack packages.
409+ """
410+ for f in os.listdir(templates_dir):
411+ if f.endswith(".init.in"):
412+ init_in_file = f
413+ init_file = f[:-8]
414+ service_file = "{}.service".format(init_file)
415+
416+ init_in_source = os.path.join(templates_dir, init_in_file)
417+ init_source = os.path.join(templates_dir, init_file)
418+ service_source = os.path.join(templates_dir, service_file)
419+
420+ init_dest = os.path.join('/etc/init.d', init_file)
421+ service_dest = os.path.join('/lib/systemd/system', service_file)
422+
423+ shutil.copyfile(init_in_source, init_source)
424+ with open(init_source, 'a') as outfile:
425+ template = '/usr/share/openstack-pkg-tools/init-script-template'
426+ with open(template) as infile:
427+ outfile.write('\n\n{}'.format(infile.read()))
428+
429+ cmd = ['pkgos-gen-systemd-unit', init_in_source]
430+ subprocess.check_call(cmd)
431+
432+ if os.path.exists(init_dest):
433+ os.remove(init_dest)
434+ if os.path.exists(service_dest):
435+ os.remove(service_dest)
436+ shutil.move(init_source, init_dest)
437+ shutil.move(service_source, service_dest)
438+ os.chmod(init_dest, 0o755)
439+
440+
441 def os_workload_status(configs, required_interfaces, charm_func=None):
442 """
443 Decorator to set workload status based on complete contexts
444@@ -1573,3 +1657,82 @@
445 restart_functions)
446 return wrapped_f
447 return wrap
448+
449+
450+def config_flags_parser(config_flags):
451+ """Parses config flags string into dict.
452+
453+ This parsing method supports a few different formats for the config
454+ flag values to be parsed:
455+
456+ 1. A string in the simple format of key=value pairs, with the possibility
457+ of specifying multiple key value pairs within the same string. For
458+ example, a string in the format of 'key1=value1, key2=value2' will
459+ return a dict of:
460+
461+ {'key1': 'value1',
462+ 'key2': 'value2'}.
463+
464+ 2. A string in the above format, but supporting a comma-delimited list
465+ of values for the same key. For example, a string in the format of
466+ 'key1=value1, key2=value3,value4,value5' will return a dict of:
467+
468+ {'key1', 'value1',
469+ 'key2', 'value2,value3,value4'}
470+
471+ 3. A string containing a colon character (:) prior to an equal
472+ character (=) will be treated as yaml and parsed as such. This can be
473+ used to specify more complex key value pairs. For example,
474+ a string in the format of 'key1: subkey1=value1, subkey2=value2' will
475+ return a dict of:
476+
477+ {'key1', 'subkey1=value1, subkey2=value2'}
478+
479+ The provided config_flags string may be a list of comma-separated values
480+ which themselves may be comma-separated list of values.
481+ """
482+ # If we find a colon before an equals sign then treat it as yaml.
483+ # Note: limit it to finding the colon first since this indicates assignment
484+ # for inline yaml.
485+ colon = config_flags.find(':')
486+ equals = config_flags.find('=')
487+ if colon > 0:
488+ if colon < equals or equals < 0:
489+ return yaml.safe_load(config_flags)
490+
491+ if config_flags.find('==') >= 0:
492+ juju_log("config_flags is not in expected format (key=value)",
493+ level=ERROR)
494+ raise OSContextError
495+
496+ # strip the following from each value.
497+ post_strippers = ' ,'
498+ # we strip any leading/trailing '=' or ' ' from the string then
499+ # split on '='.
500+ split = config_flags.strip(' =').split('=')
501+ limit = len(split)
502+ flags = {}
503+ for i in range(0, limit - 1):
504+ current = split[i]
505+ next = split[i + 1]
506+ vindex = next.rfind(',')
507+ if (i == limit - 2) or (vindex < 0):
508+ value = next
509+ else:
510+ value = next[:vindex]
511+
512+ if i == 0:
513+ key = current
514+ else:
515+ # if this not the first entry, expect an embedded key.
516+ index = current.rfind(',')
517+ if index < 0:
518+ juju_log("Invalid config value(s) at index %s" % (i),
519+ level=ERROR)
520+ raise OSContextError
521+ key = current[index + 1:]
522+
523+ # Add to collection.
524+ flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
525+
526+ return flags
527
528=== modified file 'hooks/charmhelpers/contrib/storage/linux/ceph.py'
529--- hooks/charmhelpers/contrib/storage/linux/ceph.py 2016-04-22 04:35:32 +0000
530+++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2016-08-29 12:04:53 +0000
531@@ -40,6 +40,7 @@
532 CalledProcessError,
533 )
534 from charmhelpers.core.hookenv import (
535+ config,
536 local_unit,
537 relation_get,
538 relation_ids,
539@@ -64,6 +65,7 @@
540 )
541
542 from charmhelpers.core.kernel import modprobe
543+from charmhelpers.contrib.openstack.utils import config_flags_parser
544
545 KEYRING = '/etc/ceph/ceph.client.{}.keyring'
546 KEYFILE = '/etc/ceph/ceph.client.{}.key'
547@@ -1204,3 +1206,42 @@
548 for rid in relation_ids(relation):
549 log('Sending request {}'.format(request.request_id), level=DEBUG)
550 relation_set(relation_id=rid, broker_req=request.request)
551+
552+
553+class CephConfContext(object):
554+ """Ceph config (ceph.conf) context.
555+
556+ Supports user-provided Ceph configuration settings. Use can provide a
557+ dictionary as the value for the config-flags charm option containing
558+ Ceph configuration settings keyede by their section in ceph.conf.
559+ """
560+ def __init__(self, permitted_sections=None):
561+ self.permitted_sections = permitted_sections or []
562+
563+ def __call__(self):
564+ conf = config('config-flags')
565+ if not conf:
566+ return {}
567+
568+ conf = config_flags_parser(conf)
569+ if type(conf) != dict:
570+ log("Provided config-flags is not a dictionary - ignoring",
571+ level=WARNING)
572+ return {}
573+
574+ permitted = self.permitted_sections
575+ if permitted:
576+ diff = set(conf.keys()).difference(set(permitted))
577+ if diff:
578+ log("Config-flags contains invalid keys '%s' - they will be "
579+ "ignored" % (', '.join(diff)), level=WARNING)
580+
581+ ceph_conf = {}
582+ for key in conf:
583+ if permitted and key not in permitted:
584+ log("Ignoring key '%s'" % key, level=WARNING)
585+ continue
586+
587+ ceph_conf[key] = conf[key]
588+
589+ return ceph_conf
590
591=== modified file 'hooks/charmhelpers/core/host.py'
592--- hooks/charmhelpers/core/host.py 2016-04-22 04:35:32 +0000
593+++ hooks/charmhelpers/core/host.py 2016-08-29 12:04:53 +0000
594@@ -128,11 +128,8 @@
595 return subprocess.call(cmd) == 0
596
597
598-def systemv_services_running():
599- output = subprocess.check_output(
600- ['service', '--status-all'],
601- stderr=subprocess.STDOUT).decode('UTF-8')
602- return [row.split()[-1] for row in output.split('\n') if '[ + ]' in row]
603+_UPSTART_CONF = "/etc/init/{}.conf"
604+_INIT_D_CONF = "/etc/init.d/{}"
605
606
607 def service_running(service_name):
608@@ -140,22 +137,22 @@
609 if init_is_systemd():
610 return service('is-active', service_name)
611 else:
612- try:
613- output = subprocess.check_output(
614- ['service', service_name, 'status'],
615- stderr=subprocess.STDOUT).decode('UTF-8')
616- except subprocess.CalledProcessError:
617- return False
618- else:
619- # This works for upstart scripts where the 'service' command
620- # returns a consistent string to represent running 'start/running'
621- if ("start/running" in output or "is running" in output or
622- "up and running" in output):
623- return True
624+ if os.path.exists(_UPSTART_CONF.format(service_name)):
625+ try:
626+ output = subprocess.check_output(
627+ ['status', service_name],
628+ stderr=subprocess.STDOUT).decode('UTF-8')
629+ except subprocess.CalledProcessError:
630+ return False
631+ else:
632+ # This works for upstart scripts where the 'service' command
633+ # returns a consistent string to represent running 'start/running'
634+ if "start/running" in output:
635+ return True
636+ elif os.path.exists(_INIT_D_CONF.format(service_name)):
637 # Check System V scripts init script return codes
638- if service_name in systemv_services_running():
639- return True
640- return False
641+ return service('status', service_name)
642+ return False
643
644
645 def service_available(service_name):
646@@ -179,7 +176,7 @@
647
648
649 def adduser(username, password=None, shell='/bin/bash', system_user=False,
650- primary_group=None, secondary_groups=None):
651+ primary_group=None, secondary_groups=None, uid=None):
652 """Add a user to the system.
653
654 Will log but otherwise succeed if the user already exists.
655@@ -190,15 +187,21 @@
656 :param bool system_user: Whether to create a login or system user
657 :param str primary_group: Primary group for user; defaults to username
658 :param list secondary_groups: Optional list of additional groups
659+ :param int uid: UID for user being created
660
661 :returns: The password database entry struct, as returned by `pwd.getpwnam`
662 """
663 try:
664 user_info = pwd.getpwnam(username)
665 log('user {0} already exists!'.format(username))
666+ if uid:
667+ user_info = pwd.getpwuid(int(uid))
668+ log('user with uid {0} already exists!'.format(uid))
669 except KeyError:
670 log('creating user {0}'.format(username))
671 cmd = ['useradd']
672+ if uid:
673+ cmd.extend(['--uid', str(uid)])
674 if system_user or password is None:
675 cmd.append('--system')
676 else:
677@@ -233,14 +236,58 @@
678 return user_exists
679
680
681-def add_group(group_name, system_group=False):
682- """Add a group to the system"""
683+def uid_exists(uid):
684+ """Check if a uid exists"""
685+ try:
686+ pwd.getpwuid(uid)
687+ uid_exists = True
688+ except KeyError:
689+ uid_exists = False
690+ return uid_exists
691+
692+
693+def group_exists(groupname):
694+ """Check if a group exists"""
695+ try:
696+ grp.getgrnam(groupname)
697+ group_exists = True
698+ except KeyError:
699+ group_exists = False
700+ return group_exists
701+
702+
703+def gid_exists(gid):
704+ """Check if a gid exists"""
705+ try:
706+ grp.getgrgid(gid)
707+ gid_exists = True
708+ except KeyError:
709+ gid_exists = False
710+ return gid_exists
711+
712+
713+def add_group(group_name, system_group=False, gid=None):
714+ """Add a group to the system
715+
716+ Will log but otherwise succeed if the group already exists.
717+
718+ :param str group_name: group to create
719+ :param bool system_group: Create system group
720+ :param int gid: GID for user being created
721+
722+ :returns: The password database entry struct, as returned by `grp.getgrnam`
723+ """
724 try:
725 group_info = grp.getgrnam(group_name)
726 log('group {0} already exists!'.format(group_name))
727+ if gid:
728+ group_info = grp.getgrgid(gid)
729+ log('group with gid {0} already exists!'.format(gid))
730 except KeyError:
731 log('creating group {0}'.format(group_name))
732 cmd = ['addgroup']
733+ if gid:
734+ cmd.extend(['--gid', str(gid)])
735 if system_group:
736 cmd.append('--system')
737 else:
738
739=== modified file 'hooks/charmhelpers/fetch/__init__.py'
740--- hooks/charmhelpers/fetch/__init__.py 2016-02-27 19:51:32 +0000
741+++ hooks/charmhelpers/fetch/__init__.py 2016-08-29 12:04:53 +0000
742@@ -106,6 +106,14 @@
743 'mitaka/proposed': 'trusty-proposed/mitaka',
744 'trusty-mitaka/proposed': 'trusty-proposed/mitaka',
745 'trusty-proposed/mitaka': 'trusty-proposed/mitaka',
746+ # Newton
747+ 'newton': 'xenial-updates/newton',
748+ 'xenial-newton': 'xenial-updates/newton',
749+ 'xenial-newton/updates': 'xenial-updates/newton',
750+ 'xenial-updates/newton': 'xenial-updates/newton',
751+ 'newton/proposed': 'xenial-proposed/newton',
752+ 'xenial-newton/proposed': 'xenial-proposed/newton',
753+ 'xenial-proposed/newton': 'xenial-proposed/newton',
754 }
755
756 # The order of this list is very important. Handlers should be listed in from
757
758=== modified file 'hooks/neutron_plumgrid_context.py'
759--- hooks/neutron_plumgrid_context.py 2016-05-17 18:03:56 +0000
760+++ hooks/neutron_plumgrid_context.py 2016-08-29 12:04:53 +0000
761@@ -118,6 +118,8 @@
762 pg_ctxt['pg_metadata_port'] = '8775'
763 pg_ctxt['metadata_mode'] = 'tunnel'
764 pg_ctxt['connector_type'] = config('connector-type')
765+ pg_ctxt['user_domain_name'] = config('user-domain-name')
766+ pg_ctxt['project_domain_name'] = config('project-domain-name')
767 if enable_metadata:
768 plumgrid_edge_ctxt = _edge_context()
769 pg_ctxt['nova_metadata_proxy_secret'] = \
770
771=== modified file 'hooks/neutron_plumgrid_hooks.py'
772--- hooks/neutron_plumgrid_hooks.py 2016-05-18 09:12:44 +0000
773+++ hooks/neutron_plumgrid_hooks.py 2016-08-29 12:04:53 +0000
774@@ -32,6 +32,7 @@
775 restart_map,
776 ensure_files,
777 set_neutron_relation,
778+ configure_pg_sources
779 )
780
781 hooks = Hooks()
782@@ -65,6 +66,8 @@
783 charm_config.changed('plumgrid-build') or
784 charm_config.changed('install_keys')):
785 status_set('maintenance', 'Upgrading apt packages')
786+ if charm_config.changed('install_sources'):
787+ configure_pg_sources()
788 configure_sources()
789 apt_update()
790 pkgs = determine_packages()
791
792=== modified file 'hooks/neutron_plumgrid_utils.py'
793--- hooks/neutron_plumgrid_utils.py 2016-05-17 18:03:56 +0000
794+++ hooks/neutron_plumgrid_utils.py 2016-08-29 12:04:53 +0000
795@@ -31,7 +31,7 @@
796 ]
797
798 NEUTRON_CONF_DIR = "/etc/neutron"
799-
800+SOURCES_LIST = '/etc/apt/sources.list'
801 SU_FILE = '/etc/sudoers.d/neutron_sudoers'
802 PLUMGRID_CONF = '%s/plugins/plumgrid/plumgrid.ini' % NEUTRON_CONF_DIR
803 PGLIB_CONF = '%s/plugins/plumgrid/plumlib.ini' % NEUTRON_CONF_DIR
804@@ -57,12 +57,28 @@
805 ])
806
807 NETWORKING_PLUMGRID_VERSION = OrderedDict([
808- ('kilo', '2015.1.1.1'),
809- ('liberty', '2015.2.1.1'),
810- ('mitaka', '2016.1.1.1'),
811+ ('kilo', '2015.1.5.4'),
812+ ('liberty', '2015.2.5.4'),
813+ ('mitaka', '2016.1.1.2'),
814 ])
815
816
817+def configure_pg_sources():
818+ '''
819+ Returns true if install sources is updated in sources.list file
820+ '''
821+ try:
822+ with open(SOURCES_LIST, 'r+') as sources:
823+ all_lines = sources.readlines()
824+ sources.seek(0)
825+ for i in (line for line in all_lines if "plumgrid" not in line):
826+ sources.write(i)
827+ sources.truncate()
828+ sources.close()
829+ except IOError:
830+ log('Unable to update /etc/apt/sources.list')
831+
832+
833 def determine_packages():
834 '''
835 Returns list of packages required to be installed alongside neutron to
836
837=== modified file 'templates/kilo/plumlib.ini'
838--- templates/kilo/plumlib.ini 2016-05-14 11:23:56 +0000
839+++ templates/kilo/plumlib.ini 2016-08-29 12:04:53 +0000
840@@ -59,4 +59,6 @@
841 admin_tenant_name = {{ admin_tenant_name }}
842 auth_uri = {{ service_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0/
843 identity_version = v2.0
844+user_domain_name = {{ user_domain_name }}
845+project_domain_name = {{ project_domain_name }}
846 {% endif -%}
847
848=== modified file 'unit_tests/test_neutron_plumgrid_plugin_context.py'
849--- unit_tests/test_neutron_plumgrid_plugin_context.py 2016-05-16 12:50:39 +0000
850+++ unit_tests/test_neutron_plumgrid_plugin_context.py 2016-08-29 12:04:53 +0000
851@@ -60,6 +60,8 @@
852 'switch-username': 'plumgrid',
853 'switch-password': 'plumgrid',
854 'connector-type': 'service',
855+ 'user-domain-name': 'Default',
856+ 'project-domain-name': 'Default'
857 }
858
859 def mock_config(key=None):
860@@ -90,6 +92,8 @@
861 'switch_password': 'plumgrid',
862 'metadata_mode': 'tunnel',
863 'connector_type': 'service',
864+ 'user_domain_name': 'Default',
865+ 'project_domain_name': 'Default',
866 'nova_metadata_proxy_secret': 'plumgrid',
867 'pg_metadata_ip': '169.254.169.254',
868 'pg_metadata_subnet': '169.254.169.254/30',

Subscribers

People subscribed via source and target branches

to all changes: