blob: eeed389e9de2f38019e2a9bbcfcf1c80aa106fd8 [file] [log] [blame]
Sean Dague6dbc6da2013-05-08 17:49:46 -04001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
ZhiQiang Fan39f97222013-09-20 04:49:44 +08003# Copyright 2012 OpenStack Foundation
Sean Dague6dbc6da2013-05-08 17:49:46 -04004# Copyright 2013 IBM Corp.
5# All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License.
18
Attila Fazekasfb7552a2013-08-27 13:02:26 +020019import logging
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120020import os
Sean Dague6dbc6da2013-05-08 17:49:46 -040021import subprocess
22
23# Default client libs
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +090024import cinderclient.client
Sean Dague6dbc6da2013-05-08 17:49:46 -040025import glanceclient
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120026import heatclient.client
Sean Dague6dbc6da2013-05-08 17:49:46 -040027import keystoneclient.v2_0.client
28import netaddr
Mark McClainf2982e82013-07-06 17:48:03 -040029from neutronclient.common import exceptions as exc
30import neutronclient.v2_0.client
Sean Dague6dbc6da2013-05-08 17:49:46 -040031import novaclient.client
fujioka yuuichi636f8db2013-08-09 12:05:24 +090032from novaclient import exceptions as nova_exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040033
Sean Dague1937d092013-05-17 16:36:38 -040034from tempest.api.network import common as net_common
Matthew Treinishb86cda92013-07-29 11:22:23 -040035from tempest.common import isolated_creds
Masayuki Igawa259c1132013-10-31 17:48:44 +090036from tempest.common.utils import data_utils
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +090037from tempest.common.utils.linux.remote_client import RemoteClient
Giulio Fidente92f77192013-08-26 17:13:28 +020038from tempest import exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040039import tempest.manager
Attila Fazekasfb7552a2013-08-27 13:02:26 +020040from tempest.openstack.common import log
Sean Dague6dbc6da2013-05-08 17:49:46 -040041import tempest.test
Sean Dague6dbc6da2013-05-08 17:49:46 -040042
43
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
54class OfficialClientManager(tempest.manager.Manager):
55 """
56 Manager that provides access to the official python clients for
57 calling various OpenStack APIs.
58 """
59
60 NOVACLIENT_VERSION = '2'
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +090061 CINDERCLIENT_VERSION = '1'
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120062 HEATCLIENT_VERSION = '1'
Sean Dague6dbc6da2013-05-08 17:49:46 -040063
Matthew Treinishb86cda92013-07-29 11:22:23 -040064 def __init__(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -040065 super(OfficialClientManager, self).__init__()
Matthew Treinishb86cda92013-07-29 11:22:23 -040066 self.compute_client = self._get_compute_client(username,
67 password,
68 tenant_name)
69 self.identity_client = self._get_identity_client(username,
70 password,
71 tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040072 self.image_client = self._get_image_client()
Sean Dague6dbc6da2013-05-08 17:49:46 -040073 self.network_client = self._get_network_client()
Matthew Treinishb86cda92013-07-29 11:22:23 -040074 self.volume_client = self._get_volume_client(username,
75 password,
76 tenant_name)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120077 self.orchestration_client = self._get_orchestration_client(
78 username,
79 password,
80 tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040081
Matthew Treinishb86cda92013-07-29 11:22:23 -040082 def _get_compute_client(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -040083 # Novaclient will not execute operations for anyone but the
84 # identified user, so a new client needs to be created for
85 # each user that operations need to be performed for.
Sean Dague43cd9052013-07-19 12:20:04 -040086 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040087
88 auth_url = self.config.identity.uri
89 dscv = self.config.identity.disable_ssl_certificate_validation
Russell Sim1fd81ce2013-11-07 17:04:21 +110090 region = self.config.identity.region
Sean Dague6dbc6da2013-05-08 17:49:46 -040091
92 client_args = (username, password, tenant_name, auth_url)
93
94 # Create our default Nova client to use in testing
95 service_type = self.config.compute.catalog_type
96 return novaclient.client.Client(self.NOVACLIENT_VERSION,
97 *client_args,
98 service_type=service_type,
Russell Sim1fd81ce2013-11-07 17:04:21 +110099 region_name=region,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400100 no_cache=True,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200101 insecure=dscv,
102 http_log_debug=True)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400103
104 def _get_image_client(self):
Matthew Treinishb86cda92013-07-29 11:22:23 -0400105 token = self.identity_client.auth_token
Russell Sim1fd81ce2013-11-07 17:04:21 +1100106 region = self.config.identity.region
Matthew Treinishb86cda92013-07-29 11:22:23 -0400107 endpoint = self.identity_client.service_catalog.url_for(
Russell Sim1fd81ce2013-11-07 17:04:21 +1100108 attr='region', filter_value=region,
Matthew Treinishb86cda92013-07-29 11:22:23 -0400109 service_type='image', endpoint_type='publicURL')
Sean Dague6dbc6da2013-05-08 17:49:46 -0400110 dscv = self.config.identity.disable_ssl_certificate_validation
111 return glanceclient.Client('1', endpoint=endpoint, token=token,
112 insecure=dscv)
113
Matthew Treinishb86cda92013-07-29 11:22:23 -0400114 def _get_volume_client(self, username, password, tenant_name):
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900115 auth_url = self.config.identity.uri
Russell Sim1fd81ce2013-11-07 17:04:21 +1100116 region = self.config.identity.region
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900117 return cinderclient.client.Client(self.CINDERCLIENT_VERSION,
118 username,
119 password,
120 tenant_name,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200121 auth_url,
Russell Sim1fd81ce2013-11-07 17:04:21 +1100122 region_name=region,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200123 http_log_debug=True)
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900124
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200125 def _get_orchestration_client(self, username=None, password=None,
126 tenant_name=None):
127 if not username:
128 username = self.config.identity.admin_username
129 if not password:
130 password = self.config.identity.admin_password
131 if not tenant_name:
132 tenant_name = self.config.identity.tenant_name
133
134 self._validate_credentials(username, password, tenant_name)
135
136 keystone = self._get_identity_client(username, password, tenant_name)
Russell Sim1fd81ce2013-11-07 17:04:21 +1100137 region = self.config.identity.region
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200138 token = keystone.auth_token
139 try:
140 endpoint = keystone.service_catalog.url_for(
Russell Sim1fd81ce2013-11-07 17:04:21 +1100141 attr='region',
142 filter_value=region,
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200143 service_type='orchestration',
144 endpoint_type='publicURL')
145 except keystoneclient.exceptions.EndpointNotFound:
146 return None
147 else:
148 return heatclient.client.Client(self.HEATCLIENT_VERSION,
149 endpoint,
150 token=token,
151 username=username,
152 password=password)
153
Matthew Treinishb86cda92013-07-29 11:22:23 -0400154 def _get_identity_client(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400155 # This identity client is not intended to check the security
156 # of the identity service, so use admin credentials by default.
Sean Dague43cd9052013-07-19 12:20:04 -0400157 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400158
159 auth_url = self.config.identity.uri
160 dscv = self.config.identity.disable_ssl_certificate_validation
161
162 return keystoneclient.v2_0.client.Client(username=username,
163 password=password,
164 tenant_name=tenant_name,
165 auth_url=auth_url,
166 insecure=dscv)
167
168 def _get_network_client(self):
169 # The intended configuration is for the network client to have
170 # admin privileges and indicate for whom resources are being
171 # created via a 'tenant_id' parameter. This will often be
172 # preferable to authenticating as a specific user because
173 # working with certain resources (public routers and networks)
174 # often requires admin privileges anyway.
175 username = self.config.identity.admin_username
176 password = self.config.identity.admin_password
177 tenant_name = self.config.identity.admin_tenant_name
178
Sean Dague43cd9052013-07-19 12:20:04 -0400179 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400180
181 auth_url = self.config.identity.uri
182 dscv = self.config.identity.disable_ssl_certificate_validation
183
Mark McClainf2982e82013-07-06 17:48:03 -0400184 return neutronclient.v2_0.client.Client(username=username,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400185 password=password,
186 tenant_name=tenant_name,
187 auth_url=auth_url,
188 insecure=dscv)
189
190
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400191class OfficialClientTest(tempest.test.BaseTestCase):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400192 """
193 Official Client test base class for scenario testing.
194
195 Official Client tests are tests that have the following characteristics:
196
197 * Test basic operations of an API, typically in an order that
198 a regular user would perform those operations
199 * Test only the correct inputs and action paths -- no fuzz or
200 random input data is sent, only valid inputs.
201 * Use only the default client tool for calling an API
202 """
203
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400204 @classmethod
205 def setUpClass(cls):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200206 super(OfficialClientTest, cls).setUpClass()
Matthew Treinishb86cda92013-07-29 11:22:23 -0400207 cls.isolated_creds = isolated_creds.IsolatedCreds(
208 __name__, tempest_client=False)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200209
210 username, tenant_name, password = cls.credentials()
Matthew Treinishb86cda92013-07-29 11:22:23 -0400211
212 cls.manager = OfficialClientManager(username, password, tenant_name)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400213 cls.compute_client = cls.manager.compute_client
214 cls.image_client = cls.manager.image_client
215 cls.identity_client = cls.manager.identity_client
216 cls.network_client = cls.manager.network_client
217 cls.volume_client = cls.manager.volume_client
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200218 cls.orchestration_client = cls.manager.orchestration_client
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400219 cls.resource_keys = {}
220 cls.os_resources = []
Sean Dague6dbc6da2013-05-08 17:49:46 -0400221
222 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200223 def credentials(cls):
224 if cls.config.compute.allow_tenant_isolation:
225 return cls.isolated_creds.get_primary_creds()
226
227 username = cls.config.identity.username
228 password = cls.config.identity.password
229 tenant_name = cls.config.identity.tenant_name
230 return username, tenant_name, password
231
232 @classmethod
Sean Dague6dbc6da2013-05-08 17:49:46 -0400233 def tearDownClass(cls):
234 # NOTE(jaypipes): Because scenario tests are typically run in a
235 # specific order, and because test methods in scenario tests
236 # generally create resources in a particular order, we destroy
237 # resources in the reverse order in which resources are added to
238 # the scenario test class object
239 while cls.os_resources:
240 thing = cls.os_resources.pop()
241 LOG.debug("Deleting %r from shared resources of %s" %
242 (thing, cls.__name__))
243
244 try:
245 # OpenStack resources are assumed to have a delete()
246 # method which destroys the resource...
247 thing.delete()
248 except Exception as e:
249 # If the resource is already missing, mission accomplished.
Yair Fried4d7efa62013-11-17 17:12:29 +0200250 # add status code as workaround for bug 1247568
251 if e.__class__.__name__ == 'NotFound' or e.status_code == 404:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400252 continue
253 raise
254
255 def is_deletion_complete():
256 # Deletion testing is only required for objects whose
257 # existence cannot be checked via retrieval.
258 if isinstance(thing, dict):
259 return True
260 try:
261 thing.get()
262 except Exception as e:
263 # Clients are expected to return an exception
264 # called 'NotFound' if retrieval fails.
265 if e.__class__.__name__ == 'NotFound':
266 return True
267 raise
268 return False
269
270 # Block until resource deletion has completed or timed-out
271 tempest.test.call_until_true(is_deletion_complete, 10, 1)
Matthew Treinishb86cda92013-07-29 11:22:23 -0400272 cls.isolated_creds.clear_isolated_creds()
273 super(OfficialClientTest, cls).tearDownClass()
Sean Dague6dbc6da2013-05-08 17:49:46 -0400274
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400275 @classmethod
276 def set_resource(cls, key, thing):
277 LOG.debug("Adding %r to shared resources of %s" %
278 (thing, cls.__name__))
279 cls.resource_keys[key] = thing
280 cls.os_resources.append(thing)
281
282 @classmethod
283 def get_resource(cls, key):
284 return cls.resource_keys[key]
285
286 @classmethod
287 def remove_resource(cls, key):
288 thing = cls.resource_keys[key]
289 cls.os_resources.remove(thing)
290 del cls.resource_keys[key]
291
Steve Bakerefde7612013-09-30 11:29:23 +1300292 def status_timeout(self, things, thing_id, expected_status,
293 error_status='ERROR',
294 not_found_exception=nova_exceptions.NotFound):
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400295 """
296 Given a thing and an expected status, do a loop, sleeping
297 for a configurable amount of time, checking for the
298 expected status to show. At any time, if the returned
299 status of the thing is ERROR, fail out.
300 """
Steve Bakerefde7612013-09-30 11:29:23 +1300301 self._status_timeout(things, thing_id,
302 expected_status=expected_status,
303 error_status=error_status,
304 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900305
Steve Bakerefde7612013-09-30 11:29:23 +1300306 def delete_timeout(self, things, thing_id,
307 error_status='ERROR',
308 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900309 """
310 Given a thing, do a loop, sleeping
311 for a configurable amount of time, checking for the
312 deleted status to show. At any time, if the returned
313 status of the thing is ERROR, fail out.
314 """
315 self._status_timeout(things,
316 thing_id,
Steve Bakerefde7612013-09-30 11:29:23 +1300317 allow_notfound=True,
318 error_status=error_status,
319 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900320
321 def _status_timeout(self,
322 things,
323 thing_id,
324 expected_status=None,
Steve Bakerefde7612013-09-30 11:29:23 +1300325 allow_notfound=False,
326 error_status='ERROR',
327 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900328
329 log_status = expected_status if expected_status else ''
330 if allow_notfound:
331 log_status += ' or NotFound' if log_status != '' else 'NotFound'
332
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400333 def check_status():
334 # python-novaclient has resources available to its client
335 # that all implement a get() method taking an identifier
336 # for the singular resource to retrieve.
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900337 try:
338 thing = things.get(thing_id)
Steve Bakerefde7612013-09-30 11:29:23 +1300339 except not_found_exception:
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900340 if allow_notfound:
341 return True
342 else:
343 raise
344
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400345 new_status = thing.status
Brent Eaglesc26d4522013-12-02 13:28:49 -0500346
347 # Some components are reporting error status in lower case
348 # so case sensitive comparisons can really mess things
349 # up.
350 if new_status.lower() == error_status.lower():
Ken'ichi Ohmichiab1496f2013-12-12 22:17:57 +0900351 message = ("%s failed to get to expected status. "
352 "In %s state.") % (thing, new_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200353 raise exceptions.BuildErrorException(message)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900354 elif new_status == expected_status and expected_status is not None:
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400355 return True # All good.
356 LOG.debug("Waiting for %s to get to %s status. "
357 "Currently in %s status",
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900358 thing, log_status, new_status)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400359 if not tempest.test.call_until_true(
360 check_status,
361 self.config.compute.build_timeout,
362 self.config.compute.build_interval):
Ken'ichi Ohmichiab1496f2013-12-12 22:17:57 +0900363 message = ("Timed out waiting for thing %s "
364 "to become %s") % (thing_id, log_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200365 raise exceptions.TimeoutException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400366
Yair Friedeb69f3f2013-10-10 13:18:16 +0300367 def _create_loginable_secgroup_rule_nova(self, client=None,
368 secgroup_id=None):
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900369 if client is None:
370 client = self.compute_client
371 if secgroup_id is None:
372 sgs = client.security_groups.list()
373 for sg in sgs:
374 if sg.name == 'default':
375 secgroup_id = sg.id
376
377 # These rules are intended to permit inbound ssh and icmp
378 # traffic from all sources, so no group_id is provided.
379 # Setting a group_id would only permit traffic from ports
380 # belonging to the same security group.
381 rulesets = [
382 {
383 # ssh
384 'ip_protocol': 'tcp',
385 'from_port': 22,
386 'to_port': 22,
387 'cidr': '0.0.0.0/0',
388 },
389 {
390 # ping
391 'ip_protocol': 'icmp',
392 'from_port': -1,
393 'to_port': -1,
394 'cidr': '0.0.0.0/0',
395 }
396 ]
Yair Friedeb69f3f2013-10-10 13:18:16 +0300397 rules = list()
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900398 for ruleset in rulesets:
399 sg_rule = client.security_group_rules.create(secgroup_id,
400 **ruleset)
401 self.set_resource(sg_rule.id, sg_rule)
Yair Friedeb69f3f2013-10-10 13:18:16 +0300402 rules.append(sg_rule)
403 return rules
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900404
Giulio Fidente61cadca2013-09-24 18:33:37 +0200405 def create_server(self, client=None, name=None, image=None, flavor=None,
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900406 create_kwargs={}):
Giulio Fidente61cadca2013-09-24 18:33:37 +0200407 if client is None:
408 client = self.compute_client
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900409 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900410 name = data_utils.rand_name('scenario-server-')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900411 if image is None:
412 image = self.config.compute.image_ref
413 if flavor is None:
414 flavor = self.config.compute.flavor_ref
415 LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
416 name, image, flavor)
417 server = client.servers.create(name, image, flavor, **create_kwargs)
Giulio Fidente92f77192013-08-26 17:13:28 +0200418 self.assertEqual(server.name, name)
419 self.set_resource(name, server)
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900420 self.status_timeout(client.servers, server.id, 'ACTIVE')
421 # The instance retrieved on creation is missing network
422 # details, necessitating retrieval after it becomes active to
423 # ensure correct details.
424 server = client.servers.get(server.id)
425 self.set_resource(name, server)
426 LOG.debug("Created server: %s", server)
427 return server
428
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900429 def create_volume(self, client=None, size=1, name=None,
430 snapshot_id=None, imageRef=None):
431 if client is None:
432 client = self.volume_client
433 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900434 name = data_utils.rand_name('scenario-volume-')
Eric Windisch2d26f1b2013-09-04 17:52:16 -0700435 LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900436 volume = client.volumes.create(size=size, display_name=name,
437 snapshot_id=snapshot_id,
438 imageRef=imageRef)
439 self.set_resource(name, volume)
440 self.assertEqual(name, volume.display_name)
441 self.status_timeout(client.volumes, volume.id, 'available')
442 LOG.debug("Created volume: %s", volume)
443 return volume
444
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900445 def create_server_snapshot(self, server, compute_client=None,
446 image_client=None, name=None):
447 if compute_client is None:
448 compute_client = self.compute_client
449 if image_client is None:
450 image_client = self.image_client
451 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900452 name = data_utils.rand_name('scenario-snapshot-')
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900453 LOG.debug("Creating a snapshot image for server: %s", server.name)
454 image_id = compute_client.servers.create_image(server, name)
455 self.addCleanup(image_client.images.delete, image_id)
456 self.status_timeout(image_client.images, image_id, 'active')
457 snapshot_image = image_client.images.get(image_id)
Chang Bo Guofc77e932013-09-16 17:38:26 -0700458 self.assertEqual(name, snapshot_image.name)
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900459 LOG.debug("Created snapshot image %s for server %s",
460 snapshot_image.name, server.name)
461 return snapshot_image
462
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900463 def create_keypair(self, client=None, name=None):
464 if client is None:
465 client = self.compute_client
466 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900467 name = data_utils.rand_name('scenario-keypair-')
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900468 keypair = client.keypairs.create(name)
469 self.assertEqual(keypair.name, name)
470 self.set_resource(name, keypair)
471 return keypair
472
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900473 def get_remote_client(self, server_or_ip, username=None, private_key=None):
474 if isinstance(server_or_ip, basestring):
475 ip = server_or_ip
476 else:
477 network_name_for_ssh = self.config.compute.network_for_ssh
478 ip = server_or_ip.networks[network_name_for_ssh][0]
479 if username is None:
480 username = self.config.scenario.ssh_user
481 if private_key is None:
482 private_key = self.keypair.private_key
483 return RemoteClient(ip, username, pkey=private_key)
484
Sean Dague6dbc6da2013-05-08 17:49:46 -0400485
486class NetworkScenarioTest(OfficialClientTest):
487 """
488 Base class for network scenario tests
489 """
490
491 @classmethod
492 def check_preconditions(cls):
Matthew Treinishfaa340d2013-07-19 16:26:21 -0400493 if (cls.config.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400494 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200495 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400496 try:
497 cls.network_client.list_networks()
498 except exc.EndpointNotFound:
499 cls.enabled = False
500 raise
501 else:
502 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400503 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400504 raise cls.skipException(msg)
505
506 @classmethod
507 def setUpClass(cls):
508 super(NetworkScenarioTest, cls).setUpClass()
Miguel Lavalleb8fabc52013-08-23 11:19:57 -0500509 if cls.config.compute.allow_tenant_isolation:
510 cls.tenant_id = cls.isolated_creds.get_primary_tenant().id
511 else:
512 cls.tenant_id = cls.manager._get_identity_client(
513 cls.config.identity.username,
514 cls.config.identity.password,
515 cls.config.identity.tenant_name).tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400516
Sean Dague6dbc6da2013-05-08 17:49:46 -0400517 def _create_network(self, tenant_id, namestart='network-smoke-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900518 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400519 body = dict(
520 network=dict(
521 name=name,
522 tenant_id=tenant_id,
523 ),
524 )
525 result = self.network_client.create_network(body=body)
526 network = net_common.DeletableNetwork(client=self.network_client,
527 **result['network'])
528 self.assertEqual(network.name, name)
529 self.set_resource(name, network)
530 return network
531
532 def _list_networks(self):
533 nets = self.network_client.list_networks()
534 return nets['networks']
535
536 def _list_subnets(self):
537 subnets = self.network_client.list_subnets()
538 return subnets['subnets']
539
540 def _list_routers(self):
541 routers = self.network_client.list_routers()
542 return routers['routers']
543
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000544 def _list_ports(self):
545 ports = self.network_client.list_ports()
546 return ports['ports']
547
548 def _get_tenant_own_network_num(self, tenant_id):
549 nets = self._list_networks()
550 ownnets = [value for value in nets if tenant_id == value['tenant_id']]
551 return len(ownnets)
552
553 def _get_tenant_own_subnet_num(self, tenant_id):
554 subnets = self._list_subnets()
555 ownsubnets = ([value for value in subnets
556 if tenant_id == value['tenant_id']])
557 return len(ownsubnets)
558
559 def _get_tenant_own_port_num(self, tenant_id):
560 ports = self._list_ports()
561 ownports = ([value for value in ports
562 if tenant_id == value['tenant_id']])
563 return len(ownports)
564
Sean Dague6dbc6da2013-05-08 17:49:46 -0400565 def _create_subnet(self, network, namestart='subnet-smoke-'):
566 """
567 Create a subnet for the given network within the cidr block
568 configured for tenant networks.
569 """
570 cfg = self.config.network
571 tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
572 result = None
573 # Repeatedly attempt subnet creation with sequential cidr
574 # blocks until an unallocated block is found.
575 for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
576 body = dict(
577 subnet=dict(
578 ip_version=4,
579 network_id=network.id,
580 tenant_id=network.tenant_id,
581 cidr=str(subnet_cidr),
582 ),
583 )
584 try:
585 result = self.network_client.create_subnet(body=body)
586 break
Mark McClainf2982e82013-07-06 17:48:03 -0400587 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400588 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
589 if not is_overlapping_cidr:
590 raise
591 self.assertIsNotNone(result, 'Unable to allocate tenant network')
592 subnet = net_common.DeletableSubnet(client=self.network_client,
593 **result['subnet'])
594 self.assertEqual(subnet.cidr, str(subnet_cidr))
Masayuki Igawa259c1132013-10-31 17:48:44 +0900595 self.set_resource(data_utils.rand_name(namestart), subnet)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400596 return subnet
597
598 def _create_port(self, network, namestart='port-quotatest-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900599 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400600 body = dict(
601 port=dict(name=name,
602 network_id=network.id,
603 tenant_id=network.tenant_id))
604 result = self.network_client.create_port(body=body)
605 self.assertIsNotNone(result, 'Unable to allocate port')
606 port = net_common.DeletablePort(client=self.network_client,
607 **result['port'])
608 self.set_resource(name, port)
609 return port
610
Sean Dague6dbc6da2013-05-08 17:49:46 -0400611 def _create_floating_ip(self, server, external_network_id):
612 result = self.network_client.list_ports(device_id=server.id)
613 ports = result.get('ports', [])
614 self.assertEqual(len(ports), 1,
615 "Unable to determine which port to target.")
616 port_id = ports[0]['id']
617 body = dict(
618 floatingip=dict(
619 floating_network_id=external_network_id,
620 port_id=port_id,
621 tenant_id=server.tenant_id,
622 )
623 )
624 result = self.network_client.create_floatingip(body=body)
625 floating_ip = net_common.DeletableFloatingIp(
626 client=self.network_client,
627 **result['floatingip'])
Masayuki Igawa259c1132013-10-31 17:48:44 +0900628 self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400629 return floating_ip
630
Yair Fried9a551c42013-12-15 14:59:34 +0200631 def _disassociate_floating_ip(self, floating_ip):
632 """
633 :param floating_ip: type DeletableFloatingIp
634 """
635 floating_ip.update(port_id=None)
636 self.assertEqual(None, floating_ip.port_id)
637 return floating_ip
638
639 def _ping_ip_address(self, ip_address, should_succeed=True):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400640 cmd = ['ping', '-c1', '-w1', ip_address]
641
642 def ping():
643 proc = subprocess.Popen(cmd,
644 stdout=subprocess.PIPE,
645 stderr=subprocess.PIPE)
646 proc.wait()
Yair Fried9a551c42013-12-15 14:59:34 +0200647 return (proc.returncode == 0) == should_succeed
Sean Dague6dbc6da2013-05-08 17:49:46 -0400648
Nachi Ueno6d580be2013-07-24 10:58:11 -0700649 return tempest.test.call_until_true(
650 ping, self.config.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000651
Yair Fried9a551c42013-12-15 14:59:34 +0200652 def _check_vm_connectivity(self, ip_address,
653 username=None,
654 private_key=None,
655 should_connect=True):
656 """
657 :param ip_address: server to test against
658 :param username: server's ssh username
659 :param private_key: server's ssh private key to be used
660 :param should_connect: True/False indicates positive/negative test
661 positive - attempt ping and ssh
662 negative - attempt ping and fail if succeed
663
664 :raises: AssertError if the result of the connectivity check does
665 not match the value of the should_connect param
666 """
667 if should_connect:
668 msg = "Timed out waiting for %s to become reachable" % ip_address
669 else:
670 msg = "ip address %s is reachable" % ip_address
671 self.assertTrue(self._ping_ip_address(ip_address,
672 should_succeed=should_connect),
673 msg=msg)
674 if should_connect:
675 # no need to check ssh for negative connectivity
Attila Fazekasad7ef7d2013-11-20 10:12:53 +0100676 linux_client = self.get_remote_client(ip_address, username,
677 private_key)
678 linux_client.validate_authentication()
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200679
Yair Friedeb69f3f2013-10-10 13:18:16 +0300680 def _create_security_group_nova(self, client=None,
681 namestart='secgroup-smoke-',
682 tenant_id=None):
683 if client is None:
684 client = self.compute_client
685 # Create security group
686 sg_name = data_utils.rand_name(namestart)
687 sg_desc = sg_name + " description"
688 secgroup = client.security_groups.create(sg_name, sg_desc)
689 self.assertEqual(secgroup.name, sg_name)
690 self.assertEqual(secgroup.description, sg_desc)
691 self.set_resource(sg_name, secgroup)
692
693 # Add rules to the security group
694 self._create_loginable_secgroup_rule_nova(client, secgroup.id)
695
696 return secgroup
697
698 def _create_security_group_neutron(self, tenant_id, client=None,
699 namestart='secgroup-smoke-'):
700 if client is None:
701 client = self.network_client
702 secgroup = self._create_empty_security_group(namestart=namestart,
703 client=client,
704 tenant_id=tenant_id)
705
706 # Add rules to the security group
707 rules = self._create_loginable_secgroup_rule_neutron(secgroup=secgroup)
708 for rule in rules:
709 self.assertEqual(tenant_id, rule.tenant_id)
710 self.assertEqual(secgroup.id, rule.security_group_id)
711 return secgroup
712
713 def _create_empty_security_group(self, tenant_id, client=None,
714 namestart='secgroup-smoke-'):
715 """Create a security group without rules.
716
717 Default rules will be created:
718 - IPv4 egress to any
719 - IPv6 egress to any
720
721 :param tenant_id: secgroup will be created in this tenant
722 :returns: DeletableSecurityGroup -- containing the secgroup created
723 """
724 if client is None:
725 client = self.network_client
726 sg_name = data_utils.rand_name(namestart)
727 sg_desc = sg_name + " description"
728 sg_dict = dict(name=sg_name,
729 description=sg_desc)
730 sg_dict['tenant_id'] = tenant_id
731 body = dict(security_group=sg_dict)
732 result = client.create_security_group(body=body)
733 secgroup = net_common.DeletableSecurityGroup(
734 client=client,
735 **result['security_group']
736 )
737 self.assertEqual(secgroup.name, sg_name)
738 self.assertEqual(tenant_id, secgroup.tenant_id)
739 self.assertEqual(secgroup.description, sg_desc)
740 self.set_resource(sg_name, secgroup)
741 return secgroup
742
743 def _default_security_group(self, tenant_id, client=None):
744 """Get default secgroup for given tenant_id.
745
746 :returns: DeletableSecurityGroup -- default secgroup for given tenant
747 """
748 if client is None:
749 client = self.network_client
750 sgs = [
751 sg for sg in client.list_security_groups().values()[0]
752 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
753 ]
754 msg = "No default security group for tenant %s." % (tenant_id)
755 self.assertTrue(len(sgs) > 0, msg)
756 if len(sgs) > 1:
757 msg = "Found %d default security groups" % len(sgs)
758 raise exc.NeutronClientNoUniqueMatch(msg=msg)
759 return net_common.DeletableSecurityGroup(client=client,
760 **sgs[0])
761
762 def _create_security_group_rule(self, client=None, secgroup=None,
763 tenant_id=None, **kwargs):
764 """Create a rule from a dictionary of rule parameters.
765
766 Create a rule in a secgroup. if secgroup not defined will search for
767 default secgroup in tenant_id.
768
769 :param secgroup: type DeletableSecurityGroup.
770 :param secgroup_id: search for secgroup by id
771 default -- choose default secgroup for given tenant_id
772 :param tenant_id: if secgroup not passed -- the tenant in which to
773 search for default secgroup
774 :param kwargs: a dictionary containing rule parameters:
775 for example, to allow incoming ssh:
776 rule = {
777 direction: 'ingress'
778 protocol:'tcp',
779 port_range_min: 22,
780 port_range_max: 22
781 }
782 """
783 if client is None:
784 client = self.network_client
785 if secgroup is None:
786 secgroup = self._default_security_group(tenant_id)
787
788 ruleset = dict(security_group_id=secgroup.id,
789 tenant_id=secgroup.tenant_id,
790 )
791 ruleset.update(kwargs)
792
793 body = dict(security_group_rule=dict(ruleset))
794 sg_rule = client.create_security_group_rule(body=body)
795 sg_rule = net_common.DeletableSecurityGroupRule(
796 client=client,
797 **sg_rule['security_group_rule']
798 )
799 self.set_resource(sg_rule.id, sg_rule)
800 self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
801 self.assertEqual(secgroup.id, sg_rule.security_group_id)
802
803 return sg_rule
804
805 def _create_loginable_secgroup_rule_neutron(self, client=None,
806 secgroup=None):
807 """These rules are intended to permit inbound ssh and icmp
808 traffic from all sources, so no group_id is provided.
809 Setting a group_id would only permit traffic from ports
810 belonging to the same security group.
811 """
812
813 if client is None:
814 client = self.network_client
815 rules = []
816 rulesets = [
817 dict(
818 # ssh
819 protocol='tcp',
820 port_range_min=22,
821 port_range_max=22,
822 ),
823 dict(
824 # ping
825 protocol='icmp',
826 )
827 ]
828 for ruleset in rulesets:
829 for r_direction in ['ingress', 'egress']:
830 ruleset['direction'] = r_direction
831 try:
832 sg_rule = self._create_security_group_rule(
833 client=client, secgroup=secgroup, **ruleset)
834 except exc.NeutronClientException as ex:
835 # if rule already exist - skip rule and continue
836 if not (ex.status_code is 409 and 'Security group rule'
837 ' already exists' in ex.message):
838 raise ex
839 else:
840 self.assertEqual(r_direction, sg_rule.direction)
841 rules.append(sg_rule)
842
843 return rules
844
Yair Fried5f670ab2013-12-09 09:26:51 +0200845 def _ssh_to_server(self, server, private_key):
846 ssh_login = self.config.compute.image_ssh_user
847 return self.get_remote_client(server,
848 username=ssh_login,
849 private_key=private_key)
850
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000851 def _show_quota_network(self, tenant_id):
852 quota = self.network_client.show_quota(tenant_id)
853 return quota['quota']['network']
854
855 def _show_quota_subnet(self, tenant_id):
856 quota = self.network_client.show_quota(tenant_id)
857 return quota['quota']['subnet']
858
859 def _show_quota_port(self, tenant_id):
860 quota = self.network_client.show_quota(tenant_id)
861 return quota['quota']['port']
862
Yair Fried4d7efa62013-11-17 17:12:29 +0200863 def _get_router(self, tenant_id):
864 """Retrieve a router for the given tenant id.
865
866 If a public router has been configured, it will be returned.
867
868 If a public router has not been configured, but a public
869 network has, a tenant router will be created and returned that
870 routes traffic to the public network.
871 """
872 router_id = self.config.network.public_router_id
873 network_id = self.config.network.public_network_id
874 if router_id:
875 result = self.network_client.show_router(router_id)
876 return net_common.AttributeDict(**result['router'])
877 elif network_id:
878 router = self._create_router(tenant_id)
879 router.add_gateway(network_id)
880 return router
881 else:
882 raise Exception("Neither of 'public_router_id' or "
883 "'public_network_id' has been defined.")
884
885 def _create_router(self, tenant_id, namestart='router-smoke-'):
886 name = data_utils.rand_name(namestart)
887 body = dict(
888 router=dict(
889 name=name,
890 admin_state_up=True,
891 tenant_id=tenant_id,
892 ),
893 )
894 result = self.network_client.create_router(body=body)
895 router = net_common.DeletableRouter(client=self.network_client,
896 **result['router'])
897 self.assertEqual(router.name, name)
898 self.set_resource(name, router)
899 return router
900
901 def _create_networks(self, tenant_id=None):
902 """Create a network with a subnet connected to a router.
903
904 :returns: network, subnet, router
905 """
906 if tenant_id is None:
907 tenant_id = self.tenant_id
908 network = self._create_network(tenant_id)
909 router = self._get_router(tenant_id)
910 subnet = self._create_subnet(network)
911 subnet.add_to_router(router.id)
912 self.networks.append(network)
913 self.subnets.append(subnet)
914 self.routers.append(router)
915 return network, subnet, router
916
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200917
918class OrchestrationScenarioTest(OfficialClientTest):
919 """
920 Base class for orchestration scenario tests
921 """
922
923 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -0700924 def setUpClass(cls):
925 super(OrchestrationScenarioTest, cls).setUpClass()
926 if not cls.config.service_available.heat:
927 raise cls.skipException("Heat support is required")
928
929 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200930 def credentials(cls):
931 username = cls.config.identity.admin_username
932 password = cls.config.identity.admin_password
933 tenant_name = cls.config.identity.tenant_name
934 return username, tenant_name, password
935
936 def _load_template(self, base_file, file_name):
937 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
938 file_name)
939 with open(filepath) as f:
940 return f.read()
941
942 @classmethod
943 def _stack_rand_name(cls):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900944 return data_utils.rand_name(cls.__name__ + '-')
Steve Baker80252da2013-09-25 13:29:10 +1200945
946 @classmethod
947 def _get_default_network(cls):
948 networks = cls.network_client.list_networks()
949 for net in networks['networks']:
950 if net['name'] == cls.config.compute.fixed_network_name:
951 return net