blob: f06a85071c7b38088a6c592b9e65dbfcf46e82af [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
llg821243b20502014-02-22 10:32:49 +080019import six
Sean Dague6dbc6da2013-05-08 17:49:46 -040020import subprocess
21
Sean Dague6dbc6da2013-05-08 17:49:46 -040022import netaddr
Mark McClainf2982e82013-07-06 17:48:03 -040023from neutronclient.common import exceptions as exc
fujioka yuuichi636f8db2013-08-09 12:05:24 +090024from novaclient import exceptions as nova_exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040025
Sean Dague1937d092013-05-17 16:36:38 -040026from tempest.api.network import common as net_common
Andrea Frittolif9cde7e2014-02-18 09:57:04 +000027from tempest import clients
Matthew Treinishb86cda92013-07-29 11:22:23 -040028from tempest.common import isolated_creds
Masayuki Igawa259c1132013-10-31 17:48:44 +090029from tempest.common.utils import data_utils
Masayuki Igawa4ded9f02014-02-17 15:05:59 +090030from tempest.common.utils.linux import remote_client
Matthew Treinish6c072292014-01-29 19:15:52 +000031from tempest import config
Giulio Fidente92f77192013-08-26 17:13:28 +020032from tempest import exceptions
Attila Fazekasfb7552a2013-08-27 13:02:26 +020033from tempest.openstack.common import log
Sean Dague6dbc6da2013-05-08 17:49:46 -040034import tempest.test
Sean Dague6dbc6da2013-05-08 17:49:46 -040035
Matthew Treinish6c072292014-01-29 19:15:52 +000036CONF = config.CONF
Sean Dague6dbc6da2013-05-08 17:49:46 -040037
Attila Fazekasfb7552a2013-08-27 13:02:26 +020038LOG = log.getLogger(__name__)
39
40# NOTE(afazekas): Workaround for the stdout logging
41LOG_nova_client = logging.getLogger('novaclient.client')
42LOG_nova_client.addHandler(log.NullHandler())
43
44LOG_cinder_client = logging.getLogger('cinderclient.client')
45LOG_cinder_client.addHandler(log.NullHandler())
Sean Dague6dbc6da2013-05-08 17:49:46 -040046
47
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040048class OfficialClientTest(tempest.test.BaseTestCase):
Sean Dague6dbc6da2013-05-08 17:49:46 -040049 """
50 Official Client test base class for scenario testing.
51
52 Official Client tests are tests that have the following characteristics:
53
54 * Test basic operations of an API, typically in an order that
55 a regular user would perform those operations
56 * Test only the correct inputs and action paths -- no fuzz or
57 random input data is sent, only valid inputs.
58 * Use only the default client tool for calling an API
59 """
60
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040061 @classmethod
62 def setUpClass(cls):
Attila Fazekasf86fa312013-07-30 19:56:39 +020063 super(OfficialClientTest, cls).setUpClass()
Matthew Treinishb86cda92013-07-29 11:22:23 -040064 cls.isolated_creds = isolated_creds.IsolatedCreds(
Sean Dague6969b902014-01-28 06:48:37 -050065 cls.__name__, tempest_client=False,
Matthew Treinish9f756a02014-01-15 10:26:07 -050066 network_resources=cls.network_resources)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120067
Yair Fried769bbff2013-12-18 16:33:17 +020068 username, password, tenant_name = cls.credentials()
Matthew Treinishb86cda92013-07-29 11:22:23 -040069
Andrea Frittolif9cde7e2014-02-18 09:57:04 +000070 cls.manager = clients.OfficialClientManager(
71 username, password, tenant_name)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040072 cls.compute_client = cls.manager.compute_client
73 cls.image_client = cls.manager.image_client
74 cls.identity_client = cls.manager.identity_client
75 cls.network_client = cls.manager.network_client
76 cls.volume_client = cls.manager.volume_client
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +000077 cls.object_storage_client = cls.manager.object_storage_client
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120078 cls.orchestration_client = cls.manager.orchestration_client
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040079 cls.resource_keys = {}
80 cls.os_resources = []
Sean Dague6dbc6da2013-05-08 17:49:46 -040081
82 @classmethod
Yair Frieda71cc442013-12-18 13:32:36 +020083 def _get_credentials(cls, get_creds, prefix):
Matthew Treinish6c072292014-01-29 19:15:52 +000084 if CONF.compute.allow_tenant_isolation:
Yair Fried769bbff2013-12-18 16:33:17 +020085 username, tenant_name, password = get_creds()
86 else:
Matthew Treinish6c072292014-01-29 19:15:52 +000087 username = getattr(CONF.identity, prefix + 'username')
88 password = getattr(CONF.identity, prefix + 'password')
89 tenant_name = getattr(CONF.identity, prefix + 'tenant_name')
Yair Fried769bbff2013-12-18 16:33:17 +020090 return username, password, tenant_name
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120091
92 @classmethod
Yair Frieda71cc442013-12-18 13:32:36 +020093 def credentials(cls):
94 return cls._get_credentials(cls.isolated_creds.get_primary_creds, '')
95
96 @classmethod
97 def alt_credentials(cls):
98 return cls._get_credentials(cls.isolated_creds.get_alt_creds, 'alt_')
99
100 @classmethod
101 def admin_credentials(cls):
102 return cls._get_credentials(cls.isolated_creds.get_admin_creds,
103 'admin_')
104
Yair Friedbf2e2c42014-01-28 12:06:38 +0200105 @staticmethod
106 def cleanup_resource(resource, test_name):
107
108 LOG.debug("Deleting %r from shared resources of %s" %
109 (resource, test_name))
110 try:
111 # OpenStack resources are assumed to have a delete()
112 # method which destroys the resource...
113 resource.delete()
114 except Exception as e:
115 # If the resource is already missing, mission accomplished.
116 # add status code as workaround for bug 1247568
117 if (e.__class__.__name__ == 'NotFound' or
118 (hasattr(e, 'status_code') and e.status_code == 404)):
119 return
120 raise
121
122 def is_deletion_complete():
123 # Deletion testing is only required for objects whose
124 # existence cannot be checked via retrieval.
125 if isinstance(resource, dict):
126 return True
127 try:
128 resource.get()
129 except Exception as e:
130 # Clients are expected to return an exception
131 # called 'NotFound' if retrieval fails.
132 if e.__class__.__name__ == 'NotFound':
133 return True
134 raise
135 return False
136
137 # Block until resource deletion has completed or timed-out
138 tempest.test.call_until_true(is_deletion_complete, 10, 1)
139
Yair Frieda71cc442013-12-18 13:32:36 +0200140 @classmethod
Sean Dague6dbc6da2013-05-08 17:49:46 -0400141 def tearDownClass(cls):
142 # NOTE(jaypipes): Because scenario tests are typically run in a
143 # specific order, and because test methods in scenario tests
144 # generally create resources in a particular order, we destroy
145 # resources in the reverse order in which resources are added to
146 # the scenario test class object
147 while cls.os_resources:
148 thing = cls.os_resources.pop()
Yair Friedbf2e2c42014-01-28 12:06:38 +0200149 cls.cleanup_resource(thing, cls.__name__)
Matthew Treinishb86cda92013-07-29 11:22:23 -0400150 cls.isolated_creds.clear_isolated_creds()
151 super(OfficialClientTest, cls).tearDownClass()
Sean Dague6dbc6da2013-05-08 17:49:46 -0400152
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400153 @classmethod
154 def set_resource(cls, key, thing):
155 LOG.debug("Adding %r to shared resources of %s" %
156 (thing, cls.__name__))
157 cls.resource_keys[key] = thing
158 cls.os_resources.append(thing)
159
160 @classmethod
161 def get_resource(cls, key):
162 return cls.resource_keys[key]
163
164 @classmethod
165 def remove_resource(cls, key):
166 thing = cls.resource_keys[key]
167 cls.os_resources.remove(thing)
168 del cls.resource_keys[key]
169
Steve Bakerefde7612013-09-30 11:29:23 +1300170 def status_timeout(self, things, thing_id, expected_status,
171 error_status='ERROR',
172 not_found_exception=nova_exceptions.NotFound):
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400173 """
174 Given a thing and an expected status, do a loop, sleeping
175 for a configurable amount of time, checking for the
176 expected status to show. At any time, if the returned
177 status of the thing is ERROR, fail out.
178 """
Steve Bakerefde7612013-09-30 11:29:23 +1300179 self._status_timeout(things, thing_id,
180 expected_status=expected_status,
181 error_status=error_status,
182 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900183
Steve Bakerefde7612013-09-30 11:29:23 +1300184 def delete_timeout(self, things, thing_id,
185 error_status='ERROR',
186 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900187 """
188 Given a thing, do a loop, sleeping
189 for a configurable amount of time, checking for the
190 deleted status to show. At any time, if the returned
191 status of the thing is ERROR, fail out.
192 """
193 self._status_timeout(things,
194 thing_id,
Steve Bakerefde7612013-09-30 11:29:23 +1300195 allow_notfound=True,
196 error_status=error_status,
197 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900198
199 def _status_timeout(self,
200 things,
201 thing_id,
202 expected_status=None,
Steve Bakerefde7612013-09-30 11:29:23 +1300203 allow_notfound=False,
204 error_status='ERROR',
205 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900206
207 log_status = expected_status if expected_status else ''
208 if allow_notfound:
209 log_status += ' or NotFound' if log_status != '' else 'NotFound'
210
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400211 def check_status():
212 # python-novaclient has resources available to its client
213 # that all implement a get() method taking an identifier
214 # for the singular resource to retrieve.
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900215 try:
216 thing = things.get(thing_id)
Steve Bakerefde7612013-09-30 11:29:23 +1300217 except not_found_exception:
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900218 if allow_notfound:
219 return True
220 else:
221 raise
222
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400223 new_status = thing.status
Brent Eaglesc26d4522013-12-02 13:28:49 -0500224
225 # Some components are reporting error status in lower case
226 # so case sensitive comparisons can really mess things
227 # up.
228 if new_status.lower() == error_status.lower():
Masayuki Igawa2a8a8122014-02-07 11:24:49 +0900229 message = ("%s failed to get to expected status (%s). "
230 "In %s state.") % (thing, expected_status,
231 new_status)
Masayuki Igawaa0e786a2014-01-27 15:25:06 +0900232 raise exceptions.BuildErrorException(message,
233 server_id=thing_id)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900234 elif new_status == expected_status and expected_status is not None:
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400235 return True # All good.
236 LOG.debug("Waiting for %s to get to %s status. "
237 "Currently in %s status",
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900238 thing, log_status, new_status)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400239 if not tempest.test.call_until_true(
240 check_status,
Matthew Treinish6c072292014-01-29 19:15:52 +0000241 CONF.compute.build_timeout,
242 CONF.compute.build_interval):
Ken'ichi Ohmichiab1496f2013-12-12 22:17:57 +0900243 message = ("Timed out waiting for thing %s "
244 "to become %s") % (thing_id, log_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200245 raise exceptions.TimeoutException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400246
Yair Friedeb69f3f2013-10-10 13:18:16 +0300247 def _create_loginable_secgroup_rule_nova(self, client=None,
248 secgroup_id=None):
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900249 if client is None:
250 client = self.compute_client
251 if secgroup_id is None:
252 sgs = client.security_groups.list()
253 for sg in sgs:
254 if sg.name == 'default':
255 secgroup_id = sg.id
256
257 # These rules are intended to permit inbound ssh and icmp
258 # traffic from all sources, so no group_id is provided.
259 # Setting a group_id would only permit traffic from ports
260 # belonging to the same security group.
261 rulesets = [
262 {
263 # ssh
264 'ip_protocol': 'tcp',
265 'from_port': 22,
266 'to_port': 22,
267 'cidr': '0.0.0.0/0',
268 },
269 {
270 # ping
271 'ip_protocol': 'icmp',
272 'from_port': -1,
273 'to_port': -1,
274 'cidr': '0.0.0.0/0',
275 }
276 ]
Yair Friedeb69f3f2013-10-10 13:18:16 +0300277 rules = list()
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900278 for ruleset in rulesets:
279 sg_rule = client.security_group_rules.create(secgroup_id,
280 **ruleset)
281 self.set_resource(sg_rule.id, sg_rule)
Yair Friedeb69f3f2013-10-10 13:18:16 +0300282 rules.append(sg_rule)
283 return rules
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900284
Giulio Fidente61cadca2013-09-24 18:33:37 +0200285 def create_server(self, client=None, name=None, image=None, flavor=None,
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900286 create_kwargs={}):
Giulio Fidente61cadca2013-09-24 18:33:37 +0200287 if client is None:
288 client = self.compute_client
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900289 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900290 name = data_utils.rand_name('scenario-server-')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900291 if image is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000292 image = CONF.compute.image_ref
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900293 if flavor is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000294 flavor = CONF.compute.flavor_ref
JordanP9c052aa2014-01-24 13:05:00 +0000295
296 fixed_network_name = CONF.compute.fixed_network_name
297 if 'nics' not in create_kwargs and fixed_network_name:
298 networks = client.networks.list()
299 # If several networks found, set the NetID on which to connect the
300 # server to avoid the following error "Multiple possible networks
301 # found, use a Network ID to be more specific."
302 # See Tempest #1250866
303 if len(networks) > 1:
304 for network in networks:
305 if network.label == fixed_network_name:
306 create_kwargs['nics'] = [{'net-id': network.id}]
307 break
308 # If we didn't find the network we were looking for :
309 else:
310 msg = ("The network on which the NIC of the server must "
311 "be connected can not be found : "
312 "fixed_network_name=%s. Starting instance without "
313 "specifying a network.") % fixed_network_name
314 LOG.info(msg)
315
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900316 LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
317 name, image, flavor)
318 server = client.servers.create(name, image, flavor, **create_kwargs)
Giulio Fidente92f77192013-08-26 17:13:28 +0200319 self.assertEqual(server.name, name)
320 self.set_resource(name, server)
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900321 self.status_timeout(client.servers, server.id, 'ACTIVE')
322 # The instance retrieved on creation is missing network
323 # details, necessitating retrieval after it becomes active to
324 # ensure correct details.
325 server = client.servers.get(server.id)
326 self.set_resource(name, server)
327 LOG.debug("Created server: %s", server)
328 return server
329
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900330 def create_volume(self, client=None, size=1, name=None,
331 snapshot_id=None, imageRef=None):
332 if client is None:
333 client = self.volume_client
334 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900335 name = data_utils.rand_name('scenario-volume-')
Eric Windisch2d26f1b2013-09-04 17:52:16 -0700336 LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900337 volume = client.volumes.create(size=size, display_name=name,
338 snapshot_id=snapshot_id,
339 imageRef=imageRef)
340 self.set_resource(name, volume)
341 self.assertEqual(name, volume.display_name)
342 self.status_timeout(client.volumes, volume.id, 'available')
343 LOG.debug("Created volume: %s", volume)
344 return volume
345
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900346 def create_server_snapshot(self, server, compute_client=None,
347 image_client=None, name=None):
348 if compute_client is None:
349 compute_client = self.compute_client
350 if image_client is None:
351 image_client = self.image_client
352 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900353 name = data_utils.rand_name('scenario-snapshot-')
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900354 LOG.debug("Creating a snapshot image for server: %s", server.name)
355 image_id = compute_client.servers.create_image(server, name)
356 self.addCleanup(image_client.images.delete, image_id)
357 self.status_timeout(image_client.images, image_id, 'active')
358 snapshot_image = image_client.images.get(image_id)
Chang Bo Guofc77e932013-09-16 17:38:26 -0700359 self.assertEqual(name, snapshot_image.name)
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900360 LOG.debug("Created snapshot image %s for server %s",
361 snapshot_image.name, server.name)
362 return snapshot_image
363
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900364 def create_keypair(self, client=None, name=None):
365 if client is None:
366 client = self.compute_client
367 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900368 name = data_utils.rand_name('scenario-keypair-')
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900369 keypair = client.keypairs.create(name)
370 self.assertEqual(keypair.name, name)
371 self.set_resource(name, keypair)
372 return keypair
373
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900374 def get_remote_client(self, server_or_ip, username=None, private_key=None):
llg821243b20502014-02-22 10:32:49 +0800375 if isinstance(server_or_ip, six.string_types):
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900376 ip = server_or_ip
377 else:
Matthew Treinish6c072292014-01-29 19:15:52 +0000378 network_name_for_ssh = CONF.compute.network_for_ssh
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900379 ip = server_or_ip.networks[network_name_for_ssh][0]
380 if username is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000381 username = CONF.scenario.ssh_user
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900382 if private_key is None:
383 private_key = self.keypair.private_key
Masayuki Igawa4ded9f02014-02-17 15:05:59 +0900384 return remote_client.RemoteClient(ip, username, pkey=private_key)
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900385
Nachi Ueno95b41282014-01-15 06:54:21 -0800386 def _log_console_output(self, servers=None):
387 if not servers:
388 servers = self.compute_client.servers.list()
389 for server in servers:
390 LOG.debug('Console output for %s', server.id)
391 LOG.debug(server.get_console_output())
392
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900393 def wait_for_volume_status(self, status):
394 volume_id = self.volume.id
395 self.status_timeout(
396 self.volume_client.volumes, volume_id, status)
397
398 def _image_create(self, name, fmt, path, properties={}):
399 name = data_utils.rand_name('%s-' % name)
400 image_file = open(path, 'rb')
401 self.addCleanup(image_file.close)
402 params = {
403 'name': name,
404 'container_format': fmt,
405 'disk_format': fmt,
406 'is_public': 'True',
407 }
408 params.update(properties)
409 image = self.image_client.images.create(**params)
410 self.addCleanup(self.image_client.images.delete, image)
411 self.assertEqual("queued", image.status)
412 image.update(data=image_file)
413 return image.id
414
415 def glance_image_create(self):
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900416 qcow2_img_path = (CONF.scenario.img_dir + "/" +
417 CONF.scenario.qcow2_img_file)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900418 aki_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.aki_img_file
419 ari_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ari_img_file
420 ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900421 LOG.debug("paths: img: %s, ami: %s, ari: %s, aki: %s"
422 % (qcow2_img_path, ami_img_path, ari_img_path, aki_img_path))
423 try:
424 self.image = self._image_create('scenario-img',
425 'bare',
426 qcow2_img_path,
427 properties={'disk_format':
428 'qcow2'})
429 except IOError:
Masayuki Igawa188fc002014-02-23 06:42:44 +0900430 LOG.debug("A qcow2 image was not found. Try to get a uec image.")
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900431 kernel = self._image_create('scenario-aki', 'aki', aki_img_path)
432 ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path)
433 properties = {
434 'properties': {'kernel_id': kernel, 'ramdisk_id': ramdisk}
435 }
436 self.image = self._image_create('scenario-ami', 'ami',
437 path=ami_img_path,
438 properties=properties)
439 LOG.debug("image:%s" % self.image)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900440
Sean Dague6dbc6da2013-05-08 17:49:46 -0400441
442class NetworkScenarioTest(OfficialClientTest):
443 """
444 Base class for network scenario tests
445 """
446
447 @classmethod
448 def check_preconditions(cls):
Matthew Treinish6c072292014-01-29 19:15:52 +0000449 if (CONF.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400450 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200451 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400452 try:
453 cls.network_client.list_networks()
454 except exc.EndpointNotFound:
455 cls.enabled = False
456 raise
457 else:
458 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400459 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400460 raise cls.skipException(msg)
461
462 @classmethod
463 def setUpClass(cls):
464 super(NetworkScenarioTest, cls).setUpClass()
Matthew Treinish6c072292014-01-29 19:15:52 +0000465 if CONF.compute.allow_tenant_isolation:
Miguel Lavalleb8fabc52013-08-23 11:19:57 -0500466 cls.tenant_id = cls.isolated_creds.get_primary_tenant().id
467 else:
468 cls.tenant_id = cls.manager._get_identity_client(
Matthew Treinish6c072292014-01-29 19:15:52 +0000469 CONF.identity.username,
470 CONF.identity.password,
471 CONF.identity.tenant_name).tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400472
Sean Dague6dbc6da2013-05-08 17:49:46 -0400473 def _create_network(self, tenant_id, namestart='network-smoke-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900474 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400475 body = dict(
476 network=dict(
477 name=name,
478 tenant_id=tenant_id,
479 ),
480 )
481 result = self.network_client.create_network(body=body)
482 network = net_common.DeletableNetwork(client=self.network_client,
483 **result['network'])
484 self.assertEqual(network.name, name)
485 self.set_resource(name, network)
486 return network
487
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200488 def _list_networks(self, **kwargs):
489 nets = self.network_client.list_networks(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400490 return nets['networks']
491
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200492 def _list_subnets(self, **kwargs):
493 subnets = self.network_client.list_subnets(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400494 return subnets['subnets']
495
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200496 def _list_routers(self, **kwargs):
497 routers = self.network_client.list_routers(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400498 return routers['routers']
499
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200500 def _list_ports(self, **kwargs):
501 ports = self.network_client.list_ports(**kwargs)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000502 return ports['ports']
503
504 def _get_tenant_own_network_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200505 nets = self._list_networks(tenant_id=tenant_id)
506 return len(nets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000507
508 def _get_tenant_own_subnet_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200509 subnets = self._list_subnets(tenant_id=tenant_id)
510 return len(subnets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000511
512 def _get_tenant_own_port_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200513 ports = self._list_ports(tenant_id=tenant_id)
514 return len(ports)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000515
Yair Fried3097dc12014-01-26 08:46:43 +0200516 def _create_subnet(self, network, namestart='subnet-smoke-', **kwargs):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400517 """
518 Create a subnet for the given network within the cidr block
519 configured for tenant networks.
520 """
Attila Fazekase857bd62013-10-21 21:02:44 +0200521
522 def cidr_in_use(cidr, tenant_id):
523 """
524 :return True if subnet with cidr already exist in tenant
525 False else
526 """
527 cidr_in_use = self._list_subnets(tenant_id=tenant_id, cidr=cidr)
528 return len(cidr_in_use) != 0
529
Matthew Treinish6c072292014-01-29 19:15:52 +0000530 tenant_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400531 result = None
532 # Repeatedly attempt subnet creation with sequential cidr
533 # blocks until an unallocated block is found.
Matthew Treinish6c072292014-01-29 19:15:52 +0000534 for subnet_cidr in tenant_cidr.subnet(
535 CONF.network.tenant_network_mask_bits):
Attila Fazekase857bd62013-10-21 21:02:44 +0200536 str_cidr = str(subnet_cidr)
537 if cidr_in_use(str_cidr, tenant_id=network.tenant_id):
538 continue
539
Sean Dague6dbc6da2013-05-08 17:49:46 -0400540 body = dict(
541 subnet=dict(
Attila Fazekase857bd62013-10-21 21:02:44 +0200542 name=data_utils.rand_name(namestart),
Sean Dague6dbc6da2013-05-08 17:49:46 -0400543 ip_version=4,
544 network_id=network.id,
545 tenant_id=network.tenant_id,
Attila Fazekase857bd62013-10-21 21:02:44 +0200546 cidr=str_cidr,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400547 ),
548 )
Yair Fried3097dc12014-01-26 08:46:43 +0200549 body['subnet'].update(kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400550 try:
551 result = self.network_client.create_subnet(body=body)
552 break
Mark McClainf2982e82013-07-06 17:48:03 -0400553 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400554 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
555 if not is_overlapping_cidr:
556 raise
557 self.assertIsNotNone(result, 'Unable to allocate tenant network')
558 subnet = net_common.DeletableSubnet(client=self.network_client,
559 **result['subnet'])
Attila Fazekase857bd62013-10-21 21:02:44 +0200560 self.assertEqual(subnet.cidr, str_cidr)
Masayuki Igawa259c1132013-10-31 17:48:44 +0900561 self.set_resource(data_utils.rand_name(namestart), subnet)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400562 return subnet
563
564 def _create_port(self, network, namestart='port-quotatest-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900565 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400566 body = dict(
567 port=dict(name=name,
568 network_id=network.id,
569 tenant_id=network.tenant_id))
570 result = self.network_client.create_port(body=body)
571 self.assertIsNotNone(result, 'Unable to allocate port')
572 port = net_common.DeletablePort(client=self.network_client,
573 **result['port'])
574 self.set_resource(name, port)
575 return port
576
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200577 def _get_server_port_id(self, server, ip_addr=None):
578 ports = self._list_ports(device_id=server.id, fixed_ip=ip_addr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400579 self.assertEqual(len(ports), 1,
580 "Unable to determine which port to target.")
Yair Fried05db2522013-11-18 11:02:10 +0200581 return ports[0]['id']
582
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200583 def _create_floating_ip(self, thing, external_network_id, port_id=None):
584 if not port_id:
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400585 port_id = self._get_server_port_id(thing)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400586 body = dict(
587 floatingip=dict(
588 floating_network_id=external_network_id,
589 port_id=port_id,
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400590 tenant_id=thing.tenant_id,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400591 )
592 )
593 result = self.network_client.create_floatingip(body=body)
594 floating_ip = net_common.DeletableFloatingIp(
595 client=self.network_client,
596 **result['floatingip'])
Masayuki Igawa259c1132013-10-31 17:48:44 +0900597 self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400598 return floating_ip
599
Yair Fried05db2522013-11-18 11:02:10 +0200600 def _associate_floating_ip(self, floating_ip, server):
601 port_id = self._get_server_port_id(server)
602 floating_ip.update(port_id=port_id)
603 self.assertEqual(port_id, floating_ip.port_id)
604 return floating_ip
605
Yair Fried9a551c42013-12-15 14:59:34 +0200606 def _disassociate_floating_ip(self, floating_ip):
607 """
608 :param floating_ip: type DeletableFloatingIp
609 """
610 floating_ip.update(port_id=None)
llg8212e4cd3922014-02-15 12:14:21 +0800611 self.assertIsNone(floating_ip.port_id)
Yair Fried9a551c42013-12-15 14:59:34 +0200612 return floating_ip
613
614 def _ping_ip_address(self, ip_address, should_succeed=True):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400615 cmd = ['ping', '-c1', '-w1', ip_address]
616
617 def ping():
618 proc = subprocess.Popen(cmd,
619 stdout=subprocess.PIPE,
620 stderr=subprocess.PIPE)
621 proc.wait()
Yair Fried9a551c42013-12-15 14:59:34 +0200622 return (proc.returncode == 0) == should_succeed
Sean Dague6dbc6da2013-05-08 17:49:46 -0400623
Nachi Ueno6d580be2013-07-24 10:58:11 -0700624 return tempest.test.call_until_true(
Matthew Treinish6c072292014-01-29 19:15:52 +0000625 ping, CONF.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000626
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400627 def _create_pool(self, lb_method, protocol, subnet_id):
628 """Wrapper utility that returns a test pool."""
629 name = data_utils.rand_name('pool-')
630 body = {
631 "pool": {
632 "protocol": protocol,
633 "name": name,
634 "subnet_id": subnet_id,
635 "lb_method": lb_method
636 }
637 }
638 resp = self.network_client.create_pool(body=body)
639 pool = net_common.DeletablePool(client=self.network_client,
640 **resp['pool'])
641 self.assertEqual(pool['name'], name)
642 self.set_resource(name, pool)
643 return pool
644
645 def _create_member(self, address, protocol_port, pool_id):
646 """Wrapper utility that returns a test member."""
647 body = {
648 "member": {
649 "protocol_port": protocol_port,
650 "pool_id": pool_id,
651 "address": address
652 }
653 }
654 resp = self.network_client.create_member(body)
655 member = net_common.DeletableMember(client=self.network_client,
656 **resp['member'])
657 self.set_resource(data_utils.rand_name('member-'), member)
658 return member
659
660 def _create_vip(self, protocol, protocol_port, subnet_id, pool_id):
661 """Wrapper utility that returns a test vip."""
662 name = data_utils.rand_name('vip-')
663 body = {
664 "vip": {
665 "protocol": protocol,
666 "name": name,
667 "subnet_id": subnet_id,
668 "pool_id": pool_id,
669 "protocol_port": protocol_port
670 }
671 }
672 resp = self.network_client.create_vip(body)
673 vip = net_common.DeletableVip(client=self.network_client,
674 **resp['vip'])
675 self.assertEqual(vip['name'], name)
676 self.set_resource(name, vip)
677 return vip
678
Yair Fried9a551c42013-12-15 14:59:34 +0200679 def _check_vm_connectivity(self, ip_address,
680 username=None,
681 private_key=None,
682 should_connect=True):
683 """
684 :param ip_address: server to test against
685 :param username: server's ssh username
686 :param private_key: server's ssh private key to be used
687 :param should_connect: True/False indicates positive/negative test
688 positive - attempt ping and ssh
689 negative - attempt ping and fail if succeed
690
691 :raises: AssertError if the result of the connectivity check does
692 not match the value of the should_connect param
693 """
694 if should_connect:
695 msg = "Timed out waiting for %s to become reachable" % ip_address
696 else:
697 msg = "ip address %s is reachable" % ip_address
698 self.assertTrue(self._ping_ip_address(ip_address,
699 should_succeed=should_connect),
700 msg=msg)
701 if should_connect:
702 # no need to check ssh for negative connectivity
Attila Fazekasad7ef7d2013-11-20 10:12:53 +0100703 linux_client = self.get_remote_client(ip_address, username,
704 private_key)
705 linux_client.validate_authentication()
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200706
Yair Fried3097dc12014-01-26 08:46:43 +0200707 def _check_remote_connectivity(self, source, dest, should_succeed=True):
708 """
709 check ping server via source ssh connection
710
711 :param source: RemoteClient: an ssh connection from which to ping
712 :param dest: and IP to ping against
713 :param should_succeed: boolean should ping succeed or not
714 :returns: boolean -- should_succeed == ping
715 :returns: ping is false if ping failed
716 """
717 def ping_remote():
718 try:
719 source.ping_host(dest)
720 except exceptions.SSHExecCommandFailed:
721 LOG.exception('Failed to ping host via ssh connection')
722 return not should_succeed
723 return should_succeed
724
725 return tempest.test.call_until_true(ping_remote,
726 CONF.compute.ping_timeout,
727 1)
728
Yair Friedeb69f3f2013-10-10 13:18:16 +0300729 def _create_security_group_nova(self, client=None,
730 namestart='secgroup-smoke-',
731 tenant_id=None):
732 if client is None:
733 client = self.compute_client
734 # Create security group
735 sg_name = data_utils.rand_name(namestart)
736 sg_desc = sg_name + " description"
737 secgroup = client.security_groups.create(sg_name, sg_desc)
738 self.assertEqual(secgroup.name, sg_name)
739 self.assertEqual(secgroup.description, sg_desc)
740 self.set_resource(sg_name, secgroup)
741
742 # Add rules to the security group
743 self._create_loginable_secgroup_rule_nova(client, secgroup.id)
744
745 return secgroup
746
747 def _create_security_group_neutron(self, tenant_id, client=None,
748 namestart='secgroup-smoke-'):
749 if client is None:
750 client = self.network_client
751 secgroup = self._create_empty_security_group(namestart=namestart,
752 client=client,
753 tenant_id=tenant_id)
754
755 # Add rules to the security group
756 rules = self._create_loginable_secgroup_rule_neutron(secgroup=secgroup)
757 for rule in rules:
758 self.assertEqual(tenant_id, rule.tenant_id)
759 self.assertEqual(secgroup.id, rule.security_group_id)
760 return secgroup
761
762 def _create_empty_security_group(self, tenant_id, client=None,
763 namestart='secgroup-smoke-'):
764 """Create a security group without rules.
765
766 Default rules will be created:
767 - IPv4 egress to any
768 - IPv6 egress to any
769
770 :param tenant_id: secgroup will be created in this tenant
771 :returns: DeletableSecurityGroup -- containing the secgroup created
772 """
773 if client is None:
774 client = self.network_client
775 sg_name = data_utils.rand_name(namestart)
776 sg_desc = sg_name + " description"
777 sg_dict = dict(name=sg_name,
778 description=sg_desc)
779 sg_dict['tenant_id'] = tenant_id
780 body = dict(security_group=sg_dict)
781 result = client.create_security_group(body=body)
782 secgroup = net_common.DeletableSecurityGroup(
783 client=client,
784 **result['security_group']
785 )
786 self.assertEqual(secgroup.name, sg_name)
787 self.assertEqual(tenant_id, secgroup.tenant_id)
788 self.assertEqual(secgroup.description, sg_desc)
789 self.set_resource(sg_name, secgroup)
790 return secgroup
791
792 def _default_security_group(self, tenant_id, client=None):
793 """Get default secgroup for given tenant_id.
794
795 :returns: DeletableSecurityGroup -- default secgroup for given tenant
796 """
797 if client is None:
798 client = self.network_client
799 sgs = [
800 sg for sg in client.list_security_groups().values()[0]
801 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
802 ]
803 msg = "No default security group for tenant %s." % (tenant_id)
804 self.assertTrue(len(sgs) > 0, msg)
805 if len(sgs) > 1:
806 msg = "Found %d default security groups" % len(sgs)
807 raise exc.NeutronClientNoUniqueMatch(msg=msg)
808 return net_common.DeletableSecurityGroup(client=client,
809 **sgs[0])
810
811 def _create_security_group_rule(self, client=None, secgroup=None,
812 tenant_id=None, **kwargs):
813 """Create a rule from a dictionary of rule parameters.
814
815 Create a rule in a secgroup. if secgroup not defined will search for
816 default secgroup in tenant_id.
817
818 :param secgroup: type DeletableSecurityGroup.
819 :param secgroup_id: search for secgroup by id
820 default -- choose default secgroup for given tenant_id
821 :param tenant_id: if secgroup not passed -- the tenant in which to
822 search for default secgroup
823 :param kwargs: a dictionary containing rule parameters:
824 for example, to allow incoming ssh:
825 rule = {
826 direction: 'ingress'
827 protocol:'tcp',
828 port_range_min: 22,
829 port_range_max: 22
830 }
831 """
832 if client is None:
833 client = self.network_client
834 if secgroup is None:
835 secgroup = self._default_security_group(tenant_id)
836
837 ruleset = dict(security_group_id=secgroup.id,
838 tenant_id=secgroup.tenant_id,
839 )
840 ruleset.update(kwargs)
841
842 body = dict(security_group_rule=dict(ruleset))
843 sg_rule = client.create_security_group_rule(body=body)
844 sg_rule = net_common.DeletableSecurityGroupRule(
845 client=client,
846 **sg_rule['security_group_rule']
847 )
848 self.set_resource(sg_rule.id, sg_rule)
849 self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
850 self.assertEqual(secgroup.id, sg_rule.security_group_id)
851
852 return sg_rule
853
854 def _create_loginable_secgroup_rule_neutron(self, client=None,
855 secgroup=None):
856 """These rules are intended to permit inbound ssh and icmp
857 traffic from all sources, so no group_id is provided.
858 Setting a group_id would only permit traffic from ports
859 belonging to the same security group.
860 """
861
862 if client is None:
863 client = self.network_client
864 rules = []
865 rulesets = [
866 dict(
867 # ssh
868 protocol='tcp',
869 port_range_min=22,
870 port_range_max=22,
871 ),
872 dict(
873 # ping
874 protocol='icmp',
875 )
876 ]
877 for ruleset in rulesets:
878 for r_direction in ['ingress', 'egress']:
879 ruleset['direction'] = r_direction
880 try:
881 sg_rule = self._create_security_group_rule(
882 client=client, secgroup=secgroup, **ruleset)
883 except exc.NeutronClientException as ex:
884 # if rule already exist - skip rule and continue
885 if not (ex.status_code is 409 and 'Security group rule'
886 ' already exists' in ex.message):
887 raise ex
888 else:
889 self.assertEqual(r_direction, sg_rule.direction)
890 rules.append(sg_rule)
891
892 return rules
893
Yair Fried5f670ab2013-12-09 09:26:51 +0200894 def _ssh_to_server(self, server, private_key):
Matthew Treinish6c072292014-01-29 19:15:52 +0000895 ssh_login = CONF.compute.image_ssh_user
Yair Fried5f670ab2013-12-09 09:26:51 +0200896 return self.get_remote_client(server,
897 username=ssh_login,
898 private_key=private_key)
899
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000900 def _show_quota_network(self, tenant_id):
901 quota = self.network_client.show_quota(tenant_id)
902 return quota['quota']['network']
903
904 def _show_quota_subnet(self, tenant_id):
905 quota = self.network_client.show_quota(tenant_id)
906 return quota['quota']['subnet']
907
908 def _show_quota_port(self, tenant_id):
909 quota = self.network_client.show_quota(tenant_id)
910 return quota['quota']['port']
911
Yair Fried4d7efa62013-11-17 17:12:29 +0200912 def _get_router(self, tenant_id):
913 """Retrieve a router for the given tenant id.
914
915 If a public router has been configured, it will be returned.
916
917 If a public router has not been configured, but a public
918 network has, a tenant router will be created and returned that
919 routes traffic to the public network.
920 """
Matthew Treinish6c072292014-01-29 19:15:52 +0000921 router_id = CONF.network.public_router_id
922 network_id = CONF.network.public_network_id
Yair Fried4d7efa62013-11-17 17:12:29 +0200923 if router_id:
924 result = self.network_client.show_router(router_id)
925 return net_common.AttributeDict(**result['router'])
926 elif network_id:
927 router = self._create_router(tenant_id)
928 router.add_gateway(network_id)
929 return router
930 else:
931 raise Exception("Neither of 'public_router_id' or "
932 "'public_network_id' has been defined.")
933
934 def _create_router(self, tenant_id, namestart='router-smoke-'):
935 name = data_utils.rand_name(namestart)
936 body = dict(
937 router=dict(
938 name=name,
939 admin_state_up=True,
940 tenant_id=tenant_id,
941 ),
942 )
943 result = self.network_client.create_router(body=body)
944 router = net_common.DeletableRouter(client=self.network_client,
945 **result['router'])
946 self.assertEqual(router.name, name)
947 self.set_resource(name, router)
948 return router
949
950 def _create_networks(self, tenant_id=None):
951 """Create a network with a subnet connected to a router.
952
953 :returns: network, subnet, router
954 """
955 if tenant_id is None:
956 tenant_id = self.tenant_id
957 network = self._create_network(tenant_id)
958 router = self._get_router(tenant_id)
959 subnet = self._create_subnet(network)
960 subnet.add_to_router(router.id)
Yair Fried4d7efa62013-11-17 17:12:29 +0200961 return network, subnet, router
962
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200963
964class OrchestrationScenarioTest(OfficialClientTest):
965 """
966 Base class for orchestration scenario tests
967 """
968
969 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -0700970 def setUpClass(cls):
971 super(OrchestrationScenarioTest, cls).setUpClass()
Matthew Treinish6c072292014-01-29 19:15:52 +0000972 if not CONF.service_available.heat:
Matt Riedemann11c5b642013-08-24 08:45:38 -0700973 raise cls.skipException("Heat support is required")
974
975 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200976 def credentials(cls):
Matthew Treinish6c072292014-01-29 19:15:52 +0000977 username = CONF.identity.admin_username
978 password = CONF.identity.admin_password
979 tenant_name = CONF.identity.tenant_name
Ryan Hsuee1017c2013-12-20 12:00:34 -0800980 return username, password, tenant_name
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200981
982 def _load_template(self, base_file, file_name):
983 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
984 file_name)
985 with open(filepath) as f:
986 return f.read()
987
988 @classmethod
989 def _stack_rand_name(cls):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900990 return data_utils.rand_name(cls.__name__ + '-')
Steve Baker80252da2013-09-25 13:29:10 +1200991
992 @classmethod
993 def _get_default_network(cls):
994 networks = cls.network_client.list_networks()
995 for net in networks['networks']:
Matthew Treinish6c072292014-01-29 19:15:52 +0000996 if net['name'] == CONF.compute.fixed_network_name:
Steve Baker80252da2013-09-25 13:29:10 +1200997 return net