blob: 1b1ddab7b3392aa61c23872f8ad5040006b3f768 [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():
Masayuki Igawa2a8a8122014-02-07 11:24:49 +0900228 message = ("%s failed to get to expected status (%s). "
229 "In %s state.") % (thing, expected_status,
230 new_status)
Masayuki Igawaa0e786a2014-01-27 15:25:06 +0900231 raise exceptions.BuildErrorException(message,
232 server_id=thing_id)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900233 elif new_status == expected_status and expected_status is not None:
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400234 return True # All good.
235 LOG.debug("Waiting for %s to get to %s status. "
236 "Currently in %s status",
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900237 thing, log_status, new_status)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400238 if not tempest.test.call_until_true(
239 check_status,
Matthew Treinish6c072292014-01-29 19:15:52 +0000240 CONF.compute.build_timeout,
241 CONF.compute.build_interval):
Ken'ichi Ohmichiab1496f2013-12-12 22:17:57 +0900242 message = ("Timed out waiting for thing %s "
243 "to become %s") % (thing_id, log_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200244 raise exceptions.TimeoutException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400245
Yair Friedeb69f3f2013-10-10 13:18:16 +0300246 def _create_loginable_secgroup_rule_nova(self, client=None,
247 secgroup_id=None):
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900248 if client is None:
249 client = self.compute_client
250 if secgroup_id is None:
251 sgs = client.security_groups.list()
252 for sg in sgs:
253 if sg.name == 'default':
254 secgroup_id = sg.id
255
256 # These rules are intended to permit inbound ssh and icmp
257 # traffic from all sources, so no group_id is provided.
258 # Setting a group_id would only permit traffic from ports
259 # belonging to the same security group.
260 rulesets = [
261 {
262 # ssh
263 'ip_protocol': 'tcp',
264 'from_port': 22,
265 'to_port': 22,
266 'cidr': '0.0.0.0/0',
267 },
268 {
269 # ping
270 'ip_protocol': 'icmp',
271 'from_port': -1,
272 'to_port': -1,
273 'cidr': '0.0.0.0/0',
274 }
275 ]
Yair Friedeb69f3f2013-10-10 13:18:16 +0300276 rules = list()
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900277 for ruleset in rulesets:
278 sg_rule = client.security_group_rules.create(secgroup_id,
279 **ruleset)
280 self.set_resource(sg_rule.id, sg_rule)
Yair Friedeb69f3f2013-10-10 13:18:16 +0300281 rules.append(sg_rule)
282 return rules
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900283
Giulio Fidente61cadca2013-09-24 18:33:37 +0200284 def create_server(self, client=None, name=None, image=None, flavor=None,
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900285 create_kwargs={}):
Giulio Fidente61cadca2013-09-24 18:33:37 +0200286 if client is None:
287 client = self.compute_client
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900288 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900289 name = data_utils.rand_name('scenario-server-')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900290 if image is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000291 image = CONF.compute.image_ref
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900292 if flavor is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000293 flavor = CONF.compute.flavor_ref
JordanP9c052aa2014-01-24 13:05:00 +0000294
295 fixed_network_name = CONF.compute.fixed_network_name
296 if 'nics' not in create_kwargs and fixed_network_name:
297 networks = client.networks.list()
298 # If several networks found, set the NetID on which to connect the
299 # server to avoid the following error "Multiple possible networks
300 # found, use a Network ID to be more specific."
301 # See Tempest #1250866
302 if len(networks) > 1:
303 for network in networks:
304 if network.label == fixed_network_name:
305 create_kwargs['nics'] = [{'net-id': network.id}]
306 break
307 # If we didn't find the network we were looking for :
308 else:
309 msg = ("The network on which the NIC of the server must "
310 "be connected can not be found : "
311 "fixed_network_name=%s. Starting instance without "
312 "specifying a network.") % fixed_network_name
313 LOG.info(msg)
314
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900315 LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
316 name, image, flavor)
317 server = client.servers.create(name, image, flavor, **create_kwargs)
Giulio Fidente92f77192013-08-26 17:13:28 +0200318 self.assertEqual(server.name, name)
319 self.set_resource(name, server)
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900320 self.status_timeout(client.servers, server.id, 'ACTIVE')
321 # The instance retrieved on creation is missing network
322 # details, necessitating retrieval after it becomes active to
323 # ensure correct details.
324 server = client.servers.get(server.id)
325 self.set_resource(name, server)
326 LOG.debug("Created server: %s", server)
327 return server
328
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900329 def create_volume(self, client=None, size=1, name=None,
330 snapshot_id=None, imageRef=None):
331 if client is None:
332 client = self.volume_client
333 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900334 name = data_utils.rand_name('scenario-volume-')
Eric Windisch2d26f1b2013-09-04 17:52:16 -0700335 LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900336 volume = client.volumes.create(size=size, display_name=name,
337 snapshot_id=snapshot_id,
338 imageRef=imageRef)
339 self.set_resource(name, volume)
340 self.assertEqual(name, volume.display_name)
341 self.status_timeout(client.volumes, volume.id, 'available')
342 LOG.debug("Created volume: %s", volume)
343 return volume
344
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900345 def create_server_snapshot(self, server, compute_client=None,
346 image_client=None, name=None):
347 if compute_client is None:
348 compute_client = self.compute_client
349 if image_client is None:
350 image_client = self.image_client
351 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900352 name = data_utils.rand_name('scenario-snapshot-')
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900353 LOG.debug("Creating a snapshot image for server: %s", server.name)
354 image_id = compute_client.servers.create_image(server, name)
355 self.addCleanup(image_client.images.delete, image_id)
356 self.status_timeout(image_client.images, image_id, 'active')
357 snapshot_image = image_client.images.get(image_id)
Chang Bo Guofc77e932013-09-16 17:38:26 -0700358 self.assertEqual(name, snapshot_image.name)
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900359 LOG.debug("Created snapshot image %s for server %s",
360 snapshot_image.name, server.name)
361 return snapshot_image
362
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900363 def create_keypair(self, client=None, name=None):
364 if client is None:
365 client = self.compute_client
366 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900367 name = data_utils.rand_name('scenario-keypair-')
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900368 keypair = client.keypairs.create(name)
369 self.assertEqual(keypair.name, name)
370 self.set_resource(name, keypair)
371 return keypair
372
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900373 def get_remote_client(self, server_or_ip, username=None, private_key=None):
374 if isinstance(server_or_ip, basestring):
375 ip = server_or_ip
376 else:
Matthew Treinish6c072292014-01-29 19:15:52 +0000377 network_name_for_ssh = CONF.compute.network_for_ssh
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900378 ip = server_or_ip.networks[network_name_for_ssh][0]
379 if username is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000380 username = CONF.scenario.ssh_user
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900381 if private_key is None:
382 private_key = self.keypair.private_key
Masayuki Igawa4ded9f02014-02-17 15:05:59 +0900383 return remote_client.RemoteClient(ip, username, pkey=private_key)
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900384
Nachi Ueno95b41282014-01-15 06:54:21 -0800385 def _log_console_output(self, servers=None):
386 if not servers:
387 servers = self.compute_client.servers.list()
388 for server in servers:
389 LOG.debug('Console output for %s', server.id)
390 LOG.debug(server.get_console_output())
391
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900392 def wait_for_volume_status(self, status):
393 volume_id = self.volume.id
394 self.status_timeout(
395 self.volume_client.volumes, volume_id, status)
396
397 def _image_create(self, name, fmt, path, properties={}):
398 name = data_utils.rand_name('%s-' % name)
399 image_file = open(path, 'rb')
400 self.addCleanup(image_file.close)
401 params = {
402 'name': name,
403 'container_format': fmt,
404 'disk_format': fmt,
405 'is_public': 'True',
406 }
407 params.update(properties)
408 image = self.image_client.images.create(**params)
409 self.addCleanup(self.image_client.images.delete, image)
410 self.assertEqual("queued", image.status)
411 image.update(data=image_file)
412 return image.id
413
414 def glance_image_create(self):
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900415 qcow2_img_path = (CONF.scenario.img_dir + "/" +
416 CONF.scenario.qcow2_img_file)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900417 aki_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.aki_img_file
418 ari_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ari_img_file
419 ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900420 LOG.debug("paths: img: %s, ami: %s, ari: %s, aki: %s"
421 % (qcow2_img_path, ami_img_path, ari_img_path, aki_img_path))
422 try:
423 self.image = self._image_create('scenario-img',
424 'bare',
425 qcow2_img_path,
426 properties={'disk_format':
427 'qcow2'})
428 except IOError:
Masayuki Igawa188fc002014-02-23 06:42:44 +0900429 LOG.debug("A qcow2 image was not found. Try to get a uec image.")
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900430 kernel = self._image_create('scenario-aki', 'aki', aki_img_path)
431 ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path)
432 properties = {
433 'properties': {'kernel_id': kernel, 'ramdisk_id': ramdisk}
434 }
435 self.image = self._image_create('scenario-ami', 'ami',
436 path=ami_img_path,
437 properties=properties)
438 LOG.debug("image:%s" % self.image)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900439
Sean Dague6dbc6da2013-05-08 17:49:46 -0400440
441class NetworkScenarioTest(OfficialClientTest):
442 """
443 Base class for network scenario tests
444 """
445
446 @classmethod
447 def check_preconditions(cls):
Matthew Treinish6c072292014-01-29 19:15:52 +0000448 if (CONF.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400449 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200450 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400451 try:
452 cls.network_client.list_networks()
453 except exc.EndpointNotFound:
454 cls.enabled = False
455 raise
456 else:
457 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400458 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400459 raise cls.skipException(msg)
460
461 @classmethod
462 def setUpClass(cls):
463 super(NetworkScenarioTest, cls).setUpClass()
Matthew Treinish6c072292014-01-29 19:15:52 +0000464 if CONF.compute.allow_tenant_isolation:
Miguel Lavalleb8fabc52013-08-23 11:19:57 -0500465 cls.tenant_id = cls.isolated_creds.get_primary_tenant().id
466 else:
467 cls.tenant_id = cls.manager._get_identity_client(
Matthew Treinish6c072292014-01-29 19:15:52 +0000468 CONF.identity.username,
469 CONF.identity.password,
470 CONF.identity.tenant_name).tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400471
Sean Dague6dbc6da2013-05-08 17:49:46 -0400472 def _create_network(self, tenant_id, namestart='network-smoke-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900473 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400474 body = dict(
475 network=dict(
476 name=name,
477 tenant_id=tenant_id,
478 ),
479 )
480 result = self.network_client.create_network(body=body)
481 network = net_common.DeletableNetwork(client=self.network_client,
482 **result['network'])
483 self.assertEqual(network.name, name)
484 self.set_resource(name, network)
485 return network
486
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200487 def _list_networks(self, **kwargs):
488 nets = self.network_client.list_networks(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400489 return nets['networks']
490
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200491 def _list_subnets(self, **kwargs):
492 subnets = self.network_client.list_subnets(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400493 return subnets['subnets']
494
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200495 def _list_routers(self, **kwargs):
496 routers = self.network_client.list_routers(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400497 return routers['routers']
498
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200499 def _list_ports(self, **kwargs):
500 ports = self.network_client.list_ports(**kwargs)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000501 return ports['ports']
502
503 def _get_tenant_own_network_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200504 nets = self._list_networks(tenant_id=tenant_id)
505 return len(nets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000506
507 def _get_tenant_own_subnet_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200508 subnets = self._list_subnets(tenant_id=tenant_id)
509 return len(subnets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000510
511 def _get_tenant_own_port_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200512 ports = self._list_ports(tenant_id=tenant_id)
513 return len(ports)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000514
Sean Dague6dbc6da2013-05-08 17:49:46 -0400515 def _create_subnet(self, network, namestart='subnet-smoke-'):
516 """
517 Create a subnet for the given network within the cidr block
518 configured for tenant networks.
519 """
Attila Fazekase857bd62013-10-21 21:02:44 +0200520
521 def cidr_in_use(cidr, tenant_id):
522 """
523 :return True if subnet with cidr already exist in tenant
524 False else
525 """
526 cidr_in_use = self._list_subnets(tenant_id=tenant_id, cidr=cidr)
527 return len(cidr_in_use) != 0
528
Matthew Treinish6c072292014-01-29 19:15:52 +0000529 tenant_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400530 result = None
531 # Repeatedly attempt subnet creation with sequential cidr
532 # blocks until an unallocated block is found.
Matthew Treinish6c072292014-01-29 19:15:52 +0000533 for subnet_cidr in tenant_cidr.subnet(
534 CONF.network.tenant_network_mask_bits):
Attila Fazekase857bd62013-10-21 21:02:44 +0200535 str_cidr = str(subnet_cidr)
536 if cidr_in_use(str_cidr, tenant_id=network.tenant_id):
537 continue
538
Sean Dague6dbc6da2013-05-08 17:49:46 -0400539 body = dict(
540 subnet=dict(
Attila Fazekase857bd62013-10-21 21:02:44 +0200541 name=data_utils.rand_name(namestart),
Sean Dague6dbc6da2013-05-08 17:49:46 -0400542 ip_version=4,
543 network_id=network.id,
544 tenant_id=network.tenant_id,
Attila Fazekase857bd62013-10-21 21:02:44 +0200545 cidr=str_cidr,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400546 ),
547 )
548 try:
549 result = self.network_client.create_subnet(body=body)
550 break
Mark McClainf2982e82013-07-06 17:48:03 -0400551 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400552 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
553 if not is_overlapping_cidr:
554 raise
555 self.assertIsNotNone(result, 'Unable to allocate tenant network')
556 subnet = net_common.DeletableSubnet(client=self.network_client,
557 **result['subnet'])
Attila Fazekase857bd62013-10-21 21:02:44 +0200558 self.assertEqual(subnet.cidr, str_cidr)
Masayuki Igawa259c1132013-10-31 17:48:44 +0900559 self.set_resource(data_utils.rand_name(namestart), subnet)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400560 return subnet
561
562 def _create_port(self, network, namestart='port-quotatest-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900563 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400564 body = dict(
565 port=dict(name=name,
566 network_id=network.id,
567 tenant_id=network.tenant_id))
568 result = self.network_client.create_port(body=body)
569 self.assertIsNotNone(result, 'Unable to allocate port')
570 port = net_common.DeletablePort(client=self.network_client,
571 **result['port'])
572 self.set_resource(name, port)
573 return port
574
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200575 def _get_server_port_id(self, server, ip_addr=None):
576 ports = self._list_ports(device_id=server.id, fixed_ip=ip_addr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400577 self.assertEqual(len(ports), 1,
578 "Unable to determine which port to target.")
Yair Fried05db2522013-11-18 11:02:10 +0200579 return ports[0]['id']
580
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200581 def _create_floating_ip(self, thing, external_network_id, port_id=None):
582 if not port_id:
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400583 port_id = self._get_server_port_id(thing)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400584 body = dict(
585 floatingip=dict(
586 floating_network_id=external_network_id,
587 port_id=port_id,
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400588 tenant_id=thing.tenant_id,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400589 )
590 )
591 result = self.network_client.create_floatingip(body=body)
592 floating_ip = net_common.DeletableFloatingIp(
593 client=self.network_client,
594 **result['floatingip'])
Masayuki Igawa259c1132013-10-31 17:48:44 +0900595 self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400596 return floating_ip
597
Yair Fried05db2522013-11-18 11:02:10 +0200598 def _associate_floating_ip(self, floating_ip, server):
599 port_id = self._get_server_port_id(server)
600 floating_ip.update(port_id=port_id)
601 self.assertEqual(port_id, floating_ip.port_id)
602 return floating_ip
603
Yair Fried9a551c42013-12-15 14:59:34 +0200604 def _disassociate_floating_ip(self, floating_ip):
605 """
606 :param floating_ip: type DeletableFloatingIp
607 """
608 floating_ip.update(port_id=None)
llg8212e4cd3922014-02-15 12:14:21 +0800609 self.assertIsNone(floating_ip.port_id)
Yair Fried9a551c42013-12-15 14:59:34 +0200610 return floating_ip
611
612 def _ping_ip_address(self, ip_address, should_succeed=True):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400613 cmd = ['ping', '-c1', '-w1', ip_address]
614
615 def ping():
616 proc = subprocess.Popen(cmd,
617 stdout=subprocess.PIPE,
618 stderr=subprocess.PIPE)
619 proc.wait()
Yair Fried9a551c42013-12-15 14:59:34 +0200620 return (proc.returncode == 0) == should_succeed
Sean Dague6dbc6da2013-05-08 17:49:46 -0400621
Nachi Ueno6d580be2013-07-24 10:58:11 -0700622 return tempest.test.call_until_true(
Matthew Treinish6c072292014-01-29 19:15:52 +0000623 ping, CONF.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000624
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400625 def _create_pool(self, lb_method, protocol, subnet_id):
626 """Wrapper utility that returns a test pool."""
627 name = data_utils.rand_name('pool-')
628 body = {
629 "pool": {
630 "protocol": protocol,
631 "name": name,
632 "subnet_id": subnet_id,
633 "lb_method": lb_method
634 }
635 }
636 resp = self.network_client.create_pool(body=body)
637 pool = net_common.DeletablePool(client=self.network_client,
638 **resp['pool'])
639 self.assertEqual(pool['name'], name)
640 self.set_resource(name, pool)
641 return pool
642
643 def _create_member(self, address, protocol_port, pool_id):
644 """Wrapper utility that returns a test member."""
645 body = {
646 "member": {
647 "protocol_port": protocol_port,
648 "pool_id": pool_id,
649 "address": address
650 }
651 }
652 resp = self.network_client.create_member(body)
653 member = net_common.DeletableMember(client=self.network_client,
654 **resp['member'])
655 self.set_resource(data_utils.rand_name('member-'), member)
656 return member
657
658 def _create_vip(self, protocol, protocol_port, subnet_id, pool_id):
659 """Wrapper utility that returns a test vip."""
660 name = data_utils.rand_name('vip-')
661 body = {
662 "vip": {
663 "protocol": protocol,
664 "name": name,
665 "subnet_id": subnet_id,
666 "pool_id": pool_id,
667 "protocol_port": protocol_port
668 }
669 }
670 resp = self.network_client.create_vip(body)
671 vip = net_common.DeletableVip(client=self.network_client,
672 **resp['vip'])
673 self.assertEqual(vip['name'], name)
674 self.set_resource(name, vip)
675 return vip
676
Yair Fried9a551c42013-12-15 14:59:34 +0200677 def _check_vm_connectivity(self, ip_address,
678 username=None,
679 private_key=None,
680 should_connect=True):
681 """
682 :param ip_address: server to test against
683 :param username: server's ssh username
684 :param private_key: server's ssh private key to be used
685 :param should_connect: True/False indicates positive/negative test
686 positive - attempt ping and ssh
687 negative - attempt ping and fail if succeed
688
689 :raises: AssertError if the result of the connectivity check does
690 not match the value of the should_connect param
691 """
692 if should_connect:
693 msg = "Timed out waiting for %s to become reachable" % ip_address
694 else:
695 msg = "ip address %s is reachable" % ip_address
696 self.assertTrue(self._ping_ip_address(ip_address,
697 should_succeed=should_connect),
698 msg=msg)
699 if should_connect:
700 # no need to check ssh for negative connectivity
Attila Fazekasad7ef7d2013-11-20 10:12:53 +0100701 linux_client = self.get_remote_client(ip_address, username,
702 private_key)
703 linux_client.validate_authentication()
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200704
Yair Friedeb69f3f2013-10-10 13:18:16 +0300705 def _create_security_group_nova(self, client=None,
706 namestart='secgroup-smoke-',
707 tenant_id=None):
708 if client is None:
709 client = self.compute_client
710 # Create security group
711 sg_name = data_utils.rand_name(namestart)
712 sg_desc = sg_name + " description"
713 secgroup = client.security_groups.create(sg_name, sg_desc)
714 self.assertEqual(secgroup.name, sg_name)
715 self.assertEqual(secgroup.description, sg_desc)
716 self.set_resource(sg_name, secgroup)
717
718 # Add rules to the security group
719 self._create_loginable_secgroup_rule_nova(client, secgroup.id)
720
721 return secgroup
722
723 def _create_security_group_neutron(self, tenant_id, client=None,
724 namestart='secgroup-smoke-'):
725 if client is None:
726 client = self.network_client
727 secgroup = self._create_empty_security_group(namestart=namestart,
728 client=client,
729 tenant_id=tenant_id)
730
731 # Add rules to the security group
732 rules = self._create_loginable_secgroup_rule_neutron(secgroup=secgroup)
733 for rule in rules:
734 self.assertEqual(tenant_id, rule.tenant_id)
735 self.assertEqual(secgroup.id, rule.security_group_id)
736 return secgroup
737
738 def _create_empty_security_group(self, tenant_id, client=None,
739 namestart='secgroup-smoke-'):
740 """Create a security group without rules.
741
742 Default rules will be created:
743 - IPv4 egress to any
744 - IPv6 egress to any
745
746 :param tenant_id: secgroup will be created in this tenant
747 :returns: DeletableSecurityGroup -- containing the secgroup created
748 """
749 if client is None:
750 client = self.network_client
751 sg_name = data_utils.rand_name(namestart)
752 sg_desc = sg_name + " description"
753 sg_dict = dict(name=sg_name,
754 description=sg_desc)
755 sg_dict['tenant_id'] = tenant_id
756 body = dict(security_group=sg_dict)
757 result = client.create_security_group(body=body)
758 secgroup = net_common.DeletableSecurityGroup(
759 client=client,
760 **result['security_group']
761 )
762 self.assertEqual(secgroup.name, sg_name)
763 self.assertEqual(tenant_id, secgroup.tenant_id)
764 self.assertEqual(secgroup.description, sg_desc)
765 self.set_resource(sg_name, secgroup)
766 return secgroup
767
768 def _default_security_group(self, tenant_id, client=None):
769 """Get default secgroup for given tenant_id.
770
771 :returns: DeletableSecurityGroup -- default secgroup for given tenant
772 """
773 if client is None:
774 client = self.network_client
775 sgs = [
776 sg for sg in client.list_security_groups().values()[0]
777 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
778 ]
779 msg = "No default security group for tenant %s." % (tenant_id)
780 self.assertTrue(len(sgs) > 0, msg)
781 if len(sgs) > 1:
782 msg = "Found %d default security groups" % len(sgs)
783 raise exc.NeutronClientNoUniqueMatch(msg=msg)
784 return net_common.DeletableSecurityGroup(client=client,
785 **sgs[0])
786
787 def _create_security_group_rule(self, client=None, secgroup=None,
788 tenant_id=None, **kwargs):
789 """Create a rule from a dictionary of rule parameters.
790
791 Create a rule in a secgroup. if secgroup not defined will search for
792 default secgroup in tenant_id.
793
794 :param secgroup: type DeletableSecurityGroup.
795 :param secgroup_id: search for secgroup by id
796 default -- choose default secgroup for given tenant_id
797 :param tenant_id: if secgroup not passed -- the tenant in which to
798 search for default secgroup
799 :param kwargs: a dictionary containing rule parameters:
800 for example, to allow incoming ssh:
801 rule = {
802 direction: 'ingress'
803 protocol:'tcp',
804 port_range_min: 22,
805 port_range_max: 22
806 }
807 """
808 if client is None:
809 client = self.network_client
810 if secgroup is None:
811 secgroup = self._default_security_group(tenant_id)
812
813 ruleset = dict(security_group_id=secgroup.id,
814 tenant_id=secgroup.tenant_id,
815 )
816 ruleset.update(kwargs)
817
818 body = dict(security_group_rule=dict(ruleset))
819 sg_rule = client.create_security_group_rule(body=body)
820 sg_rule = net_common.DeletableSecurityGroupRule(
821 client=client,
822 **sg_rule['security_group_rule']
823 )
824 self.set_resource(sg_rule.id, sg_rule)
825 self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
826 self.assertEqual(secgroup.id, sg_rule.security_group_id)
827
828 return sg_rule
829
830 def _create_loginable_secgroup_rule_neutron(self, client=None,
831 secgroup=None):
832 """These rules are intended to permit inbound ssh and icmp
833 traffic from all sources, so no group_id is provided.
834 Setting a group_id would only permit traffic from ports
835 belonging to the same security group.
836 """
837
838 if client is None:
839 client = self.network_client
840 rules = []
841 rulesets = [
842 dict(
843 # ssh
844 protocol='tcp',
845 port_range_min=22,
846 port_range_max=22,
847 ),
848 dict(
849 # ping
850 protocol='icmp',
851 )
852 ]
853 for ruleset in rulesets:
854 for r_direction in ['ingress', 'egress']:
855 ruleset['direction'] = r_direction
856 try:
857 sg_rule = self._create_security_group_rule(
858 client=client, secgroup=secgroup, **ruleset)
859 except exc.NeutronClientException as ex:
860 # if rule already exist - skip rule and continue
861 if not (ex.status_code is 409 and 'Security group rule'
862 ' already exists' in ex.message):
863 raise ex
864 else:
865 self.assertEqual(r_direction, sg_rule.direction)
866 rules.append(sg_rule)
867
868 return rules
869
Yair Fried5f670ab2013-12-09 09:26:51 +0200870 def _ssh_to_server(self, server, private_key):
Matthew Treinish6c072292014-01-29 19:15:52 +0000871 ssh_login = CONF.compute.image_ssh_user
Yair Fried5f670ab2013-12-09 09:26:51 +0200872 return self.get_remote_client(server,
873 username=ssh_login,
874 private_key=private_key)
875
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000876 def _show_quota_network(self, tenant_id):
877 quota = self.network_client.show_quota(tenant_id)
878 return quota['quota']['network']
879
880 def _show_quota_subnet(self, tenant_id):
881 quota = self.network_client.show_quota(tenant_id)
882 return quota['quota']['subnet']
883
884 def _show_quota_port(self, tenant_id):
885 quota = self.network_client.show_quota(tenant_id)
886 return quota['quota']['port']
887
Yair Fried4d7efa62013-11-17 17:12:29 +0200888 def _get_router(self, tenant_id):
889 """Retrieve a router for the given tenant id.
890
891 If a public router has been configured, it will be returned.
892
893 If a public router has not been configured, but a public
894 network has, a tenant router will be created and returned that
895 routes traffic to the public network.
896 """
Matthew Treinish6c072292014-01-29 19:15:52 +0000897 router_id = CONF.network.public_router_id
898 network_id = CONF.network.public_network_id
Yair Fried4d7efa62013-11-17 17:12:29 +0200899 if router_id:
900 result = self.network_client.show_router(router_id)
901 return net_common.AttributeDict(**result['router'])
902 elif network_id:
903 router = self._create_router(tenant_id)
904 router.add_gateway(network_id)
905 return router
906 else:
907 raise Exception("Neither of 'public_router_id' or "
908 "'public_network_id' has been defined.")
909
910 def _create_router(self, tenant_id, namestart='router-smoke-'):
911 name = data_utils.rand_name(namestart)
912 body = dict(
913 router=dict(
914 name=name,
915 admin_state_up=True,
916 tenant_id=tenant_id,
917 ),
918 )
919 result = self.network_client.create_router(body=body)
920 router = net_common.DeletableRouter(client=self.network_client,
921 **result['router'])
922 self.assertEqual(router.name, name)
923 self.set_resource(name, router)
924 return router
925
926 def _create_networks(self, tenant_id=None):
927 """Create a network with a subnet connected to a router.
928
929 :returns: network, subnet, router
930 """
931 if tenant_id is None:
932 tenant_id = self.tenant_id
933 network = self._create_network(tenant_id)
934 router = self._get_router(tenant_id)
935 subnet = self._create_subnet(network)
936 subnet.add_to_router(router.id)
Yair Fried4d7efa62013-11-17 17:12:29 +0200937 return network, subnet, router
938
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200939
940class OrchestrationScenarioTest(OfficialClientTest):
941 """
942 Base class for orchestration scenario tests
943 """
944
945 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -0700946 def setUpClass(cls):
947 super(OrchestrationScenarioTest, cls).setUpClass()
Matthew Treinish6c072292014-01-29 19:15:52 +0000948 if not CONF.service_available.heat:
Matt Riedemann11c5b642013-08-24 08:45:38 -0700949 raise cls.skipException("Heat support is required")
950
951 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200952 def credentials(cls):
Matthew Treinish6c072292014-01-29 19:15:52 +0000953 username = CONF.identity.admin_username
954 password = CONF.identity.admin_password
955 tenant_name = CONF.identity.tenant_name
Ryan Hsuee1017c2013-12-20 12:00:34 -0800956 return username, password, tenant_name
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200957
958 def _load_template(self, base_file, file_name):
959 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
960 file_name)
961 with open(filepath) as f:
962 return f.read()
963
964 @classmethod
965 def _stack_rand_name(cls):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900966 return data_utils.rand_name(cls.__name__ + '-')
Steve Baker80252da2013-09-25 13:29:10 +1200967
968 @classmethod
969 def _get_default_network(cls):
970 networks = cls.network_client.list_networks()
971 for net in networks['networks']:
Matthew Treinish6c072292014-01-29 19:15:52 +0000972 if net['name'] == CONF.compute.fixed_network_name:
Steve Baker80252da2013-09-25 13:29:10 +1200973 return net