package com.sixsq.slipstream.persistence;
/*
* +=================================================================+
* SlipStream Server (WAR)
* =====
* Copyright (C) 2013 SixSq Sarl (sixsq.com)
* =====
* Licensed 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.
* -=================================================================-
*/
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.simpleframework.xml.*;
import com.sixsq.slipstream.exceptions.ConfigurationException;
import com.sixsq.slipstream.exceptions.ValidationException;
/**
* Unit test see:
*
* @see ImageModuleTest
*
*/
@Entity
@SuppressWarnings("serial")
public class ImageModule extends TargetContainerModule {
public static final String INSTANCE_TYPE_KEY = "instance.type";
public static final String INSTANCE_TYPE_INHERITED = "inherited";
public static final String RAM_KEY = "ram";
public static final String CPU_KEY = "cpu";
public static final String SMP_KEY = "smp";
public static final String NETWORK_KEY = "network";
public static final String LOGINPASSWORD_KEY = "login.password";
public static final String EXTRADISK_PARAM_PREFIX = "extra.disk";
private static final String EXTRADISK_NAME_VOLATILE = "volatile";
public static final String EXTRADISK_VOLATILE_PARAM = EXTRADISK_PARAM_PREFIX + "." + EXTRADISK_NAME_VOLATILE;
private static final String VOLATILE_DISK_VALUE_REGEX = "^[0-9]*$";
private static final String VOLATILE_DISK_VALUE_REGEXERROR = "Integer value expected for volatile extra disk";
public static final String CPU_PARAM = "cpu.nb";
public static final String RAM_PARAM = "ram.GB";
public static final String DISK_PARAM = "disk.GB";
private static class BuildState implements Serializable {
@Attribute
public final String moduleUri;
@Attribute
public final String builtOn;
public BuildState(String moduleUri, List<String> clouds) {
this.moduleUri = moduleUri;
this.builtOn = clouds.stream().collect(Collectors.joining(","));
}
}
@ElementList(required = false)
@Fetch(FetchMode.SELECT)
@OneToMany(mappedBy = "module", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<Package> packages = new HashSet<Package>();
@Transient
private Set<Package> packagesExpanded;
@Transient
protected Map<String, ModuleParameter> inputParametersExpanded;
@Transient
protected Map<String, ModuleParameter> outputParametersExpanded;
@Element(required = false, data = true)
@Column(length = 65536)
private String prerecipe = "";
@Element(required = false, data = true)
@Column(length = 65536)
private String recipe = "";
@Attribute
private Boolean isBase = false;
private String loginUser = "root";
private String platform = "other";
@Fetch(FetchMode.SELECT)
@OneToMany(mappedBy = "container", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@ElementList(required = false, data = true)
private Set<CloudImageIdentifier> cloudImageIdentifiers = new HashSet<CloudImageIdentifier>();
@Transient
private Set<BuildState> buildStates;
@Transient
private volatile ImageModule parentModule;
protected ImageModule() {
super();
}
public ImageModule(String name) throws ValidationException, ConfigurationException {
super(name, ModuleCategory.Image);
setDefaultParameters();
}
/**
* Validate for an image run (as opposed to a build or as part of a
* deployment).
*
* @param cloudService
* @throws ValidationException
*/
public void validateForRun(String cloudService) throws ValidationException {
validateHasImageId(cloudService);
validateExtraDisksParameters();
}
private void validateExtraDisksParameters() throws ValidationException {
validateExtraVolatileDiskValue();
}
private void validateExtraVolatileDiskValue() throws ValidationException {
String paramValue = getParameterValue(EXTRADISK_VOLATILE_PARAM, "");
if (!paramValue.matches(VOLATILE_DISK_VALUE_REGEX))
throw (new ValidationException(VOLATILE_DISK_VALUE_REGEXERROR));
}
private void validateHasImageId(String cloudService) throws ValidationException {
if (isBase) {
validateBaseImage(cloudService, true);
return;
}
if (isVirtual()) {
extractBaseImageId(cloudService);
return;
}
}
public boolean hasToRunBuildRecipes(String cloudService) {
return !isVirtual() && getImageId(cloudService) == null;
}
public List<String> getCloudNamesWhereBuilt() {
List<String> cloudNames = new LinkedList<>();
if (!isVirtual() && !isBase()) {
String cloudId = null;
for (CloudImageIdentifier c : getCloudImageIdentifiers()) {
cloudId = c.getCloudMachineIdentifer();
if (cloudId != null) {
cloudNames.add(c.getCloudServiceName());
}
}
}
return cloudNames;
}
private void validateBaseImage(String cloudService, boolean throwOnError) throws ValidationException {
CloudImageIdentifier cloudImageIdentifier = getCloudImageIdentifier(cloudService);
if (cloudImageIdentifier == null || !Parameter.hasValueSet(cloudImageIdentifier.getCloudMachineIdentifer())) {
throw (new ValidationException("Base image must have an image id for cloud service: " + cloudService));
}
}
public String getImageId(String cloudService) {
CloudImageIdentifier cloudImageId = null;
for (CloudImageIdentifier c : getCloudImageIdentifiers()) {
if (cloudService.equals(c.getCloudServiceName())) {
cloudImageId = c;
break;
}
}
String id = null;
if (cloudImageId != null) {
id = cloudImageId.getCloudMachineIdentifer();
}
return id;
}
/**
* Finds the base image id
*
* @param cloudService
* @return image id
* @throws ValidationException
*/
public String extractBaseImageId(String cloudService) throws ValidationException {
String imageId = getCloudImageId(cloudService);
if (isSet(imageId)) {
return imageId;
}
if (isBase()) {
throw (new ValidationException("Missing image id for base image: " + getName() + " on cloud service: "
+ cloudService));
}
ImageModule parentModule = getParentModule();
if (parentModule == null) {
throw (new ValidationException("Missing reference module"));
}
imageId = parentModule.extractBaseImageId(cloudService);
if (!isSet(imageId)) {
throw (new ValidationException("Missing image id in reference module: " + parentModule.getName()
+ " for cloud service: " + cloudService));
}
return imageId;
}
/**
* This method will look into the parent image if the parameter doesn't exist, not if it's empty.
* @return the value of the parameter.
* @throws ValidationException if the parameter doesn't exist in the image hierarchy.
*/
public ModuleParameter extractParameter(String parameterName) throws ValidationException {
ModuleParameter parameter = getParameter(parameterName);
if (parameter == null) {
throw (new ValidationException("Parameter " + parameterName + " not found."));
}
return parameter;
}
/**
* Override the method in {@link Parameterized} to add recursivity.
* This method will look into the parent image if the parameter doesn't exist, not if it's empty.
* @return the value of the parameter or null if it doesn't exist in the image hierarchy.
*/
@Override
public ModuleParameter getParameter(String name) {
ModuleParameter parameter = super.getParameter(name);
if (parameter != null) {
return parameter;
}
ImageModule parentModule = getParentModule();
if (parentModule == null) {
return null;
}
return parentModule.getParameter(name);
}
@ElementMap(required = false)
public Map<String, ModuleParameter> getInputParametersExpanded() {
if (inputParametersExpanded == null) {
inputParametersExpanded = new HashMap<>();
findAndAddInheritedApplicationParameters(inputParametersExpanded, this, ParameterCategory.Input);
}
return inputParametersExpanded;
}
@ElementMap(required = false)
public void setInputParametersExpanded(Map<String, ModuleParameter> parameters) {
}
@ElementMap(required = false)
public Map<String, ModuleParameter> getOutputParametersExpanded() {
if (outputParametersExpanded == null) {
outputParametersExpanded = new HashMap<>();
findAndAddInheritedApplicationParameters(outputParametersExpanded, this, ParameterCategory.Output);
}
return outputParametersExpanded;
}
@ElementMap(required = false)
public void setOutputParametersExpanded(Map<String, ModuleParameter> parameters) {
}
private void findAndAddInheritedApplicationParameters(Map<String, ModuleParameter> params, ImageModule image,
ParameterCategory type) {
for (Map.Entry<String, ModuleParameter> entry : image.getParameters().entrySet()) {
String parameterName = entry.getKey();
ModuleParameter parameter = entry.getValue();
String category = parameter.getCategory();
if (type.toString().equals(category)) {
params.putIfAbsent(parameterName, parameter);
}
}
ImageModule parent = image.getParentModule();
if (parent != null) {
findAndAddInheritedApplicationParameters(params, parent, type);
}
}
/**
* @return parent module. If the module doesn't have a parent, returns null
*/
public ImageModule getParentModule() {
if (parentModule != null) {
return parentModule;
}
if (getModuleReference() == null) {
return null;
}
parentModule = (ImageModule) Module.load(getModuleReference());
return parentModule;
}
private void setDefaultParameters() throws ValidationException, ConfigurationException {
addMandatoryParameter(RuntimeParameter.HOSTNAME_KEY, RuntimeParameter.HOSTNAME_DESCRIPTION,
ParameterCategory.Output);
addMandatoryParameter(RuntimeParameter.INSTANCE_ID_KEY, RuntimeParameter.INSTANCE_ID_DESCRIPTION,
ParameterCategory.Output);
updateCPU();
updateRAM();
updateDisk();
updateExtraDisks();
updateNetwork();
}
private void updateNetwork() throws ValidationException {
addMandatoryEnumParameter(NETWORK_KEY, "Network type", ParameterCategory.Cloud, NetworkType.getValues());
}
private void updateExtraDisks() throws ValidationException, ConfigurationException {
addVolatileDiskParameter();
}
private void updateCPU() throws ValidationException {
addCPUParameter();
}
private void updateRAM() throws ValidationException {
addRAMParameter();
}
private void updateDisk() throws ValidationException {
addDiskParameter();
}
private void addCPUParameter() throws ValidationException {
addMandatoryParameter(CPU_PARAM, "Number of CPUs", ParameterCategory.Cloud);
}
private void addRAMParameter() throws ValidationException {
addMandatoryParameter(RAM_PARAM, "RAM in GB", ParameterCategory.Cloud);
}
private void addDiskParameter() throws ValidationException {
addMandatoryParameter(DISK_PARAM, "Disk in GB", ParameterCategory.Cloud);
}
private void addVolatileDiskParameter() throws ValidationException {
addMandatoryParameter(EXTRADISK_VOLATILE_PARAM, "Volatile extra disk in GB", ParameterCategory.Cloud);
}
private void addMandatoryParameter(String name, String description, ParameterCategory category)
throws ValidationException {
addMandatoryParameter(name, description, category, ParameterType.String);
}
private void addMandatoryEnumParameter(String name, String description, ParameterCategory category,
List<String> enumValues) throws ValidationException {
addMandatoryParameter(name, description, category, ParameterType.Enum, enumValues);
}
private void addMandatoryParameter(String name, String description, ParameterCategory category, ParameterType type)
throws ValidationException {
addMandatoryParameter(name, description, category, type, null);
}
private void addMandatoryParameter(String name, String description, ParameterCategory category, ParameterType type,
List<String> enumValues) throws ValidationException {
ModuleParameter parameter = new ModuleParameter(name, null, description, category);
parameter.setMandatory(true);
parameter.setType(type);
if (enumValues != null) {
parameter.setEnumValues(enumValues);
parameter.setValue(enumValues.get(0));
}
setParameter(parameter);
}
public Boolean isBase() {
return isBase == null ? false : isBase;
}
public void setIsBase(Boolean isBase) throws ValidationException {
this.isBase = isBase;
}
public String getPreRecipe() {
return prerecipe;
}
public void setPreRecipe(String prerecipe) {
this.prerecipe = prerecipe;
}
public Set<Package> getPackages() {
return packages;
}
public void setPackages(Set<Package> packages) {
this.packages.clear();
for (Package p : packages) {
setPackage(p);
}
}
public void setPackage(Package package_) {
package_.setModule(this);
packages.add(package_);
}
public String getRecipe() {
return recipe;
}
public void setRecipe(String recipe) {
this.recipe = recipe;
}
@Attribute
public String getLoginUser() throws ValidationException {
if (isBase()) {
return loginUser;
}
if (getModuleReference() == null) {
return "";
}
if (getParentModule() != null) {
return getParentModule().getLoginUser();
} else {
return "";
}
}
@Attribute
public void setLoginUser(String loginUser) {
this.loginUser = loginUser;
}
@Attribute
public void setPlatform(String platform) {
this.platform = platform;
}
@Attribute
public String getPlatform() throws ValidationException {
if (isBase()) {
return platform;
}
if (getModuleReference() == null) {
return "";
}
if (getParentModule() != null) {
return getParentModule().getPlatform();
} else {
return "";
}
}
@Override
public boolean isVirtual() {
if (isPreRecipeEmpty() && isRecipeEmpty() && isPackagesEmpty()) {
return true;
} else {
return false;
}
}
private boolean isRecipeEmpty() {
return !Parameter.hasValueSet(getRecipe());
}
private boolean isPreRecipeEmpty() {
return !Parameter.hasValueSet(getPreRecipe());
}
private boolean isPackagesEmpty() {
return getPackages().isEmpty() ? true : false;
}
public ImageModule store() {
setVersion();
setModuleToTargets();
if (packages != null) {
for (Package p : packages) {
p.setModule(this);
}
}
for (CloudImageIdentifier id : getCloudImageIdentifiers()) {
id.setResourceUri(getResourceUri() + "/" + id.getCloudServiceName());
}
return (ImageModule) store(false);
}
public static ImageModule load(String uri) {
return (ImageModule) Module.load(uri);
}
public void setImageId(String imageId, String cloudService) {
if (!isSet(imageId)) {
return;
}
if (!isSet(cloudService)) {
return;
}
CloudImageIdentifier cloudImageIdentifier = getCloudImageIdentifier(cloudService);
if (cloudImageIdentifier == null) {
getCloudImageIdentifiers().add(new CloudImageIdentifier(this, cloudService, imageId));
} else {
cloudImageIdentifier.setCloudMachineIdentifer(imageId);
}
}
protected boolean isSet(String value) {
return Parameter.hasValueSet(value);
}
/**
* Assembled notes. Includes notes from inherited images.
*/
@Transient
@ElementArray(required = false, entry = "note")
public String[] getNotes() {
List<String> notes = new ArrayList<String>();
String moduleReference = getModuleReference();
if (moduleReference != null) {
ImageModule parent = load(moduleReference);
if (parent != null) {
notes.addAll(Arrays.asList(parent.getNotes()));
}
}
if (getNote() != null) {
notes.add(getNote());
}
return notes.toArray(new String[notes.size()]);
}
/**
* Empty setter needed for serializer on a read only property
*
*/
@Transient
@ElementArray(required = false, entry = "note")
private void setNotes(String[] notes) {
}
@ElementList(required = false, entry = "packageExpanded")
public Set<Package> getPackagesExpanded() {
if(packagesExpanded == null) {
packagesExpanded = new HashSet<>();
findAndAddPackages(this);
}
return packagesExpanded;
}
@ElementList(required = false, entry = "packageExpanded")
public void setPackagesExpanded(Set<Package> packagesExpanded) {
}
private void findAndAddPackages(ImageModule image) {
ImageModule parent = image.getParentModule();
if (parent != null)
findAndAddPackages(parent);
packagesExpanded.addAll(image.getPackages());
}
@ElementList(required = false)
public Set<BuildState> getBuildStates() {
if (buildStates == null) {
buildStates = new HashSet<>();
findAndAddBuildStates(this);
}
return buildStates;
}
@ElementList(required = false)
public void setBuildStates(Set<BuildState> buildStates) { }
private void findAndAddBuildStates(ImageModule image) {
ImageModule parent = image.getParentModule();
if (parent != null)
findAndAddBuildStates(parent);
buildStates.add(new BuildState(image.getResourceUri(), image.getCloudNamesWhereBuilt()));
}
public ImageModule copy() throws ValidationException {
ImageModule copy = (ImageModule) copyTo(new ImageModule(getName()));
for (CloudImageIdentifier cii : getCloudImageIdentifiers()) {
cii.copyTo(copy);
}
copy.setIsBase(isBase());
copy.setLoginUser(getLoginUser());
for (Package package_ : getPackages()) {
copy.getPackages().add(package_.copy());
}
copy.setPlatform(getPlatform());
copy.setPreRecipe(getPreRecipe());
copy.setRecipe(getRecipe());
copy.setPlacementPolicy(getPlacementPolicy());
for (Target target : getTargets()) {
copy.getTargets().add(target.copy());
}
return copy;
}
public void setCloudImageIdentifiers(Set<CloudImageIdentifier> cloudImageIdentifiers) {
this.cloudImageIdentifiers = cloudImageIdentifiers;
}
public Set<CloudImageIdentifier> getCloudImageIdentifiers() {
return cloudImageIdentifiers;
}
public CloudImageIdentifier getCloudImageIdentifier(String cloudService) {
// TODO: turn cloudImageIdentifiers into a map?
CloudImageIdentifier cloudImageIdentifier = null;
for (CloudImageIdentifier c : cloudImageIdentifiers) {
if (cloudService.equals(c.getCloudServiceName())) {
cloudImageIdentifier = c;
break;
}
}
return cloudImageIdentifier;
}
public String getCloudImageId(String cloudService) {
CloudImageIdentifier cloudImageIdentifer = getCloudImageIdentifier(cloudService);
return cloudImageIdentifer == null ? "" : cloudImageIdentifer.getCloudMachineIdentifer();
}
public void postDeserialization() {
super.postDeserialization();
for (CloudImageIdentifier c : getCloudImageIdentifiers()) {
c.setContainer(this);
}
}
@Override
protected void expandTargets() {
super.expandTargets();
targetsExpanded.add(new TargetExpanded(this, TargetExpanded.BuildRecipe.PRE_RECIPE));
targetsExpanded.add(new TargetExpanded(this, TargetExpanded.BuildRecipe.RECIPE));
}
private boolean nullOrEmpty(String s) {
return s == null || s.isEmpty();
}
private String andPolicies(String policy1, String policy2) {
if (nullOrEmpty(policy1) && nullOrEmpty(policy2)) {
return null;
} else if (nullOrEmpty(policy1)) {
return policy2;
} else if (nullOrEmpty(policy2)) {
return policy1;
} else {
return "(" + policy1 + ") and (" + policy2 + ")";
}
}
@Override
public Map<String, String> placementPoliciesPerComponent() {
Map<String, String> result = new HashMap<>();
String resultPolicy = null;
if (getModuleReference() == null) {
resultPolicy = getPlacementPolicy();
}
if (getParentModule() != null) {
String policy = getPlacementPolicy();
String parentPolicy = null;
Map<String, String> parentPlacementPolicies = getParentModule().placementPoliciesPerComponent();
if (parentPlacementPolicies != null && !parentPlacementPolicies.isEmpty()) {
parentPolicy = (String) parentPlacementPolicies.values().toArray()[0];
}
resultPolicy = andPolicies(parentPolicy, policy);
}
result.put(getResourceUri(), resultPolicy);
return result;
}
}