1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 # and others. All rights reserved.
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:
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 from neutronclient.common.exceptions import PortNotFoundClient
19 from novaclient.exceptions import NotFound
21 from snaps.openstack.create_network import PortSettings
22 from snaps.openstack.openstack_creator import OpenStackComputeObject
23 from snaps.openstack.utils import glance_utils, cinder_utils
24 from snaps.openstack.utils import neutron_utils
25 from snaps.openstack.utils import nova_utils
26 from snaps.provisioning import ansible_utils
28 __author__ = 'spisarski'
30 logger = logging.getLogger('create_instance')
33 STATUS_ACTIVE = 'ACTIVE'
34 STATUS_DELETED = 'DELETED'
37 class OpenStackVmInstance(OpenStackComputeObject):
39 Class responsible for managing a VM instance in OpenStack
42 def __init__(self, os_creds, instance_settings, image_settings,
43 keypair_settings=None):
46 :param os_creds: The connection credentials to the OpenStack API
47 :param instance_settings: Contains the settings for this VM
48 :param image_settings: The OpenStack image object settings
49 :param keypair_settings: The keypair metadata (Optional)
52 super(self.__class__, self).__init__(os_creds)
56 self.instance_settings = instance_settings
57 self.image_settings = image_settings
58 self.keypair_settings = keypair_settings
60 self.__floating_ip_dict = dict()
62 # Instantiated in self.create()
65 # Note: this object does not change after the VM becomes active
70 Loads the existing VMInst, Port, FloatingIps
71 :return: VMInst domain object
73 super(self.__class__, self).initialize()
75 self.__neutron = neutron_utils.neutron_client(self._os_creds)
77 self.__ports = self.__query_ports(self.instance_settings.port_settings)
78 self.__lookup_existing_vm_by_name()
80 def create(self, block=False):
82 Creates a VM instance and associated objects unless they already exist
83 :param block: Thread will block until instance has either become
84 active, error, or timeout waiting.
85 Additionally, when True, floating IPs will not be applied
87 :return: VMInst domain object
91 if len(self.__ports) == 0:
92 self.__ports = self.__create_ports(
93 self.instance_settings.port_settings)
95 self.__create_vm(block)
99 def __lookup_existing_vm_by_name(self):
101 Populates the member variables 'self.vm' and 'self.floating_ips' if a
102 VM with the same name already exists
105 server = nova_utils.get_server(
106 self._nova, vm_inst_settings=self.instance_settings)
108 if server.name == self.instance_settings.name:
111 'Found existing machine with name - %s',
112 self.instance_settings.name)
114 fips = neutron_utils.get_floating_ips(self.__neutron,
116 for port_id, fip in fips:
117 settings = self.instance_settings.floating_ip_settings
118 for fip_setting in settings:
119 if port_id == fip_setting.port_id:
120 self.__floating_ip_dict[fip_setting.name] = fip
122 port = neutron_utils.get_port_by_id(
123 self.__neutron, port_id)
124 if port and port.name == fip_setting.port_name:
125 self.__floating_ip_dict[fip_setting.name] = fip
127 def __create_vm(self, block=False):
129 Responsible for creating the VM instance
130 :param block: Thread will block until instance has either become
131 active, error, or timeout waiting. Floating IPs will be
132 assigned after active when block=True
134 glance = glance_utils.glance_client(self._os_creds)
135 self.__vm = nova_utils.create_server(
136 self._nova, self.__neutron, glance, self.instance_settings,
137 self.image_settings, self.keypair_settings)
138 logger.info('Created instance with name - %s',
139 self.instance_settings.name)
142 if not self.vm_active(block=True):
143 raise VmInstanceCreationError(
144 'Fatal error, VM did not become ACTIVE within the alloted '
147 # Create server should do this but found it needed to occur here
148 for sec_grp_name in self.instance_settings.security_group_names:
149 if self.vm_active(block=True):
150 nova_utils.add_security_group(self._nova, self.__vm,
153 raise VmInstanceCreationError(
154 'Cannot applying security group with name ' +
156 ' to VM that did not activate with name - ' +
157 self.instance_settings.name)
159 if self.instance_settings.volume_names:
160 for volume_name in self.instance_settings.volume_names:
161 cinder = cinder_utils.cinder_client(self._os_creds)
162 volume = cinder_utils.get_volume(
163 cinder, volume_name=volume_name)
165 if volume and self.vm_active(block=True):
167 vm = nova_utils.attach_volume(
168 self._nova, self.__vm, volume, timeout)
173 logger.warn('Volume [%s] not attached within timeout '
174 'of [%s]', volume.name, timeout)
176 logger.warn('Unable to attach volume named [%s]',
179 self.__apply_floating_ips()
181 def __apply_floating_ips(self):
183 Applies the configured floating IPs to the necessary ports
186 for key, port in self.__ports:
187 port_dict[key] = port
190 for floating_ip_setting in self.instance_settings.floating_ip_settings:
191 port = port_dict.get(floating_ip_setting.port_name)
194 raise VmInstanceCreationError(
195 'Cannot find port object with name - ' +
196 floating_ip_setting.port_name)
198 # Setup Floating IP only if there is a router with an external
200 ext_gateway = self.__ext_gateway_by_router(
201 floating_ip_setting.router_name)
203 subnet = neutron_utils.get_subnet(
205 subnet_name=floating_ip_setting.subnet_name)
206 floating_ip = neutron_utils.create_floating_ip(
207 self.__neutron, ext_gateway)
208 self.__floating_ip_dict[floating_ip_setting.name] = floating_ip
211 'Created floating IP %s via router - %s', floating_ip.ip,
212 floating_ip_setting.router_name)
213 self.__add_floating_ip(floating_ip, port, subnet)
215 raise VmInstanceCreationError(
216 'Unable to add floating IP to port, cannot locate router '
217 'with an external gateway ')
219 def __ext_gateway_by_router(self, router_name):
221 Returns network name for the external network attached to a router or
223 :param router_name: The name of the router to lookup
224 :return: the external network name or None
226 router = neutron_utils.get_router(
227 self.__neutron, router_name=router_name)
228 if router and router.external_gateway_info:
229 network = neutron_utils.get_network_by_id(
231 router.external_gateway_info['network_id'])
238 Destroys the VM instance
241 # Cleanup floating IPs
242 for name, floating_ip in self.__floating_ip_dict.items():
244 logger.info('Deleting Floating IP - ' + floating_ip.ip)
245 neutron_utils.delete_floating_ip(self.__neutron, floating_ip)
246 except Exception as e:
247 logger.error('Error deleting Floating IP - ' + str(e))
248 self.__floating_ip_dict = dict()
251 for volume_rec in self.__vm.volume_ids:
252 cinder = cinder_utils.cinder_client(self._os_creds)
253 volume = cinder_utils.get_volume_by_id(cinder, volume_rec['id'])
256 vm = nova_utils.detach_volume(
257 self._nova, self.__vm, volume, 30)
262 'Timeout waiting to detach volume %s', volume.name)
263 except Exception as e:
264 logger.error('Unexpected error detaching volume %s '
265 'with error %s', volume.name, e)
267 logger.warn('Unable to detach volume with ID - [%s]',
271 for name, port in self.__ports:
272 logger.info('Deleting Port with ID - %s ', port.id)
274 neutron_utils.delete_port(self.__neutron, port)
275 except PortNotFoundClient as e:
276 logger.warning('Unexpected error deleting port - %s', e)
278 self.__ports = list()
284 'Deleting VM instance - ' + self.instance_settings.name)
285 nova_utils.delete_vm_instance(self._nova, self.__vm)
286 except Exception as e:
287 logger.error('Error deleting VM - %s', e)
289 # Block until instance cannot be found or returns the status of
291 logger.info('Checking deletion status')
294 if self.vm_deleted(block=True):
296 'VM has been properly deleted VM with name - %s',
297 self.instance_settings.name)
301 'VM not deleted within the timeout period of %s '
302 'seconds', self.instance_settings.vm_delete_timeout)
303 except Exception as e:
305 'Unexpected error while checking VM instance status - %s',
308 def __query_ports(self, port_settings):
310 Returns the previously configured ports or an empty list if none
312 :param port_settings: A list of PortSetting objects
313 :return: a list of OpenStack port tuples where the first member is the
314 port name and the second is the port object
318 for port_setting in port_settings:
319 port = neutron_utils.get_port(
320 self.__neutron, port_settings=port_setting)
322 ports.append((port_setting.name, port))
326 def __create_ports(self, port_settings):
328 Returns the previously configured ports or creates them if they do not
330 :param port_settings: A list of PortSetting objects
331 :return: a list of OpenStack port tuples where the first member is the
332 port name and the second is the port object
336 for port_setting in port_settings:
337 port = neutron_utils.get_port(
338 self.__neutron, port_settings=port_setting)
340 port = neutron_utils.create_port(
341 self.__neutron, self._os_creds, port_setting)
343 ports.append((port_setting.name, port))
347 def __add_floating_ip(self, floating_ip, port, subnet, timeout=30,
348 poll_interval=POLL_INTERVAL):
350 Returns True when active else False
351 TODO - Make timeout and poll_interval configurable...
356 # Take IP of subnet if there is one configured on which to place
358 for fixed_ip in port.ips:
359 if fixed_ip['subnet_id'] == subnet.id:
360 ip = fixed_ip['ip_address']
363 # Simply take the first
364 ip = port.ips[0]['ip_address']
367 count = timeout / poll_interval
369 logger.debug('Attempting to add floating IP to instance')
371 nova_utils.add_floating_ip_to_server(
372 self._nova, self.__vm, floating_ip, ip)
374 'Added floating IP %s to port IP %s on instance %s',
375 floating_ip.ip, ip, self.instance_settings.name)
377 except Exception as e:
379 'Retry adding floating IP to instance. Last attempt '
380 'failed with - %s', e)
381 time.sleep(poll_interval)
385 raise VmInstanceCreationError(
386 'Unable find IP address on which to place the floating IP')
388 logger.error('Timeout attempting to add the floating IP to instance.')
389 raise VmInstanceCreationError(
390 'Timeout while attempting add floating IP to instance')
392 def get_os_creds(self):
394 Returns the OpenStack credentials used to create these objects
395 :return: the credentials
397 return self._os_creds
399 def get_vm_inst(self):
401 Returns the latest version of this server object from OpenStack
402 :return: Server object
404 return nova_utils.get_server_object_by_id(self._nova, self.__vm.id)
406 def get_console_output(self):
408 Returns the vm console object for parsing logs
409 :return: the console output object
411 return nova_utils.get_server_console_output(self._nova, self.__vm)
413 def get_port_ip(self, port_name, subnet_name=None):
415 Returns the first IP for the port corresponding with the port_name
416 parameter when subnet_name is None else returns the IP address that
417 corresponds to the subnet_name parameter
418 :param port_name: the name of the port from which to return the IP
419 :param subnet_name: the name of the subnet attached to this IP
420 :return: the IP or None if not found
422 port = self.get_port_by_name(port_name)
425 subnet = neutron_utils.get_subnet(
426 self.__neutron, subnet_name=subnet_name)
428 logger.warning('Cannot retrieve port IP as subnet could '
429 'not be located with name - %s',
432 for fixed_ip in port.ips:
433 if fixed_ip['subnet_id'] == subnet.id:
434 return fixed_ip['ip_address']
436 if port.ips and len(port.ips) > 0:
437 return port.ips[0]['ip_address']
440 def get_port_mac(self, port_name):
442 Returns the first IP for the port corresponding with the port_name
444 TODO - Add in the subnet as an additional parameter as a port may have
446 :param port_name: the name of the port from which to return the IP
447 :return: the IP or None if not found
449 port = self.get_port_by_name(port_name)
451 return port.mac_address
454 def get_port_by_name(self, port_name):
456 Retrieves the OpenStack port object by its given name
457 :param port_name: the name of the port
458 :return: the OpenStack port object or None if not exists
460 for key, port in self.__ports:
463 logger.warning('Cannot find port with name - ' + port_name)
466 def get_vm_info(self):
468 Returns a dictionary of a VMs info as returned by OpenStack
471 return nova_utils.get_server_info(self._nova, self.__vm)
473 def config_nics(self):
475 Responsible for configuring NICs on RPM systems where the instance has
476 more than one configured port
477 :return: the value returned by ansible_utils.apply_ansible_playbook()
479 if len(self.__ports) > 1 and len(self.__floating_ip_dict) > 0:
480 if self.vm_active(block=True) and self.vm_ssh_active(block=True):
481 for key, port in self.__ports:
482 port_index = self.__ports.index((key, port))
484 nic_name = 'eth' + repr(port_index)
485 retval = self.__config_nic(
487 self.__get_first_provisioning_floating_ip().ip)
488 logger.info('Configured NIC - %s on VM - %s',
489 nic_name, self.instance_settings.name)
492 def __get_first_provisioning_floating_ip(self):
494 Returns the first floating IP tagged with the Floating IP name if
495 exists else the first one found
498 for floating_ip_setting in self.instance_settings.floating_ip_settings:
499 if floating_ip_setting.provisioning:
500 fip = self.__floating_ip_dict.get(floating_ip_setting.name)
503 elif len(self.__floating_ip_dict) > 0:
504 for key, fip in self.__floating_ip_dict.items():
507 def __config_nic(self, nic_name, port, ip):
509 Although ports/NICs can contain multiple IPs, this code currently only
512 :param nic_name: Name of the interface
513 :param port: The port information containing the expected IP values.
514 :param ip: The IP on which to apply the playbook.
515 :return: the return value from ansible
517 port_ip = port.ips[0]['ip_address']
520 'nic_name': nic_name,
524 if self.image_settings.nic_config_pb_loc and self.keypair_settings:
525 return self.apply_ansible_playbook(
526 self.image_settings.nic_config_pb_loc, variables)
529 'VM %s cannot self configure NICs eth1++. No playbook or '
530 'keypairs found.', self.instance_settings.name)
532 def apply_ansible_playbook(self, pb_file_loc, variables=None,
535 Applies a playbook to a VM
536 :param pb_file_loc: the file location of the playbook to be applied
537 :param variables: a dict() of substitution values required by the
539 :param fip_name: the name of the floating IP to use for applying the
540 playbook (default - will take the first)
541 :return: the return value from ansible
543 return ansible_utils.apply_playbook(
544 pb_file_loc, [self.get_floating_ip(fip_name=fip_name).ip],
545 self.get_image_user(), self.keypair_settings.private_filepath,
546 variables, self._os_creds.proxy_settings)
548 def get_image_user(self):
550 Returns the instance sudo_user if it has been configured in the
551 instance_settings else it returns the image_settings.image_user value
553 if self.instance_settings.sudo_user:
554 return self.instance_settings.sudo_user
556 return self.image_settings.image_user
558 def vm_deleted(self, block=False, poll_interval=POLL_INTERVAL):
560 Returns true when the VM status returns the value of
561 expected_status_code or instance retrieval throws a NotFound exception.
562 :param block: When true, thread will block until active or timeout
563 value in seconds has been exceeded (False)
564 :param poll_interval: The polling interval in seconds
568 return self.__vm_status_check(
569 STATUS_DELETED, block,
570 self.instance_settings.vm_delete_timeout, poll_interval)
571 except NotFound as e:
573 "Instance not found when querying status for %s with message "
574 "%s", STATUS_DELETED, e)
577 def vm_active(self, block=False, poll_interval=POLL_INTERVAL):
579 Returns true when the VM status returns the value of the constant
581 :param block: When true, thread will block until active or timeout
582 value in seconds has been exceeded (False)
583 :param poll_interval: The polling interval in seconds
586 return self.__vm_status_check(STATUS_ACTIVE, block,
587 self.instance_settings.vm_boot_timeout,
590 def __vm_status_check(self, expected_status_code, block, timeout,
593 Returns true when the VM status returns the value of
595 :param expected_status_code: instance status evaluated with this
597 :param block: When true, thread will block until active or timeout
598 value in seconds has been exceeded (False)
599 :param timeout: The timeout value
600 :param poll_interval: The polling interval in seconds
603 # sleep and wait for VM status change
607 return self.__status(expected_status_code)
609 while timeout > time.time() - start:
610 status = self.__status(expected_status_code)
612 logger.info('VM is - ' + expected_status_code)
615 logger.debug('Retry querying VM status in ' + str(
616 poll_interval) + ' seconds')
617 time.sleep(poll_interval)
618 logger.debug('VM status query timeout in ' + str(
619 timeout - (time.time() - start)))
622 'Timeout checking for VM status for ' + expected_status_code)
625 def __status(self, expected_status_code):
627 Returns True when active else False
628 :param expected_status_code: instance status evaluated with this string
633 if expected_status_code == STATUS_DELETED:
638 status = nova_utils.get_server_status(self._nova, self.__vm)
640 logger.warning('Cannot find instance with id - ' + self.__vm.id)
643 if status == 'ERROR':
644 raise VmInstanceCreationError(
645 'Instance had an error during deployment')
647 'Instance status [%s] is - %s', self.instance_settings.name,
649 return status == expected_status_code
651 def vm_ssh_active(self, block=False, poll_interval=POLL_INTERVAL):
653 Returns true when the VM can be accessed via SSH
654 :param block: When true, thread will block until active or timeout
655 value in seconds has been exceeded (False)
656 :param poll_interval: The polling interval
659 # sleep and wait for VM status change
660 logger.info('Checking if VM is active')
662 timeout = self.instance_settings.ssh_connect_timeout
664 if self.vm_active(block=True):
668 start = time.time() - timeout
670 while timeout > time.time() - start:
671 status = self.__ssh_active()
673 logger.info('SSH is active for VM instance')
676 logger.debug('Retry SSH connection in ' + str(
677 poll_interval) + ' seconds')
678 time.sleep(poll_interval)
679 logger.debug('SSH connection timeout in ' + str(
680 timeout - (time.time() - start)))
682 logger.error('Timeout attempting to connect with VM via SSH')
685 def __ssh_active(self):
687 Returns True when can create a SSH session else False
690 if len(self.__floating_ip_dict) > 0:
691 ssh = self.ssh_client()
697 def get_floating_ip(self, fip_name=None):
699 Returns the floating IP object byt name if found, else the first known,
701 :param fip_name: the name of the floating IP to return
702 :return: the SSH client or None
705 if fip_name and self.__floating_ip_dict.get(fip_name):
706 return self.__floating_ip_dict.get(fip_name)
708 return self.__get_first_provisioning_floating_ip()
710 def ssh_client(self, fip_name=None):
712 Returns an SSH client using the name or the first known floating IP if
714 :param fip_name: the name of the floating IP to return
715 :return: the SSH client or None
717 fip = self.get_floating_ip(fip_name)
719 return ansible_utils.ssh_client(
720 self.__get_first_provisioning_floating_ip().ip,
721 self.get_image_user(),
722 self.keypair_settings.private_filepath,
723 proxy_settings=self._os_creds.proxy_settings)
726 'Cannot return an SSH client. No Floating IP configured')
728 def add_security_group(self, security_group):
730 Adds a security group to this VM. Call will block until VM is active.
731 :param security_group: the SNAPS SecurityGroup domain object
732 :return True if successful else False
734 self.vm_active(block=True)
736 if not security_group:
737 logger.warning('Security group object is None, cannot add')
741 nova_utils.add_security_group(self._nova, self.get_vm_inst(),
744 except NotFound as e:
745 logger.warning('Security group not added - ' + str(e))
748 def remove_security_group(self, security_group):
750 Removes a security group to this VM. Call will block until VM is active
751 :param security_group: the OpenStack security group object
752 :return True if successful else False
754 self.vm_active(block=True)
756 if not security_group:
757 logger.warning('Security group object is None, cannot remove')
761 nova_utils.remove_security_group(self._nova, self.get_vm_inst(),
764 except NotFound as e:
765 logger.warning('Security group not removed - ' + str(e))
769 class VmInstanceSettings:
771 Class responsible for holding configuration setting for a VM Instance
774 def __init__(self, **kwargs):
777 :param name: the name of the VM
778 :param flavor: the VM's flavor name
779 :param port_settings: the port configuration settings (required)
780 :param security_group_names: a set of names of the security groups to
782 :param floating_ip_settings: the floating IP configuration settings
783 :param sudo_user: the sudo user of the VM that will override the
784 instance_settings.image_user when trying to
786 :param vm_boot_timeout: the amount of time a thread will sleep waiting
787 for an instance to boot
788 :param vm_delete_timeout: the amount of time a thread will sleep
789 waiting for an instance to be deleted
790 :param ssh_connect_timeout: the amount of time a thread will sleep
791 waiting obtaining an SSH connection to a VM
792 :param availability_zone: the name of the compute server on which to
793 deploy the VM (optional)
794 :param volume_names: a list of the names of the volume to attach
796 :param userdata: the string contents of any optional cloud-init script
797 to execute after the VM has been activated.
798 This value may also contain a dict who's key value
799 must contain the key 'cloud-init_file' which denotes
800 the location of some file containing the cloud-init
803 self.name = kwargs.get('name')
804 self.flavor = kwargs.get('flavor')
805 self.sudo_user = kwargs.get('sudo_user')
806 self.userdata = kwargs.get('userdata')
808 self.port_settings = list()
809 port_settings = kwargs.get('ports')
810 if not port_settings:
811 port_settings = kwargs.get('port_settings')
813 for port_setting in port_settings:
814 if isinstance(port_setting, dict):
815 self.port_settings.append(PortSettings(**port_setting))
816 elif isinstance(port_setting, PortSettings):
817 self.port_settings.append(port_setting)
819 if kwargs.get('security_group_names'):
820 if isinstance(kwargs['security_group_names'], list):
821 self.security_group_names = kwargs['security_group_names']
822 elif isinstance(kwargs['security_group_names'], set):
823 self.security_group_names = kwargs['security_group_names']
824 elif isinstance(kwargs['security_group_names'], str):
825 self.security_group_names = [kwargs['security_group_names']]
827 raise VmInstanceSettingsError(
828 'Invalid data type for security_group_names attribute')
830 self.security_group_names = set()
832 self.floating_ip_settings = list()
833 floating_ip_settings = kwargs.get('floating_ips')
834 if not floating_ip_settings:
835 floating_ip_settings = kwargs.get('floating_ip_settings')
836 if floating_ip_settings:
837 for floating_ip_config in floating_ip_settings:
838 if isinstance(floating_ip_config, FloatingIpSettings):
839 self.floating_ip_settings.append(floating_ip_config)
841 self.floating_ip_settings.append(FloatingIpSettings(
842 **floating_ip_config['floating_ip']))
844 self.vm_boot_timeout = kwargs.get('vm_boot_timeout', 900)
845 self.vm_delete_timeout = kwargs.get('vm_delete_timeout', 300)
846 self.ssh_connect_timeout = kwargs.get('ssh_connect_timeout', 180)
847 self.availability_zone = kwargs.get('availability_zone')
848 self.volume_names = kwargs.get('volume_names')
850 if self.volume_names and not isinstance(self.volume_names, list):
851 raise VmInstanceSettingsError('volume_names must be a list')
853 if not self.name or not self.flavor:
854 raise VmInstanceSettingsError(
855 'Instance configuration requires the attributes: name, flavor')
857 if len(self.port_settings) == 0:
858 raise VmInstanceSettingsError(
859 'Instance configuration requires port settings (aka. NICS)')
862 class FloatingIpSettings:
864 Class responsible for holding configuration settings for a floating IP
867 def __init__(self, **kwargs):
870 :param name: the name of the floating IP
871 :param port_name: the name of the router to the external network
872 :param router_name: the name of the router to the external network
873 :param subnet_name: the name of the subnet on which to attach the
875 :param provisioning: when true, this floating IP can be used for
878 TODO - provisioning flag is a hack as I have only observed a single
879 Floating IPs that actually works on an instance. Multiple floating IPs
880 placed on different subnets from the same port are especially
881 troublesome as you cannot predict which one will actually connect.
882 For now, it is recommended not to setup multiple floating IPs on an
883 instance unless absolutely necessary.
885 self.name = kwargs.get('name')
886 self.port_name = kwargs.get('port_name')
887 self.port_id = kwargs.get('port_id')
888 self.router_name = kwargs.get('router_name')
889 self.subnet_name = kwargs.get('subnet_name')
890 if kwargs.get('provisioning') is not None:
891 self.provisioning = kwargs['provisioning']
893 self.provisioning = True
895 # if not self.name or not self.port_name or not self.router_name:
896 if not self.name or not self.router_name:
897 raise FloatingIpSettingsError(
898 'The attributes name, port_name and router_name are required')
900 if not self.port_name and not self.port_id:
901 raise FloatingIpSettingsError(
902 'The attributes port_name or port_id are required')
905 class VmInstanceSettingsError(Exception):
907 Exception to be thrown when an VM instance settings are incorrect
911 class FloatingIpSettingsError(Exception):
913 Exception to be thrown when an VM instance settings are incorrect
917 class VmInstanceCreationError(Exception):
919 Exception to be thrown when an VM instance cannot be created