~tribaal/charms/trusty/odl-controller/retry-on-client-call-error

« back to all changes in this revision

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

  • Committer: Robert Ayres
  • Date: 2015-02-19 22:08:13 UTC
  • Revision ID: robert.ayres@canonical.com-20150219220813-tb9hek7sppu2i3g2
Initial charm

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 os
 
19
import time
 
20
import urllib
 
21
 
 
22
import glanceclient.v1.client as glance_client
 
23
import keystoneclient.v2_0 as keystone_client
 
24
import novaclient.v1_1.client as nova_client
 
25
 
 
26
import six
 
27
 
 
28
from charmhelpers.contrib.amulet.utils import (
 
29
    AmuletUtils
 
30
)
 
31
 
 
32
DEBUG = logging.DEBUG
 
33
ERROR = logging.ERROR
 
34
 
 
35
 
 
36
class OpenStackAmuletUtils(AmuletUtils):
 
37
    """OpenStack amulet utilities.
 
38
 
 
39
       This class inherits from AmuletUtils and has additional support
 
40
       that is specifically for use by OpenStack charms.
 
41
       """
 
42
 
 
43
    def __init__(self, log_level=ERROR):
 
44
        """Initialize the deployment environment."""
 
45
        super(OpenStackAmuletUtils, self).__init__(log_level)
 
46
 
 
47
    def validate_endpoint_data(self, endpoints, admin_port, internal_port,
 
48
                               public_port, expected):
 
49
        """Validate endpoint data.
 
50
 
 
51
           Validate actual endpoint data vs expected endpoint data. The ports
 
52
           are used to find the matching endpoint.
 
53
           """
 
54
        found = False
 
55
        for ep in endpoints:
 
56
            self.log.debug('endpoint: {}'.format(repr(ep)))
 
57
            if (admin_port in ep.adminurl and
 
58
                    internal_port in ep.internalurl and
 
59
                    public_port in ep.publicurl):
 
60
                found = True
 
61
                actual = {'id': ep.id,
 
62
                          'region': ep.region,
 
63
                          'adminurl': ep.adminurl,
 
64
                          'internalurl': ep.internalurl,
 
65
                          'publicurl': ep.publicurl,
 
66
                          'service_id': ep.service_id}
 
67
                ret = self._validate_dict_data(expected, actual)
 
68
                if ret:
 
69
                    return 'unexpected endpoint data - {}'.format(ret)
 
70
 
 
71
        if not found:
 
72
            return 'endpoint not found'
 
73
 
 
74
    def validate_svc_catalog_endpoint_data(self, expected, actual):
 
75
        """Validate service catalog endpoint data.
 
76
 
 
77
           Validate a list of actual service catalog endpoints vs a list of
 
78
           expected service catalog endpoints.
 
79
           """
 
80
        self.log.debug('actual: {}'.format(repr(actual)))
 
81
        for k, v in six.iteritems(expected):
 
82
            if k in actual:
 
83
                ret = self._validate_dict_data(expected[k][0], actual[k][0])
 
84
                if ret:
 
85
                    return self.endpoint_error(k, ret)
 
86
            else:
 
87
                return "endpoint {} does not exist".format(k)
 
88
        return ret
 
89
 
 
90
    def validate_tenant_data(self, expected, actual):
 
91
        """Validate tenant data.
 
92
 
 
93
           Validate a list of actual tenant data vs list of expected tenant
 
94
           data.
 
95
           """
 
96
        self.log.debug('actual: {}'.format(repr(actual)))
 
97
        for e in expected:
 
98
            found = False
 
99
            for act in actual:
 
100
                a = {'enabled': act.enabled, 'description': act.description,
 
101
                     'name': act.name, 'id': act.id}
 
102
                if e['name'] == a['name']:
 
103
                    found = True
 
104
                    ret = self._validate_dict_data(e, a)
 
105
                    if ret:
 
106
                        return "unexpected tenant data - {}".format(ret)
 
107
            if not found:
 
108
                return "tenant {} does not exist".format(e['name'])
 
