blob: b72b99f5ba385e1d3410e7af1300299ab9f67aed [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
Sean Dague6dbc6da2013-05-08 17:49:46 -040037from tempest.common.utils.data_utils import rand_name
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
91
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,
99 no_cache=True,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200100 insecure=dscv,
101 http_log_debug=True)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400102
103 def _get_image_client(self):
Matthew Treinishb86cda92013-07-29 11:22:23 -0400104 token = self.identity_client.auth_token
105 endpoint = self.identity_client.service_catalog.url_for(
106 service_type='image', endpoint_type='publicURL')
Sean Dague6dbc6da2013-05-08 17:49:46 -0400107 dscv = self.config.identity.disable_ssl_certificate_validation
108 return glanceclient.Client('1', endpoint=endpoint, token=token,
109 insecure=dscv)
110
Matthew Treinishb86cda92013-07-29 11:22:23 -0400111 def _get_volume_client(self, username, password, tenant_name):
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900112 auth_url = self.config.identity.uri
113 return cinderclient.client.Client(self.CINDERCLIENT_VERSION,
114 username,
115 password,
116 tenant_name,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200117 auth_url,
118 http_log_debug=True)
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900119
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200120 def _get_orchestration_client(self, username=None, password=None,
121 tenant_name=None):
122 if not username:
123 username = self.config.identity.admin_username
124 if not password:
125 password = self.config.identity.admin_password
126 if not tenant_name:
127 tenant_name = self.config.identity.tenant_name
128
129 self._validate_credentials(username, password, tenant_name)
130
131 keystone = self._get_identity_client(username, password, tenant_name)
132 token = keystone.auth_token
133 try:
134 endpoint = keystone.service_catalog.url_for(
135 service_type='orchestration',
136 endpoint_type='publicURL')
137 except keystoneclient.exceptions.EndpointNotFound:
138 return None
139 else:
140 return heatclient.client.Client(self.HEATCLIENT_VERSION,
141 endpoint,
142 token=token,
143 username=username,
144 password=password)
145
Matthew Treinishb86cda92013-07-29 11:22:23 -0400146 def _get_identity_client(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400147 # This identity client is not intended to check the security
148 # of the identity service, so use admin credentials by default.
Sean Dague43cd9052013-07-19 12:20:04 -0400149 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400150
151 auth_url = self.config.identity.uri
152 dscv = self.config.identity.disable_ssl_certificate_validation
153
154 return keystoneclient.v2_0.client.Client(username=username,
155 password=password,
156 tenant_name=tenant_name,
157 auth_url=auth_url,
158 insecure=dscv)
159
160 def _get_network_client(self):
161 # The intended configuration is for the network client to have
162 # admin privileges and indicate for whom resources are being
163 # created via a 'tenant_id' parameter. This will often be
164 # preferable to authenticating as a specific user because
165 # working with certain resources (public routers and networks)
166 # often requires admin privileges anyway.
167 username = self.config.identity.admin_username
168 password = self.config.identity.admin_password
169 tenant_name = self.config.identity.admin_tenant_name
170
Sean Dague43cd9052013-07-19 12:20:04 -0400171 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400172
173 auth_url = self.config.identity.uri
174 dscv = self.config.identity.disable_ssl_certificate_validation
175
Mark McClainf2982e82013-07-06 17:48:03 -0400176 return neutronclient.v2_0.client.Client(username=username,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400177 password=password,
178 tenant_name=tenant_name,
179 auth_url=auth_url,
180 insecure=dscv)
181
182
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400183class OfficialClientTest(tempest.test.BaseTestCase):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400184 """
185 Official Client test base class for scenario testing.
186
187 Official Client tests are tests that have the following characteristics:
188
189 * Test basic operations of an API, typically in an order that
190 a regular user would perform those operations
191 * Test only the correct inputs and action paths -- no fuzz or
192 random input data is sent, only valid inputs.
193 * Use only the default client tool for calling an API
194 """
195
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400196 @classmethod
197 def setUpClass(cls):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200198 super(OfficialClientTest, cls).setUpClass()
Matthew Treinishb86cda92013-07-29 11:22:23 -0400199 cls.isolated_creds = isolated_creds.IsolatedCreds(
200 __name__, tempest_client=False)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200201
202 username, tenant_name, password = cls.credentials()
Matthew Treinishb86cda92013-07-29 11:22:23 -0400203
204 cls.manager = OfficialClientManager(username, password, tenant_name)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400205 cls.compute_client = cls.manager.compute_client
206 cls.image_client = cls.manager.image_client
207 cls.identity_client = cls.manager.identity_client
208 cls.network_client = cls.manager.network_client
209 cls.volume_client = cls.manager.volume_client
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200210 cls.orchestration_client = cls.manager.orchestration_client
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400211 cls.resource_keys = {}
212 cls.os_resources = []
Sean Dague6dbc6da2013-05-08 17:49:46 -0400213
214 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200215 def credentials(cls):
216 if cls.config.compute.allow_tenant_isolation:
217 return cls.isolated_creds.get_primary_creds()
218
219 username = cls.config.identity.username
220 password = cls.config.identity.password
221 tenant_name = cls.config.identity.tenant_name
222 return username, tenant_name, password
223
224 @classmethod
Sean Dague6dbc6da2013-05-08 17:49:46 -0400225 def tearDownClass(cls):
226 # NOTE(jaypipes): Because scenario tests are typically run in a
227 # specific order, and because test methods in scenario tests
228 # generally create resources in a particular order, we destroy
229 # resources in the reverse order in which resources are added to
230 # the scenario test class object
231 while cls.os_resources:
232 thing = cls.os_resources.pop()
233 LOG.debug("Deleting %r from shared resources of %s" %
234 (thing, cls.__name__))
235
236 try:
237 # OpenStack resources are assumed to have a delete()
238 # method which destroys the resource...
239 thing.delete()
240 except Exception as e:
241 # If the resource is already missing, mission accomplished.
242 if e.__class__.__name__ == 'NotFound':
243 continue
244 raise
245
246 def is_deletion_complete():
247 # Deletion testing is only required for objects whose
248 # existence cannot be checked via retrieval.
249 if isinstance(thing, dict):
250 return True
251 try:
252 thing.get()
253 except Exception as e:
254 # Clients are expected to return an exception
255 # called 'NotFound' if retrieval fails.
256 if e.__class__.__name__ == 'NotFound':
257 return True
258 raise
259 return False
260
261 # Block until resource deletion has completed or timed-out
262 tempest.test.call_until_true(is_deletion_complete, 10, 1)
Matthew Treinishb86cda92013-07-29 11:22:23 -0400263 cls.isolated_creds.clear_isolated_creds()
264 super(OfficialClientTest, cls).tearDownClass()
Sean Dague6dbc6da2013-05-08 17:49:46 -0400265
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400266 @classmethod
267 def set_resource(cls, key, thing):
268 LOG.debug("Adding %r to shared resources of %s" %
269 (thing, cls.__name__))
270 cls.resource_keys[key] = thing
271 cls.os_resources.append(thing)
272
273 @classmethod
274 def get_resource(cls, key):
275 return cls.resource_keys[key]
276
277 @classmethod
278 def remove_resource(cls, key):
279 thing = cls.resource_keys[key]
280 cls.os_resources.remove(thing)
281 del cls.resource_keys[key]
282
283 def status_timeout(self, things, thing_id, expected_status):
284 """
285 Given a thing and an expected status, do a loop, sleeping
286 for a configurable amount of time, checking for the
287 expected status to show. At any time, if the returned
288 status of the thing is ERROR, fail out.
289 """
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900290 self._status_timeout(things, thing_id, expected_status=expected_status)
291
292 def delete_timeout(self, things, thing_id):
293 """
294 Given a thing, do a loop, sleeping
295 for a configurable amount of time, checking for the
296 deleted status to show. At any time, if the returned
297 status of the thing is ERROR, fail out.
298 """
299 self._status_timeout(things,
300 thing_id,
301 allow_notfound=True)
302
303 def _status_timeout(self,
304 things,
305 thing_id,
306 expected_status=None,
307 allow_notfound=False):
308
309 log_status = expected_status if expected_status else ''
310 if allow_notfound:
311 log_status += ' or NotFound' if log_status != '' else 'NotFound'
312
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400313 def check_status():
314 # python-novaclient has resources available to its client
315 # that all implement a get() method taking an identifier
316 # for the singular resource to retrieve.
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900317 try:
318 thing = things.get(thing_id)
319 except nova_exceptions.NotFound:
320 if allow_notfound:
321 return True
322 else:
323 raise
324
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400325 new_status = thing.status
326 if new_status == 'ERROR':
Giulio Fidente92f77192013-08-26 17:13:28 +0200327 message = "%s failed to get to expected status. \
328 In ERROR state." % (thing)
329 raise exceptions.BuildErrorException(message)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900330 elif new_status == expected_status and expected_status is not None:
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400331 return True # All good.
332 LOG.debug("Waiting for %s to get to %s status. "
333 "Currently in %s status",
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900334 thing, log_status, new_status)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400335 if not tempest.test.call_until_true(
336 check_status,
337 self.config.compute.build_timeout,
338 self.config.compute.build_interval):
Giulio Fidente92f77192013-08-26 17:13:28 +0200339 message = "Timed out waiting for thing %s \
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900340 to become %s" % (thing_id, log_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200341 raise exceptions.TimeoutException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400342
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900343 def create_loginable_secgroup_rule(self, client=None, secgroup_id=None):
344 if client is None:
345 client = self.compute_client
346 if secgroup_id is None:
347 sgs = client.security_groups.list()
348 for sg in sgs:
349 if sg.name == 'default':
350 secgroup_id = sg.id
351
352 # These rules are intended to permit inbound ssh and icmp
353 # traffic from all sources, so no group_id is provided.
354 # Setting a group_id would only permit traffic from ports
355 # belonging to the same security group.
356 rulesets = [
357 {
358 # ssh
359 'ip_protocol': 'tcp',
360 'from_port': 22,
361 'to_port': 22,
362 'cidr': '0.0.0.0/0',
363 },
364 {
365 # ping
366 'ip_protocol': 'icmp',
367 'from_port': -1,
368 'to_port': -1,
369 'cidr': '0.0.0.0/0',
370 }
371 ]
372 for ruleset in rulesets:
373 sg_rule = client.security_group_rules.create(secgroup_id,
374 **ruleset)
375 self.set_resource(sg_rule.id, sg_rule)
376
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900377 def create_server(self, client, name=None, image=None, flavor=None,
378 create_kwargs={}):
379 if name is None:
380 name = rand_name('scenario-server-')
381 if image is None:
382 image = self.config.compute.image_ref
383 if flavor is None:
384 flavor = self.config.compute.flavor_ref
385 LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
386 name, image, flavor)
387 server = client.servers.create(name, image, flavor, **create_kwargs)
Giulio Fidente92f77192013-08-26 17:13:28 +0200388 self.assertEqual(server.name, name)
389 self.set_resource(name, server)
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900390 self.status_timeout(client.servers, server.id, 'ACTIVE')
391 # The instance retrieved on creation is missing network
392 # details, necessitating retrieval after it becomes active to
393 # ensure correct details.
394 server = client.servers.get(server.id)
395 self.set_resource(name, server)
396 LOG.debug("Created server: %s", server)
397 return server
398
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900399 def create_volume(self, client=None, size=1, name=None,
400 snapshot_id=None, imageRef=None):
401 if client is None:
402 client = self.volume_client
403 if name is None:
404 name = rand_name('scenario-volume-')
Eric Windisch2d26f1b2013-09-04 17:52:16 -0700405 LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900406 volume = client.volumes.create(size=size, display_name=name,
407 snapshot_id=snapshot_id,
408 imageRef=imageRef)
409 self.set_resource(name, volume)
410 self.assertEqual(name, volume.display_name)
411 self.status_timeout(client.volumes, volume.id, 'available')
412 LOG.debug("Created volume: %s", volume)
413 return volume
414
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900415 def create_server_snapshot(self, server, compute_client=None,
416 image_client=None, name=None):
417 if compute_client is None:
418 compute_client = self.compute_client
419 if image_client is None:
420 image_client = self.image_client
421 if name is None:
422 name = rand_name('scenario-snapshot-')
423 LOG.debug("Creating a snapshot image for server: %s", server.name)
424 image_id = compute_client.servers.create_image(server, name)
425 self.addCleanup(image_client.images.delete, image_id)
426 self.status_timeout(image_client.images, image_id, 'active')
427 snapshot_image = image_client.images.get(image_id)
Chang Bo Guofc77e932013-09-16 17:38:26 -0700428 self.assertEqual(name, snapshot_image.name)
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900429 LOG.debug("Created snapshot image %s for server %s",
430 snapshot_image.name, server.name)
431 return snapshot_image
432
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900433 def create_keypair(self, client=None, name=None):
434 if client is None:
435 client = self.compute_client
436 if name is None:
437 name = rand_name('scenario-keypair-')
438 keypair = client.keypairs.create(name)
439 self.assertEqual(keypair.name, name)
440 self.set_resource(name, keypair)
441 return keypair
442
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900443 def get_remote_client(self, server_or_ip, username=None, private_key=None):
444 if isinstance(server_or_ip, basestring):
445 ip = server_or_ip
446 else:
447 network_name_for_ssh = self.config.compute.network_for_ssh
448 ip = server_or_ip.networks[network_name_for_ssh][0]
449 if username is None:
450 username = self.config.scenario.ssh_user
451 if private_key is None:
452 private_key = self.keypair.private_key
453 return RemoteClient(ip, username, pkey=private_key)
454
Sean Dague6dbc6da2013-05-08 17:49:46 -0400455
456class NetworkScenarioTest(OfficialClientTest):
457 """
458 Base class for network scenario tests
459 """
460
461 @classmethod
462 def check_preconditions(cls):
Matthew Treinishfaa340d2013-07-19 16:26:21 -0400463 if (cls.config.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400464 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200465 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400466 try:
467 cls.network_client.list_networks()
468 except exc.EndpointNotFound:
469 cls.enabled = False
470 raise
471 else:
472 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400473 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400474 raise cls.skipException(msg)
475
476 @classmethod
477 def setUpClass(cls):
478 super(NetworkScenarioTest, cls).setUpClass()
Miguel Lavalleb8fabc52013-08-23 11:19:57 -0500479 if cls.config.compute.allow_tenant_isolation:
480 cls.tenant_id = cls.isolated_creds.get_primary_tenant().id
481 else:
482 cls.tenant_id = cls.manager._get_identity_client(
483 cls.config.identity.username,
484 cls.config.identity.password,
485 cls.config.identity.tenant_name).tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400486
Sean Dague6dbc6da2013-05-08 17:49:46 -0400487 def _create_security_group(self, client, namestart='secgroup-smoke-'):
488 # Create security group
489 sg_name = rand_name(namestart)
490 sg_desc = sg_name + " description"
491 secgroup = client.security_groups.create(sg_name, sg_desc)
Giulio Fidente92f77192013-08-26 17:13:28 +0200492 self.assertEqual(secgroup.name, sg_name)
493 self.assertEqual(secgroup.description, sg_desc)
494 self.set_resource(sg_name, secgroup)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400495
496 # Add rules to the security group
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900497 self.create_loginable_secgroup_rule(client, secgroup.id)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400498
499 return secgroup
500
501 def _create_network(self, tenant_id, namestart='network-smoke-'):
502 name = rand_name(namestart)
503 body = dict(
504 network=dict(
505 name=name,
506 tenant_id=tenant_id,
507 ),
508 )
509 result = self.network_client.create_network(body=body)
510 network = net_common.DeletableNetwork(client=self.network_client,
511 **result['network'])
512 self.assertEqual(network.name, name)
513 self.set_resource(name, network)
514 return network
515
516 def _list_networks(self):
517 nets = self.network_client.list_networks()
518 return nets['networks']
519
520 def _list_subnets(self):
521 subnets = self.network_client.list_subnets()
522 return subnets['subnets']
523
524 def _list_routers(self):
525 routers = self.network_client.list_routers()
526 return routers['routers']
527
528 def _create_subnet(self, network, namestart='subnet-smoke-'):
529 """
530 Create a subnet for the given network within the cidr block
531 configured for tenant networks.
532 """
533 cfg = self.config.network
534 tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
535 result = None
536 # Repeatedly attempt subnet creation with sequential cidr
537 # blocks until an unallocated block is found.
538 for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
539 body = dict(
540 subnet=dict(
541 ip_version=4,
542 network_id=network.id,
543 tenant_id=network.tenant_id,
544 cidr=str(subnet_cidr),
545 ),
546 )
547 try:
548 result = self.network_client.create_subnet(body=body)
549 break
Mark McClainf2982e82013-07-06 17:48:03 -0400550 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400551 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
552 if not is_overlapping_cidr:
553 raise
554 self.assertIsNotNone(result, 'Unable to allocate tenant network')
555 subnet = net_common.DeletableSubnet(client=self.network_client,
556 **result['subnet'])
557 self.assertEqual(subnet.cidr, str(subnet_cidr))
558 self.set_resource(rand_name(namestart), subnet)
559 return subnet
560
561 def _create_port(self, network, namestart='port-quotatest-'):
562 name = rand_name(namestart)
563 body = dict(
564 port=dict(name=name,
565 network_id=network.id,
566 tenant_id=network.tenant_id))
567 result = self.network_client.create_port(body=body)
568 self.assertIsNotNone(result, 'Unable to allocate port')
569 port = net_common.DeletablePort(client=self.network_client,
570 **result['port'])
571 self.set_resource(name, port)
572 return port
573
Sean Dague6dbc6da2013-05-08 17:49:46 -0400574 def _create_floating_ip(self, server, external_network_id):
575 result = self.network_client.list_ports(device_id=server.id)
576 ports = result.get('ports', [])
577 self.assertEqual(len(ports), 1,
578 "Unable to determine which port to target.")
579 port_id = ports[0]['id']
580 body = dict(
581 floatingip=dict(
582 floating_network_id=external_network_id,
583 port_id=port_id,
584 tenant_id=server.tenant_id,
585 )
586 )
587 result = self.network_client.create_floatingip(body=body)
588 floating_ip = net_common.DeletableFloatingIp(
589 client=self.network_client,
590 **result['floatingip'])
591 self.set_resource(rand_name('floatingip-'), floating_ip)
592 return floating_ip
593
594 def _ping_ip_address(self, ip_address):
595 cmd = ['ping', '-c1', '-w1', ip_address]
596
597 def ping():
598 proc = subprocess.Popen(cmd,
599 stdout=subprocess.PIPE,
600 stderr=subprocess.PIPE)
601 proc.wait()
602 if proc.returncode == 0:
603 return True
604
Nachi Ueno6d580be2013-07-24 10:58:11 -0700605 return tempest.test.call_until_true(
606 ping, self.config.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000607
608 def _is_reachable_via_ssh(self, ip_address, username, private_key,
Nachi Ueno6d580be2013-07-24 10:58:11 -0700609 timeout):
Maru Newbyaf292e82013-05-20 21:32:28 +0000610 ssh_client = ssh.Client(ip_address, username,
611 pkey=private_key,
612 timeout=timeout)
613 return ssh_client.test_connection_auth()
614
Nachi Ueno6d580be2013-07-24 10:58:11 -0700615 def _check_vm_connectivity(self, ip_address, username, private_key):
Maru Newbyaf292e82013-05-20 21:32:28 +0000616 self.assertTrue(self._ping_ip_address(ip_address),
617 "Timed out waiting for %s to become "
618 "reachable" % ip_address)
Nachi Ueno6d580be2013-07-24 10:58:11 -0700619 self.assertTrue(self._is_reachable_via_ssh(
620 ip_address,
621 username,
622 private_key,
623 timeout=self.config.compute.ssh_timeout),
624 'Auth failure in connecting to %s@%s via ssh' %
625 (username, ip_address))
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200626
627
628class OrchestrationScenarioTest(OfficialClientTest):
629 """
630 Base class for orchestration scenario tests
631 """
632
633 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -0700634 def setUpClass(cls):
635 super(OrchestrationScenarioTest, cls).setUpClass()
636 if not cls.config.service_available.heat:
637 raise cls.skipException("Heat support is required")
638
639 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200640 def credentials(cls):
641 username = cls.config.identity.admin_username
642 password = cls.config.identity.admin_password
643 tenant_name = cls.config.identity.tenant_name
644 return username, tenant_name, password
645
646 def _load_template(self, base_file, file_name):
647 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
648 file_name)
649 with open(filepath) as f:
650 return f.read()
651
652 @classmethod
653 def _stack_rand_name(cls):
654 return rand_name(cls.__name__ + '-')