/*******************************************************************************
* Copyright (c) 2013 GigaSpaces Technologies Ltd. All rights reserved
*
* 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.
*******************************************************************************/
package org.cloudifysource.rest.controllers;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import org.apache.commons.collections.ListUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.cloudifysource.domain.ComputeTemplateHolder;
import org.cloudifysource.domain.cloud.Cloud;
import org.cloudifysource.domain.cloud.compute.ComputeTemplate;
import org.cloudifysource.dsl.internal.CloudifyConstants;
import org.cloudifysource.dsl.internal.CloudifyErrorMessages;
import org.cloudifysource.dsl.internal.CloudifyMessageKeys;
import org.cloudifysource.dsl.internal.DSLException;
import org.cloudifysource.dsl.internal.DSLReader;
import org.cloudifysource.dsl.internal.DSLUtils;
import org.cloudifysource.dsl.rest.AddTemplatesException;
import org.cloudifysource.dsl.rest.request.AddTemplatesInternalRequest;
import org.cloudifysource.dsl.rest.request.AddTemplatesRequest;
import org.cloudifysource.dsl.rest.response.AddTemplateResponse;
import org.cloudifysource.dsl.rest.response.AddTemplatesInternalResponse;
import org.cloudifysource.dsl.rest.response.AddTemplatesResponse;
import org.cloudifysource.dsl.rest.response.AddTemplatesStatus;
import org.cloudifysource.dsl.rest.response.GetTemplateResponse;
import org.cloudifysource.dsl.rest.response.ListTemplatesResponse;
import org.cloudifysource.dsl.rest.response.RemoveTemplatesResponse;
import org.cloudifysource.dsl.utils.IPUtils;
import org.cloudifysource.rest.RestConfiguration;
import org.cloudifysource.rest.internal.RestClientInternal;
import org.cloudifysource.rest.repo.UploadRepo;
import org.cloudifysource.rest.util.RestUtils;
import org.cloudifysource.rest.validators.AddTemplatesValidationContext;
import org.cloudifysource.rest.validators.AddTemplatesValidator;
import org.cloudifysource.rest.validators.TemplatesValidationContext;
import org.cloudifysource.rest.validators.TemplatesValidator;
import org.cloudifysource.restDoclet.annotations.InternalMethod;
import org.cloudifysource.restclient.exceptions.RestClientException;
import org.cloudifysource.restclient.messages.MessagesUtils;
import org.cloudifysource.security.CustomPermissionEvaluator;
import org.cloudifysource.utilitydomain.data.reader.ComputeTemplatesReader;
import org.openspaces.admin.Admin;
import org.openspaces.admin.pu.ProcessingUnit;
import org.openspaces.admin.pu.ProcessingUnitInstance;
import org.openspaces.admin.pu.ProcessingUnits;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.j_spaces.kernel.PlatformVersion;
/**
* @author yael
* @since 2.7.0
*
*/
@Controller
@RequestMapping(value = "/{version}/templates")
public class TemplatesController extends BaseRestController {
private static final Logger logger = Logger.getLogger(TemplatesController.class.getName());
@Autowired
private RestConfiguration restConfig;
@Autowired
private UploadRepo repo;
@Autowired
private final AddTemplatesValidator[] addTemplatesValidators = new AddTemplatesValidator[0];
@Autowired
private final TemplatesValidator[] templatesValidators = new TemplatesValidator[0];
private Cloud cloud;
private Admin admin;
private CustomPermissionEvaluator permissionEvaluator;
private File cloudConfigurationDir;
/**
* Initialization.
*/
@PostConstruct
public void init() {
log(Level.INFO, "Initializing Templates controller.");
cloud = restConfig.getCloud();
admin = restConfig.getAdmin();
permissionEvaluator = restConfig.getPermissionEvaluator();
cloudConfigurationDir = restConfig.getCloudConfigurationDir();
}
/**
* Add templates from templates folder to the cloud. Returns a response in case of success or partial failure.
*
* @param request
* {@link AddTemplatesRequest}
* @return {@link AddTemplatesResponse}
* @throws RestErrorException
* if failed to validate the addTemplates request.
* @throws IOException
* If failed to unzip templates folder.
* @throws DSLException
* If failed to read the templates from templates folder.
* @throws AddTemplatesException
* If failed to add templates (failure or partial failure).
*/
@PreAuthorize("isFullyAuthenticated() and hasAnyRole('ROLE_CLOUDADMINS')")
@RequestMapping(method = RequestMethod.POST)
public AddTemplatesResponse addTemplates(@RequestBody final AddTemplatesRequest request)
throws RestErrorException, IOException, DSLException, AddTemplatesException {
log(Level.INFO, "[addTemplates] - starting add templates.");
// validate
validateAddTemplates(request);
File templatesZippedFolder = null;
try {
// get templates folder
final String uploadKey = request.getUploadKey();
templatesZippedFolder = repo.get(uploadKey);
if (templatesZippedFolder == null) {
throw new RestErrorException(CloudifyMessageKeys.WRONG_TEMPLATES_UPLOAD_KEY.getName(), uploadKey);
}
final AddTemplatesInternalRequest internalRequest = createInternalRequest(request, templatesZippedFolder);
final List<String> expectedTemplates = internalRequest.getExpectedTemplates();
log(Level.INFO, "expecting to add " + expectedTemplates.size() + " templates: " + expectedTemplates);
// add the templates to all REST instances
final AddTemplatesResponse addTemplatesToRestInstances =
addTemplatesToRestInstances(internalRequest, templatesZippedFolder);
handleAddTemplatesResponse(addTemplatesToRestInstances);
return addTemplatesToRestInstances;
} finally {
if (templatesZippedFolder != null) {
FileUtils.deleteQuietly(templatesZippedFolder);
}
}
}
private void handleAddTemplatesResponse(final AddTemplatesResponse addTemplatesResponse)
throws AddTemplatesException {
final Map<String, AddTemplateResponse> templatesResponse = addTemplatesResponse.getTemplates();
boolean atLeastOneFailed = false;
boolean atLeastOneSucceeded = false;
for (final AddTemplateResponse templateResponse : templatesResponse.values()) {
final Map<String, String> failedToAddHosts = templateResponse.getFailedToAddHosts();
if (failedToAddHosts != null && !failedToAddHosts.isEmpty()) {
atLeastOneFailed = true;
if (atLeastOneSucceeded) {
break;
}
}
final List<String> successfullyAddedHosts = templateResponse.getSuccessfullyAddedHosts();
if (successfullyAddedHosts != null && !successfullyAddedHosts.isEmpty()) {
atLeastOneSucceeded = true;
if (atLeastOneFailed) {
break;
}
}
}
/*
* partial failure or failure
*/
if (atLeastOneFailed) {
if (atLeastOneSucceeded) {
// partial
log(Level.WARNING,
"[addTemplates] - Partial failure: " + templatesResponse);
addTemplatesResponse.setStatus(AddTemplatesStatus.PARTIAL_FAILURE);
throw new AddTemplatesException(addTemplatesResponse);
}
// failure
log(Level.WARNING,
"[addTemplates] - Failed to add all templates: " + templatesResponse);
addTemplatesResponse.setStatus(AddTemplatesStatus.FAILURE);
throw new AddTemplatesException(addTemplatesResponse);
}
addTemplatesResponse.setStatus(AddTemplatesStatus.SUCCESS);
log(Level.INFO, "[addTemplatesToRestInstances] - successfully added all templates to all ("
+ addTemplatesResponse.getInstances().size() + ") REST instances.");
}
/**
* Get the cloud's templates.
*
* @return {@link ListTemplatesResponse} containing the cloud's templates.
* @throws RestErrorException
* If cloud is a local cloud.
*/
@RequestMapping(method = RequestMethod.GET)
@PreAuthorize("isFullyAuthenticated() and hasAnyRole('ROLE_CLOUDADMINS', 'ROLE_APPMANAGERS')")
public ListTemplatesResponse listTemplates()
throws RestErrorException {
validateTemplateOperation("list-templates");
final ListTemplatesResponse response = new ListTemplatesResponse();
final Map<String, ComputeTemplate> templates = cloud.getCloudCompute().getTemplates();
log(Level.FINE, "listTemplates found " + templates.size() + " templates: " + templates);
response.setTemplates(templates);
return response;
}
/**
* Get template from the cloud.
*
* @param templateName
* The name of the template to get.
* @return a map containing the template and a success status if succeeded, else returns an error status.
* @throws RestErrorException
* if the cloud is a local cloud or the template doesn't exist.
*/
@RequestMapping(value = "{templateName}", method = RequestMethod.GET)
@PreAuthorize("isFullyAuthenticated() and hasAnyRole('ROLE_CLOUDADMINS', 'ROLE_APPMANAGERS')")
public GetTemplateResponse getTemplate(@PathVariable final String templateName)
throws RestErrorException {
validateTemplateOperation("get-template");
// get template from cloud
final ComputeTemplate cloudTemplate = cloud.getCloudCompute().getTemplates().get(templateName);
if (cloudTemplate == null) {
log(Level.WARNING, "[getTemplate] - template [" + templateName
+ "] not found. cloud templates list: " + cloud.getCloudCompute().getTemplates());
throw new RestErrorException(CloudifyErrorMessages.TEMPLATE_NOT_EXIST.getName(), templateName);
}
final GetTemplateResponse response = new GetTemplateResponse();
response.setTemplate(cloudTemplate);
return response;
}
private AddTemplatesInternalRequest createInternalRequest(final AddTemplatesRequest request,
final File templatesZippedFolder)
throws DSLException, IOException {
final AddTemplatesInternalRequest internalRequest = new AddTemplatesInternalRequest();
// cloud templates
final File unzippedFolder = new ComputeTemplatesReader().unzipCloudTemplatesFolder(templatesZippedFolder);
try {
final List<ComputeTemplateHolder> cloudTemplatesHolders =
new ComputeTemplatesReader().readCloudTemplatesFromDirectory(unzippedFolder);
internalRequest.setCloudTemplates(cloudTemplatesHolders);
// expected templates
final List<String> expectedAddedTemplates = new LinkedList<String>();
for (final ComputeTemplateHolder templateHolder : cloudTemplatesHolders) {
expectedAddedTemplates.add(templateHolder.getName());
}
internalRequest.setExpectedTemplates(expectedAddedTemplates);
return internalRequest;
} finally {
if (unzippedFolder != null) {
FileUtils.deleteQuietly(unzippedFolder);
}
}
}
/**
* For each puInstance - send the invoke an add templates request.
*
* @param templatesFolder
* .
* @param expectedTemplates
* The expected templates to add.
* @param addedTemplatesByHost
* a map updates by this method to specify the failed to add templates for each instance.
* @param failedToAddTemplatesByHost
* a map updates by this method to specify the failed to add templates for each instance.
*/
private AddTemplatesResponse addTemplatesToRestInstances(final AddTemplatesInternalRequest request,
final File templatesZippedFolder) {
final Map<String, AddTemplateResponse> templatesResponse = new HashMap<String, AddTemplateResponse>();
// get the instances
final ProcessingUnitInstance[] instances = admin.getProcessingUnits().
waitFor("rest", RestUtils.TIMEOUT_IN_SECOND, TimeUnit.SECONDS).getInstances();
final List<String> instancesList = new ArrayList<String>(instances.length);
// execute add-template on each rest instance
log(Level.INFO, "[addTemplatesToRestInstances] - sending add-templates request to "
+ instances.length + " instances.");
for (final ProcessingUnitInstance puInstance : instances) {
final String hostAddress = puInstance.getMachine().getHostAddress();
instancesList.add(hostAddress);
log(Level.INFO, "[addTemplatesToRestInstances] - sending request to " + hostAddress);
/*
* add template to instance and get the response
*/
final AddTemplatesInternalResponse instanceResponse =
executeAddTemplateOnInstance(
hostAddress, Integer.toString(puInstance.getJeeDetails().getPort()),
request, templatesZippedFolder);
final Map<String, String> failedToAddTempaltesToHost = instanceResponse.getFailedToAddTempaltesAndReasons();
final List<String> addedTempaltes = instanceResponse.getAddedTempaltes();
/*
* failed to add templates
*/
if (failedToAddTempaltesToHost != null) {
for (final Entry<String, String> entry : failedToAddTempaltesToHost.entrySet()) {
log(Level.WARNING, "[addTemplatesToRestInstances] - failed to add templates to host ["
+ hostAddress + "]: " + failedToAddTempaltesToHost);
// update template's entry in the final response
// for each template - add the current host to the failure hosts map of the template.
String templateName = entry.getKey();
AddTemplateResponse addTemplateResponse = templatesResponse.get(templateName);
// create new response if the template doesn't have one yet.
if (addTemplateResponse == null) {
addTemplateResponse = new AddTemplateResponse();
}
// get the failure map (hosts and reasons).
Map<String, String> failedHostsReasons = addTemplateResponse.getFailedToAddHosts();
if (failedHostsReasons == null) {
failedHostsReasons = new HashMap<String, String>();
}
// add the failed host (and failure reason) to the failure map.
failedHostsReasons.put(hostAddress, entry.getValue());
// set the updated failure map at template's response.
addTemplateResponse.setFailedToAddHosts(failedHostsReasons);
// add the template and its response to the final templates response.
templatesResponse.put(templateName, addTemplateResponse);
}
}
/*
* successfully added templates
*/
if (addedTempaltes != null) {
log(Level.INFO, "[addTemplatesToRestInstances] - successfully added templates to host ["
+ hostAddress + "]: " + addedTempaltes);
for (final String templateName : addedTempaltes) {
AddTemplateResponse addTemplateResponse = templatesResponse.get(templateName);
// create new response if the template doesn't have one yet.
if (addTemplateResponse == null) {
addTemplateResponse = new AddTemplateResponse();
}
// get the successfully hosts list.
List<String> successfullyAddedHosts = addTemplateResponse.getSuccessfullyAddedHosts();
if (successfullyAddedHosts == null) {
successfullyAddedHosts = new LinkedList<String>();
}
// add the host to the successfully added hosts list.
successfullyAddedHosts.add(hostAddress);
// set the updated list at template's response.
addTemplateResponse.setSuccessfullyAddedHosts(successfullyAddedHosts);
// add the template and its response to the final templates response.
templatesResponse.put(templateName, addTemplateResponse);
}
}
}
// create and return the response (the status of the response will be set later).
final AddTemplatesResponse response = new AddTemplatesResponse();
response.setInstances(instancesList);
response.setTemplates(templatesResponse);
return response;
}
/**
* Invoke add templates on the given instance.
*
* @param puInstance
* @param request
* @param host
* @return AddTemplatesInternalResponse
*/
private AddTemplatesInternalResponse executeAddTemplateOnInstance(
final String host,
final String port,
final AddTemplatesInternalRequest request,
final File templatesZippedFolder) {
AddTemplatesInternalResponse instanceResponse;
String requestName = "create rest client";
try {
// invoke upload and add-templates commands on each REST instance.
/*
* create rest client
*/
final RestClientInternal client = createRestClientInternal(host, port);
requestName = "execute upload request";
/*
* upload
*/
String uploadKey = client.uploadInternal(null, templatesZippedFolder).getUploadKey();
log(Level.FINE, "[executeAddTemplateOnInstance] - Uploaded templates zipped folder ["
+ templatesZippedFolder + "] to host [" + host + "], upload key = " + uploadKey);
request.setUploadKey(uploadKey);
requestName = "execute add-templates-internal request";
/*
* add templates
*/
instanceResponse = client.addTemplatesInternal(request);
} catch (final RestClientException e) {
// the request failed => all expected templates failed to be added
// create a response that contains all expected templates in a failure map.
log(Level.WARNING, "[executeAddTemplateOnInstance] - Failed to " + requestName + " to "
+ host + ". Error message: " + e.getMessageFormattedText() + ", verbose: " + e.getVerbose());
final Map<String, String> failedMap = new HashMap<String, String>();
for (final String expectedTemplate : request.getExpectedTemplates()) {
failedMap.put(expectedTemplate, "http request failed [" + e.getMessageFormattedText() + "]");
}
instanceResponse = new AddTemplatesInternalResponse();
instanceResponse.setFailedToAddTempaltesAndReasons(failedMap);
return instanceResponse;
}
final List<String> addedTempaltes = instanceResponse.getAddedTempaltes();
log(Level.FINE, "[executeAddTemplateOnInstance] - added "
+ addedTempaltes.size() + " templates: " + addedTempaltes);
final Map<String, String> failedToAddTempaltesAndReasons =
instanceResponse.getFailedToAddTempaltesAndReasons();
final List<String> failedList = new ArrayList<String>(failedToAddTempaltesAndReasons.keySet());
log(Level.FINE, "[executeAddTemplateOnInstance] - failed to add "
+ failedList.size() + " templates: " + failedList);
// addedTempaltes and failedList suppose to contain all templates from expectedTemplates.
final List<?> union = ListUtils.union(addedTempaltes, failedList);
final List<?> subtract = ListUtils.subtract(request.getExpectedTemplates(), union);
if (!subtract.isEmpty()) {
// add all missing templates to the failure map.
for (final Object templateName : subtract) {
failedToAddTempaltesAndReasons.put((String) templateName,
"expected template missing (not found in failure list)");
}
instanceResponse.setFailedToAddTempaltesAndReasons(failedToAddTempaltesAndReasons);
}
return instanceResponse;
}
/**
* Add template files to the cloud configuration directory and to the cloud object. This method supposed to be
* invoked by the MNG on all REST instances.
*
* @param request
* The request.
* @return {@link AddTemplatesInternalResponse}
* @throws IOException
* in case of reading error.
* @throws RestErrorException .
*/
@InternalMethod
@RequestMapping(value = "internal", method = RequestMethod.POST)
public AddTemplatesInternalResponse
addTemplatesInternal(
@RequestBody final AddTemplatesInternalRequest request)
throws IOException, RestErrorException {
final ComputeTemplatesReader reader = new ComputeTemplatesReader();
String uploadKey = request.getUploadKey();
final File templatesFolder = repo.get(uploadKey);
if (templatesFolder == null) {
throw new RestErrorException(CloudifyMessageKeys.WRONG_TEMPLATES_UPLOAD_KEY.getName(), uploadKey);
}
final File unzippedTemplatesFolder = reader.unzipCloudTemplatesFolder(templatesFolder);
try {
log(Level.INFO, "[addTemplatesInternal] - adding templates " + request.getExpectedTemplates());
// add templates to the cloud and return the added templates.
return addTemplatesToCloud(unzippedTemplatesFolder, request.getCloudTemplates());
} finally {
FileUtils.deleteQuietly(unzippedTemplatesFolder);
}
}
/**
* Adds templates to cloud's templates. Adds templates' files to cloud configuration directory.
*
* @param templatesFolder
* @return {@link AddTemplatesInternalResponse}
*/
private AddTemplatesInternalResponse addTemplatesToCloud(final File templatesFolder,
final List<ComputeTemplateHolder> templatesHolders) {
log(Level.FINE,
"[addTemplatesToCloud] - Adding " + templatesHolders.size() + " templates to cloud.");
// adds the templates to the cloud's templates list, deletes failed to added templates from the folder.
final AddTemplatesInternalResponse addTemplatesToCloudListresponse =
addTemplatesToCloudList(templatesFolder, templatesHolders);
List<String> addedTemplates = addTemplatesToCloudListresponse.getAddedTempaltes();
final Map<String, String> failedToAddTemplates =
addTemplatesToCloudListresponse.getFailedToAddTempaltesAndReasons();
// if no templates were added, throw an exception
if (addedTemplates.isEmpty()) {
log(Level.WARNING,
"[addTemplatesToCloud] - Failed to add templates from " + templatesFolder.getAbsolutePath());
} else {
// at least one template was added, copy files from template folder to a new folder.
log(Level.FINE,
"[addTemplatesToCloud] - Coping templates files from " + templatesFolder.getAbsolutePath()
+ " to a new folder under " + cloudConfigurationDir.getAbsolutePath());
try {
final File localTemplatesDir = copyTemplateFilesToCloudConfigDir(templatesFolder);
log(Level.FINE, "[addTemplatesToCloud] - The templates files were copied to "
+ localTemplatesDir.getAbsolutePath());
updateCloudTemplatesUploadPath(addedTemplates, localTemplatesDir);
} catch (final IOException e) {
// failed to copy files - remove all added templates from cloud and them to the failed map.
log(Level.WARNING,
"[addTemplatesToCloud] - Failed to copy templates files, error: " + e.getMessage(), e);
for (final String templateName : addedTemplates) {
cloud.getCloudCompute().getTemplates().remove(templateName);
failedToAddTemplates.put(templateName, "failed to copy templates files");
}
// added templates should not include templates.
addedTemplates = new LinkedList<String>();
}
}
if (!failedToAddTemplates.isEmpty()) {
log(Level.WARNING, "[addTemplatesToCloud] - Failed to add the following templates: "
+ failedToAddTemplates.toString());
}
// create and return the result.
final AddTemplatesInternalResponse response = new AddTemplatesInternalResponse();
response.setAddedTempaltes(addedTemplates);
response.setFailedToAddTempaltesAndReasons(failedToAddTemplates);
return response;
}
/**
* Updates the upload local path in all added cloud templates.
*
* @param addedTemplates
* the added templates.
* @param localTemplatesDir
* the directory where the upload directory expected to be found.
*/
private void updateCloudTemplatesUploadPath(final List<String> addedTemplates, final File localTemplatesDir) {
for (final String templateName : addedTemplates) {
final ComputeTemplate cloudTemplate = cloud.getCloudCompute().getTemplates().get(templateName);
final String localUploadPath =
new File(localTemplatesDir, cloudTemplate.getLocalDirectory()).getAbsolutePath();
cloudTemplate.setAbsoluteUploadDir(localUploadPath);
}
}
/**
* Scans the cloudTemplatesHolders list and adds each template that doesn't already exist. Rename template's file if
* needed (if its prefix is not the template's name).
*
* @param templatesFolder
* the folder contains templates files.
* @param cloudTemplates
* the list of cloud templates.
* @param addedTemplates
* a list for this method to update with all the added templates.
* @param failedToAddTemplates
* a list for this method to update with all the failed to add templates.
*/
private AddTemplatesInternalResponse addTemplatesToCloudList(
final File templatesFolder, final List<ComputeTemplateHolder> cloudTemplates) {
final List<String> addedTemplates = new LinkedList<String>();
final Map<String, String> failedToAddTemplates = new HashMap<String, String>();
log(Level.FINE,
"[addTemplatesToCloudList] - adding " + cloudTemplates.size() + " templates to cloud's list.");
for (final ComputeTemplateHolder holder : cloudTemplates) {
final String templateName = holder.getName();
final String originalTemplateFileName = holder.getTemplateFileName();
// check if template already exist
final Map<String, ComputeTemplate> templates = cloud.getCloudCompute().getTemplates();
if (templates.containsKey(templateName)) {
// template already exists
log(Level.WARNING, "[addTemplatesToCloudList] - Template already exists: " + templateName);
failedToAddTemplates.put(templateName, "template already exists");
new File(templatesFolder, originalTemplateFileName).delete();
continue;
}
// rename template file to <templateName>-template.groovy if needed
// rename the properties and overrides files as well.
try {
renameTemplateFilesIfNeeded(templatesFolder, holder);
} catch (final IOException e) {
failedToAddTemplates.put(templateName, "failed to rename template's file. error: " + e.getMessage());
// rename failed - delete the file so it wont be added to the additional templates folder.
log(Level.WARNING, "[renameTemplateFileIfNeeded] - Failed to rename template's file."
+ " The file [" + originalTemplateFileName + "] will be deleted.", e);
new File(templatesFolder, originalTemplateFileName).delete();
continue;
}
// add template to cloud templates list
final ComputeTemplate cloudTemplate = holder.getCloudTemplate();
templates.put(templateName, cloudTemplate);
addedTemplates.add(templateName);
}
final AddTemplatesInternalResponse response = new AddTemplatesInternalResponse();
response.setAddedTempaltes(addedTemplates);
response.setFailedToAddTempaltesAndReasons(failedToAddTemplates);
return response;
}
/**
* Copies all the files from templatesFolder to a new directory under cloud configuration directory.
*
* @param templatesDirToCopy
* the directory contains all the files to copy.
* @throws IOException
* If failed to copy files.
*/
private File copyTemplateFilesToCloudConfigDir(final File templatesDirToCopy)
throws IOException {
final File templatesDirParent = restConfig.getAdditionalTempaltesFolder();
// create new templates folder with a unique name.
String folderName = CloudifyConstants.TEMPLATE_FOLDER_PREFIX
+ restConfig.getLastTemplateFileNum().incrementAndGet();
File copiedtemplatesFolder = new File(templatesDirParent, folderName);
while (copiedtemplatesFolder.exists()) {
folderName = CloudifyConstants.TEMPLATE_FOLDER_PREFIX
+ restConfig.getLastTemplateFileNum().incrementAndGet();
copiedtemplatesFolder = new File(templatesDirParent, folderName);
}
copiedtemplatesFolder.mkdir();
try {
FileUtils.copyDirectory(templatesDirToCopy, copiedtemplatesFolder);
return copiedtemplatesFolder;
} catch (final IOException e) {
FileUtils.deleteDirectory(copiedtemplatesFolder);
restConfig.getLastTemplateFileNum().decrementAndGet();
throw e;
}
}
/**
* If the original template's file name prefix is not the template's name, rename it. Also, rename the properties
* and overrides files if exist.
*
* @param templatesFolder
* the folder that contains the template's file.
* @param holder
* holds the relevant template
* @throws IOException
* If failed to rename.
*/
private void renameTemplateFilesIfNeeded(final File templatesFolder, final ComputeTemplateHolder holder)
throws IOException {
final String templateName = holder.getName();
final String templateFileName = holder.getTemplateFileName();
final File templateFile = new File(templatesFolder, templateFileName);
final String propertiesFileName = holder.getPropertiesFileName();
final String overridesFileName = holder.getOverridesFileName();
log(Level.FINE, "[renameTemplateFileIfNeeded] - Renaming template files [template name = "
+ templateName + "]");
// rename groovy file if needed
String newName = DSLUtils.renameCloudTemplateFileNameIfNeeded(templateFile, templateName,
DSLUtils.TEMPLATE_DSL_FILE_NAME_SUFFIX);
if (newName != null) {
log(Level.FINE, "[renameTemplateFileIfNeeded] - Renamed template file name from "
+ templateFileName + " to " + newName + ".");
}
// rename properties file if needed
if (propertiesFileName != null) {
final File propertiesFile = new File(templatesFolder, propertiesFileName);
newName = DSLUtils.renameCloudTemplateFileNameIfNeeded(propertiesFile, templateName,
DSLUtils.TEMPLATES_PROPERTIES_FILE_NAME_SUFFIX);
if (newName != null) {
log(Level.FINE, "[renameTemplateFileIfNeeded] - Renamed template's properties file name from"
+ " " + propertiesFileName + " to " + newName + ".");
}
}
// rename overrides file if needed
if (overridesFileName != null) {
final File overridesFile = new File(templatesFolder, overridesFileName);
newName = DSLUtils.renameCloudTemplateFileNameIfNeeded(overridesFile, templateName,
DSLUtils.TEMPLATES_OVERRIDES_FILE_NAME_SUFFIX);
if (newName != null) {
log(Level.FINE, "[renameTemplateFileIfNeeded] - Renamed template's overrides file name from "
+ overridesFileName + " to " + newName + ".");
}
}
}
private void validateAddTemplates(final AddTemplatesRequest request)
throws RestErrorException {
final AddTemplatesValidationContext validationContext = new AddTemplatesValidationContext();
validationContext.setCloud(cloud);
validationContext.setRequest(request);
for (final AddTemplatesValidator validator : addTemplatesValidators) {
validator.validate(validationContext);
}
}
private void validateTemplateOperation(final String opName)
throws RestErrorException {
final TemplatesValidationContext validationContext = new TemplatesValidationContext();
validationContext.setCloud(cloud);
validationContext.setOperationName(opName);
for (final TemplatesValidator validator : templatesValidators) {
validator.validate(validationContext);
}
}
/**
* Removes a template from the cloud.
*
* @param templateName
* The name of the template to remove.
* @throws RestErrorException
* If cloud is a local cloud or one of the REST instances failed to remove the template.
*/
@PreAuthorize("isFullyAuthenticated() and hasAnyRole('ROLE_CLOUDADMINS')")
@RequestMapping(value = "{templateName}", method = RequestMethod.DELETE)
public void removeTemplate(@PathVariable final String templateName)
throws RestErrorException {
validateTemplateOperation("remove-template");
log(Level.INFO, "[removeTemplate] - starting remove template [" + templateName + "]");
// check if the template is being used by at least one service, so it cannot be removed.
final List<String> templateServices = getTemplateServices(templateName);
if (!templateServices.isEmpty()) {
log(Level.WARNING, "[removeTemplate] - failed to remove template [" + templateName
+ "]. The template is being used by " + templateServices.size() + " services: " + templateServices);
throw new RestErrorException(CloudifyErrorMessages.TEMPLATE_IN_USE.getName(),
templateName, templateServices);
}
// remove template from all REST instances
final RemoveTemplatesResponse resposne = removeTemplateFromRestInstances(templateName);
handleRemoveTemplateResponse(resposne, templateName);
log(Level.INFO, "[removeTemplate] - successfully removed template [" + templateName + "].");
}
private void handleRemoveTemplateResponse(final RemoveTemplatesResponse resposne, final String templateName)
throws RestErrorException {
final Map<String, String> failedToRemoveFromHosts = resposne.getFailedToRemoveFromHosts();
final List<String> successfullyRemovedFromHosts = resposne.getSuccessfullyRemovedFromHosts();
// check if some REST instances failed to remove the template
if (!failedToRemoveFromHosts.isEmpty()) {
String message = "[removeTemplate] - failed to remove template [" + templateName + "] from: "
+ failedToRemoveFromHosts;
if (!successfullyRemovedFromHosts.isEmpty()) {
message += ". Succeeded to remove the template from: " + successfullyRemovedFromHosts;
}
log(Level.WARNING, message);
throw new RestErrorException(CloudifyErrorMessages.FAILED_REMOVE_TEMPLATE.getName(),
templateName, failedToRemoveFromHosts.toString());
}
}
private List<String> getTemplateServices(final String templateName) {
final List<String> services = new LinkedList<String>();
final ProcessingUnits processingUnits = admin.getProcessingUnits();
for (final ProcessingUnit processingUnit : processingUnits) {
final Properties puProps = processingUnit.getBeanLevelProperties().getContextProperties();
final String puTemplateName = puProps.getProperty(CloudifyConstants.CONTEXT_PROPERTY_TEMPLATE);
if (puTemplateName != null && puTemplateName.equals(templateName)) {
services.add(processingUnit.getName());
}
}
return services;
}
private RemoveTemplatesResponse removeTemplateFromRestInstances(final String templateName) {
// get rest instances
final ProcessingUnit processingUnit =
admin.getProcessingUnits().waitFor("rest", RestUtils.TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
final ProcessingUnitInstance[] instances = processingUnit.getInstances();
// invoke remove-template command on each REST instance.
log(Level.INFO, "[removeTemplateFromRestInstances] - sending remove-template request to "
+ instances.length + " REST instances.");
final Map<String, String> failedToRemoveFromHosts = new HashMap<String, String>();
final List<String> successfullyRemovedFromHosts = new LinkedList<String>();
for (final ProcessingUnitInstance puInstance : instances) {
final String hostAddress = puInstance.getMachine().getHostAddress();
final String port = Integer.toString(puInstance.getJeeDetails().getPort());
try {
final RestClientInternal client = createRestClientInternal(hostAddress, port);
log(Level.INFO, "sending request to " + hostAddress);
client.removeTemplateInternal(templateName);
} catch (final RestClientException e) {
failedToRemoveFromHosts.put(hostAddress, e.getMessageFormattedText());
log(Level.WARNING, "[removeTemplateFromRestInstances] - remove template ["
+ templateName + "] from instance [" + hostAddress + "] failed. Error: "
+ e.getMessageFormattedText(), e);
continue;
}
successfullyRemovedFromHosts.add(hostAddress);
log(Level.INFO, "[removeTemplateFromRestInstances] - Successfully removed template ["
+ templateName + "] from " + hostAddress);
}
final RemoveTemplatesResponse response = new RemoveTemplatesResponse();
response.setFailedToRemoveFromHosts(failedToRemoveFromHosts);
response.setSuccessfullyRemovedFromHosts(successfullyRemovedFromHosts);
return response;
}
/**
* Internal method. Remove template file from the cloud configuration directory and from the cloud's templates map.
* This method supposed to be invoked from removeTemplate of a REST instance.
*
* @param templateName
* the name of the template to remove.
* @throws RestErrorException
* If failed to remove the template.
*/
@InternalMethod
@RequestMapping(value = "internal/{templateName}", method = RequestMethod.DELETE)
public void
removeTemplateInternal(@PathVariable final String templateName)
throws RestErrorException {
log(Level.INFO, "[removeTemplateInternal] - removing template [" + templateName + "].");
// check if the template is being used by at least one service, so it cannot be removed.
final List<String> templateServices = getTemplateServices(templateName);
if (!templateServices.isEmpty()) {
log(Level.WARNING, "[removeTemplateInternal] - failed to remove template [" + templateName
+ "]. The template is being used by the following services: " + templateServices);
throw new RestErrorException(CloudifyErrorMessages.TEMPLATE_IN_USE.getName(),
templateName, templateServices);
}
// try to remove the template
try {
removeTemplateFromCloud(templateName);
} catch (final RestErrorException e) {
log(Level.WARNING, "[removeTemplateInternal] - failed to remove template [" + templateName + "].", e);
throw e;
}
log(Level.INFO, "[removeTemplateInternal] - Successfully removed template [" + templateName + "].");
}
private void removeTemplateFromCloud(final String templateName)
throws RestErrorException {
log(Level.FINE, "[removeTemplateFromCloud] - removing template [" + templateName + "] from cloud.");
// delete template's file from the cloud configuration directory.
try {
deleteTemplateFile(templateName);
} catch (final RestErrorException e) {
log(Level.WARNING, "[removeTemplateFromCloud] - failed to remove template's files: "
+ e.getLocalizedMessage() + ". The template will not be removed from the cloud's tempaltes list.");
throw e;
}
// remove template from cloud's list
removeTemplateFromCloudList(templateName);
}
private void removeTemplateFromCloudList(final String templateName)
throws RestErrorException {
log(Level.FINE, "[removeTemplateFromCloudList] - removing template [" + templateName + "] from cloud's list.");
final Map<String, ComputeTemplate> cloudTemplates = cloud.getCloudCompute().getTemplates();
if (!cloudTemplates.containsKey(templateName)) {
log(Level.WARNING,
"[removeTemplateFromCloudList] - tempalte [" + templateName + "] doesn't exist in cloud's list.");
throw new RestErrorException(CloudifyErrorMessages.TEMPLATE_NOT_EXIST.getName(), templateName);
}
cloudTemplates.remove(templateName);
log(Level.FINE, "[removeTemplateFromCloudList] - template [" + templateName
+ "] was removed from cloud's list.");
}
/**
* Deletes the template's file. Deletes the templates folder if no other templates files exist in the folder.
* Deletes the {@link CloudifyConstants#ADDITIONAL_TEMPLATES_FOLDER_NAME} folder if empty.
*
* @param templateName
* @throws RestErrorException
*/
private void deleteTemplateFile(final String templateName) throws RestErrorException {
final File templateFolder = getTemplateFolder(templateName);
if (templateFolder == null) {
throw new RestErrorException(CloudifyErrorMessages.FAILED_REMOVE_TEMPLATE_FILE.getName(),
templateName, "failed to get template's folder");
}
final File templateFile = getTemplateFile(templateName, templateFolder);
if (templateFile == null) {
throw new RestErrorException(CloudifyErrorMessages.FAILED_REMOVE_TEMPLATE_FILE.getName(),
templateName, "template file doesn't exist");
}
// delete the file from the directory.
final String templatesPath = templateFile.getAbsolutePath();
log(Level.FINE, "[deleteTemplateFile] - removing template file " + templatesPath);
boolean deleted = false;
try {
deleted = templateFile.delete();
} catch (final SecurityException e) {
log(Level.WARNING, "[deleteTemplateFile] - Failed to deleted template file " + templatesPath
+ ", Error: " + e.getMessage(), e);
throw new RestErrorException(CloudifyErrorMessages.FAILED_REMOVE_TEMPLATE_FILE.getName(),
templatesPath, "Security exception: " + e.getMessage());
}
if (!deleted) {
throw new RestErrorException(CloudifyErrorMessages.FAILED_REMOVE_TEMPLATE_FILE.getName(),
templatesPath, "template file was not deleted.");
}
log(Level.FINE, "[deleteTemplateFile] - Successfully deleted template file [" + templatesPath + "].");
// delete properties and overrides files if exist.
ComputeTemplatesReader.removeTemplateFiles(templateFolder, templateName);
deleteTemplateFolderIfNeeded(templateName, templateFolder);
}
private void deleteTemplateFolderIfNeeded(final String templateName, final File templateFolder) {
log(Level.FINE,
"[deleteTemplateFile] - checking if the folder of template ["
+ templateName + "] can be deleted [" + templateFolder + "].");
final File[] templatesFiles =
DSLReader.findDefaultDSLFiles(DSLUtils.TEMPLATE_DSL_FILE_NAME_SUFFIX, templateFolder);
if (templatesFiles == null || templatesFiles.length == 0) {
// no other templates files in this folder
try {
log(Level.FINE, "[deleteTemplateFile] - templates folder is empty, deleting it.");
FileUtils.deleteDirectory(templateFolder);
} catch (final IOException e) {
log(Level.WARNING, "[deleteTemplateFile] - Failed to delete templates folder"
+ templateFolder, e);
}
} else {
log(Level.FINE, "[deleteTemplateFile] - templates folder is not empty.");
}
}
private File getTemplateFolder(final String templateName) {
final ComputeTemplate computeTemplate = cloud.getCloudCompute().getTemplates().get(templateName);
final String absoluteUploadDir = computeTemplate.getAbsoluteUploadDir();
final File parentFile = new File(absoluteUploadDir).getParentFile();
if (parentFile == null) {
log(Level.WARNING, "Failed to get template's folder for template " + templateName
+ ". The template's upload directory is " + absoluteUploadDir);
}
return parentFile;
}
/**
* Searches for a file with file name templateName-template.groovy in the given folder.
*
* @param templateName
* the name of the template (also the prefix of the wanted file).
* @return the found file or null.
*/
private File getTemplateFile(final String templateName, final File templateFolder) {
final String templateFileName = templateName + DSLUtils.TEMPLATE_DSL_FILE_NAME_SUFFIX;
log(Level.FINE, "Searching for template file " + templateFileName + " in "
+ templateFolder.getAbsolutePath());
final File[] listFiles = templateFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(final File dir, final String name) {
return templateFileName.equals(name);
}
});
final int length = listFiles.length;
if (length == 0) {
log(Level.WARNING, "Didn't find template file with name " + templateName + " at "
+ templateFolder.getAbsolutePath());
return null;
}
if (length > 1) {
log(Level.WARNING, "Found " + length + " templates files with name " + templateName
+ ": " + Arrays.toString(listFiles) + ". Returning the first one found.");
}
return listFiles[0];
}
/**
* Returns the name of the protocol used for communication with the rest server. If the security is secure (SSL)
* returns "https", otherwise returns "http".
*
* @param isSecureConnection
* Indicates whether SSL is used or not.
* @return "https" if this is a secure connection, "http" otherwise.
*/
private static String getRestProtocol(final boolean isSecureConnection) {
if (isSecureConnection) {
return "https";
}
return "http";
}
private RestClientInternal createRestClientInternal(final String host, final String port)
throws RestClientException {
final String protocol = getRestProtocol(permissionEvaluator != null);
final String baseUrl = protocol + "://" + IPUtils.getSafeIpAddress(host) + ":" + port;
final String apiVersion = PlatformVersion.getVersion();
try {
return new RestClientInternal(new URL(baseUrl), "", "", apiVersion);
} catch (final MalformedURLException e) {
throw MessagesUtils.createRestClientException(
ExceptionUtils.getFullStackTrace(e),
CloudifyErrorMessages.FAILED_CREATE_REST_CLIENT.getName(),
ExceptionUtils.getFullStackTrace(e));
}
}
private void log(final Level level, final String content) {
if (logger.isLoggable(level)) {
logger.log(level, content);
}
}
private void log(final Level level, final String content, final Throwable thrown) {
if (logger.isLoggable(level)) {
logger.log(level, content, thrown);
}
}
}