109
        return ret
 
110
 
 
111
    def validate_role_data(self, expected, actual):
 
112
        """Validate role data.
 
113
 
 
114
           Validate a list of actual role data vs a list of expected role
 
115
           data.
 
116
           """
 
117
        self.log.debug('actual: {}'.format(repr(actual)))
 
118
        for e in expected:
 
119
            found = False
 
120
            for act in actual:
 
121
                a = {'name': act.name, 'id': act.id}
 
122
                if e['name'] == a['name']:
 
123
                    found = True
 
124
                    ret = self._validate_dict_data(e, a)
 
125
                    if ret:
 
126
                        return "unexpected role data - {}".format(ret)
 
127
            if not found:
 
128
                return "role {} does not exist".format(e['name'])
 
129
        return ret
 
130
 
 
131
    def validate_user_data(self, expected, actual):
 
132
        """Validate user data.
 
133
 
 
134
           Validate a list of actual user data vs a list of expected user
 
135
           data.
 
136
           """
 
137
        self.log.debug('actual: {}'.format(repr(actual)))
 
138
        for e in expected:
 
139
            found = False
 
140
            for act in actual:
 
141
                a = {'enabled': act.enabled, 'name': act.name,
 
142
                     'email': act.email, 'tenantId': act.tenantId,
 
143
                     'id': act.id}
 
144
                if e['name'] == a['name']:
 
145
                    found = True
 
146
                    ret = self._validate_dict_data(e, a)
 
147
                    if ret:
 
148
                        return "unexpected user data - {}".format(ret)
 
149
            if not found:
 
150
                return "user {} does not exist".format(e['name'])
 
151
        return ret
 
152
 
 
153
    def validate_flavor_data(self, expected, actual):
 
154
        """Validate flavor data.
 
155
 
 
156
           Validate a list of actual flavors vs a list of expected flavors.
 
157
           """
 
158
        self.log.debug('actual: {}'.format(repr(actual)))
 
159
        act = [a.name for a in actual]
 
160
        return self._validate_list_data(expected, act)
 
161
 
 
162
    def tenant_exists(self, keystone, tenant):
 
163
        """Return True if tenant exists."""
 
164
        return tenant in [t.name for t in keystone.tenants.list()]
 
165
 
 
166
    def authenticate_keystone_admin(self, keystone_sentry, user, password,
 
167
                                    tenant):
 
168
        """Authenticates admin user with the keystone admin endpoint."""
 
169
        unit = keystone_sentry
 
170
        service_ip = unit.relation('shared-db',
 
171
                                   'mysql:shared-db')['private-address']
 
172
        ep = "http://{}:35357/v2.0".format(service_ip.strip().decode('utf-8'))
 
173
        return keystone_client.Client(username=user, password=password,
 
174
                                      tenant_name=tenant, auth_url=ep)
 
175
 
 
176
    def authenticate_keystone_user(self, keystone, user, password, tenant):
 
177
        """Authenticates a regular user with the keystone public endpoint."""
 
178
        ep = keystone.service_catalog.url_for(service_type='identity',
 
179
                                              endpoint_type='publicURL')
 
180
        return keystone_client.Client(username=user, password=password,
 
181
                                      tenant_name=tenant, auth_url=ep)
 
182
 
 
183
    def authenticate_glance_admin(self, keystone):
 
184
        """Authenticates admin user with glance."""
 
185
        ep = keystone.service_catalog.url_for(service_type='image',
 
186
                                              endpoint_type='adminURL')
 
187
        return glance_client.Client(ep, token=keystone.auth_token)
 
188
 
 
189
    def authenticate_nova_user(self, keystone, user, password, tenant):
 
190
        """Authenticates a regular user with nova-api."""
 
191
        ep = keystone.service_catalog.url_for(service_type='identity',
 
192
                                              endpoint_type='publicURL')
 
193
        return nova_client.Client(username=user, api_key=password,
 
194
                                  project_id=tenant, auth_url=ep)
 
195
 
 
196
    def create_cirros_image(self, glance, image_name):
 
