/**
   * Create a new Floating IP from the given floating network and assign it to the given port
   *
   * @param port the port to which a Floating IP to be assigned
   * @param floatingNetworkUuid the network uuid of the floating network from which the Floating IP
   *     should be created
   * @return the newly created/assigned {@link FloatingIP}
   */
  private FloatingIP createAndAssignFloatingIP(Port port, String floatingNetworkUuid) {

    assertNotNull(port, "Cannot create floating IP. Invalid port. Port cannot be null");
    assertNotNullAndNotEmpty(
        floatingNetworkUuid,
        "Cannot create floating IP. Invalid floating network uuid. "
            + "Floating network uuid cannot be null");

    if (log.isDebugEnabled()) {
      String msg =
          String.format(
              "Trying to create a floating IP from network %s to assign to the port %s",
              floatingNetworkUuid, port.getId());
      log.debug(msg);
    }

    FloatingIP.CreateFloatingIP createFip;
    try {
      createFip = FloatingIP.createBuilder(floatingNetworkUuid).portId(port.getId()).build();
    } catch (Exception e) {
      String msg =
          String.format(
              "Error while getting floating IP builder for the external network %s and port %s",
              floatingNetworkUuid, port.toString());
      log.error(msg, e);
      throw new CloudControllerException(msg, e);
    }

    FloatingIP floatingIP = null;
    try {
      floatingIP = floatingIPApi.create(createFip);
    } catch (Exception e) {
      String msg =
          String.format(
              "Error while creating floating IP for the port %s, from floating network %s",
              port.toString(), floatingNetworkUuid);
      log.error(msg, e);
      throw new CloudControllerException(msg, e);
    }

    String msg =
        String.format("Unable to create a floating IP from network %s", floatingNetworkUuid);
    assertNotNull(floatingIP, msg);

    return floatingIP;
  }
  /**
   * Assign the given Floating IP to the given port.
   *
   * @param floatingIP the Floating IP to be assigned
   * @param portTobeAssigned the port to which the given Floating IP to be assigned
   * @return the updated {@link FloatingIP}
   */
  private FloatingIP updateFloatingIP(FloatingIP floatingIP, Port portTobeAssigned) {

    assertNotNull(floatingIP, "Cannot update floating IP. Given floating IP is null");
    String portNotNullMsg =
        String.format(
            "Cannot update floating IP %s. Given port is null", floatingIP.getFloatingIpAddress());
    assertNotNull(portTobeAssigned, portNotNullMsg);

    FloatingIP updatedFloatingIP = null;
    if (log.isDebugEnabled()) {
      String msg =
          String.format(
              "Trying to assign existing floating IP %s to the port %s",
              floatingIP.getFloatingIpAddress(), portTobeAssigned.getId());
      log.debug(msg);
    }

    try {
      updatedFloatingIP =
          floatingIPApi.update(
              floatingIP.getId(),
              FloatingIP.UpdateFloatingIP.updateBuilder()
                  .portId(portTobeAssigned.getId())
                  .fixedIpAddress(portTobeAssigned.getFixedIps().iterator().next().getIpAddress())
                  .build());
    } catch (Exception e) {
      String msg =
          String.format(
              "Error while trying to assign existing floating IP %s to the port %s",
              floatingIP.toString(), portTobeAssigned.toString());
      log.error(msg, e);
      throw new CloudControllerException(msg, e);
    }

    String updatedFloatingIPNullMessage =
        String.format(
            "Unable to assign existing floating IP %s " + "to the port %s",
            floatingIP.toString(), portTobeAssigned.toString());
    assertNotNull(updatedFloatingIP, updatedFloatingIPNullMessage);

    if (log.isDebugEnabled()) {
      String msg = String.format("Successfully updated the floating IP %s", floatingIP.toString());
      log.debug(msg);
    }
    return updatedFloatingIP;
  }
  @Override
  public List<String> associateAddresses(NodeMetadata node) {

    assertNotNull(node, "Node cannot be null");

    if (null == neutronApi || null == portApi || null == floatingIPApi) {
      buildNeutronApi();
    }

    // internal network uuid to floating networks map, as defined in cartridge definition
    Map<String, List<FloatingNetwork>> networkUuidToFloatingNetworksMap =
        getNetworkUuidToFloatingNetworksMap(iaasProvider.getNetworkInterfaces());

    // private IP to floating networks map, as defined in cartridge definition
    Map<String, List<FloatingNetwork>> fixedIPToFloatingNetworksMap =
        getFixedIPToFloatingNetworksMap(iaasProvider.getNetworkInterfaces());

    // list of IPs allocated to this node
    List<String> associatedFloatingIPs = new ArrayList<String>();

    // wait until node gets private IPs
    while (node.getPrivateAddresses() == null) {
      CloudControllerUtil.sleep(1000);
    }

    // loop through all the fixed IPs of this node
    // and see whether we need to assign floating IP to each according to the cartridge deployment
    for (String privateIPOfTheNode : node.getPrivateAddresses()) {
      Port portOfTheFixedIP = getPortByFixedIP(privateIPOfTheNode);
      if (null == portOfTheFixedIP) {
        // we can't assign floating IP if port is null
        // it can't happen, a fixed/private IP can't live without a port
        // but doing a null check to be on the safe side
        if (log.isDebugEnabled()) {
          String msg = String.format("Port not found for fixed IP %s", privateIPOfTheNode);
          log.debug(msg);
        }
        continue;
      }
      // get list of floating networks associated with each network interfaces (refer cartridge
      // definition)
      List<FloatingNetwork> floatingNetworks =
          networkUuidToFloatingNetworksMap.get(portOfTheFixedIP.getNetworkId());
      // if no floating networks is defined for a network interface, no need to assign any floating
      // IPs, skip the current iteration
      if (null == floatingNetworks || floatingNetworks.isEmpty()) {
        // since no floating networks found in networkUuidToFloatingNetworksMap,
        // we will search in fixedIPToFloatingNetworksMap
        floatingNetworks = fixedIPToFloatingNetworksMap.get(privateIPOfTheNode);
        if (null == floatingNetworks || floatingNetworks.isEmpty()) {
          if (log.isDebugEnabled()) {
            String msg =
                String.format(
                    "No floating networks defined for the network interface %s",
                    portOfTheFixedIP.getNetworkId());
            log.debug(msg);
          }
        }
        continue;
      }
      // if floating networks defined for a network interface, assign one floating IP from each
      // floating network
      for (FloatingNetwork floatingNetwork : floatingNetworks) {
        FloatingIP allocatedFloatingIP = null;
        if (floatingNetwork.getNetworkUuid() != null
            && !floatingNetwork.getNetworkUuid().isEmpty()) {
          allocatedFloatingIP =
              assignFloatingIP(portOfTheFixedIP, floatingNetwork.getNetworkUuid());
        } else if (floatingNetwork.getFloatingIP() != null
            && !floatingNetwork.getFloatingIP().isEmpty()) {
          allocatedFloatingIP =
              assignPredefinedFloatingIP(portOfTheFixedIP, floatingNetwork.getFloatingIP());
        } else {
          String msg =
              String.format(
                  "Neither floating network uuid or floating IP defined for the floating network %s",
                  floatingNetwork.getName());
          log.error(msg);
          throw new CloudControllerException(msg);
        }

        String allocatedFloatingIPNullMsg =
            String.format(
                "Error occured while assigning floating IP. "
                    + "Please check whether the floating network %s can be reached from the fixed IP range",
                floatingNetwork.getNetworkUuid());
        assertNotNull(allocatedFloatingIP, allocatedFloatingIPNullMsg);

        String allocatedFloatingIPAddressNullOrEmptyMsg =
            String.format(
                "Error occured while assigning floating IP. "
                    + "Please check whether the floating network %s can be reached from the fixed IP range",
                floatingNetwork.getNetworkUuid());
        assertNotNullAndNotEmpty(
            allocatedFloatingIP.getFloatingIpAddress(), allocatedFloatingIPAddressNullOrEmptyMsg);

        associatedFloatingIPs.add(allocatedFloatingIP.getFloatingIpAddress());
      }
    }
    return associatedFloatingIPs;
  }