Add flavor metadata for volume related tests
[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         while iters < 60:
346             if create_instance.STATUS_ACTIVE == nova_utils.get_server_status(
347                     self.nova, self.vm_inst):
348                 active = True
349                 break
350
351             time.sleep(3)
352             iters += 1
353
354         self.assertTrue(active)
355         vm_inst = nova_utils.get_latest_server_object(
356             self.nova, self.neutron, self.keystone, self.vm_inst,
357             self.os_creds.project_name)
358
359         self.assertEqual(self.vm_inst.name, vm_inst.name)
360         self.assertEqual(self.vm_inst.id, vm_inst.id)
361
362
363 class NovaUtilsInstanceVolumeTests(OSComponentTestCase):
364     """
365     Tests the creation of VM instances via nova_utils.py
366     """
367
368     def setUp(self):
369         """
370         Setup objects required by VM instances
371         :return:
372         """
373
374         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
375
376         self.nova = nova_utils.nova_client(self.os_creds, self.os_session)
377         self.cinder = cinder_utils.cinder_client(
378             self.os_creds, self.os_session)
379
380         self.image_creator = None
381         self.network_creator = None
382         self.flavor_creator = None
383         self.volume_creator = None
384         self.instance_creator = None
385
386         try:
387             image_settings = openstack_tests.cirros_image_settings(
388                 name=guid + '-image', image_metadata=self.image_metadata)
389             self.image_creator = OpenStackImage(
390                 self.os_creds, image_settings=image_settings)
391             self.image_creator.create()
392
393             network_settings = openstack_tests.get_priv_net_config(
394                 guid + '-net', guid + '-subnet').network_settings
395             self.network_creator = OpenStackNetwork(
396                 self.os_creds, network_settings)
397             self.network_creator.create()
398
399             self.flavor_creator = OpenStackFlavor(
400                 self.os_creds,
401                 FlavorConfig(
402                     name=guid + '-flavor-name', ram=256, disk=10, vcpus=1,
403                     metadata=self.flavor_metadata))
404             self.flavor_creator.create()
405
406             # Create Volume
407             volume_settings = VolumeConfig(
408                 name=self.__class__.__name__ + '-' + str(guid))
409             self.volume_creator = OpenStackVolume(
410                 self.os_creds, volume_settings)
411             self.volume_creator.create(block=True)
412
413             port_settings = PortConfig(
414                 name=guid + '-port', network_name=network_settings.name)
415             instance_settings = VmInstanceConfig(
416                 name=guid + '-vm_inst',
417                 flavor=self.flavor_creator.flavor_settings.name,
418                 port_settings=[port_settings])
419             self.instance_creator = OpenStackVmInstance(
420                 self.os_creds, instance_settings, image_settings)
421             self.instance_creator.create(block=True)
422         except:
423             self.tearDown()
424             raise
425
426     def tearDown(self):
427         """
428         Cleanup deployed resources
429         :return:
430         """
431         if self.instance_creator:
432             try:
433                 self.instance_creator.clean()
434             except:
435                 pass
436         if self.volume_creator:
437             try:
438                 self.volume_creator.clean()
439             except:
440                 pass
441         if self.flavor_creator:
442             try:
443                 self.flavor_creator.clean()
444             except:
445                 pass
446         if self.network_creator:
447             try:
448                 self.network_creator.clean()
449             except:
450                 pass
451         if self.image_creator:
452             try:
453                 self.image_creator.clean()
454             except:
455                 pass
456
457         super(self.__class__, self).__clean__()
458
459     def test_add_remove_volume(self):
460         """
461         Tests the nova_utils.attach_volume() and detach_volume functions with
462         a timeout value
463         :return:
464         """
465
466         self.assertIsNotNone(self.volume_creator.get_volume())
467         self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
468
469         # Attach volume to VM
470         neutron = neutron_utils.neutron_client(
471             self.os_creds, self.os_session)
472         keystone = keystone_utils.keystone_client(
473             self.os_creds, self.os_session)
474         self.assertIsNotNone(nova_utils.attach_volume(
475             self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
476             self.volume_creator.get_volume(), self.os_creds.project_name))
477
478         vol_attach = None
479         vol_detach = None
480         attached = False
481         start_time = time.time()
482         while time.time() < start_time + 120:
483             vol_attach = cinder_utils.get_volume_by_id(
484                 self.cinder, self.volume_creator.get_volume().id)
485
486             if len(vol_attach.attachments) > 0:
487                 attached = True
488                 break
489
490             time.sleep(3)
491
492         self.assertTrue(attached)
493         self.assertIsNotNone(vol_attach)
494
495         keystone = keystone_utils.keystone_client(
496             self.os_creds, self.os_session)
497         vm_attach = nova_utils.get_server_object_by_id(
498             self.nova, neutron, keystone,
499             self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
500
501         # Validate Attachment
502         self.assertIsNotNone(vol_attach)
503         self.assertEqual(self.volume_creator.get_volume().id, vol_attach.id)
504         self.assertEqual(1, len(vol_attach.attachments))
505         self.assertEqual(vm_attach.volume_ids[0]['id'],
506                          vol_attach.attachments[0]['volume_id'])
507
508         # Detach volume to VM
509         self.assertIsNotNone(nova_utils.detach_volume(
510             self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
511             self.volume_creator.get_volume(), self.os_creds.project_name))
512
513         start_time = time.time()
514         while time.time() < start_time + 120:
515             vol_detach = cinder_utils.get_volume_by_id(
516                 self.cinder, self.volume_creator.get_volume().id)
517             if len(vol_detach.attachments) == 0:
518                 attached = False
519                 break
520
521             time.sleep(3)
522
523         self.assertFalse(attached)
524         self.assertIsNotNone(vol_detach)
525
526         vm_detach = nova_utils.get_server_object_by_id(
527             self.nova, neutron, keystone,
528             self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
529
530         # Validate Detachment
531         self.assertIsNotNone(vol_detach)
532         self.assertEqual(self.volume_creator.get_volume().id, vol_detach.id)
533
534         self.assertEqual(0, len(vol_detach.attachments))
535         self.assertEqual(0, len(vm_detach.volume_ids))
536
537     def test_attach_volume_nowait(self):
538         """
539         Tests the nova_utils.attach_volume() with a timeout value that is too
540         small to have the volume attachment data to be included on the VmInst
541         object that was supposed to be returned
542         """
543
544         self.assertIsNotNone(self.volume_creator.get_volume())
545         self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
546
547         # Attach volume to VM
548         neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
549         keystone = keystone_utils.keystone_client(
550             self.os_creds, self.os_session)
551         with self.assertRaises(NovaException):
552             nova_utils.attach_volume(
553                 self.nova, neutron, keystone,
554                 self.instance_creator.get_vm_inst(),
555                 self.volume_creator.get_volume(), self.os_creds.project_name,
556                 0)
557
558     def test_detach_volume_nowait(self):
559         """
560         Tests the nova_utils.detach_volume() with a timeout value that is too
561         small to have the volume attachment data to be included on the VmInst
562         object that was supposed to be returned
563         """
564
565         self.assertIsNotNone(self.volume_creator.get_volume())
566         self.assertEqual(0, len(self.volume_creator.get_volume().attachments))
567
568         # Attach volume to VM
569         neutron = neutron_utils.neutron_client(self.os_creds, self.os_session)
570         keystone = keystone_utils.keystone_client(
571             self.os_creds, self.os_session)
572         nova_utils.attach_volume(
573             self.nova, neutron, keystone, self.instance_creator.get_vm_inst(),
574             self.volume_creator.get_volume(), self.os_creds.project_name)
575
576         # Check VmInst for attachment
577         keystone = keystone_utils.keystone_client(
578             self.os_creds, self.os_session)
579         latest_vm = nova_utils.get_server_object_by_id(
580             self.nova, neutron, keystone,
581             self.instance_creator.get_vm_inst().id, self.os_creds.project_name)
582         self.assertEqual(1, len(latest_vm.volume_ids))
583
584         # Check Volume for attachment
585         vol_attach = None
586         attached = False
587         start_time = time.time()
588         while time.time() < start_time + 120:
589             vol_attach = cinder_utils.get_volume_by_id(
590                 self.cinder, self.volume_creator.get_volume().id)
591
592             if len(vol_attach.attachments) > 0:
593                 attached = True
594                 break
595
596             time.sleep(3)
597
598         self.assertTrue(attached)
599         self.assertIsNotNone(vol_attach)
600
601         # Detach volume
602         with self.assertRaises(NovaException):
603             nova_utils.detach_volume(
604                 self.nova, neutron, keystone,
605                 self.instance_creator.get_vm_inst(),
606                 self.volume_creator.get_volume(), self.os_creds.project_name,
607                 0)