197
        """Download the latest cirros image and upload it to glance."""
 
198
        http_proxy = os.getenv('AMULET_HTTP_PROXY')
 
199
        self.log.debug('AMULET_HTTP_PROXY: {}'.format(http_proxy))
 
200
        if http_proxy:
 
201
            proxies = {'http': http_proxy}
 
202
            opener = urllib.FancyURLopener(proxies)
 
203
        else:
 
204
            opener = urllib.FancyURLopener()
 
205
 
 
206
        f = opener.open("http://download.cirros-cloud.net/version/released")
 
207
        version = f.read().strip()
 
208
        cirros_img = "cirros-{}-x86_64-disk.img".format(version)
 
209
        local_path = os.path.join('tests', cirros_img)
 
210
 
 
211
        if not os.path.exists(local_path):
 
212
            cirros_url = "http://{}/{}/{}".format("download.cirros-cloud.net",
 
213
                                                  version, cirros_img)
 
214
            opener.retrieve(cirros_url, local_path)
 
215
        f.close()
 
216
 
 
217
        with open(local_path) as f:
 
218
            image = glance.images.create(name=image_name, is_public=True,
 
219
                                         disk_format='qcow2',
 
220
                                         container_format='bare', data=f)
 
221
        count = 1
 
222
        status = image.status
 
223
        while status != 'active' and count < 10:
 
224
            time.sleep(3)
 
225
            image = glance.images.get(image.id)
 
226
            status = image.status
 
227
            self.log.debug('image status: {}'.format(status))
 
228
            count += 1
 
229
 
 
230
        if status != 'active':
 
231
            self.log.error('image creation timed out')
 
232
            return None
 
233
 
 
234
        return image
 
235
 
 
236
    def delete_image(self, glance, image):
 
237
        """Delete the specified image."""
 
238
        num_before = len(list(glance.images.list()))
 
239
        glance.images.delete(image)
 
240
 
 
241
        count = 1
 
242
        num_after = len(list(glance.images.list()))
 
243
        while num_after != (num_before - 1) and count < 10:
 
244
            time.sleep(3)
 
245
            num_after = len(list(glance.images.list()))
 
246
            self.log.debug('number of images: {}'.format(num_after))
 
247
            count += 1
 
248
 
 
249
        if num_after != (num_before - 1):
 
250
            self.log.error('image deletion timed out')
 
251
            return False
 
252
 
 
253
        return True
 
254
 
 
255
    def create_instance(self, nova, image_name, instance_name, flavor):
 
256
        """Create the specified instance."""
 
257
        image = nova.images.find(name=image_name)
 
258
        flavor = nova.flavors.find(name=flavor)
 
259
        instance = nova.servers.create(name=instance_name, image=image,
 
260
                                       flavor=flavor)
 
261
 
 
262
        count = 1
 
263
        status = instance.status
 
264
        while status != 'ACTIVE' and count < 60:
 
265
            time.sleep(3)
 
266
            instance = nova.servers.get(instance.id)
 
267
            status = instance.status
 
268
            self.log.debug('instance status: {}'.format(status))
 
269
            count += 1
 
270
 
 
271
        if status != 'ACTIVE':
 
272
            self.log.error('instance creation timed out')
 
273
            return None
 
274
 
 
275
        return instance
 
276
 
 
277
    def delete_instance(self, nova, instance):
 
278
        """Delete the specified instance."""
 
279
        num_before = len(list(nova.servers.list()))
 
280
        nova.servers.delete(instance)
 
281
 
 
282
        count = 1
 
283
        num_after = len(list(nova.servers.list()))
 
284
        while num_after != (num_before - 1) and count < 10:
 
285
            time.sleep(3)
 
286
            num_after = len(list(nova.servers.list()))
 
287
            self.log.debug('number of instances: {}'.format(num_after))
 
288
            count += 1
 
289
 
 
290
        if num_after != (num_before - 1):
 
291
            self.log.error('instance deletion timed out')
 
292
            return False
 
293
 
 
294
        return True