Merge lp:~1chb1n/charms/trusty/neutron-openvswitch/next-amulet-15.11 into lp:~openstack-charmers-archive/charms/trusty/neutron-openvswitch/next

Proposed by Ryan Beisner
Status: Work in progress
Proposed branch: lp:~1chb1n/charms/trusty/neutron-openvswitch/next-amulet-15.11
Merge into: lp:~openstack-charmers-archive/charms/trusty/neutron-openvswitch/next
Diff against target: 1115 lines (+530/-92)
17 files modified
Makefile (+1/-0)
hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+100/-1)
hooks/charmhelpers/contrib/openstack/amulet/utils.py (+26/-4)
hooks/charmhelpers/contrib/openstack/context.py (+25/-9)
hooks/charmhelpers/contrib/openstack/neutron.py (+14/-0)
hooks/charmhelpers/contrib/openstack/utils.py (+4/-1)
hooks/charmhelpers/core/hookenv.py (+14/-0)
hooks/charmhelpers/core/host.py (+28/-1)
hooks/charmhelpers/core/hugepage.py (+2/-0)
hooks/charmhelpers/core/templating.py (+5/-1)
hooks/charmhelpers/fetch/__init__.py (+1/-1)
tests/020-basic-trusty-liberty (+11/-0)
tests/021-basic-wily-liberty (+9/-0)
tests/README (+10/-0)
tests/basic_deployment.py (+155/-70)
tests/charmhelpers/contrib/openstack/amulet/deployment.py (+100/-1)
tests/charmhelpers/contrib/openstack/amulet/utils.py (+25/-3)
To merge this branch: bzr merge lp:~1chb1n/charms/trusty/neutron-openvswitch/next-amulet-15.11
Reviewer Review Type Date Requested Status
OpenStack Charmers Pending
Review via email: mp+275767@code.launchpad.net

Description of the change

Update amulet tests for Liberty. Sync charm helpers. Wait for workload status before testing.

To post a comment you must log in.
97. By Ryan Beisner

update tests

Unmerged revisions

97. By Ryan Beisner

update tests

96. By Ryan Beisner

update test deployment topology to satisfy workload status; wait for ready state before testing.

95. By Ryan Beisner

fix old lint

94. By Ryan Beisner

update readme; add liberty amuelt test definitions; move 00-setup to prevent superfluous bootstrap/destroy iteration.

93. By Ryan Beisner

