Adding assertion message to test validation.
[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, self.os_session)
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, self.os_session)
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, self.os_session)
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         super(self.__class__, self).__clean__()
127
128     def test_create_keypair(self):
129         """
130         Tests the creation of an OpenStack keypair that does not exist.
131         """
132         self.keypair = nova_utils.upload_keypair(self.nova, self.keypair_name,
133                                                  self.public_key)
134         result = nova_utils.keypair_exists(self.nova, self.keypair)
135         self.assertEqual(self.keypair, result)
136         keypair = nova_utils.get_keypair_by_name(self.nova, self.keypair_name)
137         self.assertEqual(self.keypair, keypair)
138
139     def test_create_delete_keypair(self):
140         """
141         Tests the creation of an OpenStack keypair that does not exist.
142         """
143         self.keypair = nova_utils.upload_keypair(self.nova, self.keypair_name,
144                                                  self.public_key)
145         result = nova_utils.keypair_exists(self.nova, self.keypair)
146         self.assertEqual(self.keypair, result)
147         nova_utils.delete_keypair(self.nova, self.keypair)
148         result2 = nova_utils.keypair_exists(self.nova, self.keypair)
149         self.assertIsNone(result2)
150
151     def test_create_key_from_file(self):
152         """
153         Tests that the generated RSA keys are properly saved to files
154         :return:
155         """
156         file_utils.save_keys_to_files(self.keys, self.pub_key_file_path,
157                                       self.priv_key_file_path)
158         self.keypair = nova_utils.upload_keypair_file(self.nova,
159                                                       self.keypair_name,
160                                                       self.pub_key_file_path)
161         pub_key_file = open(os.path.expanduser(self.pub_key_file_path))
162         pub_key = pub_key_file.read()
163         pub_key_file.close()
164         self.assertEqual(self.keypair.public_key, pub_key)
165
166
167 class NovaUtilsFlavorTests(OSComponentTestCase):
168     """
169     Test basic nova flavor functionality
170     """
171
172     def setUp(self):
173         """
174         Instantiates the CreateImage object that is responsible for downloading
175         and creating an OS image file within OpenStack
176         """
177         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
178         self.flavor_settings = FlavorConfig(
179             name=guid + '-name', flavor_id=guid + '-id', ram=1, disk=1,
180             vcpus=1, ephemeral=1, swap=2, rxtx_factor=3.0, is_public=False)
181         self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
182         self.flavor = None
183
184     def tearDown(self):
185         """
186         Cleans the image and downloaded image file
187         """
188         if self.flavor:
189             try:
190                 nova_utils.delete_flavor(self.nova, self.flavor)
191             except:
192                 pass
193
194         super(self.__class__, self).__clean__()
195
196     def test_create_flavor(self):
197         """
198         Tests the creation of an OpenStack keypair that does not exist.
199         """
200         self.flavor = nova_utils.create_flavor(self.nova, self.flavor_settings)
201         self.validate_flavor()
202
203     def test_create_delete_flavor(self):
204         """
205         Tests the creation of an OpenStack keypair that does not exist.
206         """
207         self.flavor = nova_utils.create_flavor(self.nova, self.flavor_settings)
208         self.validate_flavor()
209         nova_utils.delete_flavor(self.nova, self.flavor)
210         flavor = nova_utils.get_flavor_by_name(self.nova,
211                                                self.flavor_settings.name)
212         self.assertIsNone(flavor)
213
214     def validate_flavor(self):
215         """
216         Validates the flavor_settings against the OpenStack flavor object
217         """
218         self.assertIsNotNone(self.flavor)
219         self.assertEqual(self.flavor_settings.name, self.flavor.name)
220         self.assertEqual(self.flavor_settings.flavor_id, self.flavor.id)
221         self.assertEqual(self.flavor_settings.ram, self.flavor.ram)
222         self.assertEqual(self.flavor_settings.disk, self.flavor.disk)
223         self.assertEqual(self.flavor_settings.vcpus, self.flavor.vcpus)
224         self.assertEqual(self.flavor_settings.ephemeral, self.flavor.ephemeral)
225
226         if self.flavor_settings.swap == 0:
227             self.assertEqual('', self.flavor.swap)
228         else:
229             self.assertEqual(self.flavor_settings.swap, self.flavor.swap)
230
231         self.assertEqual(self.flavor_settings.rxtx_factor,
232                          self.flavor.rxtx_factor)
233         self.assertEqual(self.flavor_settings.is_public, self.flavor.is_public)
234
235
236 class NovaUtilsInstanceTests(OSComponentTestCase):
237     """
238     Tests the creation of VM instances via nova_utils.py
239     """
240
241     def setUp(self):
242         """
243         Setup objects required by VM instances
244         :return:
245         """
246
247         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
248
249         self.nova = nova_utils.nova_client(
250             self.os_creds, self.os_session)
251         self.keystone = keystone_utils.keystone_client(
252             self.os_creds, self.os_session)
253         self.neutron = neutron_utils.neutron_client(
254             self.os_creds, self.os_session)
255         self.glance = glance_utils.glance_client(
256             self.os_creds, self.os_session)
257
258         self.image_creator = None
259         self.network_creator = None
260         self.flavor_creator = None
261         self.port = None
262         self.vm_inst = None
263
264         try:
265             image_settings = openstack_tests.cirros_image_settings(
266                 name=guid + '-image', image_metadata=self.image_metadata)
267             self.image_creator = OpenStackImage(
268                 self.os_creds, image_settings=image_settings)
269             self.image_creator.create()
270
271             network_settings = openstack_tests.get_priv_net_config(
272                 guid + '-net', guid + '-subnet').network_settings
273             self.network_creator = OpenStackNetwork(
274                 self.os_creds, network_settings)
275             self.network_creator.create()
276
277             self.flavor_creator = OpenStackFlavor(
278                 self.os_creds,
279                 FlavorConfig(
280                     name=guid + '-flavor-name', ram=256, disk=10, vcpus=1))
281             self.flavor_creator.create()
282
283             port_settings = PortConfig(
284                 name=guid + '-port', network_name=network_settings.name)
285             self.port = neutron_utils.create_port(
286                 self.neutron, self.os_creds, port_settings)
287
288             self.instance_settings = VmInstanceConfig(
289                 name=guid + '-vm_inst',
290                 flavor=self.flavor_creator.flavor_settings.name,
291                 port_settings=[port_settings])
292         except:
293             self.tearDown()
294             raise
295
296     def tearDown(self):
297         """
298         Cleanup deployed resources
299         :return:
300         """
301         if self.vm_inst:
302             try:
303                 nova_utils.delete_vm_instance(self.nova, self.vm_inst)
304             except:
305                 pass
306         if self.port:
307             try:
308                 neutron_utils.delete_port(self.neutron, self.port)
309             except:
310                 pass
311         if self.flavor_creator:
312             try:
313                 self.flavor_creator.clean()
314             except:
315                 pass
316         if self.network_creator:
317             try:
318                 self.network_creator.clean()
319             except:
320                 pass
321         if self.image_creator:
322             try:
323                 self.image_creator.clean()
324             except:
325                 pass
326
327         super(self.__class__, self).__clean__()
328
329     def test_create_instance(self):
330         """
331         Tests the nova_utils.create_server() method
332         :return:
333         """
334
335         self.vm_inst = nova_utils.create_server(
336             self.nova, self.keystone, self.neutron, self.glance,
337             self.instance_settings, self.image_creator.image_settings,
338             self.os_creds.project_name)
339
340         self.assertIsNotNone(self.vm_inst)
341
342         # Wait until instance is ACTIVE
343         iters = 0
344         active = False
345         status = None
346         while iters < 60:
347             status = nova_utils.get_server_status(self.nova, self.vm_inst)
348             if create_instance.STATUS_ACTIVE == status:
349                 active = True
350                 break
351
352             time.sleep(3)
353             iters += 1
354
355         self.assertTrue(active, msg='VM {} status {} is not {}'.format(
356             self.vm_inst.name, status, create_instance.STATUS_ACTIVE))
357         vm_inst = nova_utils.get_latest_server_object(
358             self.nova, self.neutron, self.keystone, self.vm_inst,
359             self.os_creds.project_name)
360
361         self.assertEqual(self.vm_inst.name, vm_inst.name)
362         self.assertEqual(self.vm_inst.id, vm_inst.id)
363
364
365 class NovaUtilsInstanceVolumeTests(OSComponentTestCase):
366     """
367     Tests the creation of VM instances via nova_utils.py
368     """
369
370     def setUp(self):
371         """
372         Setup objects required by VM instances
373         :return:
374         """
375
376         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
377
378         self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
379         self.cinder = cinder_utils.cinder_client(
380             self.os_creds, self.os_session)
381
382         self.image_creator = None
383         self.network_creator = None
384         self.flavor_creator = None
385         self.volume_creator = None
386         self.instance_creator = None
387
388         try:
389             image_settings = openstack_tests.cirros_image_settings(
390                 name=guid + '-image', image_metadata=self.image_metadata)
391             self.image_creator = OpenStackImage(
392                 self.os_creds, image_settings=image_settings)
393             self.image_creator.create()
394
395             network_settings = openstack_tests.get_priv_net_config(
396                 guid + '-net', guid + '-subnet').network_settings
397             self.network_creator = OpenStackNetwork(
398                 self.os_creds, network_settings)
399             self.network_creator.create()
400
401             self.flavor_creator = OpenStackFlavor(
402                 self.os_creds,
403                 FlavorConfig(
404                     name=guid + '-flavor-name', ram=256, disk=10, vcpus=1))
405             self.flavor_creator.create()
406
407             # Create Volume
408             volume_settings = VolumeConfig(
409                 name=self.__class__.__name__ + '-' + str(guid))
410             self.volume_creator = OpenStackVolume(
411                 self.os_creds, volume_settings)
412             self.volume_creator.create(block=True)
413
414             port_settings = PortConfig(
415                 name=guid + '-port', network_name=network_settings.name)
416             instance_settings = VmInstanceConfig(
417                 name=guid + '-vm_inst',
418                 flavor=self.flavor_creator.flavor_settings.name,
419                 port_settings=[port_settings])
420             self.instance_creator = OpenStackVmInstance(
421                 self.os_creds, instance_settings, image_settings)
422             self.instance_creator.create(block=True)
423         except:
424             self.tearDown()
425             raise
426
427     def tearDown(self):
428         """
429         Cleanup deployed resources
430         :return:
431         """
432         if self.instance_creator:
433             try:
434                 self.instance_creator.clean()
435             except:
436                 pass
437         if self.volume_creator:
438             try:
439                 self.volume_creator.clean()
440             except:
441                 pass
442         if self.flavor_creator:
443             try:
444                 self.flavor_creator.clean()
445             except:
446                 pass
447         if self.network_creator:
448             try:
449                 self.network_creator.clean()
450             except:
451                 pass
452         if self.image_creator:
453             try:
454                 self.image_creator.clean()
455             except:
456                 pass
457
458         super(self.__class__, self).__clean__()
459
460     def test_add_remove_volume(self):
461         """
462         Tests the nova_utils.attach_volume() and detach_volume functions with
463         a timeout value
464         :return:
465         """
466
467         self.assertIsNotNone(self.volume_creator.get_volume())
468         self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
469
470         # Attach volume to VM
471         neutron = neutron_utils.neutron_client(
472             self.os_creds, self.os_session)
473         keystone = keystone_utils.keystone_client(
474             self.os_creds, self.os_session)
475         self.assertIsNotNone(nova_utils.attach_volume(
476             self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
477             self.volume_creator.get_volume(), self.os_creds.project_name))
478
479         vol_attach = None
480         vol_detach = None
481         attached = False
482         start_time = time.time()
483         while time.time() < start_time + 120:
484             vol_attach = cinder_utils.get_volume_by_id(
485                 self.cinder, self.volume_creator.get_volume().id)
486
487             if len(vol_attach.attachments) > 0:
488                 attached = True
489                 break
490
491             time.sleep(3)
492
493         self.assertTrue(attached)
494         self.assertIsNotNone(vol_attach)
495
496         keystone = keystone_utils.keystone_client(
497             self.os_creds, self.os_session)
498         vm_attach = nova_utils.get_server_object_by_id(
499             self.nova, neutron, keystone,
500             self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
501
502         # Validate Attachment
503         self.assertIsNotNone(vol_attach)
504         self.assertEqual(self.volume_creator.get_volume().id, vol_attach.id)
505         self.assertEqual(1, len(vol_attach.attachments))
506         self.assertEqual(vm_attach.volume_ids[0]['id'],
507                          vol_attach.attachments[0]['volume_id'])
508
509         # Detach volume to VM
510         self.assertIsNotNone(nova_utils.detach_volume(
511             self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
512             self.volume_creator.get_volume(), self.os_creds.project_name))
513
514         start_time = time.time()
515         while time.time() < start_time + 120:
516             vol_detach = cinder_utils.get_volume_by_id(
517                 self.cinder, self.volume_creator.get_volume().id)
518             if len(vol_detach.attachments) == 0:
519                 attached = False
520                 break
521
522             time.sleep(3)
523
524         self.assertFalse(attached)
525         self.assertIsNotNone(vol_detach)
526
527         vm_detach = nova_utils.get_server_object_by_id(
528             self.nova, neutron, keystone,
529             self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
530
531         # Validate Detachment
532         self.assertIsNotNone(vol_detach)
533         self.assertEqual(self.volume_creator.get_volume().id, vol_detach.id)
534
535         self.assertEqual(0, len(vol_detach.attachments))
536         self.assertEqual(0, len(vm_detach.volume_ids))
537
538     def test_attach_volume_nowait(self):
539         """
540         Tests the nova_utils.attach_volume() with a timeout value that is too
541         small to have the volume attachment data to be included on the VmInst
542         object that was supposed to be returned
543         """
544
545         self.assertIsNotNone(self.volume_creator.get_volume())
546         self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
547
548         # Attach volume to VM
549         neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
550         keystone = keystone_utils.keystone_client(
551             self.os_creds, self.os_session)
552         with self.assertRaises(NovaException):
553             nova_utils.attach_volume(
554                 self.nova, neutron, keystone,
555                 self.instance_creator.get_vm_inst(),
556                 self.volume_creator.get_volume(), self.os_creds.project_name,
557                 0)
558
559     def test_detach_volume_nowait(self):
560         """
561         Tests the nova_utils.detach_volume() with a timeout value that is too
562         small to have the volume attachment data to be included on the VmInst
563         object that was supposed to be returned
564         """
565
566         self.assertIsNotNone(self.volume_creator.get_volume())
567         self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
568
569         # Attach volume to VM
570         neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
571         keystone = keystone_utils.keystone_client(
572             self.os_creds, self.os_session)
573         nova_utils.attach_volume(
574             self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
575             self.volume_creator.get_volume(), self.os_creds.project_name)
576
577         # Check VmInst for attachment
578         keystone = keystone_utils.keystone_client(
579             self.os_creds, self.os_session)
580         latest_vm = nova_utils.get_server_object_by_id(
581             self.nova, neutron, keystone,
582             self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
583         self.assertEqual(1, len(latest_vm.volume_ids))
584
585         # Check Volume for attachment
586         vol_attach = None
587         attached = False
588         start_time = time.time()
589         while time.time() < start_time + 120:
590             vol_attach = cinder_utils.get_volume_by_id(
591                 self.cinder, self.volume_creator.get_volume().id)
592
593             if len(vol_attach.attachments) > 0:
594                 attached = True
595                 break
596
597             time.sleep(3)
598
599         self.assertTrue(attached)
600         self.assertIsNotNone(vol_attach)
601
602         # Detach volume
603         with self.assertRaises(NovaException):
604             nova_utils.detach_volume(
605                 self.nova, neutron, keystone,
606                 self.instance_creator.get_vm_inst(),
607                 self.volume_creator.get_volume(), self.os_creds.project_name,
608                 0)