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_network_id:
229 network = neutron_utils.get_network_by_id(
230 self.__neutron, router.external_network_id)
237 Destroys the VM instance
240 # Cleanup floating IPs
241 for name, floating_ip in self.__floating_ip_dict.items():
243 logger.info('Deleting Floating IP - ' + floating_ip.ip)
244 neutron_utils.delete_floating_ip(self.__neutron, floating_ip)
245 except Exception as e:
246 logger.error('Error deleting Floating IP - ' + str(e))
247 self.__floating_ip_dict = dict()
250 for volume_rec in self.__vm.volume_ids:
251 cinder = cinder_utils.cinder_client(self._os_creds)
252 volume = cinder_utils.get_volume_by_id(cinder, volume_rec['id'])
255 vm = nova_utils.detach_volume(
256 self._nova, self.__vm, volume, 30)
261 'Timeout waiting to detach volume %s', volume.name)
262 except Exception as e:
263 logger.error('Unexpected error detaching volume %s '
264 'with error %s', volume.name, e)
266 logger.warn('Unable to detach volume with ID - [%s]',
270 for name, port in self.__ports:
271 logger.info('Deleting Port with ID - %s ', port.id)
273 neutron_utils.delete_port(self.__neutron, port)
274 except PortNotFoundClient as e:
275 logger.warning('Unexpected error deleting port - %s', e)
277 self.__ports = list()
283 'Deleting VM instance - ' + self.instance_settings.name)
284 nova_utils.delete_vm_instance(self._nova, self.__vm)
285 except Exception as e:
286 logger.error('Error deleting VM - %s', e)
288 # Block until instance cannot be found or returns the status of
290 logger.info('Checking deletion status')
293 if self.vm_deleted(block=True):
295 'VM has been properly deleted VM with name - %s',
296 self.instance_settings.name)
300 'VM not deleted within the timeout period of %s '
301 'seconds', self.instance_settings.vm_delete_timeout)
302 except Exception as e:
304 'Unexpected error while checking VM instance status - %s',
307 def __query_ports(self, port_settings):
309 Returns the previously configured ports or an empty list if none
311 :param port_settings: A list of PortSetting objects
312 :return: a list of OpenStack port tuples where the first member is the
313 port name and the second is the port object
317 for port_setting in port_settings:
318 port = neutron_utils.get_port(
319 self.__neutron, port_settings=port_setting)
321 ports.append((port_setting.name, port))
325 def __create_ports(self, port_settings):
327 Returns the previously configured ports or creates them if they do not
329 :param port_settings: A list of PortSetting objects
330 :return: a list of OpenStack port tuples where the first member is the
331 port name and the second is the port object
335 for port_setting in port_settings:
336 port = neutron_utils.get_port(
337 self.__neutron, port_settings=port_setting)
339 port = neutron_utils.create_port(
340 self.__neutron, self._os_creds, port_setting)
342 ports.append((port_setting.name, port))
346 def __add_floating_ip(self, floating_ip, port, subnet, timeout=30,
347 poll_interval=POLL_INTERVAL):
349 Returns True when active else False
350 TODO - Make timeout and poll_interval configurable...
355 # Take IP of subnet if there is one configured on which to place
357 for fixed_ip in port.ips:
358 if fixed_ip['subnet_id'] == subnet.id:
359 ip = fixed_ip['ip_address']
362 # Simply take the first
363 ip = port.ips[0]['ip_address']
366 count = timeout / poll_interval
368 logger.debug('Attempting to add floating IP to instance')
370 nova_utils.add_floating_ip_to_server(
371 self._nova, self.__vm, floating_ip, ip)
373 'Added floating IP %s to port IP %s on instance %s',
374 floating_ip.ip, ip, self.instance_settings.name)
376 except Exception as e:
378 'Retry adding floating IP to instance. Last attempt '
379 'failed with - %s', e)
380 time.sleep(poll_interval)
384 raise VmInstanceCreationError(
385 'Unable find IP address on which to place the floating IP')
387 logger.error('Timeout attempting to add the floating IP to instance.')
388 raise VmInstanceCreationError(
389 'Timeout while attempting add floating IP to instance')
391 def get_os_creds(self):
393 Returns the OpenStack credentials used to create these objects
394 :return: the credentials
396 return self._os_creds
398 def get_vm_inst(self):
400 Returns the latest version of this server object from OpenStack
401 :return: Server object
403 return nova_utils.get_server_object_by_id(self._nova, self.__vm.id)
405 def get_console_output(self):
407 Returns the vm console object for parsing logs
408 :return: the console output object
410 return nova_utils.get_server_console_output(self._nova, self.__vm)
412 def get_port_ip(self, port_name, subnet_name=None):
414 Returns the first IP for the port corresponding with the port_name
415 parameter when subnet_name is None else returns the IP address that
416 corresponds to the subnet_name parameter
417 :param port_name: the name of the port from which to return the IP
418 :param subnet_name: the name of the subnet attached to this IP
419 :return: the IP or None if not found
421 port = self.get_port_by_name(port_name)
424 subnet = neutron_utils.get_subnet(
425 self.__neutron, subnet_name=subnet_name)
427 logger.warning('Cannot retrieve port IP as subnet could '
428 'not be located with name - %s',
431 for fixed_ip in port.ips:
432 if fixed_ip['subnet_id'] == subnet.id:
433 return fixed_ip['ip_address']
435 if port.ips and len(port.ips) > 0:
436 return port.ips[0]['ip_address']
439 def get_port_mac(self, port_name):
441 Returns the first IP for the port corresponding with the port_name
443 TODO - Add in the subnet as an additional parameter as a port may have
445 :param port_name: the name of the port from which to return the IP
446 :return: the IP or None if not found
448 port = self.get_port_by_name(port_name)
450 return port.mac_address
453 def get_port_by_name(self, port_name):
455 Retrieves the OpenStack port object by its given name
456 :param port_name: the name of the port
457 :return: the OpenStack port object or None if not exists
459 for key, port in self.__ports:
462 logger.warning('Cannot find port with name - ' + port_name)
465 def get_vm_info(self):
467 Returns a dictionary of a VMs info as returned by OpenStack
470 return nova_utils.get_server_info(self._nova, self.__vm)
472 def config_nics(self):
474 Responsible for configuring NICs on RPM systems where the instance has
475 more than one configured port
476 :return: the value returned by ansible_utils.apply_ansible_playbook()
478 if len(self.__ports) > 1 and len(self.__floating_ip_dict) > 0:
479 if self.vm_active(block=True) and self.vm_ssh_active(block=True):
480 for key, port in self.__ports:
481 port_index = self.__ports.index((key, port))
483 nic_name = 'eth' + repr(port_index)
484 retval = self.__config_nic(
486 self.__get_first_provisioning_floating_ip().ip)
487 logger.info('Configured NIC - %s on VM - %s',
488 nic_name, self.instance_settings.name)
491 def __get_first_provisioning_floating_ip(self):
493 Returns the first floating IP tagged with the Floating IP name if
494 exists else the first one found
497 for floating_ip_setting in self.instance_settings.floating_ip_settings:
498 if floating_ip_setting.provisioning:
499 fip = self.__floating_ip_dict.get(floating_ip_setting.name)
502 elif len(self.__floating_ip_dict) > 0:
503 for key, fip in self.__floating_ip_dict.items():
506 def __config_nic(self, nic_name, port, ip):
508 Although ports/NICs can contain multiple IPs, this code currently only
511 :param nic_name: Name of the interface
512 :param port: The port information containing the expected IP values.
513 :param ip: The IP on which to apply the playbook.
514 :return: the return value from ansible
516 port_ip = port.ips[0]['ip_address']
519 'nic_name': nic_name,
523 if self.image_settings.nic_config_pb_loc and self.keypair_settings:
524 return self.apply_ansible_playbook(
525 self.image_settings.nic_config_pb_loc, variables)
528 'VM %s cannot self configure NICs eth1++. No playbook or '
529 'keypairs found.', self.instance_settings.name)
531 def apply_ansible_playbook(self, pb_file_loc, variables=None,
534 Applies a playbook to a VM
535 :param pb_file_loc: the file location of the playbook to be applied
536 :param variables: a dict() of substitution values required by the
538 :param fip_name: the name of the floating IP to use for applying the
539 playbook (default - will take the first)
540 :return: the return value from ansible
542 return ansible_utils.apply_playbook(
543 pb_file_loc, [self.get_floating_ip(fip_name=fip_name).ip],
544 self.get_image_user(), self.keypair_settings.private_filepath,
545 variables, self._os_creds.proxy_settings)
547 def get_image_user(self):
549 Returns the instance sudo_user if it has been configured in the
550 instance_settings else it returns the image_settings.image_user value
552 if self.instance_settings.sudo_user:
553 return self.instance_settings.sudo_user
555 return self.image_settings.image_user
557 def vm_deleted(self, block=False, poll_interval=POLL_INTERVAL):
559 Returns true when the VM status returns the value of
560 expected_status_code or instance retrieval throws a NotFound exception.
561 :param block: When true, thread will block until active or timeout
562 value in seconds has been exceeded (False)
563 :param poll_interval: The polling interval in seconds
567 return self.__vm_status_check(
568 STATUS_DELETED, block,
569 self.instance_settings.vm_delete_timeout, poll_interval)
570 except NotFound as e:
572 "Instance not found when querying status for %s with message "
573 "%s", STATUS_DELETED, e)
576 def vm_active(self, block=False, poll_interval=POLL_INTERVAL):
578 Returns true when the VM status returns the value of the constant
580 :param block: When true, thread will block until active or timeout
581 value in seconds has been exceeded (False)
582 :param poll_interval: The polling interval in seconds
585 return self.__vm_status_check(STATUS_ACTIVE, block,
586 self.instance_settings.vm_boot_timeout,
589 def __vm_status_check(self, expected_status_code, block, timeout,
592 Returns true when the VM status returns the value of
594 :param expected_status_code: instance status evaluated with this
596 :param block: When true, thread will block until active or timeout
597 value in seconds has been exceeded (False)
598 :param timeout: The timeout value
599 :param poll_interval: The polling interval in seconds
602 # sleep and wait for VM status change
606 return self.__status(expected_status_code)
608 while timeout > time.time() - start:
609 status = self.__status(expected_status_code)
611 logger.info('VM is - ' + expected_status_code)
614 logger.debug('Retry querying VM status in ' + str(
615 poll_interval) + ' seconds')
616 time.sleep(poll_interval)
617 logger.debug('VM status query timeout in ' + str(
618 timeout - (time.time() - start)))
621 'Timeout checking for VM status for ' + expected_status_code)
624 def __status(self, expected_status_code):
626 Returns True when active else False
627 :param expected_status_code: instance status evaluated with this string
632 if expected_status_code == STATUS_DELETED:
637 status = nova_utils.get_server_status(self._nova, self.__vm)
639 logger.warning('Cannot find instance with id - ' + self.__vm.id)
642 if status == 'ERROR':
643 raise VmInstanceCreationError(
644 'Instance had an error during deployment')
646 'Instance status [%s] is - %s', self.instance_settings.name,
648 return status == expected_status_code
650 def vm_ssh_active(self, block=False, poll_interval=POLL_INTERVAL):
652 Returns true when the VM can be accessed via SSH
653 :param block: When true, thread will block until active or timeout
654 value in seconds has been exceeded (False)
655 :param poll_interval: The polling interval
658 # sleep and wait for VM status change
659 logger.info('Checking if VM is active')
661 timeout = self.instance_settings.ssh_connect_timeout
663 if self.vm_active(block=True):
667 start = time.time() - timeout
669 while timeout > time.time() - start:
670 status = self.__ssh_active()
672 logger.info('SSH is active for VM instance')
675 logger.debug('Retry SSH connection in ' + str(
676 poll_interval) + ' seconds')
677 time.sleep(poll_interval)
678 logger.debug('SSH connection timeout in ' + str(
679 timeout - (time.time() - start)))
681 logger.error('Timeout attempting to connect with VM via SSH')
684 def __ssh_active(self):
686 Returns True when can create a SSH session else False
689 if len(self.__floating_ip_dict) > 0:
690 ssh = self.ssh_client()
696 def get_floating_ip(self, fip_name=None):
698 Returns the floating IP object byt name if found, else the first known,
700 :param fip_name: the name of the floating IP to return
701 :return: the SSH client or None
704 if fip_name and self.__floating_ip_dict.get(fip_name):
705 return self.__floating_ip_dict.get(fip_name)
707 return self.__get_first_provisioning_floating_ip()
709 def ssh_client(self, fip_name=None):
711 Returns an SSH client using the name or the first known floating IP if
713 :param fip_name: the name of the floating IP to return
714 :return: the SSH client or None
716 fip = self.get_floating_ip(fip_name)
718 return ansible_utils.ssh_client(
719 self.__get_first_provisioning_floating_ip().ip,
720 self.get_image_user(),
721 self.keypair_settings.private_filepath,
722 proxy_settings=self._os_creds.proxy_settings)
725 'Cannot return an SSH client. No Floating IP configured')
727 def add_security_group(self, security_group):
729 Adds a security group to this VM. Call will block until VM is active.
730 :param security_group: the SNAPS SecurityGroup domain object
731 :return True if successful else False
733 self.vm_active(block=True)
735 if not security_group:
736 logger.warning('Security group object is None, cannot add')
740 nova_utils.add_security_group(self._nova, self.get_vm_inst(),
743 except NotFound as e:
744 logger.warning('Security group not added - ' + str(e))
747 def remove_security_group(self, security_group):
749 Removes a security group to this VM. Call will block until VM is active
750 :param security_group: the OpenStack security group object
751 :return True if successful else False
753 self.vm_active(block=True)
755 if not security_group:
756 logger.warning('Security group object is None, cannot remove')
760 nova_utils.remove_security_group(self._nova, self.get_vm_inst(),
763 except NotFound as e:
764 logger.warning('Security group not removed - ' + str(e))
768 class VmInstanceSettings:
770 Class responsible for holding configuration setting for a VM Instance
773 def __init__(self, **kwargs):
776 :param name: the name of the VM
777 :param flavor: the VM's flavor name
778 :param port_settings: the port configuration settings (required)
779 :param security_group_names: a set of names of the security groups to
781 :param floating_ip_settings: the floating IP configuration settings
782 :param sudo_user: the sudo user of the VM that will override the
783 instance_settings.image_user when trying to
785 :param vm_boot_timeout: the amount of time a thread will sleep waiting
786 for an instance to boot
787 :param vm_delete_timeout: the amount of time a thread will sleep
788 waiting for an instance to be deleted
789 :param ssh_connect_timeout: the amount of time a thread will sleep
790 waiting obtaining an SSH connection to a VM
791 :param availability_zone: the name of the compute server on which to
792 deploy the VM (optional)
793 :param volume_names: a list of the names of the volume to attach
795 :param userdata: the string contents of any optional cloud-init script
796 to execute after the VM has been activated.
797 This value may also contain a dict who's key value
798 must contain the key 'cloud-init_file' which denotes
799 the location of some file containing the cloud-init
802 self.name = kwargs.get('name')
803 self.flavor = kwargs.get('flavor')
804 self.sudo_user = kwargs.get('sudo_user')
805 self.userdata = kwargs.get('userdata')
807 self.port_settings = list()
808 port_settings = kwargs.get('ports')
809 if not port_settings:
810 port_settings = kwargs.get('port_settings')
812 for port_setting in port_settings:
813 if isinstance(port_setting, dict):
814 self.port_settings.append(PortSettings(**port_setting))
815 elif isinstance(port_setting, PortSettings):
816 self.port_settings.append(port_setting)
818 if kwargs.get('security_group_names'):
819 if isinstance(kwargs['security_group_names'], list):
820 self.security_group_names = kwargs['security_group_names']
821 elif isinstance(kwargs['security_group_names'], set):
822 self.security_group_names = kwargs['security_group_names']
823 elif isinstance(kwargs['security_group_names'], str):
824 self.security_group_names = [kwargs['security_group_names']]
826 raise VmInstanceSettingsError(
827 'Invalid data type for security_group_names attribute')
829 self.security_group_names = set()
831 self.floating_ip_settings = list()
832 floating_ip_settings = kwargs.get('floating_ips')
833 if not floating_ip_settings:
834 floating_ip_settings = kwargs.get('floating_ip_settings')
835 if floating_ip_settings:
836 for floating_ip_config in floating_ip_settings:
837 if isinstance(floating_ip_config, FloatingIpSettings):
838 self.floating_ip_settings.append(floating_ip_config)
840 self.floating_ip_settings.append(FloatingIpSettings(
841 **floating_ip_config['floating_ip']))
843 self.vm_boot_timeout = kwargs.get('vm_boot_timeout', 900)
844 self.vm_delete_timeout = kwargs.get('vm_delete_timeout', 300)
845 self.ssh_connect_timeout = kwargs.get('ssh_connect_timeout', 180)
846 self.availability_zone = kwargs.get('availability_zone')
847 self.volume_names = kwargs.get('volume_names')
849 if self.volume_names and not isinstance(self.volume_names, list):
850 raise VmInstanceSettingsError('volume_names must be a list')
852 if not self.name or not self.flavor:
853 raise VmInstanceSettingsError(
854 'Instance configuration requires the attributes: name, flavor')
856 if len(self.port_settings) == 0:
857 raise VmInstanceSettingsError(
858 'Instance configuration requires port settings (aka. NICS)')
861 class FloatingIpSettings:
863 Class responsible for holding configuration settings for a floating IP
866 def __init__(self, **kwargs):
869 :param name: the name of the floating IP
870 :param port_name: the name of the router to the external network
871 :param router_name: the name of the router to the external network
872 :param subnet_name: the name of the subnet on which to attach the
874 :param provisioning: when true, this floating IP can be used for
877 TODO - provisioning flag is a hack as I have only observed a single
878 Floating IPs that actually works on an instance. Multiple floating IPs
879 placed on different subnets from the same port are especially
880 troublesome as you cannot predict which one will actually connect.
881 For now, it is recommended not to setup multiple floating IPs on an
882 instance unless absolutely necessary.
884 self.name = kwargs.get('name')
885 self.port_name = kwargs.get('port_name')
886 self.port_id = kwargs.get('port_id')
887 self.router_name = kwargs.get('router_name')
888 self.subnet_name = kwargs.get('subnet_name')
889 if kwargs.get('provisioning') is not None:
890 self.provisioning = kwargs['provisioning']
892 self.provisioning = True
894 # if not self.name or not self.port_name or not self.router_name:
895 if not self.name or not self.router_name:
896 raise FloatingIpSettingsError(
897 'The attributes name, port_name and router_name are required')
899 if not self.port_name and not self.port_id:
900 raise FloatingIpSettingsError(
901 'The attributes port_name or port_id are required')
904 class VmInstanceSettingsError(Exception):
906 Exception to be thrown when an VM instance settings are incorrect
910 class FloatingIpSettingsError(Exception):
912 Exception to be thrown when an VM instance settings are incorrect
916 class VmInstanceCreationError(Exception):
918 Exception to be thrown when an VM instance cannot be created