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                     metadata=self.flavor_metadata))
406             self.flavor_creator.create()
407
408             # Create Volume
409             volume_settings = VolumeConfig(
410                 name=self.__class__.__name__ + '-' + str(guid))
411             self.volume_creator = OpenStackVolume(
412                 self.os_creds, volume_settings)
413             self.volume_creator.create(block=True)
414
415             port_settings = PortConfig(
416                 name=guid + '-port', network_name=network_settings.name)
417             instance_settings = VmInstanceConfig(
418                 name=guid + '-vm_inst',
419                 flavor=self.flavor_creator.flavor_settings.name,
420                 port_settings=[port_settings])
421             self.instance_creator = OpenStackVmInstance(
422                 self.os_creds, instance_settings, image_settings)
423             self.instance_creator.create(block=True)
424         except:
425             self.tearDown()
426             raise
427
428     def tearDown(self):
429         """
430         Cleanup deployed resources
431         :return:
432         """
433         if self.instance_creator:
434             try:
435                 self.instance_creator.clean()
436             except:
437                 pass
438         if self.volume_creator:
439             try:
440                 self.volume_creator.clean()
441             except:
442                 pass
443         if self.flavor_creator:
444             try:
445                 self.flavor_creator.clean()
446             except:
447                 pass
448         if self.network_creator:
449             try:
450                 self.network_creator.clean()
451             except:
452                 pass
453         if self.image_creator:
454             try:
455                 self.image_creator.clean()
456             except:
457                 pass
458
459         super(self.__class__, self).__clean__()
460
461     def test_add_remove_volume(self):
462         """
463         Tests the nova_utils.attach_volume() and detach_volume functions with
464         a timeout value
465         :return:
466         """
467
468         self.assertIsNotNone(self.volume_creator.get_volume())
469         self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
470
471         # Attach volume to VM
472         neutron = neutron_utils.neutron_client(
473             self.os_creds, self.os_session)
474         keystone = keystone_utils.keystone_client(
475             self.os_creds, self.os_session)
476         self.assertIsNotNone(nova_utils.attach_volume(
477             self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
478             self.volume_creator.get_volume(), self.os_creds.project_name))
479
480         vol_attach = None
481         vol_detach = None
482         attached = False
483         start_time = time.time()
484         while time.time() < start_time + 120:
485             vol_attach = cinder_utils.get_volume_by_id(
486                 self.cinder, self.volume_creator.get_volume().id)
487
488             if len(vol_attach.attachments) > 0:
489                 attached = True
490                 break
491
492             time.sleep(3)
493
494         self.assertTrue(attached)
495         self.assertIsNotNone(vol_attach)
496
497         keystone = keystone_utils.keystone_client(
498             self.os_creds, self.os_session)
499         vm_attach = nova_utils.get_server_object_by_id(
500             self.nova, neutron, keystone,
501             self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
502
503         # Validate Attachment
504         self.assertIsNotNone(vol_attach)
505         self.assertEqual(self.volume_creator.get_volume().id, vol_attach.id)
506         self.assertEqual(1, len(vol_attach.attachments))
507         self.assertEqual(vm_attach.volume_ids[0]['id'],
508                          vol_attach.attachments[0]['volume_id'])
509
510         # Detach volume to VM
511         self.assertIsNotNone(nova_utils.detach_volume(
512             self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
513             self.volume_creator.get_volume(), self.os_creds.project_name))
514
515         start_time = time.time()
516         while time.time() < start_time + 120:
517             vol_detach = cinder_utils.get_volume_by_id(
518                 self.cinder, self.volume_creator.get_volume().id)
519             if len(vol_detach.attachments) == 0:
520                 attached = False
521                 break
522
523             time.sleep(3)
524
525         self.assertFalse(attached)
526         self.assertIsNotNone(vol_detach)
527
528         vm_detach = nova_utils.get_server_object_by_id(
529             self.nova, neutron, keystone,
530             self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
531
532         # Validate Detachment
533         self.assertIsNotNone(vol_detach)
534         self.assertEqual(self.volume_creator.get_volume().id, vol_detach.id)
535
536         self.assertEqual(0, len(vol_detach.attachments))
537         self.assertEqual(0, len(vm_detach.volume_ids))
538
539     def test_attach_volume_nowait(self):
540         """
541         Tests the nova_utils.attach_volume() with a timeout value that is too
542         small to have the volume attachment data to be included on the VmInst
543         object that was supposed to be returned
544         """
545
546         self.assertIsNotNone(self.volume_creator.get_volume())
547         self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
548
549         # Attach volume to VM
550         neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
551         keystone = keystone_utils.keystone_client(
552             self.os_creds, self.os_session)
553         with self.assertRaises(NovaException):
554             nova_utils.attach_volume(
555                 self.nova, neutron, keystone,
556                 self.instance_creator.get_vm_inst(),
557                 self.volume_creator.get_volume(), self.os_creds.project_name,
558                 0)
559
560     def test_detach_volume_nowait(self):
561         """
562         Tests the nova_utils.detach_volume() with a timeout value that is too
563         small to have the volume attachment data to be included on the VmInst
564         object that was supposed to be returned
565         """
566
567         self.assertIsNotNone(self.volume_creator.get_volume())
568         self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
569
570         # Attach volume to VM
571         neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
572         keystone = keystone_utils.keystone_client(
573             self.os_creds, self.os_session)
574         nova_utils.attach_volume(
575             self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
576             self.volume_creator.get_volume(), self.os_creds.project_name)
577
578         # Check VmInst for attachment
579         keystone = keystone_utils.keystone_client(
580             self.os_creds, self.os_session)
581         latest_vm = nova_utils.get_server_object_by_id(
582             self.nova, neutron, keystone,
583             self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
584         self.assertEqual(1, len(latest_vm.volume_ids))
585
586         # Check Volume for attachment
587         vol_attach = None
588         attached = False
589         start_time = time.time()
590         while time.time() < start_time + 120:
591             vol_attach = cinder_utils.get_volume_by_id(
592                 self.cinder, self.volume_creator.get_volume().id)
593
594             if len(vol_attach.attachments) > 0:
595                 attached = True
596                 break
597
598             time.sleep(3)
599
600         self.assertTrue(attached)
601         self.assertIsNotNone(vol_attach)
602
603         # Detach volume
604         with self.assertRaises(NovaException):
605             nova_utils.detach_volume(
606                 self.nova, neutron, keystone,
607                 self.instance_creator.get_vm_inst(),
608                 self.volume_creator.get_volume(), self.os_creds.project_name,
609                 0)