blob: 80663ab16a91806769a94691f73e40a00a0f8372 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.openstack.nova.v2_0.compute.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
import javax.annotation.Resource;
import javax.inject.Named;
import com.google.common.collect.Sets;
import org.jclouds.Context;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeMetadataBuilder;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.openstack.neutron.v2.NeutronApi;
import org.jclouds.openstack.neutron.v2.domain.Network;
import org.jclouds.openstack.neutron.v2.domain.Networks;
import org.jclouds.openstack.neutron.v2.domain.Port;
import org.jclouds.openstack.neutron.v2.features.NetworkApi;
import org.jclouds.openstack.neutron.v2.features.PortApi;
import org.jclouds.openstack.nova.v2_0.NovaApi;
import org.jclouds.openstack.nova.v2_0.compute.options.NodeAndNovaTemplateOptions;
import org.jclouds.openstack.nova.v2_0.domain.FloatingIP;
import org.jclouds.openstack.nova.v2_0.domain.FloatingIpForServer;
import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
import org.jclouds.openstack.nova.v2_0.extensions.FloatingIPApi;
import org.jclouds.rest.ApiContext;
import org.jclouds.rest.InsufficientResourcesException;
import org.jclouds.rest.ResourceNotFoundException;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
/**
* A function for adding and allocating an ip to a node
*/
public class AllocateAndAddFloatingIpToNode
implements Function<AtomicReference<NodeAndNovaTemplateOptions>, AtomicReference<NodeMetadata>> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
@Inject(optional = true)
@Named("openstack-neutron")
private Supplier<Context> neutronContextSupplier;
private final Predicate<AtomicReference<NodeMetadata>> nodeRunning;
private final NovaApi novaApi;
private final LoadingCache<RegionAndId, Iterable<? extends FloatingIpForServer>> floatingIpCache;
private final CleanupResources cleanupResources;
@Inject
public AllocateAndAddFloatingIpToNode(
@Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning, NovaApi novaApi,
@Named("FLOATINGIP") LoadingCache<RegionAndId, Iterable<? extends FloatingIpForServer>> floatingIpCache,
CleanupResources cleanupResources) {
this.nodeRunning = checkNotNull(nodeRunning, "nodeRunning");
this.novaApi = checkNotNull(novaApi, "novaApi");
this.floatingIpCache = checkNotNull(floatingIpCache, "floatingIpCache");
this.cleanupResources = checkNotNull(cleanupResources, "cleanupResources");
}
@Override
public AtomicReference<NodeMetadata> apply(AtomicReference<NodeAndNovaTemplateOptions> input) {
checkState(nodeRunning.apply(input.get().getNodeMetadata()), "node never achieved state running %s", input.get().getNodeMetadata());
final NodeMetadata node = input.get().getNodeMetadata().get();
// node's location is a host
String regionId = node.getLocation().getParent().getId();
Optional<Set<String>> poolNames = input.get().getNovaTemplateOptions().get().getFloatingIpPoolNames();
String availabilityZone = getAvailabilityZoneFromTemplateOptionsOrDefault(input, regionId);
if (isNeutronLinked()) {
org.jclouds.openstack.neutron.v2.features.FloatingIPApi neutronFloatingApi = getFloatingIPApi(regionId);
final Optional<Port> optionalPort = getPortApi(regionId).list().concat().firstMatch(new Predicate<Port>() {
@Override
public boolean apply(@Nullable Port input) {
return input.getDeviceId().equals(node.getProviderId());
}
});
if (optionalPort.isPresent()) {
Optional<org.jclouds.openstack.neutron.v2.domain.FloatingIP> floatingIPOptional = tryFindExistingFloatingIp(neutronFloatingApi, availabilityZone);
org.jclouds.openstack.neutron.v2.domain.FloatingIP floatingIP;
if (floatingIPOptional.isPresent()) {
floatingIP = floatingIPOptional.get();
} else {
floatingIP = createFloatingIpUsingNeutron(neutronFloatingApi, node, poolNames, availabilityZone);
}
org.jclouds.openstack.neutron.v2.domain.FloatingIP ip = neutronFloatingApi.update(floatingIP.getId(),
org.jclouds.openstack.neutron.v2.domain.FloatingIP.UpdateFloatingIP
.updateBuilder()
.portId(optionalPort.get().getId())
.build());
input.get().getNodeMetadata().set(NodeMetadataBuilder.fromNodeMetadata(node).publicAddresses(ImmutableSet.of(ip.getFloatingIpAddress())).build());
} else {
logger.error("Node %s doesn't have a port to attach a floating IP", node);
throw new IllegalStateException("Missing required port in node: " + node);
}
} else { // try nova
FloatingIPApi floatingIpApi = novaApi.getFloatingIPApi(regionId).get();
Optional<FloatingIP> ip = allocateFloatingIPForNodeOnNova(floatingIpApi, poolNames, node.getId());
if (!ip.isPresent()) {
cleanupResources.apply(node);
throw new InsufficientResourcesException("Failed to allocate a FloatingIP for node(" + node.getId() + ")");
}
logger.debug(">> adding floatingIp(%s) to node(%s)", ip.get().getIp(), node.getId());
floatingIpApi.addToServer(ip.get().getIp(), node.getProviderId());
input.get().getNodeMetadata().set(NodeMetadataBuilder.fromNodeMetadata(node).publicAddresses(ImmutableSet.of(ip.get().getIp())).build());
floatingIpCache.asMap().put(RegionAndId.fromSlashEncoded(node.getId()), ImmutableList.of(FloatingIpForServer.create(RegionAndId.fromSlashEncoded(node.getId()), ip.get().getId(), ip.get().getIp())));
}
return input.get().getNodeMetadata();
}
private String getAvailabilityZoneFromTemplateOptionsOrDefault(AtomicReference<NodeAndNovaTemplateOptions> input, String regionId) {
return MoreObjects.firstNonNull(input.get().getNovaTemplateOptions().get().getAvailabilityZone(),
Iterables.get(novaApi.getAvailabilityZoneApi(regionId).get().listAvailabilityZones(), 0).getName());
}
/**
* Allocates a FloatingIP for a given Node
*
* @param floatingIpApi
* FloatingIPApi to create or query for a valid FloatingIP
* @param poolNames
* optional set of pool names from which we will attempt to allocate
* an IP from. Most cases this is null
* @param nodeID
* optional id of the Node we are trying to allocate a FloatingIP for.
* Used here only for logging purposes
* @return Optional<FloatingIP>
*/
private synchronized Optional<FloatingIP> allocateFloatingIPForNodeOnNova(FloatingIPApi floatingIpApi,
Optional<Set<String>> poolNames, String nodeID) {
FloatingIP ip;
// 1.) Attempt to allocate from optionally passed poolNames
if (poolNames.isPresent()) {
for (String poolName : poolNames.get()) {
try {
logger.debug(">> allocating floating IP from pool %s for node(%s)", poolName, nodeID);
ip = floatingIpApi.allocateFromPool(poolName);
return Optional.of(ip);
} catch (ResourceNotFoundException ex) {
logger.trace("<< [%s] failed to allocate floating IP from pool %s for node(%s)", ex.getMessage(),
poolName, nodeID);
} catch (InsufficientResourcesException ire) {
logger.trace("<< [%s] failed to allocate floating IP from pool %s for node(%s)", ire.getMessage(),
poolName, nodeID);
}
}
}
// 2.) Attempt to allocate, if necessary, via 'create()' call
try {
logger.debug(">> creating floating IP for node(%s)", nodeID);
ip = floatingIpApi.create();
return Optional.of(ip);
} catch (ResourceNotFoundException ex) {
logger.trace("<< [%s] failed to create floating IP for node(%s)", ex.getMessage(), nodeID);
} catch (InsufficientResourcesException ire) {
logger.trace("<< [%s] failed to create floating IP for node(%s)", ire.getMessage(), nodeID);
}
// 3.) If no IP was found make final attempt by searching through list of
// available IP's
logger.trace(">> searching for existing, unassigned floating IP for node(%s)", nodeID);
List<FloatingIP> unassignedIps = Lists
.newArrayList(Iterables.filter(floatingIpApi.list(), new Predicate<FloatingIP>() {
@Override
public boolean apply(FloatingIP arg0) {
return arg0.getFixedIp() == null;
}
}));
// try to prevent multiple parallel launches from choosing the same ip.
if (unassignedIps.isEmpty()) {
return Optional.absent();
}
Collections.shuffle(unassignedIps);
ip = Iterables.getLast(unassignedIps);
return Optional.fromNullable(ip);
}
private Optional<org.jclouds.openstack.neutron.v2.domain.FloatingIP> tryFindExistingFloatingIp(org.jclouds.openstack.neutron.v2.features.FloatingIPApi neutronFloatingApi, final String availabilityZone) {
Optional<org.jclouds.openstack.neutron.v2.domain.FloatingIP> floatingIPOptional = neutronFloatingApi.list().concat().firstMatch(new Predicate<org.jclouds.openstack.neutron.v2.domain.FloatingIP>() {
@Override
public boolean apply(@Nullable org.jclouds.openstack.neutron.v2.domain.FloatingIP input) {
return input.getPortId() == null && input.getAvailabilityZone().equals(availabilityZone);
}
});
return floatingIPOptional;
}
private org.jclouds.openstack.neutron.v2.domain.FloatingIP createFloatingIpUsingNeutron(org.jclouds.openstack.neutron.v2.features.FloatingIPApi neutronFloatingApi,
NodeMetadata node, Optional<Set<String>> poolNames, final String availabilityZone) {
String regionId = node.getLocation().getParent().getId();
List<Network> networks = getSuitableNetworks(regionId, availabilityZone, poolNames.or(Sets.<String>newHashSet()));
org.jclouds.openstack.neutron.v2.domain.FloatingIP floatingIP = null;
for (Network network : networks) {
try {
logger.debug(">> allocating floating IP from network %s for node(%s)", network, node);
org.jclouds.openstack.neutron.v2.domain.FloatingIP createFloatingIP = org.jclouds.openstack.neutron.v2.domain.FloatingIP.CreateFloatingIP.createBuilder(network.getId()).availabilityZone(network.getAvailabilityZone()).build();
floatingIP = neutronFloatingApi.create((org.jclouds.openstack.neutron.v2.domain.FloatingIP.CreateFloatingIP) createFloatingIP);
logger.debug(">> allocated floating IP(%s) from network(%s) for node(%s)", floatingIP, network, node);
floatingIpCache.asMap().put(RegionAndId.fromSlashEncoded(node.getId()), ImmutableList.of(FloatingIpForServer.create(RegionAndId.fromSlashEncoded(node.getId()), floatingIP.getId(), floatingIP.getFloatingIpAddress())));
return floatingIP;
} catch (Exception ex) {
logger.trace("<< [%s] failed to allocate a floating IP from network %s for node(%s)", ex.getMessage(), network, node);
}
}
throw new IllegalStateException("Failed to allocate a floating IP for node " + node + ".\n" +
"Failed to find suitable external networks or to allocate from poolNames specified: "
+ Iterables.toString(poolNames.get()));
}
/**
* Get all suitable networks to allocate a floating ip
*
* It will prefer networks specified using the poolNames first and then the external networks in the given availability zone
*/
private List<Network> getSuitableNetworks(String regionId, final String availabilityZone, final Set<String> poolNames) {
List<Network> allNetworks = getNetworkApi(regionId).list().concat().toList();
Iterable<Network> externalNetworks = Iterables.filter(allNetworks, Networks.Predicates.externalNetworks(availabilityZone));
Iterable<Network> networksFromPoolName = Iterables.filter(allNetworks, Networks.Predicates.namedNetworks(poolNames));
return Lists.newArrayList(Iterables.concat(networksFromPoolName, externalNetworks));
}
private boolean isNeutronLinked() {
return neutronContextSupplier != null && neutronContextSupplier.get() != null;
}
private org.jclouds.openstack.neutron.v2.features.FloatingIPApi getFloatingIPApi(String region) {
return ((ApiContext<NeutronApi>) neutronContextSupplier.get()).getApi().getFloatingIPApi(region);
}
private PortApi getPortApi(String regionId) {
return ((ApiContext<NeutronApi>) neutronContextSupplier.get()).getApi().getPortApi(regionId);
}
private NetworkApi getNetworkApi(String regionId) {
return ((ApiContext<NeutronApi>) neutronContextSupplier.get()).getApi().getNetworkApi(regionId);
}
@Override
public String toString() {
return MoreObjects.toStringHelper("AllocateAndAddFloatingIpToNode").toString();
}
}