/*
* 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.apache.brooklyn.location.multi;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.location.LocationSpec;
import org.apache.brooklyn.api.location.MachineLocation;
import org.apache.brooklyn.api.location.MachineProvisioningLocation;
import org.apache.brooklyn.api.location.NoMachinesAvailableException;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.location.AbstractLocation;
import org.apache.brooklyn.core.location.cloud.AbstractAvailabilityZoneExtension;
import org.apache.brooklyn.core.location.cloud.AvailabilityZoneExtension;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.flags.SetFromFlag;
import org.apache.brooklyn.util.exceptions.CompoundRuntimeException;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.text.Strings;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
/** A location which consists of multiple locations stitched together to form availability zones.
* The first location will be used by default, but if an {@link AvailabilityZoneExtension}-aware entity
* is used, it may stripe across each of the locations. See notes at {@link AvailabilityZoneExtension}. */
public class MultiLocation<T extends MachineLocation> extends AbstractLocation implements MachineProvisioningLocation<T> {
@SuppressWarnings("serial")
@SetFromFlag("subLocations")
public static final ConfigKey<List<MachineProvisioningLocation<?>>> SUB_LOCATIONS = ConfigKeys.newConfigKey(
new TypeToken<List<MachineProvisioningLocation<?>>>() {},
"subLocations",
"The sub-machines that this location can delegate to");
@Override
public void init() {
super.init();
List<MachineProvisioningLocation<?>> subLocs = getSubLocations();
checkState(subLocs.size() >= 1, "sub-locations must not be empty");
AvailabilityZoneExtension azExtension = new AvailabilityZoneExtensionImpl(getManagementContext(), subLocs);
addExtension(AvailabilityZoneExtension.class, azExtension);
}
public T obtain() throws NoMachinesAvailableException {
return obtain(MutableMap.of());
}
/** finds (creates) and returns a {@link MachineLocation};
* this always tries the first sub-location, moving on the second and subsequent if the first throws {@link NoMachinesAvailableException}.
* (if you want striping across locations, see notes in {@link AvailabilityZoneExtension}.) */
@SuppressWarnings("unchecked")
@Override
public T obtain(Map<?, ?> flags) throws NoMachinesAvailableException {
List<MachineProvisioningLocation<?>> sublocsList = getSubLocations();
Iterator<MachineProvisioningLocation<?>> sublocs = sublocsList.iterator();
List<NoMachinesAvailableException> errors = MutableList.of();
while (sublocs.hasNext()) {
try {
return (T) sublocs.next().obtain(flags);
} catch (NoMachinesAvailableException e) {
errors.add(e);
}
}
Exception wrapped;
String msg;
if (errors.size()>1) {
wrapped = new CompoundRuntimeException(errors.size()+" sublocation exceptions, including: "+
Exceptions.collapseText(errors.get(0)), errors);
msg = Exceptions.collapseText(wrapped);
} else if (errors.size()==1) {
wrapped = errors.get(0);
msg = wrapped.getMessage();
if (Strings.isBlank(msg)) msg = wrapped.toString();
} else {
msg = "no sub-locations set for this multi-location";
wrapped = null;
}
throw new NoMachinesAvailableException("No machines available in any of the "+sublocsList.size()+" location"+Strings.s(sublocsList.size())+
" configured here: "+msg, wrapped);
}
public List<MachineProvisioningLocation<?>> getSubLocations() {
return getRequiredConfig(SUB_LOCATIONS);
}
@SuppressWarnings("unchecked")
@Override
public MachineProvisioningLocation<T> newSubLocation(Map<?, ?> newFlags) {
// TODO shouldn't have to copy config bag as it should be inherited (but currently it is not used inherited everywhere; just most places)
return getManagementContext().getLocationManager().createLocation(LocationSpec.create(getClass())
.parent(this)
.configure(config().getLocalBag().getAllConfig()) // FIXME Should this just be inherited?
.configure(newFlags));
}
@SuppressWarnings("unchecked")
@Override
public void release(T machine) {
((MachineProvisioningLocation<T>)machine.getParent()).release(machine);
}
@Override
public Map<String, Object> getProvisioningFlags(Collection<String> tags) {
return Maps.<String,Object>newLinkedHashMap();
}
@SuppressWarnings("unchecked")
protected MachineProvisioningLocation<T> firstSubLoc() {
return (MachineProvisioningLocation<T>) Iterables.get(getSubLocations(), 0);
}
protected <K> K getRequiredConfig(ConfigKey<K> key) {
return checkNotNull(getConfig(key), key.getName());
}
public static class AvailabilityZoneExtensionImpl extends AbstractAvailabilityZoneExtension implements AvailabilityZoneExtension {
private final List<MachineProvisioningLocation<?>> subLocations;
public AvailabilityZoneExtensionImpl(ManagementContext managementContext, List<MachineProvisioningLocation<?>> subLocations) {
super(managementContext);
this.subLocations = ImmutableList.copyOf(subLocations);
}
@Override
protected List<Location> doGetAllSubLocations() {
return ImmutableList.<Location>copyOf(subLocations);
}
@Override
protected boolean isNameMatch(Location loc, Predicate<? super String> namePredicate) {
return namePredicate.apply(loc.getDisplayName());
}
}
}