blob: b2071617ce5ed1e1bb70c06a40dc4c1dcbba2a3e [file] [log] [blame]
ZhiQiang Fan39f97222013-09-20 04:49:44 +08001# Copyright 2012 OpenStack Foundation
Sean Dague6dbc6da2013-05-08 17:49:46 -04002# Copyright 2013 IBM Corp.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
Attila Fazekasfb7552a2013-08-27 13:02:26 +020017import logging
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120018import os
Steve Baker22c16602014-05-05 13:34:19 +120019import re
llg821243b20502014-02-22 10:32:49 +080020import six
Sean Dague6dbc6da2013-05-08 17:49:46 -040021import subprocess
Steve Baker22c16602014-05-05 13:34:19 +120022import time
Sean Dague6dbc6da2013-05-08 17:49:46 -040023
Steve Baker22c16602014-05-05 13:34:19 +120024from heatclient import exc as heat_exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040025import netaddr
Mark McClainf2982e82013-07-06 17:48:03 -040026from neutronclient.common import exceptions as exc
fujioka yuuichi636f8db2013-08-09 12:05:24 +090027from novaclient import exceptions as nova_exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040028
Sean Dague1937d092013-05-17 16:36:38 -040029from tempest.api.network import common as net_common
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000030from tempest import auth
Andrea Frittolif9cde7e2014-02-18 09:57:04 +000031from tempest import clients
Matt Riedemann343305f2014-05-27 09:55:03 -070032from tempest.common import debug
Matthew Treinishb86cda92013-07-29 11:22:23 -040033from tempest.common import isolated_creds
Masayuki Igawa259c1132013-10-31 17:48:44 +090034from tempest.common.utils import data_utils
Masayuki Igawa4ded9f02014-02-17 15:05:59 +090035from tempest.common.utils.linux import remote_client
Matthew Treinish6c072292014-01-29 19:15:52 +000036from tempest import config
Giulio Fidente92f77192013-08-26 17:13:28 +020037from tempest import exceptions
Attila Fazekasfb7552a2013-08-27 13:02:26 +020038from tempest.openstack.common import log
Steve Baker22c16602014-05-05 13:34:19 +120039from tempest.openstack.common import timeutils
Sean Dague6dbc6da2013-05-08 17:49:46 -040040import tempest.test
Sean Dague6dbc6da2013-05-08 17:49:46 -040041
Matthew Treinish6c072292014-01-29 19:15:52 +000042CONF = config.CONF
Sean Dague6dbc6da2013-05-08 17:49:46 -040043
Attila Fazekasfb7552a2013-08-27 13:02:26 +020044LOG = log.getLogger(__name__)
45
46# NOTE(afazekas): Workaround for the stdout logging
47LOG_nova_client = logging.getLogger('novaclient.client')
48LOG_nova_client.addHandler(log.NullHandler())
49
50LOG_cinder_client = logging.getLogger('cinderclient.client')
51LOG_cinder_client.addHandler(log.NullHandler())
Sean Dague6dbc6da2013-05-08 17:49:46 -040052
53
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040054class OfficialClientTest(tempest.test.BaseTestCase):
Sean Dague6dbc6da2013-05-08 17:49:46 -040055 """
56 Official Client test base class for scenario testing.
57
58 Official Client tests are tests that have the following characteristics:
59
60 * Test basic operations of an API, typically in an order that
61 a regular user would perform those operations
62 * Test only the correct inputs and action paths -- no fuzz or
63 random input data is sent, only valid inputs.
64 * Use only the default client tool for calling an API
65 """
66
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040067 @classmethod
68 def setUpClass(cls):
Attila Fazekasf86fa312013-07-30 19:56:39 +020069 super(OfficialClientTest, cls).setUpClass()
Matthew Treinishb86cda92013-07-29 11:22:23 -040070 cls.isolated_creds = isolated_creds.IsolatedCreds(
Sean Dague6969b902014-01-28 06:48:37 -050071 cls.__name__, tempest_client=False,
Matthew Treinish9f756a02014-01-15 10:26:07 -050072 network_resources=cls.network_resources)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120073
Andrea Frittolif9cde7e2014-02-18 09:57:04 +000074 cls.manager = clients.OfficialClientManager(
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000075 credentials=cls.credentials())
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040076 cls.compute_client = cls.manager.compute_client
77 cls.image_client = cls.manager.image_client
Adam Gandelman4a48a602014-03-20 18:23:18 -070078 cls.baremetal_client = cls.manager.baremetal_client
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040079 cls.identity_client = cls.manager.identity_client
80 cls.network_client = cls.manager.network_client
81 cls.volume_client = cls.manager.volume_client
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +000082 cls.object_storage_client = cls.manager.object_storage_client
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120083 cls.orchestration_client = cls.manager.orchestration_client
Sergey Lukjanov7409e2e2014-03-27 12:55:50 +040084 cls.data_processing_client = cls.manager.data_processing_client
Artur Svechnikovc3bf9252014-05-05 16:37:37 +040085 cls.ceilometer_client = cls.manager.ceilometer_client
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040086 cls.resource_keys = {}
87 cls.os_resources = []
Sean Dague6dbc6da2013-05-08 17:49:46 -040088
89 @classmethod
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000090 def _get_credentials(cls, get_creds, ctype):
Matthew Treinish6c072292014-01-29 19:15:52 +000091 if CONF.compute.allow_tenant_isolation:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000092 creds = get_creds()
Yair Fried769bbff2013-12-18 16:33:17 +020093 else:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000094 creds = auth.get_default_credentials(ctype)
95 return creds
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120096
97 @classmethod
Yair Frieda71cc442013-12-18 13:32:36 +020098 def credentials(cls):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000099 return cls._get_credentials(cls.isolated_creds.get_primary_creds,
100 'user')
Yair Frieda71cc442013-12-18 13:32:36 +0200101
102 @classmethod
103 def alt_credentials(cls):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000104 return cls._get_credentials(cls.isolated_creds.get_alt_creds,
105 'alt_user')
Yair Frieda71cc442013-12-18 13:32:36 +0200106
107 @classmethod
108 def admin_credentials(cls):
109 return cls._get_credentials(cls.isolated_creds.get_admin_creds,
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000110 'identity_admin')
Yair Frieda71cc442013-12-18 13:32:36 +0200111
Yair Friedbf2e2c42014-01-28 12:06:38 +0200112 @staticmethod
113 def cleanup_resource(resource, test_name):
114
115 LOG.debug("Deleting %r from shared resources of %s" %
116 (resource, test_name))
117 try:
118 # OpenStack resources are assumed to have a delete()
119 # method which destroys the resource...
120 resource.delete()
121 except Exception as e:
122 # If the resource is already missing, mission accomplished.
Steven Hardyef1c8962014-05-07 10:05:45 +0100123 # - Status code tolerated as a workaround for bug 1247568
124 # - HTTPNotFound tolerated as this is currently raised when
125 # attempting to delete an already-deleted heat stack.
126 if (e.__class__.__name__ in ('NotFound', 'HTTPNotFound') or
Yair Friedbf2e2c42014-01-28 12:06:38 +0200127 (hasattr(e, 'status_code') and e.status_code == 404)):
128 return
129 raise
130
131 def is_deletion_complete():
132 # Deletion testing is only required for objects whose
133 # existence cannot be checked via retrieval.
134 if isinstance(resource, dict):
135 return True
136 try:
137 resource.get()
138 except Exception as e:
139 # Clients are expected to return an exception
140 # called 'NotFound' if retrieval fails.
141 if e.__class__.__name__ == 'NotFound':
142 return True
143 raise
144 return False
145
146 # Block until resource deletion has completed or timed-out
147 tempest.test.call_until_true(is_deletion_complete, 10, 1)
148
Yair Frieda71cc442013-12-18 13:32:36 +0200149 @classmethod
Sean Dague6dbc6da2013-05-08 17:49:46 -0400150 def tearDownClass(cls):
151 # NOTE(jaypipes): Because scenario tests are typically run in a
152 # specific order, and because test methods in scenario tests
153 # generally create resources in a particular order, we destroy
154 # resources in the reverse order in which resources are added to
155 # the scenario test class object
156 while cls.os_resources:
157 thing = cls.os_resources.pop()
Yair Friedbf2e2c42014-01-28 12:06:38 +0200158 cls.cleanup_resource(thing, cls.__name__)
Matthew Treinishb86cda92013-07-29 11:22:23 -0400159 cls.isolated_creds.clear_isolated_creds()
160 super(OfficialClientTest, cls).tearDownClass()
Sean Dague6dbc6da2013-05-08 17:49:46 -0400161
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400162 @classmethod
163 def set_resource(cls, key, thing):
164 LOG.debug("Adding %r to shared resources of %s" %
165 (thing, cls.__name__))
166 cls.resource_keys[key] = thing
167 cls.os_resources.append(thing)
168
169 @classmethod
170 def get_resource(cls, key):
171 return cls.resource_keys[key]
172
173 @classmethod
174 def remove_resource(cls, key):
175 thing = cls.resource_keys[key]
176 cls.os_resources.remove(thing)
177 del cls.resource_keys[key]
178
Steve Bakerefde7612013-09-30 11:29:23 +1300179 def status_timeout(self, things, thing_id, expected_status,
180 error_status='ERROR',
181 not_found_exception=nova_exceptions.NotFound):
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400182 """
183 Given a thing and an expected status, do a loop, sleeping
184 for a configurable amount of time, checking for the
185 expected status to show. At any time, if the returned
186 status of the thing is ERROR, fail out.
187 """
Steve Bakerefde7612013-09-30 11:29:23 +1300188 self._status_timeout(things, thing_id,
189 expected_status=expected_status,
190 error_status=error_status,
191 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900192
Steve Bakerefde7612013-09-30 11:29:23 +1300193 def delete_timeout(self, things, thing_id,
194 error_status='ERROR',
195 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900196 """
197 Given a thing, do a loop, sleeping
198 for a configurable amount of time, checking for the
199 deleted status to show. At any time, if the returned
200 status of the thing is ERROR, fail out.
201 """
202 self._status_timeout(things,
203 thing_id,
Steve Bakerefde7612013-09-30 11:29:23 +1300204 allow_notfound=True,
205 error_status=error_status,
206 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900207
208 def _status_timeout(self,
209 things,
210 thing_id,
211 expected_status=None,
Steve Bakerefde7612013-09-30 11:29:23 +1300212 allow_notfound=False,
213 error_status='ERROR',
214 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900215
216 log_status = expected_status if expected_status else ''
217 if allow_notfound:
218 log_status += ' or NotFound' if log_status != '' else 'NotFound'
219
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400220 def check_status():
221 # python-novaclient has resources available to its client
222 # that all implement a get() method taking an identifier
223 # for the singular resource to retrieve.
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900224 try:
225 thing = things.get(thing_id)
Steve Bakerefde7612013-09-30 11:29:23 +1300226 except not_found_exception:
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900227 if allow_notfound:
228 return True
229 else:
230 raise
231
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400232 new_status = thing.status
Brent Eaglesc26d4522013-12-02 13:28:49 -0500233
234 # Some components are reporting error status in lower case
235 # so case sensitive comparisons can really mess things
236 # up.
237 if new_status.lower() == error_status.lower():
Masayuki Igawa2a8a8122014-02-07 11:24:49 +0900238 message = ("%s failed to get to expected status (%s). "
239 "In %s state.") % (thing, expected_status,
240 new_status)
Masayuki Igawaa0e786a2014-01-27 15:25:06 +0900241 raise exceptions.BuildErrorException(message,
242 server_id=thing_id)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900243 elif new_status == expected_status and expected_status is not None:
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400244 return True # All good.
245 LOG.debug("Waiting for %s to get to %s status. "
246 "Currently in %s status",
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900247 thing, log_status, new_status)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400248 if not tempest.test.call_until_true(
249 check_status,
Matthew Treinish6c072292014-01-29 19:15:52 +0000250 CONF.compute.build_timeout,
251 CONF.compute.build_interval):
Ken'ichi Ohmichiab1496f2013-12-12 22:17:57 +0900252 message = ("Timed out waiting for thing %s "
253 "to become %s") % (thing_id, log_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200254 raise exceptions.TimeoutException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400255
Yair Friedeb69f3f2013-10-10 13:18:16 +0300256 def _create_loginable_secgroup_rule_nova(self, client=None,
257 secgroup_id=None):
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900258 if client is None:
259 client = self.compute_client
260 if secgroup_id is None:
261 sgs = client.security_groups.list()
262 for sg in sgs:
263 if sg.name == 'default':
264 secgroup_id = sg.id
265
266 # These rules are intended to permit inbound ssh and icmp
267 # traffic from all sources, so no group_id is provided.
268 # Setting a group_id would only permit traffic from ports
269 # belonging to the same security group.
270 rulesets = [
271 {
272 # ssh
273 'ip_protocol': 'tcp',
274 'from_port': 22,
275 'to_port': 22,
276 'cidr': '0.0.0.0/0',
277 },
278 {
279 # ping
280 'ip_protocol': 'icmp',
281 'from_port': -1,
282 'to_port': -1,
283 'cidr': '0.0.0.0/0',
284 }
285 ]
Yair Friedeb69f3f2013-10-10 13:18:16 +0300286 rules = list()
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900287 for ruleset in rulesets:
288 sg_rule = client.security_group_rules.create(secgroup_id,
289 **ruleset)
Yair Friedeb69f3f2013-10-10 13:18:16 +0300290 rules.append(sg_rule)
291 return rules
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900292
Grishkin0f1e11c2014-05-04 20:44:52 +0400293 def _create_security_group_nova(self, client=None,
294 namestart='secgroup-smoke-'):
295 if client is None:
296 client = self.compute_client
297 # Create security group
298 sg_name = data_utils.rand_name(namestart)
299 sg_desc = sg_name + " description"
300 secgroup = client.security_groups.create(sg_name, sg_desc)
301 self.assertEqual(secgroup.name, sg_name)
302 self.assertEqual(secgroup.description, sg_desc)
303 self.set_resource(sg_name, secgroup)
304
305 # Add rules to the security group
306 self._create_loginable_secgroup_rule_nova(client, secgroup.id)
307
308 return secgroup
309
Giulio Fidente61cadca2013-09-24 18:33:37 +0200310 def create_server(self, client=None, name=None, image=None, flavor=None,
Adam Gandelman4a48a602014-03-20 18:23:18 -0700311 wait=True, create_kwargs={}):
Giulio Fidente61cadca2013-09-24 18:33:37 +0200312 if client is None:
313 client = self.compute_client
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900314 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900315 name = data_utils.rand_name('scenario-server-')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900316 if image is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000317 image = CONF.compute.image_ref
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900318 if flavor is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000319 flavor = CONF.compute.flavor_ref
JordanP9c052aa2014-01-24 13:05:00 +0000320
321 fixed_network_name = CONF.compute.fixed_network_name
322 if 'nics' not in create_kwargs and fixed_network_name:
323 networks = client.networks.list()
324 # If several networks found, set the NetID on which to connect the
325 # server to avoid the following error "Multiple possible networks
326 # found, use a Network ID to be more specific."
327 # See Tempest #1250866
328 if len(networks) > 1:
329 for network in networks:
330 if network.label == fixed_network_name:
331 create_kwargs['nics'] = [{'net-id': network.id}]
332 break
333 # If we didn't find the network we were looking for :
334 else:
335 msg = ("The network on which the NIC of the server must "
336 "be connected can not be found : "
337 "fixed_network_name=%s. Starting instance without "
338 "specifying a network.") % fixed_network_name
339 LOG.info(msg)
340
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900341 LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
342 name, image, flavor)
343 server = client.servers.create(name, image, flavor, **create_kwargs)
Giulio Fidente92f77192013-08-26 17:13:28 +0200344 self.assertEqual(server.name, name)
345 self.set_resource(name, server)
Adam Gandelman4a48a602014-03-20 18:23:18 -0700346 if wait:
347 self.status_timeout(client.servers, server.id, 'ACTIVE')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900348 # The instance retrieved on creation is missing network
349 # details, necessitating retrieval after it becomes active to
350 # ensure correct details.
351 server = client.servers.get(server.id)
352 self.set_resource(name, server)
353 LOG.debug("Created server: %s", server)
354 return server
355
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900356 def create_volume(self, client=None, size=1, name=None,
357 snapshot_id=None, imageRef=None):
358 if client is None:
359 client = self.volume_client
360 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900361 name = data_utils.rand_name('scenario-volume-')
Eric Windisch2d26f1b2013-09-04 17:52:16 -0700362 LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900363 volume = client.volumes.create(size=size, display_name=name,
364 snapshot_id=snapshot_id,
365 imageRef=imageRef)
366 self.set_resource(name, volume)
367 self.assertEqual(name, volume.display_name)
368 self.status_timeout(client.volumes, volume.id, 'available')
369 LOG.debug("Created volume: %s", volume)
370 return volume
371
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900372 def create_server_snapshot(self, server, compute_client=None,
373 image_client=None, name=None):
374 if compute_client is None:
375 compute_client = self.compute_client
376 if image_client is None:
377 image_client = self.image_client
378 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900379 name = data_utils.rand_name('scenario-snapshot-')
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900380 LOG.debug("Creating a snapshot image for server: %s", server.name)
381 image_id = compute_client.servers.create_image(server, name)
382 self.addCleanup(image_client.images.delete, image_id)
383 self.status_timeout(image_client.images, image_id, 'active')
384 snapshot_image = image_client.images.get(image_id)
Chang Bo Guofc77e932013-09-16 17:38:26 -0700385 self.assertEqual(name, snapshot_image.name)
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900386 LOG.debug("Created snapshot image %s for server %s",
387 snapshot_image.name, server.name)
388 return snapshot_image
389
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900390 def create_keypair(self, client=None, name=None):
391 if client is None:
392 client = self.compute_client
393 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900394 name = data_utils.rand_name('scenario-keypair-')
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900395 keypair = client.keypairs.create(name)
396 self.assertEqual(keypair.name, name)
397 self.set_resource(name, keypair)
398 return keypair
399
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900400 def get_remote_client(self, server_or_ip, username=None, private_key=None):
llg821243b20502014-02-22 10:32:49 +0800401 if isinstance(server_or_ip, six.string_types):
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900402 ip = server_or_ip
403 else:
Matthew Treinish6c072292014-01-29 19:15:52 +0000404 network_name_for_ssh = CONF.compute.network_for_ssh
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900405 ip = server_or_ip.networks[network_name_for_ssh][0]
406 if username is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000407 username = CONF.scenario.ssh_user
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900408 if private_key is None:
409 private_key = self.keypair.private_key
Yair Fried3960c4d2014-05-07 15:20:30 +0300410 linux_client = remote_client.RemoteClient(ip, username,
411 pkey=private_key)
412 try:
413 linux_client.validate_authentication()
414 except exceptions.SSHTimeout:
415 LOG.exception('ssh connection to %s failed' % ip)
416 debug.log_net_debug()
417 raise
418
419 return linux_client
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900420
Nachi Ueno95b41282014-01-15 06:54:21 -0800421 def _log_console_output(self, servers=None):
422 if not servers:
423 servers = self.compute_client.servers.list()
424 for server in servers:
425 LOG.debug('Console output for %s', server.id)
426 LOG.debug(server.get_console_output())
427
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900428 def wait_for_volume_status(self, status):
429 volume_id = self.volume.id
430 self.status_timeout(
431 self.volume_client.volumes, volume_id, status)
432
433 def _image_create(self, name, fmt, path, properties={}):
434 name = data_utils.rand_name('%s-' % name)
435 image_file = open(path, 'rb')
436 self.addCleanup(image_file.close)
437 params = {
438 'name': name,
439 'container_format': fmt,
440 'disk_format': fmt,
Aaron Rosenc7720622014-05-20 10:38:10 -0700441 'is_public': 'False',
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900442 }
443 params.update(properties)
444 image = self.image_client.images.create(**params)
445 self.addCleanup(self.image_client.images.delete, image)
446 self.assertEqual("queued", image.status)
447 image.update(data=image_file)
448 return image.id
449
450 def glance_image_create(self):
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900451 qcow2_img_path = (CONF.scenario.img_dir + "/" +
452 CONF.scenario.qcow2_img_file)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900453 aki_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.aki_img_file
454 ari_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ari_img_file
455 ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900456 LOG.debug("paths: img: %s, ami: %s, ari: %s, aki: %s"
457 % (qcow2_img_path, ami_img_path, ari_img_path, aki_img_path))
458 try:
459 self.image = self._image_create('scenario-img',
460 'bare',
461 qcow2_img_path,
462 properties={'disk_format':
463 'qcow2'})
464 except IOError:
Masayuki Igawa188fc002014-02-23 06:42:44 +0900465 LOG.debug("A qcow2 image was not found. Try to get a uec image.")
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900466 kernel = self._image_create('scenario-aki', 'aki', aki_img_path)
467 ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path)
468 properties = {
469 'properties': {'kernel_id': kernel, 'ramdisk_id': ramdisk}
470 }
471 self.image = self._image_create('scenario-ami', 'ami',
472 path=ami_img_path,
473 properties=properties)
474 LOG.debug("image:%s" % self.image)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900475
Sean Dague6dbc6da2013-05-08 17:49:46 -0400476
David Shrewsbury06f7f8a2014-05-20 13:55:57 -0400477# power/provision states as of icehouse
478class BaremetalPowerStates(object):
479 """Possible power states of an Ironic node."""
480 POWER_ON = 'power on'
481 POWER_OFF = 'power off'
482 REBOOT = 'rebooting'
483 SUSPEND = 'suspended'
484
485
486class BaremetalProvisionStates(object):
487 """Possible provision states of an Ironic node."""
488 NOSTATE = None
489 INIT = 'initializing'
490 ACTIVE = 'active'
491 BUILDING = 'building'
492 DEPLOYWAIT = 'wait call-back'
493 DEPLOYING = 'deploying'
494 DEPLOYFAIL = 'deploy failed'
495 DEPLOYDONE = 'deploy complete'
496 DELETING = 'deleting'
497 DELETED = 'deleted'
498 ERROR = 'error'
499
500
Adam Gandelman4a48a602014-03-20 18:23:18 -0700501class BaremetalScenarioTest(OfficialClientTest):
502 @classmethod
503 def setUpClass(cls):
504 super(BaremetalScenarioTest, cls).setUpClass()
505
506 if (not CONF.service_available.ironic or
507 not CONF.baremetal.driver_enabled):
508 msg = 'Ironic not available or Ironic compute driver not enabled'
509 raise cls.skipException(msg)
510
511 # use an admin client manager for baremetal client
Adam Gandelmanacc13e62014-05-08 11:12:47 -0700512 admin_creds = cls.admin_credentials()
513 manager = clients.OfficialClientManager(credentials=admin_creds)
Adam Gandelman4a48a602014-03-20 18:23:18 -0700514 cls.baremetal_client = manager.baremetal_client
515
516 # allow any issues obtaining the node list to raise early
517 cls.baremetal_client.node.list()
518
519 def _node_state_timeout(self, node_id, state_attr,
520 target_states, timeout=10, interval=1):
521 if not isinstance(target_states, list):
522 target_states = [target_states]
523
524 def check_state():
525 node = self.get_node(node_id=node_id)
526 if getattr(node, state_attr) in target_states:
527 return True
528 return False
529
530 if not tempest.test.call_until_true(
531 check_state, timeout, interval):
532 msg = ("Timed out waiting for node %s to reach %s state(s) %s" %
533 (node_id, state_attr, target_states))
534 raise exceptions.TimeoutException(msg)
535
536 def wait_provisioning_state(self, node_id, state, timeout):
537 self._node_state_timeout(
538 node_id=node_id, state_attr='provision_state',
539 target_states=state, timeout=timeout)
540
541 def wait_power_state(self, node_id, state):
542 self._node_state_timeout(
543 node_id=node_id, state_attr='power_state',
544 target_states=state, timeout=CONF.baremetal.power_timeout)
545
546 def wait_node(self, instance_id):
547 """Waits for a node to be associated with instance_id."""
Zhi Kun Liu4a8d1ea2014-04-15 22:08:21 -0500548 from ironicclient import exc as ironic_exceptions
549
Adam Gandelman4a48a602014-03-20 18:23:18 -0700550 def _get_node():
551 node = None
552 try:
553 node = self.get_node(instance_id=instance_id)
554 except ironic_exceptions.HTTPNotFound:
555 pass
556 return node is not None
557
558 if not tempest.test.call_until_true(
559 _get_node, CONF.baremetal.association_timeout, 1):
560 msg = ('Timed out waiting to get Ironic node by instance id %s'
561 % instance_id)
562 raise exceptions.TimeoutException(msg)
563
564 def get_node(self, node_id=None, instance_id=None):
565 if node_id:
566 return self.baremetal_client.node.get(node_id)
567 elif instance_id:
568 return self.baremetal_client.node.get_by_instance_uuid(instance_id)
569
570 def get_ports(self, node_id):
571 ports = []
572 for port in self.baremetal_client.node.list_ports(node_id):
573 ports.append(self.baremetal_client.port.get(port.uuid))
574 return ports
575
David Shrewsbury06f7f8a2014-05-20 13:55:57 -0400576 def add_keypair(self):
577 self.keypair = self.create_keypair()
578
579 def verify_connectivity(self, ip=None):
580 if ip:
581 dest = self.get_remote_client(ip)
582 else:
583 dest = self.get_remote_client(self.instance)
584 dest.validate_authentication()
585
586 def boot_instance(self):
587 create_kwargs = {
588 'key_name': self.keypair.id
589 }
590 self.instance = self.create_server(
591 wait=False, create_kwargs=create_kwargs)
592
593 self.set_resource('instance', self.instance)
594
595 self.wait_node(self.instance.id)
596 self.node = self.get_node(instance_id=self.instance.id)
597
598 self.wait_power_state(self.node.uuid, BaremetalPowerStates.POWER_ON)
599
600 self.wait_provisioning_state(
601 self.node.uuid,
602 [BaremetalProvisionStates.DEPLOYWAIT,
603 BaremetalProvisionStates.ACTIVE],
604 timeout=15)
605
606 self.wait_provisioning_state(self.node.uuid,
607 BaremetalProvisionStates.ACTIVE,
608 timeout=CONF.baremetal.active_timeout)
609
610 self.status_timeout(
611 self.compute_client.servers, self.instance.id, 'ACTIVE')
612
613 self.node = self.get_node(instance_id=self.instance.id)
614 self.instance = self.compute_client.servers.get(self.instance.id)
615
616 def terminate_instance(self):
617 self.instance.delete()
618 self.remove_resource('instance')
619 self.wait_power_state(self.node.uuid, BaremetalPowerStates.POWER_OFF)
620 self.wait_provisioning_state(
621 self.node.uuid,
622 BaremetalProvisionStates.NOSTATE,
623 timeout=CONF.baremetal.unprovision_timeout)
624
Adam Gandelman4a48a602014-03-20 18:23:18 -0700625
Sean Dague6dbc6da2013-05-08 17:49:46 -0400626class NetworkScenarioTest(OfficialClientTest):
627 """
628 Base class for network scenario tests
629 """
630
631 @classmethod
632 def check_preconditions(cls):
Matthew Treinish6c072292014-01-29 19:15:52 +0000633 if (CONF.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400634 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200635 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400636 try:
637 cls.network_client.list_networks()
638 except exc.EndpointNotFound:
639 cls.enabled = False
640 raise
641 else:
642 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400643 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400644 raise cls.skipException(msg)
645
646 @classmethod
647 def setUpClass(cls):
648 super(NetworkScenarioTest, cls).setUpClass()
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000649 cls.tenant_id = cls.manager.identity_client.tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400650
Sean Dague6dbc6da2013-05-08 17:49:46 -0400651 def _create_network(self, tenant_id, namestart='network-smoke-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900652 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400653 body = dict(
654 network=dict(
655 name=name,
656 tenant_id=tenant_id,
657 ),
658 )
659 result = self.network_client.create_network(body=body)
660 network = net_common.DeletableNetwork(client=self.network_client,
661 **result['network'])
662 self.assertEqual(network.name, name)
663 self.set_resource(name, network)
664 return network
665
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200666 def _list_networks(self, **kwargs):
667 nets = self.network_client.list_networks(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400668 return nets['networks']
669
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200670 def _list_subnets(self, **kwargs):
671 subnets = self.network_client.list_subnets(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400672 return subnets['subnets']
673
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200674 def _list_routers(self, **kwargs):
675 routers = self.network_client.list_routers(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400676 return routers['routers']
677
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200678 def _list_ports(self, **kwargs):
679 ports = self.network_client.list_ports(**kwargs)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000680 return ports['ports']
681
682 def _get_tenant_own_network_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200683 nets = self._list_networks(tenant_id=tenant_id)
684 return len(nets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000685
686 def _get_tenant_own_subnet_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200687 subnets = self._list_subnets(tenant_id=tenant_id)
688 return len(subnets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000689
690 def _get_tenant_own_port_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200691 ports = self._list_ports(tenant_id=tenant_id)
692 return len(ports)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000693
Yair Fried3097dc12014-01-26 08:46:43 +0200694 def _create_subnet(self, network, namestart='subnet-smoke-', **kwargs):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400695 """
696 Create a subnet for the given network within the cidr block
697 configured for tenant networks.
698 """
Attila Fazekase857bd62013-10-21 21:02:44 +0200699
700 def cidr_in_use(cidr, tenant_id):
701 """
702 :return True if subnet with cidr already exist in tenant
703 False else
704 """
705 cidr_in_use = self._list_subnets(tenant_id=tenant_id, cidr=cidr)
706 return len(cidr_in_use) != 0
707
Matthew Treinish6c072292014-01-29 19:15:52 +0000708 tenant_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400709 result = None
710 # Repeatedly attempt subnet creation with sequential cidr
711 # blocks until an unallocated block is found.
Matthew Treinish6c072292014-01-29 19:15:52 +0000712 for subnet_cidr in tenant_cidr.subnet(
713 CONF.network.tenant_network_mask_bits):
Attila Fazekase857bd62013-10-21 21:02:44 +0200714 str_cidr = str(subnet_cidr)
715 if cidr_in_use(str_cidr, tenant_id=network.tenant_id):
716 continue
717
Sean Dague6dbc6da2013-05-08 17:49:46 -0400718 body = dict(
719 subnet=dict(
Attila Fazekase857bd62013-10-21 21:02:44 +0200720 name=data_utils.rand_name(namestart),
Sean Dague6dbc6da2013-05-08 17:49:46 -0400721 ip_version=4,
722 network_id=network.id,
723 tenant_id=network.tenant_id,
Attila Fazekase857bd62013-10-21 21:02:44 +0200724 cidr=str_cidr,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400725 ),
726 )
Yair Fried3097dc12014-01-26 08:46:43 +0200727 body['subnet'].update(kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400728 try:
729 result = self.network_client.create_subnet(body=body)
730 break
Mark McClainf2982e82013-07-06 17:48:03 -0400731 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400732 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
733 if not is_overlapping_cidr:
734 raise
735 self.assertIsNotNone(result, 'Unable to allocate tenant network')
736 subnet = net_common.DeletableSubnet(client=self.network_client,
737 **result['subnet'])
Attila Fazekase857bd62013-10-21 21:02:44 +0200738 self.assertEqual(subnet.cidr, str_cidr)
Masayuki Igawa259c1132013-10-31 17:48:44 +0900739 self.set_resource(data_utils.rand_name(namestart), subnet)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400740 return subnet
741
742 def _create_port(self, network, namestart='port-quotatest-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900743 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400744 body = dict(
745 port=dict(name=name,
746 network_id=network.id,
747 tenant_id=network.tenant_id))
748 result = self.network_client.create_port(body=body)
749 self.assertIsNotNone(result, 'Unable to allocate port')
750 port = net_common.DeletablePort(client=self.network_client,
751 **result['port'])
752 self.set_resource(name, port)
753 return port
754
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200755 def _get_server_port_id(self, server, ip_addr=None):
756 ports = self._list_ports(device_id=server.id, fixed_ip=ip_addr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400757 self.assertEqual(len(ports), 1,
758 "Unable to determine which port to target.")
Yair Fried05db2522013-11-18 11:02:10 +0200759 return ports[0]['id']
760
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200761 def _create_floating_ip(self, thing, external_network_id, port_id=None):
762 if not port_id:
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400763 port_id = self._get_server_port_id(thing)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400764 body = dict(
765 floatingip=dict(
766 floating_network_id=external_network_id,
767 port_id=port_id,
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400768 tenant_id=thing.tenant_id,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400769 )
770 )
771 result = self.network_client.create_floatingip(body=body)
772 floating_ip = net_common.DeletableFloatingIp(
773 client=self.network_client,
774 **result['floatingip'])
Masayuki Igawa259c1132013-10-31 17:48:44 +0900775 self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400776 return floating_ip
777
Yair Fried05db2522013-11-18 11:02:10 +0200778 def _associate_floating_ip(self, floating_ip, server):
779 port_id = self._get_server_port_id(server)
780 floating_ip.update(port_id=port_id)
781 self.assertEqual(port_id, floating_ip.port_id)
782 return floating_ip
783
Yair Fried9a551c42013-12-15 14:59:34 +0200784 def _disassociate_floating_ip(self, floating_ip):
785 """
786 :param floating_ip: type DeletableFloatingIp
787 """
788 floating_ip.update(port_id=None)
llg8212e4cd3922014-02-15 12:14:21 +0800789 self.assertIsNone(floating_ip.port_id)
Yair Fried9a551c42013-12-15 14:59:34 +0200790 return floating_ip
791
792 def _ping_ip_address(self, ip_address, should_succeed=True):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400793 cmd = ['ping', '-c1', '-w1', ip_address]
794
795 def ping():
796 proc = subprocess.Popen(cmd,
797 stdout=subprocess.PIPE,
798 stderr=subprocess.PIPE)
799 proc.wait()
Yair Fried9a551c42013-12-15 14:59:34 +0200800 return (proc.returncode == 0) == should_succeed
Sean Dague6dbc6da2013-05-08 17:49:46 -0400801
Nachi Ueno6d580be2013-07-24 10:58:11 -0700802 return tempest.test.call_until_true(
Matthew Treinish6c072292014-01-29 19:15:52 +0000803 ping, CONF.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000804
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400805 def _create_pool(self, lb_method, protocol, subnet_id):
806 """Wrapper utility that returns a test pool."""
807 name = data_utils.rand_name('pool-')
808 body = {
809 "pool": {
810 "protocol": protocol,
811 "name": name,
812 "subnet_id": subnet_id,
813 "lb_method": lb_method
814 }
815 }
816 resp = self.network_client.create_pool(body=body)
817 pool = net_common.DeletablePool(client=self.network_client,
818 **resp['pool'])
819 self.assertEqual(pool['name'], name)
820 self.set_resource(name, pool)
821 return pool
822
823 def _create_member(self, address, protocol_port, pool_id):
824 """Wrapper utility that returns a test member."""
825 body = {
826 "member": {
827 "protocol_port": protocol_port,
828 "pool_id": pool_id,
829 "address": address
830 }
831 }
832 resp = self.network_client.create_member(body)
833 member = net_common.DeletableMember(client=self.network_client,
834 **resp['member'])
835 self.set_resource(data_utils.rand_name('member-'), member)
836 return member
837
838 def _create_vip(self, protocol, protocol_port, subnet_id, pool_id):
839 """Wrapper utility that returns a test vip."""
840 name = data_utils.rand_name('vip-')
841 body = {
842 "vip": {
843 "protocol": protocol,
844 "name": name,
845 "subnet_id": subnet_id,
846 "pool_id": pool_id,
847 "protocol_port": protocol_port
848 }
849 }
850 resp = self.network_client.create_vip(body)
851 vip = net_common.DeletableVip(client=self.network_client,
852 **resp['vip'])
853 self.assertEqual(vip['name'], name)
854 self.set_resource(name, vip)
855 return vip
856
Yair Fried9a551c42013-12-15 14:59:34 +0200857 def _check_vm_connectivity(self, ip_address,
858 username=None,
859 private_key=None,
860 should_connect=True):
861 """
862 :param ip_address: server to test against
863 :param username: server's ssh username
864 :param private_key: server's ssh private key to be used
865 :param should_connect: True/False indicates positive/negative test
866 positive - attempt ping and ssh
867 negative - attempt ping and fail if succeed
868
869 :raises: AssertError if the result of the connectivity check does
870 not match the value of the should_connect param
871 """
872 if should_connect:
873 msg = "Timed out waiting for %s to become reachable" % ip_address
874 else:
875 msg = "ip address %s is reachable" % ip_address
876 self.assertTrue(self._ping_ip_address(ip_address,
877 should_succeed=should_connect),
878 msg=msg)
879 if should_connect:
880 # no need to check ssh for negative connectivity
Yair Fried3960c4d2014-05-07 15:20:30 +0300881 self.get_remote_client(ip_address, username, private_key)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200882
Matt Riedemann343305f2014-05-27 09:55:03 -0700883 def _check_public_network_connectivity(self, ip_address, username,
884 private_key, should_connect=True,
885 msg=None, servers=None):
886 # The target login is assumed to have been configured for
887 # key-based authentication by cloud-init.
888 LOG.debug('checking network connections to IP %s with user: %s' %
889 (ip_address, username))
890 try:
891 self._check_vm_connectivity(ip_address,
892 username,
893 private_key,
894 should_connect=should_connect)
Yair Fried3960c4d2014-05-07 15:20:30 +0300895 except Exception as e:
Matt Riedemann343305f2014-05-27 09:55:03 -0700896 ex_msg = 'Public network connectivity check failed'
897 if msg:
898 ex_msg += ": " + msg
899 LOG.exception(ex_msg)
900 self._log_console_output(servers)
Yair Fried3960c4d2014-05-07 15:20:30 +0300901 # network debug is called as part of ssh init
902 if not isinstance(e, exceptions.SSHTimeout):
903 debug.log_net_debug()
Matt Riedemann343305f2014-05-27 09:55:03 -0700904 raise
905
Matt Riedemann2d005be2014-05-27 10:52:35 -0700906 def _check_tenant_network_connectivity(self, server,
907 username,
908 private_key,
909 should_connect=True,
910 servers_for_debug=None):
911 if not CONF.network.tenant_networks_reachable:
912 msg = 'Tenant networks not configured to be reachable.'
913 LOG.info(msg)
914 return
915 # The target login is assumed to have been configured for
916 # key-based authentication by cloud-init.
917 try:
918 for net_name, ip_addresses in server.networks.iteritems():
919 for ip_address in ip_addresses:
920 self._check_vm_connectivity(ip_address,
921 username,
922 private_key,
923 should_connect=should_connect)
Yair Fried3960c4d2014-05-07 15:20:30 +0300924 except Exception as e:
Matt Riedemann2d005be2014-05-27 10:52:35 -0700925 LOG.exception('Tenant network connectivity check failed')
926 self._log_console_output(servers_for_debug)
Yair Fried3960c4d2014-05-07 15:20:30 +0300927 # network debug is called as part of ssh init
928 if not isinstance(e, exceptions.SSHTimeout):
929 debug.log_net_debug()
Matt Riedemann2d005be2014-05-27 10:52:35 -0700930 raise
931
Yair Fried3097dc12014-01-26 08:46:43 +0200932 def _check_remote_connectivity(self, source, dest, should_succeed=True):
933 """
934 check ping server via source ssh connection
935
936 :param source: RemoteClient: an ssh connection from which to ping
937 :param dest: and IP to ping against
938 :param should_succeed: boolean should ping succeed or not
939 :returns: boolean -- should_succeed == ping
940 :returns: ping is false if ping failed
941 """
942 def ping_remote():
943 try:
944 source.ping_host(dest)
945 except exceptions.SSHExecCommandFailed:
Matthew Treinishc4131d82014-05-27 11:59:28 -0400946 LOG.warn('Failed to ping IP: %s via a ssh connection from: %s.'
947 % (dest, source.ssh_client.host))
Yair Fried3097dc12014-01-26 08:46:43 +0200948 return not should_succeed
949 return should_succeed
950
951 return tempest.test.call_until_true(ping_remote,
952 CONF.compute.ping_timeout,
953 1)
954
Yair Friedeb69f3f2013-10-10 13:18:16 +0300955 def _create_security_group_neutron(self, tenant_id, client=None,
956 namestart='secgroup-smoke-'):
957 if client is None:
958 client = self.network_client
959 secgroup = self._create_empty_security_group(namestart=namestart,
960 client=client,
961 tenant_id=tenant_id)
962
963 # Add rules to the security group
964 rules = self._create_loginable_secgroup_rule_neutron(secgroup=secgroup)
965 for rule in rules:
966 self.assertEqual(tenant_id, rule.tenant_id)
967 self.assertEqual(secgroup.id, rule.security_group_id)
968 return secgroup
969
970 def _create_empty_security_group(self, tenant_id, client=None,
971 namestart='secgroup-smoke-'):
972 """Create a security group without rules.
973
974 Default rules will be created:
975 - IPv4 egress to any
976 - IPv6 egress to any
977
978 :param tenant_id: secgroup will be created in this tenant
979 :returns: DeletableSecurityGroup -- containing the secgroup created
980 """
981 if client is None:
982 client = self.network_client
983 sg_name = data_utils.rand_name(namestart)
984 sg_desc = sg_name + " description"
985 sg_dict = dict(name=sg_name,
986 description=sg_desc)
987 sg_dict['tenant_id'] = tenant_id
988 body = dict(security_group=sg_dict)
989 result = client.create_security_group(body=body)
990 secgroup = net_common.DeletableSecurityGroup(
991 client=client,
992 **result['security_group']
993 )
994 self.assertEqual(secgroup.name, sg_name)
995 self.assertEqual(tenant_id, secgroup.tenant_id)
996 self.assertEqual(secgroup.description, sg_desc)
997 self.set_resource(sg_name, secgroup)
998 return secgroup
999
1000 def _default_security_group(self, tenant_id, client=None):
1001 """Get default secgroup for given tenant_id.
1002
1003 :returns: DeletableSecurityGroup -- default secgroup for given tenant
1004 """
1005 if client is None:
1006 client = self.network_client
1007 sgs = [
1008 sg for sg in client.list_security_groups().values()[0]
1009 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
1010 ]
1011 msg = "No default security group for tenant %s." % (tenant_id)
1012 self.assertTrue(len(sgs) > 0, msg)
1013 if len(sgs) > 1:
1014 msg = "Found %d default security groups" % len(sgs)
1015 raise exc.NeutronClientNoUniqueMatch(msg=msg)
1016 return net_common.DeletableSecurityGroup(client=client,
1017 **sgs[0])
1018
1019 def _create_security_group_rule(self, client=None, secgroup=None,
1020 tenant_id=None, **kwargs):
1021 """Create a rule from a dictionary of rule parameters.
1022
1023 Create a rule in a secgroup. if secgroup not defined will search for
1024 default secgroup in tenant_id.
1025
1026 :param secgroup: type DeletableSecurityGroup.
1027 :param secgroup_id: search for secgroup by id
1028 default -- choose default secgroup for given tenant_id
1029 :param tenant_id: if secgroup not passed -- the tenant in which to
1030 search for default secgroup
1031 :param kwargs: a dictionary containing rule parameters:
1032 for example, to allow incoming ssh:
1033 rule = {
1034 direction: 'ingress'
1035 protocol:'tcp',
1036 port_range_min: 22,
1037 port_range_max: 22
1038 }
1039 """
1040 if client is None:
1041 client = self.network_client
1042 if secgroup is None:
1043 secgroup = self._default_security_group(tenant_id)
1044
1045 ruleset = dict(security_group_id=secgroup.id,
1046 tenant_id=secgroup.tenant_id,
1047 )
1048 ruleset.update(kwargs)
1049
1050 body = dict(security_group_rule=dict(ruleset))
1051 sg_rule = client.create_security_group_rule(body=body)
1052 sg_rule = net_common.DeletableSecurityGroupRule(
1053 client=client,
1054 **sg_rule['security_group_rule']
1055 )
Yair Friedeb69f3f2013-10-10 13:18:16 +03001056 self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
1057 self.assertEqual(secgroup.id, sg_rule.security_group_id)
1058
1059 return sg_rule
1060
1061 def _create_loginable_secgroup_rule_neutron(self, client=None,
1062 secgroup=None):
1063 """These rules are intended to permit inbound ssh and icmp
1064 traffic from all sources, so no group_id is provided.
1065 Setting a group_id would only permit traffic from ports
1066 belonging to the same security group.
1067 """
1068
1069 if client is None:
1070 client = self.network_client
1071 rules = []
1072 rulesets = [
1073 dict(
1074 # ssh
1075 protocol='tcp',
1076 port_range_min=22,
1077 port_range_max=22,
1078 ),
1079 dict(
1080 # ping
1081 protocol='icmp',
1082 )
1083 ]
1084 for ruleset in rulesets:
1085 for r_direction in ['ingress', 'egress']:
1086 ruleset['direction'] = r_direction
1087 try:
1088 sg_rule = self._create_security_group_rule(
1089 client=client, secgroup=secgroup, **ruleset)
1090 except exc.NeutronClientException as ex:
1091 # if rule already exist - skip rule and continue
1092 if not (ex.status_code is 409 and 'Security group rule'
1093 ' already exists' in ex.message):
1094 raise ex
1095 else:
1096 self.assertEqual(r_direction, sg_rule.direction)
1097 rules.append(sg_rule)
1098
1099 return rules
1100
Yair Fried5f670ab2013-12-09 09:26:51 +02001101 def _ssh_to_server(self, server, private_key):
Matthew Treinish6c072292014-01-29 19:15:52 +00001102 ssh_login = CONF.compute.image_ssh_user
Yair Fried5f670ab2013-12-09 09:26:51 +02001103 return self.get_remote_client(server,
1104 username=ssh_login,
1105 private_key=private_key)
1106
Yuiko Takada7f4b1b32013-11-20 08:06:26 +00001107 def _show_quota_network(self, tenant_id):
1108 quota = self.network_client.show_quota(tenant_id)
1109 return quota['quota']['network']
1110
1111 def _show_quota_subnet(self, tenant_id):
1112 quota = self.network_client.show_quota(tenant_id)
1113 return quota['quota']['subnet']
1114
1115 def _show_quota_port(self, tenant_id):
1116 quota = self.network_client.show_quota(tenant_id)
1117 return quota['quota']['port']
1118
Yair Fried4d7efa62013-11-17 17:12:29 +02001119 def _get_router(self, tenant_id):
1120 """Retrieve a router for the given tenant id.
1121
1122 If a public router has been configured, it will be returned.
1123
1124 If a public router has not been configured, but a public
1125 network has, a tenant router will be created and returned that
1126 routes traffic to the public network.
1127 """
Matthew Treinish6c072292014-01-29 19:15:52 +00001128 router_id = CONF.network.public_router_id
1129 network_id = CONF.network.public_network_id
Yair Fried4d7efa62013-11-17 17:12:29 +02001130 if router_id:
1131 result = self.network_client.show_router(router_id)
1132 return net_common.AttributeDict(**result['router'])
1133 elif network_id:
1134 router = self._create_router(tenant_id)
1135 router.add_gateway(network_id)
1136 return router
1137 else:
1138 raise Exception("Neither of 'public_router_id' or "
1139 "'public_network_id' has been defined.")
1140
1141 def _create_router(self, tenant_id, namestart='router-smoke-'):
1142 name = data_utils.rand_name(namestart)
1143 body = dict(
1144 router=dict(
1145 name=name,
1146 admin_state_up=True,
1147 tenant_id=tenant_id,
1148 ),
1149 )
1150 result = self.network_client.create_router(body=body)
1151 router = net_common.DeletableRouter(client=self.network_client,
1152 **result['router'])
1153 self.assertEqual(router.name, name)
1154 self.set_resource(name, router)
1155 return router
1156
1157 def _create_networks(self, tenant_id=None):
1158 """Create a network with a subnet connected to a router.
1159
1160 :returns: network, subnet, router
1161 """
1162 if tenant_id is None:
1163 tenant_id = self.tenant_id
1164 network = self._create_network(tenant_id)
1165 router = self._get_router(tenant_id)
1166 subnet = self._create_subnet(network)
1167 subnet.add_to_router(router.id)
Yair Fried4d7efa62013-11-17 17:12:29 +02001168 return network, subnet, router
1169
Steve Bakerdd7c6ce2013-06-24 14:46:47 +12001170
1171class OrchestrationScenarioTest(OfficialClientTest):
1172 """
1173 Base class for orchestration scenario tests
1174 """
1175
1176 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -07001177 def setUpClass(cls):
1178 super(OrchestrationScenarioTest, cls).setUpClass()
Matthew Treinish6c072292014-01-29 19:15:52 +00001179 if not CONF.service_available.heat:
Matt Riedemann11c5b642013-08-24 08:45:38 -07001180 raise cls.skipException("Heat support is required")
1181
1182 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +12001183 def credentials(cls):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +00001184 admin_creds = auth.get_default_credentials('identity_admin')
1185 creds = auth.get_default_credentials('user')
1186 admin_creds.tenant_name = creds.tenant_name
1187 return admin_creds
Steve Bakerdd7c6ce2013-06-24 14:46:47 +12001188
1189 def _load_template(self, base_file, file_name):
1190 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
1191 file_name)
1192 with open(filepath) as f:
1193 return f.read()
1194
1195 @classmethod
1196 def _stack_rand_name(cls):
Masayuki Igawa259c1132013-10-31 17:48:44 +09001197 return data_utils.rand_name(cls.__name__ + '-')
Steve Baker80252da2013-09-25 13:29:10 +12001198
1199 @classmethod
1200 def _get_default_network(cls):
1201 networks = cls.network_client.list_networks()
1202 for net in networks['networks']:
Matthew Treinish6c072292014-01-29 19:15:52 +00001203 if net['name'] == CONF.compute.fixed_network_name:
Steve Baker80252da2013-09-25 13:29:10 +12001204 return net
Steve Baker22c16602014-05-05 13:34:19 +12001205
1206 @staticmethod
1207 def _stack_output(stack, output_key):
1208 """Return a stack output value for a given key."""
1209 return next((o['output_value'] for o in stack.outputs
1210 if o['output_key'] == output_key), None)
1211
1212 def _ping_ip_address(self, ip_address, should_succeed=True):
1213 cmd = ['ping', '-c1', '-w1', ip_address]
1214
1215 def ping():
1216 proc = subprocess.Popen(cmd,
1217 stdout=subprocess.PIPE,
1218 stderr=subprocess.PIPE)
1219 proc.wait()
1220 return (proc.returncode == 0) == should_succeed
1221
1222 return tempest.test.call_until_true(
1223 ping, CONF.orchestration.build_timeout, 1)
1224
1225 def _wait_for_resource_status(self, stack_identifier, resource_name,
1226 status, failure_pattern='^.*_FAILED$'):
1227 """Waits for a Resource to reach a given status."""
1228 fail_regexp = re.compile(failure_pattern)
1229 build_timeout = CONF.orchestration.build_timeout
1230 build_interval = CONF.orchestration.build_interval
1231
1232 start = timeutils.utcnow()
1233 while timeutils.delta_seconds(start,
1234 timeutils.utcnow()) < build_timeout:
1235 try:
1236 res = self.client.resources.get(
1237 stack_identifier, resource_name)
1238 except heat_exceptions.HTTPNotFound:
1239 # ignore this, as the resource may not have
1240 # been created yet
1241 pass
1242 else:
1243 if res.resource_status == status:
1244 return
1245 if fail_regexp.search(res.resource_status):
1246 raise exceptions.StackResourceBuildErrorException(
1247 resource_name=res.resource_name,
1248 stack_identifier=stack_identifier,
1249 resource_status=res.resource_status,
1250 resource_status_reason=res.resource_status_reason)
1251 time.sleep(build_interval)
1252
1253 message = ('Resource %s failed to reach %s status within '
1254 'the required time (%s s).' %
1255 (res.resource_name, status, build_timeout))
1256 raise exceptions.TimeoutException(message)
1257
1258 def _wait_for_stack_status(self, stack_identifier, status,
1259 failure_pattern='^.*_FAILED$'):
1260 """
1261 Waits for a Stack to reach a given status.
1262
1263 Note this compares the full $action_$status, e.g
1264 CREATE_COMPLETE, not just COMPLETE which is exposed
1265 via the status property of Stack in heatclient
1266 """
1267 fail_regexp = re.compile(failure_pattern)
1268 build_timeout = CONF.orchestration.build_timeout
1269 build_interval = CONF.orchestration.build_interval
1270
1271 start = timeutils.utcnow()
1272 while timeutils.delta_seconds(start,
1273 timeutils.utcnow()) < build_timeout:
1274 try:
1275 stack = self.client.stacks.get(stack_identifier)
1276 except heat_exceptions.HTTPNotFound:
1277 # ignore this, as the stackource may not have
1278 # been created yet
1279 pass
1280 else:
1281 if stack.stack_status == status:
1282 return
1283 if fail_regexp.search(stack.stack_status):
1284 raise exceptions.StackBuildErrorException(
1285 stack_identifier=stack_identifier,
1286 stack_status=stack.stack_status,
1287 stack_status_reason=stack.stack_status_reason)
1288 time.sleep(build_interval)
1289
1290 message = ('Stack %s failed to reach %s status within '
1291 'the required time (%s s).' %
1292 (stack.stack_name, status, build_timeout))
1293 raise exceptions.TimeoutException(message)
1294
1295 def _stack_delete(self, stack_identifier):
1296 try:
1297 self.client.stacks.delete(stack_identifier)
1298 except heat_exceptions.HTTPNotFound:
1299 pass