package com.sequenceiq.cloudbreak.cloud.gcp; 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.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.PostConstruct; import javax.inject.Inject; 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.api.client.util.Lists; import com.google.common.base.Optional; import com.google.common.base.Strings; import com.sequenceiq.cloudbreak.cloud.PlatformParameters; import com.sequenceiq.cloudbreak.cloud.model.TagSpecification; import com.sequenceiq.cloudbreak.cloud.gcp.model.MachineDefinitionView; import com.sequenceiq.cloudbreak.cloud.gcp.model.MachineDefinitionWrapper; import com.sequenceiq.cloudbreak.cloud.gcp.model.ZoneDefinitionView; import com.sequenceiq.cloudbreak.cloud.gcp.model.ZoneDefinitionWrapper; import com.sequenceiq.cloudbreak.cloud.model.AvailabilityZone; import com.sequenceiq.cloudbreak.cloud.model.AvailabilityZones; 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.StringTypesCompare; 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.VolumeParameterType; 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 GcpPlatformParameters implements PlatformParameters { private static final int DEFAULT_REGION_TYPE_POSITION = 2; private static final int DEFAULT_VM_TYPE_POSITION = 14; private static final float THOUSAND = 1000.0f; private static final int TEN = 10; private static final Integer START_LABEL = 97; private static final ScriptParams SCRIPT_PARAMS = new ScriptParams("sd", START_LABEL); @Value("${cb.gcp.vm.parameter.definition.path:}") private String gcpVmParameterDefinitionPath; @Value("${cb.gcp.zone.parameter.definition.path:}") private String gcpZoneParameterDefinitionPath; @Inject private CloudbreakResourceReaderService cloudbreakResourceReaderService; @Inject private Environment environment; @Inject @Qualifier("GcpTagSpecification") private TagSpecification tagSpecification; private Map<Region, List<AvailabilityZone>> regions = new HashMap<>(); private Map<AvailabilityZone, List<VmType>> vmTypes = new HashMap<>(); private Region defaultRegion; private VmType defaultVmType; private Map<AvailabilityZone, VmType> defaultVmTypes = new HashMap<>(); @PostConstruct public void init() { this.regions = readRegionsGcp(); this.vmTypes = readVmTypes(); this.defaultRegion = nthElement(this.regions.keySet(), DEFAULT_REGION_TYPE_POSITION); this.defaultVmType = nthElement(this.vmTypes.get(this.vmTypes.keySet().iterator().next()), DEFAULT_VM_TYPE_POSITION); initDefaultVmTypes(); } private void initDefaultVmTypes() { for (Map.Entry<AvailabilityZone, List<VmType>> vmType: vmTypes.entrySet()) { defaultVmTypes.put(vmType.getKey(), nthElement(vmType.getValue(), DEFAULT_VM_TYPE_POSITION)); } } private Map<AvailabilityZone, List<VmType>> readVmTypes() { Map<AvailabilityZone, List<VmType>> vmTypes = new HashMap<>(); String vm = getDefinition(gcpVmParameterDefinitionPath, "vm"); try { MachineDefinitionWrapper machineDefinitionWrapper = JsonUtil.readValue(vm, MachineDefinitionWrapper.class); for (Map.Entry<String, Object> object : machineDefinitionWrapper.getItems().entrySet()) { Map value = (Map) object.getValue(); List<Object> machineTpes = (List<Object>) value.get("machineTypes"); for (Object machineType : machineTpes) { MachineDefinitionView machineDefinitionView = new MachineDefinitionView((Map) machineType); AvailabilityZone availabilityZone = new AvailabilityZone(machineDefinitionView.getZone()); if (!vmTypes.containsKey(availabilityZone)) { List<VmType> vmTypeList = new ArrayList<>(); vmTypes.put(availabilityZone, vmTypeList); } VmTypeMeta vmTypeMeta = VmTypeMeta.VmTypeMetaBuilder.builder() .withCpuAndMemory(Integer.valueOf(machineDefinitionView.getGuestCpus()), Float.valueOf(machineDefinitionView.getMemoryMb()) / THOUSAND) .withMagneticConfig(TEN, Integer.valueOf(machineDefinitionView.getMaximumPersistentDisksSizeGb()), 1, machineDefinitionView.getMaximumNumberWithLimit()) .withSsdConfig(TEN, Integer.valueOf(machineDefinitionView.getMaximumPersistentDisksSizeGb()), 1, machineDefinitionView.getMaximumNumberWithLimit()) .withMaximumPersistentDisksSizeGb(machineDefinitionView.getMaximumPersistentDisksSizeGb()) .create(); VmType vmType = VmType.vmTypeWithMeta(machineDefinitionView.getName(), vmTypeMeta, true); vmTypes.get(availabilityZone).add(vmType); } } } catch (IOException e) { return vmTypes; } for (Map.Entry<AvailabilityZone, List<VmType>> availabilityZoneListEntry : vmTypes.entrySet()) { Collections.sort(availabilityZoneListEntry.getValue(), new StringTypesCompare()); } return sortMap(vmTypes); } private Map<Region, List<AvailabilityZone>> readRegionsGcp() { Map<Region, List<AvailabilityZone>> regions = new HashMap<>(); String zone = getDefinition(gcpZoneParameterDefinitionPath, "zone"); try { ZoneDefinitionWrapper zoneDefinitionWrapper = JsonUtil.readValue(zone, ZoneDefinitionWrapper.class); for (ZoneDefinitionView object : zoneDefinitionWrapper.getItems()) { String region = object.getRegion(); String avZone = object.getSelfLink(); String[] splitRegion = region.split("/"); String[] splitZone = avZone.split("/"); Region regionObject = Region.region(splitRegion[splitRegion.length - 1]); AvailabilityZone availabilityZoneObject = AvailabilityZone.availabilityZone(splitZone[splitZone.length - 1]); if (!regions.keySet().contains(regionObject)) { List<AvailabilityZone> availabilityZones = new ArrayList<>(); regions.put(regionObject, availabilityZones); } regions.get(regionObject).add(availabilityZoneObject); } } catch (IOException e) { return regions; } for (Map.Entry<Region, List<AvailabilityZone>> availabilityZoneListEntry : regions.entrySet()) { Collections.sort(availabilityZoneListEntry.getValue(), new StringTypesCompare()); } return sortMap(regions); } @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(GcpDiskType.HDD.value(), VolumeParameterType.MAGNETIC); map.put(GcpDiskType.SSD.value(), VolumeParameterType.SSD); return map; } private Collection<DiskType> getDiskTypes() { Collection<DiskType> disks = Lists.newArrayList(); for (GcpDiskType diskType : GcpDiskType.values()) { disks.add(diskType(diskType.value())); } return disks; } private DiskType defaultDiskType() { return diskType(GcpDiskType.HDD.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("gcp", resource); } private String getDefinition(String parameter, String type) { if (Strings.isNullOrEmpty(parameter)) { return resourceDefinition(type); } else { return FileReaderUtils.readFileFromClasspathQuietly(parameter); } } @Override public List<StackParamValidation> additionalStackParameters() { List<StackParamValidation> additionalStackParameterValidations = Lists.newArrayList(); additionalStackParameterValidations.add(new StackParamValidation(TTL, false, String.class, Optional.absent())); return additionalStackParameterValidations; } @Override public PlatformOrchestrator orchestratorParams() { return new PlatformOrchestrator(Collections.singletonList(orchestrator(OrchestratorConstants.SALT)), orchestrator(OrchestratorConstants.SALT)); } @Override public VmTypes vmTypes(Boolean extended) { Set<VmType> lists = new LinkedHashSet<>(); vmTypes.values().forEach(lists::addAll); return new VmTypes(lists, defaultVirtualMachine()); } @Override public Map<AvailabilityZone, VmTypes> vmTypesPerAvailabilityZones(Boolean extended) { Map<AvailabilityZone, VmTypes> result = new HashMap<>(); for (Map.Entry<AvailabilityZone, List<VmType>> zoneTypes : vmTypes.entrySet()) { AvailabilityZone zone = zoneTypes.getKey(); result.put(zone, new VmTypes(zoneTypes.getValue(), defaultVmTypes.get(zone))); } return result; } @Override public PlatformImage images() { List<CustomImage> customImages = new ArrayList<>(); for (Map.Entry<Region, List<AvailabilityZone>> regionListEntry : regions.entrySet()) { String property = environment.getProperty("gcp." + "default"); customImages.add(customImage(regionListEntry.getKey().value(), property)); } return new PlatformImage(customImages, imageRegex()); } @Override public String imageRegex() { return "(.*)\\/(.*).tar.gz$"; } @Override public TagSpecification tagSpecification() { return tagSpecification; } private VmType defaultVirtualMachine() { return defaultVmType; } public enum GcpDiskType { SSD("pd-ssd"), HDD("pd-standard"); private final String value; GcpDiskType(String value) { this.value = value; } public static String getUrl(String projectId, AvailabilityZone zone, String volumeId) { return String.format("https://www.googleapis.com/compute/v1/projects/%s/zones/%s/diskTypes/%s", projectId, zone.value(), volumeId); } public String value() { return value; } public String getUrl(String projectId, AvailabilityZone zone) { return getUrl(projectId, zone, value); } } }