1
# Copyright 2014-2015 Canonical Limited.
3
# This file is part of charm-helpers.
5
# charm-helpers is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU Lesser General Public License version 3 as
7
# published by the Free Software Foundation.
9
# charm-helpers is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU Lesser General Public License for more details.
14
# You should have received a copy of the GNU Lesser General Public License
15
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
21
from collections import OrderedDict
22
from charmhelpers.contrib.amulet.deployment import (
30
class OpenStackAmuletDeployment(AmuletDeployment):
31
"""OpenStack amulet deployment.
33
This class inherits from AmuletDeployment and has additional support
34
that is specifically for use by OpenStack charms.
37
def __init__(self, series=None, openstack=None, source=None,
38
stable=True, log_level=DEBUG):
39
"""Initialize the deployment environment."""
40
super(OpenStackAmuletDeployment, self).__init__(series)
41
self.log = self.get_logger(level=log_level)
42
self.log.info('OpenStackAmuletDeployment: init')
43
self.openstack = openstack
46
# Note(coreycb): this needs to be changed when new next branches come
48
self.current_next = "trusty"
50
def get_logger(self, name="deployment-logger", level=logging.DEBUG):
51
"""Get a logger object that will log to stdout."""
53
logger = log.getLogger(name)
54
fmt = log.Formatter("%(asctime)s %(funcName)s "
55
"%(levelname)s: %(message)s")
57
handler = log.StreamHandler(stream=sys.stdout)
58
handler.setLevel(level)
59
handler.setFormatter(fmt)
61
logger.addHandler(handler)
62
logger.setLevel(level)
66
def _determine_branch_locations(self, other_services):
67
"""Determine the branch locations for the other services.
69
Determine if the local branch being tested is derived from its
70
stable or next (dev) branch, and based on this, use the corresonding
71
stable or next branches for the other_services."""
73
self.log.info('OpenStackAmuletDeployment: determine branch locations')
75
# Charms outside the lp:~openstack-charmers namespace
76
base_charms = ['mysql', 'mongodb', 'nrpe']
78
# Force these charms to current series even when using an older series.
79
# ie. Use trusty/nrpe even when series is precise, as the P charm
80
# does not possess the necessary external master config and hooks.
81
force_series_current = ['nrpe']
83
if self.series in ['precise', 'trusty']:
84
base_series = self.series
86
base_series = self.current_next
88
for svc in other_services:
89
if svc['name'] in force_series_current:
90
base_series = self.current_next
91
# If a location has been explicitly set, use it
92
if svc.get('location'):
95
temp = 'lp:charms/{}/{}'
96
svc['location'] = temp.format(base_series,
99
if svc['name'] in base_charms:
100
temp = 'lp:charms/{}/{}'
101
svc['location'] = temp.format(base_series,
104
temp = 'lp:~openstack-charmers/charms/{}/{}/next'
105
svc['location'] = temp.format(self.current_next,
108
return other_services
110
def _add_services(self, this_service, other_services):
111
"""Add services to the deployment and set openstack-origin/source."""
112
self.log.info('OpenStackAmuletDeployment: adding services')
114
other_services = self._determine_branch_locations(other_services)
116
super(OpenStackAmuletDeployment, self)._add_services(this_service,
119
services = other_services
120
services.append(this_service)
122
# Charms which should use the source config option
123
use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
124
'ceph-osd', 'ceph-radosgw', 'ceph-mon']
126
# Charms which can not use openstack-origin, ie. many subordinates
127
no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe',
128
'openvswitch-odl', 'neutron-api-odl', 'odl-controller',
133
if svc['name'] not in use_source + no_origin:
134
config = {'openstack-origin': self.openstack}
135
self.d.configure(svc['name'], config)
139
if svc['name'] in use_source and svc['name'] not in no_origin:
140
config = {'source': self.source}
141
self.d.configure(svc['name'], config)
143
def _configure_services(self, configs):
144
"""Configure all of the services."""
145
self.log.info('OpenStackAmuletDeployment: configure services')
146
for service, config in six.iteritems(configs):
147
self.d.configure(service, config)
149
def _auto_wait_for_status(self, message=None, exclude_services=None,
150
include_only=None, timeout=1800):
151
"""Wait for all units to have a specific extended status, except
152
for any defined as excluded. Unless specified via message, any
153
status containing any case of 'ready' will be considered a match.
155
Examples of message usage:
157
Wait for all unit status to CONTAIN any case of 'ready' or 'ok':
158
message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE)
160
Wait for all units to reach this status (exact match):
161
message = re.compile('^Unit is ready and clustered$')
163
Wait for all units to reach any one of these (exact match):
164
message = re.compile('Unit is ready|OK|Ready')
166
Wait for at least one unit to reach this status (exact match):
169
See Amulet's sentry.wait_for_messages() for message usage detail.
170
https://github.com/juju/amulet/blob/master/amulet/sentry.py
172
:param message: Expected status match
173
:param exclude_services: List of juju service names to ignore,
174
not to be used in conjuction with include_only.
175
:param include_only: List of juju service names to exclusively check,
176
not to be used in conjuction with exclude_services.
177
:param timeout: Maximum time in seconds to wait for status match
178
:returns: None. Raises if timeout is hit.
180
self.log.info('Waiting for extended status on units...')
182
all_services = self.d.services.keys()
184
if exclude_services and include_only:
185
raise ValueError('exclude_services can not be used '
189
if isinstance(message, re._pattern_type):
190
match = message.pattern
194
self.log.debug('Custom extended status wait match: '
197
self.log.debug('Default extended status wait match: contains '
198
'READY (case-insensitive)')
199
message = re.compile('.*ready.*', re.IGNORECASE)
202
self.log.debug('Excluding services from extended status match: '
203
'{}'.format(exclude_services))
205
exclude_services = []
208
services = include_only
210
services = list(set(all_services) - set(exclude_services))
212
self.log.debug('Waiting up to {}s for extended status on services: '
213
'{}'.format(timeout, services))
214
service_messages = {service: message for service in services}
215
self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
218
def _get_openstack_release(self):
219
"""Get openstack release.
221
Return an integer representing the enum value of the openstack
224
# Must be ordered by OpenStack release (not by Ubuntu release):
225
(self.precise_essex, self.precise_folsom, self.precise_grizzly,
226
self.precise_havana, self.precise_icehouse,
227
self.trusty_icehouse, self.trusty_juno, self.utopic_juno,
228
self.trusty_kilo, self.vivid_kilo, self.trusty_liberty,
229
self.wily_liberty, self.trusty_mitaka,
230
self.xenial_mitaka) = range(14)
233
('precise', None): self.precise_essex,
234
('precise', 'cloud:precise-folsom'): self.precise_folsom,
235
('precise', 'cloud:precise-grizzly'): self.precise_grizzly,
236
('precise', 'cloud:precise-havana'): self.precise_havana,
237
('precise', 'cloud:precise-icehouse'): self.precise_icehouse,
238
('trusty', None): self.trusty_icehouse,
239
('trusty', 'cloud:trusty-juno'): self.trusty_juno,
240
('trusty', 'cloud:trusty-kilo'): self.trusty_kilo,
241
('trusty', 'cloud:trusty-liberty'): self.trusty_liberty,
242
('trusty', 'cloud:trusty-mitaka'): self.trusty_mitaka,
243
('utopic', None): self.utopic_juno,
244
('vivid', None): self.vivid_kilo,
245
('wily', None): self.wily_liberty,
246
('xenial', None): self.xenial_mitaka}
247
return releases[(self.series, self.openstack)]
249
def _get_openstack_release_string(self):
250
"""Get openstack release string.
252
Return a string representing the openstack release.
254
releases = OrderedDict([
255
('precise', 'essex'),
256
('quantal', 'folsom'),
257
('raring', 'grizzly'),
259
('trusty', 'icehouse'),
263
('xenial', 'mitaka'),
266
os_origin = self.openstack.split(':')[1]
267
return os_origin.split('%s-' % self.series)[1].split('/')[0]
269
return releases[self.series]
271
def get_ceph_expected_pools(self, radosgw=False):
272
"""Return a list of expected ceph pools in a ceph + cinder + glance
273
test scenario, based on OpenStack release and whether ceph radosgw
274
is flagged as present or not."""
276
if self._get_openstack_release() >= self.trusty_kilo: