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

« back to all changes in this revision

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

  • Committer: anton.skriptsov at nexenta
  • Date: 2015-11-12 19:21:10 UTC
  • Revision ID: anton.skriptsov@nexenta.com-20151112192110-y49mpvnvf3pp3xk1
initial

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']
 
125
 
 
126
        # Charms which can not use openstack-origin, ie. many subordinates
 
127
        no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe']
 
128
 
 
129
        if self.openstack:
 
130
            for svc in services:
 
131
                if svc['name'] not in use_source + no_origin:
 
132
                    config = {'openstack-origin': self.openstack}
 
133
                    self.d.configure(svc['name'], config)
 
134
 
 
135
        if self.source:
 
136
            for svc in services:
 
137
                if svc['name'] in use_source and svc['name'] not in no_origin:
 
138
                    config = {'source': self.source}
 
139
                    self.d.configure(svc['name'], config)
 
140
 
 
141
    def _configure_services(self, configs):
 
142
        """Configure all of the services."""
 
143
        self.log.info('OpenStackAmuletDeployment:  configure services')
 
144
        for service, config in six.iteritems(configs):
 
145
            self.d.configure(service, config)
 
146
 
 
147
    def _auto_wait_for_status(self, message=None, exclude_services=None,
 
148
                              include_only=None, timeout=1800):
 
149
        """Wait for all units to have a specific extended status, except
 
150
        for any defined as excluded.  Unless specified via message, any
 
151
        status containing any case of 'ready' will be considered a match.
 
152
 
 
153
        Examples of message usage:
 
154
 
 
155
          Wait for all unit status to CONTAIN any case of 'ready' or 'ok':
 
156
              message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE)
 
157
 
 
158
          Wait for all units to reach this status (exact match):
 
159
              message = re.compile('^Unit is ready and clustered$')
 
160
 
 
161
          Wait for all units to reach any one of these (exact match):
 
162
              message = re.compile('Unit is ready|OK|Ready')
 
163
 
 
164
          Wait for at least one unit to reach this status (exact match):
 
165
              message = {'ready'}
 
166
 
 
167
        See Amulet's sentry.wait_for_messages() for message usage detail.
 
168
        https://github.com/juju/amulet/blob/master/amulet/sentry.py
 
169
 
 
170
        :param message: Expected status match
 
171
        :param exclude_services: List of juju service names to ignore,
 
172
            not to be used in conjuction with include_only.
 
173
        :param include_only: List of juju service names to exclusively check,
 
174
            not to be used in conjuction with exclude_services.
 
175
        :param timeout: Maximum time in seconds to wait for status match
 
176
        :returns: None.  Raises if timeout is hit.
 
177
        """
 
178
        self.log.info('Waiting for extended status on units...')
 
179
 
 
180
        all_services = self.d.services.keys()
 
181
 
 
182
        if exclude_services and include_only:
 
183
            raise ValueError('exclude_services can not be used '
 
184
                             'with include_only')
 
185
 
 
186
        if message:
 
187
            if isinstance(message, re._pattern_type):
 
188
                match = message.pattern
 
189
            else:
 
190
                match = message
 
191
 
 
192
            self.log.debug('Custom extended status wait match: '
 
193
                           '{}'.format(match))
 
194
        else:
 
195
            self.log.debug('Default extended status wait match:  contains '
 
196
                           'READY (case-insensitive)')
 
197
            message = re.compile('.*ready.*', re.IGNORECASE)
 
198
 
 
199
        if exclude_services:
 
200
            self.log.debug('Excluding services from extended status match: '
 
201
                           '{}'.format(exclude_services))
 
202
        else:
 
203
            exclude_services = []
 
204
 
 
205
        if include_only:
 
206
            services = include_only
 
207
        else:
 
208
            services = list(set(all_services) - set(exclude_services))
 
209
 
 
210
        self.log.debug('Waiting up to {}s for extended status on services: '
 
211
                       '{}'.format(timeout, services))
 
212
        service_messages = {service: message for service in services}
 
213
        self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
 
214
        self.log.info('OK')
 
215
 
 
216
    def _get_openstack_release(self):
 
217
        """Get openstack release.
 
218
 
 
219
           Return an integer representing the enum value of the openstack
 
220
           release.
 
221
           """
 
222
        # Must be ordered by OpenStack release (not by Ubuntu release):
 
223
        (self.precise_essex, self.precise_folsom, self.precise_grizzly,
 
224
         self.precise_havana, self.precise_icehouse,
 
225
         self.trusty_icehouse, self.trusty_juno, self.utopic_juno,
 
226
         self.trusty_kilo, self.vivid_kilo, self.trusty_liberty,
 
227
         self.wily_liberty) = range(12)
 
228
 
 
229
        releases = {
 
230
            ('precise', None): self.precise_essex,
 
231
            ('precise', 'cloud:precise-folsom'): self.precise_folsom,
 
232
            ('precise', 'cloud:precise-grizzly'): self.precise_grizzly,
 
233
            ('precise', 'cloud:precise-havana'): self.precise_havana,
 
234
            ('precise', 'cloud:precise-icehouse'): self.precise_icehouse,
 
235
            ('trusty', None): self.trusty_icehouse,
 
236
            ('trusty', 'cloud:trusty-juno'): self.trusty_juno,
 
237
            ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo,
 
238
            ('trusty', 'cloud:trusty-liberty'): self.trusty_liberty,
 
239
            ('utopic', None): self.utopic_juno,
 
240
            ('vivid', None): self.vivid_kilo,
 
241
            ('wily', None): self.wily_liberty}
 
242
        return releases[(self.series, self.openstack)]
 
243
 
 
244
    def _get_openstack_release_string(self):
 
245
        """Get openstack release string.
 
246
 
 
247
           Return a string representing the openstack release.
 
248
           """
 
249
        releases = OrderedDict([
 
250
            ('precise', 'essex'),
 
251
            ('quantal', 'folsom'),
 
252
            ('raring', 'grizzly'),
 
253
            ('saucy', 'havana'),
 
254
            ('trusty', 'icehouse'),
 
255
            ('utopic', 'juno'),
 
256
            ('vivid', 'kilo'),
 
257
            ('wily', 'liberty'),
 
258
        ])
 
259
        if self.openstack:
 
260
            os_origin = self.openstack.split(':')[1]
 
261
            return os_origin.split('%s-' % self.series)[1].split('/')[0]
 
262
        else:
 
263
            return releases[self.series]
 
264
 
 
265
    def get_ceph_expected_pools(self, radosgw=False):
 
266
        """Return a list of expected ceph pools in a ceph + cinder + glance
 
267
        test scenario, based on OpenStack release and whether ceph radosgw
 
268
        is flagged as present or not."""
 
269
 
 
270
        if self._get_openstack_release() >= self.trusty_kilo:
 
271
            # Kilo or later
 
272
            pools = [
 
273
                'rbd',
 
274
                'cinder',
 
275
                'glance'
 
276
            ]
 
277
        else:
 
278
            # Juno or earlier
 
279
            pools = [
 
280
                'data',
 
281
                'metadata',
 
282
                'rbd',
 
283
                'cinder',
 
284
                'glance'
 
285
            ]
 
286
 
 
287
        if radosgw:
 
288
            pools.extend([
 
289
                '.rgw.root',
 
290
                '.rgw.control',
 
291
                '.rgw',
 
292
                '.rgw.gc',
 
293
                '.users.uid'
 
294
            ])
 
295
 
 
296
        return pools