~anton-skriptsov/charms/trusty/cinder-nexentaedge/trunk

« back to all changes in this revision

Viewing changes to tests/charmhelpers/contrib/openstack/amulet/deployment.py

  • Committer: anton.skriptsov at nexenta
  • Date: 2016-04-15 12:20:00 UTC
  • Revision ID: anton.skriptsov@nexenta.com-20160415122000-v6ml4rqq0l9duj6t
update wheel

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2014-2015 Canonical Limited.
 
2
#
 
3
# This file is part of charm-helpers.
 
4
#
 
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.
 
8
#
 
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.
 
13
#
 
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/>.
 
16
 
 
17
import logging
 
18
import re
 
19
import sys
 
20
import six
 
21
from collections import OrderedDict
 
22
from charmhelpers.contrib.amulet.deployment import (
 
23
    AmuletDeployment
 
24
)
 
25
 
 
26
DEBUG = logging.DEBUG
 
27
ERROR = logging.ERROR
 
28
 
 
29
 
 
30
class OpenStackAmuletDeployment(AmuletDeployment):
 
31
    """OpenStack amulet deployment.
 
32
 
 
33
       This class inherits from AmuletDeployment and has additional support
 
34
       that is specifically for use by OpenStack charms.
 
35
       """
 
36
 
 
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
 
44
        self.source = source
 
45
        self.stable = stable
 
46
        # Note(coreycb): this needs to be changed when new next branches come
 
47
        # out.
 
48
        self.current_next = "trusty"
 
49
 
 
50
    def get_logger(self, name="deployment-logger", level=logging.DEBUG):
 
51
        """Get a logger object that will log to stdout."""
 
52
        log = logging
 
53
        logger = log.getLogger(name)
 
54
        fmt = log.Formatter("%(asctime)s %(funcName)s "
 
55
                            "%(levelname)s: %(message)s")
 
56
 
 
57
        handler = log.StreamHandler(stream=sys.stdout)
 
58
        handler.setLevel(level)
 
59
        handler.setFormatter(fmt)
 
60
 
 
61
        logger.addHandler(handler)
 
62
        logger.setLevel(level)
 
63
 
 
64
        return logger
 
65
 
 
66
    def _determine_branch_locations(self, other_services):
 
67
        """Determine the branch locations for the other services.
 
68
 
 
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."""
 
72
 
 
73
        self.log.info('OpenStackAmuletDeployment:  determine branch locations')
 
74
 
 
75
        # Charms outside the lp:~openstack-charmers namespace
 
76
        base_charms = ['mysql', 'mongodb', 'nrpe']
 
77
 
 
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']
 
82
 
 
83
        if self.series in ['precise', 'trusty']:
 
84
            base_series = self.series
 
85
        else:
 
86
            base_series = self.current_next
 
87
 
 
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'):
 
93
                continue
 
94
            if self.stable:
 
95
                temp = 'lp:charms/{}/{}'
 
96
                svc['location'] = temp.format(base_series,
 
97
                                              svc['name'])
 
98
            else:
 
99
                if svc['name'] in base_charms:
 
100
                    temp = 'lp:charms/{}/{}'
 
101
                    svc['location'] = temp.format(base_series,
 
102
                                                  svc['name'])
 
103
                else:
 
104
                    temp = 'lp:~openstack-charmers/charms/{}/{}/next'
 
105
                    svc['location'] = temp.format(self.current_next,
 
106
                                                  svc['name'])
 
107
 
 
108
        return other_services
 
109
 
 
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')
 
113
 
 
114
        other_services = self._determine_branch_locations(other_services)
 
115
 
 
116
        super(OpenStackAmuletDeployment, self)._add_services(this_service,
 
117
                                                             other_services)
 
118
 
 
119
        services = other_services
 
120
        services.append(this_service)
 
121
 
 
122
        # Charms which should use the source config option
 
123
        use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
 
124
                      'ceph-osd', 'ceph-radosgw', 'ceph-mon']
 
125
 
 
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',
 
129
                     'cinder-backup']
 
130
 
 
131
        if self.openstack:
 
132
            for svc in services:
 
133
                if svc['name'] not in use_source + no_origin:
 
