blob: 7681f0494e40edacd7cfbf48b0ba4abb899ef262 [file] [log] [blame]
Sean Dague6dbc6da2013-05-08 17:49:46 -04001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2012 OpenStack, LLC
4# 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
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120019import os
Sean Dague6dbc6da2013-05-08 17:49:46 -040020import subprocess
21
22# Default client libs
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +090023import cinderclient.client
Sean Dague6dbc6da2013-05-08 17:49:46 -040024import glanceclient
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120025import heatclient.client
Sean Dague6dbc6da2013-05-08 17:49:46 -040026import keystoneclient.v2_0.client
27import netaddr
Mark McClainf2982e82013-07-06 17:48:03 -040028from neutronclient.common import exceptions as exc
29import neutronclient.v2_0.client
Sean Dague6dbc6da2013-05-08 17:49:46 -040030import novaclient.client
Sean Dague6dbc6da2013-05-08 17:49:46 -040031
Sean Dague1937d092013-05-17 16:36:38 -040032from tempest.api.network import common as net_common
Matthew Treinishb86cda92013-07-29 11:22:23 -040033from tempest.common import isolated_creds
Maru Newbyaf292e82013-05-20 21:32:28 +000034from tempest.common import ssh
Sean Dague6dbc6da2013-05-08 17:49:46 -040035from tempest.common.utils.data_utils import rand_name
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +090036from tempest.common.utils.linux.remote_client import RemoteClient
Giulio Fidente92f77192013-08-26 17:13:28 +020037from tempest import exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040038import tempest.manager
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040039from tempest.openstack.common import log as logging
Sean Dague6dbc6da2013-05-08 17:49:46 -040040import tempest.test
Sean Dague6dbc6da2013-05-08 17:49:46 -040041
42
43LOG = logging.getLogger(__name__)
44
45
46class OfficialClientManager(tempest.manager.Manager):
47 """
48 Manager that provides access to the official python clients for
49 calling various OpenStack APIs.
50 """
51
52 NOVACLIENT_VERSION = '2'
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +090053 CINDERCLIENT_VERSION = '1'
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120054 HEATCLIENT_VERSION = '1'
Sean Dague6dbc6da2013-05-08 17:49:46 -040055
Matthew Treinishb86cda92013-07-29 11:22:23 -040056 def __init__(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -040057 super(OfficialClientManager, self).__init__()
Matthew Treinishb86cda92013-07-29 11:22:23 -040058 self.compute_client = self._get_compute_client(username,
59 password,
60 tenant_name)
61 self.identity_client = self._get_identity_client(username,
62 password,
63 tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040064 self.image_client = self._get_image_client()
Sean Dague6dbc6da2013-05-08 17:49:46 -040065 self.network_client = self._get_network_client()
Matthew Treinishb86cda92013-07-29 11:22:23 -040066 self.volume_client = self._get_volume_client(username,
67 password,
68 tenant_name)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120069 self.orchestration_client = self._get_orchestration_client(
70 username,
71 password,
72 tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040073
Matthew Treinishb86cda92013-07-29 11:22:23 -040074 def _get_compute_client(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -040075 # Novaclient will not execute operations for anyone but the
76 # identified user, so a new client needs to be created for
77 # each user that operations need to be performed for.
Sean Dague43cd9052013-07-19 12:20:04 -040078 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040079
80 auth_url = self.config.identity.uri
81 dscv = self.config.identity.disable_ssl_certificate_validation
82
83 client_args = (username, password, tenant_name, auth_url)
84
85 # Create our default Nova client to use in testing
86 service_type = self.config.compute.catalog_type
87 return novaclient.client.Client(self.NOVACLIENT_VERSION,
88 *client_args,
89 service_type=service_type,
90 no_cache=True,
91 insecure=dscv)
92
93 def _get_image_client(self):
Matthew Treinishb86cda92013-07-29 11:22:23 -040094 token = self.identity_client.auth_token
95 endpoint = self.identity_client.service_catalog.url_for(
96 service_type='image', endpoint_type='publicURL')
Sean Dague6dbc6da2013-05-08 17:49:46 -040097 dscv = self.config.identity.disable_ssl_certificate_validation
98 return glanceclient.Client('1', endpoint=endpoint, token=token,
99 insecure=dscv)
100
Matthew Treinishb86cda92013-07-29 11:22:23 -0400101 def _get_volume_client(self, username, password, tenant_name):
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900102 auth_url = self.config.identity.uri
103 return cinderclient.client.Client(self.CINDERCLIENT_VERSION,
104 username,
105 password,
106 tenant_name,
107 auth_url)
108
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200109 def _get_orchestration_client(self, username=None, password=None,
110 tenant_name=None):
111 if not username:
112 username = self.config.identity.admin_username
113 if not password:
114 password = self.config.identity.admin_password
115 if not tenant_name:
116 tenant_name = self.config.identity.tenant_name
117
118 self._validate_credentials(username, password, tenant_name)
119
120 keystone = self._get_identity_client(username, password, tenant_name)
121 token = keystone.auth_token
122 try:
123 endpoint = keystone.service_catalog.url_for(
124 service_type='orchestration',
125 endpoint_type='publicURL')
126 except keystoneclient.exceptions.EndpointNotFound:
127 return None
128 else:
129 return heatclient.client.Client(self.HEATCLIENT_VERSION,
130 endpoint,
131 token=token,
132 username=username,
133 password=password)
134
Matthew Treinishb86cda92013-07-29 11:22:23 -0400135 def _get_identity_client(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400136 # This identity client is not intended to check the security
137 # of the identity service, so use admin credentials by default.
Sean Dague43cd9052013-07-19 12:20:04 -0400138 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400139
140 auth_url = self.config.identity.uri
141 dscv = self.config.identity.disable_ssl_certificate_validation
142
143 return keystoneclient.v2_0.client.Client(username=username,
144 password=password,
145 tenant_name=tenant_name,
146 auth_url=auth_url,
147 insecure=dscv)
148
149 def _get_network_client(self):
150 # The intended configuration is for the network client to have
151 # admin privileges and indicate for whom resources are being
152 # created via a 'tenant_id' parameter. This will often be
153 # preferable to authenticating as a specific user because
154 # working with certain resources (public routers and networks)
155 # often requires admin privileges anyway.
156 username = self.config.identity.admin_username
157 password = self.config.identity.admin_password
158 tenant_name = self.config.identity.admin_tenant_name
159
Sean Dague43cd9052013-07-19 12:20:04 -0400160 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400161
162 auth_url = self.config.identity.uri
163 dscv = self.config.identity.disable_ssl_certificate_validation
164
Mark McClainf2982e82013-07-06 17:48:03 -0400165 return neutronclient.v2_0.client.Client(username=username,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400166 password=password,
167 tenant_name=tenant_name,
168 auth_url=auth_url,
169 insecure=dscv)
170
171
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400172class OfficialClientTest(tempest.test.BaseTestCase):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400173 """
174 Official Client test base class for scenario testing.
175
176 Official Client tests are tests that have the following characteristics:
177
178 * Test basic operations of an API, typically in an order that
179 a regular user would perform those operations
180 * Test only the correct inputs and action paths -- no fuzz or
181 random input data is sent, only valid inputs.
182 * Use only the default client tool for calling an API
183 """
184
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400185 @classmethod
186 def setUpClass(cls):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200187 super(OfficialClientTest, cls).setUpClass()
Matthew Treinishb86cda92013-07-29 11:22:23 -0400188 cls.isolated_creds = isolated_creds.IsolatedCreds(
189 __name__, tempest_client=False)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200190
191 username, tenant_name, password = cls.credentials()
Matthew Treinishb86cda92013-07-29 11:22:23 -0400192
193 cls.manager = OfficialClientManager(username, password, tenant_name)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400194 cls.compute_client = cls.manager.compute_client
195 cls.image_client = cls.manager.image_client
196 cls.identity_client = cls.manager.identity_client
197 cls.network_client = cls.manager.network_client
198 cls.volume_client = cls.manager.volume_client
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200199 cls.orchestration_client = cls.manager.orchestration_client
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400200 cls.resource_keys = {}
201 cls.os_resources = []
Sean Dague6dbc6da2013-05-08 17:49:46 -0400202
203 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200204 def credentials(cls):
205 if cls.config.compute.allow_tenant_isolation:
206 return cls.isolated_creds.get_primary_creds()
207
208 username = cls.config.identity.username
209 password = cls.config.identity.password
210 tenant_name = cls.config.identity.tenant_name
211 return username, tenant_name, password
212
213 @classmethod
Sean Dague6dbc6da2013-05-08 17:49:46 -0400214 def tearDownClass(cls):
215 # NOTE(jaypipes): Because scenario tests are typically run in a
216 # specific order, and because test methods in scenario tests
217 # generally create resources in a particular order, we destroy
218 # resources in the reverse order in which resources are added to
219 # the scenario test class object
220 while cls.os_resources:
221 thing = cls.os_resources.pop()
222 LOG.debug("Deleting %r from shared resources of %s" %
223 (thing, cls.__name__))
224
225 try:
226 # OpenStack resources are assumed to have a delete()
227 # method which destroys the resource...
228 thing.delete()
229 except Exception as e:
230 # If the resource is already missing, mission accomplished.
231 if e.__class__.__name__ == 'NotFound':
232 continue
233 raise
234
235 def is_deletion_complete():
236 # Deletion testing is only required for objects whose
237 # existence cannot be checked via retrieval.
238 if isinstance(thing, dict):
239 return True
240 try:
241 thing.get()
242 except Exception as e:
243 # Clients are expected to return an exception
244 # called 'NotFound' if retrieval fails.
245 if e.__class__.__name__ == 'NotFound':
246 return True
247 raise
248 return False
249
250 # Block until resource deletion has completed or timed-out
251 tempest.test.call_until_true(is_deletion_complete, 10, 1)
Matthew Treinishb86cda92013-07-29 11:22:23 -0400252 cls.isolated_creds.clear_isolated_creds()
253 super(OfficialClientTest, cls).tearDownClass()
Sean Dague6dbc6da2013-05-08 17:49:46 -0400254
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400255 @classmethod
256 def set_resource(cls, key, thing):
257 LOG.debug("Adding %r to shared resources of %s" %
258 (thing, cls.__name__))
259 cls.resource_keys[key] = thing
260 cls.os_resources.append(thing)
261
262 @classmethod
263 def get_resource(cls, key):
264 return cls.resource_keys[key]
265
266 @classmethod
267 def remove_resource(cls, key):
268 thing = cls.resource_keys[key]
269 cls.os_resources.remove(thing)
270 del cls.resource_keys[key]
271
272 def status_timeout(self, things, thing_id, expected_status):
273 """
274 Given a thing and an expected status, do a loop, sleeping
275 for a configurable amount of time, checking for the
276 expected status to show. At any time, if the returned
277 status of the thing is ERROR, fail out.
278 """
279 def check_status():
280 # python-novaclient has resources available to its client
281 # that all implement a get() method taking an identifier
282 # for the singular resource to retrieve.
283 thing = things.get(thing_id)
284 new_status = thing.status
285 if new_status == 'ERROR':
Giulio Fidente92f77192013-08-26 17:13:28 +0200286 message = "%s failed to get to expected status. \
287 In ERROR state." % (thing)
288 raise exceptions.BuildErrorException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400289 elif new_status == expected_status:
290 return True # All good.
291 LOG.debug("Waiting for %s to get to %s status. "
292 "Currently in %s status",
293 thing, expected_status, new_status)
294 if not tempest.test.call_until_true(
295 check_status,
296 self.config.compute.build_timeout,
297 self.config.compute.build_interval):
Giulio Fidente92f77192013-08-26 17:13:28 +0200298 message = "Timed out waiting for thing %s \
299 to become %s" % (thing_id, expected_status)
300 raise exceptions.TimeoutException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400301
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900302 def create_loginable_secgroup_rule(self, client=None, secgroup_id=None):
303 if client is None:
304 client = self.compute_client
305 if secgroup_id is None:
306 sgs = client.security_groups.list()
307 for sg in sgs:
308 if sg.name == 'default':
309 secgroup_id = sg.id
310
311 # These rules are intended to permit inbound ssh and icmp
312 # traffic from all sources, so no group_id is provided.
313 # Setting a group_id would only permit traffic from ports
314 # belonging to the same security group.
315 rulesets = [
316 {
317 # ssh
318 'ip_protocol': 'tcp',
319 'from_port': 22,
320 'to_port': 22,
321 'cidr': '0.0.0.0/0',
322 },
323 {
324 # ping
325 'ip_protocol': 'icmp',
326 'from_port': -1,
327 'to_port': -1,
328 'cidr': '0.0.0.0/0',
329 }
330 ]
331 for ruleset in rulesets:
332 sg_rule = client.security_group_rules.create(secgroup_id,
333 **ruleset)
334 self.set_resource(sg_rule.id, sg_rule)
335
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900336 def create_server(self, client, name=None, image=None, flavor=None,
337 create_kwargs={}):
338 if name is None:
339 name = rand_name('scenario-server-')
340 if image is None:
341 image = self.config.compute.image_ref
342 if flavor is None:
343 flavor = self.config.compute.flavor_ref
344 LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
345 name, image, flavor)
346 server = client.servers.create(name, image, flavor, **create_kwargs)
Giulio Fidente92f77192013-08-26 17:13:28 +0200347 self.assertEqual(server.name, name)
348 self.set_resource(name, server)
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900349 self.status_timeout(client.servers, server.id, 'ACTIVE')
350 # The instance retrieved on creation is missing network
351 # details, necessitating retrieval after it becomes active to
352 # ensure correct details.
353 server = client.servers.get(server.id)
354 self.set_resource(name, server)
355 LOG.debug("Created server: %s", server)
356 return server
357
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900358 def create_volume(self, client=None, size=1, name=None,
359 snapshot_id=None, imageRef=None):
360 if client is None:
361 client = self.volume_client
362 if name is None:
363 name = rand_name('scenario-volume-')
364 LOG.debug("Creating a volume (size :%s, name: %s)", size, name)
365 volume = client.volumes.create(size=size, display_name=name,
366 snapshot_id=snapshot_id,
367 imageRef=imageRef)
368 self.set_resource(name, volume)
369 self.assertEqual(name, volume.display_name)
370 self.status_timeout(client.volumes, volume.id, 'available')
371 LOG.debug("Created volume: %s", volume)
372 return volume
373
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900374 def create_server_snapshot(self, server, compute_client=None,
375 image_client=None, name=None):
376 if compute_client is None:
377 compute_client = self.compute_client
378 if image_client is None:
379 image_client = self.image_client
380 if name is None:
381 name = rand_name('scenario-snapshot-')
382 LOG.debug("Creating a snapshot image for server: %s", server.name)
383 image_id = compute_client.servers.create_image(server, name)
384 self.addCleanup(image_client.images.delete, image_id)
385 self.status_timeout(image_client.images, image_id, 'active')
386 snapshot_image = image_client.images.get(image_id)
387 self.assertEquals(name, snapshot_image.name)
388 LOG.debug("Created snapshot image %s for server %s",
389 snapshot_image.name, server.name)
390 return snapshot_image
391
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900392 def create_keypair(self, client=None, name=None):
393 if client is None:
394 client = self.compute_client
395 if name is None:
396 name = rand_name('scenario-keypair-')
397 keypair = client.keypairs.create(name)
398 self.assertEqual(keypair.name, name)
399 self.set_resource(name, keypair)
400 return keypair
401
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900402 def get_remote_client(self, server_or_ip, username=None, private_key=None):
403 if isinstance(server_or_ip, basestring):
404 ip = server_or_ip
405 else:
406 network_name_for_ssh = self.config.compute.network_for_ssh
407 ip = server_or_ip.networks[network_name_for_ssh][0]
408 if username is None:
409 username = self.config.scenario.ssh_user
410 if private_key is None:
411 private_key = self.keypair.private_key
412 return RemoteClient(ip, username, pkey=private_key)
413
Sean Dague6dbc6da2013-05-08 17:49:46 -0400414
415class NetworkScenarioTest(OfficialClientTest):
416 """
417 Base class for network scenario tests
418 """
419
420 @classmethod
421 def check_preconditions(cls):
Matthew Treinishfaa340d2013-07-19 16:26:21 -0400422 if (cls.config.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400423 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200424 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400425 try:
426 cls.network_client.list_networks()
427 except exc.EndpointNotFound:
428 cls.enabled = False
429 raise
430 else:
431 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400432 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400433 raise cls.skipException(msg)
434
435 @classmethod
436 def setUpClass(cls):
437 super(NetworkScenarioTest, cls).setUpClass()
438 cls.tenant_id = cls.manager._get_identity_client(
439 cls.config.identity.username,
440 cls.config.identity.password,
441 cls.config.identity.tenant_name).tenant_id
442
Sean Dague6dbc6da2013-05-08 17:49:46 -0400443 def _create_security_group(self, client, namestart='secgroup-smoke-'):
444 # Create security group
445 sg_name = rand_name(namestart)
446 sg_desc = sg_name + " description"
447 secgroup = client.security_groups.create(sg_name, sg_desc)
Giulio Fidente92f77192013-08-26 17:13:28 +0200448 self.assertEqual(secgroup.name, sg_name)
449 self.assertEqual(secgroup.description, sg_desc)
450 self.set_resource(sg_name, secgroup)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400451
452 # Add rules to the security group
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900453 self.create_loginable_secgroup_rule(client, secgroup.id)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400454
455 return secgroup
456
457 def _create_network(self, tenant_id, namestart='network-smoke-'):
458 name = rand_name(namestart)
459 body = dict(
460 network=dict(
461 name=name,
462 tenant_id=tenant_id,
463 ),
464 )
465 result = self.network_client.create_network(body=body)
466 network = net_common.DeletableNetwork(client=self.network_client,
467 **result['network'])
468 self.assertEqual(network.name, name)
469 self.set_resource(name, network)
470 return network
471
472 def _list_networks(self):
473 nets = self.network_client.list_networks()
474 return nets['networks']
475
476 def _list_subnets(self):
477 subnets = self.network_client.list_subnets()
478 return subnets['subnets']
479
480 def _list_routers(self):
481 routers = self.network_client.list_routers()
482 return routers['routers']
483
484 def _create_subnet(self, network, namestart='subnet-smoke-'):
485 """
486 Create a subnet for the given network within the cidr block
487 configured for tenant networks.
488 """
489 cfg = self.config.network
490 tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
491 result = None
492 # Repeatedly attempt subnet creation with sequential cidr
493 # blocks until an unallocated block is found.
494 for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
495 body = dict(
496 subnet=dict(
497 ip_version=4,
498 network_id=network.id,
499 tenant_id=network.tenant_id,
500 cidr=str(subnet_cidr),
501 ),
502 )
503 try:
504 result = self.network_client.create_subnet(body=body)
505 break
Mark McClainf2982e82013-07-06 17:48:03 -0400506 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400507 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
508 if not is_overlapping_cidr:
509 raise
510 self.assertIsNotNone(result, 'Unable to allocate tenant network')
511 subnet = net_common.DeletableSubnet(client=self.network_client,
512 **result['subnet'])
513 self.assertEqual(subnet.cidr, str(subnet_cidr))
514 self.set_resource(rand_name(namestart), subnet)
515 return subnet
516
517 def _create_port(self, network, namestart='port-quotatest-'):
518 name = rand_name(namestart)
519 body = dict(
520 port=dict(name=name,
521 network_id=network.id,
522 tenant_id=network.tenant_id))
523 result = self.network_client.create_port(body=body)
524 self.assertIsNotNone(result, 'Unable to allocate port')
525 port = net_common.DeletablePort(client=self.network_client,
526 **result['port'])
527 self.set_resource(name, port)
528 return port
529
Sean Dague6dbc6da2013-05-08 17:49:46 -0400530 def _create_floating_ip(self, server, external_network_id):
531 result = self.network_client.list_ports(device_id=server.id)
532 ports = result.get('ports', [])
533 self.assertEqual(len(ports), 1,
534 "Unable to determine which port to target.")
535 port_id = ports[0]['id']
536 body = dict(
537 floatingip=dict(
538 floating_network_id=external_network_id,
539 port_id=port_id,
540 tenant_id=server.tenant_id,
541 )
542 )
543 result = self.network_client.create_floatingip(body=body)
544 floating_ip = net_common.DeletableFloatingIp(
545 client=self.network_client,
546 **result['floatingip'])
547 self.set_resource(rand_name('floatingip-'), floating_ip)
548 return floating_ip
549
550 def _ping_ip_address(self, ip_address):
551 cmd = ['ping', '-c1', '-w1', ip_address]
552
553 def ping():
554 proc = subprocess.Popen(cmd,
555 stdout=subprocess.PIPE,
556 stderr=subprocess.PIPE)
557 proc.wait()
558 if proc.returncode == 0:
559 return True
560
Nachi Ueno6d580be2013-07-24 10:58:11 -0700561 return tempest.test.call_until_true(
562 ping, self.config.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000563
564 def _is_reachable_via_ssh(self, ip_address, username, private_key,
Nachi Ueno6d580be2013-07-24 10:58:11 -0700565 timeout):
Maru Newbyaf292e82013-05-20 21:32:28 +0000566 ssh_client = ssh.Client(ip_address, username,
567 pkey=private_key,
568 timeout=timeout)
569 return ssh_client.test_connection_auth()
570
Nachi Ueno6d580be2013-07-24 10:58:11 -0700571 def _check_vm_connectivity(self, ip_address, username, private_key):
Maru Newbyaf292e82013-05-20 21:32:28 +0000572 self.assertTrue(self._ping_ip_address(ip_address),
573 "Timed out waiting for %s to become "
574 "reachable" % ip_address)
Nachi Ueno6d580be2013-07-24 10:58:11 -0700575 self.assertTrue(self._is_reachable_via_ssh(
576 ip_address,
577 username,
578 private_key,
579 timeout=self.config.compute.ssh_timeout),
580 'Auth failure in connecting to %s@%s via ssh' %
581 (username, ip_address))
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200582
583
584class OrchestrationScenarioTest(OfficialClientTest):
585 """
586 Base class for orchestration scenario tests
587 """
588
589 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -0700590 def setUpClass(cls):
591 super(OrchestrationScenarioTest, cls).setUpClass()
592 if not cls.config.service_available.heat:
593 raise cls.skipException("Heat support is required")
594
595 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200596 def credentials(cls):
597 username = cls.config.identity.admin_username
598 password = cls.config.identity.admin_password
599 tenant_name = cls.config.identity.tenant_name
600 return username, tenant_name, password
601
602 def _load_template(self, base_file, file_name):
603 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
604 file_name)
605 with open(filepath) as f:
606 return f.read()
607
608 @classmethod
609 def _stack_rand_name(cls):
610 return rand_name(cls.__name__ + '-')