blob: c836227c4213f41ea1b97deb81a1525bf55c0b05 [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
Masayuki Igawa4ded9f02014-02-17 15:05:59 +0900410 return remote_client.RemoteClient(ip, username, pkey=private_key)
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900411
Nachi Ueno95b41282014-01-15 06:54:21 -0800412 def _log_console_output(self, servers=None):
413 if not servers:
414 servers = self.compute_client.servers.list()
415 for server in servers:
416 LOG.debug('Console output for %s', server.id)
417 LOG.debug(server.get_console_output())
418
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900419 def wait_for_volume_status(self, status):
420 volume_id = self.volume.id
421 self.status_timeout(
422 self.volume_client.volumes, volume_id, status)
423
424 def _image_create(self, name, fmt, path, properties={}):
425 name = data_utils.rand_name('%s-' % name)
426 image_file = open(path, 'rb')
427 self.addCleanup(image_file.close)
428 params = {
429 'name': name,
430 'container_format': fmt,
431 'disk_format': fmt,
Aaron Rosenc7720622014-05-20 10:38:10 -0700432 'is_public': 'False',
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900433 }
434 params.update(properties)
435 image = self.image_client.images.create(**params)
436 self.addCleanup(self.image_client.images.delete, image)
437 self.assertEqual("queued", image.status)
438 image.update(data=image_file)
439 return image.id
440
441 def glance_image_create(self):
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900442 qcow2_img_path = (CONF.scenario.img_dir + "/" +
443 CONF.scenario.qcow2_img_file)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900444 aki_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.aki_img_file
445 ari_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ari_img_file
446 ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900447 LOG.debug("paths: img: %s, ami: %s, ari: %s, aki: %s"
448 % (qcow2_img_path, ami_img_path, ari_img_path, aki_img_path))
449 try:
450 self.image = self._image_create('scenario-img',
451 'bare',
452 qcow2_img_path,
453 properties={'disk_format':
454 'qcow2'})
455 except IOError:
Masayuki Igawa188fc002014-02-23 06:42:44 +0900456 LOG.debug("A qcow2 image was not found. Try to get a uec image.")
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900457 kernel = self._image_create('scenario-aki', 'aki', aki_img_path)
458 ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path)
459 properties = {
460 'properties': {'kernel_id': kernel, 'ramdisk_id': ramdisk}
461 }
462 self.image = self._image_create('scenario-ami', 'ami',
463 path=ami_img_path,
464 properties=properties)
465 LOG.debug("image:%s" % self.image)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900466
Sean Dague6dbc6da2013-05-08 17:49:46 -0400467
David Shrewsbury06f7f8a2014-05-20 13:55:57 -0400468# power/provision states as of icehouse
469class BaremetalPowerStates(object):
470 """Possible power states of an Ironic node."""
471 POWER_ON = 'power on'
472 POWER_OFF = 'power off'
473 REBOOT = 'rebooting'
474 SUSPEND = 'suspended'
475
476
477class BaremetalProvisionStates(object):
478 """Possible provision states of an Ironic node."""
479 NOSTATE = None
480 INIT = 'initializing'
481 ACTIVE = 'active'
482 BUILDING = 'building'
483 DEPLOYWAIT = 'wait call-back'
484 DEPLOYING = 'deploying'
485 DEPLOYFAIL = 'deploy failed'
486 DEPLOYDONE = 'deploy complete'
487 DELETING = 'deleting'
488 DELETED = 'deleted'
489 ERROR = 'error'
490
491
Adam Gandelman4a48a602014-03-20 18:23:18 -0700492class BaremetalScenarioTest(OfficialClientTest):
493 @classmethod
494 def setUpClass(cls):
495 super(BaremetalScenarioTest, cls).setUpClass()
496
497 if (not CONF.service_available.ironic or
498 not CONF.baremetal.driver_enabled):
499 msg = 'Ironic not available or Ironic compute driver not enabled'
500 raise cls.skipException(msg)
501
502 # use an admin client manager for baremetal client
Adam Gandelmanacc13e62014-05-08 11:12:47 -0700503 admin_creds = cls.admin_credentials()
504 manager = clients.OfficialClientManager(credentials=admin_creds)
Adam Gandelman4a48a602014-03-20 18:23:18 -0700505 cls.baremetal_client = manager.baremetal_client
506
507 # allow any issues obtaining the node list to raise early
508 cls.baremetal_client.node.list()
509
510 def _node_state_timeout(self, node_id, state_attr,
511 target_states, timeout=10, interval=1):
512 if not isinstance(target_states, list):
513 target_states = [target_states]
514
515 def check_state():
516 node = self.get_node(node_id=node_id)
517 if getattr(node, state_attr) in target_states:
518 return True
519 return False
520
521 if not tempest.test.call_until_true(
522 check_state, timeout, interval):
523 msg = ("Timed out waiting for node %s to reach %s state(s) %s" %
524 (node_id, state_attr, target_states))
525 raise exceptions.TimeoutException(msg)
526
527 def wait_provisioning_state(self, node_id, state, timeout):
528 self._node_state_timeout(
529 node_id=node_id, state_attr='provision_state',
530 target_states=state, timeout=timeout)
531
532 def wait_power_state(self, node_id, state):
533 self._node_state_timeout(
534 node_id=node_id, state_attr='power_state',
535 target_states=state, timeout=CONF.baremetal.power_timeout)
536
537 def wait_node(self, instance_id):
538 """Waits for a node to be associated with instance_id."""
Zhi Kun Liu4a8d1ea2014-04-15 22:08:21 -0500539 from ironicclient import exc as ironic_exceptions
540
Adam Gandelman4a48a602014-03-20 18:23:18 -0700541 def _get_node():
542 node = None
543 try:
544 node = self.get_node(instance_id=instance_id)
545 except ironic_exceptions.HTTPNotFound:
546 pass
547 return node is not None
548
549 if not tempest.test.call_until_true(
550 _get_node, CONF.baremetal.association_timeout, 1):
551 msg = ('Timed out waiting to get Ironic node by instance id %s'
552 % instance_id)
553 raise exceptions.TimeoutException(msg)
554
555 def get_node(self, node_id=None, instance_id=None):
556 if node_id:
557 return self.baremetal_client.node.get(node_id)
558 elif instance_id:
559 return self.baremetal_client.node.get_by_instance_uuid(instance_id)
560
561 def get_ports(self, node_id):
562 ports = []
563 for port in self.baremetal_client.node.list_ports(node_id):
564 ports.append(self.baremetal_client.port.get(port.uuid))
565 return ports
566
David Shrewsbury06f7f8a2014-05-20 13:55:57 -0400567 def add_keypair(self):
568 self.keypair = self.create_keypair()
569
570 def verify_connectivity(self, ip=None):
571 if ip:
572 dest = self.get_remote_client(ip)
573 else:
574 dest = self.get_remote_client(self.instance)
575 dest.validate_authentication()
576
577 def boot_instance(self):
578 create_kwargs = {
579 'key_name': self.keypair.id
580 }
581 self.instance = self.create_server(
582 wait=False, create_kwargs=create_kwargs)
583
584 self.set_resource('instance', self.instance)
585
586 self.wait_node(self.instance.id)
587 self.node = self.get_node(instance_id=self.instance.id)
588
589 self.wait_power_state(self.node.uuid, BaremetalPowerStates.POWER_ON)
590
591 self.wait_provisioning_state(
592 self.node.uuid,
593 [BaremetalProvisionStates.DEPLOYWAIT,
594 BaremetalProvisionStates.ACTIVE],
595 timeout=15)
596
597 self.wait_provisioning_state(self.node.uuid,
598 BaremetalProvisionStates.ACTIVE,
599 timeout=CONF.baremetal.active_timeout)
600
601 self.status_timeout(
602 self.compute_client.servers, self.instance.id, 'ACTIVE')
603
604 self.node = self.get_node(instance_id=self.instance.id)
605 self.instance = self.compute_client.servers.get(self.instance.id)
606
607 def terminate_instance(self):
608 self.instance.delete()
609 self.remove_resource('instance')
610 self.wait_power_state(self.node.uuid, BaremetalPowerStates.POWER_OFF)
611 self.wait_provisioning_state(
612 self.node.uuid,
613 BaremetalProvisionStates.NOSTATE,
614 timeout=CONF.baremetal.unprovision_timeout)
615
Adam Gandelman4a48a602014-03-20 18:23:18 -0700616
Sean Dague6dbc6da2013-05-08 17:49:46 -0400617class NetworkScenarioTest(OfficialClientTest):
618 """
619 Base class for network scenario tests
620 """
621
622 @classmethod
623 def check_preconditions(cls):
Matthew Treinish6c072292014-01-29 19:15:52 +0000624 if (CONF.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400625 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200626 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400627 try:
628 cls.network_client.list_networks()
629 except exc.EndpointNotFound:
630 cls.enabled = False
631 raise
632 else:
633 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400634 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400635 raise cls.skipException(msg)
636
637 @classmethod
638 def setUpClass(cls):
639 super(NetworkScenarioTest, cls).setUpClass()
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000640 cls.tenant_id = cls.manager.identity_client.tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400641
Sean Dague6dbc6da2013-05-08 17:49:46 -0400642 def _create_network(self, tenant_id, namestart='network-smoke-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900643 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400644 body = dict(
645 network=dict(
646 name=name,
647 tenant_id=tenant_id,
648 ),
649 )
650 result = self.network_client.create_network(body=body)
651 network = net_common.DeletableNetwork(client=self.network_client,
652 **result['network'])
653 self.assertEqual(network.name, name)
654 self.set_resource(name, network)
655 return network
656
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200657 def _list_networks(self, **kwargs):
658 nets = self.network_client.list_networks(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400659 return nets['networks']
660
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200661 def _list_subnets(self, **kwargs):
662 subnets = self.network_client.list_subnets(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400663 return subnets['subnets']
664
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200665 def _list_routers(self, **kwargs):
666 routers = self.network_client.list_routers(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400667 return routers['routers']
668
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200669 def _list_ports(self, **kwargs):
670 ports = self.network_client.list_ports(**kwargs)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000671 return ports['ports']
672
673 def _get_tenant_own_network_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200674 nets = self._list_networks(tenant_id=tenant_id)
675 return len(nets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000676
677 def _get_tenant_own_subnet_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200678 subnets = self._list_subnets(tenant_id=tenant_id)
679 return len(subnets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000680
681 def _get_tenant_own_port_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200682 ports = self._list_ports(tenant_id=tenant_id)
683 return len(ports)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000684
Yair Fried3097dc12014-01-26 08:46:43 +0200685 def _create_subnet(self, network, namestart='subnet-smoke-', **kwargs):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400686 """
687 Create a subnet for the given network within the cidr block
688 configured for tenant networks.
689 """
Attila Fazekase857bd62013-10-21 21:02:44 +0200690
691 def cidr_in_use(cidr, tenant_id):
692 """
693 :return True if subnet with cidr already exist in tenant
694 False else
695 """
696 cidr_in_use = self._list_subnets(tenant_id=tenant_id, cidr=cidr)
697 return len(cidr_in_use) != 0
698
Matthew Treinish6c072292014-01-29 19:15:52 +0000699 tenant_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400700 result = None
701 # Repeatedly attempt subnet creation with sequential cidr
702 # blocks until an unallocated block is found.
Matthew Treinish6c072292014-01-29 19:15:52 +0000703 for subnet_cidr in tenant_cidr.subnet(
704 CONF.network.tenant_network_mask_bits):
Attila Fazekase857bd62013-10-21 21:02:44 +0200705 str_cidr = str(subnet_cidr)
706 if cidr_in_use(str_cidr, tenant_id=network.tenant_id):
707 continue
708
Sean Dague6dbc6da2013-05-08 17:49:46 -0400709 body = dict(
710 subnet=dict(
Attila Fazekase857bd62013-10-21 21:02:44 +0200711 name=data_utils.rand_name(namestart),
Sean Dague6dbc6da2013-05-08 17:49:46 -0400712 ip_version=4,
713 network_id=network.id,
714 tenant_id=network.tenant_id,
Attila Fazekase857bd62013-10-21 21:02:44 +0200715 cidr=str_cidr,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400716 ),
717 )
Yair Fried3097dc12014-01-26 08:46:43 +0200718 body['subnet'].update(kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400719 try:
720 result = self.network_client.create_subnet(body=body)
721 break
Mark McClainf2982e82013-07-06 17:48:03 -0400722 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400723 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
724 if not is_overlapping_cidr:
725 raise
726 self.assertIsNotNone(result, 'Unable to allocate tenant network')
727 subnet = net_common.DeletableSubnet(client=self.network_client,
728 **result['subnet'])
Attila Fazekase857bd62013-10-21 21:02:44 +0200729 self.assertEqual(subnet.cidr, str_cidr)
Masayuki Igawa259c1132013-10-31 17:48:44 +0900730 self.set_resource(data_utils.rand_name(namestart), subnet)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400731 return subnet
732
733 def _create_port(self, network, namestart='port-quotatest-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900734 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400735 body = dict(
736 port=dict(name=name,
737 network_id=network.id,
738 tenant_id=network.tenant_id))
739 result = self.network_client.create_port(body=body)
740 self.assertIsNotNone(result, 'Unable to allocate port')
741 port = net_common.DeletablePort(client=self.network_client,
742 **result['port'])
743 self.set_resource(name, port)
744 return port
745
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200746 def _get_server_port_id(self, server, ip_addr=None):
747 ports = self._list_ports(device_id=server.id, fixed_ip=ip_addr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400748 self.assertEqual(len(ports), 1,
749 "Unable to determine which port to target.")
Yair Fried05db2522013-11-18 11:02:10 +0200750 return ports[0]['id']
751
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200752 def _create_floating_ip(self, thing, external_network_id, port_id=None):
753 if not port_id:
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400754 port_id = self._get_server_port_id(thing)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400755 body = dict(
756 floatingip=dict(
757 floating_network_id=external_network_id,
758 port_id=port_id,
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400759 tenant_id=thing.tenant_id,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400760 )
761 )
762 result = self.network_client.create_floatingip(body=body)
763 floating_ip = net_common.DeletableFloatingIp(
764 client=self.network_client,
765 **result['floatingip'])
Masayuki Igawa259c1132013-10-31 17:48:44 +0900766 self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400767 return floating_ip
768
Yair Fried05db2522013-11-18 11:02:10 +0200769 def _associate_floating_ip(self, floating_ip, server):
770 port_id = self._get_server_port_id(server)
771 floating_ip.update(port_id=port_id)
772 self.assertEqual(port_id, floating_ip.port_id)
773 return floating_ip
774
Yair Fried9a551c42013-12-15 14:59:34 +0200775 def _disassociate_floating_ip(self, floating_ip):
776 """
777 :param floating_ip: type DeletableFloatingIp
778 """
779 floating_ip.update(port_id=None)
llg8212e4cd3922014-02-15 12:14:21 +0800780 self.assertIsNone(floating_ip.port_id)
Yair Fried9a551c42013-12-15 14:59:34 +0200781 return floating_ip
782
783 def _ping_ip_address(self, ip_address, should_succeed=True):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400784 cmd = ['ping', '-c1', '-w1', ip_address]
785
786 def ping():
787 proc = subprocess.Popen(cmd,
788 stdout=subprocess.PIPE,
789 stderr=subprocess.PIPE)
790 proc.wait()
Yair Fried9a551c42013-12-15 14:59:34 +0200791 return (proc.returncode == 0) == should_succeed
Sean Dague6dbc6da2013-05-08 17:49:46 -0400792
Nachi Ueno6d580be2013-07-24 10:58:11 -0700793 return tempest.test.call_until_true(
Matthew Treinish6c072292014-01-29 19:15:52 +0000794 ping, CONF.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000795
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400796 def _create_pool(self, lb_method, protocol, subnet_id):
797 """Wrapper utility that returns a test pool."""
798 name = data_utils.rand_name('pool-')
799 body = {
800 "pool": {
801 "protocol": protocol,
802 "name": name,
803 "subnet_id": subnet_id,
804 "lb_method": lb_method
805 }
806 }
807 resp = self.network_client.create_pool(body=body)
808 pool = net_common.DeletablePool(client=self.network_client,
809 **resp['pool'])
810 self.assertEqual(pool['name'], name)
811 self.set_resource(name, pool)
812 return pool
813
814 def _create_member(self, address, protocol_port, pool_id):
815 """Wrapper utility that returns a test member."""
816 body = {
817 "member": {
818 "protocol_port": protocol_port,
819 "pool_id": pool_id,
820 "address": address
821 }
822 }
823 resp = self.network_client.create_member(body)
824 member = net_common.DeletableMember(client=self.network_client,
825 **resp['member'])
826 self.set_resource(data_utils.rand_name('member-'), member)
827 return member
828
829 def _create_vip(self, protocol, protocol_port, subnet_id, pool_id):
830 """Wrapper utility that returns a test vip."""
831 name = data_utils.rand_name('vip-')
832 body = {
833 "vip": {
834 "protocol": protocol,
835 "name": name,
836 "subnet_id": subnet_id,
837 "pool_id": pool_id,
838 "protocol_port": protocol_port
839 }
840 }
841 resp = self.network_client.create_vip(body)
842 vip = net_common.DeletableVip(client=self.network_client,
843 **resp['vip'])
844 self.assertEqual(vip['name'], name)
845 self.set_resource(name, vip)
846 return vip
847
Yair Fried9a551c42013-12-15 14:59:34 +0200848 def _check_vm_connectivity(self, ip_address,
849 username=None,
850 private_key=None,
851 should_connect=True):
852 """
853 :param ip_address: server to test against
854 :param username: server's ssh username
855 :param private_key: server's ssh private key to be used
856 :param should_connect: True/False indicates positive/negative test
857 positive - attempt ping and ssh
858 negative - attempt ping and fail if succeed
859
860 :raises: AssertError if the result of the connectivity check does
861 not match the value of the should_connect param
862 """
863 if should_connect:
864 msg = "Timed out waiting for %s to become reachable" % ip_address
865 else:
866 msg = "ip address %s is reachable" % ip_address
867 self.assertTrue(self._ping_ip_address(ip_address,
868 should_succeed=should_connect),
869 msg=msg)
870 if should_connect:
871 # no need to check ssh for negative connectivity
Attila Fazekasad7ef7d2013-11-20 10:12:53 +0100872 linux_client = self.get_remote_client(ip_address, username,
873 private_key)
874 linux_client.validate_authentication()
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200875
Matt Riedemann343305f2014-05-27 09:55:03 -0700876 def _check_public_network_connectivity(self, ip_address, username,
877 private_key, should_connect=True,
878 msg=None, servers=None):
879 # The target login is assumed to have been configured for
880 # key-based authentication by cloud-init.
881 LOG.debug('checking network connections to IP %s with user: %s' %
882 (ip_address, username))
883 try:
884 self._check_vm_connectivity(ip_address,
885 username,
886 private_key,
887 should_connect=should_connect)
888 except Exception:
889 ex_msg = 'Public network connectivity check failed'
890 if msg:
891 ex_msg += ": " + msg
892 LOG.exception(ex_msg)
893 self._log_console_output(servers)
894 debug.log_net_debug()
895 raise
896
Matt Riedemann2d005be2014-05-27 10:52:35 -0700897 def _check_tenant_network_connectivity(self, server,
898 username,
899 private_key,
900 should_connect=True,
901 servers_for_debug=None):
902 if not CONF.network.tenant_networks_reachable:
903 msg = 'Tenant networks not configured to be reachable.'
904 LOG.info(msg)
905 return
906 # The target login is assumed to have been configured for
907 # key-based authentication by cloud-init.
908 try:
909 for net_name, ip_addresses in server.networks.iteritems():
910 for ip_address in ip_addresses:
911 self._check_vm_connectivity(ip_address,
912 username,
913 private_key,
914 should_connect=should_connect)
915 except Exception:
916 LOG.exception('Tenant network connectivity check failed')
917 self._log_console_output(servers_for_debug)
918 debug.log_net_debug()
919 raise
920
Yair Fried3097dc12014-01-26 08:46:43 +0200921 def _check_remote_connectivity(self, source, dest, should_succeed=True):
922 """
923 check ping server via source ssh connection
924
925 :param source: RemoteClient: an ssh connection from which to ping
926 :param dest: and IP to ping against
927 :param should_succeed: boolean should ping succeed or not
928 :returns: boolean -- should_succeed == ping
929 :returns: ping is false if ping failed
930 """
931 def ping_remote():
932 try:
933 source.ping_host(dest)
934 except exceptions.SSHExecCommandFailed:
935 LOG.exception('Failed to ping host via ssh connection')
936 return not should_succeed
937 return should_succeed
938
939 return tempest.test.call_until_true(ping_remote,
940 CONF.compute.ping_timeout,
941 1)
942
Yair Friedeb69f3f2013-10-10 13:18:16 +0300943 def _create_security_group_neutron(self, tenant_id, client=None,
944 namestart='secgroup-smoke-'):
945 if client is None:
946 client = self.network_client
947 secgroup = self._create_empty_security_group(namestart=namestart,
948 client=client,
949 tenant_id=tenant_id)
950
951 # Add rules to the security group
952 rules = self._create_loginable_secgroup_rule_neutron(secgroup=secgroup)
953 for rule in rules:
954 self.assertEqual(tenant_id, rule.tenant_id)
955 self.assertEqual(secgroup.id, rule.security_group_id)
956 return secgroup
957
958 def _create_empty_security_group(self, tenant_id, client=None,
959 namestart='secgroup-smoke-'):
960 """Create a security group without rules.
961
962 Default rules will be created:
963 - IPv4 egress to any
964 - IPv6 egress to any
965
966 :param tenant_id: secgroup will be created in this tenant
967 :returns: DeletableSecurityGroup -- containing the secgroup created
968 """
969 if client is None:
970 client = self.network_client
971 sg_name = data_utils.rand_name(namestart)
972 sg_desc = sg_name + " description"
973 sg_dict = dict(name=sg_name,
974 description=sg_desc)
975 sg_dict['tenant_id'] = tenant_id
976 body = dict(security_group=sg_dict)
977 result = client.create_security_group(body=body)
978 secgroup = net_common.DeletableSecurityGroup(
979 client=client,
980 **result['security_group']
981 )
982 self.assertEqual(secgroup.name, sg_name)
983 self.assertEqual(tenant_id, secgroup.tenant_id)
984 self.assertEqual(secgroup.description, sg_desc)
985 self.set_resource(sg_name, secgroup)
986 return secgroup
987
988 def _default_security_group(self, tenant_id, client=None):
989 """Get default secgroup for given tenant_id.
990
991 :returns: DeletableSecurityGroup -- default secgroup for given tenant
992 """
993 if client is None:
994 client = self.network_client
995 sgs = [
996 sg for sg in client.list_security_groups().values()[0]
997 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
998 ]
999 msg = "No default security group for tenant %s." % (tenant_id)
1000 self.assertTrue(len(sgs) > 0, msg)
1001 if len(sgs) > 1:
1002 msg = "Found %d default security groups" % len(sgs)
1003 raise exc.NeutronClientNoUniqueMatch(msg=msg)
1004 return net_common.DeletableSecurityGroup(client=client,
1005 **sgs[0])
1006
1007 def _create_security_group_rule(self, client=None, secgroup=None,
1008 tenant_id=None, **kwargs):
1009 """Create a rule from a dictionary of rule parameters.
1010
1011 Create a rule in a secgroup. if secgroup not defined will search for
1012 default secgroup in tenant_id.
1013
1014 :param secgroup: type DeletableSecurityGroup.
1015 :param secgroup_id: search for secgroup by id
1016 default -- choose default secgroup for given tenant_id
1017 :param tenant_id: if secgroup not passed -- the tenant in which to
1018 search for default secgroup
1019 :param kwargs: a dictionary containing rule parameters:
1020 for example, to allow incoming ssh:
1021 rule = {
1022 direction: 'ingress'
1023 protocol:'tcp',
1024 port_range_min: 22,
1025 port_range_max: 22
1026 }
1027 """
1028 if client is None:
1029 client = self.network_client
1030 if secgroup is None:
1031 secgroup = self._default_security_group(tenant_id)
1032
1033 ruleset = dict(security_group_id=secgroup.id,
1034 tenant_id=secgroup.tenant_id,
1035 )
1036 ruleset.update(kwargs)
1037
1038 body = dict(security_group_rule=dict(ruleset))
1039 sg_rule = client.create_security_group_rule(body=body)
1040 sg_rule = net_common.DeletableSecurityGroupRule(
1041 client=client,
1042 **sg_rule['security_group_rule']
1043 )
Yair Friedeb69f3f2013-10-10 13:18:16 +03001044 self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
1045 self.assertEqual(secgroup.id, sg_rule.security_group_id)
1046
1047 return sg_rule
1048
1049 def _create_loginable_secgroup_rule_neutron(self, client=None,
1050 secgroup=None):
1051 """These rules are intended to permit inbound ssh and icmp
1052 traffic from all sources, so no group_id is provided.
1053 Setting a group_id would only permit traffic from ports
1054 belonging to the same security group.
1055 """
1056
1057 if client is None:
1058 client = self.network_client
1059 rules = []
1060 rulesets = [
1061 dict(
1062 # ssh
1063 protocol='tcp',
1064 port_range_min=22,
1065 port_range_max=22,
1066 ),
1067 dict(
1068 # ping
1069 protocol='icmp',
1070 )
1071 ]
1072 for ruleset in rulesets:
1073 for r_direction in ['ingress', 'egress']:
1074 ruleset['direction'] = r_direction
1075 try:
1076 sg_rule = self._create_security_group_rule(
1077 client=client, secgroup=secgroup, **ruleset)
1078 except exc.NeutronClientException as ex:
1079 # if rule already exist - skip rule and continue
1080 if not (ex.status_code is 409 and 'Security group rule'
1081 ' already exists' in ex.message):
1082 raise ex
1083 else:
1084 self.assertEqual(r_direction, sg_rule.direction)
1085 rules.append(sg_rule)
1086
1087 return rules
1088
Yair Fried5f670ab2013-12-09 09:26:51 +02001089 def _ssh_to_server(self, server, private_key):
Matthew Treinish6c072292014-01-29 19:15:52 +00001090 ssh_login = CONF.compute.image_ssh_user
Yair Fried5f670ab2013-12-09 09:26:51 +02001091 return self.get_remote_client(server,
1092 username=ssh_login,
1093 private_key=private_key)
1094
Yuiko Takada7f4b1b32013-11-20 08:06:26 +00001095 def _show_quota_network(self, tenant_id):
1096 quota = self.network_client.show_quota(tenant_id)
1097 return quota['quota']['network']
1098
1099 def _show_quota_subnet(self, tenant_id):
1100 quota = self.network_client.show_quota(tenant_id)
1101 return quota['quota']['subnet']
1102
1103 def _show_quota_port(self, tenant_id):
1104 quota = self.network_client.show_quota(tenant_id)
1105 return quota['quota']['port']
1106
Yair Fried4d7efa62013-11-17 17:12:29 +02001107 def _get_router(self, tenant_id):
1108 """Retrieve a router for the given tenant id.
1109
1110 If a public router has been configured, it will be returned.
1111
1112 If a public router has not been configured, but a public
1113 network has, a tenant router will be created and returned that
1114 routes traffic to the public network.
1115 """
Matthew Treinish6c072292014-01-29 19:15:52 +00001116 router_id = CONF.network.public_router_id
1117 network_id = CONF.network.public_network_id
Yair Fried4d7efa62013-11-17 17:12:29 +02001118 if router_id:
1119 result = self.network_client.show_router(router_id)
1120 return net_common.AttributeDict(**result['router'])
1121 elif network_id:
1122 router = self._create_router(tenant_id)
1123 router.add_gateway(network_id)
1124 return router
1125 else:
1126 raise Exception("Neither of 'public_router_id' or "
1127 "'public_network_id' has been defined.")
1128
1129 def _create_router(self, tenant_id, namestart='router-smoke-'):
1130 name = data_utils.rand_name(namestart)
1131 body = dict(
1132 router=dict(
1133 name=name,
1134 admin_state_up=True,
1135 tenant_id=tenant_id,
1136 ),
1137 )
1138 result = self.network_client.create_router(body=body)
1139 router = net_common.DeletableRouter(client=self.network_client,
1140 **result['router'])
1141 self.assertEqual(router.name, name)
1142 self.set_resource(name, router)
1143 return router
1144
1145 def _create_networks(self, tenant_id=None):
1146 """Create a network with a subnet connected to a router.
1147
1148 :returns: network, subnet, router
1149 """
1150 if tenant_id is None:
1151 tenant_id = self.tenant_id
1152 network = self._create_network(tenant_id)
1153 router = self._get_router(tenant_id)
1154 subnet = self._create_subnet(network)
1155 subnet.add_to_router(router.id)
Yair Fried4d7efa62013-11-17 17:12:29 +02001156 return network, subnet, router
1157
Steve Bakerdd7c6ce2013-06-24 14:46:47 +12001158
1159class OrchestrationScenarioTest(OfficialClientTest):
1160 """
1161 Base class for orchestration scenario tests
1162 """
1163
1164 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -07001165 def setUpClass(cls):
1166 super(OrchestrationScenarioTest, cls).setUpClass()
Matthew Treinish6c072292014-01-29 19:15:52 +00001167 if not CONF.service_available.heat:
Matt Riedemann11c5b642013-08-24 08:45:38 -07001168 raise cls.skipException("Heat support is required")
1169
1170 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +12001171 def credentials(cls):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +00001172 admin_creds = auth.get_default_credentials('identity_admin')
1173 creds = auth.get_default_credentials('user')
1174 admin_creds.tenant_name = creds.tenant_name
1175 return admin_creds
Steve Bakerdd7c6ce2013-06-24 14:46:47 +12001176
1177 def _load_template(self, base_file, file_name):
1178 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
1179 file_name)
1180 with open(filepath) as f:
1181 return f.read()
1182
1183 @classmethod
1184 def _stack_rand_name(cls):
Masayuki Igawa259c1132013-10-31 17:48:44 +09001185 return data_utils.rand_name(cls.__name__ + '-')
Steve Baker80252da2013-09-25 13:29:10 +12001186
1187 @classmethod
1188 def _get_default_network(cls):
1189 networks = cls.network_client.list_networks()
1190 for net in networks['networks']:
Matthew Treinish6c072292014-01-29 19:15:52 +00001191 if net['name'] == CONF.compute.fixed_network_name:
Steve Baker80252da2013-09-25 13:29:10 +12001192 return net
Steve Baker22c16602014-05-05 13:34:19 +12001193
1194 @staticmethod
1195 def _stack_output(stack, output_key):
1196 """Return a stack output value for a given key."""
1197 return next((o['output_value'] for o in stack.outputs
1198 if o['output_key'] == output_key), None)
1199
1200 def _ping_ip_address(self, ip_address, should_succeed=True):
1201 cmd = ['ping', '-c1', '-w1', ip_address]
1202
1203 def ping():
1204 proc = subprocess.Popen(cmd,
1205 stdout=subprocess.PIPE,
1206 stderr=subprocess.PIPE)
1207 proc.wait()
1208 return (proc.returncode == 0) == should_succeed
1209
1210 return tempest.test.call_until_true(
1211 ping, CONF.orchestration.build_timeout, 1)
1212
1213 def _wait_for_resource_status(self, stack_identifier, resource_name,
1214 status, failure_pattern='^.*_FAILED$'):
1215 """Waits for a Resource to reach a given status."""
1216 fail_regexp = re.compile(failure_pattern)
1217 build_timeout = CONF.orchestration.build_timeout
1218 build_interval = CONF.orchestration.build_interval
1219
1220 start = timeutils.utcnow()
1221 while timeutils.delta_seconds(start,
1222 timeutils.utcnow()) < build_timeout:
1223 try:
1224 res = self.client.resources.get(
1225 stack_identifier, resource_name)
1226 except heat_exceptions.HTTPNotFound:
1227 # ignore this, as the resource may not have
1228 # been created yet
1229 pass
1230 else:
1231 if res.resource_status == status:
1232 return
1233 if fail_regexp.search(res.resource_status):
1234 raise exceptions.StackResourceBuildErrorException(
1235 resource_name=res.resource_name,
1236 stack_identifier=stack_identifier,
1237 resource_status=res.resource_status,
1238 resource_status_reason=res.resource_status_reason)
1239 time.sleep(build_interval)
1240
1241 message = ('Resource %s failed to reach %s status within '
1242 'the required time (%s s).' %
1243 (res.resource_name, status, build_timeout))
1244 raise exceptions.TimeoutException(message)
1245
1246 def _wait_for_stack_status(self, stack_identifier, status,
1247 failure_pattern='^.*_FAILED$'):
1248 """
1249 Waits for a Stack to reach a given status.
1250
1251 Note this compares the full $action_$status, e.g
1252 CREATE_COMPLETE, not just COMPLETE which is exposed
1253 via the status property of Stack in heatclient
1254 """
1255 fail_regexp = re.compile(failure_pattern)
1256 build_timeout = CONF.orchestration.build_timeout
1257 build_interval = CONF.orchestration.build_interval
1258
1259 start = timeutils.utcnow()
1260 while timeutils.delta_seconds(start,
1261 timeutils.utcnow()) < build_timeout:
1262 try:
1263 stack = self.client.stacks.get(stack_identifier)
1264 except heat_exceptions.HTTPNotFound:
1265 # ignore this, as the stackource may not have
1266 # been created yet
1267 pass
1268 else:
1269 if stack.stack_status == status:
1270 return
1271 if fail_regexp.search(stack.stack_status):
1272 raise exceptions.StackBuildErrorException(
1273 stack_identifier=stack_identifier,
1274 stack_status=stack.stack_status,
1275 stack_status_reason=stack.stack_status_reason)
1276 time.sleep(build_interval)
1277
1278 message = ('Stack %s failed to reach %s status within '
1279 'the required time (%s s).' %
1280 (stack.stack_name, status, build_timeout))
1281 raise exceptions.TimeoutException(message)
1282
1283 def _stack_delete(self, stack_identifier):
1284 try:
1285 self.client.stacks.delete(stack_identifier)
1286 except heat_exceptions.HTTPNotFound:
1287 pass