~ubuntu-branches/ubuntu/vivid/heat/vivid

« back to all changes in this revision

Viewing changes to heat_integrationtests/common/test.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Chuck Short, Corey Bryant
  • Date: 2015-01-06 08:55:22 UTC
  • mfrom: (1.1.21)
  • Revision ID: package-import@ubuntu.com-20150106085522-4o3hnaff5lacvtrf
Tags: 2015.1~b1-0ubuntu1
[ Chuck Short ]
* Open up for vivid.
* debian/control: Update bzr branch. 
* debian/control: Add python-saharaclient,
  python-osprofiler, python-oslo.middleware, python-oslo.serialization.
* debian/patches/fix-reqirements.patch: Refreshed.
* debian/patches/skip-tests.patch: Updated to skip more tests.
* debian/rules: Skip integration tests.

[ Corey Bryant ]
* New upstream release.
  - d/control: Align requirements with upstream.
  - d/watch: Update uversionmangle for kilo beta naming.
  - d/rules: Generate heat.conf.sample and apply patch before copy.
  - d/rules: Run base tests instead of integration tests.
  - d/p/fix-requirements.patch: Refreshed.
  - d/p/remove-gettextutils-import.patch: Cherry picked from master.
* d/control: Bumped Standards-Version to 3.9.6.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 
2
#    not use this file except in compliance with the License. You may obtain
 
3
#    a copy of the License at
 
4
#
 
5
#         http://www.apache.org/licenses/LICENSE-2.0
 
6
#
 
7
#    Unless required by applicable law or agreed to in writing, software
 
8
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
9
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
10
#    License for the specific language governing permissions and limitations
 
11
#    under the License.
 
12
 
 
13
import fixtures
 
14
import logging
 
15
import os
 
16
import random
 
17
import re
 
18
import six
 
19
import subprocess
 
20
import testscenarios
 
21
import testtools
 
22
import time
 
23
 
 
24
from heatclient import exc as heat_exceptions
 
25
 
 
26
from heat.openstack.common import timeutils
 
27
from heat_integrationtests.common import clients
 
28
from heat_integrationtests.common import config
 
29
from heat_integrationtests.common import exceptions
 
30
from heat_integrationtests.common import remote_client
 
31
 
 
32
LOG = logging.getLogger(__name__)
 
33
_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
 
34
 
 
35
 
 
36
def call_until_true(func, duration, sleep_for):
 
37
    """
 
38
    Call the given function until it returns True (and return True) or
 
39
    until the specified duration (in seconds) elapses (and return
 
40
    False).
 
41
 
 
42
    :param func: A zero argument callable that returns True on success.
 
43
    :param duration: The number of seconds for which to attempt a
 
44
        successful call of the function.
 
45
    :param sleep_for: The number of seconds to sleep after an unsuccessful
 
46
                      invocation of the function.
 
47
    """
 
48
    now = time.time()
 
49
    timeout = now + duration
 
50
    while now < timeout:
 
51
        if func():
 
52
            return True
 
53
        LOG.debug("Sleeping for %d seconds", sleep_for)
 
54
        time.sleep(sleep_for)
 
55
        now = time.time()
 
56
    return False
 
57
 
 
58
 
 
59
def rand_name(name=''):
 
60
    randbits = str(random.randint(1, 0x7fffffff))
 
61
    if name:
 
62
        return name + '-' + randbits
 
63
    else:
 
64
        return randbits
 
65
 
 
66
 
 
67
class HeatIntegrationTest(testscenarios.WithScenarios,
 
68
                          testtools.TestCase):
 
69
 
 
70
    def setUp(self):
 
71
        super(HeatIntegrationTest, self).setUp()
 
72
 
 
73
        self.conf = config.init_conf()
 
74
 
 
75
        self.assertIsNotNone(self.conf.auth_url,
 
76
                             'No auth_url configured')
 
