e2a699183f107c07b59d2b6887770439929cce61
[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         """
478         ansible_utils.apply_playbook(
479             pb_file_loc, [self.get_floating_ip(fip_name=fip_name).ip],
480             self.get_image_user(),
481             ssh_priv_key_file_path=self.keypair_settings.private_filepath,
482             variables=variables, proxy_setting=self._os_creds.proxy_settings)
483
484     def get_image_user(self):
485         """
486         Returns the instance sudo_user if it has been configured in the
487         instance_settings else it returns the image_settings.image_user value
488         """
489         if self.instance_settings.sudo_user:
490             return self.instance_settings.sudo_user
491         else:
492             return self.image_settings.image_user
493
494     def vm_deleted(self, block=False, poll_interval=POLL_INTERVAL):
495         """
496         Returns true when the VM status returns the value of
497         expected_status_code or instance retrieval throws a NotFound exception.
498         :param block: When true, thread will block until active or timeout
499                       value in seconds has been exceeded (False)
500         :param poll_interval: The polling interval in seconds
501         :return: T/F
502         """
503         try:
504             return self.__vm_status_check(
505                 STATUS_DELETED, block,
506                 self.instance_settings.vm_delete_timeout, poll_interval)
507         except NotFound as e:
508             logger.debug(
509                 "Instance not found when querying status for %s with message "
510                 "%s", STATUS_DELETED, e)
511             return True
512
513     def vm_active(self, block=False, poll_interval=POLL_INTERVAL):
514         """
515         Returns true when the VM status returns the value of the constant
516         STATUS_ACTIVE
517         :param block: When true, thread will block until active or timeout
518                       value in seconds has been exceeded (False)
519         :param poll_interval: The polling interval in seconds
520         :return: T/F
521         """
522         if self.__vm_status_check(
523                 STATUS_ACTIVE, block, self.instance_settings.vm_boot_timeout,
524                 poll_interval):
525             self.__vm = nova_utils.get_server_object_by_id(
526                 self._nova, self.__neutron, self.__keystone, self.__vm.id,
527                 self._os_creds.project_name)
528             return True
529         return False
530
531     def __vm_status_check(self, expected_status_code, block, timeout,
532                           poll_interval):
533         """
534         Returns true when the VM status returns the value of
535         expected_status_code
536         :param expected_status_code: instance status evaluated with this
537                                      string value
538         :param block: When true, thread will block until active or timeout
539                       value in seconds has been exceeded (False)
540         :param timeout: The timeout value
541         :param poll_interval: The polling interval in seconds
542         :return: T/F
543         """
544         # sleep and wait for VM status change
545         if block:
546             start = time.time()
547         else:
548             return self.__status(expected_status_code)
549
550         while timeout > time.time() - start:
551             status = self.__status(expected_status_code)
552             if status:
553                 logger.info('VM is - ' + expected_status_code)
554                 return True
555
556             logger.debug('Retry querying VM status in ' + str(
557                 poll_interval) + ' seconds')
558             time.sleep(poll_interval)
559             logger.debug('VM status query timeout in ' + str(
560                 timeout - (time.time() - start)))
561
562         logger.error(
563             'Timeout checking for VM status for ' + expected_status_code)
564         return False
565
566     def __status(self, expected_status_code):
567         """
568         Returns True when active else False
569         :param expected_status_code: instance status evaluated with this string
570                                      value
571         :return: T/F
572         """
573         if not self.__vm:
574             if expected_status_code == STATUS_DELETED:
575                 return True
576             else:
577                 return False
578
579         status = nova_utils.get_server_status(self._nova, self.__vm)
580         if not status:
581             logger.warning('Cannot find instance with id - ' + self.__vm.id)
582             return False
583
584         if status == 'ERROR':
585             raise VmInstanceCreationError(
586                 'Instance had an error during deployment')
587         logger.debug(
588             'Instance status [%s] is - %s', self.instance_settings.name,
589             status)
590         return status == expected_status_code
591
592     def vm_ssh_active(self, user_override=None, password=None, block=False,
593                       timeout=None, poll_interval=POLL_INTERVAL):
594         """
595         Returns true when the VM can be accessed via SSH
596         :param user_override: overrides the user with which to create the
597                               connection
598         :param password: overrides the use of a password instead of a private
599                          key with which to create the connection
600         :param block: When true, thread will block until active or timeout
601                       value in seconds has been exceeded (False)
602         :param timeout: the number of seconds to retry obtaining the connection
603                         and overrides the ssh_connect_timeout member of the
604                         self.instance_settings object
605         :param poll_interval: The polling interval
606         :return: T/F
607         """
608         # sleep and wait for VM status change
609         logger.info('Checking if VM is active')
610
611         if not timeout:
612             timeout = self.instance_settings.ssh_connect_timeout
613
614         if self.vm_active(block=True):
615             if block:
616                 start = time.time()
617             else:
618                 start = time.time() - timeout
619
620             while timeout > time.time() - start:
621                 status = self.__ssh_active(
622                     user_override=user_override, password=password)
623                 if status:
624                     logger.info('SSH is active for VM instance')
625                     return True
626
627                 logger.debug('Retry SSH connection in ' + str(
628                     poll_interval) + ' seconds')
629                 time.sleep(poll_interval)
630                 logger.debug('SSH connection timeout in ' + str(
631                     timeout - (time.time() - start)))
632
633         logger.error('Timeout attempting to connect with VM via SSH')
634         return False
635
636     def __ssh_active(self, user_override=None, password=None):
637         """
638         Returns True when can create a SSH session else False
639         :return: T/F
640         """
641         if len(self.__floating_ip_dict) > 0:
642             ssh = self.ssh_client(
643                 user_override=user_override, password=password)
644             if ssh:
645                 ssh.close()
646                 return True
647         return False
648
649     def cloud_init_complete(self, block=False, poll_interval=POLL_INTERVAL):
650         """
651         Returns true when the VM's cloud-init routine has completed.
652         Note: this is currently done via SSH, therefore, if this instance does
653               not have a Floating IP or a running SSH server, this routine
654               will always return False or raise an Exception
655         :param block: When true, thread will block until active or timeout
656                       value in seconds has been exceeded (False)
657         :param poll_interval: The polling interval
658         :return: T/F
659         """
660         # sleep and wait for VM status change
661         logger.info('Checking if cloud-init has completed')
662
663         timeout = self.instance_settings.cloud_init_timeout
664
665         if self.vm_active(block=True) and self.vm_ssh_active(block=True):
666             if block:
667                 start = time.time()
668             else:
669                 start = time.time() - timeout
670
671             while timeout > time.time() - start:
672                 status = self.__cloud_init_complete()
673                 if status:
674                     logger.info('cloud-init complete for VM instance')
675                     return True
676
677                 logger.debug('Retry cloud-init query in ' + str(
678                     poll_interval) + ' seconds')
679                 time.sleep(poll_interval)
680                 logger.debug('cloud-init complete timeout in ' + str(
681                     timeout - (time.time() - start)))
682
683         logger.error('Timeout waiting for cloud-init to complete')
684         return False
685
686     def __cloud_init_complete(self):
687         """
688         Returns True when can create a SSH session else False
689         :return: T/F
690         """
691         if len(self.__floating_ip_dict) > 0:
692             ssh = self.ssh_client()
693             if ssh:
694                 stdin1, stdout1, sterr1 = ssh.exec_command(
695                     'ls -l /var/lib/cloud/instance/boot-finished')
696                 return stdout1.channel.recv_exit_status() == 0
697         return False
698
699     def get_floating_ip(self, fip_name=None):
700         """
701         Returns the floating IP object byt name if found, else the first known,
702         else None
703         :param fip_name: the name of the floating IP to return
704         :return: the SSH client or None
705         """
706         if fip_name and self.__floating_ip_dict.get(fip_name):
707             return self.__floating_ip_dict.get(fip_name)
708         else:
709             return self.__get_first_provisioning_floating_ip()
710
711     def ssh_client(self, fip_name=None, user_override=None, password=None):
712         """
713         Returns an SSH client using the name or the first known floating IP if
714         exists, else None
715         :param fip_name: the name of the floating IP to return
716         :param user_override: the username to use instead of the default
717         :param password: the password to use instead of the private key
718         :return: the SSH client or None
719         """
720         fip = self.get_floating_ip(fip_name)
721
722         ansible_user = self.get_image_user()
723         if user_override:
724             ansible_user = user_override
725
726         if password:
727             private_key = None
728         else:
729             private_key = self.keypair_settings.private_filepath
730
731         if fip:
732             return ansible_utils.ssh_client(
733                 self.__get_first_provisioning_floating_ip().ip,
734                 ansible_user,
735                 private_key_filepath=private_key,
736                 password=password,
737                 proxy_settings=self._os_creds.proxy_settings)
738         else:
739             FloatingIPAllocationError(
740                 'Cannot return an SSH client. No Floating IP configured')
741
742     def add_security_group(self, security_group):
743         """
744         Adds a security group to this VM. Call will block until VM is active.
745         :param security_group: the SNAPS SecurityGroup domain object
746         :return True if successful else False
747         """
748         self.vm_active(block=True)
749
750         if not security_group:
751             logger.warning('Security group object is None, cannot add')
752             return False
753
754         try:
755             nova_utils.add_security_group(self._nova, self.get_vm_inst(),
756                                           security_group.name)
757             return True
758         except NotFound as e:
759             logger.warning('Security group not added - ' + str(e))
760             return False
761
762     def remove_security_group(self, security_group):
763         """
764         Removes a security group to this VM. Call will block until VM is active
765         :param security_group: the OpenStack security group object
766         :return True if successful else False
767         """
768         self.vm_active(block=True)
769
770         if not security_group:
771             logger.warning('Security group object is None, cannot remove')
772             return False
773
774         try:
775             nova_utils.remove_security_group(self._nova, self.get_vm_inst(),
776                                              security_group)
777             return True
778         except NotFound as e:
779             logger.warning('Security group not removed - ' + str(e))
780             return False
781
782     def reboot(self, reboot_type=RebootType.soft):
783         """
784         Issues a reboot call
785         :param reboot_type: instance of
786                             snaps.openstack.utils.nova_utils.RebootType
787                             enumeration
788         :return:
789         """
790         nova_utils.reboot_server(
791             self._nova, self.__vm, reboot_type=reboot_type)
792
793
794 def generate_creator(os_creds, vm_inst, image_config, project_name,
795                      keypair_config=None):
796     """
797     Initializes an OpenStackVmInstance object
798     :param os_creds: the OpenStack credentials
799     :param vm_inst: the SNAPS-OO VmInst domain object
800     :param image_config: the associated ImageConfig object
801     :param project_name: the associated project ID
802     :param keypair_config: the associated KeypairConfig object (optional)
803     :return: an initialized OpenStackVmInstance object
804     """
805     session = keystone_utils.keystone_session(os_creds)
806     nova = nova_utils.nova_client(os_creds, session)
807     keystone = keystone_utils.keystone_client(os_creds, session)
808     neutron = neutron_utils.neutron_client(os_creds, session)
809
810     try:
811         derived_inst_config = settings_utils.create_vm_inst_config(
812             nova, keystone, neutron, vm_inst, project_name)
813
814         derived_inst_creator = OpenStackVmInstance(
815             os_creds, derived_inst_config, image_config, keypair_config)
816         derived_inst_creator.initialize()
817         return derived_inst_creator
818     finally:
819         keystone_utils.close_session(session)
820
821
822 class VmInstanceSettings(VmInstanceConfig):
823     """
824     Deprecated, use snaps.config.vm_inst.VmInstanceConfig instead
825     """
826     def __init__(self, **kwargs):
827         from warnings import warn
828         warn('Use snaps.config.vm_inst.VmInstanceConfig instead',
829              DeprecationWarning)
830         super(self.__class__, self).__init__(**kwargs)
831
832
833 class FloatingIpSettings(FloatingIpConfig):
834     """
835     Deprecated, use snaps.config.vm_inst.FloatingIpConfig instead
836     """
837     def __init__(self, **kwargs):
838         from warnings import warn
839         warn('Use snaps.config.vm_inst.FloatingIpConfig instead',
840              DeprecationWarning)
841         super(self.__class__, self).__init__(**kwargs)
842
843
844 class VmInstanceCreationError(Exception):
845     """
846     Exception to be thrown when an VM instance cannot be created
847     """
848
849
850 class FloatingIPAllocationError(Exception):
851     """
852     Exception to be thrown when an VM instance cannot allocate a floating IP
853     """