sync charmhelpers

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2015-10-06 16:23:59 +0000
3+++ Makefile 2015-10-26 21:25:29 +0000
4@@ -13,6 +13,7 @@
5
6 functional_test:
7 @echo Starting Amulet tests...
8+ @tests/setup/00-setup
9 @juju test -v -p AMULET_HTTP_PROXY,AMULET_OS_VIP --timeout 2700
10
11 bin/charm_helpers_sync.py:
12
13=== modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py'
14--- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-21 22:49:35 +0000
15+++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-10-26 21:25:29 +0000
16@@ -14,12 +14,18 @@
17 # You should have received a copy of the GNU Lesser General Public License
18 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
19
20+import logging
21+import re
22+import sys
23 import six
24 from collections import OrderedDict
25 from charmhelpers.contrib.amulet.deployment import (
26 AmuletDeployment
27 )
28
29+DEBUG = logging.DEBUG
30+ERROR = logging.ERROR
31+
32
33 class OpenStackAmuletDeployment(AmuletDeployment):
34 """OpenStack amulet deployment.
35@@ -28,9 +34,12 @@
36 that is specifically for use by OpenStack charms.
37 """
38
39- def __init__(self, series=None, openstack=None, source=None, stable=True):
40+ def __init__(self, series=None, openstack=None, source=None,
41+ stable=True, log_level=DEBUG):
42 """Initialize the deployment environment."""
43 super(OpenStackAmuletDeployment, self).__init__(series)
44+ self.log = self.get_logger(level=log_level)
45+ self.log.info('OpenStackAmuletDeployment: init')
46 self.openstack = openstack
47 self.source = source
48 self.stable = stable
49@@ -38,6 +47,22 @@
50 # out.
51 self.current_next = "trusty"
52
53+ def get_logger(self, name="deployment-logger", level=logging.DEBUG):
54+ """Get a logger object that will log to stdout."""
55+ log = logging
56+ logger = log.getLogger(name)
57+ fmt = log.Formatter("%(asctime)s %(funcName)s "
58+ "%(levelname)s: %(message)s")
59+
60+ handler = log.StreamHandler(stream=sys.stdout)
61+ handler.setLevel(level)
62+ handler.setFormatter(fmt)
63+
64+ logger.addHandler(handler)
65+ logger.setLevel(level)
66+
67+ return logger
68+
69 def _determine_branch_locations(self, other_services):
70 """Determine the branch locations for the other services.
71
72@@ -45,6 +70,8 @@
73 stable or next (dev) branch, and based on this, use the corresonding
74 stable or next branches for the other_services."""
75
76+ self.log.info('OpenStackAmuletDeployment: determine branch locations')
77+
78 # Charms outside the lp:~openstack-charmers namespace
79 base_charms = ['mysql', 'mongodb', 'nrpe']
80
81@@ -82,6 +109,8 @@
82
83 def _add_services(self, this_service, other_services):
84 """Add services to the deployment and set openstack-origin/source."""
85+ self.log.info('OpenStackAmuletDeployment: adding services')
86+
87 other_services = self._determine_branch_locations(other_services)
88
89 super(OpenStackAmuletDeployment, self)._add_services(this_service,
90@@ -111,9 +140,79 @@
91
92 def _configure_services(self, configs):
93 """Configure all of the services."""
94+ self.log.info('OpenStackAmuletDeployment: configure services')
95 for service, config in six.iteritems(configs):
96 self.d.configure(service, config)
97
98+ def _auto_wait_for_status(self, message=None, exclude_services=None,
99+ include_only=None, timeout=1800):
100+ """Wait for all units to have a specific extended status, except
101+ for any defined as excluded. Unless specified via message, any
102+ status containing any case of 'ready' will be considered a match.
103+
104+ Examples of message usage:
105+
106+ Wait for all unit status to CONTAIN any case of 'ready' or 'ok':
107+ message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE)
108+
109+ Wait for all units to reach this status (exact match):
110+ message = re.compile('^Unit is ready and clustered$')
111+
112+ Wait for all units to reach any one of these (exact match):
113+ message = re.compile('Unit is ready|OK|Ready')
114+
115+ Wait for at least one unit to reach this status (exact match):
116+ message = {'ready'}
117+
118+ See Amulet's sentry.wait_for_messages() for message usage detail.
119+ https://github.com/juju/amulet/blob/master/amulet/sentry.py
120+
121+ :param message: Expected status match
122+ :param exclude_services: List of juju service names to ignore,
123+ not to be used in conjuction with include_only.
124+ :param include_only: List of juju service names to exclusively check,
125+ not to be used in conjuction with exclude_services.
126+ :param timeout: Maximum time in seconds to wait for status match
127+ :returns: None. Raises if timeout is hit.
128+ """
129+ self.log.info('Waiting for extended status on units...')
130+
131+ all_services = self.d.services.keys()
132+
133+ if exclude_services and include_only:
134+ raise ValueError('exclude_services can not be used '
135+ 'with include_only')
136+
137+ if message:
138+ if isinstance(message, re._pattern_type):
139+ match = message.pattern
140+ else:
141+ match = message
142+
143+ self.log.debug('Custom extended status wait match: '
144+ '{}'.format(match))
145+ else:
146+ self.log.debug('Default extended status wait match: contains '
147+ 'READY (case-insensitive)')
148+ message = re.compile('.*ready.*', re.IGNORECASE)
149+
150+ if exclude_services:
151+ self.log.debug('Excluding services from extended status match: '
152+ '{}'.format(exclude_services))
153+ else:
154+ exclude_services = []
155+
156+ if include_only:
157+ services = include_only
158+ else:
159+ services = list(set(all_services) - set(exclude_services))
160+
161+ self.log.debug('Waiting up to {}s for extended status on services: '
162+ '{}'.format(timeout, services))
163+ service_messages = {service: message for service in services}
164+ self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
165+ self.log.info('OK')
166+
167 def _get_openstack_release(self):
168 """Get openstack release.
169
170
171=== modified file 'hooks/charmhelpers/contrib/openstack/amulet/utils.py'
172--- hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-09-21 22:49:35 +0000
173+++ hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-10-26 21:25:29 +0000
174@@ -18,6 +18,7 @@
175 import json
176 import logging
177 import os
178+import re
179 import six
180 import time
181 import urllib
182@@ -604,7 +605,22 @@
183 '{}'.format(sample_type, samples))
184 return None
185
186-# rabbitmq/amqp specific helpers:
187+ # rabbitmq/amqp specific helpers:
188+
189+ def rmq_wait_for_cluster(self, deployment, init_sleep=15, timeout=1200):
190+ """Wait for rmq units extended status to show cluster readiness,
191+ after an optional initial sleep period. Initial sleep is likely
192+ necessary to be effective following a config change, as status
193+ message may not instantly update to non-ready."""
194+
195+ if init_sleep:
196+ time.sleep(init_sleep)
197+
198+ message = re.compile('^Unit is ready and clustered$')
199+ deployment._auto_wait_for_status(message=message,
200+ timeout=timeout,
201+ include_only=['rabbitmq-server'])
202+
203 def add_rmq_test_user(self, sentry_units,
204 username="testuser1", password="changeme"):
205 """Add a test user via the first rmq juju unit, check connection as
206@@ -752,7 +768,7 @@
207 self.log.debug('SSL is enabled @{}:{} '
208 '({})'.format(host, port, unit_name))
209 return True
210- elif not port and not conf_ssl:
211+ elif not conf_ssl:
212 self.log.debug('SSL not enabled @{}:{} '
213 '({})'.format(host, port, unit_name))
214 return False
215@@ -805,7 +821,10 @@
216 if port:
217 config['ssl_port'] = port
218
219- deployment.configure('rabbitmq-server', config)
220+ deployment.d.configure('rabbitmq-server', config)
221+
222+ # Wait for unit status
223+ self.rmq_wait_for_cluster(deployment)
224
225 # Confirm
226 tries = 0
227@@ -832,7 +851,10 @@
228
229 # Disable RMQ SSL
230 config = {'ssl': 'off'}
231- deployment.configure('rabbitmq-server', config)
232+ deployment.d.configure('rabbitmq-server', config)
233+
234+ # Wait for unit status
235+ self.rmq_wait_for_cluster(deployment)
236
237 # Confirm
238 tries = 0
239
240=== modified file 'hooks/charmhelpers/contrib/openstack/context.py'
241--- hooks/charmhelpers/contrib/openstack/context.py 2015-09-29 20:59:00 +0000
242+++ hooks/charmhelpers/contrib/openstack/context.py 2015-10-26 21:25:29 +0000
243@@ -952,6 +952,19 @@
244 'config': config}
245 return ovs_ctxt
246
247+ def midonet_ctxt(self):
248+ driver = neutron_plugin_attribute(self.plugin, 'driver',
249+ self.network_manager)
250+ midonet_config = neutron_plugin_attribute(self.plugin, 'config',
251+ self.network_manager)
252+ mido_ctxt = {'core_plugin': driver,
253+ 'neutron_plugin': 'midonet',
254+ 'neutron_security_groups': self.neutron_security_groups,
255+ 'local_ip': unit_private_ip(),
256+ 'config': midonet_config}
257+
258+ return mido_ctxt
259+
260 def __call__(self):
261 if self.network_manager not in ['quantum', 'neutron']:
262 return {}
263@@ -973,6 +986,8 @@
264 ctxt.update(self.nuage_ctxt())
265 elif self.plugin == 'plumgrid':
266 ctxt.update(self.pg_ctxt())
267+ elif self.plugin == 'midonet':
268+ ctxt.update(self.midonet_ctxt())
269
270 alchemy_flags = config('neutron-alchemy-flags')
271 if alchemy_flags:
272@@ -1105,7 +1120,7 @@
273
274 ctxt = {
275 ... other context ...
276- 'subordinate_config': {
277+ 'subordinate_configuration': {
278 'DEFAULT': {
279 'key1': 'value1',
280 },
281@@ -1146,22 +1161,23 @@
282 try:
283 sub_config = json.loads(sub_config)
284 except:
285- log('Could not parse JSON from subordinate_config '
286- 'setting from %s' % rid, level=ERROR)
287+ log('Could not parse JSON from '
288+ 'subordinate_configuration setting from %s'
289+ % rid, level=ERROR)
290 continue
291
292 for service in self.services:
293 if service not in sub_config:
294- log('Found subordinate_config on %s but it contained'
295- 'nothing for %s service' % (rid, service),
296- level=INFO)
297+ log('Found subordinate_configuration on %s but it '
298+ 'contained nothing for %s service'
299+ % (rid, service), level=INFO)
300 continue
301
302 sub_config = sub_config[service]
303 if self.config_file not in sub_config:
304- log('Found subordinate_config on %s but it contained'
305- 'nothing for %s' % (rid, self.config_file),
306- level=INFO)
307+ log('Found subordinate_configuration on %s but it '
308+ 'contained nothing for %s'
309+ % (rid, self.config_file), level=INFO)
310 continue
311
312 sub_config = sub_config[self.config_file]
313
314=== modified file 'hooks/charmhelpers/contrib/openstack/neutron.py'
315--- hooks/charmhelpers/contrib/openstack/neutron.py 2015-09-28 11:32:27 +0000
316+++ hooks/charmhelpers/contrib/openstack/neutron.py 2015-10-26 21:25:29 +0000
317@@ -209,6 +209,20 @@
318 'server_packages': ['neutron-server',
319 'neutron-plugin-plumgrid'],
320 'server_services': ['neutron-server']
321+ },
322+ 'midonet': {
323+ 'config': '/etc/neutron/plugins/midonet/midonet.ini',
324+ 'driver': 'midonet.neutron.plugin.MidonetPluginV2',
325+ 'contexts': [
326+ context.SharedDBContext(user=config('neutron-database-user'),
327+ database=config('neutron-database'),
328+ relation_prefix='neutron',
329+ ssl_dir=NEUTRON_CONF_DIR)],
330+ 'services': [],
331+ 'packages': [[headers_package()] + determine_dkms_package()],
332+ 'server_packages': ['neutron-server',
333+ 'python-neutron-plugin-midonet'],
334+ 'server_services': ['neutron-server']
335 }
336 }
337 if release >= 'icehouse':
338
339=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
340--- hooks/charmhelpers/contrib/openstack/utils.py 2015-09-28 11:32:27 +0000
341+++ hooks/charmhelpers/contrib/openstack/utils.py 2015-10-26 21:25:29 +0000
342@@ -121,6 +121,7 @@
343 ('2.2.2', 'kilo'),
344 ('2.3.0', 'liberty'),
345 ('2.4.0', 'liberty'),
346+ ('2.5.0', 'liberty'),
347 ])
348
349 # >= Liberty version->codename mapping
350@@ -858,7 +859,9 @@
351 if charm_state != 'active' and charm_state != 'unknown':
352 state = workload_state_compare(state, charm_state)
353 if message:
354- message = "{} {}".format(message, charm_message)
355+ charm_message = charm_message.replace("Incomplete relations: ",
356+ "")
357+ message = "{}, {}".format(message, charm_message)
358 else:
359 message = charm_message
360
361
362=== modified file 'hooks/charmhelpers/core/hookenv.py'
363--- hooks/charmhelpers/core/hookenv.py 2015-09-28 11:32:27 +0000
364+++ hooks/charmhelpers/core/hookenv.py 2015-10-26 21:25:29 +0000
365@@ -491,6 +491,19 @@
366
367
368 @cached
369+def peer_relation_id():
370+ '''Get a peer relation id if a peer relation has been joined, else None.'''
371+ md = metadata()
372+ section = md.get('peers')
373+ if section:
374+ for key in section:
375+ relids = relation_ids(key)
376+ if relids:
377+ return relids[0]
378+ return None
379+
380+
381+@cached
382 def relation_to_interface(relation_name):
383 """
384 Given the name of a relation, return the interface that relation uses.
385@@ -820,6 +833,7 @@
386
387 def translate_exc(from_exc, to_exc):
388 def inner_translate_exc1(f):
389+ @wraps(f)
390 def inner_translate_exc2(*args, **kwargs):
391 try:
392 return f(*args, **kwargs)
393
394=== modified file 'hooks/charmhelpers/core/host.py'
395--- hooks/charmhelpers/core/host.py 2015-09-21 22:49:35 +0000
396+++ hooks/charmhelpers/core/host.py 2015-10-26 21:25:29 +0000
397@@ -566,7 +566,14 @@
398 os.chdir(cur)
399
400
401-def chownr(path, owner, group, follow_links=True):
402+def chownr(path, owner, group, follow_links=True, chowntopdir=False):
403+ """
404+ Recursively change user and group ownership of files and directories
405+ in given path. Doesn't chown path itself by default, only its children.
406+
407+ :param bool follow_links: Also Chown links if True
408+ :param bool chowntopdir: Also chown path itself if True
409+ """
410 uid = pwd.getpwnam(owner).pw_uid
411 gid = grp.getgrnam(group).gr_gid
412 if follow_links:
413@@ -574,6 +581,10 @@
414 else:
415 chown = os.lchown
416
417+ if chowntopdir:
418+ broken_symlink = os.path.lexists(path) and not os.path.exists(path)
419+ if not broken_symlink:
420+ chown(path, uid, gid)
421 for root, dirs, files in os.walk(path):
422 for name in dirs + files:
423 full = os.path.join(root, name)
424@@ -584,3 +595,19 @@
425
426 def lchownr(path, owner, group):
427 chownr(path, owner, group, follow_links=False)
428+
429+
430+def get_total_ram():
431+ '''The total amount of system RAM in bytes.
432+
433+ This is what is reported by the OS, and may be overcommitted when
434+ there are multiple containers hosted on the same machine.
435+ '''
436+ with open('/proc/meminfo', 'r') as f:
437+ for line in f.readlines():
438+ if line:
439+ key, value, unit = line.split()
440+ if key == 'MemTotal:':
441+ assert unit == 'kB', 'Unknown unit'
442+ return int(value) * 1024 # Classic, not KiB.
443+ raise NotImplementedError()
444
445=== modified file 'hooks/charmhelpers/core/hugepage.py'
446--- hooks/charmhelpers/core/hugepage.py 2015-09-21 22:49:35 +0000
447+++ hooks/charmhelpers/core/hugepage.py 2015-10-26 21:25:29 +0000
448@@ -46,6 +46,8 @@
449 group_info = add_group(group)
450 gid = group_info.gr_gid
451 add_user_to_group(user, group)
452+ if max_map_count < 2 * nr_hugepages:
453+ max_map_count = 2 * nr_hugepages
454 sysctl_settings = {
455 'vm.nr_hugepages': nr_hugepages,
456 'vm.max_map_count': max_map_count,
457
458=== modified file 'hooks/charmhelpers/core/templating.py'
459--- hooks/charmhelpers/core/templating.py 2015-02-11 18:54:05 +0000
460+++ hooks/charmhelpers/core/templating.py 2015-10-26 21:25:29 +0000
461@@ -64,5 +64,9 @@
462 level=hookenv.ERROR)
463 raise e
464 content = template.render(context)
465- host.mkdir(os.path.dirname(target), owner, group, perms=0o755)
466+ target_dir = os.path.dirname(target)
467+ if not os.path.exists(target_dir):
468+ # This is a terrible default directory permission, as the file
469+ # or its siblings will often contain secrets.
470+ host.mkdir(os.path.dirname(target), owner, group, perms=0o755)
471 host.write_file(target, content.encode(encoding), owner, group, perms)
472
473=== modified file 'hooks/charmhelpers/fetch/__init__.py'
474--- hooks/charmhelpers/fetch/__init__.py 2015-08-18 21:19:31 +0000
475+++ hooks/charmhelpers/fetch/__init__.py 2015-10-26 21:25:29 +0000
476@@ -225,12 +225,12 @@
477
478 def apt_mark(packages, mark, fatal=False):
479 """Flag one or more packages using apt-mark"""
480+ log("Marking {} as {}".format(packages, mark))
481 cmd = ['apt-mark', mark]
482 if isinstance(packages, six.string_types):
483 cmd.append(packages)
484 else:
485 cmd.extend(packages)
486- log("Holding {}".format(packages))
487
488 if fatal:
489 subprocess.check_call(cmd, universal_newlines=True)
490
491=== modified file 'tests/019-basic-vivid-kilo' (properties changed: -x to +x)
492=== added file 'tests/020-basic-trusty-liberty'
493--- tests/020-basic-trusty-liberty 1970-01-01 00:00:00 +0000
494+++ tests/020-basic-trusty-liberty 2015-10-26 21:25:29 +0000
495@@ -0,0 +1,11 @@
496+#!/usr/bin/python
497+
498+"""Amulet tests on a basic neutron-openvswitch deployment on trusty-liberty."""
499+
500+from basic_deployment import NeutronOVSBasicDeployment
501+
502+if __name__ == '__main__':
503+ deployment = NeutronOVSBasicDeployment(series='trusty',
504+ openstack='cloud:trusty-liberty',
505+ source='cloud:trusty-updates/liberty')
506+ deployment.run_tests()
507
508=== added file 'tests/021-basic-wily-liberty'
509--- tests/021-basic-wily-liberty 1970-01-01 00:00:00 +0000
510+++ tests/021-basic-wily-liberty 2015-10-26 21:25:29 +0000
511@@ -0,0 +1,9 @@
512+#!/usr/bin/python
513+
514+"""Amulet tests on a basic neutron-openvswitch deployment on wily-liberty."""
515+
516+from basic_deployment import NeutronOVSBasicDeployment
517+
518+if __name__ == '__main__':
519+ deployment = NeutronOVSBasicDeployment(series='wily')
520+ deployment.run_tests()
521
522=== modified file 'tests/README'
523--- tests/README 2015-02-13 12:02:13 +0000
524+++ tests/README 2015-10-26 21:25:29 +0000
525@@ -1,6 +1,16 @@
526 This directory provides Amulet tests that focus on verification of
527 neutron-openvswitch deployments.
528
529+test_* methods are called in lexical sort order, although each individual test
530+should be idempotent, and expected to pass regardless of run order.
531+
532+Test name convention to ensure desired test order:
533+ 1xx service and endpoint checks
534+ 2xx relation checks
535+ 3xx config checks
536+ 4xx functional checks
537+ 9xx restarts and other final check
538+
539 In order to run tests, you'll need charm-tools installed (in addition to
540 juju, of course):
541 sudo add-apt-repository ppa:juju/stable
542
543=== modified file 'tests/basic_deployment.py'
544--- tests/basic_deployment.py 2015-07-13 16:07:30 +0000
545+++ tests/basic_deployment.py 2015-10-26 21:25:29 +0000
546@@ -1,8 +1,6 @@
547-#!/usr/bin/python
548-
549 import amulet
550 import os
551-import time
552+import re
553 import yaml
554
555 from charmhelpers.contrib.openstack.amulet.deployment import (
556@@ -11,8 +9,8 @@
557
558 from charmhelpers.contrib.openstack.amulet.utils import (
559 OpenStackAmuletUtils,
560- DEBUG, # flake8: noqa
561- ERROR
562+ DEBUG,
563+ # ERROR
564 )
565
566 # Use DEBUG to turn on debug logging
567@@ -36,17 +34,30 @@
568 self._add_relations()
569 self._configure_services()
570 self._deploy()
571+ self._wait_for_status()
572 self._initialize_tests()
573
574+ def _wait_for_status(self):
575+ """Wait for unit extended status to indicate ready."""
576+ u.log.info('Waiting on extended status checks...')
577+ message = re.compile('^Unit is ready$|^Unit is ready and clustered$',
578+ re.IGNORECASE)
579+ exclude_services = ['mysql']
580+ self._auto_wait_for_status(message=message,
581+ exclude_services=exclude_services)
582+
583 def _add_services(self):
584 """Add services
585
586- Add the services that we're testing, where neutron-openvswitch is local,
587+ Add the services to test, where neutron-openvswitch is local,
588 and the rest of the service are from lp branches that are
589 compatible with the local charm (e.g. stable or next).
590 """
591 this_service = {'name': 'neutron-openvswitch'}
592 other_services = [{'name': 'nova-compute'},
593+ {'name': 'keystone'}, # satisfy workload stat
594+ {'name': 'mysql'}, # satisfy workload stat
595+ {'name': 'glance'}, # satisfy workload stat
596 {'name': 'rabbitmq-server'},
597 {'name': 'neutron-api'}]
598 super(NeutronOVSBasicDeployment, self)._add_services(this_service,
599@@ -60,6 +71,16 @@
600 'nova-compute:neutron-plugin',
601 'neutron-openvswitch:neutron-plugin-api':
602 'neutron-api:neutron-plugin-api',
603+ # Satisfy workload stat:
604+ 'neutron-api:identity-service': 'keystone:identity-service',
605+ 'neutron-api:shared-db': 'mysql:shared-db',
606+ 'neutron-api:amqp': 'rabbitmq-server:amqp',
607+ 'nova-compute:amqp': 'rabbitmq-server:amqp',
608+ 'nova-compute:image-service': 'glance:image-service',
609+ 'glance:identity-service': 'keystone:identity-service',
610+ 'glance:shared-db': 'mysql:shared-db',
611+ 'glance:amqp': 'rabbitmq-server:amqp',
612+ 'keystone:shared-db': 'mysql:shared-db',
613 }
614 super(NeutronOVSBasicDeployment, self)._add_relations(relations)
615
616@@ -75,19 +96,24 @@
617 openstack_origin_git = {
618 'repositories': [
619 {'name': 'requirements',
620- 'repository': 'git://github.com/openstack/requirements',
621+ 'repository':
622+ 'git://github.com/openstack/requirements',
623 'branch': branch},
624 {'name': 'neutron-fwaas',
625- 'repository': 'git://github.com/openstack/neutron-fwaas',
626+ 'repository':
627+ 'git://github.com/openstack/neutron-fwaas',
628 'branch': branch},
629 {'name': 'neutron-lbaas',
630- 'repository': 'git://github.com/openstack/neutron-lbaas',
631+ 'repository':
632+ 'git://github.com/openstack/neutron-lbaas',
633 'branch': branch},
634 {'name': 'neutron-vpnaas',
635- 'repository': 'git://github.com/openstack/neutron-vpnaas',
636+ 'repository':
637+ 'git://github.com/openstack/neutron-vpnaas',
638 'branch': branch},
639 {'name': 'neutron',
640- 'repository': 'git://github.com/openstack/neutron',
641+ 'repository':
642+ 'git://github.com/openstack/neutron',
643 'branch': branch},
644 ],
645 'directory': '/mnt/openstack-git',
646@@ -114,7 +140,9 @@
647 'http_proxy': amulet_http_proxy,
648 'https_proxy': amulet_http_proxy,
649 }
650- neutron_ovs_config['openstack-origin-git'] = yaml.dump(openstack_origin_git)
651+ neutron_ovs_config['openstack-origin-git'] = \
652+ yaml.dump(openstack_origin_git)
653+
654 configs = {'neutron-openvswitch': neutron_ovs_config}
655 super(NeutronOVSBasicDeployment, self)._configure_services(configs)
656
657@@ -122,27 +150,34 @@
658 """Perform final initialization before tests get run."""
659 # Access the sentries for inspecting service units
660 self.compute_sentry = self.d.sentry.unit['nova-compute/0']
661- self.rabbitmq_sentry = self.d.sentry.unit['rabbitmq-server/0']
662- self.neutron_api_sentry = self.d.sentry.unit['neutron-api/0']
663+ self.rmq_sentry = self.d.sentry.unit['rabbitmq-server/0']
664+ self.keystone_sentry = self.d.sentry.unit['keystone/0']
665+ self.mysql_sentry = self.d.sentry.unit['mysql/0']
666+ self.n_api_sentry = self.d.sentry.unit['neutron-api/0']
667+ self.n_ovs_sentry = self.d.sentry.unit['neutron-openvswitch/0']
668
669- def test_services(self):
670+ def test_100_services(self):
671 """Verify the expected services are running on the corresponding
672 service units."""
673+ u.log.debug('Checking system services on units...')
674
675- commands = {
676- self.compute_sentry: ['status nova-compute',
677- 'status neutron-plugin-openvswitch-agent'],
678- self.rabbitmq_sentry: ['service rabbitmq-server status'],
679- self.neutron_api_sentry: ['status neutron-server'],
680+ services = {
681+ self.compute_sentry: ['nova-compute',
682+ 'neutron-plugin-openvswitch-agent'],
683+ self.rmq_sentry: ['rabbitmq-server'],
684+ self.n_api_sentry: ['neutron-server'],
685 }
686
687- ret = u.validate_services(commands)
688+ ret = u.validate_services_by_name(services)
689 if ret:
690 amulet.raise_status(amulet.FAIL, msg=ret)
691
692- def test_rabbitmq_amqp_relation(self):
693+ u.log.debug('OK')
694+
695+ def test_200_rmq_n_ovs_amqp_relation(self):
696 """Verify data in rabbitmq-server/neutron-openvswitch amqp relation"""
697- unit = self.rabbitmq_sentry
698+ u.log.debug('Checking rmq:n-ovs amqp relation data...')
699+ unit = self.rmq_sentry
700 relation = ['amqp', 'neutron-openvswitch:amqp']
701 expected = {
702 'private-address': u.valid_ip,
703@@ -155,8 +190,12 @@
704 message = u.relation_error('rabbitmq amqp', ret)
705 amulet.raise_status(amulet.FAIL, msg=message)
706
707- def test_nova_compute_relation(self):
708+ u.log.debug('OK')
709+
710+ def test_202_nova_compute_relation(self):
711 """Verify the nova-compute to neutron-openvswitch relation data"""
712+ u.log.debug('Checking nova-compute:n-ovs neutron-plugin '
713+ 'relation data...')
714 unit = self.compute_sentry
715 relation = ['neutron-plugin', 'neutron-openvswitch:neutron-plugin']
716 expected = {
717@@ -168,9 +207,13 @@
718 message = u.relation_error('nova-compute neutron-plugin', ret)
719 amulet.raise_status(amulet.FAIL, msg=message)
720
721- def test_neutron_api_relation(self):
722+ u.log.debug('OK')
723+
724+ def test_204_neutron_api_relation(self):
725 """Verify the neutron-api to neutron-openvswitch relation data"""
726- unit = self.neutron_api_sentry
727+ u.log.debug('Checking neuron-api:neutron-ovs neutron-plugin-api '
728+ 'relation data...')
729+ unit = self.n_api_sentry
730 relation = ['neutron-plugin-api',
731 'neutron-openvswitch:neutron-plugin-api']
732 expected = {
733@@ -182,75 +225,117 @@
734 message = u.relation_error('neutron-api neutron-plugin-api', ret)
735 amulet.raise_status(amulet.FAIL, msg=message)
736
737- def process_ret(self, ret=None, message=None):
738+ u.log.debug('OK')
739+
740+ def _process_ret(self, ret=None, message=None):
741 if ret:
742 amulet.raise_status(amulet.FAIL, msg=message)
743
744- def check_ml2_setting_propagation(self, service, charm_key,
745- config_file_key, vpair,
746- section):
747+ def _check_ml2_setting_propagation(self, service, charm_key,
748+ config_file_key, vpair,
749+ section):
750+
751+ # Needs love - test actions not clear in log
752 unit = self.compute_sentry
753 conf = "/etc/neutron/plugins/ml2/ml2_conf.ini"
754 for value in vpair:
755 self.d.configure(service, {charm_key: value})
756- time.sleep(60)
757+ self._wait_for_status()
758+
759 ret = u.validate_config_data(unit, conf, section,
760 {config_file_key: value})
761 msg = "Propagation error, expected %s=%s" % (config_file_key,
762 value)
763- self.process_ret(ret=ret, message=msg)
764-
765- def test_l2pop_propagation(self):
766+ self._process_ret(ret=ret, message=msg)
767+
768+ def test_300_neutron_api_default_config(self):
769+ u.log.warn('TEST NOT IMPLEMENTED')
770+ pass
771+
772+ def test_302_l2pop_propagation(self):
773 """Verify that neutron-api l2pop setting propagates to neutron-ovs"""
774- self.check_ml2_setting_propagation('neutron-api',
775- 'l2-population',
776- 'l2_population',
777- ['False', 'True'],
778- 'agent')
779-
780- def test_nettype_propagation(self):
781+ u.log.debug('Checking n-api l2pop setting propagation to n-ovs...')
782+
783+ # Needs love - not idempotent
784+ self._check_ml2_setting_propagation('neutron-api',
785+ 'l2-population',
786+ 'l2_population',
787+ ['False', 'True'],
788+ 'agent')
789+ u.log.debug('OK')
790+
791+ def test_303_nettype_propagation(self):
792 """Verify that neutron-api nettype setting propagates to neutron-ovs"""
793- self.check_ml2_setting_propagation('neutron-api',
794- 'overlay-network-type',
795- 'tunnel_types',
796- ['vxlan', 'gre'],
797- 'agent')
798-
799- def test_secgroup_propagation_local_override(self):
800+ u.log.debug('Checking n-api nettype setting propagation to n-ovs...')
801+
802+ # Needs love - not idempotent
803+ self._check_ml2_setting_propagation('neutron-api',
804+ 'overlay-network-type',
805+ 'tunnel_types',
806+ ['vxlan', 'gre'],
807+ 'agent')
808+ u.log.debug('OK')
809+
810+ def test_304_secgroup_propagation_local_override(self):
811 """Verify disable-security-groups overrides what neutron-api says"""
812+ u.log.debug('Checking n-api disablee sec group override setting '
813+ 'propagation to n-ovs...')
814+
815+ # Needs love - not idempotent
816 unit = self.compute_sentry
817 conf = "/etc/neutron/plugins/ml2/ml2_conf.ini"
818 self.d.configure('neutron-api', {'neutron-security-groups': 'True'})
819 self.d.configure('neutron-openvswitch',
820 {'disable-security-groups': 'True'})
821- time.sleep(30)
822+
823+ self._wait_for_status()
824+
825 ret = u.validate_config_data(unit, conf, 'securitygroup',
826 {'enable_security_group': 'False'})
827 msg = "Propagation error, expected %s=%s" % ('enable_security_group',
828 'False')
829- self.process_ret(ret=ret, message=msg)
830+ self._process_ret(ret=ret, message=msg)
831 self.d.configure('neutron-openvswitch',
832 {'disable-security-groups': 'False'})
833 self.d.configure('neutron-api', {'neutron-security-groups': 'True'})
834- time.sleep(30)
835+
836+ self._wait_for_status()
837 ret = u.validate_config_data(unit, conf, 'securitygroup',
838 {'enable_security_group': 'True'})
839-
840- def test_z_restart_on_config_change(self):
841- """Verify that the specified services are restarted when the config
842- is changed.
843-
844- Note(coreycb): The method name with the _z_ is a little odd
845- but it forces the test to run last. It just makes things
846- easier because restarting services requires re-authorization.
847- """
848- conf = '/etc/neutron/neutron.conf'
849- self.d.configure('neutron-openvswitch', {'use-syslog': 'True'})
850- if not u.service_restarted(self.compute_sentry,
851- 'neutron-openvswitch-agent', conf,
852- pgrep_full=True, sleep_time=60):
853- self.d.configure('neutron-openvswitch', {'use-syslog': 'False'})
854- msg = ('service neutron-openvswitch-agent did not restart after '
855- 'config change')
856- amulet.raise_status(amulet.FAIL, msg=msg)
857- self.d.configure('neutron-openvswitch', {'use-syslog': 'False'})
858+ u.log.debug('OK')
859+
860+ def test_900_restart_on_config_change(self):
861+ """Verify that the specified services are restarted when the
862+ config is changed."""
863+
864+ sentry = self.n_ovs_sentry
865+ juju_service = 'neutron-openvswitch'
866+
867+ # Expected default and alternate values
868+ set_default = {'debug': 'False'}
869+ set_alternate = {'debug': 'True'}
870+
871+ # Services which are expected to restart upon config change,
872+ # and corresponding config files affected by the change
873+ conf_file = '/etc/neutron/neutron.conf'
874+ services = {
875+ 'neutron-openvswitch-agent': conf_file
876+ }
877+
878+ # Make config change, check for svc restart, conf file mod time change
879+ u.log.debug('Making config change on {}...'.format(juju_service))
880+ mtime = u.get_sentry_time(sentry)
881+ self.d.configure(juju_service, set_alternate)
882+
883+ sleep_time = 60
884+ for s, conf_file in services.iteritems():
885+ u.log.debug("Checking that service restarted: {}".format(s))
886+ if not u.validate_service_config_changed(sentry, mtime, s,
887+ conf_file,
888+ sleep_time=sleep_time):
889+ self.d.configure(juju_service, set_default)
890+ msg = "service {} didn't restart after config change".format(s)
891+ amulet.raise_status(amulet.FAIL, msg=msg)
892+
893+ self.d.configure(juju_service, set_default)
894+ u.log.debug('OK')
895
896=== modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py'
897--- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-21 22:49:35 +0000
898+++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-10-26 21:25:29 +0000
899@@ -14,12 +14,18 @@
900 # You should have received a copy of the GNU Lesser General Public License
901 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
902
903+import logging
904+import re
905+import sys
906 import six
907 from collections import OrderedDict
908 from charmhelpers.contrib.amulet.deployment import (
909 AmuletDeployment
910 )
911
912+DEBUG = logging.DEBUG
913+ERROR = logging.ERROR
914+
915
916 class OpenStackAmuletDeployment(AmuletDeployment):
917 """OpenStack amulet deployment.
918@@ -28,9 +34,12 @@
919 that is specifically for use by OpenStack charms.
920 """
921
922- def __init__(self, series=None, openstack=None, source=None, stable=True):
923+ def __init__(self, series=None, openstack=None, source=None,
924+ stable=True, log_level=DEBUG):
925 """Initialize the deployment environment."""
926 super(OpenStackAmuletDeployment, self).__init__(series)
927+ self.log = self.get_logger(level=log_level)
928+ self.log.info('OpenStackAmuletDeployment: init')
929 self.openstack = openstack
930 self.source = source
931 self.stable = stable
932@@ -38,6 +47,22 @@
933 # out.
934 self.current_next = "trusty"
935
936+ def get_logger(self, name="deployment-logger", level=logging.DEBUG):
937+ """Get a logger object that will log to stdout."""
938+ log = logging
939+ logger = log.getLogger(name)
940+ fmt = log.Formatter("%(asctime)s %(funcName)s "
941+ "%(levelname)s: %(message)s")
942+
943+ handler = log.StreamHandler(stream=sys.stdout)
944+ handler.setLevel(level)
945+ handler.setFormatter(fmt)
946+
947+ logger.addHandler(handler)
948+ logger.setLevel(level)
949+
950+ return logger
951+
952 def _determine_branch_locations(self, other_services):
953 """Determine the branch locations for the other services.
954
955@@ -45,6 +70,8 @@
956 stable or next (dev) branch, and based on this, use the corresonding
957 stable or next branches for the other_services."""
958
959+ self.log.info('OpenStackAmuletDeployment: determine branch locations')
960+
961 # Charms outside the lp:~openstack-charmers namespace
962 base_charms = ['mysql', 'mongodb', 'nrpe']
963
964@@ -82,6 +109,8 @@
965
966 def _add_services(self, this_service, other_services):
967 """Add services to the deployment and set openstack-origin/source."""
968+ self.log.info('OpenStackAmuletDeployment: adding services')
969+
970 other_services = self._determine_branch_locations(other_services)
971
972 super(OpenStackAmuletDeployment, self)._add_services(this_service,
973@@ -111,9 +140,79 @@
974
975 def _configure_services(self, configs):
976 """Configure all of the services."""
977+ self.log.info('OpenStackAmuletDeployment: configure services')
978 for service, config in six.iteritems(configs):
979 self.d.configure(service, config)
980
981+ def _auto_wait_for_status(self, message=None, exclude_services=None,
982+ include_only=None, timeout=1800):
983+ """Wait for all units to have a specific extended status, except
984+ for any defined as excluded. Unless specified via message, any
985+ status containing any case of 'ready' will be considered a match.
986+
987+ Examples of message usage:
988+
989+ Wait for all unit status to CONTAIN any case of 'ready' or 'ok':
990+ message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE)
991+
992+ Wait for all units to reach this status (exact match):
993+ message = re.compile('^Unit is ready and clustered$')
994+
995+ Wait for all units to reach any one of these (exact match):
996+ message = re.compile('Unit is ready|OK|Ready')
997+
998+ Wait for at least one unit to reach this status (exact match):
999+ message = {'ready'}
1000+
1001+ See Amulet's sentry.wait_for_messages() for message usage detail.
1002+ https://github.com/juju/amulet/blob/master/amulet/sentry.py
1003+
1004+ :param message: Expected status match
1005+ :param exclude_services: List of juju service names to ignore,
1006+ not to be used in conjuction with include_only.
1007+ :param include_only: List of juju service names to exclusively check,
1008+ not to be used in conjuction with exclude_services.
1009+ :param timeout: Maximum time in seconds to wait for status match
1010+ :returns: None. Raises if timeout is hit.
1011+ """
1012+ self.log.info('Waiting for extended status on units...')
1013+
1014+ all_services = self.d.services.keys()
1015+
1016+ if exclude_services and include_only:
1017+ raise ValueError('exclude_services can not be used '
1018+ 'with include_only')
1019+
1020+ if message:
1021+ if isinstance(message, re._pattern_type):
1022+ match = message.pattern
1023+ else:
1024+ match = message
1025+
1026+ self.log.debug('Custom extended status wait match: '
1027+ '{}'.format(match))
1028+ else:
1029+ self.log.debug('Default extended status wait match: contains '
1030+ 'READY (case-insensitive)')
1031+ message = re.compile('.*ready.*', re.IGNORECASE)
1032+
1033+ if exclude_services:
1034+ self.log.debug('Excluding services from extended status match: '
1035+ '{}'.format(exclude_services))
1036+ else:
1037+ exclude_services = []
1038+
1039+ if include_only:
1040+ services = include_only
1041+ else:
1042+ services = list(set(all_services) - set(exclude_services))
1043+
1044+ self.log.debug('Waiting up to {}s for extended status on services: '
1045+ '{}'.format(timeout, services))
1046+ service_messages = {service: message for service in services}
1047+ self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
1048+ self.log.info('OK')
1049+
1050 def _get_openstack_release(self):
1051 """Get openstack release.
1052
1053
1054=== modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py'
1055--- tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-09-29 20:59:00 +0000
1056+++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-10-26 21:25:29 +0000
1057@@ -18,6 +18,7 @@
1058 import json
1059 import logging
1060 import os
1061+import re
1062 import six
1063 import time
1064 import urllib
1065@@ -604,7 +605,22 @@
1066 '{}'.format(sample_type, samples))
1067 return None
1068
1069-# rabbitmq/amqp specific helpers:
1070+ # rabbitmq/amqp specific helpers:
1071+
1072+ def rmq_wait_for_cluster(self, deployment, init_sleep=15, timeout=1200):
1073+ """Wait for rmq units extended status to show cluster readiness,
1074+ after an optional initial sleep period. Initial sleep is likely
1075+ necessary to be effective following a config change, as status
1076+ message may not instantly update to non-ready."""
1077+
1078+ if init_sleep:
1079+ time.sleep(init_sleep)
1080+
1081+ message = re.compile('^Unit is ready and clustered$')
1082+ deployment._auto_wait_for_status(message=message,
1083+ timeout=timeout,
1084+ include_only=['rabbitmq-server'])
1085+
1086 def add_rmq_test_user(self, sentry_units,
1087 username="testuser1", password="changeme"):
1088 """Add a test user via the first rmq juju unit, check connection as
1089@@ -805,7 +821,10 @@
1090 if port:
1091 config['ssl_port'] = port
1092
1093- deployment.configure('rabbitmq-server', config)
1094+ deployment.d.configure('rabbitmq-server', config)
1095+
1096+ # Wait for unit status
1097+ self.rmq_wait_for_cluster(deployment)
1098
1099 # Confirm
1100 tries = 0
1101@@ -832,7 +851,10 @@
1102
1103 # Disable RMQ SSL
1104 config = {'ssl': 'off'}
1105- deployment.configure('rabbitmq-server', config)
1106+ deployment.d.configure('rabbitmq-server', config)
1107+
1108+ # Wait for unit status
1109+ self.rmq_wait_for_cluster(deployment)
1110
1111 # Confirm
1112 tries = 0
1113
1114=== added directory 'tests/setup'
1115=== renamed file 'tests/00-setup' => 'tests/setup/00-setup'

Subscribers

People subscribed via source and target branches