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
5
# http://www.apache.org/licenses/LICENSE-2.0
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
24
from heatclient import exc as heat_exceptions
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
32
LOG = logging.getLogger(__name__)
33
_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
36
def call_until_true(func, duration, sleep_for):
38
Call the given function until it returns True (and return True) or
39
until the specified duration (in seconds) elapses (and return
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.
49
timeout = now + duration
53
LOG.debug("Sleeping for %d seconds", sleep_for)
59
def rand_name(name=''):
60
randbits = str(random.randint(1, 0x7fffffff))
62
return name + '-' + randbits
67
class HeatIntegrationTest(testscenarios.WithScenarios,
71
super(HeatIntegrationTest, self).setUp()
73
self.conf = config.init_conf()
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')
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))
90
def status_timeout(self, things, thing_id, expected_status,
92
not_found_exception=heat_exceptions.NotFound):
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.
99
self._status_timeout(things, thing_id,
100
expected_status=expected_status,
101
error_status=error_status,
102
not_found_exception=not_found_exception)
104
def _status_timeout(self,
107
expected_status=None,
108
allow_notfound=False,
109
error_status='ERROR',
110
not_found_exception=heat_exceptions.NotFound):
112
log_status = expected_status if expected_status else ''
114
log_status += ' or NotFound' if log_status != '' else 'NotFound'
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.
121
thing = things.get(thing_id)
122
except not_found_exception:
126
except Exception as e:
127
if allow_notfound and self.not_found_exception(e):
131
new_status = thing.status
133
# Some components are reporting error status in lower case
134
# so case sensitive comparisons can really mess things
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,
140
raise exceptions.BuildErrorException(message,
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(
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)
155
def get_remote_client(self, server_or_ip, username, private_key=None):
156
if isinstance(server_or_ip, six.string_types):
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,
167
linux_client.validate_authentication()
168
except exceptions.SSHTimeout:
169
LOG.exception('ssh connection to %s failed' % ip)
174
def _log_console_output(self, servers=None):
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())
181
def _load_template(self, base_file, file_name):
182
filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
184
with open(filepath) as f:
187
def create_keypair(self, client=None, name=None):
189
client = self.compute_client
191
name = rand_name('heat-keypair')
192
keypair = client.keypairs.create(name)
193
self.assertEqual(keypair.name, name)
195
def delete_keypair():
198
self.addCleanup(delete_keypair)
202
def _stack_rand_name(cls):
203
return rand_name(cls.__name__)
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:
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)
217
def _ping_ip_address(self, ip_address, should_succeed=True):
218
cmd = ['ping', '-c1', '-w1', ip_address]
221
proc = subprocess.Popen(cmd,
222
stdout=subprocess.PIPE,
223
stderr=subprocess.PIPE)
225
return (proc.returncode == 0) == should_succeed
227
return call_until_true(
228
ping, self.conf.build_timeout, 1)
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
238
start = timeutils.utcnow()
239
while timeutils.delta_seconds(start,
240
timeutils.utcnow()) < build_timeout:
242
res = self.client.resources.get(
243
stack_identifier, resource_name)
244
except heat_exceptions.HTTPNotFound:
245
if success_on_not_found:
247
# ignore this, as the resource may not have
250
if res.resource_status == status:
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)
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)
265
def _wait_for_stack_status(self, stack_identifier, status,
266
failure_pattern='^.*_FAILED$',
267
success_on_not_found=False):
269
Waits for a Stack to reach a given status.
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
275
fail_regexp = re.compile(failure_pattern)
276
build_timeout = self.conf.build_timeout
277
build_interval = self.conf.build_interval
279
start = timeutils.utcnow()
280
while timeutils.delta_seconds(start,
281
timeutils.utcnow()) < build_timeout:
283
stack = self.client.stacks.get(stack_identifier)
284
except heat_exceptions.HTTPNotFound:
285
if success_on_not_found:
287
# ignore this, as the resource may not have
290
if stack.stack_status == status:
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)
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)
304
def _stack_delete(self, stack_identifier):
306
self.client.stacks.delete(stack_identifier)
307
except heat_exceptions.HTTPNotFound:
309
self._wait_for_stack_status(
310
stack_identifier, 'DELETE_COMPLETE',
311
success_on_not_found=True)
313
def update_stack(self, stack_identifier, template, environment=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,
323
disable_rollback=True,
327
self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
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)
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(
344
disable_rollback=True,
348
self.addCleanup(self.client.stacks.delete, name)
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