910684cf174c4ff2f01e02647d5b16a221ce28fd
[snaps.git] / snaps / openstack / utils / tests / nova_utils_tests.py
1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 #                    and others.  All rights reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 import logging
16 import time
17 import uuid
18
19 import os
20
21 from snaps import file_utils
22 from snaps.config.flavor import FlavorConfig
23 from snaps.config.network import PortConfig
24 from snaps.config.vm_inst import VmInstanceConfig
25 from snaps.config.volume import VolumeConfig
26 from snaps.openstack import create_instance
27 from snaps.openstack.create_flavor import OpenStackFlavor
28 from snaps.openstack.create_image import OpenStackImage
29 from snaps.openstack.create_instance import OpenStackVmInstance
30 from snaps.openstack.create_network import OpenStackNetwork
31 from snaps.openstack.create_volume import OpenStackVolume
32 from snaps.openstack.tests import openstack_tests
33 from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
34 from snaps.openstack.utils import (
35     nova_utils, neutron_utils, glance_utils, cinder_utils, keystone_utils)
36 from snaps.openstack.utils.nova_utils import NovaException
37
38 __author__ = 'spisarski'
39
40 logger = logging.getLogger('nova_utils_tests')
41
42
43 class NovaSmokeTests(OSComponentTestCase):
44     """
45     Tests to ensure that the nova client can communicate with the cloud
46     """
47
48     def test_nova_connect_success(self):
49         """
50         Tests to ensure that the proper credentials can connect.
51         """
52         nova = nova_utils.nova_client(self.os_creds)
53
54         # This should not throw an exception
55         nova.flavors.list()
56
57     def test_nova_get_hypervisor_hosts(self):
58         """
59         Tests to ensure that get_hypervisors() function works.
60         """
61         nova = nova_utils.nova_client(self.os_creds)
62
63         hosts = nova_utils.get_hypervisor_hosts(nova)
64         # This should not throw an exception
65         self.assertGreaterEqual(len(hosts), 1)
66
67     def test_nova_connect_fail(self):
68         """
69         Tests to ensure that the improper credentials cannot connect.
70         """
71         from snaps.openstack.os_credentials import OSCreds
72
73         nova = nova_utils.nova_client(
74             OSCreds(username='user', password='pass',
75                     auth_url=self.os_creds.auth_url,
76                     project_name=self.os_creds.project_name,
77                     proxy_settings=self.os_creds.proxy_settings))
78
79         # This should throw an exception
80         with self.assertRaises(Exception):
81             nova.flavors.list()
82
83
84 class NovaUtilsKeypairTests(OSComponentTestCase):
85     """
86     Test basic nova keypair functionality
87     """
88
89     def setUp(self):
90         """
91         Instantiates the CreateImage object that is responsible for downloading
92         and creating an OS image file within OpenStack
93         """
94         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
95         self.priv_key_file_path = 'tmp/' + guid
96         self.pub_key_file_path = self.priv_key_file_path + '.pub'
97
98         self.nova = nova_utils.nova_client(self.os_creds)
99         self.keys = nova_utils.create_keys()
100         self.public_key = nova_utils.public_key_openssh(self.keys)
101         self.keypair_name = guid
102         self.keypair = None
103
104     def tearDown(self):
105         """
106         Cleans the image and downloaded image file
107         """
108         if self.keypair:
109             try:
110                 nova_utils.delete_keypair(self.nova, self.keypair)
111             except:
112                 pass
113
114         try:
115             os.chmod(self.priv_key_file_path, 0o777)
116             os.remove(self.priv_key_file_path)
117         except:
118             pass
119
120         try:
121             os.chmod(self.pub_key_file_path, 0o777)
122             os.remove(self.pub_key_file_path)
123         except:
124             pass
125
126     def test_create_keypair(self):
127         """
128         Tests the creation of an OpenStack keypair that does not exist.
129         """
130         self.keypair = nova_utils.upload_keypair(self.nova, self.keypair_name,
131                                                  self.public_key)
132         result = nova_utils.keypair_exists(self.nova, self.keypair)
133         self.assertEqual(self.keypair, result)
134         keypair = nova_utils.get_keypair_by_name(self.nova, self.keypair_name)
135         self.assertEqual(self.keypair, keypair)
136
137     def test_create_delete_keypair(self):
138         """
139         Tests the creation of an OpenStack keypair that does not exist.
140         """
141         self.keypair = nova_utils.upload_keypair(self.nova, self.keypair_name,
142                                                  self.public_key)
143         result = nova_utils.keypair_exists(self.nova, self.keypair)
144         self.assertEqual(self.keypair, result)
145         nova_utils.delete_keypair(self.nova, self.keypair)
146         result2 = nova_utils.keypair_exists(self.nova, self.keypair)
147         self.assertIsNone(result2)
148
149     def test_create_key_from_file(self):
150         """
151         Tests that the generated RSA keys are properly saved to files
152         :return:
153         """
154         file_utils.save_keys_to_files(self.keys, self.pub_key_file_path,
155                                       self.priv_key_file_path)
156         self.keypair = nova_utils.upload_keypair_file(self.nova,
157                                                       self.keypair_name,
158                                                       self.pub_key_file_path)
159         pub_key_file = open(os.path.expanduser(self.pub_key_file_path))
160         pub_key = pub_key_file.read()
161         pub_key_file.close()
162         self.assertEqual(self.keypair.public_key, pub_key)
163
164
165 class NovaUtilsFlavorTests(OSComponentTestCase):
166     """
167     Test basic nova flavor functionality
168     """
169
170     def setUp(self):
171         """
172         Instantiates the CreateImage object that is responsible for downloading
173         and creating an OS image file within OpenStack
174         """
175         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
176         self.flavor_settings = FlavorConfig(
177             name=guid + '-name', flavor_id=guid + '-id', ram=1, disk=1,
178             vcpus=1, ephemeral=1, swap=2, rxtx_factor=3.0, is_public=False)
179         self.nova = nova_utils.nova_client(self.os_creds)
180         self.flavor = None
181
182     def tearDown(self):
183         """
184         Cleans the image and downloaded image file
185         """
186         if self.flavor:
187             try:
188                 nova_utils.delete_flavor(self.nova, self.flavor)
189             except:
190                 pass
191
192     def test_create_flavor(self):
193         """
194         Tests the creation of an OpenStack keypair that does not exist.
195         """
196         self.flavor = nova_utils.create_flavor(self.nova, self.flavor_settings)
197         self.validate_flavor()
198
199     def test_create_delete_flavor(self):
200         """
201         Tests the creation of an OpenStack keypair that does not exist.
202         """
203         self.flavor = nova_utils.create_flavor(self.nova, self.flavor_settings)
204         self.validate_flavor()
205         nova_utils.delete_flavor(self.nova, self.flavor)
206         flavor = nova_utils.get_flavor_by_name(self.nova,
207                                                self.flavor_settings.name)
208         self.assertIsNone(flavor)
209
210     def validate_flavor(self):
211         """
212         Validates the flavor_settings against the OpenStack flavor object
213         """
214         self.assertIsNotNone(self.flavor)
215         self.assertEqual(self.flavor_settings.name, self.flavor.name)
216         self.assertEqual(self.flavor_settings.flavor_id, self.flavor.id)
217         self.assertEqual(self.flavor_settings.ram, self.flavor.ram)
218         self.assertEqual(self.flavor_settings.disk, self.flavor.disk)
219         self.assertEqual(self.flavor_settings.vcpus, self.flavor.vcpus)
220         self.assertEqual(self.flavor_settings.ephemeral, self.flavor.ephemeral)
221
222         if self.flavor_settings.swap == 0:
223             self.assertEqual('', self.flavor.swap)
224         else:
225             self.assertEqual(self.flavor_settings.swap, self.flavor.swap)
226
227         self.assertEqual(self.flavor_settings.rxtx_factor,
228                          self.flavor.rxtx_factor)
229         self.assertEqual(self.flavor_settings.is_public, self.flavor.is_public)
230
231
232 class NovaUtilsInstanceTests(OSComponentTestCase):
233     """
234     Tests the creation of VM instances via nova_utils.py
235     """
236
237     def setUp(self):
238         """
239         Setup objects required by VM instances
240         :return:
241         """
242
243         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
244
245         self.nova = nova_utils.nova_client(self.os_creds)
246         self.keystone = keystone_utils.keystone_client(self.os_creds)
247         self.neutron = neutron_utils.neutron_client(self.os_creds)
248         self.glance = glance_utils.glance_client(self.os_creds)
249
250         self.image_creator = None
251         self.network_creator = None
252         self.flavor_creator = None
253         self.port = None
254         self.vm_inst = None
255
256         try:
257             image_settings = openstack_tests.cirros_image_settings(
258                 name=guid + '-image', image_metadata=self.image_metadata)
259             self.image_creator = OpenStackImage(
260                 self.os_creds, image_settings=image_settings)
261             self.image_creator.create()
262
263             network_settings = openstack_tests.get_priv_net_config(
264                 guid + '-net', guid + '-subnet').network_settings
265             self.network_creator = OpenStackNetwork(
266                 self.os_creds, network_settings)
267             self.network_creator.create()
268
269             self.flavor_creator = OpenStackFlavor(
270                 self.os_creds,
271                 FlavorConfig(
272                     name=guid + '-flavor-name', ram=256, disk=10, vcpus=1))
273             self.flavor_creator.create()
274
275             port_settings = PortConfig(
276                 name=guid + '-port', network_name=network_settings.name)
277             self.port = neutron_utils.create_port(
278                 self.neutron, self.os_creds, port_settings)
279
280             self.instance_settings = VmInstanceConfig(
281                 name=guid + '-vm_inst',
282                 flavor=self.flavor_creator.flavor_settings.name,
283                 port_settings=[port_settings])
284         except:
285             self.tearDown()
286             raise
287
288     def tearDown(self):
289         """
290         Cleanup deployed resources
291         :return:
292         """
293         if self.vm_inst:
294             try:
295                 nova_utils.delete_vm_instance(self.nova, self.vm_inst)
296             except:
297                 pass
298         if self.port:
299             try:
300                 neutron_utils.delete_port(self.neutron, self.port)
301             except:
302                 pass
303         if self.flavor_creator:
304             try:
305                 self.flavor_creator.clean()
306             except:
307                 pass
308         if self.network_creator:
309             try:
310                 self.network_creator.clean()
311             except:
312                 pass
313         if self.image_creator:
314             try:
315                 self.image_creator.clean()
316             except:
317                 pass
318
319     def test_create_instance(self):
320         """
321         Tests the nova_utils.create_server() method
322         :return:
323         """
324
325         self.vm_inst = nova_utils.create_server(
326             self.nova, self.keystone, self.neutron, self.glance,
327             self.instance_settings, self.image_creator.image_settings,
328             self.os_creds.project_name)
329
330         self.assertIsNotNone(self.vm_inst)
331
332         # Wait until instance is ACTIVE
333         iters = 0
334         active = False
335         while iters < 60:
336             if create_instance.STATUS_ACTIVE == nova_utils.get_server_status(
337                     self.nova, self.vm_inst):
338                 active = True
339                 break
340
341             time.sleep(3)
342             iters += 1
343
344         self.assertTrue(active)
345         vm_inst = nova_utils.get_latest_server_object(
346             self.nova, self.neutron, self.keystone, self.vm_inst,
347             self.os_creds.project_name)
348
349         self.assertEqual(self.vm_inst.name, vm_inst.name)
350         self.assertEqual(self.vm_inst.id, vm_inst.id)
351
352
353 class NovaUtilsInstanceVolumeTests(OSComponentTestCase):
354     """
355     Tests the creation of VM instances via nova_utils.py
356     """
357
358     def setUp(self):
359         """
360         Setup objects required by VM instances
361         :return:
362         """
363
364         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
365
366         self.nova = nova_utils.nova_client(self.os_creds)
367         self.cinder = cinder_utils.cinder_client(self.os_creds)
368
369         self.image_creator = None
370         self.network_creator = None
371         self.flavor_creator = None
372         self.volume_creator = None
373         self.instance_creator = None
374
375         try:
376             image_settings = openstack_tests.cirros_image_settings(
377                 name=guid + '-image', image_metadata=self.image_metadata)
378             self.image_creator = OpenStackImage(
379                 self.os_creds, image_settings=image_settings)
380             self.image_creator.create()
381
382             network_settings = openstack_tests.get_priv_net_config(
383                 guid + '-net', guid + '-subnet').network_settings
384             self.network_creator = OpenStackNetwork(
385                 self.os_creds, network_settings)
386             self.network_creator.create()
387
388             self.flavor_creator = OpenStackFlavor(
389                 self.os_creds,
390                 FlavorConfig(
391                     name=guid + '-flavor-name', ram=256, disk=10, vcpus=1))
392             self.flavor_creator.create()
393
394             # Create Volume
395             volume_settings = VolumeConfig(
396                 name=self.__class__.__name__ + '-' + str(guid))
397             self.volume_creator = OpenStackVolume(
398                 self.os_creds, volume_settings)
399             self.volume_creator.create(block=True)
400
401             port_settings = PortConfig(
402                 name=guid + '-port', network_name=network_settings.name)
403             instance_settings = VmInstanceConfig(
404                 name=guid + '-vm_inst',
405                 flavor=self.flavor_creator.flavor_settings.name,
406                 port_settings=[port_settings])
407             self.instance_creator = OpenStackVmInstance(
408                 self.os_creds, instance_settings, image_settings)
409             self.instance_creator.create(block=True)
410         except:
411             self.tearDown()
412             raise
413
414     def tearDown(self):
415         """
416         Cleanup deployed resources
417         :return:
418         """
419         if self.instance_creator:
420             try:
421                 self.instance_creator.clean()
422             except:
423                 pass
424         if self.volume_creator:
425             try:
426                 self.volume_creator.clean()
427             except:
428                 pass
429         if self.flavor_creator:
430             try:
431                 self.flavor_creator.clean()
432             except:
433                 pass
434         if self.network_creator:
435             try:
436                 self.network_creator.clean()
437             except:
438                 pass
439         if self.image_creator:
440             try:
441                 self.image_creator.clean()
442             except:
443                 pass
444
445     def test_add_remove_volume(self):
446         """
447         Tests the nova_utils.attach_volume() and detach_volume functions with
448         a timeout value
449         :return:
450         """
451
452         self.assertIsNotNone(self.volume_creator.get_volume())
453         self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
454
455         # Attach volume to VM
456         neutron = neutron_utils.neutron_client(self.os_creds)
457         keystone = keystone_utils.keystone_client(self.os_creds)
458         self.assertIsNotNone(nova_utils.attach_volume(
459             self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
460             self.volume_creator.get_volume(), self.os_creds.project_name))
461
462         vol_attach = None
463         vol_detach = None
464         attached = False
465         start_time = time.time()
466         while time.time() < start_time + 120:
467             vol_attach = cinder_utils.get_volume_by_id(
468                 self.cinder, self.volume_creator.get_volume().id)
469
470             if len(vol_attach.attachments) > 0:
471                 attached = True
472                 break
473
474             time.sleep(3)
475
476         self.assertTrue(attached)
477         self.assertIsNotNone(vol_attach)
478
479         keystone = keystone_utils.keystone_client(self.os_creds)
480         vm_attach = nova_utils.get_server_object_by_id(
481             self.nova, neutron, keystone,
482             self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
483
484         # Validate Attachment
485         self.assertIsNotNone(vol_attach)
486         self.assertEqual(self.volume_creator.get_volume().id, vol_attach.id)
487         self.assertEqual(1, len(vol_attach.attachments))
488         self.assertEqual(vm_attach.volume_ids[0]['id'],
489                          vol_attach.attachments[0]['volume_id'])
490
491         # Detach volume to VM
492         self.assertIsNotNone(nova_utils.detach_volume(
493             self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
494             self.volume_creator.get_volume(), self.os_creds.project_name))
495
496         start_time = time.time()
497         while time.time() < start_time + 120:
498             vol_detach = cinder_utils.get_volume_by_id(
499                 self.cinder, self.volume_creator.get_volume().id)
500             if len(vol_detach.attachments) == 0:
501                 attached = False
502                 break
503
504             time.sleep(3)
505
506         self.assertFalse(attached)
507         self.assertIsNotNone(vol_detach)
508
509         vm_detach = nova_utils.get_server_object_by_id(
510             self.nova, neutron, keystone,
511             self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
512
513         # Validate Detachment
514         self.assertIsNotNone(vol_detach)
515         self.assertEqual(self.volume_creator.get_volume().id, vol_detach.id)
516
517         self.assertEqual(0, len(vol_detach.attachments))
518         self.assertEqual(0, len(vm_detach.volume_ids))
519
520     def test_attach_volume_nowait(self):
521         """
522         Tests the nova_utils.attach_volume() with a timeout value that is too
523         small to have the volume attachment data to be included on the VmInst
524         object that was supposed to be returned
525         """
526
527         self.assertIsNotNone(self.volume_creator.get_volume())
528         self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
529
530         # Attach volume to VM
531         neutron = neutron_utils.neutron_client(self.os_creds)
532         keystone = keystone_utils.keystone_client(self.os_creds)
533         with self.assertRaises(NovaException):
534             nova_utils.attach_volume(
535                 self.nova, neutron, keystone,
536                 self.instance_creator.get_vm_inst(),
537                 self.volume_creator.get_volume(), self.os_creds.project_name,
538                 0)
539
540     def test_detach_volume_nowait(self):
541         """
542         Tests the nova_utils.detach_volume() with a timeout value that is too
543         small to have the volume attachment data to be included on the VmInst
544         object that was supposed to be returned
545         """
546
547         self.assertIsNotNone(self.volume_creator.get_volume())
548         self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
549
550         # Attach volume to VM
551         neutron = neutron_utils.neutron_client(self.os_creds)
552         keystone = keystone_utils.keystone_client(self.os_creds)
553         nova_utils.attach_volume(
554             self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
555             self.volume_creator.get_volume(), self.os_creds.project_name)
556
557         # Check VmInst for attachment
558         keystone = keystone_utils.keystone_client(self.os_creds)
559         latest_vm = nova_utils.get_server_object_by_id(
560             self.nova, neutron, keystone,
561             self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
562         self.assertEqual(1, len(latest_vm.volume_ids))
563
564         # Check Volume for attachment
565         vol_attach = None
566         attached = False
567         start_time = time.time()
568         while time.time() < start_time + 120:
569             vol_attach = cinder_utils.get_volume_by_id(
570                 self.cinder, self.volume_creator.get_volume().id)
571
572             if len(vol_attach.attachments) > 0:
573                 attached = True
574                 break
575
576             time.sleep(3)
577
578         self.assertTrue(attached)
579         self.assertIsNotNone(vol_attach)
580
581         # Detach volume
582         with self.assertRaises(NovaException):
583             nova_utils.detach_volume(
584                 self.nova, neutron, keystone,
585                 self.instance_creator.get_vm_inst(),
586                 self.volume_creator.get_volume(), self.os_creds.project_name,
587                 0)