blob: a2bd1b0b0958d7a2bb78505fcda4d4ea4c0d0bbd [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.
250 if e.__class__.__name__ == 'NotFound':
251 continue
252 raise
253
254 def is_deletion_complete():
255 # Deletion testing is only required for objects whose
256 # existence cannot be checked via retrieval.
257 if isinstance(thing, dict):
258 return True
259 try:
260 thing.get()
261 except Exception as e:
262 # Clients are expected to return an exception
263 # called 'NotFound' if retrieval fails.
264 if e.__class__.__name__ == 'NotFound':
265 return True
266 raise
267 return False
268
269 # Block until resource deletion has completed or timed-out
270 tempest.test.call_until_true(is_deletion_complete, 10, 1)
Matthew Treinishb86cda92013-07-29 11:22:23 -0400271 cls.isolated_creds.clear_isolated_creds()
272 super(OfficialClientTest, cls).tearDownClass()
Sean Dague6dbc6da2013-05-08 17:49:46 -0400273
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400274 @classmethod
275 def set_resource(cls, key, thing):
276 LOG.debug("Adding %r to shared resources of %s" %
277 (thing, cls.__name__))
278 cls.resource_keys[key] = thing
279 cls.os_resources.append(thing)
280
281 @classmethod
282 def get_resource(cls, key):
283 return cls.resource_keys[key]
284
285 @classmethod
286 def remove_resource(cls, key):
287 thing = cls.resource_keys[key]
288 cls.os_resources.remove(thing)
289 del cls.resource_keys[key]
290
Steve Bakerefde7612013-09-30 11:29:23 +1300291 def status_timeout(self, things, thing_id, expected_status,
292 error_status='ERROR',
293 not_found_exception=nova_exceptions.NotFound):
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400294 """
295 Given a thing and an expected status, do a loop, sleeping
296 for a configurable amount of time, checking for the
297 expected status to show. At any time, if the returned
298 status of the thing is ERROR, fail out.
299 """
Steve Bakerefde7612013-09-30 11:29:23 +1300300 self._status_timeout(things, thing_id,
301 expected_status=expected_status,
302 error_status=error_status,
303 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900304
Steve Bakerefde7612013-09-30 11:29:23 +1300305 def delete_timeout(self, things, thing_id,
306 error_status='ERROR',
307 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900308 """
309 Given a thing, do a loop, sleeping
310 for a configurable amount of time, checking for the
311 deleted status to show. At any time, if the returned
312 status of the thing is ERROR, fail out.
313 """
314 self._status_timeout(things,
315 thing_id,
Steve Bakerefde7612013-09-30 11:29:23 +1300316 allow_notfound=True,
317 error_status=error_status,
318 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900319
320 def _status_timeout(self,
321 things,
322 thing_id,
323 expected_status=None,
Steve Bakerefde7612013-09-30 11:29:23 +1300324 allow_notfound=False,
325 error_status='ERROR',
326 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900327
328 log_status = expected_status if expected_status else ''
329 if allow_notfound:
330 log_status += ' or NotFound' if log_status != '' else 'NotFound'
331
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400332 def check_status():
333 # python-novaclient has resources available to its client
334 # that all implement a get() method taking an identifier
335 # for the singular resource to retrieve.
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900336 try:
337 thing = things.get(thing_id)
Steve Bakerefde7612013-09-30 11:29:23 +1300338 except not_found_exception:
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900339 if allow_notfound:
340 return True
341 else:
342 raise
343
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400344 new_status = thing.status
Brent Eaglesc26d4522013-12-02 13:28:49 -0500345
346 # Some components are reporting error status in lower case
347 # so case sensitive comparisons can really mess things
348 # up.
349 if new_status.lower() == error_status.lower():
Giulio Fidente92f77192013-08-26 17:13:28 +0200350 message = "%s failed to get to expected status. \
Steve Bakerefde7612013-09-30 11:29:23 +1300351 In %s state." % (thing, new_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200352 raise exceptions.BuildErrorException(message)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900353 elif new_status == expected_status and expected_status is not None:
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400354 return True # All good.
355 LOG.debug("Waiting for %s to get to %s status. "
356 "Currently in %s status",
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900357 thing, log_status, new_status)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400358 if not tempest.test.call_until_true(
359 check_status,
360 self.config.compute.build_timeout,
361 self.config.compute.build_interval):
Giulio Fidente92f77192013-08-26 17:13:28 +0200362 message = "Timed out waiting for thing %s \
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900363 to become %s" % (thing_id, log_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200364 raise exceptions.TimeoutException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400365
Yair Friedeb69f3f2013-10-10 13:18:16 +0300366 def _create_loginable_secgroup_rule_nova(self, client=None,
367 secgroup_id=None):
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900368 if client is None:
369 client = self.compute_client
370 if secgroup_id is None:
371 sgs = client.security_groups.list()
372 for sg in sgs:
373 if sg.name == 'default':
374 secgroup_id = sg.id
375
376 # These rules are intended to permit inbound ssh and icmp
377 # traffic from all sources, so no group_id is provided.
378 # Setting a group_id would only permit traffic from ports
379 # belonging to the same security group.
380 rulesets = [
381 {
382 # ssh
383 'ip_protocol': 'tcp',
384 'from_port': 22,
385 'to_port': 22,
386 'cidr': '0.0.0.0/0',
387 },
388 {
389 # ping
390 'ip_protocol': 'icmp',
391 'from_port': -1,
392 'to_port': -1,
393 'cidr': '0.0.0.0/0',
394 }
395 ]
Yair Friedeb69f3f2013-10-10 13:18:16 +0300396 rules = list()
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900397 for ruleset in rulesets:
398 sg_rule = client.security_group_rules.create(secgroup_id,
399 **ruleset)
400 self.set_resource(sg_rule.id, sg_rule)
Yair Friedeb69f3f2013-10-10 13:18:16 +0300401 rules.append(sg_rule)
402 return rules
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900403
Giulio Fidente61cadca2013-09-24 18:33:37 +0200404 def create_server(self, client=None, name=None, image=None, flavor=None,
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900405 create_kwargs={}):
Giulio Fidente61cadca2013-09-24 18:33:37 +0200406 if client is None:
407 client = self.compute_client
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900408 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900409 name = data_utils.rand_name('scenario-server-')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900410 if image is None:
411 image = self.config.compute.image_ref
412 if flavor is None:
413 flavor = self.config.compute.flavor_ref
414 LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
415 name, image, flavor)
416 server = client.servers.create(name, image, flavor, **create_kwargs)
Giulio Fidente92f77192013-08-26 17:13:28 +0200417 self.assertEqual(server.name, name)
418 self.set_resource(name, server)
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900419 self.status_timeout(client.servers, server.id, 'ACTIVE')
420 # The instance retrieved on creation is missing network
421 # details, necessitating retrieval after it becomes active to
422 # ensure correct details.
423 server = client.servers.get(server.id)
424 self.set_resource(name, server)
425 LOG.debug("Created server: %s", server)
426 return server
427
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900428 def create_volume(self, client=None, size=1, name=None,
429 snapshot_id=None, imageRef=None):
430 if client is None:
431 client = self.volume_client
432 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900433 name = data_utils.rand_name('scenario-volume-')
Eric Windisch2d26f1b2013-09-04 17:52:16 -0700434 LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900435 volume = client.volumes.create(size=size, display_name=name,
436 snapshot_id=snapshot_id,
437 imageRef=imageRef)
438 self.set_resource(name, volume)
439 self.assertEqual(name, volume.display_name)
440 self.status_timeout(client.volumes, volume.id, 'available')
441 LOG.debug("Created volume: %s", volume)
442 return volume
443
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900444 def create_server_snapshot(self, server, compute_client=None,
445 image_client=None, name=None):
446 if compute_client is None:
447 compute_client = self.compute_client
448 if image_client is None:
449 image_client = self.image_client
450 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900451 name = data_utils.rand_name('scenario-snapshot-')
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900452 LOG.debug("Creating a snapshot image for server: %s", server.name)
453 image_id = compute_client.servers.create_image(server, name)
454 self.addCleanup(image_client.images.delete, image_id)
455 self.status_timeout(image_client.images, image_id, 'active')
456 snapshot_image = image_client.images.get(image_id)
Chang Bo Guofc77e932013-09-16 17:38:26 -0700457 self.assertEqual(name, snapshot_image.name)
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900458 LOG.debug("Created snapshot image %s for server %s",
459 snapshot_image.name, server.name)
460 return snapshot_image
461
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900462 def create_keypair(self, client=None, name=None):
463 if client is None:
464 client = self.compute_client
465 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900466 name = data_utils.rand_name('scenario-keypair-')
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900467 keypair = client.keypairs.create(name)
468 self.assertEqual(keypair.name, name)
469 self.set_resource(name, keypair)
470 return keypair
471
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900472 def get_remote_client(self, server_or_ip, username=None, private_key=None):
473 if isinstance(server_or_ip, basestring):
474 ip = server_or_ip
475 else:
476 network_name_for_ssh = self.config.compute.network_for_ssh
477 ip = server_or_ip.networks[network_name_for_ssh][0]
478 if username is None:
479 username = self.config.scenario.ssh_user
480 if private_key is None:
481 private_key = self.keypair.private_key
482 return RemoteClient(ip, username, pkey=private_key)
483
Sean Dague6dbc6da2013-05-08 17:49:46 -0400484
485class NetworkScenarioTest(OfficialClientTest):
486 """
487 Base class for network scenario tests
488 """
489
490 @classmethod
491 def check_preconditions(cls):
Matthew Treinishfaa340d2013-07-19 16:26:21 -0400492 if (cls.config.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400493 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200494 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400495 try:
496 cls.network_client.list_networks()
497 except exc.EndpointNotFound:
498 cls.enabled = False
499 raise
500 else:
501 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400502 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400503 raise cls.skipException(msg)
504
505 @classmethod
506 def setUpClass(cls):
507 super(NetworkScenarioTest, cls).setUpClass()
Miguel Lavalleb8fabc52013-08-23 11:19:57 -0500508 if cls.config.compute.allow_tenant_isolation:
509 cls.tenant_id = cls.isolated_creds.get_primary_tenant().id
510 else:
511 cls.tenant_id = cls.manager._get_identity_client(
512 cls.config.identity.username,
513 cls.config.identity.password,
514 cls.config.identity.tenant_name).tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400515
Sean Dague6dbc6da2013-05-08 17:49:46 -0400516 def _create_network(self, tenant_id, namestart='network-smoke-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900517 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400518 body = dict(
519 network=dict(
520 name=name,
521 tenant_id=tenant_id,
522 ),
523 )
524 result = self.network_client.create_network(body=body)
525 network = net_common.DeletableNetwork(client=self.network_client,
526 **result['network'])
527 self.assertEqual(network.name, name)
528 self.set_resource(name, network)
529 return network
530
531 def _list_networks(self):
532 nets = self.network_client.list_networks()
533 return nets['networks']
534
535 def _list_subnets(self):
536 subnets = self.network_client.list_subnets()
537 return subnets['subnets']
538
539 def _list_routers(self):
540 routers = self.network_client.list_routers()
541 return routers['routers']
542
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000543 def _list_ports(self):
544 ports = self.network_client.list_ports()
545 return ports['ports']
546
547 def _get_tenant_own_network_num(self, tenant_id):
548 nets = self._list_networks()
549 ownnets = [value for value in nets if tenant_id == value['tenant_id']]
550 return len(ownnets)
551
552 def _get_tenant_own_subnet_num(self, tenant_id):
553 subnets = self._list_subnets()
554 ownsubnets = ([value for value in subnets
555 if tenant_id == value['tenant_id']])
556 return len(ownsubnets)
557
558 def _get_tenant_own_port_num(self, tenant_id):
559 ports = self._list_ports()
560 ownports = ([value for value in ports
561 if tenant_id == value['tenant_id']])
562 return len(ownports)
563
Sean Dague6dbc6da2013-05-08 17:49:46 -0400564 def _create_subnet(self, network, namestart='subnet-smoke-'):
565 """
566 Create a subnet for the given network within the cidr block
567 configured for tenant networks.
568 """
569 cfg = self.config.network
570 tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
571 result = None
572 # Repeatedly attempt subnet creation with sequential cidr
573 # blocks until an unallocated block is found.
574 for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
575 body = dict(
576 subnet=dict(
577 ip_version=4,
578 network_id=network.id,
579 tenant_id=network.tenant_id,
580 cidr=str(subnet_cidr),
581 ),
582 )
583 try:
584 result = self.network_client.create_subnet(body=body)
585 break
Mark McClainf2982e82013-07-06 17:48:03 -0400586 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400587 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
588 if not is_overlapping_cidr:
589 raise
590 self.assertIsNotNone(result, 'Unable to allocate tenant network')
591 subnet = net_common.DeletableSubnet(client=self.network_client,
592 **result['subnet'])
593 self.assertEqual(subnet.cidr, str(subnet_cidr))
Masayuki Igawa259c1132013-10-31 17:48:44 +0900594 self.set_resource(data_utils.rand_name(namestart), subnet)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400595 return subnet
596
597 def _create_port(self, network, namestart='port-quotatest-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900598 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400599 body = dict(
600 port=dict(name=name,
601 network_id=network.id,
602 tenant_id=network.tenant_id))
603 result = self.network_client.create_port(body=body)
604 self.assertIsNotNone(result, 'Unable to allocate port')
605 port = net_common.DeletablePort(client=self.network_client,
606 **result['port'])
607 self.set_resource(name, port)
608 return port
609
Sean Dague6dbc6da2013-05-08 17:49:46 -0400610 def _create_floating_ip(self, server, external_network_id):
611 result = self.network_client.list_ports(device_id=server.id)
612 ports = result.get('ports', [])
613 self.assertEqual(len(ports), 1,
614 "Unable to determine which port to target.")
615 port_id = ports[0]['id']
616 body = dict(
617 floatingip=dict(
618 floating_network_id=external_network_id,
619 port_id=port_id,
620 tenant_id=server.tenant_id,
621 )
622 )
623 result = self.network_client.create_floatingip(body=body)
624 floating_ip = net_common.DeletableFloatingIp(
625 client=self.network_client,
626 **result['floatingip'])
Masayuki Igawa259c1132013-10-31 17:48:44 +0900627 self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400628 return floating_ip
629
Yair Fried9a551c42013-12-15 14:59:34 +0200630 def _disassociate_floating_ip(self, floating_ip):
631 """
632 :param floating_ip: type DeletableFloatingIp
633 """
634 floating_ip.update(port_id=None)
635 self.assertEqual(None, floating_ip.port_id)
636 return floating_ip
637
638 def _ping_ip_address(self, ip_address, should_succeed=True):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400639 cmd = ['ping', '-c1', '-w1', ip_address]
640
641 def ping():
642 proc = subprocess.Popen(cmd,
643 stdout=subprocess.PIPE,
644 stderr=subprocess.PIPE)
645 proc.wait()
Yair Fried9a551c42013-12-15 14:59:34 +0200646 return (proc.returncode == 0) == should_succeed
Sean Dague6dbc6da2013-05-08 17:49:46 -0400647
Nachi Ueno6d580be2013-07-24 10:58:11 -0700648 return tempest.test.call_until_true(
649 ping, self.config.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000650
Yair Fried9a551c42013-12-15 14:59:34 +0200651 def _check_vm_connectivity(self, ip_address,
652 username=None,
653 private_key=None,
654 should_connect=True):
655 """
656 :param ip_address: server to test against
657 :param username: server's ssh username
658 :param private_key: server's ssh private key to be used
659 :param should_connect: True/False indicates positive/negative test
660 positive - attempt ping and ssh
661 negative - attempt ping and fail if succeed
662
663 :raises: AssertError if the result of the connectivity check does
664 not match the value of the should_connect param
665 """
666 if should_connect:
667 msg = "Timed out waiting for %s to become reachable" % ip_address
668 else:
669 msg = "ip address %s is reachable" % ip_address
670 self.assertTrue(self._ping_ip_address(ip_address,
671 should_succeed=should_connect),
672 msg=msg)
673 if should_connect:
674 # no need to check ssh for negative connectivity
Attila Fazekasad7ef7d2013-11-20 10:12:53 +0100675 linux_client = self.get_remote_client(ip_address, username,
676 private_key)
677 linux_client.validate_authentication()
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200678
Yair Friedeb69f3f2013-10-10 13:18:16 +0300679 def _create_security_group_nova(self, client=None,
680 namestart='secgroup-smoke-',
681 tenant_id=None):
682 if client is None:
683 client = self.compute_client
684 # Create security group
685 sg_name = data_utils.rand_name(namestart)
686 sg_desc = sg_name + " description"
687 secgroup = client.security_groups.create(sg_name, sg_desc)
688 self.assertEqual(secgroup.name, sg_name)
689 self.assertEqual(secgroup.description, sg_desc)
690 self.set_resource(sg_name, secgroup)
691
692 # Add rules to the security group
693 self._create_loginable_secgroup_rule_nova(client, secgroup.id)
694
695 return secgroup
696
697 def _create_security_group_neutron(self, tenant_id, client=None,
698 namestart='secgroup-smoke-'):
699 if client is None:
700 client = self.network_client
701 secgroup = self._create_empty_security_group(namestart=namestart,
702 client=client,
703 tenant_id=tenant_id)
704
705 # Add rules to the security group
706 rules = self._create_loginable_secgroup_rule_neutron(secgroup=secgroup)
707 for rule in rules:
708 self.assertEqual(tenant_id, rule.tenant_id)
709 self.assertEqual(secgroup.id, rule.security_group_id)
710 return secgroup
711
712 def _create_empty_security_group(self, tenant_id, client=None,
713 namestart='secgroup-smoke-'):
714 """Create a security group without rules.
715
716 Default rules will be created:
717 - IPv4 egress to any
718 - IPv6 egress to any
719
720 :param tenant_id: secgroup will be created in this tenant
721 :returns: DeletableSecurityGroup -- containing the secgroup created
722 """
723 if client is None:
724 client = self.network_client
725 sg_name = data_utils.rand_name(namestart)
726 sg_desc = sg_name + " description"
727 sg_dict = dict(name=sg_name,
728 description=sg_desc)
729 sg_dict['tenant_id'] = tenant_id
730 body = dict(security_group=sg_dict)
731 result = client.create_security_group(body=body)
732 secgroup = net_common.DeletableSecurityGroup(
733 client=client,
734 **result['security_group']
735 )
736 self.assertEqual(secgroup.name, sg_name)
737 self.assertEqual(tenant_id, secgroup.tenant_id)
738 self.assertEqual(secgroup.description, sg_desc)
739 self.set_resource(sg_name, secgroup)
740 return secgroup
741
742 def _default_security_group(self, tenant_id, client=None):
743 """Get default secgroup for given tenant_id.
744
745 :returns: DeletableSecurityGroup -- default secgroup for given tenant
746 """
747 if client is None:
748 client = self.network_client
749 sgs = [
750 sg for sg in client.list_security_groups().values()[0]
751 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
752 ]
753 msg = "No default security group for tenant %s." % (tenant_id)
754 self.assertTrue(len(sgs) > 0, msg)
755 if len(sgs) > 1:
756 msg = "Found %d default security groups" % len(sgs)
757 raise exc.NeutronClientNoUniqueMatch(msg=msg)
758 return net_common.DeletableSecurityGroup(client=client,
759 **sgs[0])
760
761 def _create_security_group_rule(self, client=None, secgroup=None,
762 tenant_id=None, **kwargs):
763 """Create a rule from a dictionary of rule parameters.
764
765 Create a rule in a secgroup. if secgroup not defined will search for
766 default secgroup in tenant_id.
767
768 :param secgroup: type DeletableSecurityGroup.
769 :param secgroup_id: search for secgroup by id
770 default -- choose default secgroup for given tenant_id
771 :param tenant_id: if secgroup not passed -- the tenant in which to
772 search for default secgroup
773 :param kwargs: a dictionary containing rule parameters:
774 for example, to allow incoming ssh:
775 rule = {
776 direction: 'ingress'
777 protocol:'tcp',
778 port_range_min: 22,
779 port_range_max: 22
780 }
781 """
782 if client is None:
783 client = self.network_client
784 if secgroup is None:
785 secgroup = self._default_security_group(tenant_id)
786
787 ruleset = dict(security_group_id=secgroup.id,
788 tenant_id=secgroup.tenant_id,
789 )
790 ruleset.update(kwargs)
791
792 body = dict(security_group_rule=dict(ruleset))
793 sg_rule = client.create_security_group_rule(body=body)
794 sg_rule = net_common.DeletableSecurityGroupRule(
795 client=client,
796 **sg_rule['security_group_rule']
797 )
798 self.set_resource(sg_rule.id, sg_rule)
799 self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
800 self.assertEqual(secgroup.id, sg_rule.security_group_id)
801
802 return sg_rule
803
804 def _create_loginable_secgroup_rule_neutron(self, client=None,
805 secgroup=None):
806 """These rules are intended to permit inbound ssh and icmp
807 traffic from all sources, so no group_id is provided.
808 Setting a group_id would only permit traffic from ports
809 belonging to the same security group.
810 """
811
812 if client is None:
813 client = self.network_client
814 rules = []
815 rulesets = [
816 dict(
817 # ssh
818 protocol='tcp',
819 port_range_min=22,
820 port_range_max=22,
821 ),
822 dict(
823 # ping
824 protocol='icmp',
825 )
826 ]
827 for ruleset in rulesets:
828 for r_direction in ['ingress', 'egress']:
829 ruleset['direction'] = r_direction
830 try:
831 sg_rule = self._create_security_group_rule(
832 client=client, secgroup=secgroup, **ruleset)
833 except exc.NeutronClientException as ex:
834 # if rule already exist - skip rule and continue
835 if not (ex.status_code is 409 and 'Security group rule'
836 ' already exists' in ex.message):
837 raise ex
838 else:
839 self.assertEqual(r_direction, sg_rule.direction)
840 rules.append(sg_rule)
841
842 return rules
843
Yair Fried5f670ab2013-12-09 09:26:51 +0200844 def _ssh_to_server(self, server, private_key):
845 ssh_login = self.config.compute.image_ssh_user
846 return self.get_remote_client(server,
847 username=ssh_login,
848 private_key=private_key)
849
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000850 def _show_quota_network(self, tenant_id):
851 quota = self.network_client.show_quota(tenant_id)
852 return quota['quota']['network']
853
854 def _show_quota_subnet(self, tenant_id):
855 quota = self.network_client.show_quota(tenant_id)
856 return quota['quota']['subnet']
857
858 def _show_quota_port(self, tenant_id):
859 quota = self.network_client.show_quota(tenant_id)
860 return quota['quota']['port']
861
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200862
863class OrchestrationScenarioTest(OfficialClientTest):
864 """
865 Base class for orchestration scenario tests
866 """
867
868 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -0700869 def setUpClass(cls):
870 super(OrchestrationScenarioTest, cls).setUpClass()
871 if not cls.config.service_available.heat:
872 raise cls.skipException("Heat support is required")
873
874 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200875 def credentials(cls):
876 username = cls.config.identity.admin_username
877 password = cls.config.identity.admin_password
878 tenant_name = cls.config.identity.tenant_name
879 return username, tenant_name, password
880
881 def _load_template(self, base_file, file_name):
882 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
883 file_name)
884 with open(filepath) as f:
885 return f.read()
886
887 @classmethod
888 def _stack_rand_name(cls):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900889 return data_utils.rand_name(cls.__name__ + '-')
Steve Baker80252da2013-09-25 13:29:10 +1200890
891 @classmethod
892 def _get_default_network(cls):
893 networks = cls.network_client.list_networks()
894 for net in networks['networks']:
895 if net['name'] == cls.config.compute.fixed_network_name:
896 return net