134
                    config = {'openstack-origin': self.openstack}
 
135
                    self.d.configure(svc['name'], config)
 
136
 
 
137
        if self.source:
 
138
            for svc in services:
 
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)
 
142
 
 
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)
 
148
 
 
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.
 
154
 
 
155
        Examples of message usage:
 
156
 
 
157
          Wait for all unit status to CONTAIN any case of 'ready' or 'ok':
 
158
              message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE)
 
159
 
 
160
          Wait for all units to reach this status (exact match):
 
161
              message = re.compile('^Unit is ready and clustered$')
 
162
 
 
163
          Wait for all units to reach any one of these (exact match):
 
164
              message = re.compile('Unit is ready|OK|Ready')
 
165
 
 
166
          Wait for at least one unit to reach this status (exact match):
 
167
              message = {'ready'}
 
168
 
 
169
        See Amulet's sentry.wait_for_messages() for message usage detail.
 
170
        https://github.com/juju/amulet/blob/master/amulet/sentry.py
 
171
 
 
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.
 
179
        """
 
180
        self.log.info('Waiting for extended status on units...')
 
181
 
 
182
        all_services = self.d.services.keys()
 
183
 
 
184
        if exclude_services and include_only:
 
185
            raise ValueError('exclude_services can not be used '
 
186
                             'with include_only')
 
187
 
 
188
        if message:
 
189
            if isinstance(message, re._pattern_type):
 
190
                match = message.pattern
 
191
            else:
 
192
                match = message
 
193
 
 
194
            self.log.debug('Custom extended status wait match: '
 
195
                           '{}'.format(match))
 
196
        else:
 
197
            self.log.debug('Default extended status wait match:  contains '
 
198
                           'READY (case-insensitive)')
 
199
            message = re.compile('.*ready.*', re.IGNORECASE)
 
200
 
 
201
        if exclude_services:
 
202
            self.log.debug('Excluding services from extended status match: '
 
203
                           '{}'.format(exclude_services))
 
204
        else:
 
205
            exclude_services = []
 
206
 
 
207
        if include_only:
 
208
            services = include_only
 
209
        else:
 
210
            services = list(set(all_services) - set(exclude_services))
 
211
 
 
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)
 
216
        self.log.info('OK')
 
217
 
 
218
    def _get_openstack_release(self):
 
219
        """Get openstack release.
 
220
 
 
221
           Return an integer representing the enum value of the openstack
 
222
           release.
 
223
           """
 
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)
 
231
 
 
232
        releases = {
 
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)]
 
248
 
 
249
    def _get_openstack_release_string(self):
 
250
        """Get openstack release string.
 
251
 
 
252
           Return a string representing the openstack release.
 
253
           """
 
254
        releases = OrderedDict([
 
255
            ('precise', 'essex'),
 
256
            ('quantal', 'folsom'),
 
257
            ('raring', 'grizzly'),
 
258
            ('saucy', 'havana'),
 
259
            ('trusty', 'icehouse'),
 
260
            ('utopic', 'juno'),
 
261
            ('vivid', 'kilo'),
 
262
            ('wily', 'liberty'),
 
263
            ('xenial', 'mitaka'),
 
264
        ])
 
265
        if self.openstack:
 
266
            os_origin = self.openstack.split(':')[1]
 
267
            return os_origin.split('%s-' % self.series)[1].split('/')[0]
 
268
        else:
 
269
            return releases[self.series]
 
270
 
 
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."""
 
275
 
 
276
        if self._get_openstack_release() >= self.trusty_kilo:
 
277
            # Kilo or later
 
278
            pools = [
 
279
                'rbd',
 
280
                'cinder',
 
281
                'glance'
 
282
            ]
 
283
        else:
 
284
            # Juno or earlier
 
285
            pools = [
 
286
                'data',
 
287
                'metadata',
 
288
                'rbd',
 
289
                'cinder',
 
290
                'glance'
 
291
            ]
 
292
 
 
293
        if radosgw:
 
294
            pools.extend([
 
295
                '.rgw.root',
 
296
                '.rgw.control',
 
297
                '.rgw',
 
298
                '.rgw.gc',
 
299
                '.users.uid'
 
300
            ])
 
301
 
 
302
        return pools