blob: 7703d4d29f39dc8bca13f3e6bd48e6891bd12e83 [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
Matthew Treinish0ae79ce2013-08-08 14:31:05 -040085 cls.resource_keys = {}
86 cls.os_resources = []
Sean Dague6dbc6da2013-05-08 17:49:46 -040087
88 @classmethod
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000089 def _get_credentials(cls, get_creds, ctype):
Matthew Treinish6c072292014-01-29 19:15:52 +000090 if CONF.compute.allow_tenant_isolation:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000091 creds = get_creds()
Yair Fried769bbff2013-12-18 16:33:17 +020092 else:
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000093 creds = auth.get_default_credentials(ctype)
94 return creds
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120095
96 @classmethod
Yair Frieda71cc442013-12-18 13:32:36 +020097 def credentials(cls):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +000098 return cls._get_credentials(cls.isolated_creds.get_primary_creds,
99 'user')
Yair Frieda71cc442013-12-18 13:32:36 +0200100
101 @classmethod
102 def alt_credentials(cls):
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000103 return cls._get_credentials(cls.isolated_creds.get_alt_creds,
104 'alt_user')
Yair Frieda71cc442013-12-18 13:32:36 +0200105
106 @classmethod
107 def admin_credentials(cls):
108 return cls._get_credentials(cls.isolated_creds.get_admin_creds,
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000109 'identity_admin')
Yair Frieda71cc442013-12-18 13:32:36 +0200110
Yair Friedbf2e2c42014-01-28 12:06:38 +0200111 @staticmethod
112 def cleanup_resource(resource, test_name):
113
114 LOG.debug("Deleting %r from shared resources of %s" %
115 (resource, test_name))
116 try:
117 # OpenStack resources are assumed to have a delete()
118 # method which destroys the resource...
119 resource.delete()
120 except Exception as e:
121 # If the resource is already missing, mission accomplished.
Steven Hardyef1c8962014-05-07 10:05:45 +0100122 # - Status code tolerated as a workaround for bug 1247568
123 # - HTTPNotFound tolerated as this is currently raised when
124 # attempting to delete an already-deleted heat stack.
125 if (e.__class__.__name__ in ('NotFound', 'HTTPNotFound') or
Yair Friedbf2e2c42014-01-28 12:06:38 +0200126 (hasattr(e, 'status_code') and e.status_code == 404)):
127 return
128 raise
129
130 def is_deletion_complete():
131 # Deletion testing is only required for objects whose
132 # existence cannot be checked via retrieval.
133 if isinstance(resource, dict):
134 return True
135 try:
136 resource.get()
137 except Exception as e:
138 # Clients are expected to return an exception
139 # called 'NotFound' if retrieval fails.
140 if e.__class__.__name__ == 'NotFound':
141 return True
142 raise
143 return False
144
145 # Block until resource deletion has completed or timed-out
146 tempest.test.call_until_true(is_deletion_complete, 10, 1)
147
Yair Frieda71cc442013-12-18 13:32:36 +0200148 @classmethod
Sean Dague6dbc6da2013-05-08 17:49:46 -0400149 def tearDownClass(cls):
150 # NOTE(jaypipes): Because scenario tests are typically run in a
151 # specific order, and because test methods in scenario tests
152 # generally create resources in a particular order, we destroy
153 # resources in the reverse order in which resources are added to
154 # the scenario test class object
155 while cls.os_resources:
156 thing = cls.os_resources.pop()
Yair Friedbf2e2c42014-01-28 12:06:38 +0200157 cls.cleanup_resource(thing, cls.__name__)
Matthew Treinishb86cda92013-07-29 11:22:23 -0400158 cls.isolated_creds.clear_isolated_creds()
159 super(OfficialClientTest, cls).tearDownClass()
Sean Dague6dbc6da2013-05-08 17:49:46 -0400160
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400161 @classmethod
162 def set_resource(cls, key, thing):
163 LOG.debug("Adding %r to shared resources of %s" %
164 (thing, cls.__name__))
165 cls.resource_keys[key] = thing
166 cls.os_resources.append(thing)
167
168 @classmethod
169 def get_resource(cls, key):
170 return cls.resource_keys[key]
171
172 @classmethod
173 def remove_resource(cls, key):
174 thing = cls.resource_keys[key]
175 cls.os_resources.remove(thing)
176 del cls.resource_keys[key]
177
Steve Bakerefde7612013-09-30 11:29:23 +1300178 def status_timeout(self, things, thing_id, expected_status,
179 error_status='ERROR',
180 not_found_exception=nova_exceptions.NotFound):
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400181 """
182 Given a thing and an expected status, do a loop, sleeping
183 for a configurable amount of time, checking for the
184 expected status to show. At any time, if the returned
185 status of the thing is ERROR, fail out.
186 """
Steve Bakerefde7612013-09-30 11:29:23 +1300187 self._status_timeout(things, thing_id,
188 expected_status=expected_status,
189 error_status=error_status,
190 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900191
Steve Bakerefde7612013-09-30 11:29:23 +1300192 def delete_timeout(self, things, thing_id,
193 error_status='ERROR',
194 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900195 """
196 Given a thing, do a loop, sleeping
197 for a configurable amount of time, checking for the
198 deleted status to show. At any time, if the returned
199 status of the thing is ERROR, fail out.
200 """
201 self._status_timeout(things,
202 thing_id,
Steve Bakerefde7612013-09-30 11:29:23 +1300203 allow_notfound=True,
204 error_status=error_status,
205 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900206
207 def _status_timeout(self,
208 things,
209 thing_id,
210 expected_status=None,
Steve Bakerefde7612013-09-30 11:29:23 +1300211 allow_notfound=False,
212 error_status='ERROR',
213 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900214
215 log_status = expected_status if expected_status else ''
216 if allow_notfound:
217 log_status += ' or NotFound' if log_status != '' else 'NotFound'
218
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400219 def check_status():
220 # python-novaclient has resources available to its client
221 # that all implement a get() method taking an identifier
222 # for the singular resource to retrieve.
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900223 try:
224 thing = things.get(thing_id)
Steve Bakerefde7612013-09-30 11:29:23 +1300225 except not_found_exception:
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900226 if allow_notfound:
227 return True
228 else:
229 raise
230
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400231 new_status = thing.status
Brent Eaglesc26d4522013-12-02 13:28:49 -0500232
233 # Some components are reporting error status in lower case
234 # so case sensitive comparisons can really mess things
235 # up.
236 if new_status.lower() == error_status.lower():
Masayuki Igawa2a8a8122014-02-07 11:24:49 +0900237 message = ("%s failed to get to expected status (%s). "
238 "In %s state.") % (thing, expected_status,
239 new_status)
Masayuki Igawaa0e786a2014-01-27 15:25:06 +0900240 raise exceptions.BuildErrorException(message,
241 server_id=thing_id)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900242 elif new_status == expected_status and expected_status is not None:
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400243 return True # All good.
244 LOG.debug("Waiting for %s to get to %s status. "
245 "Currently in %s status",
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900246 thing, log_status, new_status)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400247 if not tempest.test.call_until_true(
248 check_status,
Matthew Treinish6c072292014-01-29 19:15:52 +0000249 CONF.compute.build_timeout,
250 CONF.compute.build_interval):
Ken'ichi Ohmichiab1496f2013-12-12 22:17:57 +0900251 message = ("Timed out waiting for thing %s "
252 "to become %s") % (thing_id, log_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200253 raise exceptions.TimeoutException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400254
Yair Friedeb69f3f2013-10-10 13:18:16 +0300255 def _create_loginable_secgroup_rule_nova(self, client=None,
256 secgroup_id=None):
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900257 if client is None:
258 client = self.compute_client
259 if secgroup_id is None:
260 sgs = client.security_groups.list()
261 for sg in sgs:
262 if sg.name == 'default':
263 secgroup_id = sg.id
264
265 # These rules are intended to permit inbound ssh and icmp
266 # traffic from all sources, so no group_id is provided.
267 # Setting a group_id would only permit traffic from ports
268 # belonging to the same security group.
269 rulesets = [
270 {
271 # ssh
272 'ip_protocol': 'tcp',
273 'from_port': 22,
274 'to_port': 22,
275 'cidr': '0.0.0.0/0',
276 },
277 {
278 # ping
279 'ip_protocol': 'icmp',
280 'from_port': -1,
281 'to_port': -1,
282 'cidr': '0.0.0.0/0',
283 }
284 ]
Yair Friedeb69f3f2013-10-10 13:18:16 +0300285 rules = list()
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900286 for ruleset in rulesets:
287 sg_rule = client.security_group_rules.create(secgroup_id,
288 **ruleset)
Yair Friedeb69f3f2013-10-10 13:18:16 +0300289 rules.append(sg_rule)
290 return rules
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900291
Giulio Fidente61cadca2013-09-24 18:33:37 +0200292 def create_server(self, client=None, name=None, image=None, flavor=None,
Adam Gandelman4a48a602014-03-20 18:23:18 -0700293 wait=True, create_kwargs={}):
Giulio Fidente61cadca2013-09-24 18:33:37 +0200294 if client is None:
295 client = self.compute_client
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900296 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900297 name = data_utils.rand_name('scenario-server-')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900298 if image is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000299 image = CONF.compute.image_ref
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900300 if flavor is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000301 flavor = CONF.compute.flavor_ref
JordanP9c052aa2014-01-24 13:05:00 +0000302
303 fixed_network_name = CONF.compute.fixed_network_name
304 if 'nics' not in create_kwargs and fixed_network_name:
305 networks = client.networks.list()
306 # If several networks found, set the NetID on which to connect the
307 # server to avoid the following error "Multiple possible networks
308 # found, use a Network ID to be more specific."
309 # See Tempest #1250866
310 if len(networks) > 1:
311 for network in networks:
312 if network.label == fixed_network_name:
313 create_kwargs['nics'] = [{'net-id': network.id}]
314 break
315 # If we didn't find the network we were looking for :
316 else:
317 msg = ("The network on which the NIC of the server must "
318 "be connected can not be found : "
319 "fixed_network_name=%s. Starting instance without "
320 "specifying a network.") % fixed_network_name
321 LOG.info(msg)
322
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900323 LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
324 name, image, flavor)
325 server = client.servers.create(name, image, flavor, **create_kwargs)
Giulio Fidente92f77192013-08-26 17:13:28 +0200326 self.assertEqual(server.name, name)
327 self.set_resource(name, server)
Adam Gandelman4a48a602014-03-20 18:23:18 -0700328 if wait:
329 self.status_timeout(client.servers, server.id, 'ACTIVE')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900330 # The instance retrieved on creation is missing network
331 # details, necessitating retrieval after it becomes active to
332 # ensure correct details.
333 server = client.servers.get(server.id)
334 self.set_resource(name, server)
335 LOG.debug("Created server: %s", server)
336 return server
337
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900338 def create_volume(self, client=None, size=1, name=None,
339 snapshot_id=None, imageRef=None):
340 if client is None:
341 client = self.volume_client
342 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900343 name = data_utils.rand_name('scenario-volume-')
Eric Windisch2d26f1b2013-09-04 17:52:16 -0700344 LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900345 volume = client.volumes.create(size=size, display_name=name,
346 snapshot_id=snapshot_id,
347 imageRef=imageRef)
348 self.set_resource(name, volume)
349 self.assertEqual(name, volume.display_name)
350 self.status_timeout(client.volumes, volume.id, 'available')
351 LOG.debug("Created volume: %s", volume)
352 return volume
353
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900354 def create_server_snapshot(self, server, compute_client=None,
355 image_client=None, name=None):
356 if compute_client is None:
357 compute_client = self.compute_client
358 if image_client is None:
359 image_client = self.image_client
360 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900361 name = data_utils.rand_name('scenario-snapshot-')
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900362 LOG.debug("Creating a snapshot image for server: %s", server.name)
363 image_id = compute_client.servers.create_image(server, name)
364 self.addCleanup(image_client.images.delete, image_id)
365 self.status_timeout(image_client.images, image_id, 'active')
366 snapshot_image = image_client.images.get(image_id)
Chang Bo Guofc77e932013-09-16 17:38:26 -0700367 self.assertEqual(name, snapshot_image.name)
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900368 LOG.debug("Created snapshot image %s for server %s",
369 snapshot_image.name, server.name)
370 return snapshot_image
371
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900372 def create_keypair(self, client=None, name=None):
373 if client is None:
374 client = self.compute_client
375 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900376 name = data_utils.rand_name('scenario-keypair-')
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900377 keypair = client.keypairs.create(name)
378 self.assertEqual(keypair.name, name)
379 self.set_resource(name, keypair)
380 return keypair
381
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900382 def get_remote_client(self, server_or_ip, username=None, private_key=None):
llg821243b20502014-02-22 10:32:49 +0800383 if isinstance(server_or_ip, six.string_types):
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900384 ip = server_or_ip
385 else:
Matthew Treinish6c072292014-01-29 19:15:52 +0000386 network_name_for_ssh = CONF.compute.network_for_ssh
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900387 ip = server_or_ip.networks[network_name_for_ssh][0]
388 if username is None:
Matthew Treinish6c072292014-01-29 19:15:52 +0000389 username = CONF.scenario.ssh_user
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900390 if private_key is None:
391 private_key = self.keypair.private_key
Masayuki Igawa4ded9f02014-02-17 15:05:59 +0900392 return remote_client.RemoteClient(ip, username, pkey=private_key)
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900393
Nachi Ueno95b41282014-01-15 06:54:21 -0800394 def _log_console_output(self, servers=None):
395 if not servers:
396 servers = self.compute_client.servers.list()
397 for server in servers:
398 LOG.debug('Console output for %s', server.id)
399 LOG.debug(server.get_console_output())
400
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900401 def wait_for_volume_status(self, status):
402 volume_id = self.volume.id
403 self.status_timeout(
404 self.volume_client.volumes, volume_id, status)
405
406 def _image_create(self, name, fmt, path, properties={}):
407 name = data_utils.rand_name('%s-' % name)
408 image_file = open(path, 'rb')
409 self.addCleanup(image_file.close)
410 params = {
411 'name': name,
412 'container_format': fmt,
413 'disk_format': fmt,
Aaron Rosenc7720622014-05-20 10:38:10 -0700414 'is_public': 'False',
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900415 }
416 params.update(properties)
417 image = self.image_client.images.create(**params)
418 self.addCleanup(self.image_client.images.delete, image)
419 self.assertEqual("queued", image.status)
420 image.update(data=image_file)
421 return image.id
422
423 def glance_image_create(self):
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900424 qcow2_img_path = (CONF.scenario.img_dir + "/" +
425 CONF.scenario.qcow2_img_file)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900426 aki_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.aki_img_file
427 ari_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ari_img_file
428 ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900429 LOG.debug("paths: img: %s, ami: %s, ari: %s, aki: %s"
430 % (qcow2_img_path, ami_img_path, ari_img_path, aki_img_path))
431 try:
432 self.image = self._image_create('scenario-img',
433 'bare',
434 qcow2_img_path,
435 properties={'disk_format':
436 'qcow2'})
437 except IOError:
Masayuki Igawa188fc002014-02-23 06:42:44 +0900438 LOG.debug("A qcow2 image was not found. Try to get a uec image.")
Masayuki Igawa4f71bf02014-02-21 14:02:29 +0900439 kernel = self._image_create('scenario-aki', 'aki', aki_img_path)
440 ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path)
441 properties = {
442 'properties': {'kernel_id': kernel, 'ramdisk_id': ramdisk}
443 }
444 self.image = self._image_create('scenario-ami', 'ami',
445 path=ami_img_path,
446 properties=properties)
447 LOG.debug("image:%s" % self.image)
Masayuki Igawa5cf31902014-02-21 17:30:25 +0900448
Sean Dague6dbc6da2013-05-08 17:49:46 -0400449
David Shrewsbury06f7f8a2014-05-20 13:55:57 -0400450# power/provision states as of icehouse
451class BaremetalPowerStates(object):
452 """Possible power states of an Ironic node."""
453 POWER_ON = 'power on'
454 POWER_OFF = 'power off'
455 REBOOT = 'rebooting'
456 SUSPEND = 'suspended'
457
458
459class BaremetalProvisionStates(object):
460 """Possible provision states of an Ironic node."""
461 NOSTATE = None
462 INIT = 'initializing'
463 ACTIVE = 'active'
464 BUILDING = 'building'
465 DEPLOYWAIT = 'wait call-back'
466 DEPLOYING = 'deploying'
467 DEPLOYFAIL = 'deploy failed'
468 DEPLOYDONE = 'deploy complete'
469 DELETING = 'deleting'
470 DELETED = 'deleted'
471 ERROR = 'error'
472
473
Adam Gandelman4a48a602014-03-20 18:23:18 -0700474class BaremetalScenarioTest(OfficialClientTest):
475 @classmethod
476 def setUpClass(cls):
477 super(BaremetalScenarioTest, cls).setUpClass()
478
479 if (not CONF.service_available.ironic or
480 not CONF.baremetal.driver_enabled):
481 msg = 'Ironic not available or Ironic compute driver not enabled'
482 raise cls.skipException(msg)
483
484 # use an admin client manager for baremetal client
Adam Gandelmanacc13e62014-05-08 11:12:47 -0700485 admin_creds = cls.admin_credentials()
486 manager = clients.OfficialClientManager(credentials=admin_creds)
Adam Gandelman4a48a602014-03-20 18:23:18 -0700487 cls.baremetal_client = manager.baremetal_client
488
489 # allow any issues obtaining the node list to raise early
490 cls.baremetal_client.node.list()
491
492 def _node_state_timeout(self, node_id, state_attr,
493 target_states, timeout=10, interval=1):
494 if not isinstance(target_states, list):
495 target_states = [target_states]
496
497 def check_state():
498 node = self.get_node(node_id=node_id)
499 if getattr(node, state_attr) in target_states:
500 return True
501 return False
502
503 if not tempest.test.call_until_true(
504 check_state, timeout, interval):
505 msg = ("Timed out waiting for node %s to reach %s state(s) %s" %
506 (node_id, state_attr, target_states))
507 raise exceptions.TimeoutException(msg)
508
509 def wait_provisioning_state(self, node_id, state, timeout):
510 self._node_state_timeout(
511 node_id=node_id, state_attr='provision_state',
512 target_states=state, timeout=timeout)
513
514 def wait_power_state(self, node_id, state):
515 self._node_state_timeout(
516 node_id=node_id, state_attr='power_state',
517 target_states=state, timeout=CONF.baremetal.power_timeout)
518
519 def wait_node(self, instance_id):
520 """Waits for a node to be associated with instance_id."""
Zhi Kun Liu4a8d1ea2014-04-15 22:08:21 -0500521 from ironicclient import exc as ironic_exceptions
522
Adam Gandelman4a48a602014-03-20 18:23:18 -0700523 def _get_node():
524 node = None
525 try:
526 node = self.get_node(instance_id=instance_id)
527 except ironic_exceptions.HTTPNotFound:
528 pass
529 return node is not None
530
531 if not tempest.test.call_until_true(
532 _get_node, CONF.baremetal.association_timeout, 1):
533 msg = ('Timed out waiting to get Ironic node by instance id %s'
534 % instance_id)
535 raise exceptions.TimeoutException(msg)
536
537 def get_node(self, node_id=None, instance_id=None):
538 if node_id:
539 return self.baremetal_client.node.get(node_id)
540 elif instance_id:
541 return self.baremetal_client.node.get_by_instance_uuid(instance_id)
542
543 def get_ports(self, node_id):
544 ports = []
545 for port in self.baremetal_client.node.list_ports(node_id):
546 ports.append(self.baremetal_client.port.get(port.uuid))
547 return ports
548
David Shrewsbury06f7f8a2014-05-20 13:55:57 -0400549 def add_keypair(self):
550 self.keypair = self.create_keypair()
551
552 def verify_connectivity(self, ip=None):
553 if ip:
554 dest = self.get_remote_client(ip)
555 else:
556 dest = self.get_remote_client(self.instance)
557 dest.validate_authentication()
558
559 def boot_instance(self):
560 create_kwargs = {
561 'key_name': self.keypair.id
562 }
563 self.instance = self.create_server(
564 wait=False, create_kwargs=create_kwargs)
565
566 self.set_resource('instance', self.instance)
567
568 self.wait_node(self.instance.id)
569 self.node = self.get_node(instance_id=self.instance.id)
570
571 self.wait_power_state(self.node.uuid, BaremetalPowerStates.POWER_ON)
572
573 self.wait_provisioning_state(
574 self.node.uuid,
575 [BaremetalProvisionStates.DEPLOYWAIT,
576 BaremetalProvisionStates.ACTIVE],
577 timeout=15)
578
579 self.wait_provisioning_state(self.node.uuid,
580 BaremetalProvisionStates.ACTIVE,
581 timeout=CONF.baremetal.active_timeout)
582
583 self.status_timeout(
584 self.compute_client.servers, self.instance.id, 'ACTIVE')
585
586 self.node = self.get_node(instance_id=self.instance.id)
587 self.instance = self.compute_client.servers.get(self.instance.id)
588
589 def terminate_instance(self):
590 self.instance.delete()
591 self.remove_resource('instance')
592 self.wait_power_state(self.node.uuid, BaremetalPowerStates.POWER_OFF)
593 self.wait_provisioning_state(
594 self.node.uuid,
595 BaremetalProvisionStates.NOSTATE,
596 timeout=CONF.baremetal.unprovision_timeout)
597
Adam Gandelman4a48a602014-03-20 18:23:18 -0700598
Sean Dague6dbc6da2013-05-08 17:49:46 -0400599class NetworkScenarioTest(OfficialClientTest):
600 """
601 Base class for network scenario tests
602 """
603
604 @classmethod
605 def check_preconditions(cls):
Matthew Treinish6c072292014-01-29 19:15:52 +0000606 if (CONF.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400607 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200608 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400609 try:
610 cls.network_client.list_networks()
611 except exc.EndpointNotFound:
612 cls.enabled = False
613 raise
614 else:
615 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400616 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400617 raise cls.skipException(msg)
618
619 @classmethod
620 def setUpClass(cls):
621 super(NetworkScenarioTest, cls).setUpClass()
Andrea Frittoli422fbdf2014-03-20 10:05:18 +0000622 cls.tenant_id = cls.manager.identity_client.tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400623
Sean Dague6dbc6da2013-05-08 17:49:46 -0400624 def _create_network(self, tenant_id, namestart='network-smoke-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900625 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400626 body = dict(
627 network=dict(
628 name=name,
629 tenant_id=tenant_id,
630 ),
631 )
632 result = self.network_client.create_network(body=body)
633 network = net_common.DeletableNetwork(client=self.network_client,
634 **result['network'])
635 self.assertEqual(network.name, name)
636 self.set_resource(name, network)
637 return network
638
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200639 def _list_networks(self, **kwargs):
640 nets = self.network_client.list_networks(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400641 return nets['networks']
642
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200643 def _list_subnets(self, **kwargs):
644 subnets = self.network_client.list_subnets(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400645 return subnets['subnets']
646
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200647 def _list_routers(self, **kwargs):
648 routers = self.network_client.list_routers(**kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400649 return routers['routers']
650
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200651 def _list_ports(self, **kwargs):
652 ports = self.network_client.list_ports(**kwargs)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000653 return ports['ports']
654
655 def _get_tenant_own_network_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200656 nets = self._list_networks(tenant_id=tenant_id)
657 return len(nets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000658
659 def _get_tenant_own_subnet_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200660 subnets = self._list_subnets(tenant_id=tenant_id)
661 return len(subnets)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000662
663 def _get_tenant_own_port_num(self, tenant_id):
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200664 ports = self._list_ports(tenant_id=tenant_id)
665 return len(ports)
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000666
Yair Fried3097dc12014-01-26 08:46:43 +0200667 def _create_subnet(self, network, namestart='subnet-smoke-', **kwargs):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400668 """
669 Create a subnet for the given network within the cidr block
670 configured for tenant networks.
671 """
Attila Fazekase857bd62013-10-21 21:02:44 +0200672
673 def cidr_in_use(cidr, tenant_id):
674 """
675 :return True if subnet with cidr already exist in tenant
676 False else
677 """
678 cidr_in_use = self._list_subnets(tenant_id=tenant_id, cidr=cidr)
679 return len(cidr_in_use) != 0
680
Matthew Treinish6c072292014-01-29 19:15:52 +0000681 tenant_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400682 result = None
683 # Repeatedly attempt subnet creation with sequential cidr
684 # blocks until an unallocated block is found.
Matthew Treinish6c072292014-01-29 19:15:52 +0000685 for subnet_cidr in tenant_cidr.subnet(
686 CONF.network.tenant_network_mask_bits):
Attila Fazekase857bd62013-10-21 21:02:44 +0200687 str_cidr = str(subnet_cidr)
688 if cidr_in_use(str_cidr, tenant_id=network.tenant_id):
689 continue
690
Sean Dague6dbc6da2013-05-08 17:49:46 -0400691 body = dict(
692 subnet=dict(
Attila Fazekase857bd62013-10-21 21:02:44 +0200693 name=data_utils.rand_name(namestart),
Sean Dague6dbc6da2013-05-08 17:49:46 -0400694 ip_version=4,
695 network_id=network.id,
696 tenant_id=network.tenant_id,
Attila Fazekase857bd62013-10-21 21:02:44 +0200697 cidr=str_cidr,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400698 ),
699 )
Yair Fried3097dc12014-01-26 08:46:43 +0200700 body['subnet'].update(kwargs)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400701 try:
702 result = self.network_client.create_subnet(body=body)
703 break
Mark McClainf2982e82013-07-06 17:48:03 -0400704 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400705 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
706 if not is_overlapping_cidr:
707 raise
708 self.assertIsNotNone(result, 'Unable to allocate tenant network')
709 subnet = net_common.DeletableSubnet(client=self.network_client,
710 **result['subnet'])
Attila Fazekase857bd62013-10-21 21:02:44 +0200711 self.assertEqual(subnet.cidr, str_cidr)
Masayuki Igawa259c1132013-10-31 17:48:44 +0900712 self.set_resource(data_utils.rand_name(namestart), subnet)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400713 return subnet
714
715 def _create_port(self, network, namestart='port-quotatest-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900716 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400717 body = dict(
718 port=dict(name=name,
719 network_id=network.id,
720 tenant_id=network.tenant_id))
721 result = self.network_client.create_port(body=body)
722 self.assertIsNotNone(result, 'Unable to allocate port')
723 port = net_common.DeletablePort(client=self.network_client,
724 **result['port'])
725 self.set_resource(name, port)
726 return port
727
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200728 def _get_server_port_id(self, server, ip_addr=None):
729 ports = self._list_ports(device_id=server.id, fixed_ip=ip_addr)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400730 self.assertEqual(len(ports), 1,
731 "Unable to determine which port to target.")
Yair Fried05db2522013-11-18 11:02:10 +0200732 return ports[0]['id']
733
Yair Frieda2e3b2c2014-02-17 10:56:10 +0200734 def _create_floating_ip(self, thing, external_network_id, port_id=None):
735 if not port_id:
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400736 port_id = self._get_server_port_id(thing)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400737 body = dict(
738 floatingip=dict(
739 floating_network_id=external_network_id,
740 port_id=port_id,
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400741 tenant_id=thing.tenant_id,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400742 )
743 )
744 result = self.network_client.create_floatingip(body=body)
745 floating_ip = net_common.DeletableFloatingIp(
746 client=self.network_client,
747 **result['floatingip'])
Masayuki Igawa259c1132013-10-31 17:48:44 +0900748 self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400749 return floating_ip
750
Yair Fried05db2522013-11-18 11:02:10 +0200751 def _associate_floating_ip(self, floating_ip, server):
752 port_id = self._get_server_port_id(server)
753 floating_ip.update(port_id=port_id)
754 self.assertEqual(port_id, floating_ip.port_id)
755 return floating_ip
756
Yair Fried9a551c42013-12-15 14:59:34 +0200757 def _disassociate_floating_ip(self, floating_ip):
758 """
759 :param floating_ip: type DeletableFloatingIp
760 """
761 floating_ip.update(port_id=None)
llg8212e4cd3922014-02-15 12:14:21 +0800762 self.assertIsNone(floating_ip.port_id)
Yair Fried9a551c42013-12-15 14:59:34 +0200763 return floating_ip
764
765 def _ping_ip_address(self, ip_address, should_succeed=True):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400766 cmd = ['ping', '-c1', '-w1', ip_address]
767
768 def ping():
769 proc = subprocess.Popen(cmd,
770 stdout=subprocess.PIPE,
771 stderr=subprocess.PIPE)
772 proc.wait()
Yair Fried9a551c42013-12-15 14:59:34 +0200773 return (proc.returncode == 0) == should_succeed
Sean Dague6dbc6da2013-05-08 17:49:46 -0400774
Nachi Ueno6d580be2013-07-24 10:58:11 -0700775 return tempest.test.call_until_true(
Matthew Treinish6c072292014-01-29 19:15:52 +0000776 ping, CONF.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000777
Elena Ezhovaa5105e62013-11-26 20:46:52 +0400778 def _create_pool(self, lb_method, protocol, subnet_id):
779 """Wrapper utility that returns a test pool."""
780 name = data_utils.rand_name('pool-')
781 body = {
782 "pool": {
783 "protocol": protocol,
784 "name": name,
785 "subnet_id": subnet_id,
786 "lb_method": lb_method
787 }
788 }
789 resp = self.network_client.create_pool(body=body)
790 pool = net_common.DeletablePool(client=self.network_client,
791 **resp['pool'])
792 self.assertEqual(pool['name'], name)
793 self.set_resource(name, pool)
794 return pool
795
796 def _create_member(self, address, protocol_port, pool_id):
797 """Wrapper utility that returns a test member."""
798 body = {
799 "member": {
800 "protocol_port": protocol_port,
801 "pool_id": pool_id,
802 "address": address
803 }
804 }
805 resp = self.network_client.create_member(body)
806 member = net_common.DeletableMember(client=self.network_client,
807 **resp['member'])
808 self.set_resource(data_utils.rand_name('member-'), member)
809 return member
810
811 def _create_vip(self, protocol, protocol_port, subnet_id, pool_id):
812 """Wrapper utility that returns a test vip."""
813 name = data_utils.rand_name('vip-')
814 body = {
815 "vip": {
816 "protocol": protocol,
817 "name": name,
818 "subnet_id": subnet_id,
819 "pool_id": pool_id,
820 "protocol_port": protocol_port
821 }
822 }
823 resp = self.network_client.create_vip(body)
824 vip = net_common.DeletableVip(client=self.network_client,
825 **resp['vip'])
826 self.assertEqual(vip['name'], name)
827 self.set_resource(name, vip)
828 return vip
829
Yair Fried9a551c42013-12-15 14:59:34 +0200830 def _check_vm_connectivity(self, ip_address,
831 username=None,
832 private_key=None,
833 should_connect=True):
834 """
835 :param ip_address: server to test against
836 :param username: server's ssh username
837 :param private_key: server's ssh private key to be used
838 :param should_connect: True/False indicates positive/negative test
839 positive - attempt ping and ssh
840 negative - attempt ping and fail if succeed
841
842 :raises: AssertError if the result of the connectivity check does
843 not match the value of the should_connect param
844 """
845 if should_connect:
846 msg = "Timed out waiting for %s to become reachable" % ip_address
847 else:
848 msg = "ip address %s is reachable" % ip_address
849 self.assertTrue(self._ping_ip_address(ip_address,
850 should_succeed=should_connect),
851 msg=msg)
852 if should_connect:
853 # no need to check ssh for negative connectivity
Attila Fazekasad7ef7d2013-11-20 10:12:53 +0100854 linux_client = self.get_remote_client(ip_address, username,
855 private_key)
856 linux_client.validate_authentication()
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200857
Matt Riedemann343305f2014-05-27 09:55:03 -0700858 def _check_public_network_connectivity(self, ip_address, username,
859 private_key, should_connect=True,
860 msg=None, servers=None):
861 # The target login is assumed to have been configured for
862 # key-based authentication by cloud-init.
863 LOG.debug('checking network connections to IP %s with user: %s' %
864 (ip_address, username))
865 try:
866 self._check_vm_connectivity(ip_address,
867 username,
868 private_key,
869 should_connect=should_connect)
870 except Exception:
871 ex_msg = 'Public network connectivity check failed'
872 if msg:
873 ex_msg += ": " + msg
874 LOG.exception(ex_msg)
875 self._log_console_output(servers)
876 debug.log_net_debug()
877 raise
878
Matt Riedemann2d005be2014-05-27 10:52:35 -0700879 def _check_tenant_network_connectivity(self, server,
880 username,
881 private_key,
882 should_connect=True,
883 servers_for_debug=None):
884 if not CONF.network.tenant_networks_reachable:
885 msg = 'Tenant networks not configured to be reachable.'
886 LOG.info(msg)
887 return
888 # The target login is assumed to have been configured for
889 # key-based authentication by cloud-init.
890 try:
891 for net_name, ip_addresses in server.networks.iteritems():
892 for ip_address in ip_addresses:
893 self._check_vm_connectivity(ip_address,
894 username,
895 private_key,
896 should_connect=should_connect)
897 except Exception:
898 LOG.exception('Tenant network connectivity check failed')
899 self._log_console_output(servers_for_debug)
900 debug.log_net_debug()
901 raise
902
Yair Fried3097dc12014-01-26 08:46:43 +0200903 def _check_remote_connectivity(self, source, dest, should_succeed=True):
904 """
905 check ping server via source ssh connection
906
907 :param source: RemoteClient: an ssh connection from which to ping
908 :param dest: and IP to ping against
909 :param should_succeed: boolean should ping succeed or not
910 :returns: boolean -- should_succeed == ping
911 :returns: ping is false if ping failed
912 """
913 def ping_remote():
914 try:
915 source.ping_host(dest)
916 except exceptions.SSHExecCommandFailed:
917 LOG.exception('Failed to ping host via ssh connection')
918 return not should_succeed
919 return should_succeed
920
921 return tempest.test.call_until_true(ping_remote,
922 CONF.compute.ping_timeout,
923 1)
924
Yair Friedeb69f3f2013-10-10 13:18:16 +0300925 def _create_security_group_nova(self, client=None,
926 namestart='secgroup-smoke-',
927 tenant_id=None):
928 if client is None:
929 client = self.compute_client
930 # Create security group
931 sg_name = data_utils.rand_name(namestart)
932 sg_desc = sg_name + " description"
933 secgroup = client.security_groups.create(sg_name, sg_desc)
934 self.assertEqual(secgroup.name, sg_name)
935 self.assertEqual(secgroup.description, sg_desc)
936 self.set_resource(sg_name, secgroup)
937
938 # Add rules to the security group
939 self._create_loginable_secgroup_rule_nova(client, secgroup.id)
940
941 return secgroup
942
943 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