blob: 1763808cf167f0678105c12a09e779b4bb6ee2f8 [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
Sean Dague6dbc6da2013-05-08 17:49:46 -040019import subprocess
20
Sean Dague6dbc6da2013-05-08 17:49:46 -040021import netaddr
Mark McClainf2982e82013-07-06 17:48:03 -040022from neutronclient.common import exceptions as exc
fujioka yuuichi636f8db2013-08-09 12:05:24 +090023from novaclient import exceptions as nova_exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040024
Sean Dague1937d092013-05-17 16:36:38 -040025from tempest.api.network import common as net_common
Andrea Frittolif9cde7e2014-02-18 09:57:04 +000026from tempest import clients
Matthew Treinishb86cda92013-07-29 11:22:23 -040027from tempest.common import isolated_creds
Masayuki Igawa259c1132013-10-31 17:48:44 +090028from tempest.common.utils import data_utils
Masayuki Igawa4ded9f02014-02-17 15:05:59 +090029from tempest.common.utils.linux import remote_client
Matthew Treinish6c072292014-01-29 19:15:52 +000030from tempest import config
Giulio Fidente92f77192013-08-26 17:13:28 +020031from tempest import exceptions
Attila Fazekasfb7552a2013-08-27 13:02:26 +020032from tempest.openstack.common import log
Sean Dague6dbc6da2013-05-08 17:49:46 -040033import tempest.test
Sean Dague6dbc6da2013-05-08 17:49:46 -040034
Matthew Treinish6c072292014-01-29 19:15:52 +000035CONF = config.CONF
Sean Dague6dbc6da2013-05-08 17:49:46 -040036
Attila Fazekasfb7552a2013-08-27 13:02:26 +020037LOG = log.getLogger(__name__)
38
39# NOTE(afazekas): Workaround for the stdout logging
40LOG_nova_client = logging.getLogger('novaclient.client')
41LOG_nova_client.addHandler(log.NullHandler())
42
43LOG_cinder_client = logging.getLogger('cinderclient.client')
44LOG_cinder_client.addHandler(log.NullHandler())
Sean Dague6dbc6da2013-05-08 17:49:46 -040045
46
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040047class OfficialClientTest(tempest.test.BaseTestCase):
Sean Dague6dbc6da2013-05-08 17:49:46 -040048 """
49 Official Client test base class for scenario testing.
50
51 Official Client tests are tests that have the following characteristics:
52
53 * Test basic operations of an API, typically in an order that
54 a regular user would perform those operations
55 * Test only the correct inputs and action paths -- no fuzz or
56 random input data is sent, only valid inputs.
57 * Use only the default client tool for calling an API
58 """
59
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040060 @classmethod
61 def setUpClass(cls):
Attila Fazekasf86fa312013-07-30 19:56:39 +020062 super(OfficialClientTest, cls).setUpClass()
Matthew Treinishb86cda92013-07-29 11:22:23 -040063 cls.isolated_creds = isolated_creds.IsolatedCreds(
Sean Dague6969b902014-01-28 06:48:37 -050064 cls.__name__, tempest_client=False,
Matthew Treinish9f756a02014-01-15 10:26:07 -050065 network_resources=cls.network_resources)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120066
Yair Fried769bbff2013-12-18 16:33:17 +020067 username, password, tenant_name = cls.credentials()
Matthew Treinishb86cda92013-07-29 11:22:23 -040068
Andrea Frittolif9cde7e2014-02-18 09:57:04 +000069 cls.manager = clients.OfficialClientManager(
70 username, password, tenant_name)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040071 cls.compute_client = cls.manager.compute_client
72 cls.image_client = cls.manager.image_client
73 cls.identity_client = cls.manager.identity_client
74 cls.network_client = cls.manager.network_client
75 cls.volume_client = cls.manager.volume_client
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +000076 cls.object_storage_client = cls.manager.object_storage_client
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120077 cls.orchestration_client = cls.manager.orchestration_client
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040078 cls.resource_keys = {}
79 cls.os_resources = []
Sean Dague6dbc6da2013-05-08 17:49:46 -040080
81 @classmethod
Yair Frieda71cc442013-12-18 13:32:36 +020082 def _get_credentials(cls, get_creds, prefix):
Matthew Treinish6c072292014-01-29 19:15:52 +000083 if CONF.compute.allow_tenant_isolation:
Yair Fried769bbff2013-12-18 16:33:17 +020084 username, tenant_name, password = get_creds()
85 else:
Matthew Treinish6c072292014-01-29 19:15:52 +000086 username = getattr(CONF.identity, prefix + 'username')
87 password = getattr(CONF.identity, prefix + 'password')
88 tenant_name = getattr(CONF.identity, prefix + 'tenant_name')
Yair Fried769bbff2013-12-18 16:33:17 +020089 return username, password, tenant_name
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120090
91 @classmethod
Yair Frieda71cc442013-12-18 13:32:36 +020092 def credentials(cls):
93 return cls._get_credentials(cls.isolated_creds.get_primary_creds, '')
94
95 @classmethod
96 def alt_credentials(cls):
97 return cls._get_credentials(cls.isolated_creds.get_alt_creds, 'alt_')
98
99 @classmethod
100 def admin_credentials(cls):
101 return cls._get_credentials(cls.isolated_creds.get_admin_creds,
102 'admin_')
103
Yair Friedbf2e2c42014-01-28 12:06:38 +0200104 @staticmethod
105 def cleanup_resource(resource, test_name):
106
107 LOG.debug("Deleting %r from shared resources of %s" %
108 (resource, test_name))
109 try:
110 # OpenStack resources are assumed to have a delete()
111 # method which destroys the resource...
112 resource.delete()
113 except Exception as e:
114 # If the resource is already missing, mission accomplished.
115 # add status code as workaround for bug 1247568
116 if (e.__class__.__name__ == 'NotFound' or
117 (hasattr(e, 'status_code') and e.status_code == 404)):
118 return
119 raise
120
121 def is_deletion_complete():
122 # Deletion testing is only required for objects whose
123 # existence cannot be checked via retrieval.
124 if isinstance(resource, dict):
125 return True
126 try:
127 resource.get()
128 except Exception as e:
129 # Clients are expected to return an exception
130 # called 'NotFound' if retrieval fails.
131 if e.__class__.__name__ == 'NotFound':
132 return True
133 raise
134 return False
135
136 # Block until resource deletion has completed or timed-out
137 tempest.test.call_until_true(is_deletion_complete, 10, 1)
138
Yair Frieda71cc442013-12-18 13:32:36 +0200139 @classmethod
Sean Dague6dbc6da2013-05-08 17:49:46 -0400140 def tearDownClass(cls):
141 # NOTE(jaypipes): Because scenario tests are typically run in a
142 # specific order, and because test methods in scenario tests
143 # generally create resources in a particular order, we destroy
144 # resources in the reverse order in which resources are added to
145 # the scenario test class object
146 while cls.os_resources:
147 thing = cls.os_resources.pop()
Yair Friedbf2e2c42014-01-28 12:06:38 +0200148 cls.cleanup_resource(thing, cls.__name__)
Matthew Treinishb86cda92013-07-29 11:22:23 -0400149 cls.isolated_creds.clear_isolated_creds()
150 super(OfficialClientTest, cls).tearDownClass()
Sean Dague6dbc6da2013-05-08 17:49:46 -0400151
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400152 @classmethod
153 def set_resource(cls, key, thing):
154 LOG.debug("Adding %r to shared resources of %s" %
155 (thing, cls.__name__))
156 cls.resource_keys[key] = thing
157 cls.os_resources.append(thing)
158
159 @classmethod
160 def get_resource(cls, key):
161 return cls.resource_keys[key]
162
163 @classmethod
164 def remove_resource(cls, key):
165 thing = cls.resource_keys[key]
166 cls.os_resources.remove(thing)
167 del cls.resource_keys[key]
168
Steve Bakerefde7612013-09-30 11:29:23 +1300169 def status_timeout(self, things, thing_id, expected_status,
170 error_status='ERROR',
171 not_found_exception=nova_exceptions.NotFound):
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400172 """
173 Given a thing and an expected status, do a loop, sleeping
174 for a configurable amount of time, checking for the
175 expected status to show. At any time, if the returned
176 status of the thing is ERROR, fail out.
177 """
Steve Bakerefde7612013-09-30 11:29:23 +1300178 self._status_timeout(things, thing_id,
179 expected_status=expected_status,
180 error_status=error_status,
181 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900182
Steve Bakerefde7612013-09-30 11:29:23 +1300183 def delete_timeout(self, things, thing_id,
184 error_status='ERROR',
185 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900186 """
187 Given a thing, do a loop, sleeping
188 for a configurable amount of time, checking for the
189 deleted status to show. At any time, if the returned
190 status of the thing is ERROR, fail out.
191 """
192 self._status_timeout(things,
193 thing_id,
Steve Bakerefde7612013-09-30 11:29:23 +1300194 allow_notfound=True,
195 error_status=error_status,
196 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900197
198 def _status_timeout(self,
199 things,
200 thing_id,
201 expected_status=None,
Steve Bakerefde7612013-09-30 11:29:23 +1300202 allow_notfound=False,
203 error_status='ERROR',
204 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900205
206 log_status = expected_status if expected_status else ''
207 if allow_notfound:
208 log_status += ' or NotFound' if log_status != '' else 'NotFound'
209
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400210 def check_status():
211 # python-novaclient has resources available to its client
212 # that all implement a get() method taking an identifier
213 # for the singular resource to retrieve.
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900214 try:
215 thing = things.get(thing_id)
Steve Bakerefde7612013-09-30 11:29:23 +1300216 except not_found_exception:
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900217 if allow_notfound:
218 return True
219 else:
220 raise
221
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400222 new_status = thing.status
Brent Eaglesc26d4522013-12-02 13:28:49 -0500223
224 # Some components are reporting error status in lower case
225 # so case sensitive comparisons can really mess things
226 # up.
227 if new_status.lower() == error_status.lower():
Ken'ichi Ohmichiab1496f2013-12-12 22:17:57 +0900228 message = ("%s failed to get to expected status. "
229 "In %s state.") % (thing, new_status)
Masayuki Igawaa0e786a2014-01-27 15:25:06 +0900230 raise exceptions.BuildErrorException(message,
231 server_id=thing_id)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900232 elif new_status == expected_status and expected_status is not None:
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400233 return True # All good.
234 LOG.debug("Waiting for %s to get to %s status. "
235 "Currently in %s status",
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900236 thing, log_status, new_status)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400237 if not tempest.test.call_until_true(
238 check_status,
Matthew Treinish6c072292014-01-29 19:15:52 +0000239 CONF.compute.build_timeout,
240 CONF.compute.build_interval):
Ken'ichi Ohmichiab1496f2013-12-12 22:17:57 +0900241 message = ("Timed out waiting for thing %s "
242 "to become %s") % (thing_id, log_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200243 raise exceptions.TimeoutException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400244
Yair Friedeb69f3f2013-10-10 13:18:16 +0300245 def _create_loginable_secgroup_rule_nova(self, client=None,
246 secgroup_id=None):
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900247 if client is None:
248 client = self.compute_client
249 if secgroup_id is None:
250 sgs = client.security_groups.list()
251 for sg in sgs:
252 if sg.name == 'default':
253 secgroup_id = sg.id
254
255 # These rules are intended to permit inbound ssh and icmp
256 # traffic from all sources, so no group_id is provided.
257 # Setting a group_id would only permit traffic from ports
258 # belonging to the same security group.
259 rulesets = [
260 {
261 # ssh
262 'ip_protocol': 'tcp',
263 'from_port': 22,
264 'to_port': 22,
265 'cidr': '0.0.0.0/0',
266 },
267 {
268 # ping
269 'ip_protocol': 'icmp',
270 'from_port': -1,
271 'to_port': -1,
272 'cidr': '0.0.0.0/0',
273 }
274 ]
Yair Friedeb69f3f2013-10-10 13:18:16 +0300275 rules = list()
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900276 for ruleset in rulesets:
277 sg_rule = client.security_group_rules.create(secgroup_id,
278 **ruleset)
279 self.set_resource(sg_rule.id, sg_rule)
Yair Friedeb69f3f2013-10-10 13:18:16 +0300280 rules.append(sg_rule)
281 return rules
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900282
Giulio Fidente61cadca2013-09-24 18:33:37 +0200283 def create_server(self, client=None, name=None, image=None, flavor=None,
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900284 create_kwargs={}):
Giulio Fidente61cadca2013-09-24 18:33:37 +0200285 if client is None:
286 client = self.compute_client
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900287 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900288 name = data_utils.rand_name('scenario-server-')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900289 if image is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000290 image = CONF.compute.image_ref
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900291 if flavor is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000292 flavor = CONF.compute.flavor_ref
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900293 LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
294 name, image, flavor)
295 server = client.servers.create(name, image, flavor, **create_kwargs)
Giulio Fidente92f77192013-08-26 17:13:28 +0200296 self.assertEqual(server.name, name)
297 self.set_resource(name, server)
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900298 self.status_timeout(client.servers, server.id, 'ACTIVE')
299 # The instance retrieved on creation is missing network
300 # details, necessitating retrieval after it becomes active to
301 # ensure correct details.
302 server = client.servers.get(server.id)
303 self.set_resource(name, server)
304 LOG.debug("Created server: %s", server)
305 return server
306
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900307 def create_volume(self, client=None, size=1, name=None,
308 snapshot_id=None, imageRef=None):
309 if client is None:
310 client = self.volume_client
311 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900312 name = data_utils.rand_name('scenario-volume-')
Eric Windisch2d26f1b2013-09-04 17:52:16 -0700313 LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900314 volume = client.volumes.create(size=size, display_name=name,
315 snapshot_id=snapshot_id,
316 imageRef=imageRef)
317 self.set_resource(name, volume)
318 self.assertEqual(name, volume.display_name)
319 self.status_timeout(client.volumes, volume.id, 'available')
320 LOG.debug("Created volume: %s", volume)
321 return volume
322
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900323 def create_server_snapshot(self, server, compute_client=None,
324 image_client=None, name=None):
325 if compute_client is None:
326 compute_client = self.compute_client
327 if image_client is None:
328 image_client = self.image_client
329 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900330 name = data_utils.rand_name('scenario-snapshot-')
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900331 LOG.debug("Creating a snapshot image for server: %s", server.name)
332 image_id = compute_client.servers.create_image(server, name)
333 self.addCleanup(image_client.images.delete, image_id)
334 self.status_timeout(image_client.images, image_id, 'active')
335 snapshot_image = image_client.images.get(image_id)
Chang Bo Guofc77e932013-09-16 17:38:26 -0700336 self.assertEqual(name, snapshot_image.name)
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900337 LOG.debug("Created snapshot image %s for server %s",
338 snapshot_image.name, server.name)
339 return snapshot_image
340
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900341 def create_keypair(self, client=None, name=None):
342 if client is None:
343 client = self.compute_client
344 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900345 name = data_utils.rand_name('scenario-keypair-')
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900346 keypair = client.keypairs.create(name)
347 self.assertEqual(keypair.name, name)
348 self.set_resource(name, keypair)
349 return keypair
350
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900351 def get_remote_client(self, server_or_ip, username=None, private_key=None):
352 if isinstance(server_or_ip, basestring):
353 ip = server_or_ip
354 else:
Matthew Treinish6c072292014-01-29 19:15:52 +0000355 network_name_for_ssh = CONF.compute.network_for_ssh
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900356 ip = server_or_ip.networks[network_name_for_ssh][0]
357 if username is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000358 username = CONF.scenario.ssh_user
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900359 if private_key is None:
360 private_key = self.keypair.private_key
Masayuki Igawa4ded9f02014-02-17 15:05:59 +0900361 return remote_client.RemoteClient(ip, username, pkey=private_key)
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900362
Nachi Ueno95b41282014-01-15 06:54:21 -0800363 def _log_console_output(self, servers=None):
364 if not servers:
365 servers = self.compute_client.servers.list()
366 for server in servers:
367 LOG.debug('Console output for %s', server.id)
368 LOG.debug(server.get_console_output())
369
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900370 def wait_for_volume_status(self, status):
371 volume_id = self.volume.id
372 self.status_timeout(
373 self.volume_client.volumes, volume_id, status)
374
375 def _image_create(self, name, fmt, path, properties={}):
376 name = data_utils.rand_name('%s-' % name)
377 image_file = open(path, 'rb')
378 self.addCleanup(image_file.close)
379 params = {
380 'name': name,
381 'container_format': fmt,
382 'disk_format': fmt,
383 'is_public': 'True',
384 }
385 params.update(properties)
386 image = self.image_client.images.create(**params)
387 self.addCleanup(self.image_client.images.delete, image)
388 self.assertEqual("queued", image.status)
389 image.update(data=image_file)
390 return image.id
391
392 def glance_image_create(self):
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900393 qcow2_img_path = (CONF.scenario.img_dir + "/" +
394 CONF.scenario.qcow2_img_file)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900395 aki_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.aki_img_file
396 ari_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ari_img_file
397 ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900398 LOG.debug("paths: img: %s, ami: %s, ari: %s, aki: %s"
399 % (qcow2_img_path, ami_img_path, ari_img_path, aki_img_path))
400 try:
401 self.image = self._image_create('scenario-img',
402 'bare',
403 qcow2_img_path,
404 properties={'disk_format':
405 'qcow2'})
406 except IOError:
Masayuki Igawa188fc002014-02-23 06:42:44 +0900407 LOG.debug("A qcow2 image was not found. Try to get a uec image.")
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900408 kernel = self._image_create('scenario-aki', 'aki', aki_img_path)
409 ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path)
410 properties = {
411 'properties': {'kernel_id': kernel, 'ramdisk_id': ramdisk}
412 }
413 self.image = self._image_create('scenario-ami', 'ami',
414 path=ami_img_path,
415 properties=properties)
416 LOG.debug("image:%s" % self.image)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900417
Sean Dague6dbc6da2013-05-08 17:49:46 -0400418
419class NetworkScenarioTest(OfficialClientTest):
420 """
421 Base class for network scenario tests
422 """
423
424 @classmethod
425 def check_preconditions(cls):
Matthew Treinish6c072292014-01-29 19:15:52 +0000426 if (CONF.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400427 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200428 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400429 try:
430 cls.network_client.list_networks()
431 except exc.EndpointNotFound:
432 cls.enabled = False
433 raise
434 else:
435 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400436 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400437 raise cls.skipException(msg)
438
439 @classmethod
440 def setUpClass(cls):
441 super(NetworkScenarioTest, cls).setUpClass()
Matthew Treinish6c072292014-01-29 19:15:52 +0000442 if CONF.compute.allow_tenant_isolation:
Miguel Lavalleb8fabc52013-08-23 11:19:57 -0500443 cls.tenant_id = cls.isolated_creds.get_primary_tenant().id
444 else:
445 cls.tenant_id = cls.manager._get_identity_client(
Matthew Treinish6c072292014-01-29 19:15:52 +0000446 CONF.identity.username,
447 CONF.identity.password,
448 CONF.identity.tenant_name).tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400449
Sean Dague6dbc6da2013-05-08 17:49:46 -0400450 def _create_network(self, tenant_id, namestart='network-smoke-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900451 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400452 body = dict(
453 network=dict(
454 name=name,
455 tenant_id=tenant_id,
456 ),
457 )
458 result = self.network_client.create_network(body=body)
459 network = net_common.DeletableNetwork(client=self.network_client,
460 **result['network'])
461 self.assertEqual(network.name, name)
462 self.set_resource(name, network)
463 return network
464
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200465 def _list_networks(self, **kwargs):
466 nets = self.network_client.list_networks(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400467 return nets['networks']
468
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200469 def _list_subnets(self, **kwargs):
470 subnets = self.network_client.list_subnets(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400471 return subnets['subnets']
472
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200473 def _list_routers(self, **kwargs):
474 routers = self.network_client.list_routers(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400475 return routers['routers']
476
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200477 def _list_ports(self, **kwargs):
478 ports = self.network_client.list_ports(**kwargs)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000479 return ports['ports']
480
481 def _get_tenant_own_network_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200482 nets = self._list_networks(tenant_id=tenant_id)
483 return len(nets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000484
485 def _get_tenant_own_subnet_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200486 subnets = self._list_subnets(tenant_id=tenant_id)
487 return len(subnets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000488
489 def _get_tenant_own_port_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200490 ports = self._list_ports(tenant_id=tenant_id)
491 return len(ports)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000492
Sean Dague6dbc6da2013-05-08 17:49:46 -0400493 def _create_subnet(self, network, namestart='subnet-smoke-'):
494 """
495 Create a subnet for the given network within the cidr block
496 configured for tenant networks.
497 """
Attila Fazekase857bd62013-10-21 21:02:44 +0200498
499 def cidr_in_use(cidr, tenant_id):
500 """
501 :return True if subnet with cidr already exist in tenant
502 False else
503 """
504 cidr_in_use = self._list_subnets(tenant_id=tenant_id, cidr=cidr)
505 return len(cidr_in_use) != 0
506
Matthew Treinish6c072292014-01-29 19:15:52 +0000507 tenant_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400508 result = None
509 # Repeatedly attempt subnet creation with sequential cidr
510 # blocks until an unallocated block is found.
Matthew Treinish6c072292014-01-29 19:15:52 +0000511 for subnet_cidr in tenant_cidr.subnet(
512 CONF.network.tenant_network_mask_bits):
Attila Fazekase857bd62013-10-21 21:02:44 +0200513 str_cidr = str(subnet_cidr)
514 if cidr_in_use(str_cidr, tenant_id=network.tenant_id):
515 continue
516
Sean Dague6dbc6da2013-05-08 17:49:46 -0400517 body = dict(
518 subnet=dict(
Attila Fazekase857bd62013-10-21 21:02:44 +0200519 name=data_utils.rand_name(namestart),
Sean Dague6dbc6da2013-05-08 17:49:46 -0400520 ip_version=4,
521 network_id=network.id,
522 tenant_id=network.tenant_id,
Attila Fazekase857bd62013-10-21 21:02:44 +0200523 cidr=str_cidr,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400524 ),
525 )
526 try:
527 result = self.network_client.create_subnet(body=body)
528 break
Mark McClainf2982e82013-07-06 17:48:03 -0400529 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400530 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
531 if not is_overlapping_cidr:
532 raise
533 self.assertIsNotNone(result, 'Unable to allocate tenant network')
534 subnet = net_common.DeletableSubnet(client=self.network_client,
535 **result['subnet'])
Attila Fazekase857bd62013-10-21 21:02:44 +0200536 self.assertEqual(subnet.cidr, str_cidr)
Masayuki Igawa259c1132013-10-31 17:48:44 +0900537 self.set_resource(data_utils.rand_name(namestart), subnet)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400538 return subnet
539
540 def _create_port(self, network, namestart='port-quotatest-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900541 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400542 body = dict(
543 port=dict(name=name,
544 network_id=network.id,
545 tenant_id=network.tenant_id))
546 result = self.network_client.create_port(body=body)
547 self.assertIsNotNone(result, 'Unable to allocate port')
548 port = net_common.DeletablePort(client=self.network_client,
549 **result['port'])
550 self.set_resource(name, port)
551 return port
552
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200553 def _get_server_port_id(self, server, ip_addr=None):
554 ports = self._list_ports(device_id=server.id, fixed_ip=ip_addr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400555 self.assertEqual(len(ports), 1,
556 "Unable to determine which port to target.")
Yair Fried05db2522013-11-18 11:02:10 +0200557 return ports[0]['id']
558
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200559 def _create_floating_ip(self, thing, external_network_id, port_id=None):
560 if not port_id:
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400561 port_id = self._get_server_port_id(thing)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400562 body = dict(
563 floatingip=dict(
564 floating_network_id=external_network_id,
565 port_id=port_id,
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400566 tenant_id=thing.tenant_id,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400567 )
568 )
569 result = self.network_client.create_floatingip(body=body)
570 floating_ip = net_common.DeletableFloatingIp(
571 client=self.network_client,
572 **result['floatingip'])
Masayuki Igawa259c1132013-10-31 17:48:44 +0900573 self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400574 return floating_ip
575
Yair Fried05db2522013-11-18 11:02:10 +0200576 def _associate_floating_ip(self, floating_ip, server):
577 port_id = self._get_server_port_id(server)
578 floating_ip.update(port_id=port_id)
579 self.assertEqual(port_id, floating_ip.port_id)
580 return floating_ip
581
Yair Fried9a551c42013-12-15 14:59:34 +0200582 def _disassociate_floating_ip(self, floating_ip):
583 """
584 :param floating_ip: type DeletableFloatingIp
585 """
586 floating_ip.update(port_id=None)
587 self.assertEqual(None, floating_ip.port_id)
588 return floating_ip
589
590 def _ping_ip_address(self, ip_address, should_succeed=True):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400591 cmd = ['ping', '-c1', '-w1', ip_address]
592
593 def ping():
594 proc = subprocess.Popen(cmd,
595 stdout=subprocess.PIPE,
596 stderr=subprocess.PIPE)
597 proc.wait()
Yair Fried9a551c42013-12-15 14:59:34 +0200598 return (proc.returncode == 0) == should_succeed
Sean Dague6dbc6da2013-05-08 17:49:46 -0400599
Nachi Ueno6d580be2013-07-24 10:58:11 -0700600 return tempest.test.call_until_true(
Matthew Treinish6c072292014-01-29 19:15:52 +0000601 ping, CONF.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000602
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400603 def _create_pool(self, lb_method, protocol, subnet_id):
604 """Wrapper utility that returns a test pool."""
605 name = data_utils.rand_name('pool-')
606 body = {
607 "pool": {
608 "protocol": protocol,
609 "name": name,
610 "subnet_id": subnet_id,
611 "lb_method": lb_method
612 }
613 }
614 resp = self.network_client.create_pool(body=body)
615 pool = net_common.DeletablePool(client=self.network_client,
616 **resp['pool'])
617 self.assertEqual(pool['name'], name)
618 self.set_resource(name, pool)
619 return pool
620
621 def _create_member(self, address, protocol_port, pool_id):
622 """Wrapper utility that returns a test member."""
623 body = {
624 "member": {
625 "protocol_port": protocol_port,
626 "pool_id": pool_id,
627 "address": address
628 }
629 }
630 resp = self.network_client.create_member(body)
631 member = net_common.DeletableMember(client=self.network_client,
632 **resp['member'])
633 self.set_resource(data_utils.rand_name('member-'), member)
634 return member
635
636 def _create_vip(self, protocol, protocol_port, subnet_id, pool_id):
637 """Wrapper utility that returns a test vip."""
638 name = data_utils.rand_name('vip-')
639 body = {
640 "vip": {
641 "protocol": protocol,
642 "name": name,
643 "subnet_id": subnet_id,
644 "pool_id": pool_id,
645 "protocol_port": protocol_port
646 }
647 }
648 resp = self.network_client.create_vip(body)
649 vip = net_common.DeletableVip(client=self.network_client,
650 **resp['vip'])
651 self.assertEqual(vip['name'], name)
652 self.set_resource(name, vip)
653 return vip
654
Yair Fried9a551c42013-12-15 14:59:34 +0200655 def _check_vm_connectivity(self, ip_address,
656 username=None,
657 private_key=None,
658 should_connect=True):
659 """
660 :param ip_address: server to test against
661 :param username: server's ssh username
662 :param private_key: server's ssh private key to be used
663 :param should_connect: True/False indicates positive/negative test
664 positive - attempt ping and ssh
665 negative - attempt ping and fail if succeed
666
667 :raises: AssertError if the result of the connectivity check does
668 not match the value of the should_connect param
669 """
670 if should_connect:
671 msg = "Timed out waiting for %s to become reachable" % ip_address
672 else:
673 msg = "ip address %s is reachable" % ip_address
674 self.assertTrue(self._ping_ip_address(ip_address,
675 should_succeed=should_connect),
676 msg=msg)
677 if should_connect:
678 # no need to check ssh for negative connectivity
Attila Fazekasad7ef7d2013-11-20 10:12:53 +0100679 linux_client = self.get_remote_client(ip_address, username,
680 private_key)
681 linux_client.validate_authentication()
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200682
Yair Friedeb69f3f2013-10-10 13:18:16 +0300683 def _create_security_group_nova(self, client=None,
684 namestart='secgroup-smoke-',
685 tenant_id=None):
686 if client is None:
687 client = self.compute_client
688 # Create security group
689 sg_name = data_utils.rand_name(namestart)
690 sg_desc = sg_name + " description"
691 secgroup = client.security_groups.create(sg_name, sg_desc)
692 self.assertEqual(secgroup.name, sg_name)
693 self.assertEqual(secgroup.description, sg_desc)
694 self.set_resource(sg_name, secgroup)
695
696 # Add rules to the security group
697 self._create_loginable_secgroup_rule_nova(client, secgroup.id)
698
699 return secgroup
700
701 def _create_security_group_neutron(self, tenant_id, client=None,
702 namestart='secgroup-smoke-'):
703 if client is None:
704 client = self.network_client
705 secgroup = self._create_empty_security_group(namestart=namestart,
706 client=client,
707 tenant_id=tenant_id)
708
709 # Add rules to the security group
710 rules = self._create_loginable_secgroup_rule_neutron(secgroup=secgroup)
711 for rule in rules:
712 self.assertEqual(tenant_id, rule.tenant_id)
713 self.assertEqual(secgroup.id, rule.security_group_id)
714 return secgroup
715
716 def _create_empty_security_group(self, tenant_id, client=None,
717 namestart='secgroup-smoke-'):
718 """Create a security group without rules.
719
720 Default rules will be created:
721 - IPv4 egress to any
722 - IPv6 egress to any
723
724 :param tenant_id: secgroup will be created in this tenant
725 :returns: DeletableSecurityGroup -- containing the secgroup created
726 """
727 if client is None:
728 client = self.network_client
729 sg_name = data_utils.rand_name(namestart)
730 sg_desc = sg_name + " description"
731 sg_dict = dict(name=sg_name,
732 description=sg_desc)
733 sg_dict['tenant_id'] = tenant_id
734 body = dict(security_group=sg_dict)
735 result = client.create_security_group(body=body)
736 secgroup = net_common.DeletableSecurityGroup(
737 client=client,
738 **result['security_group']
739 )
740 self.assertEqual(secgroup.name, sg_name)
741 self.assertEqual(tenant_id, secgroup.tenant_id)
742 self.assertEqual(secgroup.description, sg_desc)
743 self.set_resource(sg_name, secgroup)
744 return secgroup
745
746 def _default_security_group(self, tenant_id, client=None):
747 """Get default secgroup for given tenant_id.
748
749 :returns: DeletableSecurityGroup -- default secgroup for given tenant
750 """
751 if client is None:
752 client = self.network_client
753 sgs = [
754 sg for sg in client.list_security_groups().values()[0]
755 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
756 ]
757 msg = "No default security group for tenant %s." % (tenant_id)
758 self.assertTrue(len(sgs) > 0, msg)
759 if len(sgs) > 1:
760 msg = "Found %d default security groups" % len(sgs)
761 raise exc.NeutronClientNoUniqueMatch(msg=msg)
762 return net_common.DeletableSecurityGroup(client=client,
763 **sgs[0])
764
765 def _create_security_group_rule(self, client=None, secgroup=None,
766 tenant_id=None, **kwargs):
767 """Create a rule from a dictionary of rule parameters.
768
769 Create a rule in a secgroup. if secgroup not defined will search for
770 default secgroup in tenant_id.
771
772 :param secgroup: type DeletableSecurityGroup.
773 :param secgroup_id: search for secgroup by id
774 default -- choose default secgroup for given tenant_id
775 :param tenant_id: if secgroup not passed -- the tenant in which to
776 search for default secgroup
777 :param kwargs: a dictionary containing rule parameters:
778 for example, to allow incoming ssh:
779 rule = {
780 direction: 'ingress'
781 protocol:'tcp',
782 port_range_min: 22,
783 port_range_max: 22
784 }
785 """
786 if client is None:
787 client = self.network_client
788 if secgroup is None:
789 secgroup = self._default_security_group(tenant_id)
790
791 ruleset = dict(security_group_id=secgroup.id,
792 tenant_id=secgroup.tenant_id,
793 )
794 ruleset.update(kwargs)
795
796 body = dict(security_group_rule=dict(ruleset))
797 sg_rule = client.create_security_group_rule(body=body)
798 sg_rule = net_common.DeletableSecurityGroupRule(
799 client=client,
800 **sg_rule['security_group_rule']
801 )
802 self.set_resource(sg_rule.id, sg_rule)
803 self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
804 self.assertEqual(secgroup.id, sg_rule.security_group_id)
805
806 return sg_rule
807
808 def _create_loginable_secgroup_rule_neutron(self, client=None,
809 secgroup=None):
810 """These rules are intended to permit inbound ssh and icmp
811 traffic from all sources, so no group_id is provided.
812 Setting a group_id would only permit traffic from ports
813 belonging to the same security group.
814 """
815
816 if client is None:
817 client = self.network_client
818 rules = []
819 rulesets = [
820 dict(
821 # ssh
822 protocol='tcp',
823 port_range_min=22,
824 port_range_max=22,
825 ),
826 dict(
827 # ping
828 protocol='icmp',
829 )
830 ]
831 for ruleset in rulesets:
832 for r_direction in ['ingress', 'egress']:
833 ruleset['direction'] = r_direction
834 try:
835 sg_rule = self._create_security_group_rule(
836 client=client, secgroup=secgroup, **ruleset)
837 except exc.NeutronClientException as ex:
838 # if rule already exist - skip rule and continue
839 if not (ex.status_code is 409 and 'Security group rule'
840 ' already exists' in ex.message):
841 raise ex
842 else:
843 self.assertEqual(r_direction, sg_rule.direction)
844 rules.append(sg_rule)
845
846 return rules
847
Yair Fried5f670ab2013-12-09 09:26:51 +0200848 def _ssh_to_server(self, server, private_key):
Matthew Treinish6c072292014-01-29 19:15:52 +0000849 ssh_login = CONF.compute.image_ssh_user
Yair Fried5f670ab2013-12-09 09:26:51 +0200850 return self.get_remote_client(server,
851 username=ssh_login,
852 private_key=private_key)
853
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000854 def _show_quota_network(self, tenant_id):
855 quota = self.network_client.show_quota(tenant_id)
856 return quota['quota']['network']
857
858 def _show_quota_subnet(self, tenant_id):
859 quota = self.network_client.show_quota(tenant_id)
860 return quota['quota']['subnet']
861
862 def _show_quota_port(self, tenant_id):
863 quota = self.network_client.show_quota(tenant_id)
864 return quota['quota']['port']
865
Yair Fried4d7efa62013-11-17 17:12:29 +0200866 def _get_router(self, tenant_id):
867 """Retrieve a router for the given tenant id.
868
869 If a public router has been configured, it will be returned.
870
871 If a public router has not been configured, but a public
872 network has, a tenant router will be created and returned that
873 routes traffic to the public network.
874 """
Matthew Treinish6c072292014-01-29 19:15:52 +0000875 router_id = CONF.network.public_router_id
876 network_id = CONF.network.public_network_id
Yair Fried4d7efa62013-11-17 17:12:29 +0200877 if router_id:
878 result = self.network_client.show_router(router_id)
879 return net_common.AttributeDict(**result['router'])
880 elif network_id:
881 router = self._create_router(tenant_id)
882 router.add_gateway(network_id)
883 return router
884 else:
885 raise Exception("Neither of 'public_router_id' or "
886 "'public_network_id' has been defined.")
887
888 def _create_router(self, tenant_id, namestart='router-smoke-'):
889 name = data_utils.rand_name(namestart)
890 body = dict(
891 router=dict(
892 name=name,
893 admin_state_up=True,
894 tenant_id=tenant_id,
895 ),
896 )
897 result = self.network_client.create_router(body=body)
898 router = net_common.DeletableRouter(client=self.network_client,
899 **result['router'])
900 self.assertEqual(router.name, name)
901 self.set_resource(name, router)
902 return router
903
904 def _create_networks(self, tenant_id=None):
905 """Create a network with a subnet connected to a router.
906
907 :returns: network, subnet, router
908 """
909 if tenant_id is None:
910 tenant_id = self.tenant_id
911 network = self._create_network(tenant_id)
912 router = self._get_router(tenant_id)
913 subnet = self._create_subnet(network)
914 subnet.add_to_router(router.id)
Yair Fried4d7efa62013-11-17 17:12:29 +0200915 return network, subnet, router
916
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200917
918class OrchestrationScenarioTest(OfficialClientTest):
919 """
920 Base class for orchestration scenario tests
921 """
922
923 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -0700924 def setUpClass(cls):
925 super(OrchestrationScenarioTest, cls).setUpClass()
Matthew Treinish6c072292014-01-29 19:15:52 +0000926 if not CONF.service_available.heat:
Matt Riedemann11c5b642013-08-24 08:45:38 -0700927 raise cls.skipException("Heat support is required")
928
929 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200930 def credentials(cls):
Matthew Treinish6c072292014-01-29 19:15:52 +0000931 username = CONF.identity.admin_username
932 password = CONF.identity.admin_password
933 tenant_name = CONF.identity.tenant_name
Ryan Hsuee1017c2013-12-20 12:00:34 -0800934 return username, password, tenant_name
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200935
936 def _load_template(self, base_file, file_name):
937 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
938 file_name)
939 with open(filepath) as f:
940 return f.read()
941
942 @classmethod
943 def _stack_rand_name(cls):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900944 return data_utils.rand_name(cls.__name__ + '-')
Steve Baker80252da2013-09-25 13:29:10 +1200945
946 @classmethod
947 def _get_default_network(cls):
948 networks = cls.network_client.list_networks()
949 for net in networks['networks']:
Matthew Treinish6c072292014-01-29 19:15:52 +0000950 if net['name'] == CONF.compute.fixed_network_name:
Steve Baker80252da2013-09-25 13:29:10 +1200951 return net