blob: a358f200bbbe234a7fffbabdfe2d794b295c6d92 [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
19import logging
20import subprocess
21
22# Default client libs
23import glanceclient
24import keystoneclient.v2_0.client
25import netaddr
26import novaclient.client
27try:
28 # TODO(sdague): is there are reason this is still optional
29 from quantumclient.common import exceptions as exc
30 import quantumclient.v2_0.client
31
32except ImportError:
33 pass
34
Sean Dague1937d092013-05-17 16:36:38 -040035from tempest.api.network import common as net_common
Sean Dague6dbc6da2013-05-08 17:49:46 -040036from tempest.common.utils.data_utils import rand_name
37from tempest import exceptions
38import tempest.manager
39import tempest.test
Sean Dague6dbc6da2013-05-08 17:49:46 -040040
41
42LOG = logging.getLogger(__name__)
43
44
45class OfficialClientManager(tempest.manager.Manager):
46 """
47 Manager that provides access to the official python clients for
48 calling various OpenStack APIs.
49 """
50
51 NOVACLIENT_VERSION = '2'
52
53 def __init__(self):
54 super(OfficialClientManager, self).__init__()
55 self.compute_client = self._get_compute_client()
56 self.image_client = self._get_image_client()
57 self.identity_client = self._get_identity_client()
58 self.network_client = self._get_network_client()
59 self.client_attr_names = [
60 'compute_client',
61 'image_client',
62 'identity_client',
63 'network_client',
64 ]
65
66 def _get_compute_client(self, username=None, password=None,
67 tenant_name=None):
68 # Novaclient will not execute operations for anyone but the
69 # identified user, so a new client needs to be created for
70 # each user that operations need to be performed for.
71 if not username:
72 username = self.config.identity.username
73 if not password:
74 password = self.config.identity.password
75 if not tenant_name:
76 tenant_name = self.config.identity.tenant_name
77
78 if None in (username, password, tenant_name):
79 msg = ("Missing required credentials for compute client. "
80 "username: %(username)s, password: %(password)s, "
81 "tenant_name: %(tenant_name)s") % locals()
82 raise exceptions.InvalidConfiguration(msg)
83
84 auth_url = self.config.identity.uri
85 dscv = self.config.identity.disable_ssl_certificate_validation
86
87 client_args = (username, password, tenant_name, auth_url)
88
89 # Create our default Nova client to use in testing
90 service_type = self.config.compute.catalog_type
91 return novaclient.client.Client(self.NOVACLIENT_VERSION,
92 *client_args,
93 service_type=service_type,
94 no_cache=True,
95 insecure=dscv)
96
97 def _get_image_client(self):
98 keystone = self._get_identity_client()
99 token = keystone.auth_token
100 endpoint = keystone.service_catalog.url_for(service_type='image',
101 endpoint_type='publicURL')
102 dscv = self.config.identity.disable_ssl_certificate_validation
103 return glanceclient.Client('1', endpoint=endpoint, token=token,
104 insecure=dscv)
105
106 def _get_identity_client(self, username=None, password=None,
107 tenant_name=None):
108 # This identity client is not intended to check the security
109 # of the identity service, so use admin credentials by default.
110 if not username:
111 username = self.config.identity.admin_username
112 if not password:
113 password = self.config.identity.admin_password
114 if not tenant_name:
115 tenant_name = self.config.identity.admin_tenant_name
116
117 if None in (username, password, tenant_name):
118 msg = ("Missing required credentials for identity client. "
119 "username: %(username)s, password: %(password)s, "
120 "tenant_name: %(tenant_name)s") % locals()
121 raise exceptions.InvalidConfiguration(msg)
122
123 auth_url = self.config.identity.uri
124 dscv = self.config.identity.disable_ssl_certificate_validation
125
126 return keystoneclient.v2_0.client.Client(username=username,
127 password=password,
128 tenant_name=tenant_name,
129 auth_url=auth_url,
130 insecure=dscv)
131
132 def _get_network_client(self):
133 # The intended configuration is for the network client to have
134 # admin privileges and indicate for whom resources are being
135 # created via a 'tenant_id' parameter. This will often be
136 # preferable to authenticating as a specific user because
137 # working with certain resources (public routers and networks)
138 # often requires admin privileges anyway.
139 username = self.config.identity.admin_username
140 password = self.config.identity.admin_password
141 tenant_name = self.config.identity.admin_tenant_name
142
143 if None in (username, password, tenant_name):
144 msg = ("Missing required credentials for network client. "
145 "username: %(username)s, password: %(password)s, "
146 "tenant_name: %(tenant_name)s") % locals()
147 raise exceptions.InvalidConfiguration(msg)
148
149 auth_url = self.config.identity.uri
150 dscv = self.config.identity.disable_ssl_certificate_validation
151
152 return quantumclient.v2_0.client.Client(username=username,
153 password=password,
154 tenant_name=tenant_name,
155 auth_url=auth_url,
156 insecure=dscv)
157
158
159class OfficialClientTest(tempest.test.TestCase):
160 """
161 Official Client test base class for scenario testing.
162
163 Official Client tests are tests that have the following characteristics:
164
165 * Test basic operations of an API, typically in an order that
166 a regular user would perform those operations
167 * Test only the correct inputs and action paths -- no fuzz or
168 random input data is sent, only valid inputs.
169 * Use only the default client tool for calling an API
170 """
171
172 manager_class = OfficialClientManager
173
174 @classmethod
175 def tearDownClass(cls):
176 # NOTE(jaypipes): Because scenario tests are typically run in a
177 # specific order, and because test methods in scenario tests
178 # generally create resources in a particular order, we destroy
179 # resources in the reverse order in which resources are added to
180 # the scenario test class object
181 while cls.os_resources:
182 thing = cls.os_resources.pop()
183 LOG.debug("Deleting %r from shared resources of %s" %
184 (thing, cls.__name__))
185
186 try:
187 # OpenStack resources are assumed to have a delete()
188 # method which destroys the resource...
189 thing.delete()
190 except Exception as e:
191 # If the resource is already missing, mission accomplished.
192 if e.__class__.__name__ == 'NotFound':
193 continue
194 raise
195
196 def is_deletion_complete():
197 # Deletion testing is only required for objects whose
198 # existence cannot be checked via retrieval.
199 if isinstance(thing, dict):
200 return True
201 try:
202 thing.get()
203 except Exception as e:
204 # Clients are expected to return an exception
205 # called 'NotFound' if retrieval fails.
206 if e.__class__.__name__ == 'NotFound':
207 return True
208 raise
209 return False
210
211 # Block until resource deletion has completed or timed-out
212 tempest.test.call_until_true(is_deletion_complete, 10, 1)
213
214
215class NetworkScenarioTest(OfficialClientTest):
216 """
217 Base class for network scenario tests
218 """
219
220 @classmethod
221 def check_preconditions(cls):
222 if (cls.config.network.quantum_available):
223 cls.enabled = True
224 #verify that quantum_available is telling the truth
225 try:
226 cls.network_client.list_networks()
227 except exc.EndpointNotFound:
228 cls.enabled = False
229 raise
230 else:
231 cls.enabled = False
232 msg = 'Quantum not available'
233 raise cls.skipException(msg)
234
235 @classmethod
236 def setUpClass(cls):
237 super(NetworkScenarioTest, cls).setUpClass()
238 cls.tenant_id = cls.manager._get_identity_client(
239 cls.config.identity.username,
240 cls.config.identity.password,
241 cls.config.identity.tenant_name).tenant_id
242
243 def _create_keypair(self, client, namestart='keypair-smoke-'):
244 kp_name = rand_name(namestart)
245 keypair = client.keypairs.create(kp_name)
246 try:
247 self.assertEqual(keypair.id, kp_name)
248 self.set_resource(kp_name, keypair)
249 except AttributeError:
250 self.fail("Keypair object not successfully created.")
251 return keypair
252
253 def _create_security_group(self, client, namestart='secgroup-smoke-'):
254 # Create security group
255 sg_name = rand_name(namestart)
256 sg_desc = sg_name + " description"
257 secgroup = client.security_groups.create(sg_name, sg_desc)
258 try:
259 self.assertEqual(secgroup.name, sg_name)
260 self.assertEqual(secgroup.description, sg_desc)
261 self.set_resource(sg_name, secgroup)
262 except AttributeError:
263 self.fail("SecurityGroup object not successfully created.")
264
265 # Add rules to the security group
266 rulesets = [
267 {
268 # ssh
269 'ip_protocol': 'tcp',
270 'from_port': 22,
271 'to_port': 22,
272 'cidr': '0.0.0.0/0',
273 'group_id': secgroup.id
274 },
275 {
276 # ping
277 'ip_protocol': 'icmp',
278 'from_port': -1,
279 'to_port': -1,
280 'cidr': '0.0.0.0/0',
281 'group_id': secgroup.id
282 }
283 ]
284 for ruleset in rulesets:
285 try:
286 client.security_group_rules.create(secgroup.id, **ruleset)
287 except Exception:
288 self.fail("Failed to create rule in security group.")
289
290 return secgroup
291
292 def _create_network(self, tenant_id, namestart='network-smoke-'):
293 name = rand_name(namestart)
294 body = dict(
295 network=dict(
296 name=name,
297 tenant_id=tenant_id,
298 ),
299 )
300 result = self.network_client.create_network(body=body)
301 network = net_common.DeletableNetwork(client=self.network_client,
302 **result['network'])
303 self.assertEqual(network.name, name)
304 self.set_resource(name, network)
305 return network
306
307 def _list_networks(self):
308 nets = self.network_client.list_networks()
309 return nets['networks']
310
311 def _list_subnets(self):
312 subnets = self.network_client.list_subnets()
313 return subnets['subnets']
314
315 def _list_routers(self):
316 routers = self.network_client.list_routers()
317 return routers['routers']
318
319 def _create_subnet(self, network, namestart='subnet-smoke-'):
320 """
321 Create a subnet for the given network within the cidr block
322 configured for tenant networks.
323 """
324 cfg = self.config.network
325 tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
326 result = None
327 # Repeatedly attempt subnet creation with sequential cidr
328 # blocks until an unallocated block is found.
329 for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
330 body = dict(
331 subnet=dict(
332 ip_version=4,
333 network_id=network.id,
334 tenant_id=network.tenant_id,
335 cidr=str(subnet_cidr),
336 ),
337 )
338 try:
339 result = self.network_client.create_subnet(body=body)
340 break
341 except exc.QuantumClientException as e:
342 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
343 if not is_overlapping_cidr:
344 raise
345 self.assertIsNotNone(result, 'Unable to allocate tenant network')
346 subnet = net_common.DeletableSubnet(client=self.network_client,
347 **result['subnet'])
348 self.assertEqual(subnet.cidr, str(subnet_cidr))
349 self.set_resource(rand_name(namestart), subnet)
350 return subnet
351
352 def _create_port(self, network, namestart='port-quotatest-'):
353 name = rand_name(namestart)
354 body = dict(
355 port=dict(name=name,
356 network_id=network.id,
357 tenant_id=network.tenant_id))
358 result = self.network_client.create_port(body=body)
359 self.assertIsNotNone(result, 'Unable to allocate port')
360 port = net_common.DeletablePort(client=self.network_client,
361 **result['port'])
362 self.set_resource(name, port)
363 return port
364
365 def _create_server(self, client, network, name, key_name, security_groups):
366 flavor_id = self.config.compute.flavor_ref
367 base_image_id = self.config.compute.image_ref
368 create_kwargs = {
369 'nics': [
370 {'net-id': network.id},
371 ],
372 'key_name': key_name,
373 'security_groups': security_groups,
374 }
375 server = client.servers.create(name, base_image_id, flavor_id,
376 **create_kwargs)
377 try:
378 self.assertEqual(server.name, name)
379 self.set_resource(name, server)
380 except AttributeError:
381 self.fail("Server not successfully created.")
382 self.status_timeout(client.servers, server.id, 'ACTIVE')
383 # The instance retrieved on creation is missing network
384 # details, necessitating retrieval after it becomes active to
385 # ensure correct details.
386 server = client.servers.get(server.id)
387 self.set_resource(name, server)
388 return server
389
390 def _create_floating_ip(self, server, external_network_id):
391 result = self.network_client.list_ports(device_id=server.id)
392 ports = result.get('ports', [])
393 self.assertEqual(len(ports), 1,
394 "Unable to determine which port to target.")
395 port_id = ports[0]['id']
396 body = dict(
397 floatingip=dict(
398 floating_network_id=external_network_id,
399 port_id=port_id,
400 tenant_id=server.tenant_id,
401 )
402 )
403 result = self.network_client.create_floatingip(body=body)
404 floating_ip = net_common.DeletableFloatingIp(
405 client=self.network_client,
406 **result['floatingip'])
407 self.set_resource(rand_name('floatingip-'), floating_ip)
408 return floating_ip
409
410 def _ping_ip_address(self, ip_address):
411 cmd = ['ping', '-c1', '-w1', ip_address]
412
413 def ping():
414 proc = subprocess.Popen(cmd,
415 stdout=subprocess.PIPE,
416 stderr=subprocess.PIPE)
417 proc.wait()
418 if proc.returncode == 0:
419 return True
420
421 # TODO(mnewby) Allow configuration of execution and sleep duration.
422 return tempest.test.call_until_true(ping, 20, 1)