blob: b88c72bc904ec3b5e1a92e7f67c7484f0b1500c8 [file] [log] [blame]
Steve Baker450aa7f2014-08-25 10:37:27 +12001# 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
Steve Baker450aa7f2014-08-25 10:37:27 +120013import os
14import random
15import re
Steve Baker450aa7f2014-08-25 10:37:27 +120016import subprocess
Steve Baker450aa7f2014-08-25 10:37:27 +120017import time
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +040018import urllib
Steve Baker450aa7f2014-08-25 10:37:27 +120019
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020020import fixtures
Steve Baker450aa7f2014-08-25 10:37:27 +120021from heatclient import exc as heat_exceptions
Steve Baker24641292015-03-13 10:47:50 +130022from oslo_log import log as logging
Jens Rosenboom4f069fb2015-02-18 14:19:07 +010023from oslo_utils import timeutils
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020024import six
25import testscenarios
26import testtools
Steve Baker450aa7f2014-08-25 10:37:27 +120027
Steve Baker450aa7f2014-08-25 10:37:27 +120028from heat_integrationtests.common import clients
29from heat_integrationtests.common import config
30from heat_integrationtests.common import exceptions
31from heat_integrationtests.common import remote_client
32
33LOG = logging.getLogger(__name__)
Angus Salkeld24043702014-11-21 08:49:26 +100034_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
Steve Baker450aa7f2014-08-25 10:37:27 +120035
36
Angus Salkeld08514ad2015-02-06 10:08:31 +100037def call_until_true(duration, sleep_for, func, *args, **kwargs):
Steve Baker450aa7f2014-08-25 10:37:27 +120038 """
39 Call the given function until it returns True (and return True) or
40 until the specified duration (in seconds) elapses (and return
41 False).
42
43 :param func: A zero argument callable that returns True on success.
44 :param duration: The number of seconds for which to attempt a
45 successful call of the function.
46 :param sleep_for: The number of seconds to sleep after an unsuccessful
47 invocation of the function.
48 """
49 now = time.time()
50 timeout = now + duration
51 while now < timeout:
Angus Salkeld08514ad2015-02-06 10:08:31 +100052 if func(*args, **kwargs):
Steve Baker450aa7f2014-08-25 10:37:27 +120053 return True
54 LOG.debug("Sleeping for %d seconds", sleep_for)
55 time.sleep(sleep_for)
56 now = time.time()
57 return False
58
59
60def rand_name(name=''):
61 randbits = str(random.randint(1, 0x7fffffff))
62 if name:
63 return name + '-' + randbits
64 else:
65 return randbits
66
67
Angus Salkeld95f65a22014-11-24 12:38:30 +100068class HeatIntegrationTest(testscenarios.WithScenarios,
69 testtools.TestCase):
Steve Baker450aa7f2014-08-25 10:37:27 +120070
71 def setUp(self):
72 super(HeatIntegrationTest, self).setUp()
73
74 self.conf = config.init_conf()
75
76 self.assertIsNotNone(self.conf.auth_url,
77 'No auth_url configured')
78 self.assertIsNotNone(self.conf.username,
79 'No username configured')
80 self.assertIsNotNone(self.conf.password,
81 'No password configured')
82
83 self.manager = clients.ClientManager(self.conf)
84 self.identity_client = self.manager.identity_client
85 self.orchestration_client = self.manager.orchestration_client
86 self.compute_client = self.manager.compute_client
87 self.network_client = self.manager.network_client
88 self.volume_client = self.manager.volume_client
Angus Salkeld4408da32015-02-03 18:53:30 +100089 self.object_client = self.manager.object_client
Angus Salkeld24043702014-11-21 08:49:26 +100090 self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT))
Steve Baker450aa7f2014-08-25 10:37:27 +120091
Steve Baker450aa7f2014-08-25 10:37:27 +120092 def get_remote_client(self, server_or_ip, username, private_key=None):
93 if isinstance(server_or_ip, six.string_types):
94 ip = server_or_ip
95 else:
96 network_name_for_ssh = self.conf.network_for_ssh
97 ip = server_or_ip.networks[network_name_for_ssh][0]
98 if private_key is None:
99 private_key = self.keypair.private_key
100 linux_client = remote_client.RemoteClient(ip, username,
101 pkey=private_key,
102 conf=self.conf)
103 try:
104 linux_client.validate_authentication()
105 except exceptions.SSHTimeout:
106 LOG.exception('ssh connection to %s failed' % ip)
107 raise
108
109 return linux_client
110
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +0400111 def check_connectivity(self, check_ip):
112 def try_connect(ip):
113 try:
114 urllib.urlopen('http://%s/' % ip)
115 return True
116 except IOError:
117 return False
118
119 timeout = self.conf.connectivity_timeout
120 elapsed_time = 0
121 while not try_connect(check_ip):
122 time.sleep(10)
123 elapsed_time += 10
124 if elapsed_time > timeout:
125 raise exceptions.TimeoutException()
126
Steve Baker450aa7f2014-08-25 10:37:27 +1200127 def _log_console_output(self, servers=None):
128 if not servers:
129 servers = self.compute_client.servers.list()
130 for server in servers:
Steve Baker24641292015-03-13 10:47:50 +1300131 LOG.info('Console output for %s', server.id)
132 LOG.info(server.get_console_output())
Steve Baker450aa7f2014-08-25 10:37:27 +1200133
Sergey Kraynevd6fa5c02015-02-13 03:03:55 -0500134 def _load_template(self, base_file, file_name, sub_dir=None):
135 sub_dir = sub_dir or ''
Steve Baker450aa7f2014-08-25 10:37:27 +1200136 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
Sergey Kraynevd6fa5c02015-02-13 03:03:55 -0500137 sub_dir, file_name)
Steve Baker450aa7f2014-08-25 10:37:27 +1200138 with open(filepath) as f:
139 return f.read()
140
141 def create_keypair(self, client=None, name=None):
142 if client is None:
143 client = self.compute_client
144 if name is None:
145 name = rand_name('heat-keypair')
146 keypair = client.keypairs.create(name)
147 self.assertEqual(keypair.name, name)
148
149 def delete_keypair():
150 keypair.delete()
151
152 self.addCleanup(delete_keypair)
153 return keypair
154
Sergey Krayneva265c132015-02-13 03:51:03 -0500155 def assign_keypair(self):
156 if self.conf.keypair_name:
157 self.keypair = None
158 self.keypair_name = self.conf.keypair_name
159 else:
160 self.keypair = self.create_keypair()
161 self.keypair_name = self.keypair.id
162
Steve Baker450aa7f2014-08-25 10:37:27 +1200163 @classmethod
164 def _stack_rand_name(cls):
165 return rand_name(cls.__name__)
166
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400167 def _get_network(self, net_name=None):
168 if net_name is None:
169 net_name = self.conf.fixed_network_name
Steve Baker450aa7f2014-08-25 10:37:27 +1200170 networks = self.network_client.list_networks()
171 for net in networks['networks']:
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400172 if net['name'] == net_name:
Steve Baker450aa7f2014-08-25 10:37:27 +1200173 return net
174
175 @staticmethod
176 def _stack_output(stack, output_key):
177 """Return a stack output value for a given key."""
178 return next((o['output_value'] for o in stack.outputs
179 if o['output_key'] == output_key), None)
180
181 def _ping_ip_address(self, ip_address, should_succeed=True):
182 cmd = ['ping', '-c1', '-w1', ip_address]
183
184 def ping():
185 proc = subprocess.Popen(cmd,
186 stdout=subprocess.PIPE,
187 stderr=subprocess.PIPE)
188 proc.wait()
189 return (proc.returncode == 0) == should_succeed
190
191 return call_until_true(
Angus Salkeld08514ad2015-02-06 10:08:31 +1000192 self.conf.build_timeout, 1, ping)
Steve Baker450aa7f2014-08-25 10:37:27 +1200193
194 def _wait_for_resource_status(self, stack_identifier, resource_name,
195 status, failure_pattern='^.*_FAILED$',
196 success_on_not_found=False):
197 """Waits for a Resource to reach a given status."""
198 fail_regexp = re.compile(failure_pattern)
199 build_timeout = self.conf.build_timeout
200 build_interval = self.conf.build_interval
201
202 start = timeutils.utcnow()
203 while timeutils.delta_seconds(start,
204 timeutils.utcnow()) < build_timeout:
205 try:
206 res = self.client.resources.get(
207 stack_identifier, resource_name)
208 except heat_exceptions.HTTPNotFound:
209 if success_on_not_found:
210 return
211 # ignore this, as the resource may not have
212 # been created yet
213 else:
214 if res.resource_status == status:
215 return
216 if fail_regexp.search(res.resource_status):
217 raise exceptions.StackResourceBuildErrorException(
218 resource_name=res.resource_name,
219 stack_identifier=stack_identifier,
220 resource_status=res.resource_status,
221 resource_status_reason=res.resource_status_reason)
222 time.sleep(build_interval)
223
224 message = ('Resource %s failed to reach %s status within '
225 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400226 (resource_name, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200227 raise exceptions.TimeoutException(message)
228
229 def _wait_for_stack_status(self, stack_identifier, status,
230 failure_pattern='^.*_FAILED$',
231 success_on_not_found=False):
232 """
233 Waits for a Stack to reach a given status.
234
235 Note this compares the full $action_$status, e.g
236 CREATE_COMPLETE, not just COMPLETE which is exposed
237 via the status property of Stack in heatclient
238 """
239 fail_regexp = re.compile(failure_pattern)
240 build_timeout = self.conf.build_timeout
241 build_interval = self.conf.build_interval
242
243 start = timeutils.utcnow()
244 while timeutils.delta_seconds(start,
245 timeutils.utcnow()) < build_timeout:
246 try:
247 stack = self.client.stacks.get(stack_identifier)
248 except heat_exceptions.HTTPNotFound:
249 if success_on_not_found:
250 return
251 # ignore this, as the resource may not have
252 # been created yet
253 else:
254 if stack.stack_status == status:
255 return
256 if fail_regexp.search(stack.stack_status):
257 raise exceptions.StackBuildErrorException(
258 stack_identifier=stack_identifier,
259 stack_status=stack.stack_status,
260 stack_status_reason=stack.stack_status_reason)
261 time.sleep(build_interval)
262
263 message = ('Stack %s failed to reach %s status within '
264 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400265 (stack_identifier, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200266 raise exceptions.TimeoutException(message)
267
268 def _stack_delete(self, stack_identifier):
269 try:
270 self.client.stacks.delete(stack_identifier)
271 except heat_exceptions.HTTPNotFound:
272 pass
273 self._wait_for_stack_status(
274 stack_identifier, 'DELETE_COMPLETE',
275 success_on_not_found=True)
Steven Hardyc9efd972014-11-20 11:31:55 +0000276
277 def update_stack(self, stack_identifier, template, environment=None,
Steven Hardy03da0742015-03-19 00:13:17 -0400278 files=None, parameters=None,
279 expected_status='UPDATE_COMPLETE'):
Steven Hardyc9efd972014-11-20 11:31:55 +0000280 env = environment or {}
281 env_files = files or {}
Sergey Kraynevbcc78df2015-02-27 04:34:32 -0500282 parameters = parameters or {}
Steven Hardyc9efd972014-11-20 11:31:55 +0000283 stack_name = stack_identifier.split('/')[0]
284 self.client.stacks.update(
285 stack_id=stack_identifier,
286 stack_name=stack_name,
287 template=template,
288 files=env_files,
289 disable_rollback=True,
Sergey Kraynevbcc78df2015-02-27 04:34:32 -0500290 parameters=parameters,
Steven Hardyc9efd972014-11-20 11:31:55 +0000291 environment=env
292 )
Steven Hardy03da0742015-03-19 00:13:17 -0400293 self._wait_for_stack_status(stack_identifier, expected_status)
Steven Hardyc9efd972014-11-20 11:31:55 +0000294
Steven Hardy03da0742015-03-19 00:13:17 -0400295 def assert_resource_is_a_stack(self, stack_identifier, res_name,
296 wait=False):
297 build_timeout = self.conf.build_timeout
298 build_interval = self.conf.build_interval
299 start = timeutils.utcnow()
300 while timeutils.delta_seconds(start,
301 timeutils.utcnow()) < build_timeout:
302 time.sleep(build_interval)
303 try:
304 nested_identifier = self._get_nested_identifier(
305 stack_identifier, res_name)
306 except Exception:
307 # We may have to wait, if the create is in-progress
308 if wait:
309 time.sleep(build_interval)
310 else:
311 raise
312 else:
313 return nested_identifier
314
315 def _get_nested_identifier(self, stack_identifier, res_name):
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000316 rsrc = self.client.resources.get(stack_identifier, res_name)
317 nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
318 nested_href = nested_link[0]['href']
319 nested_id = nested_href.split('/')[-1]
320 nested_identifier = '/'.join(nested_href.split('/')[-2:])
321 self.assertEqual(rsrc.physical_resource_id, nested_id)
322
323 nested_stack = self.client.stacks.get(nested_id)
324 nested_identifier2 = '%s/%s' % (nested_stack.stack_name,
325 nested_stack.id)
326 self.assertEqual(nested_identifier, nested_identifier2)
327 parent_id = stack_identifier.split("/")[-1]
328 self.assertEqual(parent_id, nested_stack.parent)
329 return nested_identifier
330
Steven Hardyc9efd972014-11-20 11:31:55 +0000331 def list_resources(self, stack_identifier):
332 resources = self.client.resources.list(stack_identifier)
333 return dict((r.resource_name, r.resource_type) for r in resources)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000334
335 def stack_create(self, stack_name=None, template=None, files=None,
Steven Hardy7c1f2242015-01-12 16:32:56 +0000336 parameters=None, environment=None,
337 expected_status='CREATE_COMPLETE'):
Steven Hardyf2c82c02014-11-20 14:02:17 +0000338 name = stack_name or self._stack_rand_name()
339 templ = template or self.template
340 templ_files = files or {}
341 params = parameters or {}
342 env = environment or {}
343 self.client.stacks.create(
344 stack_name=name,
345 template=templ,
346 files=templ_files,
347 disable_rollback=True,
348 parameters=params,
349 environment=env
350 )
351 self.addCleanup(self.client.stacks.delete, name)
352
353 stack = self.client.stacks.get(name)
354 stack_identifier = '%s/%s' % (name, stack.id)
Steve Bakerf6c8f122015-02-10 13:54:46 +1300355 if expected_status:
356 self._wait_for_stack_status(stack_identifier, expected_status)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000357 return stack_identifier
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000358
359 def stack_adopt(self, stack_name=None, files=None,
360 parameters=None, environment=None, adopt_data=None,
361 wait_for_status='ADOPT_COMPLETE'):
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530362 if self.conf.skip_stack_adopt_tests:
363 self.skipTest('Testing Stack adopt disabled in conf, skipping')
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000364 name = stack_name or self._stack_rand_name()
365 templ_files = files or {}
366 params = parameters or {}
367 env = environment or {}
368 self.client.stacks.create(
369 stack_name=name,
370 files=templ_files,
371 disable_rollback=True,
372 parameters=params,
373 environment=env,
374 adopt_stack_data=adopt_data,
375 )
376 self.addCleanup(self.client.stacks.delete, name)
377
378 stack = self.client.stacks.get(name)
379 stack_identifier = '%s/%s' % (name, stack.id)
380 self._wait_for_stack_status(stack_identifier, wait_for_status)
381 return stack_identifier
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530382
383 def stack_abandon(self, stack_id):
384 if self.conf.skip_stack_abandon_tests:
385 self.addCleanup(self.client.stacks.delete, stack_id)
386 self.skipTest('Testing Stack abandon disabled in conf, skipping')
387 info = self.client.stacks.abandon(stack_id=stack_id)
388 return info
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500389
390 def stack_suspend(self, stack_identifier):
391 stack_name = stack_identifier.split('/')[0]
392 self.client.actions.suspend(stack_name)
393 self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
394
395 def stack_resume(self, stack_identifier):
396 stack_name = stack_identifier.split('/')[0]
397 self.client.actions.resume(stack_name)
398 self._wait_for_stack_status(stack_identifier, 'RESUME_COMPLETE')
Steven Hardy03da0742015-03-19 00:13:17 -0400399
400 def wait_for_event_with_reason(self, stack_identifier, reason,
401 rsrc_name=None, num_expected=1):
402 build_timeout = self.conf.build_timeout
403 build_interval = self.conf.build_interval
404 start = timeutils.utcnow()
405 while timeutils.delta_seconds(start,
406 timeutils.utcnow()) < build_timeout:
407 try:
408 rsrc_events = self.client.events.list(stack_identifier,
409 resource_name=rsrc_name)
410 except heat_exceptions.HTTPNotFound:
411 LOG.debug("No events yet found for %s" % rsrc_name)
412 else:
413 matched = [e for e in rsrc_events
414 if e.resource_status_reason == reason]
415 if len(matched) == num_expected:
416 return matched
417 time.sleep(build_interval)