blob: c35a01a21ac8c965f2e8ec680f2e7ca8991a8d98 [file] [log] [blame]
nithya-ganesan222efd72015-01-22 12:20:27 +00001# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
Andrea Frittolib4ec4942017-09-01 18:29:45 +01002# Copyright (c) 2017 IBM Corp.
nithya-ganesan222efd72015-01-22 12:20:27 +00003# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Andrea Frittoli9806f2d2017-09-01 14:50:07 +010015import fixtures
nithya-ganesan222efd72015-01-22 12:20:27 +000016from oslo_log import log as logging
Andrea Frittolib4ec4942017-09-01 18:29:45 +010017from oslo_utils import excutils
nithya-ganesan222efd72015-01-22 12:20:27 +000018
Ken'ichi Ohmichief1c1ce2017-03-10 11:07:10 -080019from tempest.lib.common.utils import data_utils
Andrea Frittoli (andreaf)db9672e2016-02-23 14:07:24 -050020from tempest.lib import exceptions as lib_exc
Andrea Frittoli (andreaf)8def7ca2015-05-13 14:24:19 +010021
nithya-ganesan222efd72015-01-22 12:20:27 +000022LOG = logging.getLogger(__name__)
23
24
Andrea Frittoli8871fca2017-08-10 23:43:25 +010025def _network_service(clients, use_neutron):
Andrea Frittoli557320e2017-08-09 21:08:08 +010026 # Internal helper to select the right network clients
Andrea Frittoli463a8a62017-08-09 16:55:33 +010027 if use_neutron:
Andrea Frittoli8871fca2017-08-10 23:43:25 +010028 return clients.network
Andrea Frittoli463a8a62017-08-09 16:55:33 +010029 else:
Andrea Frittoli8871fca2017-08-10 23:43:25 +010030 return clients.compute
Matthew Treinish861619c2016-06-16 17:11:49 -040031
32
Andrea Frittoli8871fca2017-08-10 23:43:25 +010033def create_ssh_security_group(clients, add_rule=False, ethertype='IPv4',
Andrea Frittoli1fa7a602017-08-09 16:28:55 +010034 use_neutron=True):
Andrea Frittoli557320e2017-08-09 21:08:08 +010035 """Create a security group for ping/ssh testing
36
37 Create a security group to be attached to a VM using the nova or neutron
38 clients. If rules are added, the group can be attached to a VM to enable
39 connectivity validation over ICMP and further testing over SSH.
40
Andrea Frittoli8871fca2017-08-10 23:43:25 +010041 :param clients: Instance of `tempest.lib.services.clients.ServiceClients`
42 or of a subclass of it. Resources are provisioned using clients from
43 `clients`.
Andrea Frittoli557320e2017-08-09 21:08:08 +010044 :param add_rule: Whether security group rules are provisioned or not.
45 Defaults to `False`.
46 :param ethertype: 'IPv4' or 'IPv6'. Honoured only in case neutron is used.
47 :param use_neutron: When True resources are provisioned via neutron, when
48 False resources are provisioned via nova.
49 :returns: A dictionary with the security group as returned by the API.
50
51 Examples::
52
53 from tempest.common import validation_resources as vr
54 from tempest.lib import auth
55 from tempest.lib.services import clients
56
57 creds = auth.get_credentials('http://mycloud/identity/v3',
58 username='me', project_name='me',
59 password='secret', domain_name='Default')
Andrea Frittoli8871fca2017-08-10 23:43:25 +010060 osclients = clients.ServiceClients(creds, 'http://mycloud/identity/v3')
Andrea Frittoli557320e2017-08-09 21:08:08 +010061 # Security group for IPv4 tests
Andrea Frittoli8871fca2017-08-10 23:43:25 +010062 sg4 = vr.create_ssh_security_group(osclients, add_rule=True)
Andrea Frittoli557320e2017-08-09 21:08:08 +010063 # Security group for IPv6 tests
Andrea Frittoli8871fca2017-08-10 23:43:25 +010064 sg6 = vr.create_ssh_security_group(osclients, ethertype='IPv6',
65 add_rule=True)
Andrea Frittoli557320e2017-08-09 21:08:08 +010066 """
Andrea Frittoli8871fca2017-08-10 23:43:25 +010067 network_service = _network_service(clients, use_neutron)
Andrea Frittoli463a8a62017-08-09 16:55:33 +010068 security_groups_client = network_service.SecurityGroupsClient()
69 security_group_rules_client = network_service.SecurityGroupRulesClient()
70 # Security Group clients for nova and neutron behave the same
nithya-ganesan222efd72015-01-22 12:20:27 +000071 sg_name = data_utils.rand_name('securitygroup-')
72 sg_description = data_utils.rand_name('description-')
Yaroslav Lobankove5cc9fb2015-08-07 17:30:51 +030073 security_group = security_groups_client.create_security_group(
ghanshyamb610b772015-08-24 17:29:38 +090074 name=sg_name, description=sg_description)['security_group']
Andrea Frittoli463a8a62017-08-09 16:55:33 +010075 # Security Group Rules clients require different parameters depending on
76 # the network service in use
nithya-ganesan222efd72015-01-22 12:20:27 +000077 if add_rule:
Andrea Frittolib4ec4942017-09-01 18:29:45 +010078 try:
79 if use_neutron:
80 security_group_rules_client.create_security_group_rule(
81 security_group_id=security_group['id'],
82 protocol='tcp',
83 ethertype=ethertype,
84 port_range_min=22,
85 port_range_max=22,
86 direction='ingress')
87 security_group_rules_client.create_security_group_rule(
88 security_group_id=security_group['id'],
89 protocol='icmp',
90 ethertype=ethertype,
91 direction='ingress')
92 else:
93 security_group_rules_client.create_security_group_rule(
94 parent_group_id=security_group['id'], ip_protocol='tcp',
95 from_port=22, to_port=22)
96 security_group_rules_client.create_security_group_rule(
97 parent_group_id=security_group['id'], ip_protocol='icmp',
98 from_port=-1, to_port=-1)
99 except Exception as sgc_exc:
100 # If adding security group rules fails, we cleanup the SG before
101 # re-raising the failure up
102 with excutils.save_and_reraise_exception():
103 try:
104 msg = ('Error while provisioning security group rules in '
105 'security group %s. Trying to cleanup.')
106 # The exceptions logging is already handled, so using
107 # debug here just to provide more context
108 LOG.debug(msg, sgc_exc)
109 clear_validation_resources(
110 clients, keypair=None, floating_ip=None,
111 security_group=security_group,
112 use_neutron=use_neutron)
113 except Exception as cleanup_exc:
114 msg = ('Error during cleanup of a security group. '
115 'The cleanup was triggered by an exception during '
116 'the provisioning of security group rules.\n'
117 'Provisioning exception: %s\n'
118 'First cleanup exception: %s')
119 LOG.exception(msg, sgc_exc, cleanup_exc)
nithya-ganesan222efd72015-01-22 12:20:27 +0000120 LOG.debug("SSH Validation resource security group with tcp and icmp "
Jordan Pittier525ec712016-12-07 17:51:26 +0100121 "rules %s created", sg_name)
nithya-ganesan222efd72015-01-22 12:20:27 +0000122 return security_group
123
124
Andrea Frittoli8871fca2017-08-10 23:43:25 +0100125def create_validation_resources(clients, keypair=False, floating_ip=False,
126 security_group=False,
127 security_group_rules=False,
Andrea Frittoli1fa7a602017-08-09 16:28:55 +0100128 ethertype='IPv4', use_neutron=True,
129 floating_network_id=None,
130 floating_network_name=None):
Andrea Frittoli557320e2017-08-09 21:08:08 +0100131 """Provision resources for VM ping/ssh testing
132
133 Create resources required to be able to ping / ssh a virtual machine:
134 keypair, security group, security group rules and a floating IP.
135 Which of those resources are required may depend on the cloud setup and on
Andrea Frittoli8871fca2017-08-10 23:43:25 +0100136 the specific test and it can be controlled via the corresponding
137 arguments.
Andrea Frittoli557320e2017-08-09 21:08:08 +0100138
139 Provisioned resources are returned in a dictionary.
140
Andrea Frittoli8871fca2017-08-10 23:43:25 +0100141 :param clients: Instance of `tempest.lib.services.clients.ServiceClients`
142 or of a subclass of it. Resources are provisioned using clients from
143 `clients`.
144 :param keypair: Whether to provision a keypair. Defaults to False.
145 :param floating_ip: Whether to provision a floating IP. Defaults to False.
146 :param security_group: Whether to provision a security group. Defaults to
147 False.
148 :param security_group_rules: Whether to provision security group rules.
149 Defaults to False.
Andrea Frittoli557320e2017-08-09 21:08:08 +0100150 :param ethertype: 'IPv4' or 'IPv6'. Honoured only in case neutron is used.
151 :param use_neutron: When True resources are provisioned via neutron, when
152 False resources are provisioned via nova.
153 :param floating_network_id: The id of the network used to provision a
154 floating IP. Only used if a floating IP is requested and with neutron.
155 :param floating_network_name: The name of the floating IP pool used to
156 provision the floating IP. Only used if a floating IP is requested and
157 with nova-net.
Andrea Frittolib4ec4942017-09-01 18:29:45 +0100158 :returns: A dictionary with the resources in the format they are returned
159 by the API. Valid keys are 'keypair', 'floating_ip' and
160 'security_group'.
Andrea Frittoli557320e2017-08-09 21:08:08 +0100161
162 Examples::
163
164 from tempest.common import validation_resources as vr
165 from tempest.lib import auth
166 from tempest.lib.services import clients
167
168 creds = auth.get_credentials('http://mycloud/identity/v3',
169 username='me', project_name='me',
170 password='secret', domain_name='Default')
Andrea Frittoli8871fca2017-08-10 23:43:25 +0100171 osclients = clients.ServiceClients(creds, 'http://mycloud/identity/v3')
Andrea Frittoli557320e2017-08-09 21:08:08 +0100172 # Request keypair and floating IP
173 resources = dict(keypair=True, security_group=False,
174 security_group_rules=False, floating_ip=True)
175 resources = vr.create_validation_resources(
Andrea Frittoli8871fca2017-08-10 23:43:25 +0100176 osclients, use_neutron=True,
177 floating_network_id='4240E68E-23DA-4C82-AC34-9FEFAA24521C',
178 **resources)
Andrea Frittoli557320e2017-08-09 21:08:08 +0100179
180 # The floating IP to be attached to the VM
181 floating_ip = resources['floating_ip']['ip']
182 """
nithya-ganesan222efd72015-01-22 12:20:27 +0000183 # Create and Return the validation resources required to validate a VM
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100184 msg = ('Requested validation resources keypair %s, floating IP %s, '
185 'security group %s')
186 LOG.debug(msg, keypair, floating_ip, security_group)
nithya-ganesan222efd72015-01-22 12:20:27 +0000187 validation_data = {}
Andrea Frittolib4ec4942017-09-01 18:29:45 +0100188 try:
189 if keypair:
190 keypair_name = data_utils.rand_name('keypair')
191 validation_data.update(
192 clients.compute.KeyPairsClient().create_keypair(
193 name=keypair_name))
194 LOG.debug("Validation resource key %s created", keypair_name)
195 if security_group:
196 validation_data['security_group'] = create_ssh_security_group(
197 clients, add_rule=security_group_rules,
198 use_neutron=use_neutron, ethertype=ethertype)
199 if floating_ip:
200 floating_ip_client = _network_service(
201 clients, use_neutron).FloatingIPsClient()
202 if use_neutron:
203 floatingip = floating_ip_client.create_floatingip(
204 floating_network_id=floating_network_id)
205 # validation_resources['floating_ip'] has historically looked
206 # like a compute API POST /os-floating-ips response, so we need
207 # to mangle it a bit for a Neutron response with different
208 # fields.
209 validation_data['floating_ip'] = floatingip['floatingip']
210 validation_data['floating_ip']['ip'] = (
211 floatingip['floatingip']['floating_ip_address'])
212 else:
213 # NOTE(mriedem): The os-floating-ips compute API was deprecated
214 # in the 2.36 microversion. Any tests for CRUD operations on
215 # floating IPs using the compute API should be capped at 2.35.
216 validation_data.update(floating_ip_client.create_floating_ip(
217 pool=floating_network_name))
218 LOG.debug("Validation resource floating IP %s created",
219 validation_data['floating_ip'])
220 except Exception as prov_exc:
221 # If something goes wrong, cleanup as much as possible before we
222 # re-raise the exception
223 with excutils.save_and_reraise_exception():
224 if validation_data:
225 # Cleanup may fail as well
226 try:
227 msg = ('Error while provisioning validation resources %s. '
228 'Trying to cleanup what we provisioned so far: %s')
229 # The exceptions logging is already handled, so using
230 # debug here just to provide more context
231 LOG.debug(msg, prov_exc, str(validation_data))
232 clear_validation_resources(
233 clients,
234 keypair=validation_data.get('keypair', None),
235 floating_ip=validation_data.get('floating_ip', None),
236 security_group=validation_data.get('security_group',
237 None),
238 use_neutron=use_neutron)
239 except Exception as cleanup_exc:
240 msg = ('Error during cleanup of validation resources. '
241 'The cleanup was triggered by an exception during '
242 'the provisioning step.\n'
243 'Provisioning exception: %s\n'
244 'First cleanup exception: %s')
245 LOG.exception(msg, prov_exc, cleanup_exc)
nithya-ganesan222efd72015-01-22 12:20:27 +0000246 return validation_data
247
248
Andrea Frittoli8871fca2017-08-10 23:43:25 +0100249def clear_validation_resources(clients, keypair=None, floating_ip=None,
250 security_group=None, use_neutron=True):
Andrea Frittoli557320e2017-08-09 21:08:08 +0100251 """Cleanup resources for VM ping/ssh testing
252
253 Cleanup a set of resources provisioned via `create_validation_resources`.
254 In case of errors during cleanup, the exception is logged and the cleanup
255 process is continued. The first exception that was raised is re-raised
256 after the cleanup is complete.
257
Andrea Frittoli8871fca2017-08-10 23:43:25 +0100258 :param clients: Instance of `tempest.lib.services.clients.ServiceClients`
259 or of a subclass of it. Resources are provisioned using clients from
260 `clients`.
261 :param keypair: A dictionary with the keypair to be deleted. Defaults to
262 None.
263 :param floating_ip: A dictionary with the floating_ip to be deleted.
264 Defaults to None.
265 :param security_group: A dictionary with the security_group to be deleted.
266 Defaults to None.
Andrea Frittoli557320e2017-08-09 21:08:08 +0100267 :param use_neutron: When True resources are provisioned via neutron, when
268 False resources are provisioned via nova.
Andrea Frittoli557320e2017-08-09 21:08:08 +0100269
270 Examples::
271
272 from tempest.common import validation_resources as vr
273 from tempest.lib import auth
274 from tempest.lib.services import clients
275
276 creds = auth.get_credentials('http://mycloud/identity/v3',
277 username='me', project_name='me',
278 password='secret', domain_name='Default')
Andrea Frittoli8871fca2017-08-10 23:43:25 +0100279 osclients = clients.ServiceClients(creds, 'http://mycloud/identity/v3')
Andrea Frittoli557320e2017-08-09 21:08:08 +0100280 # Request keypair and floating IP
281 resources = dict(keypair=True, security_group=False,
282 security_group_rules=False, floating_ip=True)
283 resources = vr.create_validation_resources(
Andrea Frittoli8871fca2017-08-10 23:43:25 +0100284 osclients, validation_resources=resources, use_neutron=True,
Andrea Frittoli557320e2017-08-09 21:08:08 +0100285 floating_network_id='4240E68E-23DA-4C82-AC34-9FEFAA24521C')
286
287 # Now cleanup the resources
288 try:
Andrea Frittoli8871fca2017-08-10 23:43:25 +0100289 vr.clear_validation_resources(osclients, use_neutron=True,
290 **resources)
Andrea Frittoli557320e2017-08-09 21:08:08 +0100291 except Exception as e:
292 LOG.exception('Something went wrong during cleanup, ignoring')
293 """
nithya-ganesan222efd72015-01-22 12:20:27 +0000294 has_exception = None
Andrea Frittoli8871fca2017-08-10 23:43:25 +0100295 if keypair:
296 keypair_client = clients.compute.KeyPairsClient()
297 keypair_name = keypair['name']
298 try:
299 keypair_client.delete_keypair(keypair_name)
300 except lib_exc.NotFound:
301 LOG.warning(
302 "Keypair %s is not found when attempting to delete",
303 keypair_name
304 )
305 except Exception as exc:
306 LOG.exception('Exception raised while deleting key %s',
307 keypair_name)
308 if not has_exception:
309 has_exception = exc
310 network_service = _network_service(clients, use_neutron)
311 if security_group:
312 security_group_client = network_service.SecurityGroupsClient()
313 sec_id = security_group['id']
314 try:
315 security_group_client.delete_security_group(sec_id)
316 security_group_client.wait_for_resource_deletion(sec_id)
317 except lib_exc.NotFound:
318 LOG.warning("Security group %s is not found when attempting "
319 "to delete", sec_id)
320 except lib_exc.Conflict as exc:
321 LOG.exception('Conflict while deleting security '
322 'group %s VM might not be deleted', sec_id)
323 if not has_exception:
324 has_exception = exc
325 except Exception as exc:
326 LOG.exception('Exception raised while deleting security '
327 'group %s', sec_id)
328 if not has_exception:
329 has_exception = exc
330 if floating_ip:
331 floating_ip_client = network_service.FloatingIPsClient()
332 fip_id = floating_ip['id']
333 try:
334 if use_neutron:
335 floating_ip_client.delete_floatingip(fip_id)
336 else:
337 floating_ip_client.delete_floating_ip(fip_id)
338 except lib_exc.NotFound:
339 LOG.warning('Floating ip %s not found while attempting to '
340 'delete', fip_id)
341 except Exception as exc:
342 LOG.exception('Exception raised while deleting ip %s', fip_id)
343 if not has_exception:
344 has_exception = exc
nithya-ganesan222efd72015-01-22 12:20:27 +0000345 if has_exception:
346 raise has_exception
Andrea Frittoli9806f2d2017-09-01 14:50:07 +0100347
348
349class ValidationResourcesFixture(fixtures.Fixture):
350 """Fixture to provision and cleanup validation resources"""
351
352 DICT_KEYS = ['keypair', 'security_group', 'floating_ip']
353
354 def __init__(self, clients, keypair=False, floating_ip=False,
355 security_group=False, security_group_rules=False,
356 ethertype='IPv4', use_neutron=True, floating_network_id=None,
357 floating_network_name=None):
358 """Create a ValidationResourcesFixture
359
360 Create a ValidationResourcesFixture fixtures, which provisions the
361 resources required to be able to ping / ssh a virtual machine upon
362 setUp and clears them out upon cleanup. Resources are keypair,
363 security group, security group rules and a floating IP - depending
364 on the params.
365
366 The fixture exposes a dictionary that includes provisioned resources.
367
368 :param clients: `tempest.lib.services.clients.ServiceClients` or of a
369 subclass of it. Resources are provisioned using clients from
370 `clients`.
371 :param keypair: Whether to provision a keypair. Defaults to False.
372 :param floating_ip: Whether to provision a floating IP.
373 Defaults to False.
374 :param security_group: Whether to provision a security group.
375 Defaults to False.
376 :param security_group_rules: Whether to provision security group rules.
377 Defaults to False.
378 :param ethertype: 'IPv4' or 'IPv6'. Honoured only if neutron is used.
379 :param use_neutron: When True resources are provisioned via neutron,
380 when False resources are provisioned via nova.
381 :param floating_network_id: The id of the network used to provision a
382 floating IP. Only used if a floating IP is requested in case
383 neutron is used.
384 :param floating_network_name: The name of the floating IP pool used to
385 provision the floating IP. Only used if a floating IP is requested
386 and with nova-net.
387 :returns: A dictionary with the same keys as the input
388 `validation_resources` and the resources for values in the format
389 they are returned by the API.
390
391 Examples::
392
393 from tempest.common import validation_resources as vr
394 from tempest.lib import auth
395 from tempest.lib.services import clients
396 import testtools
397
398
399 class TestWithVR(testtools.TestCase):
400
401 def setUp(self):
402 creds = auth.get_credentials(
403 'http://mycloud/identity/v3',
404 username='me', project_name='me',
405 password='secret', domain_name='Default')
406
407 osclients = clients.ServiceClients(
408 creds, 'http://mycloud/identity/v3')
409 # Request keypair and floating IP
410 resources = dict(keypair=True, security_group=False,
411 security_group_rules=False,
412 floating_ip=True)
413 network_id = '4240E68E-23DA-4C82-AC34-9FEFAA24521C'
414 self.vr = self.useFixture(vr.ValidationResourcesFixture(
415 osclients, use_neutron=True,
416 floating_network_id=network_id,
417 **resources)
418
419 def test_use_ip(self):
420 # The floating IP to be attached to the VM
421 floating_ip = self.vr['floating_ip']['ip']
422 """
423 self._clients = clients
424 self._keypair = keypair
425 self._floating_ip = floating_ip
426 self._security_group = security_group
427 self._security_group_rules = security_group_rules
428 self._ethertype = ethertype
429 self._use_neutron = use_neutron
430 self._floating_network_id = floating_network_id
431 self._floating_network_name = floating_network_name
432 self._validation_resources = None
433
434 def _setUp(self):
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100435 msg = ('Requested setup of ValidationResources keypair %s, floating '
436 'IP %s, security group %s')
437 LOG.debug(msg, self._keypair, self._floating_ip, self._security_group)
Andrea Frittoli9806f2d2017-09-01 14:50:07 +0100438 self._validation_resources = create_validation_resources(
439 self._clients, keypair=self._keypair,
440 floating_ip=self._floating_ip,
441 security_group=self._security_group,
442 security_group_rules=self._security_group_rules,
443 ethertype=self._ethertype, use_neutron=self._use_neutron,
444 floating_network_id=self._floating_network_id,
445 floating_network_name=self._floating_network_name)
446 # If provisioning raises an exception we won't have anything to
447 # cleanup here, so we don't need a try-finally around provisioning
448 vr = self._validation_resources
449 self.addCleanup(clear_validation_resources, self._clients,
Andrea Frittoli9f416dd2017-08-10 15:38:00 +0100450 keypair=vr.get('keypair', None),
451 floating_ip=vr.get('floating_ip', None),
452 security_group=vr.get('security_group', None),
Andrea Frittoli9806f2d2017-09-01 14:50:07 +0100453 use_neutron=self._use_neutron)
454
455 @property
456 def resources(self):
457 return self._validation_resources