77
        self.assertIsNotNone(self.conf.username,
 
78
                             'No username configured')
 
79
        self.assertIsNotNone(self.conf.password,
 
80
                             'No password configured')
 
81
 
 
82
        self.manager = clients.ClientManager(self.conf)
 
83
        self.identity_client = self.manager.identity_client
 
84
        self.orchestration_client = self.manager.orchestration_client
 
85
        self.compute_client = self.manager.compute_client
 
86
        self.network_client = self.manager.network_client
 
87
        self.volume_client = self.manager.volume_client
 
88
        self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT))
 
89
 
 
90
    def status_timeout(self, things, thing_id, expected_status,
 
91
                       error_status='ERROR',
 
92
                       not_found_exception=heat_exceptions.NotFound):
 
93
        """
 
94
        Given a thing and an expected status, do a loop, sleeping
 
95
        for a configurable amount of time, checking for the
 
96
        expected status to show. At any time, if the returned
 
97
        status of the thing is ERROR, fail out.
 
98
        """
 
99
        self._status_timeout(things, thing_id,
 
100
                             expected_status=expected_status,
 
101
                             error_status=error_status,
 
102
                             not_found_exception=not_found_exception)
 
103
 
 
104
    def _status_timeout(self,
 
105
                        things,
 
106
                        thing_id,
 
107
                        expected_status=None,
 
108
                        allow_notfound=False,
 
109
                        error_status='ERROR',
 
110
                        not_found_exception=heat_exceptions.NotFound):
 
111
 
 
112
        log_status = expected_status if expected_status else ''
 
113
        if allow_notfound:
 
114
            log_status += ' or NotFound' if log_status != '' else 'NotFound'
 
115
 
 
116
        def check_status():
 
117
            # python-novaclient has resources available to its client
 
118
            # that all implement a get() method taking an identifier
 
119
            # for the singular resource to retrieve.
 
120
            try:
 
121
                thing = things.get(thing_id)
 
122
            except not_found_exception:
 
123
                if allow_notfound:
 
124
                    return True
 
125
                raise
 
126
            except Exception as e:
 
127
                if allow_notfound and self.not_found_exception(e):
 
128
                    return True
 
129
                raise
 
130
 
 
131
            new_status = thing.status
 
132
 
 
133
            # Some components are reporting error status in lower case
 
134
            # so case sensitive comparisons can really mess things
 
135
            # up.
 
136
            if new_status.lower() == error_status.lower():
 
137
                message = ("%s failed to get to expected status (%s). "
 
138
                           "In %s state.") % (thing, expected_status,
 
139
                                              new_status)
 
140
                raise exceptions.BuildErrorException(message,
 
141
                                                     server_id=thing_id)
 
142
            elif new_status == expected_status and expected_status is not None:
 
143
                return True  # All good.
 
144
            LOG.debug("Waiting for %s to get to %s status. "
 
145
                      "Currently in %s status",
 
146
                      thing, log_status, new_status)
 
147
        if not call_until_true(
 
148
                check_status,
 
149
                self.conf.build_timeout,
 
150
                self.conf.build_interval):
 
151
            message = ("Timed out waiting for thing %s "
 
152
                       "to become %s") % (thing_id, log_status)
 
153
            raise exceptions.TimeoutException(message)
 
154
 
 
155
    def get_remote_client(self, server_or_ip, username, private_key=None):
 
156
        if isinstance(server_or_ip, six.string_types):
 
157
            ip = server_or_ip
 
158
        else:
 
159
            network_name_for_ssh = self.conf.network_for_ssh
 
160
            ip = server_or_ip.networks[network_name_for_ssh][0]
 
161
        if private_key is None:
 
162
            private_key = self.keypair.private_key
 
163
        linux_client = remote_client.RemoteClient(ip, username,
 
164
                                                  pkey=private_key,
 
165
                                                  conf=self.conf)
 
166
        try:
 
