package com.sequenceiq.cloudbreak.cloud.aws;
import static com.sequenceiq.cloudbreak.cloud.PlatformParametersConsts.TTL;
import static com.sequenceiq.cloudbreak.cloud.model.CustomImage.customImage;
import static com.sequenceiq.cloudbreak.cloud.model.DiskType.diskType;
import static com.sequenceiq.cloudbreak.cloud.model.Orchestrator.orchestrator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.sequenceiq.cloudbreak.api.model.InstanceProfileStrategy;
import com.sequenceiq.cloudbreak.cloud.PlatformParameters;
import com.sequenceiq.cloudbreak.cloud.model.TagSpecification;
import com.sequenceiq.cloudbreak.cloud.model.AvailabilityZone;
import com.sequenceiq.cloudbreak.cloud.model.AvailabilityZones;
import com.sequenceiq.cloudbreak.cloud.model.ConfigSpecification;
import com.sequenceiq.cloudbreak.cloud.model.CustomImage;
import com.sequenceiq.cloudbreak.cloud.model.DiskType;
import com.sequenceiq.cloudbreak.cloud.model.DiskTypes;
import com.sequenceiq.cloudbreak.cloud.model.PlatformImage;
import com.sequenceiq.cloudbreak.cloud.model.PlatformOrchestrator;
import com.sequenceiq.cloudbreak.cloud.model.Region;
import com.sequenceiq.cloudbreak.cloud.model.Regions;
import com.sequenceiq.cloudbreak.cloud.model.ScriptParams;
import com.sequenceiq.cloudbreak.cloud.model.StackParamValidation;
import com.sequenceiq.cloudbreak.cloud.model.VmSpecification;
import com.sequenceiq.cloudbreak.cloud.model.VmType;
import com.sequenceiq.cloudbreak.cloud.model.VmTypeMeta;
import com.sequenceiq.cloudbreak.cloud.model.VmTypes;
import com.sequenceiq.cloudbreak.cloud.model.VmsSpecification;
import com.sequenceiq.cloudbreak.cloud.model.VolumeParameterConfig;
import com.sequenceiq.cloudbreak.cloud.model.VolumeParameterType;
import com.sequenceiq.cloudbreak.cloud.model.ZoneVmSpecification;
import com.sequenceiq.cloudbreak.cloud.model.ZoneVmSpecifications;
import com.sequenceiq.cloudbreak.cloud.service.CloudbreakResourceReaderService;
import com.sequenceiq.cloudbreak.common.type.OrchestratorConstants;
import com.sequenceiq.cloudbreak.util.FileReaderUtils;
import com.sequenceiq.cloudbreak.util.JsonUtil;
@Service
public class AwsPlatformParameters implements PlatformParameters {
public static final String DEDICATED_INSTANCES = "dedicatedInstances";
public static final String INSTANCE_PROFILE_STRATEGY = "instanceProfileStrategy";
public static final String INSTANCE_PROFILE = "instanceProfile";
private static final Integer START_LABEL = 97;
private static final ScriptParams SCRIPT_PARAMS = new ScriptParams("xvd", START_LABEL);
private static final Logger LOGGER = LoggerFactory.getLogger(AwsPlatformParameters.class);
private static final int DEFAULT_REGION_TYPE_POSITION = 4;
@Value("${cb.aws.vm.parameter.definition.path:}")
private String awsVmParameterDefinitionPath;
@Inject
private CloudbreakResourceReaderService cloudbreakResourceReaderService;
@Inject
private Environment environment;
@Inject
@Qualifier("AwsTagSpecification")
private TagSpecification tagSpecification;
private Map<Region, List<AvailabilityZone>> regions = new HashMap<>();
private Map<AvailabilityZone, List<VmType>> vmTypes = new HashMap<>();
private Map<AvailabilityZone, List<VmType>> sortListOfVmTypes = new HashMap<>();
private Map<AvailabilityZone, VmType> defaultVmTypes = new HashMap<>();
private Region defaultRegion;
private VmType defaultVmType;
@PostConstruct
public void init() {
this.regions = readRegions(resourceDefinition("zone"));
readVmTypes();
this.sortListOfVmTypes = refineList();
this.defaultRegion = nthElement(this.regions.keySet(), DEFAULT_REGION_TYPE_POSITION);
this.defaultVmType = defaultVmTypes.get(regions.get(defaultRegion).get(0));
}
private void readVmTypes() {
Map<String, VmType> vmTypeMap = new TreeMap<>();
String vm = getDefinition(awsVmParameterDefinitionPath, "vm");
String zoneVms = getDefinition(awsVmParameterDefinitionPath, "zone-vm");
try {
VmsSpecification oVms = JsonUtil.readValue(vm, VmsSpecification.class);
for (VmSpecification vmSpecification : oVms.getItems()) {
VmTypeMeta.VmTypeMetaBuilder builder = VmTypeMeta.VmTypeMetaBuilder.builder()
.withCpuAndMemory(vmSpecification.getMetaSpecification().getProperties().getCpu(),
vmSpecification.getMetaSpecification().getProperties().getMemory())
.withPrice(vmSpecification.getMetaSpecification().getProperties().getPrice());
for (ConfigSpecification configSpecification : vmSpecification.getMetaSpecification().getConfigSpecification()) {
addConfig(builder, configSpecification);
}
VmTypeMeta vmTypeMeta = builder.create();
vmTypeMap.put(vmSpecification.getValue(), VmType.vmTypeWithMeta(vmSpecification.getValue(), vmTypeMeta, vmSpecification.getExtended()));
}
ZoneVmSpecifications zoneVmSpecifications = JsonUtil.readValue(zoneVms, ZoneVmSpecifications.class);
Map<String, List<AvailabilityZone>> azmap = regions.entrySet().stream().collect(Collectors.toMap(av -> av.getKey().value(), av -> av.getValue()));
for (ZoneVmSpecification zvs : zoneVmSpecifications.getItems()) {
List<VmType> regionVmTypes = zvs.getVmTypes().stream().filter(vmTypeName -> vmTypeMap.containsKey(vmTypeName))
.map(vmTypeName -> vmTypeMap.get(vmTypeName)).collect(Collectors.toList());
List<AvailabilityZone> azs = azmap.get(zvs.getZone());
if (azs != null) {
azs.forEach(zone -> vmTypes.put(zone, regionVmTypes));
azs.forEach(zone -> defaultVmTypes.put(zone, vmTypeMap.get(zvs.getDefaultVmType())));
}
}
} catch (IOException e) {
LOGGER.error("Cannot initialize platform parameters for aws", e);
}
}
private Map<AvailabilityZone, List<VmType>> refineList() {
Map<AvailabilityZone, List<VmType>> resultMap = new HashMap<>();
for (Map.Entry<AvailabilityZone, List<VmType>> availabilityZoneListEntry : this.vmTypes.entrySet()) {
List<VmType> tmpList = new ArrayList<>();
for (VmType vmType : availabilityZoneListEntry.getValue()) {
if (!vmType.getExtended()) {
tmpList.add(vmType);
}
}
resultMap.put(availabilityZoneListEntry.getKey(), tmpList);
}
return sortMap(resultMap);
}
private void addConfig(VmTypeMeta.VmTypeMetaBuilder builder, ConfigSpecification configSpecification) {
if (configSpecification.getVolumeParameterType().equals(VolumeParameterType.AUTO_ATTACHED.name())) {
builder.withAutoAttachedConfig(volumeParameterConfig(configSpecification));
} else if (configSpecification.getVolumeParameterType().equals(VolumeParameterType.EPHEMERAL.name())) {
builder.withEphemeralConfig(volumeParameterConfig(configSpecification));
} else if (configSpecification.getVolumeParameterType().equals(VolumeParameterType.MAGNETIC.name())) {
builder.withMagneticConfig(volumeParameterConfig(configSpecification));
} else if (configSpecification.getVolumeParameterType().equals(VolumeParameterType.SSD.name())) {
builder.withSsdConfig(volumeParameterConfig(configSpecification));
} else if (configSpecification.getVolumeParameterType().equals(VolumeParameterType.ST1.name())) {
builder.withSt1Config(volumeParameterConfig(configSpecification));
}
}
private VolumeParameterConfig volumeParameterConfig(ConfigSpecification configSpecification) {
return new VolumeParameterConfig(
VolumeParameterType.valueOf(configSpecification.getVolumeParameterType()),
Integer.valueOf(configSpecification.getMinimumSize()),
Integer.valueOf(configSpecification.getMaximumSize()),
Integer.valueOf(configSpecification.getMinimumNumber()),
configSpecification.getMaximumNumberWithLimit());
}
private String getDefinition(String parameter, String type) {
if (Strings.isNullOrEmpty(parameter)) {
return resourceDefinition(type);
} else {
return FileReaderUtils.readFileFromClasspathQuietly(parameter);
}
}
@Override
public ScriptParams scriptParams() {
return SCRIPT_PARAMS;
}
@Override
public DiskTypes diskTypes() {
return new DiskTypes(getDiskTypes(), defaultDiskType(), diskMappings());
}
private Map<String, VolumeParameterType> diskMappings() {
Map<String, VolumeParameterType> map = new HashMap<>();
map.put(AwsDiskType.Standard.value, VolumeParameterType.MAGNETIC);
map.put(AwsDiskType.Gp2.value, VolumeParameterType.SSD);
map.put(AwsDiskType.Ephemeral.value, VolumeParameterType.EPHEMERAL);
map.put(AwsDiskType.St1.value, VolumeParameterType.ST1);
return map;
}
private Collection<DiskType> getDiskTypes() {
Collection<DiskType> disks = Lists.newArrayList();
for (AwsDiskType diskType : AwsDiskType.values()) {
disks.add(diskType(diskType.value));
}
return disks;
}
private DiskType defaultDiskType() {
return diskType(AwsDiskType.Standard.value());
}
@Override
public Regions regions() {
return new Regions(regions.keySet(), defaultRegion);
}
@Override
public AvailabilityZones availabilityZones() {
return new AvailabilityZones(regions);
}
@Override
public String resourceDefinition(String resource) {
return cloudbreakResourceReaderService.resourceDefinition("aws", resource);
}
@Override
public List<StackParamValidation> additionalStackParameters() {
List<StackParamValidation> additionalStackParameterValidations = Lists.newArrayList();
additionalStackParameterValidations.add(new StackParamValidation(TTL, false, String.class, Optional.absent()));
additionalStackParameterValidations.add(new StackParamValidation(DEDICATED_INSTANCES, false, Boolean.class, Optional.absent()));
additionalStackParameterValidations.add(new StackParamValidation(INSTANCE_PROFILE_STRATEGY, false, InstanceProfileStrategy.class,
Optional.absent()));
additionalStackParameterValidations.add(new StackParamValidation(INSTANCE_PROFILE, false, String.class, Optional.absent()));
return additionalStackParameterValidations;
}
@Override
public VmTypes vmTypes(Boolean extended) {
Set<VmType> lists = new TreeSet<>(new Comparator<VmType>() {
@Override
public int compare(VmType o1, VmType o2) {
return o1.value().compareTo(o2.value());
}
});
if (extended) {
for (List<VmType> vmTypeList : vmTypes.values()) {
lists.addAll(vmTypeList);
}
} else {
for (List<VmType> vmTypeList : sortListOfVmTypes.values()) {
lists.addAll(vmTypeList);
}
}
return new VmTypes(lists, defaultVmType);
}
@Override
public Map<AvailabilityZone, VmTypes> vmTypesPerAvailabilityZones(Boolean extended) {
Map<AvailabilityZone, List<VmType>> map = extended ? vmTypes : sortListOfVmTypes;
return map.entrySet().stream().collect(
Collectors.toMap(vmt -> vmt.getKey(), vmt -> new VmTypes(vmt.getValue(), defaultVmTypes.get(vmt.getKey()))));
}
@Override
public PlatformOrchestrator orchestratorParams() {
return new PlatformOrchestrator(Collections.singletonList(orchestrator(OrchestratorConstants.SALT)), orchestrator(OrchestratorConstants.SALT));
}
@Override
public PlatformImage images() {
List<CustomImage> customImages = new ArrayList<>();
for (Map.Entry<Region, List<AvailabilityZone>> regionListEntry : regions.entrySet()) {
String property = environment.getProperty("aws." + regionListEntry.getKey().value());
customImages.add(customImage(regionListEntry.getKey().value(), property));
}
return new PlatformImage(customImages, imageRegex());
}
@Override
public String imageRegex() {
return "^ami-[a-zA-Z0-9]{8}$";
}
@Override
public TagSpecification tagSpecification() {
return tagSpecification;
}
public enum AwsDiskType {
Standard("standard"),
Ephemeral("ephemeral"),
Gp2("gp2"),
St1("st1");
private final String value;
AwsDiskType(String value) {
this.value = value;
}
public String value() {
return value;
}
}
}