blob: 06841e17329c963719f12b6cb67f18c7e033d2d8 [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
Maru Newbyaf292e82013-05-20 21:32:28 +000036from tempest.common import ssh
Masayuki Igawa259c1132013-10-31 17:48:44 +090037from tempest.common.utils import data_utils
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +090038from tempest.common.utils.linux.remote_client import RemoteClient
Giulio Fidente92f77192013-08-26 17:13:28 +020039from tempest import exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040040import tempest.manager
Attila Fazekasfb7552a2013-08-27 13:02:26 +020041from tempest.openstack.common import log
Sean Dague6dbc6da2013-05-08 17:49:46 -040042import tempest.test
Sean Dague6dbc6da2013-05-08 17:49:46 -040043
44
Attila Fazekasfb7552a2013-08-27 13:02:26 +020045LOG = log.getLogger(__name__)
46
47# NOTE(afazekas): Workaround for the stdout logging
48LOG_nova_client = logging.getLogger('novaclient.client')
49LOG_nova_client.addHandler(log.NullHandler())
50
51LOG_cinder_client = logging.getLogger('cinderclient.client')
52LOG_cinder_client.addHandler(log.NullHandler())
Sean Dague6dbc6da2013-05-08 17:49:46 -040053
54
55class OfficialClientManager(tempest.manager.Manager):
56 """
57 Manager that provides access to the official python clients for
58 calling various OpenStack APIs.
59 """
60
61 NOVACLIENT_VERSION = '2'
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +090062 CINDERCLIENT_VERSION = '1'
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120063 HEATCLIENT_VERSION = '1'
Sean Dague6dbc6da2013-05-08 17:49:46 -040064
Matthew Treinishb86cda92013-07-29 11:22:23 -040065 def __init__(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -040066 super(OfficialClientManager, self).__init__()
Matthew Treinishb86cda92013-07-29 11:22:23 -040067 self.compute_client = self._get_compute_client(username,
68 password,
69 tenant_name)
70 self.identity_client = self._get_identity_client(username,
71 password,
72 tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040073 self.image_client = self._get_image_client()
Sean Dague6dbc6da2013-05-08 17:49:46 -040074 self.network_client = self._get_network_client()
Matthew Treinishb86cda92013-07-29 11:22:23 -040075 self.volume_client = self._get_volume_client(username,
76 password,
77 tenant_name)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120078 self.orchestration_client = self._get_orchestration_client(
79 username,
80 password,
81 tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040082
Matthew Treinishb86cda92013-07-29 11:22:23 -040083 def _get_compute_client(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -040084 # Novaclient will not execute operations for anyone but the
85 # identified user, so a new client needs to be created for
86 # each user that operations need to be performed for.
Sean Dague43cd9052013-07-19 12:20:04 -040087 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040088
89 auth_url = self.config.identity.uri
90 dscv = self.config.identity.disable_ssl_certificate_validation
Russell Sim1fd81ce2013-11-07 17:04:21 +110091 region = self.config.identity.region
Sean Dague6dbc6da2013-05-08 17:49:46 -040092
93 client_args = (username, password, tenant_name, auth_url)
94
95 # Create our default Nova client to use in testing
96 service_type = self.config.compute.catalog_type
97 return novaclient.client.Client(self.NOVACLIENT_VERSION,
98 *client_args,
99 service_type=service_type,
Russell Sim1fd81ce2013-11-07 17:04:21 +1100100 region_name=region,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400101 no_cache=True,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200102 insecure=dscv,
103 http_log_debug=True)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400104
105 def _get_image_client(self):
Matthew Treinishb86cda92013-07-29 11:22:23 -0400106 token = self.identity_client.auth_token
Russell Sim1fd81ce2013-11-07 17:04:21 +1100107 region = self.config.identity.region
Matthew Treinishb86cda92013-07-29 11:22:23 -0400108 endpoint = self.identity_client.service_catalog.url_for(
Russell Sim1fd81ce2013-11-07 17:04:21 +1100109 attr='region', filter_value=region,
Matthew Treinishb86cda92013-07-29 11:22:23 -0400110 service_type='image', endpoint_type='publicURL')
Sean Dague6dbc6da2013-05-08 17:49:46 -0400111 dscv = self.config.identity.disable_ssl_certificate_validation
112 return glanceclient.Client('1', endpoint=endpoint, token=token,
113 insecure=dscv)
114
Matthew Treinishb86cda92013-07-29 11:22:23 -0400115 def _get_volume_client(self, username, password, tenant_name):
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900116 auth_url = self.config.identity.uri
Russell Sim1fd81ce2013-11-07 17:04:21 +1100117 region = self.config.identity.region
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900118 return cinderclient.client.Client(self.CINDERCLIENT_VERSION,
119 username,
120 password,
121 tenant_name,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200122 auth_url,
Russell Sim1fd81ce2013-11-07 17:04:21 +1100123 region_name=region,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200124 http_log_debug=True)
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900125
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200126 def _get_orchestration_client(self, username=None, password=None,
127 tenant_name=None):
128 if not username:
129 username = self.config.identity.admin_username
130 if not password:
131 password = self.config.identity.admin_password
132 if not tenant_name:
133 tenant_name = self.config.identity.tenant_name
134
135 self._validate_credentials(username, password, tenant_name)
136
137 keystone = self._get_identity_client(username, password, tenant_name)
Russell Sim1fd81ce2013-11-07 17:04:21 +1100138 region = self.config.identity.region
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200139 token = keystone.auth_token
140 try:
141 endpoint = keystone.service_catalog.url_for(
Russell Sim1fd81ce2013-11-07 17:04:21 +1100142 attr='region',
143 filter_value=region,
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200144 service_type='orchestration',
145 endpoint_type='publicURL')
146 except keystoneclient.exceptions.EndpointNotFound:
147 return None
148 else:
149 return heatclient.client.Client(self.HEATCLIENT_VERSION,
150 endpoint,
151 token=token,
152 username=username,
153 password=password)
154
Matthew Treinishb86cda92013-07-29 11:22:23 -0400155 def _get_identity_client(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400156 # This identity client is not intended to check the security
157 # of the identity service, so use admin credentials by default.
Sean Dague43cd9052013-07-19 12:20:04 -0400158 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400159
160 auth_url = self.config.identity.uri
161 dscv = self.config.identity.disable_ssl_certificate_validation
162
163 return keystoneclient.v2_0.client.Client(username=username,
164 password=password,
165 tenant_name=tenant_name,
166 auth_url=auth_url,
167 insecure=dscv)
168
169 def _get_network_client(self):
170 # The intended configuration is for the network client to have
171 # admin privileges and indicate for whom resources are being
172 # created via a 'tenant_id' parameter. This will often be
173 # preferable to authenticating as a specific user because
174 # working with certain resources (public routers and networks)
175 # often requires admin privileges anyway.
176 username = self.config.identity.admin_username
177 password = self.config.identity.admin_password
178 tenant_name = self.config.identity.admin_tenant_name
179
Sean Dague43cd9052013-07-19 12:20:04 -0400180 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400181
182 auth_url = self.config.identity.uri
183 dscv = self.config.identity.disable_ssl_certificate_validation
184
Mark McClainf2982e82013-07-06 17:48:03 -0400185 return neutronclient.v2_0.client.Client(username=username,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400186 password=password,
187 tenant_name=tenant_name,
188 auth_url=auth_url,
189 insecure=dscv)
190
191
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400192class OfficialClientTest(tempest.test.BaseTestCase):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400193 """
194 Official Client test base class for scenario testing.
195
196 Official Client tests are tests that have the following characteristics:
197
198 * Test basic operations of an API, typically in an order that
199 a regular user would perform those operations
200 * Test only the correct inputs and action paths -- no fuzz or
201 random input data is sent, only valid inputs.
202 * Use only the default client tool for calling an API
203 """
204
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400205 @classmethod
206 def setUpClass(cls):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200207 super(OfficialClientTest, cls).setUpClass()
Matthew Treinishb86cda92013-07-29 11:22:23 -0400208 cls.isolated_creds = isolated_creds.IsolatedCreds(
209 __name__, tempest_client=False)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200210
211 username, tenant_name, password = cls.credentials()
Matthew Treinishb86cda92013-07-29 11:22:23 -0400212
213 cls.manager = OfficialClientManager(username, password, tenant_name)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400214 cls.compute_client = cls.manager.compute_client
215 cls.image_client = cls.manager.image_client
216 cls.identity_client = cls.manager.identity_client
217 cls.network_client = cls.manager.network_client
218 cls.volume_client = cls.manager.volume_client
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200219 cls.orchestration_client = cls.manager.orchestration_client
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400220 cls.resource_keys = {}
221 cls.os_resources = []
Sean Dague6dbc6da2013-05-08 17:49:46 -0400222
223 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200224 def credentials(cls):
225 if cls.config.compute.allow_tenant_isolation:
226 return cls.isolated_creds.get_primary_creds()
227
228 username = cls.config.identity.username
229 password = cls.config.identity.password
230 tenant_name = cls.config.identity.tenant_name
231 return username, tenant_name, password
232
233 @classmethod
Sean Dague6dbc6da2013-05-08 17:49:46 -0400234 def tearDownClass(cls):
235 # NOTE(jaypipes): Because scenario tests are typically run in a
236 # specific order, and because test methods in scenario tests
237 # generally create resources in a particular order, we destroy
238 # resources in the reverse order in which resources are added to
239 # the scenario test class object
240 while cls.os_resources:
241 thing = cls.os_resources.pop()
242 LOG.debug("Deleting %r from shared resources of %s" %
243 (thing, cls.__name__))
244
245 try:
246 # OpenStack resources are assumed to have a delete()
247 # method which destroys the resource...
248 thing.delete()
249 except Exception as e:
250 # If the resource is already missing, mission accomplished.
251 if e.__class__.__name__ == 'NotFound':
252 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
Steve Bakerefde7612013-09-30 11:29:23 +1300346 if new_status == error_status:
Giulio Fidente92f77192013-08-26 17:13:28 +0200347 message = "%s failed to get to expected status. \
Steve Bakerefde7612013-09-30 11:29:23 +1300348 In %s state." % (thing, new_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200349 raise exceptions.BuildErrorException(message)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900350 elif new_status == expected_status and expected_status is not None:
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400351 return True # All good.
352 LOG.debug("Waiting for %s to get to %s status. "
353 "Currently in %s status",
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900354 thing, log_status, new_status)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400355 if not tempest.test.call_until_true(
356 check_status,
357 self.config.compute.build_timeout,
358 self.config.compute.build_interval):
Giulio Fidente92f77192013-08-26 17:13:28 +0200359 message = "Timed out waiting for thing %s \
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900360 to become %s" % (thing_id, log_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200361 raise exceptions.TimeoutException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400362
Yair Friedeb69f3f2013-10-10 13:18:16 +0300363 def _create_loginable_secgroup_rule_nova(self, client=None,
364 secgroup_id=None):
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900365 if client is None:
366 client = self.compute_client
367 if secgroup_id is None:
368 sgs = client.security_groups.list()
369 for sg in sgs:
370 if sg.name == 'default':
371 secgroup_id = sg.id
372
373 # These rules are intended to permit inbound ssh and icmp
374 # traffic from all sources, so no group_id is provided.
375 # Setting a group_id would only permit traffic from ports
376 # belonging to the same security group.
377 rulesets = [
378 {
379 # ssh
380 'ip_protocol': 'tcp',
381 'from_port': 22,
382 'to_port': 22,
383 'cidr': '0.0.0.0/0',
384 },
385 {
386 # ping
387 'ip_protocol': 'icmp',
388 'from_port': -1,
389 'to_port': -1,
390 'cidr': '0.0.0.0/0',
391 }
392 ]
Yair Friedeb69f3f2013-10-10 13:18:16 +0300393 rules = list()
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900394 for ruleset in rulesets:
395 sg_rule = client.security_group_rules.create(secgroup_id,
396 **ruleset)
397 self.set_resource(sg_rule.id, sg_rule)
Yair Friedeb69f3f2013-10-10 13:18:16 +0300398 rules.append(sg_rule)
399 return rules
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900400
Giulio Fidente61cadca2013-09-24 18:33:37 +0200401 def create_server(self, client=None, name=None, image=None, flavor=None,
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900402 create_kwargs={}):
Giulio Fidente61cadca2013-09-24 18:33:37 +0200403 if client is None:
404 client = self.compute_client
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900405 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900406 name = data_utils.rand_name('scenario-server-')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900407 if image is None:
408 image = self.config.compute.image_ref
409 if flavor is None:
410 flavor = self.config.compute.flavor_ref
411 LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
412 name, image, flavor)
413 server = client.servers.create(name, image, flavor, **create_kwargs)
Giulio Fidente92f77192013-08-26 17:13:28 +0200414 self.assertEqual(server.name, name)
415 self.set_resource(name, server)
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900416 self.status_timeout(client.servers, server.id, 'ACTIVE')
417 # The instance retrieved on creation is missing network
418 # details, necessitating retrieval after it becomes active to
419 # ensure correct details.
420 server = client.servers.get(server.id)
421 self.set_resource(name, server)
422 LOG.debug("Created server: %s", server)
423 return server
424
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900425 def create_volume(self, client=None, size=1, name=None,
426 snapshot_id=None, imageRef=None):
427 if client is None:
428 client = self.volume_client
429 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900430 name = data_utils.rand_name('scenario-volume-')
Eric Windisch2d26f1b2013-09-04 17:52:16 -0700431 LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900432 volume = client.volumes.create(size=size, display_name=name,
433 snapshot_id=snapshot_id,
434 imageRef=imageRef)
435 self.set_resource(name, volume)
436 self.assertEqual(name, volume.display_name)
437 self.status_timeout(client.volumes, volume.id, 'available')
438 LOG.debug("Created volume: %s", volume)
439 return volume
440
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900441 def create_server_snapshot(self, server, compute_client=None,
442 image_client=None, name=None):
443 if compute_client is None:
444 compute_client = self.compute_client
445 if image_client is None:
446 image_client = self.image_client
447 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900448 name = data_utils.rand_name('scenario-snapshot-')
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900449 LOG.debug("Creating a snapshot image for server: %s", server.name)
450 image_id = compute_client.servers.create_image(server, name)
451 self.addCleanup(image_client.images.delete, image_id)
452 self.status_timeout(image_client.images, image_id, 'active')
453 snapshot_image = image_client.images.get(image_id)
Chang Bo Guofc77e932013-09-16 17:38:26 -0700454 self.assertEqual(name, snapshot_image.name)
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900455 LOG.debug("Created snapshot image %s for server %s",
456 snapshot_image.name, server.name)
457 return snapshot_image
458
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900459 def create_keypair(self, client=None, name=None):
460 if client is None:
461 client = self.compute_client
462 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900463 name = data_utils.rand_name('scenario-keypair-')
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900464 keypair = client.keypairs.create(name)
465 self.assertEqual(keypair.name, name)
466 self.set_resource(name, keypair)
467 return keypair
468
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900469 def get_remote_client(self, server_or_ip, username=None, private_key=None):
470 if isinstance(server_or_ip, basestring):
471 ip = server_or_ip
472 else:
473 network_name_for_ssh = self.config.compute.network_for_ssh
474 ip = server_or_ip.networks[network_name_for_ssh][0]
475 if username is None:
476 username = self.config.scenario.ssh_user
477 if private_key is None:
478 private_key = self.keypair.private_key
479 return RemoteClient(ip, username, pkey=private_key)
480
Sean Dague6dbc6da2013-05-08 17:49:46 -0400481
482class NetworkScenarioTest(OfficialClientTest):
483 """
484 Base class for network scenario tests
485 """
486
487 @classmethod
488 def check_preconditions(cls):
Matthew Treinishfaa340d2013-07-19 16:26:21 -0400489 if (cls.config.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400490 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200491 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400492 try:
493 cls.network_client.list_networks()
494 except exc.EndpointNotFound:
495 cls.enabled = False
496 raise
497 else:
498 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400499 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400500 raise cls.skipException(msg)
501
502 @classmethod
503 def setUpClass(cls):
504 super(NetworkScenarioTest, cls).setUpClass()
Miguel Lavalleb8fabc52013-08-23 11:19:57 -0500505 if cls.config.compute.allow_tenant_isolation:
506 cls.tenant_id = cls.isolated_creds.get_primary_tenant().id
507 else:
508 cls.tenant_id = cls.manager._get_identity_client(
509 cls.config.identity.username,
510 cls.config.identity.password,
511 cls.config.identity.tenant_name).tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400512
Sean Dague6dbc6da2013-05-08 17:49:46 -0400513 def _create_network(self, tenant_id, namestart='network-smoke-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900514 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400515 body = dict(
516 network=dict(
517 name=name,
518 tenant_id=tenant_id,
519 ),
520 )
521 result = self.network_client.create_network(body=body)
522 network = net_common.DeletableNetwork(client=self.network_client,
523 **result['network'])
524 self.assertEqual(network.name, name)
525 self.set_resource(name, network)
526 return network
527
528 def _list_networks(self):
529 nets = self.network_client.list_networks()
530 return nets['networks']
531
532 def _list_subnets(self):
533 subnets = self.network_client.list_subnets()
534 return subnets['subnets']
535
536 def _list_routers(self):
537 routers = self.network_client.list_routers()
538 return routers['routers']
539
540 def _create_subnet(self, network, namestart='subnet-smoke-'):
541 """
542 Create a subnet for the given network within the cidr block
543 configured for tenant networks.
544 """
545 cfg = self.config.network
546 tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
547 result = None
548 # Repeatedly attempt subnet creation with sequential cidr
549 # blocks until an unallocated block is found.
550 for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
551 body = dict(
552 subnet=dict(
553 ip_version=4,
554 network_id=network.id,
555 tenant_id=network.tenant_id,
556 cidr=str(subnet_cidr),
557 ),
558 )
559 try:
560 result = self.network_client.create_subnet(body=body)
561 break
Mark McClainf2982e82013-07-06 17:48:03 -0400562 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400563 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
564 if not is_overlapping_cidr:
565 raise
566 self.assertIsNotNone(result, 'Unable to allocate tenant network')
567 subnet = net_common.DeletableSubnet(client=self.network_client,
568 **result['subnet'])
569 self.assertEqual(subnet.cidr, str(subnet_cidr))
Masayuki Igawa259c1132013-10-31 17:48:44 +0900570 self.set_resource(data_utils.rand_name(namestart), subnet)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400571 return subnet
572
573 def _create_port(self, network, namestart='port-quotatest-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900574 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400575 body = dict(
576 port=dict(name=name,
577 network_id=network.id,
578 tenant_id=network.tenant_id))
579 result = self.network_client.create_port(body=body)
580 self.assertIsNotNone(result, 'Unable to allocate port')
581 port = net_common.DeletablePort(client=self.network_client,
582 **result['port'])
583 self.set_resource(name, port)
584 return port
585
Sean Dague6dbc6da2013-05-08 17:49:46 -0400586 def _create_floating_ip(self, server, external_network_id):
587 result = self.network_client.list_ports(device_id=server.id)
588 ports = result.get('ports', [])
589 self.assertEqual(len(ports), 1,
590 "Unable to determine which port to target.")
591 port_id = ports[0]['id']
592 body = dict(
593 floatingip=dict(
594 floating_network_id=external_network_id,
595 port_id=port_id,
596 tenant_id=server.tenant_id,
597 )
598 )
599 result = self.network_client.create_floatingip(body=body)
600 floating_ip = net_common.DeletableFloatingIp(
601 client=self.network_client,
602 **result['floatingip'])
Masayuki Igawa259c1132013-10-31 17:48:44 +0900603 self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400604 return floating_ip
605
606 def _ping_ip_address(self, ip_address):
607 cmd = ['ping', '-c1', '-w1', ip_address]
608
609 def ping():
610 proc = subprocess.Popen(cmd,
611 stdout=subprocess.PIPE,
612 stderr=subprocess.PIPE)
613 proc.wait()
614 if proc.returncode == 0:
615 return True
616
Nachi Ueno6d580be2013-07-24 10:58:11 -0700617 return tempest.test.call_until_true(
618 ping, self.config.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000619
620 def _is_reachable_via_ssh(self, ip_address, username, private_key,
Nachi Ueno6d580be2013-07-24 10:58:11 -0700621 timeout):
Maru Newbyaf292e82013-05-20 21:32:28 +0000622 ssh_client = ssh.Client(ip_address, username,
623 pkey=private_key,
624 timeout=timeout)
625 return ssh_client.test_connection_auth()
626
Nachi Ueno6d580be2013-07-24 10:58:11 -0700627 def _check_vm_connectivity(self, ip_address, username, private_key):
Maru Newbyaf292e82013-05-20 21:32:28 +0000628 self.assertTrue(self._ping_ip_address(ip_address),
629 "Timed out waiting for %s to become "
630 "reachable" % ip_address)
Nachi Ueno6d580be2013-07-24 10:58:11 -0700631 self.assertTrue(self._is_reachable_via_ssh(
632 ip_address,
633 username,
634 private_key,
635 timeout=self.config.compute.ssh_timeout),
636 'Auth failure in connecting to %s@%s via ssh' %
637 (username, ip_address))
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200638
Yair Friedeb69f3f2013-10-10 13:18:16 +0300639 def _create_security_group_nova(self, client=None,
640 namestart='secgroup-smoke-',
641 tenant_id=None):
642 if client is None:
643 client = self.compute_client
644 # Create security group
645 sg_name = data_utils.rand_name(namestart)
646 sg_desc = sg_name + " description"
647 secgroup = client.security_groups.create(sg_name, sg_desc)
648 self.assertEqual(secgroup.name, sg_name)
649 self.assertEqual(secgroup.description, sg_desc)
650 self.set_resource(sg_name, secgroup)
651
652 # Add rules to the security group
653 self._create_loginable_secgroup_rule_nova(client, secgroup.id)
654
655 return secgroup
656
657 def _create_security_group_neutron(self, tenant_id, client=None,
658 namestart='secgroup-smoke-'):
659 if client is None:
660 client = self.network_client
661 secgroup = self._create_empty_security_group(namestart=namestart,
662 client=client,
663 tenant_id=tenant_id)
664
665 # Add rules to the security group
666 rules = self._create_loginable_secgroup_rule_neutron(secgroup=secgroup)
667 for rule in rules:
668 self.assertEqual(tenant_id, rule.tenant_id)
669 self.assertEqual(secgroup.id, rule.security_group_id)
670 return secgroup
671
672 def _create_empty_security_group(self, tenant_id, client=None,
673 namestart='secgroup-smoke-'):
674 """Create a security group without rules.
675
676 Default rules will be created:
677 - IPv4 egress to any
678 - IPv6 egress to any
679
680 :param tenant_id: secgroup will be created in this tenant
681 :returns: DeletableSecurityGroup -- containing the secgroup created
682 """
683 if client is None:
684 client = self.network_client
685 sg_name = data_utils.rand_name(namestart)
686 sg_desc = sg_name + " description"
687 sg_dict = dict(name=sg_name,
688 description=sg_desc)
689 sg_dict['tenant_id'] = tenant_id
690 body = dict(security_group=sg_dict)
691 result = client.create_security_group(body=body)
692 secgroup = net_common.DeletableSecurityGroup(
693 client=client,
694 **result['security_group']
695 )
696 self.assertEqual(secgroup.name, sg_name)
697 self.assertEqual(tenant_id, secgroup.tenant_id)
698 self.assertEqual(secgroup.description, sg_desc)
699 self.set_resource(sg_name, secgroup)
700 return secgroup
701
702 def _default_security_group(self, tenant_id, client=None):
703 """Get default secgroup for given tenant_id.
704
705 :returns: DeletableSecurityGroup -- default secgroup for given tenant
706 """
707 if client is None:
708 client = self.network_client
709 sgs = [
710 sg for sg in client.list_security_groups().values()[0]
711 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
712 ]
713 msg = "No default security group for tenant %s." % (tenant_id)
714 self.assertTrue(len(sgs) > 0, msg)
715 if len(sgs) > 1:
716 msg = "Found %d default security groups" % len(sgs)
717 raise exc.NeutronClientNoUniqueMatch(msg=msg)
718 return net_common.DeletableSecurityGroup(client=client,
719 **sgs[0])
720
721 def _create_security_group_rule(self, client=None, secgroup=None,
722 tenant_id=None, **kwargs):
723 """Create a rule from a dictionary of rule parameters.
724
725 Create a rule in a secgroup. if secgroup not defined will search for
726 default secgroup in tenant_id.
727
728 :param secgroup: type DeletableSecurityGroup.
729 :param secgroup_id: search for secgroup by id
730 default -- choose default secgroup for given tenant_id
731 :param tenant_id: if secgroup not passed -- the tenant in which to
732 search for default secgroup
733 :param kwargs: a dictionary containing rule parameters:
734 for example, to allow incoming ssh:
735 rule = {
736 direction: 'ingress'
737 protocol:'tcp',
738 port_range_min: 22,
739 port_range_max: 22
740 }
741 """
742 if client is None:
743 client = self.network_client
744 if secgroup is None:
745 secgroup = self._default_security_group(tenant_id)
746
747 ruleset = dict(security_group_id=secgroup.id,
748 tenant_id=secgroup.tenant_id,
749 )
750 ruleset.update(kwargs)
751
752 body = dict(security_group_rule=dict(ruleset))
753 sg_rule = client.create_security_group_rule(body=body)
754 sg_rule = net_common.DeletableSecurityGroupRule(
755 client=client,
756 **sg_rule['security_group_rule']
757 )
758 self.set_resource(sg_rule.id, sg_rule)
759 self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
760 self.assertEqual(secgroup.id, sg_rule.security_group_id)
761
762 return sg_rule
763
764 def _create_loginable_secgroup_rule_neutron(self, client=None,
765 secgroup=None):
766 """These rules are intended to permit inbound ssh and icmp
767 traffic from all sources, so no group_id is provided.
768 Setting a group_id would only permit traffic from ports
769 belonging to the same security group.
770 """
771
772 if client is None:
773 client = self.network_client
774 rules = []
775 rulesets = [
776 dict(
777 # ssh
778 protocol='tcp',
779 port_range_min=22,
780 port_range_max=22,
781 ),
782 dict(
783 # ping
784 protocol='icmp',
785 )
786 ]
787 for ruleset in rulesets:
788 for r_direction in ['ingress', 'egress']:
789 ruleset['direction'] = r_direction
790 try:
791 sg_rule = self._create_security_group_rule(
792 client=client, secgroup=secgroup, **ruleset)
793 except exc.NeutronClientException as ex:
794 # if rule already exist - skip rule and continue
795 if not (ex.status_code is 409 and 'Security group rule'
796 ' already exists' in ex.message):
797 raise ex
798 else:
799 self.assertEqual(r_direction, sg_rule.direction)
800 rules.append(sg_rule)
801
802 return rules
803
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200804
805class OrchestrationScenarioTest(OfficialClientTest):
806 """
807 Base class for orchestration scenario tests
808 """
809
810 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -0700811 def setUpClass(cls):
812 super(OrchestrationScenarioTest, cls).setUpClass()
813 if not cls.config.service_available.heat:
814 raise cls.skipException("Heat support is required")
815
816 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200817 def credentials(cls):
818 username = cls.config.identity.admin_username
819 password = cls.config.identity.admin_password
820 tenant_name = cls.config.identity.tenant_name
821 return username, tenant_name, password
822
823 def _load_template(self, base_file, file_name):
824 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
825 file_name)
826 with open(filepath) as f:
827 return f.read()
828
829 @classmethod
830 def _stack_rand_name(cls):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900831 return data_utils.rand_name(cls.__name__ + '-')
Steve Baker80252da2013-09-25 13:29:10 +1200832
833 @classmethod
834 def _get_default_network(cls):
835 networks = cls.network_client.list_networks()
836 for net in networks['networks']:
837 if net['name'] == cls.config.compute.fixed_network_name:
838 return net