167
            linux_client.validate_authentication()
 
168
        except exceptions.SSHTimeout:
 
169
            LOG.exception('ssh connection to %s failed' % ip)
 
170
            raise
 
171
 
 
172
        return linux_client
 
173
 
 
174
    def _log_console_output(self, servers=None):
 
175
        if not servers:
 
176
            servers = self.compute_client.servers.list()
 
177
        for server in servers:
 
178
            LOG.debug('Console output for %s', server.id)
 
179
            LOG.debug(server.get_console_output())
 
180
 
 
181
    def _load_template(self, base_file, file_name):
 
182
        filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
 
183
                                file_name)
 
184
        with open(filepath) as f:
 
185
            return f.read()
 
186
 
 
187
    def create_keypair(self, client=None, name=None):
 
188
        if client is None:
 
189
            client = self.compute_client
 
190
        if name is None:
 
191
            name = rand_name('heat-keypair')
 
192
        keypair = client.keypairs.create(name)
 
193
        self.assertEqual(keypair.name, name)
 
194
 
 
195
        def delete_keypair():
 
196
            keypair.delete()
 
197
 
 
198
        self.addCleanup(delete_keypair)
 
199
        return keypair
 
200
 
 
201
    @classmethod
 
202
    def _stack_rand_name(cls):
 
203
        return rand_name(cls.__name__)
 
204
 
 
205
    def _get_default_network(self):
 
206
        networks = self.network_client.list_networks()
 
207
        for net in networks['networks']:
 
208
            if net['name'] == self.conf.fixed_network_name:
 
209
                return net
 
210
 
 
211
    @staticmethod
 
212
    def _stack_output(stack, output_key):
 
213
        """Return a stack output value for a given key."""
 
214
        return next((o['output_value'] for o in stack.outputs
 
215
                    if o['output_key'] == output_key), None)
 
216
 
 
217
    def _ping_ip_address(self, ip_address, should_succeed=True):
 
218
        cmd = ['ping', '-c1', '-w1', ip_address]
 
219
 
 
220
        def ping():
 
221
            proc = subprocess.Popen(cmd,
 
222
                                    stdout=subprocess.PIPE,
 
223
                                    stderr=subprocess.PIPE)
 
224
            proc.wait()
 
225
            return (proc.returncode == 0) == should_succeed
 
226
 
 
227
        return call_until_true(
 
228
            ping, self.conf.build_timeout, 1)
 
229
 
 
230
    def _wait_for_resource_status(self, stack_identifier, resource_name,
 
231
                                  status, failure_pattern='^.*_FAILED$',
 
232
                                  success_on_not_found=False):
 
233
        """Waits for a Resource to reach a given status."""
 
234
        fail_regexp = re.compile(failure_pattern)
 
235
        build_timeout = self.conf.build_timeout
 
236
        build_interval = self.conf.build_interval
 
237
 
 
238
        start = timeutils.utcnow()
 
239
        while timeutils.delta_seconds(start,
 
240
                                      timeutils.utcnow()) < build_timeout:
 
241
            try:
 
242
                res = self.client.resources.get(
 
243
                    stack_identifier, resource_name)
 
244
            except heat_exceptions.HTTPNotFound:
 
245
                if success_on_not_found:
 
246
                    return
 
247
                # ignore this, as the resource may not have
 
248
                # been created yet
 
249
            else:
 
250
                if res.resource_status == status:
 
251
                    return
 
252
                if fail_regexp.search(res.resource_status):
 
253
                    raise exceptions.StackResourceBuildErrorException(
 
254
                        resource_name=res.resource_name,
 
255
                        stack_identifier=stack_identifier,
 
256
                        resource_status=res.resource_status,
 
257
                        resource_status_reason=res.resource_status_reason)
 
258
            time.sleep(build_interval)
 
259
 
 
260
        message = ('Resource %s failed to reach %s status within '
 
261
                   'the required time (%s s).' %
 
262
                   (res.resource_name, status, build_timeout))
 
