blob: 0ffa2b03fc65676653f6818b1c9dff018a12e4ee [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
13import logging
14import os
15import random
16import re
Steve Baker450aa7f2014-08-25 10:37:27 +120017import subprocess
Steve Baker450aa7f2014-08-25 10:37:27 +120018import time
19
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020020import fixtures
Steve Baker450aa7f2014-08-25 10:37:27 +120021from heatclient import exc as heat_exceptions
Jens Rosenboom4f069fb2015-02-18 14:19:07 +010022from oslo_utils import timeutils
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020023import six
24import testscenarios
25import testtools
Steve Baker450aa7f2014-08-25 10:37:27 +120026
Steve Baker450aa7f2014-08-25 10:37:27 +120027from heat_integrationtests.common import clients
28from heat_integrationtests.common import config
29from heat_integrationtests.common import exceptions
30from heat_integrationtests.common import remote_client
31
32LOG = logging.getLogger(__name__)
Angus Salkeld24043702014-11-21 08:49:26 +100033_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
Steve Baker450aa7f2014-08-25 10:37:27 +120034
35
Angus Salkeld08514ad2015-02-06 10:08:31 +100036def call_until_true(duration, sleep_for, func, *args, **kwargs):
Steve Baker450aa7f2014-08-25 10:37:27 +120037 """
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:
Angus Salkeld08514ad2015-02-06 10:08:31 +100051 if func(*args, **kwargs):
Steve Baker450aa7f2014-08-25 10:37:27 +120052 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
59def rand_name(name=''):
60 randbits = str(random.randint(1, 0x7fffffff))
61 if name:
62 return name + '-' + randbits
63 else:
64 return randbits
65
66
Angus Salkeld95f65a22014-11-24 12:38:30 +100067class HeatIntegrationTest(testscenarios.WithScenarios,
68 testtools.TestCase):
Steve Baker450aa7f2014-08-25 10:37:27 +120069
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
Angus Salkeld4408da32015-02-03 18:53:30 +100088 self.object_client = self.manager.object_client
Angus Salkeld24043702014-11-21 08:49:26 +100089 self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT))
Steve Baker450aa7f2014-08-25 10:37:27 +120090
Steve Baker450aa7f2014-08-25 10:37:27 +120091 def get_remote_client(self, server_or_ip, username, private_key=None):
92 if isinstance(server_or_ip, six.string_types):
93 ip = server_or_ip
94 else:
95 network_name_for_ssh = self.conf.network_for_ssh
96 ip = server_or_ip.networks[network_name_for_ssh][0]
97 if private_key is None:
98 private_key = self.keypair.private_key
99 linux_client = remote_client.RemoteClient(ip, username,
100 pkey=private_key,
101 conf=self.conf)
102 try:
103 linux_client.validate_authentication()
104 except exceptions.SSHTimeout:
105 LOG.exception('ssh connection to %s failed' % ip)
106 raise
107
108 return linux_client
109
110 def _log_console_output(self, servers=None):
111 if not servers:
112 servers = self.compute_client.servers.list()
113 for server in servers:
114 LOG.debug('Console output for %s', server.id)
115 LOG.debug(server.get_console_output())
116
Sergey Kraynevd6fa5c02015-02-13 03:03:55 -0500117 def _load_template(self, base_file, file_name, sub_dir=None):
118 sub_dir = sub_dir or ''
Steve Baker450aa7f2014-08-25 10:37:27 +1200119 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
Sergey Kraynevd6fa5c02015-02-13 03:03:55 -0500120 sub_dir, file_name)
Steve Baker450aa7f2014-08-25 10:37:27 +1200121 with open(filepath) as f:
122 return f.read()
123
124 def create_keypair(self, client=None, name=None):
125 if client is None:
126 client = self.compute_client
127 if name is None:
128 name = rand_name('heat-keypair')
129 keypair = client.keypairs.create(name)
130 self.assertEqual(keypair.name, name)
131
132 def delete_keypair():
133 keypair.delete()
134
135 self.addCleanup(delete_keypair)
136 return keypair
137
Sergey Krayneva265c132015-02-13 03:51:03 -0500138 def assign_keypair(self):
139 if self.conf.keypair_name:
140 self.keypair = None
141 self.keypair_name = self.conf.keypair_name
142 else:
143 self.keypair = self.create_keypair()
144 self.keypair_name = self.keypair.id
145
Steve Baker450aa7f2014-08-25 10:37:27 +1200146 @classmethod
147 def _stack_rand_name(cls):
148 return rand_name(cls.__name__)
149
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400150 def _get_network(self, net_name=None):
151 if net_name is None:
152 net_name = self.conf.fixed_network_name
Steve Baker450aa7f2014-08-25 10:37:27 +1200153 networks = self.network_client.list_networks()
154 for net in networks['networks']:
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400155 if net['name'] == net_name:
Steve Baker450aa7f2014-08-25 10:37:27 +1200156 return net
157
158 @staticmethod
159 def _stack_output(stack, output_key):
160 """Return a stack output value for a given key."""
161 return next((o['output_value'] for o in stack.outputs
162 if o['output_key'] == output_key), None)
163
164 def _ping_ip_address(self, ip_address, should_succeed=True):
165 cmd = ['ping', '-c1', '-w1', ip_address]
166
167 def ping():
168 proc = subprocess.Popen(cmd,
169 stdout=subprocess.PIPE,
170 stderr=subprocess.PIPE)
171 proc.wait()
172 return (proc.returncode == 0) == should_succeed
173
174 return call_until_true(
Angus Salkeld08514ad2015-02-06 10:08:31 +1000175 self.conf.build_timeout, 1, ping)
Steve Baker450aa7f2014-08-25 10:37:27 +1200176
177 def _wait_for_resource_status(self, stack_identifier, resource_name,
178 status, failure_pattern='^.*_FAILED$',
179 success_on_not_found=False):
180 """Waits for a Resource to reach a given status."""
181 fail_regexp = re.compile(failure_pattern)
182 build_timeout = self.conf.build_timeout
183 build_interval = self.conf.build_interval
184
185 start = timeutils.utcnow()
186 while timeutils.delta_seconds(start,
187 timeutils.utcnow()) < build_timeout:
188 try:
189 res = self.client.resources.get(
190 stack_identifier, resource_name)
191 except heat_exceptions.HTTPNotFound:
192 if success_on_not_found:
193 return
194 # ignore this, as the resource may not have
195 # been created yet
196 else:
197 if res.resource_status == status:
198 return
199 if fail_regexp.search(res.resource_status):
200 raise exceptions.StackResourceBuildErrorException(
201 resource_name=res.resource_name,
202 stack_identifier=stack_identifier,
203 resource_status=res.resource_status,
204 resource_status_reason=res.resource_status_reason)
205 time.sleep(build_interval)
206
207 message = ('Resource %s failed to reach %s status within '
208 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400209 (resource_name, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200210 raise exceptions.TimeoutException(message)
211
212 def _wait_for_stack_status(self, stack_identifier, status,
213 failure_pattern='^.*_FAILED$',
214 success_on_not_found=False):
215 """
216 Waits for a Stack to reach a given status.
217
218 Note this compares the full $action_$status, e.g
219 CREATE_COMPLETE, not just COMPLETE which is exposed
220 via the status property of Stack in heatclient
221 """
222 fail_regexp = re.compile(failure_pattern)
223 build_timeout = self.conf.build_timeout
224 build_interval = self.conf.build_interval
225
226 start = timeutils.utcnow()
227 while timeutils.delta_seconds(start,
228 timeutils.utcnow()) < build_timeout:
229 try:
230 stack = self.client.stacks.get(stack_identifier)
231 except heat_exceptions.HTTPNotFound:
232 if success_on_not_found:
233 return
234 # ignore this, as the resource may not have
235 # been created yet
236 else:
237 if stack.stack_status == status:
238 return
239 if fail_regexp.search(stack.stack_status):
240 raise exceptions.StackBuildErrorException(
241 stack_identifier=stack_identifier,
242 stack_status=stack.stack_status,
243 stack_status_reason=stack.stack_status_reason)
244 time.sleep(build_interval)
245
246 message = ('Stack %s failed to reach %s status within '
247 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400248 (stack_identifier, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200249 raise exceptions.TimeoutException(message)
250
251 def _stack_delete(self, stack_identifier):
252 try:
253 self.client.stacks.delete(stack_identifier)
254 except heat_exceptions.HTTPNotFound:
255 pass
256 self._wait_for_stack_status(
257 stack_identifier, 'DELETE_COMPLETE',
258 success_on_not_found=True)
Steven Hardyc9efd972014-11-20 11:31:55 +0000259
260 def update_stack(self, stack_identifier, template, environment=None,
Sergey Kraynevbcc78df2015-02-27 04:34:32 -0500261 files=None, parameters=None):
Steven Hardyc9efd972014-11-20 11:31:55 +0000262 env = environment or {}
263 env_files = files or {}
Sergey Kraynevbcc78df2015-02-27 04:34:32 -0500264 parameters = parameters or {}
Steven Hardyc9efd972014-11-20 11:31:55 +0000265 stack_name = stack_identifier.split('/')[0]
266 self.client.stacks.update(
267 stack_id=stack_identifier,
268 stack_name=stack_name,
269 template=template,
270 files=env_files,
271 disable_rollback=True,
Sergey Kraynevbcc78df2015-02-27 04:34:32 -0500272 parameters=parameters,
Steven Hardyc9efd972014-11-20 11:31:55 +0000273 environment=env
274 )
275 self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
276
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000277 def assert_resource_is_a_stack(self, stack_identifier, res_name):
278 rsrc = self.client.resources.get(stack_identifier, res_name)
279 nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
280 nested_href = nested_link[0]['href']
281 nested_id = nested_href.split('/')[-1]
282 nested_identifier = '/'.join(nested_href.split('/')[-2:])
283 self.assertEqual(rsrc.physical_resource_id, nested_id)
284
285 nested_stack = self.client.stacks.get(nested_id)
286 nested_identifier2 = '%s/%s' % (nested_stack.stack_name,
287 nested_stack.id)
288 self.assertEqual(nested_identifier, nested_identifier2)
289 parent_id = stack_identifier.split("/")[-1]
290 self.assertEqual(parent_id, nested_stack.parent)
291 return nested_identifier
292
Steven Hardyc9efd972014-11-20 11:31:55 +0000293 def list_resources(self, stack_identifier):
294 resources = self.client.resources.list(stack_identifier)
295 return dict((r.resource_name, r.resource_type) for r in resources)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000296
297 def stack_create(self, stack_name=None, template=None, files=None,
Steven Hardy7c1f2242015-01-12 16:32:56 +0000298 parameters=None, environment=None,
299 expected_status='CREATE_COMPLETE'):
Steven Hardyf2c82c02014-11-20 14:02:17 +0000300 name = stack_name or self._stack_rand_name()
301 templ = template or self.template
302 templ_files = files or {}
303 params = parameters or {}
304 env = environment or {}
305 self.client.stacks.create(
306 stack_name=name,
307 template=templ,
308 files=templ_files,
309 disable_rollback=True,
310 parameters=params,
311 environment=env
312 )
313 self.addCleanup(self.client.stacks.delete, name)
314
315 stack = self.client.stacks.get(name)
316 stack_identifier = '%s/%s' % (name, stack.id)
Steve Bakerf6c8f122015-02-10 13:54:46 +1300317 if expected_status:
318 self._wait_for_stack_status(stack_identifier, expected_status)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000319 return stack_identifier
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000320
321 def stack_adopt(self, stack_name=None, files=None,
322 parameters=None, environment=None, adopt_data=None,
323 wait_for_status='ADOPT_COMPLETE'):
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530324 if self.conf.skip_stack_adopt_tests:
325 self.skipTest('Testing Stack adopt disabled in conf, skipping')
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000326 name = stack_name or self._stack_rand_name()
327 templ_files = files or {}
328 params = parameters or {}
329 env = environment or {}
330 self.client.stacks.create(
331 stack_name=name,
332 files=templ_files,
333 disable_rollback=True,
334 parameters=params,
335 environment=env,
336 adopt_stack_data=adopt_data,
337 )
338 self.addCleanup(self.client.stacks.delete, name)
339
340 stack = self.client.stacks.get(name)
341 stack_identifier = '%s/%s' % (name, stack.id)
342 self._wait_for_stack_status(stack_identifier, wait_for_status)
343 return stack_identifier
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530344
345 def stack_abandon(self, stack_id):
346 if self.conf.skip_stack_abandon_tests:
347 self.addCleanup(self.client.stacks.delete, stack_id)
348 self.skipTest('Testing Stack abandon disabled in conf, skipping')
349 info = self.client.stacks.abandon(stack_id=stack_id)
350 return info