+ time.sleep(5)
+ elapsed_time += 5
+
+ return elapsed_time
+
+ def _wait_for_existing_volumes_availability(
+ self, elapsed_time: int, existing_vim_volumes: list
+ ) -> Optional[int]:
+ """Wait till existing volumes become available.
+
+ Args:
+ elapsed_time (int): Passed time while waiting
+ existing_vim_volumes (list): Existing volume details
+
+ Returns:
+ elapsed_time (int): Time spent while waiting
+
+ """
+
+ while elapsed_time < volume_timeout:
+ for volume in existing_vim_volumes:
+ v = self.cinder.volumes.get(volume["id"])
+ if v.volume_type == "multiattach" and v.status == "in-use":
+ return elapsed_time
+ elif v.status != "available":
+ break
+ else: # all ready: break from while
+ break
+
+ time.sleep(5)
+ elapsed_time += 5
+
+ return elapsed_time
+
+ def _prepare_disk_for_vminstance(
+ self,
+ name: str,
+ existing_vim_volumes: list,
+ created_items: dict,
+ vm_av_zone: list,
+ block_device_mapping: dict,
+ disk_list: list = None,
+ ) -> None:
+ """Prepare all volumes for new VM instance.
+
+ Args:
+ name (str): Name of Instance
+ existing_vim_volumes (list): List of existing volumes
+ created_items (dict): All created items belongs to VM
+ vm_av_zone (list): VM availability zone
+ block_device_mapping (dict): Block devices to be attached to VM
+ disk_list (list): List of disks
+
+ """
+ # Create additional volumes in case these are present in disk_list
+ base_disk_index = ord("b")
+ boot_volume_id = None
+ elapsed_time = 0
+ for disk in disk_list:
+ if "image_id" in disk:
+ # Root persistent volume
+ base_disk_index = ord("a")
+ boot_volume_id = self._prepare_persistent_root_volumes(
+ name=name,
+ vm_av_zone=vm_av_zone,
+ disk=disk,
+ base_disk_index=base_disk_index,
+ block_device_mapping=block_device_mapping,
+ existing_vim_volumes=existing_vim_volumes,
+ created_items=created_items,
+ )
+ elif disk.get("multiattach"):
+ self._prepare_shared_volumes(
+ name=name,
+ disk=disk,
+ base_disk_index=base_disk_index,
+ block_device_mapping=block_device_mapping,
+ existing_vim_volumes=existing_vim_volumes,
+ created_items=created_items,
+ )
+ else:
+ # Non-root persistent volume
+ self._prepare_non_root_persistent_volumes(
+ name=name,
+ disk=disk,
+ vm_av_zone=vm_av_zone,
+ block_device_mapping=block_device_mapping,
+ base_disk_index=base_disk_index,
+ existing_vim_volumes=existing_vim_volumes,
+ created_items=created_items,
+ )
+ base_disk_index += 1
+
+ # Wait until created volumes are with status available
+ elapsed_time = self._wait_for_created_volumes_availability(
+ elapsed_time, created_items
+ )
+ # Wait until existing volumes in vim are with status available
+ elapsed_time = self._wait_for_existing_volumes_availability(
+ elapsed_time, existing_vim_volumes
+ )
+ # If we exceeded the timeout rollback
+ if elapsed_time >= volume_timeout:
+ raise vimconn.VimConnException(
+ "Timeout creating volumes for instance " + name,
+ http_code=vimconn.HTTP_Request_Timeout,
+ )
+ if boot_volume_id:
+ self.cinder.volumes.set_bootable(boot_volume_id, True)
+
+ def _find_the_external_network_for_floating_ip(self):
+ """Get the external network ip in order to create floating IP.
+
+ Returns:
+ pool_id (str): External network pool ID
+
+ """
+
+ # Find the external network
+ external_nets = list()
+
+ for net in self.neutron.list_networks()["networks"]:
+ if net["router:external"]:
+ external_nets.append(net)
+
+ if len(external_nets) == 0:
+ raise vimconn.VimConnException(
+ "Cannot create floating_ip automatically since "
+ "no external network is present",
+ http_code=vimconn.HTTP_Conflict,
+ )
+
+ if len(external_nets) > 1:
+ raise vimconn.VimConnException(
+ "Cannot create floating_ip automatically since "
+ "multiple external networks are present",
+ http_code=vimconn.HTTP_Conflict,
+ )
+
+ # Pool ID
+ return external_nets[0].get("id")
+
+ def _neutron_create_float_ip(self, param: dict, created_items: dict) -> None:
+ """Trigger neutron to create a new floating IP using external network ID.
+
+ Args:
+ param (dict): Input parameters to create a floating IP
+ created_items (dict): All created items belongs to new VM instance
+
+ Raises:
+
+ VimConnException
+ """
+ try:
+ self.logger.debug("Creating floating IP")
+ new_floating_ip = self.neutron.create_floatingip(param)
+ free_floating_ip = new_floating_ip["floatingip"]["id"]
+ created_items["floating_ip:" + str(free_floating_ip)] = True
+
+ except Exception as e:
+ raise vimconn.VimConnException(
+ type(e).__name__ + ": Cannot create new floating_ip " + str(e),
+ http_code=vimconn.HTTP_Conflict,
+ )
+
+ def _create_floating_ip(
+ self, floating_network: dict, server: object, created_items: dict
+ ) -> None:
+ """Get the available Pool ID and create a new floating IP.
+
+ Args:
+ floating_network (dict): Dict including external network ID
+ server (object): Server object
+ created_items (dict): All created items belongs to new VM instance
+
+ """
+
+ # Pool_id is available
+ if (
+ isinstance(floating_network["floating_ip"], str)
+ and floating_network["floating_ip"].lower() != "true"
+ ):
+ pool_id = floating_network["floating_ip"]
+
+ # Find the Pool_id
+ else:
+ pool_id = self._find_the_external_network_for_floating_ip()
+
+ param = {
+ "floatingip": {
+ "floating_network_id": pool_id,
+ "tenant_id": server.tenant_id,
+ }
+ }
+
+ self._neutron_create_float_ip(param, created_items)
+
+ def _find_floating_ip(
+ self,
+ server: object,
+ floating_ips: list,
+ floating_network: dict,
+ ) -> Optional[str]:
+ """Find the available free floating IPs if there are.
+
+ Args:
+ server (object): Server object
+ floating_ips (list): List of floating IPs
+ floating_network (dict): Details of floating network such as ID
+
+ Returns:
+ free_floating_ip (str): Free floating ip address
+
+ """
+ for fip in floating_ips:
+ if fip.get("port_id") or fip.get("tenant_id") != server.tenant_id:
+ continue
+
+ if isinstance(floating_network["floating_ip"], str):
+ if fip.get("floating_network_id") != floating_network["floating_ip"]:
+ continue
+
+ return fip["id"]
+
+ def _assign_floating_ip(
+ self, free_floating_ip: str, floating_network: dict
+ ) -> Dict:
+ """Assign the free floating ip address to port.
+
+ Args:
+ free_floating_ip (str): Floating IP to be assigned
+ floating_network (dict): ID of floating network
+
+ Returns:
+ fip (dict) (dict): Floating ip details
+
+ """
+ # The vim_id key contains the neutron.port_id
+ self.neutron.update_floatingip(
+ free_floating_ip,
+ {"floatingip": {"port_id": floating_network["vim_id"]}},
+ )
+ # For race condition ensure not re-assigned to other VM after 5 seconds
+ time.sleep(5)
+
+ return self.neutron.show_floatingip(free_floating_ip)
+
+ def _get_free_floating_ip(
+ self, server: object, floating_network: dict
+ ) -> Optional[str]:
+ """Get the free floating IP address.
+
+ Args:
+ server (object): Server Object
+ floating_network (dict): Floating network details
+
+ Returns:
+ free_floating_ip (str): Free floating ip addr
+
+ """
+
+ floating_ips = self.neutron.list_floatingips().get("floatingips", ())
+
+ # Randomize
+ random.shuffle(floating_ips)
+
+ return self._find_floating_ip(server, floating_ips, floating_network)
+
+ def _prepare_external_network_for_vminstance(
+ self,
+ external_network: list,
+ server: object,
+ created_items: dict,
+ vm_start_time: float,
+ ) -> None:
+ """Assign floating IP address for VM instance.
+
+ Args:
+ external_network (list): ID of External network
+ server (object): Server Object
+ created_items (dict): All created items belongs to new VM instance
+ vm_start_time (float): Time as a floating point number expressed in seconds since the epoch, in UTC
+
+ Raises:
+ VimConnException
+
+ """
+ for floating_network in external_network:
+ try:
+ assigned = False
+ floating_ip_retries = 3
+ # In case of RO in HA there can be conflicts, two RO trying to assign same floating IP, so retry
+ # several times
+ while not assigned:
+ free_floating_ip = self._get_free_floating_ip(
+ server, floating_network
+ )
+
+ if not free_floating_ip:
+ self._create_floating_ip(
+ floating_network, server, created_items
+ )
+
+ try:
+ # For race condition ensure not already assigned
+ fip = self.neutron.show_floatingip(free_floating_ip)
+
+ if fip["floatingip"].get("port_id"):
+ continue
+
+ # Assign floating ip
+ fip = self._assign_floating_ip(
+ free_floating_ip, floating_network
+ )
+
+ if fip["floatingip"]["port_id"] != floating_network["vim_id"]:
+ self.logger.warning(
+ "floating_ip {} re-assigned to other port".format(
+ free_floating_ip
+ )
+ )
+ continue
+
+ self.logger.debug(
+ "Assigned floating_ip {} to VM {}".format(
+ free_floating_ip, server.id
+ )
+ )
+
+ assigned = True
+
+ except Exception as e:
+ # Openstack need some time after VM creation to assign an IP. So retry if fails
+ vm_status = self.nova.servers.get(server.id).status
+
+ if vm_status not in ("ACTIVE", "ERROR"):
+ if time.time() - vm_start_time < server_timeout:
+ time.sleep(5)