~plumgrid-team/charms/trusty/plumgrid-director/trunk

« back to all changes in this revision

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

  • Committer: bbaqar at plumgrid
  • Date: 2016-04-25 09:14:38 UTC
  • mfrom: (30.1.3 plumgrid-director)
  • Revision ID: bbaqar@plumgrid.com-20160425091438-4hk5s00dydf00jem
Merge: Liberty/Mitaka support

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# You should have received a copy of the GNU Lesser General Public License
15
15
# along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>.
16
16
 
 
17
import logging
 
18
import re
 
19
import sys
17
20
import six
18
21
from collections import OrderedDict
19
22
from charmhelpers.contrib.amulet.deployment import (
20
23
    AmuletDeployment
21
24
)
22
25
 
 
26
DEBUG = logging.DEBUG
 
27
ERROR = logging.ERROR
 
28
 
23
29
 
24
30
class OpenStackAmuletDeployment(AmuletDeployment):
25
31
    """OpenStack amulet deployment.
28
34
       that is specifically for use by OpenStack charms.
29
35
       """
30
36
 
31
 
    def __init__(self, series=None, openstack=None, source=None, stable=True):
 
37
    def __init__(self, series=None, openstack=None, source=None,
 
38
                 stable=True, log_level=DEBUG):
32
39
        """Initialize the deployment environment."""
33
40
        super(OpenStackAmuletDeployment, self).__init__(series)
 
41
        self.log = self.get_logger(level=log_level)
 
42
        self.log.info('OpenStackAmuletDeployment:  init')
34
43
        self.openstack = openstack
35
44
        self.source = source
36
45
        self.stable = stable
38
47
        # out.
39
48
        self.current_next = "trusty"
40
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
 
41
66
    def _determine_branch_locations(self, other_services):
42
67
        """Determine the branch locations for the other services.
43
68
 
44
69
           Determine if the local branch being tested is derived from its
45
70
           stable or next (dev) branch, and based on this, use the corresonding
46
71
           stable or next branches for the other_services."""
47
 
        base_charms = ['mysql', 'mongodb']
 
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']
48
82
 
49
83
        if self.series in ['precise', 'trusty']:
50
84
            base_series = self.series
51
85
        else:
52
86
            base_series = self.current_next
53
87
 
54
 
        if self.stable:
55
 
            for svc in other_services:
 
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:
56
95
                temp = 'lp:charms/{}/{}'
57
96
                svc['location'] = temp.format(base_series,
58
97
                                              svc['name'])
59
 
        else:
60
 
            for svc in other_services:
 
98
            else:
61
99
                if svc['name'] in base_charms:
62
100
                    temp = 'lp:charms/{}/{}'
63
101
                    svc['location'] = temp.format(base_series,
66
104
                    temp = 'lp:~openstack-charmers/charms/{}/{}/next'
67
105
                    svc['location'] = temp.format(self.current_next,
68
106
                                                  svc['name'])
 
107
 
69
108
        return other_services
70
109
 
71
110
    def _add_services(self, this_service, other_services):
72
111
        """Add services to the deployment and set openstack-origin/source."""
 
112
        self.log.info('OpenStackAmuletDeployment:  adding services')
 
113
 
73
114
        other_services = self._determine_branch_locations(other_services)
74
115
 
75
116
        super(OpenStackAmuletDeployment, self)._add_services(this_service,
77
118
 
78
119
        services = other_services
79
120
        services.append(this_service)
 
121
 
 
122
        # Charms which should use the source config option
80
123
        use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
81
 
                      'ceph-osd', 'ceph-radosgw']
82
 
        # Most OpenStack subordinate charms do not expose an origin option
83
 
        # as that is controlled by the principle.
84
 
        ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch']
 
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', 'nexentaedge-data',
 
130
                     'nexentaedge-iscsi-gw', 'nexentaedge-swift-gw',
 
131
                     'cinder-nexentaedge', 'nexentaedge-mgmt']
85
132
 
86
133
        if self.openstack:
87
134
            for svc in services:
88
 
                if svc['name'] not in use_source + ignore:
 
135
                if svc['name'] not in use_source + no_origin:
89
136
                    config = {'openstack-origin': self.openstack}
90
137
                    self.d.configure(svc['name'], config)
91
138
 
92
139
        if self.source:
93
140
            for svc in services:
94
 
                if svc['name'] in use_source and svc['name'] not in ignore:
 
141
                if svc['name'] in use_source and svc['name'] not in no_origin:
95
142
                    config = {'source': self.source}
96
143
                    self.d.configure(svc['name'], config)
97
144
 
98
145
    def _configure_services(self, configs):
99
146
        """Configure all of the services."""
 
