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