Merge "Patch to make flavor configuration for tests more flexible."
[snaps.git] / snaps / openstack / create_instance.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
18 from novaclient.exceptions import NotFound
19
20 from snaps.config.vm_inst import VmInstanceConfig, FloatingIpConfig
21 from snaps.openstack.openstack_creator import OpenStackComputeObject
22 from snaps.openstack.utils import (
23     glance_utils, cinder_utils, settings_utils, keystone_utils)
24 from snaps.openstack.utils import neutron_utils
25 from snaps.openstack.utils import nova_utils
26 from snaps.openstack.utils.nova_utils import RebootType
27 from snaps.provisioning import ansible_utils
28
29 __author__ = 'spisarski'
30
31 logger = logging.getLogger('create_instance')
32
33 POLL_INTERVAL = 3
34 STATUS_ACTIVE = 'ACTIVE'
35 STATUS_DELETED = 'DELETED'
36
37
38 class OpenStackVmInstance(OpenStackComputeObject):
39     """
40     Class responsible for managing a VM instance in OpenStack
41     """
42
43     def __init__(self, os_creds, instance_settings, image_settings,
44                  keypair_settings=None):
45         """
46         Constructor
47         :param os_creds: The connection credentials to the OpenStack API
48         :param instance_settings: Contains the settings for this VM
49         :param image_settings: The OpenStack image object settings
50         :param keypair_settings: The keypair metadata (Optional)
51         :raises Exception
52         """
53         super(self.__class__, self).__init__(os_creds)
54
55         self.__neutron = None
56
57         self.instance_settings = instance_settings
58         self.image_settings = image_settings
59         self.keypair_settings = keypair_settings
60
61         self.__floating_ip_dict = dict()
62
63         # Instantiated in self.create()
64         self.__ports = list()
65
66         # Note: this object does not change after the VM becomes active
67         self.__vm = None
68
69     def initialize(self):
70         """
71         Loads the existing VMInst, Port, FloatingIps
72         :return: VMInst domain object
73         """
74         super(self.__class__, self).initialize()
75
76         self.__neutron = neutron_utils.neutron_client(
77             self._os_creds, self._os_session)
78         self.__keystone = keystone_utils.keystone_client(
79             self._os_creds, self._os_session)
80         self.__cinder = cinder_utils.cinder_client(
81             self._os_creds, self._os_session)
82         self.__glance = glance_utils.glance_client(
83             self._os_creds, self._os_session)
84
85         self.__ports = self.__query_ports(self.instance_settings.port_settings)
86         self.__lookup_existing_vm_by_name()
87
88     def create(self, block=False):
89         """
90         Creates a VM instance and associated objects unless they already exist
91         :param block: Thread will block until instance has either become
92                       active, error, or timeout waiting.
93                       Additionally, when True, floating IPs will not be applied
94                       until VM is active.
95         :return: VMInst domain object
96         """
97         self.initialize()
98
99         if len(self.__ports) != len(self.instance_settings.port_settings):
100             self.__ports = self.__create_ports(
101                 self.instance_settings.port_settings)
102         if not self.__vm:
103             self.__create_vm(block)
104
105         return self.__vm
106
107     def __lookup_existing_vm_by_name(self):
108         """
109         Populates the member variables 'self.vm' and 'self.floating_ips' if a
110         VM with the same name already exists
111         within the project
112         """
113         server = nova_utils.get_server(
114             self._nova, self.__neutron, self.__keystone,
115             vm_inst_settings=self.instance_settings)
116         if server:
117             if server.name == self.instance_settings.name:
118                 self.__vm = server
119                 logger.info(
120                     'Found existing machine with name - %s',
121                     self.instance_settings.name)
122
123                 fips = neutron_utils.get_port_floating_ips(
124                     self.__neutron, self.__ports)
125                 for port_id, fip in fips:
126                     settings = self.instance_settings.floating_ip_settings
127                     for fip_setting in settings:
128                         if port_id == fip_setting.port_id:
129                             self.__floating_ip_dict[fip_setting.name] = fip
130                         else:
131                             port = neutron_utils.get_port_by_id(
132                                 self.__neutron, port_id)
133                             if port and port.name == fip_setting.port_name:
134                                 self.__floating_ip_dict[fip_setting.name] = fip
135
136     def __create_vm(self, block=False):
137         """
138         Responsible for creating the VM instance
139         :param block: Thread will block until instance has either become
140                       active, error, or timeout waiting. Floating IPs will be
141                       assigned after active when block=True
142         """
143         self.__vm = nova_utils.create_server(
144             self._nova, self.__keystone, self.__neutron, self.__glance,
145             self.instance_settings, self.image_settings,
146             self._os_creds.project_name, self.keypair_settings)
147         logger.info('Created instance with name - %s',
148                     self.instance_settings.name)
149
150         if block:
151             if not self.vm_active(block=True):
152                 raise VmInstanceCreationError(
153                     'Fatal error, VM did not become ACTIVE within the alloted '
154                     'time')
155
156         # Create server should do this but found it needed to occur here
157         for sec_grp_name in self.instance_settings.security_group_names:
158             if self.vm_active(block=True):
159                 nova_utils.add_security_group(self._nova, self.__vm,
160                                               sec_grp_name)
161             else:
162                 raise VmInstanceCreationError(
163                     'Cannot applying security group with name ' +
164                     sec_grp_name +
165                     ' to VM that did not activate with name - ' +
166                     self.instance_settings.name)
167
168         if self.instance_settings.volume_names:
169             for volume_name in self.instance_settings.volume_names:
170                 volume = cinder_utils.get_volume(
171                     self.__cinder, self.__keystone, volume_name=volume_name,
172                     project_name=self._os_creds.project_name)
173
174                 if volume and self.vm_active(block=True):
175                     vm = nova_utils.attach_volume(
176                         self._nova, self.__neutron, self.__keystone, self.__vm,
177                         volume, self._os_creds.project_name)
178
179                     if vm:
180                         self.__vm = vm
181                     else:
182                         logger.warn(
183                             'Volume [%s] attachment timeout ', volume.name)
184                 else:
185                     logger.warn('Unable to attach volume named [%s]',
186                                 volume_name)
187
188         self.__apply_floating_ips()
189
190     def __apply_floating_ips(self):
191         """
192         Applies the configured floating IPs to the necessary ports
193         """
194         port_dict = dict()
195         for key, port in self.__ports:
196             port_dict[key] = port
197
198         # Apply floating IPs
199         for floating_ip_setting in self.instance_settings.floating_ip_settings:
200             self.add_floating_ip(floating_ip_setting)
201
202     def add_floating_ip(self, floating_ip_setting):
203         """
204         Adds a floating IP to a running instance
205         :param floating_ip_setting - the floating IP configuration
206         :return: the floating ip object
207         """
208         port_dict = dict()
209         for key, port in self.__ports:
210             port_dict[key] = port
211
212         # Apply floating IP
213         port = port_dict.get(floating_ip_setting.port_name)
214
215         if not port:
216             raise VmInstanceCreationError(
217                 'Cannot find port object with name - ' +
218                 floating_ip_setting.port_name)
219
220         # Setup Floating IP only if there is a router with an external
221         # gateway
222         ext_gateway = self.__ext_gateway_by_router(
223             floating_ip_setting.router_name)
224         if ext_gateway and self.vm_active(block=True):
225             floating_ip = neutron_utils.create_floating_ip(
226                 self.__neutron, self.__keystone, ext_gateway, port.id)
227             self.__floating_ip_dict[floating_ip_setting.name] = floating_ip
228
229             logger.info(
230                 'Created floating IP %s via router - %s', floating_ip.ip,
231                 floating_ip_setting.router_name)
232
233             return floating_ip
234         else:
235             raise VmInstanceCreationError(
236                 'Unable to add floating IP to port, cannot locate router '
237                 'with an external gateway ')
238
239     def __ext_gateway_by_router(self, router_name):
240         """
241         Returns network name for the external network attached to a router or
242         None if not found
243         :param router_name: The name of the router to lookup
244         :return: the external network name or None
245         """
246         router = neutron_utils.get_router(
247             self.__neutron, self.__keystone, router_name=router_name,
248             project_name=self._os_creds.project_name)
249         if router and router.external_network_id:
250             network = neutron_utils.get_network_by_id(
251                 self.__neutron, router.external_network_id)
252             if network:
253                 return network.name
254         return None
255
256     def clean(self):
257         """
258         Destroys the VM instance
259         """
260
261         # Cleanup floating IPs
262         for name, floating_ip in self.__floating_ip_dict.items():
263             logger.info('Deleting Floating IP - ' + floating_ip.ip)
264             neutron_utils.delete_floating_ip(self.__neutron, floating_ip)
265
266         self.__floating_ip_dict = dict()
267
268         # Cleanup ports
269         for name, port in self.__ports:
270             logger.info('Deleting Port with ID - %s ', port.id)
271             neutron_utils.delete_port(self.__neutron, port)
272
273         self.__ports = list()
274
275         if self.__vm:
276             # Detach Volume
277             for volume_rec in self.__vm.volume_ids:
278                 volume = cinder_utils.get_volume_by_id(
279                     self.__cinder, volume_rec['id'])
280                 if volume:
281                     vm = nova_utils.detach_volume(
282                         self._nova, self.__neutron, self.__keystone, self.__vm,
283                         volume, self._os_creds.project_name)
284                     if vm:
285                         self.__vm = vm
286                     else:
287                         logger.warn(
288                             'Timeout waiting to detach volume %s', volume.name)
289                 else:
290                     logger.warn('Unable to detach volume with ID - [%s]',
291                                 volume_rec['id'])
292
293             # Cleanup VM
294             logger.info(
295                 'Deleting VM instance - ' + self.instance_settings.name)
296
297             try:
298                 nova_utils.delete_vm_instance(self._nova, self.__vm)
299             except NotFound as e:
300                 logger.warn('Instance already deleted - %s', e)
301
302             # Block until instance cannot be found or returns the status of
303             # DELETED
304             logger.info('Checking deletion status')
305
306             if self.vm_deleted(block=True):
307                 logger.info(
308                     'VM has been properly deleted VM with name - %s',
309                     self.instance_settings.name)
310                 self.__vm = None
311             else:
312                 logger.error(
313                     'VM not deleted within the timeout period of %s '
314                     'seconds', self.instance_settings.vm_delete_timeout)
315
316         super(self.__class__, self).clean()
317
318     def __query_ports(self, port_settings):
319         """
320         Returns the previously configured ports or an empty list if none
321         exist
322         :param port_settings: A list of PortSetting objects
323         :return: a list of OpenStack port tuples where the first member is the
324                  port name and the second is the port object
325         """
326         ports = list()
327
328         for port_setting in port_settings:
329             port = neutron_utils.get_port(
330                 self.__neutron, self.__keystone, port_settings=port_setting,
331                 project_name=self._os_creds.project_name)
332             if port:
333                 ports.append((port_setting.name, port))
334
335         return ports
336
337     def __create_ports(self, port_settings):
338         """
339         Returns the previously configured ports or creates them if they do not
340         exist
341         :param port_settings: A list of PortSetting objects
342         :return: a list of OpenStack port tuples where the first member is the
343                  port name and the second is the port object
344         """
345         ports = list()
346
347         for port_setting in port_settings:
348             port = neutron_utils.get_port(
349                 self.__neutron, self.__keystone, port_settings=port_setting,
350                 project_name=self._os_creds.project_name)
351             if not port:
352                 port = neutron_utils.create_port(
353                     self.__neutron, self._os_creds, port_setting)
354             if port:
355                 ports.append((port_setting.name, port))
356
357         return ports
358
359     def get_os_creds(self):
360         """
361         Returns the OpenStack credentials used to create these objects
362         :return: the credentials
363         """
364         return self._os_creds
365
366     def get_vm_inst(self):
367         """
368         Returns the latest version of this server object from OpenStack
369         :return: Server object
370         """
371         return nova_utils.get_server_object_by_id(
372             self._nova, self.__neutron, self.__keystone, self.__vm.id,
373             self._os_creds.project_name)
374
375     def get_console_output(self):
376         """
377         Returns the vm console object for parsing logs
378         :return: the console output object
379         """
380         return nova_utils.get_server_console_output(self._nova, self.__vm)
381
382     def get_port_ip(self, port_name, subnet_name=None):
383         """
384         Returns the first IP for the port corresponding with the port_name
385         parameter when subnet_name is None else returns the IP address that
386         corresponds to the subnet_name parameter
387         :param port_name: the name of the port from which to return the IP
388         :param subnet_name: the name of the subnet attached to this IP
389         :return: the IP or None if not found
390         """
391         port = self.get_port_by_name(port_name)
392         if port:
393             if subnet_name:
394                 network = neutron_utils.get_network_by_id(
395                     self.__neutron, port.network_id)
396                 subnet = neutron_utils.get_subnet(
397                     self.__neutron, network, subnet_name=subnet_name)
398                 if not subnet:
399                     logger.warning('Cannot retrieve port IP as subnet could '
400                                    'not be located with name - %s',
401                                    subnet_name)
402                     return None
403                 for fixed_ip in port.ips:
404                     if fixed_ip['subnet_id'] == subnet.id:
405                         return fixed_ip['ip_address']
406             else:
407                 if port.ips and len(port.ips) > 0:
408                     return port.ips[0]['ip_address']
409         return None
410
411     def get_port_mac(self, port_name):
412         """
413         Returns the first IP for the port corresponding with the port_name
414         parameter
415         TODO - Add in the subnet as an additional parameter as a port may have
416         multiple fixed_ips
417         :param port_name: the name of the port from which to return the IP
418         :return: the IP or None if not found
419         """
420         port = self.get_port_by_name(port_name)
421         if port:
422             return port.mac_address
423         return None
424
425     def get_port_by_name(self, port_name):
426         """
427         Retrieves the OpenStack port object by its given name
428         :param port_name: the name of the port
429         :return: the OpenStack port object or None if not exists
430         """
431         for key, port in self.__ports:
432             if key == port_name:
433                 return port
434         logger.warning('Cannot find port with name - ' + port_name)
435         return None
436
437     def get_vm_info(self):
438         """
439         Returns a dictionary of a VMs info as returned by OpenStack
440         :return: a dict()
441         """
442         from warnings import warn
443         warn('Do not use the returned dict() structure',
444              DeprecationWarning)
445
446         return nova_utils.get_server_info(self._nova, self.__vm)
447
448     def __get_first_provisioning_floating_ip(self):
449         """
450         Returns the first floating IP tagged with the Floating IP name if
451         exists else the first one found
452         :return:
453         """
454         for floating_ip_setting in self.instance_settings.floating_ip_settings:
455             if floating_ip_setting.provisioning:
456                 fip = self.__floating_ip_dict.get(floating_ip_setting.name)
457                 if fip:
458                     return fip
459                 elif len(self.__floating_ip_dict) > 0:
460                     for key, fip in self.__floating_ip_dict.items():
461                         return fip
462
463         # When cannot be found above
464         if len(self.__floating_ip_dict) > 0:
465             for key, fip in self.__floating_ip_dict.items():
466                 return fip
467
468     def apply_ansible_playbook(self, pb_file_loc, variables=None,
469                                fip_name=None):
470         """
471         Applies a playbook to a VM
472         :param pb_file_loc: the file location of the playbook to be applied
473         :param variables: a dict() of substitution values required by the
474                           playbook
475         :param fip_name: the name of the floating IP to use for applying the
476                          playbook (default - will take the first)
477         :return: the return value from ansible
478         """
479         return ansible_utils.apply_playbook(
480             pb_file_loc, [self.get_floating_ip(fip_name=fip_name).ip],
481             self.get_image_user(),
482             ssh_priv_key_file_path=self.keypair_settings.private_filepath,
483             variables=variables, proxy_setting=self._os_creds.proxy_settings)
484
485     def get_image_user(self):
486         """
487         Returns the instance sudo_user if it has been configured in the
488         instance_settings else it returns the image_settings.image_user value
489         """
490         if self.instance_settings.sudo_user:
491             return self.instance_settings.sudo_user
492         else:
493             return self.image_settings.image_user
494
495     def vm_deleted(self, block=False, poll_interval=POLL_INTERVAL):
496         """
497         Returns true when the VM status returns the value of
498         expected_status_code or instance retrieval throws a NotFound exception.
499         :param block: When true, thread will block until active or timeout
500                       value in seconds has been exceeded (False)
501         :param poll_interval: The polling interval in seconds
502         :return: T/F
503         """
504         try:
505             return self.__vm_status_check(
506                 STATUS_DELETED, block,
507                 self.instance_settings.vm_delete_timeout, poll_interval)
508         except NotFound as e:
509             logger.debug(
510                 "Instance not found when querying status for %s with message "
511                 "%s", STATUS_DELETED, e)
512             return True
513
514     def vm_active(self, block=False, poll_interval=POLL_INTERVAL):
515         """
516         Returns true when the VM status returns the value of the constant
517         STATUS_ACTIVE
518         :param block: When true, thread will block until active or timeout
519                       value in seconds has been exceeded (False)
520         :param poll_interval: The polling interval in seconds
521         :return: T/F
522         """
523         if self.__vm_status_check(
524                 STATUS_ACTIVE, block, self.instance_settings.vm_boot_timeout,
525                 poll_interval):
526             self.__vm = nova_utils.get_server_object_by_id(
527                 self._nova, self.__neutron, self.__keystone, self.__vm.id,
528                 self._os_creds.project_name)
529             return True
530         return False
531
532     def __vm_status_check(self, expected_status_code, block, timeout,
533                           poll_interval):
534         """
535         Returns true when the VM status returns the value of
536         expected_status_code
537         :param expected_status_code: instance status evaluated with this
538                                      string value
539         :param block: When true, thread will block until active or timeout
540                       value in seconds has been exceeded (False)
541         :param timeout: The timeout value
542         :param poll_interval: The polling interval in seconds
543         :return: T/F
544         """
545         # sleep and wait for VM status change
546         if block:
547             start = time.time()
548         else:
549             return self.__status(expected_status_code)
550
551         while timeout > time.time() - start:
552             status = self.__status(expected_status_code)
553             if status:
554                 logger.info('VM is - ' + expected_status_code)
555                 return True
556
557             logger.debug('Retry querying VM status in ' + str(
558                 poll_interval) + ' seconds')
559             time.sleep(poll_interval)
560             logger.debug('VM status query timeout in ' + str(
561                 timeout - (time.time() - start)))
562
563         logger.error(
564             'Timeout checking for VM status for ' + expected_status_code)
565         return False
566
567     def __status(self, expected_status_code):
568         """
569         Returns True when active else False
570         :param expected_status_code: instance status evaluated with this string
571                                      value
572         :return: T/F
573         """
574         if not self.__vm:
575             if expected_status_code == STATUS_DELETED:
576                 return True
577             else:
578                 return False
579
580         status = nova_utils.get_server_status(self._nova, self.__vm)
581         if not status:
582             logger.warning('Cannot find instance with id - ' + self.__vm.id)
583             return False
584
585         if status == 'ERROR':
586             raise VmInstanceCreationError(
587                 'Instance had an error during deployment')
588         logger.debug(
589             'Instance status [%s] is - %s', self.instance_settings.name,
590             status)
591         return status == expected_status_code
592
593     def vm_ssh_active(self, user_override=None, password=None, block=False,
594                       timeout=None, poll_interval=POLL_INTERVAL):
595         """
596         Returns true when the VM can be accessed via SSH
597         :param user_override: overrides the user with which to create the
598                               connection
599         :param password: overrides the use of a password instead of a private
600                          key with which to create the connection
601         :param block: When true, thread will block until active or timeout
602                       value in seconds has been exceeded (False)
603         :param timeout: the number of seconds to retry obtaining the connection
604                         and overrides the ssh_connect_timeout member of the
605                         self.instance_settings object
606         :param poll_interval: The polling interval
607         :return: T/F
608         """
609         # sleep and wait for VM status change
610         logger.info('Checking if VM is active')
611
612         if not timeout:
613             timeout = self.instance_settings.ssh_connect_timeout
614
615         if self.vm_active(block=True):
616             if block:
617                 start = time.time()
618             else:
619                 start = time.time() - timeout
620
621             while timeout > time.time() - start:
622                 status = self.__ssh_active(
623                     user_override=user_override, password=password)
624                 if status:
625                     logger.info('SSH is active for VM instance')
626                     return True
627
628                 logger.debug('Retry SSH connection in ' + str(
629                     poll_interval) + ' seconds')
630                 time.sleep(poll_interval)
631                 logger.debug('SSH connection timeout in ' + str(
632                     timeout - (time.time() - start)))
633
634         logger.error('Timeout attempting to connect with VM via SSH')
635         return False
636
637     def __ssh_active(self, user_override=None, password=None):
638         """
639         Returns True when can create a SSH session else False
640         :return: T/F
641         """
642         if len(self.__floating_ip_dict) > 0:
643             ssh = self.ssh_client(
644                 user_override=user_override, password=password)
645             if ssh:
646                 ssh.close()
647                 return True
648         return False
649
650     def cloud_init_complete(self, block=False, poll_interval=POLL_INTERVAL):
651         """
652         Returns true when the VM's cloud-init routine has completed.
653         Note: this is currently done via SSH, therefore, if this instance does
654               not have a Floating IP or a running SSH server, this routine
655               will always return False or raise an Exception
656         :param block: When true, thread will block until active or timeout
657                       value in seconds has been exceeded (False)
658         :param poll_interval: The polling interval
659         :return: T/F
660         """
661         # sleep and wait for VM status change
662         logger.info('Checking if cloud-init has completed')
663
664         timeout = self.instance_settings.cloud_init_timeout
665
666         if self.vm_active(block=True) and self.vm_ssh_active(block=True):
667             if block:
668                 start = time.time()
669             else:
670                 start = time.time() - timeout
671
672             while timeout > time.time() - start:
673                 status = self.__cloud_init_complete()
674                 if status:
675                     logger.info('cloud-init complete for VM instance')
676                     return True
677
678                 logger.debug('Retry cloud-init query in ' + str(
679                     poll_interval) + ' seconds')
680                 time.sleep(poll_interval)
681                 logger.debug('cloud-init complete timeout in ' + str(
682                     timeout - (time.time() - start)))
683
684         logger.error('Timeout waiting for cloud-init to complete')
685         return False
686
687     def __cloud_init_complete(self):
688         """
689         Returns True when can create a SSH session else False
690         :return: T/F
691         """
692         if len(self.__floating_ip_dict) > 0:
693             ssh = self.ssh_client()
694             if ssh:
695                 stdin1, stdout1, sterr1 = ssh.exec_command(
696                     'ls -l /var/lib/cloud/instance/boot-finished')
697                 return stdout1.channel.recv_exit_status() == 0
698         return False
699
700     def get_floating_ip(self, fip_name=None):
701         """
702         Returns the floating IP object byt name if found, else the first known,
703         else None
704         :param fip_name: the name of the floating IP to return
705         :return: the SSH client or None
706         """
707         if fip_name and self.__floating_ip_dict.get(fip_name):
708             return self.__floating_ip_dict.get(fip_name)
709         else:
710             return self.__get_first_provisioning_floating_ip()
711
712     def ssh_client(self, fip_name=None, user_override=None, password=None):
713         """
714         Returns an SSH client using the name or the first known floating IP if
715         exists, else None
716         :param fip_name: the name of the floating IP to return
717         :param user_override: the username to use instead of the default
718         :param password: the password to use instead of the private key
719         :return: the SSH client or None
720         """
721         fip = self.get_floating_ip(fip_name)
722
723         ansible_user = self.get_image_user()
724         if user_override:
725             ansible_user = user_override
726
727         if password:
728             private_key = None
729         else:
730             private_key = self.keypair_settings.private_filepath
731
732         if fip:
733             return ansible_utils.ssh_client(
734                 self.__get_first_provisioning_floating_ip().ip,
735                 ansible_user,
736                 private_key_filepath=private_key,
737                 password=password,
738                 proxy_settings=self._os_creds.proxy_settings)
739         else:
740             FloatingIPAllocationError(
741                 'Cannot return an SSH client. No Floating IP configured')
742
743     def add_security_group(self, security_group):
744         """
745         Adds a security group to this VM. Call will block until VM is active.
746         :param security_group: the SNAPS SecurityGroup domain object
747         :return True if successful else False
748         """
749         self.vm_active(block=True)
750
751         if not security_group:
752             logger.warning('Security group object is None, cannot add')
753             return False
754
755         try:
756             nova_utils.add_security_group(self._nova, self.get_vm_inst(),
757                                           security_group.name)
758             return True
759         except NotFound as e:
760             logger.warning('Security group not added - ' + str(e))
761             return False
762
763     def remove_security_group(self, security_group):
764         """
765         Removes a security group to this VM. Call will block until VM is active
766         :param security_group: the OpenStack security group object
767         :return True if successful else False
768         """
769         self.vm_active(block=True)
770
771         if not security_group:
772             logger.warning('Security group object is None, cannot remove')
773             return False
774
775         try:
776             nova_utils.remove_security_group(self._nova, self.get_vm_inst(),
777                                              security_group)
778             return True
779         except NotFound as e:
780             logger.warning('Security group not removed - ' + str(e))
781             return False
782
783     def reboot(self, reboot_type=RebootType.soft):
784         """
785         Issues a reboot call
786         :param reboot_type: instance of
787                             snaps.openstack.utils.nova_utils.RebootType
788                             enumeration
789         :return:
790         """
791         nova_utils.reboot_server(
792             self._nova, self.__vm, reboot_type=reboot_type)
793
794
795 def generate_creator(os_creds, vm_inst, image_config, project_name,
796                      keypair_config=None):
797     """
798     Initializes an OpenStackVmInstance object
799     :param os_creds: the OpenStack credentials
800     :param vm_inst: the SNAPS-OO VmInst domain object
801     :param image_config: the associated ImageConfig object
802     :param project_name: the associated project ID
803     :param keypair_config: the associated KeypairConfig object (optional)
804     :return: an initialized OpenStackVmInstance object
805     """
806     session = keystone_utils.keystone_session(os_creds)
807     nova = nova_utils.nova_client(os_creds, session)
808     keystone = keystone_utils.keystone_client(os_creds, session)
809     neutron = neutron_utils.neutron_client(os_creds, session)
810
811     try:
812         derived_inst_config = settings_utils.create_vm_inst_config(
813             nova, keystone, neutron, vm_inst, project_name)
814
815         derived_inst_creator = OpenStackVmInstance(
816             os_creds, derived_inst_config, image_config, keypair_config)
817         derived_inst_creator.initialize()
818         return derived_inst_creator
819     finally:
820         keystone_utils.close_session(session)
821
822
823 class VmInstanceSettings(VmInstanceConfig):
824     """
825     Deprecated, use snaps.config.vm_inst.VmInstanceConfig instead
826     """
827     def __init__(self, **kwargs):
828         from warnings import warn
829         warn('Use snaps.config.vm_inst.VmInstanceConfig instead',
830              DeprecationWarning)
831         super(self.__class__, self).__init__(**kwargs)
832
833
834 class FloatingIpSettings(FloatingIpConfig):
835     """
836     Deprecated, use snaps.config.vm_inst.FloatingIpConfig instead
837     """
838     def __init__(self, **kwargs):
839         from warnings import warn
840         warn('Use snaps.config.vm_inst.FloatingIpConfig instead',
841              DeprecationWarning)
842         super(self.__class__, self).__init__(**kwargs)
843
844
845 class VmInstanceCreationError(Exception):
846     """
847     Exception to be thrown when an VM instance cannot be created
848     """
849
850
851 class FloatingIPAllocationError(Exception):
852     """
853     Exception to be thrown when an VM instance cannot allocate a floating IP
854     """