263
        raise exceptions.TimeoutException(message)
 
264
 
 
265
    def _wait_for_stack_status(self, stack_identifier, status,
 
266
                               failure_pattern='^.*_FAILED$',
 
267
                               success_on_not_found=False):
 
268
        """
 
269
        Waits for a Stack to reach a given status.
 
270
 
 
271
        Note this compares the full $action_$status, e.g
 
272
        CREATE_COMPLETE, not just COMPLETE which is exposed
 
273
        via the status property of Stack in heatclient
 
274
        """
 
275
        fail_regexp = re.compile(failure_pattern)
 
276
        build_timeout = self.conf.build_timeout
 
277
        build_interval = self.conf.build_interval
 
278
 
 
279
        start = timeutils.utcnow()
 
280
        while timeutils.delta_seconds(start,
 
281
                                      timeutils.utcnow()) < build_timeout:
 
282
            try:
 
283
                stack = self.client.stacks.get(stack_identifier)
 
284
            except heat_exceptions.HTTPNotFound:
 
285
                if success_on_not_found:
 
286
                    return
 
287
                # ignore this, as the resource may not have
 
288
                # been created yet
 
289
            else:
 
290
                if stack.stack_status == status:
 
291
                    return
 
292
                if fail_regexp.search(stack.stack_status):
 
293
                    raise exceptions.StackBuildErrorException(
 
294
                        stack_identifier=stack_identifier,
 
295
                        stack_status=stack.stack_status,
 
296
                        stack_status_reason=stack.stack_status_reason)
 
297
            time.sleep(build_interval)
 
298
 
 
299
        message = ('Stack %s failed to reach %s status within '
 
300
                   'the required time (%s s).' %
 
301
                   (stack.stack_name, status, build_timeout))
 
302
        raise exceptions.TimeoutException(message)
 
303
 
 
304
    def _stack_delete(self, stack_identifier):
 
305
        try:
 
306
            self.client.stacks.delete(stack_identifier)
 
307
        except heat_exceptions.HTTPNotFound:
 
308
            pass
 
309
        self._wait_for_stack_status(
 
310
            stack_identifier, 'DELETE_COMPLETE',
 
311
            success_on_not_found=True)
 
312
 
 
313
    def update_stack(self, stack_identifier, template, environment=None,
 
314
                     files=None):
 
315
        env = environment or {}
 
316
        env_files = files or {}
 
317
        stack_name = stack_identifier.split('/')[0]
 
318
        self.client.stacks.update(
 
319
            stack_id=stack_identifier,
 
320
            stack_name=stack_name,
 
321
            template=template,
 
322
            files=env_files,
 
323
            disable_rollback=True,
 
324
            parameters={},
 
325
            environment=env
 
326
        )
 
327
        self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
 
328
 
 
329
    def list_resources(self, stack_identifier):
 
330
        resources = self.client.resources.list(stack_identifier)
 
331
        return dict((r.resource_name, r.resource_type) for r in resources)
 
332
 
 
333
    def stack_create(self, stack_name=None, template=None, files=None,
 
334
                     parameters=None, environment=None):
 
335
        name = stack_name or self._stack_rand_name()
 
336
        templ = template or self.template
 
337
        templ_files = files or {}
 
338
        params = parameters or {}
 
339
        env = environment or {}
 
340
        self.client.stacks.create(
 
341
            stack_name=name,
 
342
            template=templ,
 
343
            files=templ_files,
 
344
            disable_rollback=True,
 
345
            parameters=params,
 
346
            environment=env
 
347
        )
 
348
        self.addCleanup(self.client.stacks.delete, name)
 
349
 
 
350
        stack = self.client.stacks.get(name)
 
351
        stack_identifier = '%s/%s' % (name, stack.id)
 
352
        self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
 
353
        return stack_identifier