blob: 21c37b99719d05a753adf542e923b9768882d581 [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
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)
428 self.assertEquals(name, snapshot_image.name)
429 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()
479 cls.tenant_id = cls.manager._get_identity_client(
480 cls.config.identity.username,
481 cls.config.identity.password,
482 cls.config.identity.tenant_name).tenant_id
483
Sean Dague6dbc6da2013-05-08 17:49:46 -0400484 def _create_security_group(self, client, namestart='secgroup-smoke-'):
485 # Create security group
486 sg_name = rand_name(namestart)
487 sg_desc = sg_name + " description"
488 secgroup = client.security_groups.create(sg_name, sg_desc)
Giulio Fidente92f77192013-08-26 17:13:28 +0200489 self.assertEqual(secgroup.name, sg_name)
490 self.assertEqual(secgroup.description, sg_desc)
491 self.set_resource(sg_name, secgroup)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400492
493 # Add rules to the security group
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900494 self.create_loginable_secgroup_rule(client, secgroup.id)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400495
496 return secgroup
497
498 def _create_network(self, tenant_id, namestart='network-smoke-'):
499 name = rand_name(namestart)
500 body = dict(
501 network=dict(
502 name=name,
503 tenant_id=tenant_id,
504 ),
505 )
506 result = self.network_client.create_network(body=body)
507 network = net_common.DeletableNetwork(client=self.network_client,
508 **result['network'])
509 self.assertEqual(network.name, name)
510 self.set_resource(name, network)
511 return network
512
513 def _list_networks(self):
514 nets = self.network_client.list_networks()
515 return nets['networks']
516
517 def _list_subnets(self):
518 subnets = self.network_client.list_subnets()
519 return subnets['subnets']
520
521 def _list_routers(self):
522 routers = self.network_client.list_routers()
523 return routers['routers']
524
525 def _create_subnet(self, network, namestart='subnet-smoke-'):
526 """
527 Create a subnet for the given network within the cidr block
528 configured for tenant networks.
529 """
530 cfg = self.config.network
531 tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
532 result = None
533 # Repeatedly attempt subnet creation with sequential cidr
534 # blocks until an unallocated block is found.
535 for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
536 body = dict(
537 subnet=dict(
538 ip_version=4,
539 network_id=network.id,
540 tenant_id=network.tenant_id,
541 cidr=str(subnet_cidr),
542 ),
543 )
544 try:
545 result = self.network_client.create_subnet(body=body)
546 break
Mark McClainf2982e82013-07-06 17:48:03 -0400547 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400548 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
549 if not is_overlapping_cidr:
550 raise
551 self.assertIsNotNone(result, 'Unable to allocate tenant network')
552 subnet = net_common.DeletableSubnet(client=self.network_client,
553 **result['subnet'])
554 self.assertEqual(subnet.cidr, str(subnet_cidr))
555 self.set_resource(rand_name(namestart), subnet)
556 return subnet
557
558 def _create_port(self, network, namestart='port-quotatest-'):
559 name = rand_name(namestart)
560 body = dict(
561 port=dict(name=name,
562 network_id=network.id,
563 tenant_id=network.tenant_id))
564 result = self.network_client.create_port(body=body)
565 self.assertIsNotNone(result, 'Unable to allocate port')
566 port = net_common.DeletablePort(client=self.network_client,
567 **result['port'])
568 self.set_resource(name, port)
569 return port
570
Sean Dague6dbc6da2013-05-08 17:49:46 -0400571 def _create_floating_ip(self, server, external_network_id):
572 result = self.network_client.list_ports(device_id=server.id)
573 ports = result.get('ports', [])
574 self.assertEqual(len(ports), 1,
575 "Unable to determine which port to target.")
576 port_id = ports[0]['id']
577 body = dict(
578 floatingip=dict(
579 floating_network_id=external_network_id,
580 port_id=port_id,
581 tenant_id=server.tenant_id,
582 )
583 )
584 result = self.network_client.create_floatingip(body=body)
585 floating_ip = net_common.DeletableFloatingIp(
586 client=self.network_client,
587 **result['floatingip'])
588 self.set_resource(rand_name('floatingip-'), floating_ip)
589 return floating_ip
590
591 def _ping_ip_address(self, ip_address):
592 cmd = ['ping', '-c1', '-w1', ip_address]
593
594 def ping():
595 proc = subprocess.Popen(cmd,
596 stdout=subprocess.PIPE,
597 stderr=subprocess.PIPE)
598 proc.wait()
599 if proc.returncode == 0:
600 return True
601
Nachi Ueno6d580be2013-07-24 10:58:11 -0700602 return tempest.test.call_until_true(
603 ping, self.config.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000604
605 def _is_reachable_via_ssh(self, ip_address, username, private_key,
Nachi Ueno6d580be2013-07-24 10:58:11 -0700606 timeout):
Maru Newbyaf292e82013-05-20 21:32:28 +0000607 ssh_client = ssh.Client(ip_address, username,
608 pkey=private_key,
609 timeout=timeout)
610 return ssh_client.test_connection_auth()
611
Nachi Ueno6d580be2013-07-24 10:58:11 -0700612 def _check_vm_connectivity(self, ip_address, username, private_key):
Maru Newbyaf292e82013-05-20 21:32:28 +0000613 self.assertTrue(self._ping_ip_address(ip_address),
614 "Timed out waiting for %s to become "
615 "reachable" % ip_address)
Nachi Ueno6d580be2013-07-24 10:58:11 -0700616 self.assertTrue(self._is_reachable_via_ssh(
617 ip_address,
618 username,
619 private_key,
620 timeout=self.config.compute.ssh_timeout),
621 'Auth failure in connecting to %s@%s via ssh' %
622 (username, ip_address))
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200623
624
625class OrchestrationScenarioTest(OfficialClientTest):
626 """
627 Base class for orchestration scenario tests
628 """
629
630 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -0700631 def setUpClass(cls):
632 super(OrchestrationScenarioTest, cls).setUpClass()
633 if not cls.config.service_available.heat:
634 raise cls.skipException("Heat support is required")
635
636 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200637 def credentials(cls):
638 username = cls.config.identity.admin_username
639 password = cls.config.identity.admin_password
640 tenant_name = cls.config.identity.tenant_name
641 return username, tenant_name, password
642
643 def _load_template(self, base_file, file_name):
644 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
645 file_name)
646 with open(filepath) as f:
647 return f.read()
648
649 @classmethod
650 def _stack_rand_name(cls):
651 return rand_name(cls.__name__ + '-')