147
        self.log.info('OpenStackAmuletDeployment:  configure services')
100
148
        for service, config in six.iteritems(configs):
101
149
            self.d.configure(service, config)
102
150
 
 
151
    def _auto_wait_for_status(self, message=None, exclude_services=None,
 
152
                              include_only=None, timeout=1800):
 
153
        """Wait for all units to have a specific extended status, except
 
154
        for any defined as excluded.  Unless specified via message, any
 
155
        status containing any case of 'ready' will be considered a match.
 
156
 
 
157
        Examples of message usage:
 
158
 
 
159
          Wait for all unit status to CONTAIN any case of 'ready' or 'ok':
 
160
              message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE)
 
161
 
 
162
          Wait for all units to reach this status (exact match):
 
163
              message = re.compile('^Unit is ready and clustered$')
 
164
 
 
165
          Wait for all units to reach any one of these (exact match):
 
166
              message = re.compile('Unit is ready|OK|Ready')
 
167
 
 
168
          Wait for at least one unit to reach this status (exact match):
 
169
              message = {'ready'}
 
170
 
 
171
        See Amulet's sentry.wait_for_messages() for message usage detail.
 
172
        https://github.com/juju/amulet/blob/master/amulet/sentry.py
 
173
 
 
174
        :param message: Expected status match
 
175
        :param exclude_services: List of juju service names to ignore,
 
176
            not to be used in conjuction with include_only.
 
177
        :param include_only: List of juju service names to exclusively check,
 
178
            not to be used in conjuction with exclude_services.
 
179
        :param timeout: Maximum time in seconds to wait for status match
 
180
        :returns: None.  Raises if timeout is hit.
 
181
        """
 
182
        self.log.info('Waiting for extended status on units...')
 
183
 
 
184
        all_services = self.d.services.keys()
 
185
 
 
186
        if exclude_services and include_only:
 
187
            raise ValueError('exclude_services can not be used '
 
188
                             'with include_only')
 
189
 
 
190
        if message:
 
191
            if isinstance(message, re._pattern_type):
 
192
                match = message.pattern
 
193
            else:
 
194
                match = message
 
195
 
 
196
            self.log.debug('Custom extended status wait match: '
 
197
                           '{}'.format(match))
 
198
        else:
 
199
            self.log.debug('Default extended status wait match:  contains '
 
200
                           'READY (case-insensitive)')
 
201
            message = re.compile('.*ready.*', re.IGNORECASE)
 
202
 
 
203
        if exclude_services:
 
204
            self.log.debug('Excluding services from extended status match: '
 
205
                           '{}'.format(exclude_services))
 
206
        else:
 
207
            exclude_services = []
 
208
 
 
209
        if include_only:
 
210
            services = include_only
 
211
        else:
 
212
            services = list(set(all_services) - set(exclude_services))
 
213
 
 
214
        self.log.debug('Waiting up to {}s for extended status on services: '
 
215
                       '{}'.format(timeout, services))
 
216
        service_messages = {service: message for service in services}
 
217
        self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
 
218
        self.log.info('OK')
 
219
 
103
220
    def _get_openstack_release(self):
104
221
        """Get openstack release.
105
222
 
111
228
         self.precise_havana, self.precise_icehouse,
112
229
         self.trusty_icehouse, self.trusty_juno, self.utopic_juno,
113
230
         self.trusty_kilo, self.vivid_kilo, self.trusty_liberty,
114
 
         self.wily_liberty) = range(12)
 
231
         self.wily_liberty, self.trusty_mitaka,
 
232
         self.xenial_mitaka) = range(14)
115
233
 
116
234
        releases = {
117
235
            ('precise', None): self.precise_essex,
123
241
            ('trusty', 'cloud:trusty-juno'): self.trusty_juno,
124
242
            ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo,
125
243
            ('trusty', 'cloud:trusty-liberty'): self.trusty_liberty,
 
244
            ('trusty', 'cloud:trusty-mitaka'): self.trusty_mitaka,
126
245
            ('utopic', None): self.utopic_juno,
127
246
            ('vivid', None): self.vivid_kilo,
128
 
            ('wily', None): self.wily_liberty}
 
247
            ('wily', None): self.wily_liberty,
 
248
            ('xenial', None): self.xenial_mitaka}
129
249
        return releases[(self.series, self.openstack)]
130
250
 
131
251
    def _get_openstack_release_string(self):
142
262
            ('utopic', 'juno'),
143
263
            ('vivid', 'kilo'),
144
264
            ('wily', 'liberty'),
 
265
            ('xenial', 'mitaka'),
145
266
        ])
146
267
        if self.openstack:
147
268
            os_origin = self.openstack.split(':')[1]