package com.sixsq.slipstream.factory; /* * +=================================================================+ * 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.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.sixsq.slipstream.connector.CloudService; import com.sixsq.slipstream.connector.Connector; import com.sixsq.slipstream.connector.ConnectorFactory; import com.sixsq.slipstream.exceptions.ConfigurationException; import com.sixsq.slipstream.exceptions.InvalidMetadataException; import com.sixsq.slipstream.exceptions.NotFoundException; import com.sixsq.slipstream.exceptions.SlipStreamClientException; import com.sixsq.slipstream.exceptions.ValidationException; import com.sixsq.slipstream.persistence.ImageModule; import com.sixsq.slipstream.persistence.Module; import com.sixsq.slipstream.persistence.ModuleParameter; import com.sixsq.slipstream.persistence.Parameter; import com.sixsq.slipstream.persistence.ParameterCategory; import com.sixsq.slipstream.persistence.ParameterType; import com.sixsq.slipstream.persistence.Run; import com.sixsq.slipstream.persistence.RunParameter; import com.sixsq.slipstream.persistence.RunType; import com.sixsq.slipstream.persistence.RuntimeParameter; import com.sixsq.slipstream.persistence.User; public class BuildImageFactory extends RunFactory { protected final static String nodeInstanceName = Run.MACHINE_NAME; @Override protected RunType getRunType() { return RunType.Machine; } @Override protected void validateModule(Module module, Map<String, String> cloudServicePerNode) throws SlipStreamClientException { ImageModule image = castToRequiredModuleType(module); if (image.isBase()) { throw new SlipStreamClientException("A base image cannot be built"); } checkNoCircularDependencies(image); checkHasSomethingToBuild(image); String cloudServiceName = cloudServicePerNode.get(nodeInstanceName); checkNotAlreadyBuilt(image, cloudServiceName); // Finding an image id will validate that one exists image.extractBaseImageId(cloudServiceName); } protected static void checkNoCircularDependencies(ImageModule image) throws InvalidMetadataException, ValidationException { List<String> visitedReferenceModules = new ArrayList<String>(); recurseDependencies(image, visitedReferenceModules); } private static void recurseDependencies(ImageModule image, List<String> visitedReferenceModules) throws InvalidMetadataException, ValidationException { for (String ref : visitedReferenceModules) { if (ref.equals(image.getResourceUri())) { throw new InvalidMetadataException( "Circular dependency detected in module " + image.getResourceUri()); } } visitedReferenceModules.add(image.getResourceUri()); if (image.getModuleReference() != null) { recurseDependencies( (ImageModule) ImageModule.load(image.getModuleReference()), visitedReferenceModules); } return; } private static void checkHasSomethingToBuild(ImageModule image) throws ValidationException { // Check if the image declares a list of packages or a recipe. // If it doesn't, we don't need to build the image. However, we // need to make sure that the referenced image is built. if (image.isVirtual()) { throw new ValidationException( "This image doesn't need to be built since it doesn't contain a package list nor a pre-recipe or a recipe."); } } private static void checkNotAlreadyBuilt(ImageModule image, String cloudServiceName) throws ValidationException { // Check that the image is not already built for this cloud service name if (image.getCloudImageIdentifier(cloudServiceName) != null) { throw new ValidationException( "This image was already built for cloud: " + cloudServiceName); } } @Override protected void init(Module module, Run run, User user) throws ValidationException, NotFoundException { initRuntimeParameters((ImageModule) module, run); initMachineState(run); String cloudService = run.getCloudServiceNameForNode(nodeInstanceName); initNodeNames(run, cloudService); } protected static void initMachineState(Run run) throws ValidationException, NotFoundException { assignCommonNodeInstanceRuntimeParameters(run, nodeInstanceName); } protected static void initRuntimeParameters(ImageModule image, Run run) throws ValidationException, NotFoundException { // Add default values for the params as set in the image // definition // Only process the standard categories and the cloud service // (not the other cloud services, if any) List<String> filter = new ArrayList<String>(); for (ParameterCategory c : ParameterCategory.values()) { filter.add(c.toString()); } String cloudServiceName = run.getCloudServiceNameForNode(nodeInstanceName); filter.add(cloudServiceName); Connector connector = null; Map<String, ModuleParameter> parameters = new HashMap<>(); try { connector = ConnectorFactory.getConnector(cloudServiceName); parameters = connector.getImageParametersTemplate(); } catch (ValidationException e) { } parameters.putAll(image.getParameters()); ImageModule parent = image.getParentModule(); if (parent != null) { findAndAddImagesApplicationParameters(parameters, parent); } for (ModuleParameter param : parameters.values()) { if (filter.contains(param.getCategory())) { run.assignRuntimeParameter(constructParamName(nodeInstanceName, param.getName()), extractInitialValue(param, image, run, cloudServiceName), param.getDescription()); } } // Add cloud service name to orchestrator and machine run.assignRuntimeParameter(constructParamName(nodeInstanceName, RuntimeParameter.CLOUD_SERVICE_NAME), cloudServiceName, RuntimeParameter.CLOUD_SERVICE_DESCRIPTION); String imageId = image.extractBaseImageId(cloudServiceName); run.assignRuntimeParameter(constructParamName(nodeInstanceName, RuntimeParameter.IMAGE_ID_PARAMETER_NAME), imageId, RuntimeParameter.IMAGE_ID_PARAMETER_DESCRIPTION, ParameterType.String); String imagePlatform = image.getPlatform(); run.assignRuntimeParameter(constructParamName(nodeInstanceName, RuntimeParameter.IMAGE_PLATFORM_PARAMETER_NAME), imagePlatform, RuntimeParameter.IMAGE_PLATFORM_PARAMETER_DESCRIPTION, ParameterType.String); } private static String extractInitialValue(ModuleParameter parameter, ImageModule image, Run run, String cloudService) throws ValidationException { String parameterName = parameter.getName(); String value = run.getParameterValue(constructParamName(nodeInstanceName, parameterName), null); if (value == null && image != null && cloudService.equals(parameter.getCategory())) { ModuleParameter imageParameter = image.getParameter(parameterName); if (imageParameter != null) { value = imageParameter.getValue(); } } if (value == null) { value = parameter.getValue(); } return value; } protected void initNodeNames(Run run, String cloudService) throws ConfigurationException, ValidationException { run.addNodeInstanceName(nodeInstanceName, cloudService); run.addGroup(nodeInstanceName, cloudService); } @Override protected void addUserFormParametersAsRunParameters(Module module, Run run, Map<String, List<Parameter<?>>> userChoices) throws ValidationException { if (!isProvidedUserChoicesForNodeInstance(userChoices, nodeInstanceName)) { return; } ImageModule image = (ImageModule) module; List<Parameter<?>> userChoicesForMachine = userChoices.get(nodeInstanceName); for (Parameter<?> parameter : userChoicesForMachine) { checkParameterIsValid(image, parameter); String key = constructParamName(nodeInstanceName, parameter.getName()); RunParameter rp = new RunParameter(key, parameter.getValue(), ""); run.setParameter(rp); } } // TODO: pull this method up to be used in other factories. public static boolean isProvidedUserChoicesForNodeInstance(Map<String, List<Parameter<?>>> userChoices, String nodeInstanceName) { if (userChoices == null || userChoices.isEmpty()) return false; List<Parameter<?>> paramsForNodeInstance = userChoices.get(nodeInstanceName); if (paramsForNodeInstance == null || paramsForNodeInstance.isEmpty()) return false; return true; } private void checkParameterIsValid(ImageModule image, Parameter<?> parameter) throws ValidationException { List<String> paramsToFilter = new ArrayList<String>(); paramsToFilter.add(RuntimeParameter.MULTIPLICITY_PARAMETER_NAME); paramsToFilter.add(RuntimeParameter.CLOUD_SERVICE_NAME); String paramName = parameter.getName(); if (!image.getParameters().containsKey(paramName) && !image.getInputParametersExpanded().containsKey(paramName) && !paramsToFilter.contains(paramName)) { throw new ValidationException("Unknown parameter: " + parameter.getName() + " in node: " +nodeInstanceName); } } @Override protected Map<String, String> resolveCloudServiceNames(Module module, User user, Map<String, List<Parameter<?>>> userChoices) { Map<String, String> cloudServiceNamesPerNode = new HashMap<String, String>(); String cloudService = null; if (isProvidedUserChoicesForNodeInstance(userChoices, nodeInstanceName)) { for (Parameter<?> parameter : userChoices.get(nodeInstanceName)) { if (parameter.getName().equals(RuntimeParameter.CLOUD_SERVICE_NAME)) { cloudService = parameter.getValue(); break; } } } if (CloudService.isDefaultCloudService(cloudService)) { cloudService = user.getDefaultCloudService(); } cloudServiceNamesPerNode.put(nodeInstanceName, cloudService); return cloudServiceNamesPerNode; } @Override protected void initExtraRunParameters(Module module, Run run) throws ValidationException { } @Override protected void updateExtraRunParameters(Module module, Run run, Map<String, List<Parameter<?>>> userChoices) throws ValidationException { } @Override protected ImageModule castToRequiredModuleType(Module module) { return (ImageModule) module; } }