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