blob: ca79325615c5d40790b37ce9017a48b44af2390b [file] [log] [blame]
ZhiQiang Fan39f97222013-09-20 04:49:44 +08001# Copyright 2012 OpenStack Foundation
Sean Dague6dbc6da2013-05-08 17:49:46 -04002# Copyright 2013 IBM Corp.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
Attila Fazekasfb7552a2013-08-27 13:02:26 +020017import logging
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120018import os
Steve Baker22c16602014-05-05 13:34:19 +120019import re
llg821243b20502014-02-22 10:32:49 +080020import six
Sean Dague6dbc6da2013-05-08 17:49:46 -040021import subprocess
Steve Baker22c16602014-05-05 13:34:19 +120022import time
Sean Dague6dbc6da2013-05-08 17:49:46 -040023
Kaitlin Farr366a51f2014-04-21 12:43:54 -040024from cinderclient import exceptions as cinder_exceptions
Matthew Treinishb7144eb2013-12-13 22:57:35 +000025import glanceclient
Steve Baker22c16602014-05-05 13:34:19 +120026from heatclient import exc as heat_exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040027import netaddr
Mark McClainf2982e82013-07-06 17:48:03 -040028from neutronclient.common import exceptions as exc
fujioka yuuichi636f8db2013-08-09 12:05:24 +090029from novaclient import exceptions as nova_exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040030
Sean Dague1937d092013-05-17 16:36:38 -040031from tempest.api.network import common as net_common
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000032from tempest import auth
Andrea Frittolif9cde7e2014-02-18 09:57:04 +000033from tempest import clients
Matt Riedemann343305f2014-05-27 09:55:03 -070034from tempest.common import debug
Matthew Treinishb86cda92013-07-29 11:22:23 -040035from tempest.common import isolated_creds
Masayuki Igawa259c1132013-10-31 17:48:44 +090036from tempest.common.utils import data_utils
Masayuki Igawa4ded9f02014-02-17 15:05:59 +090037from tempest.common.utils.linux import remote_client
Matthew Treinish6c072292014-01-29 19:15:52 +000038from tempest import config
Giulio Fidente92f77192013-08-26 17:13:28 +020039from tempest import exceptions
Attila Fazekasfb7552a2013-08-27 13:02:26 +020040from tempest.openstack.common import log
Steve Baker22c16602014-05-05 13:34:19 +120041from tempest.openstack.common import timeutils
Sean Dague6dbc6da2013-05-08 17:49:46 -040042import tempest.test
Sean Dague6dbc6da2013-05-08 17:49:46 -040043
Matthew Treinish6c072292014-01-29 19:15:52 +000044CONF = config.CONF
Sean Dague6dbc6da2013-05-08 17:49:46 -040045
Attila Fazekasfb7552a2013-08-27 13:02:26 +020046LOG = log.getLogger(__name__)
47
48# NOTE(afazekas): Workaround for the stdout logging
49LOG_nova_client = logging.getLogger('novaclient.client')
50LOG_nova_client.addHandler(log.NullHandler())
51
52LOG_cinder_client = logging.getLogger('cinderclient.client')
53LOG_cinder_client.addHandler(log.NullHandler())
Sean Dague6dbc6da2013-05-08 17:49:46 -040054
55
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040056class OfficialClientTest(tempest.test.BaseTestCase):
Sean Dague6dbc6da2013-05-08 17:49:46 -040057 """
58 Official Client test base class for scenario testing.
59
60 Official Client tests are tests that have the following characteristics:
61
62 * Test basic operations of an API, typically in an order that
63 a regular user would perform those operations
64 * Test only the correct inputs and action paths -- no fuzz or
65 random input data is sent, only valid inputs.
66 * Use only the default client tool for calling an API
67 """
68
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040069 @classmethod
70 def setUpClass(cls):
Attila Fazekasf86fa312013-07-30 19:56:39 +020071 super(OfficialClientTest, cls).setUpClass()
Matthew Treinishb86cda92013-07-29 11:22:23 -040072 cls.isolated_creds = isolated_creds.IsolatedCreds(
Sean Dague6969b902014-01-28 06:48:37 -050073 cls.__name__, tempest_client=False,
Matthew Treinish9f756a02014-01-15 10:26:07 -050074 network_resources=cls.network_resources)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120075
Andrea Frittolif9cde7e2014-02-18 09:57:04 +000076 cls.manager = clients.OfficialClientManager(
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000077 credentials=cls.credentials())
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040078 cls.compute_client = cls.manager.compute_client
79 cls.image_client = cls.manager.image_client
Adam Gandelman4a48a602014-03-20 18:23:18 -070080 cls.baremetal_client = cls.manager.baremetal_client
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040081 cls.identity_client = cls.manager.identity_client
82 cls.network_client = cls.manager.network_client
83 cls.volume_client = cls.manager.volume_client
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +000084 cls.object_storage_client = cls.manager.object_storage_client
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120085 cls.orchestration_client = cls.manager.orchestration_client
Sergey Lukjanov7409e2e2014-03-27 12:55:50 +040086 cls.data_processing_client = cls.manager.data_processing_client
Artur Svechnikovc3bf9252014-05-05 16:37:37 +040087 cls.ceilometer_client = cls.manager.ceilometer_client
Sean Dague6dbc6da2013-05-08 17:49:46 -040088
89 @classmethod
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000090 def _get_credentials(cls, get_creds, ctype):
Matthew Treinish6c072292014-01-29 19:15:52 +000091 if CONF.compute.allow_tenant_isolation:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000092 creds = get_creds()
Yair Fried769bbff2013-12-18 16:33:17 +020093 else:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000094 creds = auth.get_default_credentials(ctype)
95 return creds
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120096
97 @classmethod
Yair Frieda71cc442013-12-18 13:32:36 +020098 def credentials(cls):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000099 return cls._get_credentials(cls.isolated_creds.get_primary_creds,
100 'user')
Yair Frieda71cc442013-12-18 13:32:36 +0200101
102 @classmethod
103 def alt_credentials(cls):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000104 return cls._get_credentials(cls.isolated_creds.get_alt_creds,
105 'alt_user')
Yair Frieda71cc442013-12-18 13:32:36 +0200106
107 @classmethod
108 def admin_credentials(cls):
109 return cls._get_credentials(cls.isolated_creds.get_admin_creds,
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000110 'identity_admin')
Yair Frieda71cc442013-12-18 13:32:36 +0200111
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000112 def setUp(self):
113 super(OfficialClientTest, self).setUp()
114 self.cleanup_waits = []
115 # NOTE(mtreinish) This is safe to do in setUp instead of setUp class
116 # because scenario tests in the same test class should not share
117 # resources. If resources were shared between test cases then it
118 # should be a single scenario test instead of multiples.
Yair Friedbf2e2c42014-01-28 12:06:38 +0200119
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000120 # NOTE(yfried): this list is cleaned at the end of test_methods and
121 # not at the end of the class
122 self.addCleanup(self._wait_for_cleanups)
123
124 @staticmethod
125 def not_found_exception(exception):
126 """
127 @return: True if exception is of NotFound type
128 """
129 NOT_FOUND_LIST = ['NotFound', 'HTTPNotFound']
130 return (exception.__class__.__name__ in NOT_FOUND_LIST
131 or
132 hasattr(exception, 'status_code') and
133 exception.status_code == 404)
134
135 def delete_wrapper(self, thing):
136 """Ignores NotFound exceptions for delete operations.
137
138 @param thing: object with delete() method.
139 OpenStack resources are assumed to have a delete() method which
140 destroys the resource
141 """
142
Yair Friedbf2e2c42014-01-28 12:06:38 +0200143 try:
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000144 thing.delete()
Yair Friedbf2e2c42014-01-28 12:06:38 +0200145 except Exception as e:
146 # If the resource is already missing, mission accomplished.
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000147 if not self.not_found_exception(e):
Yair Friedbf2e2c42014-01-28 12:06:38 +0200148 raise
Yair Friedbf2e2c42014-01-28 12:06:38 +0200149
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000150 def _wait_for_cleanups(self):
151 """To handle async delete actions, a list of waits is added
152 which will be iterated over as the last step of clearing the
153 cleanup queue. That way all the delete calls are made up front
154 and the tests won't succeed unless the deletes are eventually
155 successful. This is the same basic approach used in the api tests to
156 limit cleanup execution time except here it is multi-resource,
157 because of the nature of the scenario tests.
158 """
159 for wait in self.cleanup_waits:
160 self.delete_timeout(**wait)
Yair Friedbf2e2c42014-01-28 12:06:38 +0200161
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000162 def addCleanup_with_wait(self, things, thing_id,
163 error_status='ERROR',
164 exc_type=nova_exceptions.NotFound,
165 cleanup_callable=None, cleanup_args=[],
166 cleanup_kwargs={}):
167 """Adds wait for ansyc resource deletion at the end of cleanups
Sean Dague6dbc6da2013-05-08 17:49:46 -0400168
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000169 @param things: type of the resource to delete
170 @param thing_id:
171 @param error_status: see manager.delete_timeout()
172 @param exc_type: see manager.delete_timeout()
173 @param cleanup_callable: method to load pass to self.addCleanup with
174 the following *cleanup_args, **cleanup_kwargs.
175 usually a delete method. if not used, will try to use:
176 things.delete(thing_id)
177 """
178 if cleanup_callable is None:
179 LOG.debug("no delete method passed. using {rclass}.delete({id}) as"
180 " default".format(rclass=things, id=thing_id))
181 self.addCleanup(things.delete, thing_id)
182 else:
183 self.addCleanup(cleanup_callable, *cleanup_args, **cleanup_kwargs)
184 wait_dict = {
185 'things': things,
186 'thing_id': thing_id,
187 'error_status': error_status,
188 'not_found_exception': exc_type,
189 }
190 self.cleanup_waits.append(wait_dict)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400191
Steve Bakerefde7612013-09-30 11:29:23 +1300192 def status_timeout(self, things, thing_id, expected_status,
193 error_status='ERROR',
194 not_found_exception=nova_exceptions.NotFound):
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400195 """
196 Given a thing and an expected status, do a loop, sleeping
197 for a configurable amount of time, checking for the
198 expected status to show. At any time, if the returned
199 status of the thing is ERROR, fail out.
200 """
Steve Bakerefde7612013-09-30 11:29:23 +1300201 self._status_timeout(things, thing_id,
202 expected_status=expected_status,
203 error_status=error_status,
204 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900205
Steve Bakerefde7612013-09-30 11:29:23 +1300206 def delete_timeout(self, things, thing_id,
207 error_status='ERROR',
208 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900209 """
210 Given a thing, do a loop, sleeping
211 for a configurable amount of time, checking for the
212 deleted status to show. At any time, if the returned
213 status of the thing is ERROR, fail out.
214 """
215 self._status_timeout(things,
216 thing_id,
Steve Bakerefde7612013-09-30 11:29:23 +1300217 allow_notfound=True,
218 error_status=error_status,
219 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900220
221 def _status_timeout(self,
222 things,
223 thing_id,
224 expected_status=None,
Steve Bakerefde7612013-09-30 11:29:23 +1300225 allow_notfound=False,
226 error_status='ERROR',
227 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900228
229 log_status = expected_status if expected_status else ''
230 if allow_notfound:
231 log_status += ' or NotFound' if log_status != '' else 'NotFound'
232
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400233 def check_status():
234 # python-novaclient has resources available to its client
235 # that all implement a get() method taking an identifier
236 # for the singular resource to retrieve.
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900237 try:
238 thing = things.get(thing_id)
Steve Bakerefde7612013-09-30 11:29:23 +1300239 except not_found_exception:
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900240 if allow_notfound:
241 return True
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000242 raise
243 except Exception as e:
244 if allow_notfound and self.not_found_exception(e):
245 return True
246 raise
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900247
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400248 new_status = thing.status
Brent Eaglesc26d4522013-12-02 13:28:49 -0500249
250 # Some components are reporting error status in lower case
251 # so case sensitive comparisons can really mess things
252 # up.
253 if new_status.lower() == error_status.lower():
Masayuki Igawa2a8a8122014-02-07 11:24:49 +0900254 message = ("%s failed to get to expected status (%s). "
255 "In %s state.") % (thing, expected_status,
256 new_status)
Masayuki Igawaa0e786a2014-01-27 15:25:06 +0900257 raise exceptions.BuildErrorException(message,
258 server_id=thing_id)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900259 elif new_status == expected_status and expected_status is not None:
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400260 return True # All good.
261 LOG.debug("Waiting for %s to get to %s status. "
262 "Currently in %s status",
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900263 thing, log_status, new_status)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400264 if not tempest.test.call_until_true(
265 check_status,
Matthew Treinish6c072292014-01-29 19:15:52 +0000266 CONF.compute.build_timeout,
267 CONF.compute.build_interval):
Ken'ichi Ohmichiab1496f2013-12-12 22:17:57 +0900268 message = ("Timed out waiting for thing %s "
269 "to become %s") % (thing_id, log_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200270 raise exceptions.TimeoutException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400271
Yair Friedeb69f3f2013-10-10 13:18:16 +0300272 def _create_loginable_secgroup_rule_nova(self, client=None,
273 secgroup_id=None):
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900274 if client is None:
275 client = self.compute_client
276 if secgroup_id is None:
277 sgs = client.security_groups.list()
278 for sg in sgs:
279 if sg.name == 'default':
280 secgroup_id = sg.id
281
282 # These rules are intended to permit inbound ssh and icmp
283 # traffic from all sources, so no group_id is provided.
284 # Setting a group_id would only permit traffic from ports
285 # belonging to the same security group.
286 rulesets = [
287 {
288 # ssh
289 'ip_protocol': 'tcp',
290 'from_port': 22,
291 'to_port': 22,
292 'cidr': '0.0.0.0/0',
293 },
294 {
295 # ping
296 'ip_protocol': 'icmp',
297 'from_port': -1,
298 'to_port': -1,
299 'cidr': '0.0.0.0/0',
300 }
301 ]
Yair Friedeb69f3f2013-10-10 13:18:16 +0300302 rules = list()
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900303 for ruleset in rulesets:
304 sg_rule = client.security_group_rules.create(secgroup_id,
305 **ruleset)
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000306 self.addCleanup(self.delete_wrapper, sg_rule)
Yair Friedeb69f3f2013-10-10 13:18:16 +0300307 rules.append(sg_rule)
308 return rules
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900309
Grishkin0f1e11c2014-05-04 20:44:52 +0400310 def _create_security_group_nova(self, client=None,
311 namestart='secgroup-smoke-'):
312 if client is None:
313 client = self.compute_client
314 # Create security group
315 sg_name = data_utils.rand_name(namestart)
316 sg_desc = sg_name + " description"
317 secgroup = client.security_groups.create(sg_name, sg_desc)
318 self.assertEqual(secgroup.name, sg_name)
319 self.assertEqual(secgroup.description, sg_desc)
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000320 self.addCleanup(self.delete_wrapper, secgroup)
Grishkin0f1e11c2014-05-04 20:44:52 +0400321
322 # Add rules to the security group
323 self._create_loginable_secgroup_rule_nova(client, secgroup.id)
324
325 return secgroup
326
Giulio Fidente61cadca2013-09-24 18:33:37 +0200327 def create_server(self, client=None, name=None, image=None, flavor=None,
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000328 wait_on_boot=True, wait_on_delete=True,
329 create_kwargs={}):
330 """Creates VM instance.
331
332 @param client: compute client to create the instance
333 @param image: image from which to create the instance
334 @param wait_on_boot: wait for status ACTIVE before continue
335 @param wait_on_delete: force synchronous delete on cleanup
336 @param create_kwargs: additional details for instance creation
337 @return: client.server object
338 """
Giulio Fidente61cadca2013-09-24 18:33:37 +0200339 if client is None:
340 client = self.compute_client
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900341 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900342 name = data_utils.rand_name('scenario-server-')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900343 if image is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000344 image = CONF.compute.image_ref
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900345 if flavor is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000346 flavor = CONF.compute.flavor_ref
JordanP9c052aa2014-01-24 13:05:00 +0000347
348 fixed_network_name = CONF.compute.fixed_network_name
349 if 'nics' not in create_kwargs and fixed_network_name:
350 networks = client.networks.list()
351 # If several networks found, set the NetID on which to connect the
352 # server to avoid the following error "Multiple possible networks
353 # found, use a Network ID to be more specific."
354 # See Tempest #1250866
355 if len(networks) > 1:
356 for network in networks:
357 if network.label == fixed_network_name:
358 create_kwargs['nics'] = [{'net-id': network.id}]
359 break
360 # If we didn't find the network we were looking for :
361 else:
362 msg = ("The network on which the NIC of the server must "
363 "be connected can not be found : "
364 "fixed_network_name=%s. Starting instance without "
365 "specifying a network.") % fixed_network_name
366 LOG.info(msg)
367
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900368 LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
369 name, image, flavor)
370 server = client.servers.create(name, image, flavor, **create_kwargs)
Giulio Fidente92f77192013-08-26 17:13:28 +0200371 self.assertEqual(server.name, name)
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000372 if wait_on_delete:
373 self.addCleanup(self.delete_timeout,
374 self.compute_client.servers,
375 server.id)
376 self.addCleanup_with_wait(self.compute_client.servers, server.id,
377 cleanup_callable=self.delete_wrapper,
378 cleanup_args=[server])
379 if wait_on_boot:
Adam Gandelman4a48a602014-03-20 18:23:18 -0700380 self.status_timeout(client.servers, server.id, 'ACTIVE')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900381 # The instance retrieved on creation is missing network
382 # details, necessitating retrieval after it becomes active to
383 # ensure correct details.
384 server = client.servers.get(server.id)
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900385 LOG.debug("Created server: %s", server)
386 return server
387
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900388 def create_volume(self, client=None, size=1, name=None,
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000389 snapshot_id=None, imageRef=None, volume_type=None,
390 wait_on_delete=True):
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900391 if client is None:
392 client = self.volume_client
393 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900394 name = data_utils.rand_name('scenario-volume-')
Eric Windisch2d26f1b2013-09-04 17:52:16 -0700395 LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900396 volume = client.volumes.create(size=size, display_name=name,
397 snapshot_id=snapshot_id,
Kaitlin Farr366a51f2014-04-21 12:43:54 -0400398 imageRef=imageRef,
399 volume_type=volume_type)
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000400 if wait_on_delete:
401 self.addCleanup(self.delete_timeout,
402 self.volume_client.volumes,
403 volume.id)
404 self.addCleanup_with_wait(self.volume_client.volumes, volume.id,
405 exc_type=cinder_exceptions.NotFound)
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900406 self.assertEqual(name, volume.display_name)
407 self.status_timeout(client.volumes, volume.id, 'available')
408 LOG.debug("Created volume: %s", volume)
409 return volume
410
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900411 def create_server_snapshot(self, server, compute_client=None,
412 image_client=None, name=None):
413 if compute_client is None:
414 compute_client = self.compute_client
415 if image_client is None:
416 image_client = self.image_client
417 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900418 name = data_utils.rand_name('scenario-snapshot-')
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900419 LOG.debug("Creating a snapshot image for server: %s", server.name)
420 image_id = compute_client.servers.create_image(server, name)
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000421 self.addCleanup_with_wait(self.image_client.images, image_id,
422 exc_type=glanceclient.exc.HTTPNotFound)
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900423 self.status_timeout(image_client.images, image_id, 'active')
424 snapshot_image = image_client.images.get(image_id)
Chang Bo Guofc77e932013-09-16 17:38:26 -0700425 self.assertEqual(name, snapshot_image.name)
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900426 LOG.debug("Created snapshot image %s for server %s",
427 snapshot_image.name, server.name)
428 return snapshot_image
429
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900430 def create_keypair(self, client=None, name=None):
431 if client is None:
432 client = self.compute_client
433 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900434 name = data_utils.rand_name('scenario-keypair-')
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900435 keypair = client.keypairs.create(name)
436 self.assertEqual(keypair.name, name)
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000437 self.addCleanup(self.delete_wrapper, keypair)
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900438 return keypair
439
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900440 def get_remote_client(self, server_or_ip, username=None, private_key=None):
llg821243b20502014-02-22 10:32:49 +0800441 if isinstance(server_or_ip, six.string_types):
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900442 ip = server_or_ip
443 else:
Matthew Treinish6c072292014-01-29 19:15:52 +0000444 network_name_for_ssh = CONF.compute.network_for_ssh
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900445 ip = server_or_ip.networks[network_name_for_ssh][0]
446 if username is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000447 username = CONF.scenario.ssh_user
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900448 if private_key is None:
449 private_key = self.keypair.private_key
Yair Fried3960c4d2014-05-07 15:20:30 +0300450 linux_client = remote_client.RemoteClient(ip, username,
451 pkey=private_key)
452 try:
453 linux_client.validate_authentication()
454 except exceptions.SSHTimeout:
455 LOG.exception('ssh connection to %s failed' % ip)
456 debug.log_net_debug()
457 raise
458
459 return linux_client
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900460
Nachi Ueno95b41282014-01-15 06:54:21 -0800461 def _log_console_output(self, servers=None):
462 if not servers:
463 servers = self.compute_client.servers.list()
464 for server in servers:
465 LOG.debug('Console output for %s', server.id)
466 LOG.debug(server.get_console_output())
467
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900468 def wait_for_volume_status(self, status):
469 volume_id = self.volume.id
470 self.status_timeout(
471 self.volume_client.volumes, volume_id, status)
472
473 def _image_create(self, name, fmt, path, properties={}):
474 name = data_utils.rand_name('%s-' % name)
475 image_file = open(path, 'rb')
476 self.addCleanup(image_file.close)
477 params = {
478 'name': name,
479 'container_format': fmt,
480 'disk_format': fmt,
Aaron Rosenc7720622014-05-20 10:38:10 -0700481 'is_public': 'False',
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900482 }
483 params.update(properties)
484 image = self.image_client.images.create(**params)
485 self.addCleanup(self.image_client.images.delete, image)
486 self.assertEqual("queued", image.status)
487 image.update(data=image_file)
488 return image.id
489
490 def glance_image_create(self):
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900491 qcow2_img_path = (CONF.scenario.img_dir + "/" +
492 CONF.scenario.qcow2_img_file)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900493 aki_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.aki_img_file
494 ari_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ari_img_file
495 ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900496 LOG.debug("paths: img: %s, ami: %s, ari: %s, aki: %s"
497 % (qcow2_img_path, ami_img_path, ari_img_path, aki_img_path))
498 try:
499 self.image = self._image_create('scenario-img',
500 'bare',
501 qcow2_img_path,
502 properties={'disk_format':
503 'qcow2'})
504 except IOError:
Masayuki Igawa188fc002014-02-23 06:42:44 +0900505 LOG.debug("A qcow2 image was not found. Try to get a uec image.")
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900506 kernel = self._image_create('scenario-aki', 'aki', aki_img_path)
507 ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path)
508 properties = {
509 'properties': {'kernel_id': kernel, 'ramdisk_id': ramdisk}
510 }
511 self.image = self._image_create('scenario-ami', 'ami',
512 path=ami_img_path,
513 properties=properties)
514 LOG.debug("image:%s" % self.image)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900515
Sean Dague6dbc6da2013-05-08 17:49:46 -0400516
David Shrewsbury06f7f8a2014-05-20 13:55:57 -0400517# power/provision states as of icehouse
518class BaremetalPowerStates(object):
519 """Possible power states of an Ironic node."""
520 POWER_ON = 'power on'
521 POWER_OFF = 'power off'
522 REBOOT = 'rebooting'
523 SUSPEND = 'suspended'
524
525
526class BaremetalProvisionStates(object):
527 """Possible provision states of an Ironic node."""
528 NOSTATE = None
529 INIT = 'initializing'
530 ACTIVE = 'active'
531 BUILDING = 'building'
532 DEPLOYWAIT = 'wait call-back'
533 DEPLOYING = 'deploying'
534 DEPLOYFAIL = 'deploy failed'
535 DEPLOYDONE = 'deploy complete'
536 DELETING = 'deleting'
537 DELETED = 'deleted'
538 ERROR = 'error'
539
540
Adam Gandelman4a48a602014-03-20 18:23:18 -0700541class BaremetalScenarioTest(OfficialClientTest):
542 @classmethod
543 def setUpClass(cls):
544 super(BaremetalScenarioTest, cls).setUpClass()
545
546 if (not CONF.service_available.ironic or
547 not CONF.baremetal.driver_enabled):
548 msg = 'Ironic not available or Ironic compute driver not enabled'
549 raise cls.skipException(msg)
550
551 # use an admin client manager for baremetal client
Adam Gandelmanacc13e62014-05-08 11:12:47 -0700552 admin_creds = cls.admin_credentials()
553 manager = clients.OfficialClientManager(credentials=admin_creds)
Adam Gandelman4a48a602014-03-20 18:23:18 -0700554 cls.baremetal_client = manager.baremetal_client
555
556 # allow any issues obtaining the node list to raise early
557 cls.baremetal_client.node.list()
558
559 def _node_state_timeout(self, node_id, state_attr,
560 target_states, timeout=10, interval=1):
561 if not isinstance(target_states, list):
562 target_states = [target_states]
563
564 def check_state():
565 node = self.get_node(node_id=node_id)
566 if getattr(node, state_attr) in target_states:
567 return True
568 return False
569
570 if not tempest.test.call_until_true(
571 check_state, timeout, interval):
572 msg = ("Timed out waiting for node %s to reach %s state(s) %s" %
573 (node_id, state_attr, target_states))
574 raise exceptions.TimeoutException(msg)
575
576 def wait_provisioning_state(self, node_id, state, timeout):
577 self._node_state_timeout(
578 node_id=node_id, state_attr='provision_state',
579 target_states=state, timeout=timeout)
580
581 def wait_power_state(self, node_id, state):
582 self._node_state_timeout(
583 node_id=node_id, state_attr='power_state',
584 target_states=state, timeout=CONF.baremetal.power_timeout)
585
586 def wait_node(self, instance_id):
587 """Waits for a node to be associated with instance_id."""
Zhi Kun Liu4a8d1ea2014-04-15 22:08:21 -0500588 from ironicclient import exc as ironic_exceptions
589
Adam Gandelman4a48a602014-03-20 18:23:18 -0700590 def _get_node():
591 node = None
592 try:
593 node = self.get_node(instance_id=instance_id)
594 except ironic_exceptions.HTTPNotFound:
595 pass
596 return node is not None
597
598 if not tempest.test.call_until_true(
599 _get_node, CONF.baremetal.association_timeout, 1):
600 msg = ('Timed out waiting to get Ironic node by instance id %s'
601 % instance_id)
602 raise exceptions.TimeoutException(msg)
603
604 def get_node(self, node_id=None, instance_id=None):
605 if node_id:
606 return self.baremetal_client.node.get(node_id)
607 elif instance_id:
608 return self.baremetal_client.node.get_by_instance_uuid(instance_id)
609
610 def get_ports(self, node_id):
611 ports = []
612 for port in self.baremetal_client.node.list_ports(node_id):
613 ports.append(self.baremetal_client.port.get(port.uuid))
614 return ports
615
David Shrewsbury06f7f8a2014-05-20 13:55:57 -0400616 def add_keypair(self):
617 self.keypair = self.create_keypair()
618
619 def verify_connectivity(self, ip=None):
620 if ip:
621 dest = self.get_remote_client(ip)
622 else:
623 dest = self.get_remote_client(self.instance)
624 dest.validate_authentication()
625
626 def boot_instance(self):
627 create_kwargs = {
628 'key_name': self.keypair.id
629 }
630 self.instance = self.create_server(
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000631 wait_on_boot=False, create_kwargs=create_kwargs)
David Shrewsbury06f7f8a2014-05-20 13:55:57 -0400632
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000633 self.addCleanup_with_wait(self.compute_client.servers,
634 self.instance.id,
635 cleanup_callable=self.delete_wrapper,
636 cleanup_args=[self.instance])
David Shrewsbury06f7f8a2014-05-20 13:55:57 -0400637
638 self.wait_node(self.instance.id)
639 self.node = self.get_node(instance_id=self.instance.id)
640
641 self.wait_power_state(self.node.uuid, BaremetalPowerStates.POWER_ON)
642
643 self.wait_provisioning_state(
644 self.node.uuid,
645 [BaremetalProvisionStates.DEPLOYWAIT,
646 BaremetalProvisionStates.ACTIVE],
647 timeout=15)
648
649 self.wait_provisioning_state(self.node.uuid,
650 BaremetalProvisionStates.ACTIVE,
651 timeout=CONF.baremetal.active_timeout)
652
653 self.status_timeout(
654 self.compute_client.servers, self.instance.id, 'ACTIVE')
655
656 self.node = self.get_node(instance_id=self.instance.id)
657 self.instance = self.compute_client.servers.get(self.instance.id)
658
659 def terminate_instance(self):
660 self.instance.delete()
David Shrewsbury06f7f8a2014-05-20 13:55:57 -0400661 self.wait_power_state(self.node.uuid, BaremetalPowerStates.POWER_OFF)
662 self.wait_provisioning_state(
663 self.node.uuid,
664 BaremetalProvisionStates.NOSTATE,
665 timeout=CONF.baremetal.unprovision_timeout)
666
Adam Gandelman4a48a602014-03-20 18:23:18 -0700667
Kaitlin Farr366a51f2014-04-21 12:43:54 -0400668class EncryptionScenarioTest(OfficialClientTest):
669 """
670 Base class for encryption scenario tests
671 """
672
673 @classmethod
674 def setUpClass(cls):
675 super(EncryptionScenarioTest, cls).setUpClass()
676
677 # use admin credentials to create encrypted volume types
678 admin_creds = cls.admin_credentials()
679 manager = clients.OfficialClientManager(credentials=admin_creds)
680 cls.admin_volume_client = manager.volume_client
681
682 def _wait_for_volume_status(self, status):
683 self.status_timeout(
684 self.volume_client.volumes, self.volume.id, status)
685
Kaitlin Farr366a51f2014-04-21 12:43:54 -0400686 def nova_boot(self):
687 self.keypair = self.create_keypair()
688 create_kwargs = {'key_name': self.keypair.name}
689 self.server = self.create_server(self.compute_client,
690 image=self.image,
691 create_kwargs=create_kwargs)
692
693 def create_volume_type(self, client=None, name=None):
694 if not client:
695 client = self.admin_volume_client
696 if not name:
697 name = 'generic'
698 randomized_name = data_utils.rand_name('scenario-type-' + name + '-')
699 LOG.debug("Creating a volume type: %s", randomized_name)
700 volume_type = client.volume_types.create(randomized_name)
701 self.addCleanup(client.volume_types.delete, volume_type.id)
702 return volume_type
703
704 def create_encryption_type(self, client=None, type_id=None, provider=None,
705 key_size=None, cipher=None,
706 control_location=None):
707 if not client:
708 client = self.admin_volume_client
709 if not type_id:
710 volume_type = self.create_volume_type()
711 type_id = volume_type.id
712 LOG.debug("Creating an encryption type for volume type: %s", type_id)
713 client.volume_encryption_types.create(type_id,
714 {'provider': provider,
715 'key_size': key_size,
716 'cipher': cipher,
717 'control_location':
718 control_location})
719
720 def nova_volume_attach(self):
721 attach_volume_client = self.compute_client.volumes.create_server_volume
722 volume = attach_volume_client(self.server.id,
723 self.volume.id,
724 '/dev/vdb')
725 self.assertEqual(self.volume.id, volume.id)
726 self._wait_for_volume_status('in-use')
727
728 def nova_volume_detach(self):
729 detach_volume_client = self.compute_client.volumes.delete_server_volume
730 detach_volume_client(self.server.id, self.volume.id)
731 self._wait_for_volume_status('available')
732
733 volume = self.volume_client.volumes.get(self.volume.id)
734 self.assertEqual('available', volume.status)
735
Kaitlin Farr366a51f2014-04-21 12:43:54 -0400736
Sean Dague6dbc6da2013-05-08 17:49:46 -0400737class NetworkScenarioTest(OfficialClientTest):
738 """
739 Base class for network scenario tests
740 """
741
742 @classmethod
743 def check_preconditions(cls):
Matthew Treinish6c072292014-01-29 19:15:52 +0000744 if (CONF.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400745 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200746 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400747 try:
748 cls.network_client.list_networks()
749 except exc.EndpointNotFound:
750 cls.enabled = False
751 raise
752 else:
753 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400754 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400755 raise cls.skipException(msg)
756
757 @classmethod
758 def setUpClass(cls):
759 super(NetworkScenarioTest, cls).setUpClass()
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000760 cls.tenant_id = cls.manager.identity_client.tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400761
Sean Dague6dbc6da2013-05-08 17:49:46 -0400762 def _create_network(self, tenant_id, namestart='network-smoke-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900763 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400764 body = dict(
765 network=dict(
766 name=name,
767 tenant_id=tenant_id,
768 ),
769 )
770 result = self.network_client.create_network(body=body)
771 network = net_common.DeletableNetwork(client=self.network_client,
772 **result['network'])
773 self.assertEqual(network.name, name)
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000774 self.addCleanup(self.delete_wrapper, network)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400775 return network
776
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200777 def _list_networks(self, **kwargs):
778 nets = self.network_client.list_networks(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400779 return nets['networks']
780
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200781 def _list_subnets(self, **kwargs):
782 subnets = self.network_client.list_subnets(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400783 return subnets['subnets']
784
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200785 def _list_routers(self, **kwargs):
786 routers = self.network_client.list_routers(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400787 return routers['routers']
788
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200789 def _list_ports(self, **kwargs):
790 ports = self.network_client.list_ports(**kwargs)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000791 return ports['ports']
792
793 def _get_tenant_own_network_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200794 nets = self._list_networks(tenant_id=tenant_id)
795 return len(nets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000796
797 def _get_tenant_own_subnet_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200798 subnets = self._list_subnets(tenant_id=tenant_id)
799 return len(subnets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000800
801 def _get_tenant_own_port_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200802 ports = self._list_ports(tenant_id=tenant_id)
803 return len(ports)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000804
Yair Fried3097dc12014-01-26 08:46:43 +0200805 def _create_subnet(self, network, namestart='subnet-smoke-', **kwargs):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400806 """
807 Create a subnet for the given network within the cidr block
808 configured for tenant networks.
809 """
Attila Fazekase857bd62013-10-21 21:02:44 +0200810
811 def cidr_in_use(cidr, tenant_id):
812 """
813 :return True if subnet with cidr already exist in tenant
814 False else
815 """
816 cidr_in_use = self._list_subnets(tenant_id=tenant_id, cidr=cidr)
817 return len(cidr_in_use) != 0
818
Matthew Treinish6c072292014-01-29 19:15:52 +0000819 tenant_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400820 result = None
821 # Repeatedly attempt subnet creation with sequential cidr
822 # blocks until an unallocated block is found.
Matthew Treinish6c072292014-01-29 19:15:52 +0000823 for subnet_cidr in tenant_cidr.subnet(
824 CONF.network.tenant_network_mask_bits):
Attila Fazekase857bd62013-10-21 21:02:44 +0200825 str_cidr = str(subnet_cidr)
826 if cidr_in_use(str_cidr, tenant_id=network.tenant_id):
827 continue
828
Sean Dague6dbc6da2013-05-08 17:49:46 -0400829 body = dict(
830 subnet=dict(
Attila Fazekase857bd62013-10-21 21:02:44 +0200831 name=data_utils.rand_name(namestart),
Sean Dague6dbc6da2013-05-08 17:49:46 -0400832 ip_version=4,
833 network_id=network.id,
834 tenant_id=network.tenant_id,
Attila Fazekase857bd62013-10-21 21:02:44 +0200835 cidr=str_cidr,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400836 ),
837 )
Yair Fried3097dc12014-01-26 08:46:43 +0200838 body['subnet'].update(kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400839 try:
840 result = self.network_client.create_subnet(body=body)
841 break
Mark McClainf2982e82013-07-06 17:48:03 -0400842 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400843 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
844 if not is_overlapping_cidr:
845 raise
846 self.assertIsNotNone(result, 'Unable to allocate tenant network')
847 subnet = net_common.DeletableSubnet(client=self.network_client,
848 **result['subnet'])
Attila Fazekase857bd62013-10-21 21:02:44 +0200849 self.assertEqual(subnet.cidr, str_cidr)
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000850 self.addCleanup(self.delete_wrapper, subnet)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400851 return subnet
852
853 def _create_port(self, network, namestart='port-quotatest-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900854 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400855 body = dict(
856 port=dict(name=name,
857 network_id=network.id,
858 tenant_id=network.tenant_id))
859 result = self.network_client.create_port(body=body)
860 self.assertIsNotNone(result, 'Unable to allocate port')
861 port = net_common.DeletablePort(client=self.network_client,
862 **result['port'])
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000863 self.addCleanup(self.delete_wrapper, port)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400864 return port
865
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200866 def _get_server_port_id(self, server, ip_addr=None):
867 ports = self._list_ports(device_id=server.id, fixed_ip=ip_addr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400868 self.assertEqual(len(ports), 1,
869 "Unable to determine which port to target.")
Yair Fried05db2522013-11-18 11:02:10 +0200870 return ports[0]['id']
871
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200872 def _create_floating_ip(self, thing, external_network_id, port_id=None):
873 if not port_id:
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400874 port_id = self._get_server_port_id(thing)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400875 body = dict(
876 floatingip=dict(
877 floating_network_id=external_network_id,
878 port_id=port_id,
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400879 tenant_id=thing.tenant_id,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400880 )
881 )
882 result = self.network_client.create_floatingip(body=body)
883 floating_ip = net_common.DeletableFloatingIp(
884 client=self.network_client,
885 **result['floatingip'])
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000886 self.addCleanup(self.delete_wrapper, floating_ip)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400887 return floating_ip
888
Yair Fried05db2522013-11-18 11:02:10 +0200889 def _associate_floating_ip(self, floating_ip, server):
890 port_id = self._get_server_port_id(server)
891 floating_ip.update(port_id=port_id)
892 self.assertEqual(port_id, floating_ip.port_id)
893 return floating_ip
894
Yair Fried9a551c42013-12-15 14:59:34 +0200895 def _disassociate_floating_ip(self, floating_ip):
896 """
897 :param floating_ip: type DeletableFloatingIp
898 """
899 floating_ip.update(port_id=None)
llg8212e4cd3922014-02-15 12:14:21 +0800900 self.assertIsNone(floating_ip.port_id)
Yair Fried9a551c42013-12-15 14:59:34 +0200901 return floating_ip
902
903 def _ping_ip_address(self, ip_address, should_succeed=True):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400904 cmd = ['ping', '-c1', '-w1', ip_address]
905
906 def ping():
907 proc = subprocess.Popen(cmd,
908 stdout=subprocess.PIPE,
909 stderr=subprocess.PIPE)
910 proc.wait()
Yair Fried9a551c42013-12-15 14:59:34 +0200911 return (proc.returncode == 0) == should_succeed
Sean Dague6dbc6da2013-05-08 17:49:46 -0400912
Nachi Ueno6d580be2013-07-24 10:58:11 -0700913 return tempest.test.call_until_true(
Matthew Treinish6c072292014-01-29 19:15:52 +0000914 ping, CONF.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000915
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400916 def _create_pool(self, lb_method, protocol, subnet_id):
917 """Wrapper utility that returns a test pool."""
918 name = data_utils.rand_name('pool-')
919 body = {
920 "pool": {
921 "protocol": protocol,
922 "name": name,
923 "subnet_id": subnet_id,
924 "lb_method": lb_method
925 }
926 }
927 resp = self.network_client.create_pool(body=body)
928 pool = net_common.DeletablePool(client=self.network_client,
929 **resp['pool'])
930 self.assertEqual(pool['name'], name)
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000931 self.addCleanup(self.delete_wrapper, pool)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400932 return pool
933
934 def _create_member(self, address, protocol_port, pool_id):
935 """Wrapper utility that returns a test member."""
936 body = {
937 "member": {
938 "protocol_port": protocol_port,
939 "pool_id": pool_id,
940 "address": address
941 }
942 }
943 resp = self.network_client.create_member(body)
944 member = net_common.DeletableMember(client=self.network_client,
945 **resp['member'])
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000946 self.addCleanup(self.delete_wrapper, member)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400947 return member
948
949 def _create_vip(self, protocol, protocol_port, subnet_id, pool_id):
950 """Wrapper utility that returns a test vip."""
951 name = data_utils.rand_name('vip-')
952 body = {
953 "vip": {
954 "protocol": protocol,
955 "name": name,
956 "subnet_id": subnet_id,
957 "pool_id": pool_id,
958 "protocol_port": protocol_port
959 }
960 }
961 resp = self.network_client.create_vip(body)
962 vip = net_common.DeletableVip(client=self.network_client,
963 **resp['vip'])
964 self.assertEqual(vip['name'], name)
Matthew Treinishb7144eb2013-12-13 22:57:35 +0000965 self.addCleanup(self.delete_wrapper, vip)
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400966 return vip
967
Yair Fried9a551c42013-12-15 14:59:34 +0200968 def _check_vm_connectivity(self, ip_address,
969 username=None,
970 private_key=None,
971 should_connect=True):
972 """
973 :param ip_address: server to test against
974 :param username: server's ssh username
975 :param private_key: server's ssh private key to be used
976 :param should_connect: True/False indicates positive/negative test
977 positive - attempt ping and ssh
978 negative - attempt ping and fail if succeed
979
980 :raises: AssertError if the result of the connectivity check does
981 not match the value of the should_connect param
982 """
983 if should_connect:
984 msg = "Timed out waiting for %s to become reachable" % ip_address
985 else:
986 msg = "ip address %s is reachable" % ip_address
987 self.assertTrue(self._ping_ip_address(ip_address,
988 should_succeed=should_connect),
989 msg=msg)
990 if should_connect:
991 # no need to check ssh for negative connectivity
Yair Fried3960c4d2014-05-07 15:20:30 +0300992 self.get_remote_client(ip_address, username, private_key)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200993
Matt Riedemann343305f2014-05-27 09:55:03 -0700994 def _check_public_network_connectivity(self, ip_address, username,
995 private_key, should_connect=True,
996 msg=None, servers=None):
997 # The target login is assumed to have been configured for
998 # key-based authentication by cloud-init.
999 LOG.debug('checking network connections to IP %s with user: %s' %
1000 (ip_address, username))
1001 try:
1002 self._check_vm_connectivity(ip_address,
1003 username,
1004 private_key,
1005 should_connect=should_connect)
Yair Fried3960c4d2014-05-07 15:20:30 +03001006 except Exception as e:
Matt Riedemann343305f2014-05-27 09:55:03 -07001007 ex_msg = 'Public network connectivity check failed'
1008 if msg:
1009 ex_msg += ": " + msg
1010 LOG.exception(ex_msg)
1011 self._log_console_output(servers)
Yair Fried3960c4d2014-05-07 15:20:30 +03001012 # network debug is called as part of ssh init
1013 if not isinstance(e, exceptions.SSHTimeout):
1014 debug.log_net_debug()
Matt Riedemann343305f2014-05-27 09:55:03 -07001015 raise
1016
Matt Riedemann2d005be2014-05-27 10:52:35 -07001017 def _check_tenant_network_connectivity(self, server,
1018 username,
1019 private_key,
1020 should_connect=True,
1021 servers_for_debug=None):
1022 if not CONF.network.tenant_networks_reachable:
1023 msg = 'Tenant networks not configured to be reachable.'
1024 LOG.info(msg)
1025 return
1026 # The target login is assumed to have been configured for
1027 # key-based authentication by cloud-init.
1028 try:
1029 for net_name, ip_addresses in server.networks.iteritems():
1030 for ip_address in ip_addresses:
1031 self._check_vm_connectivity(ip_address,
1032 username,
1033 private_key,
1034 should_connect=should_connect)
Yair Fried3960c4d2014-05-07 15:20:30 +03001035 except Exception as e:
Matt Riedemann2d005be2014-05-27 10:52:35 -07001036 LOG.exception('Tenant network connectivity check failed')
1037 self._log_console_output(servers_for_debug)
Yair Fried3960c4d2014-05-07 15:20:30 +03001038 # network debug is called as part of ssh init
1039 if not isinstance(e, exceptions.SSHTimeout):
1040 debug.log_net_debug()
Matt Riedemann2d005be2014-05-27 10:52:35 -07001041 raise
1042
Yair Fried3097dc12014-01-26 08:46:43 +02001043 def _check_remote_connectivity(self, source, dest, should_succeed=True):
1044 """
1045 check ping server via source ssh connection
1046
1047 :param source: RemoteClient: an ssh connection from which to ping
1048 :param dest: and IP to ping against
1049 :param should_succeed: boolean should ping succeed or not
1050 :returns: boolean -- should_succeed == ping
1051 :returns: ping is false if ping failed
1052 """
1053 def ping_remote():
1054 try:
1055 source.ping_host(dest)
1056 except exceptions.SSHExecCommandFailed:
1057 LOG.exception('Failed to ping host via ssh connection')
1058 return not should_succeed
1059 return should_succeed
1060
1061 return tempest.test.call_until_true(ping_remote,
1062 CONF.compute.ping_timeout,
1063 1)
1064
Yair Friedeb69f3f2013-10-10 13:18:16 +03001065 def _create_security_group_neutron(self, tenant_id, client=None,
1066 namestart='secgroup-smoke-'):
1067 if client is None:
1068 client = self.network_client
1069 secgroup = self._create_empty_security_group(namestart=namestart,
1070 client=client,
1071 tenant_id=tenant_id)
1072
1073 # Add rules to the security group
1074 rules = self._create_loginable_secgroup_rule_neutron(secgroup=secgroup)
1075 for rule in rules:
1076 self.assertEqual(tenant_id, rule.tenant_id)
1077 self.assertEqual(secgroup.id, rule.security_group_id)
1078 return secgroup
1079
1080 def _create_empty_security_group(self, tenant_id, client=None,
1081 namestart='secgroup-smoke-'):
1082 """Create a security group without rules.
1083
1084 Default rules will be created:
1085 - IPv4 egress to any
1086 - IPv6 egress to any
1087
1088 :param tenant_id: secgroup will be created in this tenant
1089 :returns: DeletableSecurityGroup -- containing the secgroup created
1090 """
1091 if client is None:
1092 client = self.network_client
1093 sg_name = data_utils.rand_name(namestart)
1094 sg_desc = sg_name + " description"
1095 sg_dict = dict(name=sg_name,
1096 description=sg_desc)
1097 sg_dict['tenant_id'] = tenant_id
1098 body = dict(security_group=sg_dict)
1099 result = client.create_security_group(body=body)
1100 secgroup = net_common.DeletableSecurityGroup(
1101 client=client,
1102 **result['security_group']
1103 )
1104 self.assertEqual(secgroup.name, sg_name)
1105 self.assertEqual(tenant_id, secgroup.tenant_id)
1106 self.assertEqual(secgroup.description, sg_desc)
Matthew Treinishb7144eb2013-12-13 22:57:35 +00001107 self.addCleanup(self.delete_wrapper, secgroup)
Yair Friedeb69f3f2013-10-10 13:18:16 +03001108 return secgroup
1109
1110 def _default_security_group(self, tenant_id, client=None):
1111 """Get default secgroup for given tenant_id.
1112
1113 :returns: DeletableSecurityGroup -- default secgroup for given tenant
1114 """
1115 if client is None:
1116 client = self.network_client
1117 sgs = [
1118 sg for sg in client.list_security_groups().values()[0]
1119 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
1120 ]
1121 msg = "No default security group for tenant %s." % (tenant_id)
1122 self.assertTrue(len(sgs) > 0, msg)
1123 if len(sgs) > 1:
1124 msg = "Found %d default security groups" % len(sgs)
1125 raise exc.NeutronClientNoUniqueMatch(msg=msg)
1126 return net_common.DeletableSecurityGroup(client=client,
1127 **sgs[0])
1128
1129 def _create_security_group_rule(self, client=None, secgroup=None,
1130 tenant_id=None, **kwargs):
1131 """Create a rule from a dictionary of rule parameters.
1132
1133 Create a rule in a secgroup. if secgroup not defined will search for
1134 default secgroup in tenant_id.
1135
1136 :param secgroup: type DeletableSecurityGroup.
1137 :param secgroup_id: search for secgroup by id
1138 default -- choose default secgroup for given tenant_id
1139 :param tenant_id: if secgroup not passed -- the tenant in which to
1140 search for default secgroup
1141 :param kwargs: a dictionary containing rule parameters:
1142 for example, to allow incoming ssh:
1143 rule = {
1144 direction: 'ingress'
1145 protocol:'tcp',
1146 port_range_min: 22,
1147 port_range_max: 22
1148 }
1149 """
1150 if client is None:
1151 client = self.network_client
1152 if secgroup is None:
1153 secgroup = self._default_security_group(tenant_id)
1154
1155 ruleset = dict(security_group_id=secgroup.id,
1156 tenant_id=secgroup.tenant_id,
1157 )
1158 ruleset.update(kwargs)
1159
1160 body = dict(security_group_rule=dict(ruleset))
1161 sg_rule = client.create_security_group_rule(body=body)
1162 sg_rule = net_common.DeletableSecurityGroupRule(
1163 client=client,
1164 **sg_rule['security_group_rule']
1165 )
Matthew Treinishb7144eb2013-12-13 22:57:35 +00001166 self.addCleanup(self.delete_wrapper, sg_rule)
Yair Friedeb69f3f2013-10-10 13:18:16 +03001167 self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
1168 self.assertEqual(secgroup.id, sg_rule.security_group_id)
1169
1170 return sg_rule
1171
1172 def _create_loginable_secgroup_rule_neutron(self, client=None,
1173 secgroup=None):
1174 """These rules are intended to permit inbound ssh and icmp
1175 traffic from all sources, so no group_id is provided.
1176 Setting a group_id would only permit traffic from ports
1177 belonging to the same security group.
1178 """
1179
1180 if client is None:
1181 client = self.network_client
1182 rules = []
1183 rulesets = [
1184 dict(
1185 # ssh
1186 protocol='tcp',
1187 port_range_min=22,
1188 port_range_max=22,
1189 ),
1190 dict(
1191 # ping
1192 protocol='icmp',
1193 )
1194 ]
1195 for ruleset in rulesets:
1196 for r_direction in ['ingress', 'egress']:
1197 ruleset['direction'] = r_direction
1198 try:
1199 sg_rule = self._create_security_group_rule(
1200 client=client, secgroup=secgroup, **ruleset)
1201 except exc.NeutronClientException as ex:
1202 # if rule already exist - skip rule and continue
1203 if not (ex.status_code is 409 and 'Security group rule'
1204 ' already exists' in ex.message):
1205 raise ex
1206 else:
1207 self.assertEqual(r_direction, sg_rule.direction)
1208 rules.append(sg_rule)
1209
1210 return rules
1211
Yair Fried5f670ab2013-12-09 09:26:51 +02001212 def _ssh_to_server(self, server, private_key):
Matthew Treinish6c072292014-01-29 19:15:52 +00001213 ssh_login = CONF.compute.image_ssh_user
Yair Fried5f670ab2013-12-09 09:26:51 +02001214 return self.get_remote_client(server,
1215 username=ssh_login,
1216 private_key=private_key)
1217
Yuiko Takada7f4b1b32013-11-20 08:06:26 +00001218 def _show_quota_network(self, tenant_id):
1219 quota = self.network_client.show_quota(tenant_id)
1220 return quota['quota']['network']
1221
1222 def _show_quota_subnet(self, tenant_id):
1223 quota = self.network_client.show_quota(tenant_id)
1224 return quota['quota']['subnet']
1225
1226 def _show_quota_port(self, tenant_id):
1227 quota = self.network_client.show_quota(tenant_id)
1228 return quota['quota']['port']
1229
Yair Fried4d7efa62013-11-17 17:12:29 +02001230 def _get_router(self, tenant_id):
1231 """Retrieve a router for the given tenant id.
1232
1233 If a public router has been configured, it will be returned.
1234
1235 If a public router has not been configured, but a public
1236 network has, a tenant router will be created and returned that
1237 routes traffic to the public network.
1238 """
Matthew Treinish6c072292014-01-29 19:15:52 +00001239 router_id = CONF.network.public_router_id
1240 network_id = CONF.network.public_network_id
Yair Fried4d7efa62013-11-17 17:12:29 +02001241 if router_id:
1242 result = self.network_client.show_router(router_id)
1243 return net_common.AttributeDict(**result['router'])
1244 elif network_id:
1245 router = self._create_router(tenant_id)
1246 router.add_gateway(network_id)
1247 return router
1248 else:
1249 raise Exception("Neither of 'public_router_id' or "
1250 "'public_network_id' has been defined.")
1251
1252 def _create_router(self, tenant_id, namestart='router-smoke-'):
1253 name = data_utils.rand_name(namestart)
1254 body = dict(
1255 router=dict(
1256 name=name,
1257 admin_state_up=True,
1258 tenant_id=tenant_id,
1259 ),
1260 )
1261 result = self.network_client.create_router(body=body)
1262 router = net_common.DeletableRouter(client=self.network_client,
1263 **result['router'])
1264 self.assertEqual(router.name, name)
Matthew Treinishb7144eb2013-12-13 22:57:35 +00001265 self.addCleanup(self.delete_wrapper, router)
Yair Fried4d7efa62013-11-17 17:12:29 +02001266 return router
1267
1268 def _create_networks(self, tenant_id=None):
1269 """Create a network with a subnet connected to a router.
1270
1271 :returns: network, subnet, router
1272 """
1273 if tenant_id is None:
1274 tenant_id = self.tenant_id
1275 network = self._create_network(tenant_id)
1276 router = self._get_router(tenant_id)
1277 subnet = self._create_subnet(network)
1278 subnet.add_to_router(router.id)
Yair Fried4d7efa62013-11-17 17:12:29 +02001279 return network, subnet, router
1280
Steve Bakerdd7c6ce2013-06-24 14:46:47 +12001281
1282class OrchestrationScenarioTest(OfficialClientTest):
1283 """
1284 Base class for orchestration scenario tests
1285 """
1286
1287 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -07001288 def setUpClass(cls):
1289 super(OrchestrationScenarioTest, cls).setUpClass()
Matthew Treinish6c072292014-01-29 19:15:52 +00001290 if not CONF.service_available.heat:
Matt Riedemann11c5b642013-08-24 08:45:38 -07001291 raise cls.skipException("Heat support is required")
1292
1293 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +12001294 def credentials(cls):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +00001295 admin_creds = auth.get_default_credentials('identity_admin')
1296 creds = auth.get_default_credentials('user')
1297 admin_creds.tenant_name = creds.tenant_name
1298 return admin_creds
Steve Bakerdd7c6ce2013-06-24 14:46:47 +12001299
1300 def _load_template(self, base_file, file_name):
1301 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
1302 file_name)
1303 with open(filepath) as f:
1304 return f.read()
1305
1306 @classmethod
1307 def _stack_rand_name(cls):
Masayuki Igawa259c1132013-10-31 17:48:44 +09001308 return data_utils.rand_name(cls.__name__ + '-')
Steve Baker80252da2013-09-25 13:29:10 +12001309
1310 @classmethod
1311 def _get_default_network(cls):
1312 networks = cls.network_client.list_networks()
1313 for net in networks['networks']:
Matthew Treinish6c072292014-01-29 19:15:52 +00001314 if net['name'] == CONF.compute.fixed_network_name:
Steve Baker80252da2013-09-25 13:29:10 +12001315 return net
Steve Baker22c16602014-05-05 13:34:19 +12001316
1317 @staticmethod
1318 def _stack_output(stack, output_key):
1319 """Return a stack output value for a given key."""
1320 return next((o['output_value'] for o in stack.outputs
1321 if o['output_key'] == output_key), None)
1322
1323 def _ping_ip_address(self, ip_address, should_succeed=True):
1324 cmd = ['ping', '-c1', '-w1', ip_address]
1325
1326 def ping():
1327 proc = subprocess.Popen(cmd,
1328 stdout=subprocess.PIPE,
1329 stderr=subprocess.PIPE)
1330 proc.wait()
1331 return (proc.returncode == 0) == should_succeed
1332
1333 return tempest.test.call_until_true(
1334 ping, CONF.orchestration.build_timeout, 1)
1335
1336 def _wait_for_resource_status(self, stack_identifier, resource_name,
1337 status, failure_pattern='^.*_FAILED$'):
1338 """Waits for a Resource to reach a given status."""
1339 fail_regexp = re.compile(failure_pattern)
1340 build_timeout = CONF.orchestration.build_timeout
1341 build_interval = CONF.orchestration.build_interval
1342
1343 start = timeutils.utcnow()
1344 while timeutils.delta_seconds(start,
1345 timeutils.utcnow()) < build_timeout:
1346 try:
1347 res = self.client.resources.get(
1348 stack_identifier, resource_name)
1349 except heat_exceptions.HTTPNotFound:
1350 # ignore this, as the resource may not have
1351 # been created yet
1352 pass
1353 else:
1354 if res.resource_status == status:
1355 return
1356 if fail_regexp.search(res.resource_status):
1357 raise exceptions.StackResourceBuildErrorException(
1358 resource_name=res.resource_name,
1359 stack_identifier=stack_identifier,
1360 resource_status=res.resource_status,
1361 resource_status_reason=res.resource_status_reason)
1362 time.sleep(build_interval)
1363
1364 message = ('Resource %s failed to reach %s status within '
1365 'the required time (%s s).' %
1366 (res.resource_name, status, build_timeout))
1367 raise exceptions.TimeoutException(message)
1368
1369 def _wait_for_stack_status(self, stack_identifier, status,
1370 failure_pattern='^.*_FAILED$'):
1371 """
1372 Waits for a Stack to reach a given status.
1373
1374 Note this compares the full $action_$status, e.g
1375 CREATE_COMPLETE, not just COMPLETE which is exposed
1376 via the status property of Stack in heatclient
1377 """
1378 fail_regexp = re.compile(failure_pattern)
1379 build_timeout = CONF.orchestration.build_timeout
1380 build_interval = CONF.orchestration.build_interval
1381
1382 start = timeutils.utcnow()
1383 while timeutils.delta_seconds(start,
1384 timeutils.utcnow()) < build_timeout:
1385 try:
1386 stack = self.client.stacks.get(stack_identifier)
1387 except heat_exceptions.HTTPNotFound:
1388 # ignore this, as the stackource may not have
1389 # been created yet
1390 pass
1391 else:
1392 if stack.stack_status == status:
1393 return
1394 if fail_regexp.search(stack.stack_status):
1395 raise exceptions.StackBuildErrorException(
1396 stack_identifier=stack_identifier,
1397 stack_status=stack.stack_status,
1398 stack_status_reason=stack.stack_status_reason)
1399 time.sleep(build_interval)
1400
1401 message = ('Stack %s failed to reach %s status within '
1402 'the required time (%s s).' %
1403 (stack.stack_name, status, build_timeout))
1404 raise exceptions.TimeoutException(message)
1405
1406 def _stack_delete(self, stack_identifier):
1407 try:
1408 self.client.stacks.delete(stack_identifier)
1409 except heat_exceptions.HTTPNotFound:
1410 pass