/*******************************************************************************
* 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 static org.cloudifysource.rest.ResponseConstants.FAILED_TO_LOCATE_LUS;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import net.jini.core.discovery.LookupLocator;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.cloudifysource.domain.Application;
import org.cloudifysource.domain.ComputeDetails;
import org.cloudifysource.domain.Service;
import org.cloudifysource.domain.cloud.Cloud;
import org.cloudifysource.dsl.internal.CloudifyConstants;
import org.cloudifysource.dsl.internal.CloudifyConstants.InvocationStatus;
import org.cloudifysource.dsl.internal.CloudifyErrorMessages;
import org.cloudifysource.dsl.internal.CloudifyMessageKeys;
import org.cloudifysource.dsl.internal.DSLApplicationCompilationResult;
import org.cloudifysource.dsl.internal.DSLServiceCompilationResult;
import org.cloudifysource.dsl.internal.DSLUtils;
import org.cloudifysource.dsl.internal.ServiceReader;
import org.cloudifysource.dsl.internal.packaging.Packager;
import org.cloudifysource.dsl.rest.request.InstallApplicationRequest;
import org.cloudifysource.dsl.rest.request.InstallServiceRequest;
import org.cloudifysource.dsl.rest.request.InvokeCustomCommandRequest;
import org.cloudifysource.dsl.rest.request.SetApplicationAttributesRequest;
import org.cloudifysource.dsl.rest.request.SetServiceAttributesRequest;
import org.cloudifysource.dsl.rest.request.SetServiceInstanceAttributesRequest;
import org.cloudifysource.dsl.rest.request.SetServiceInstancesRequest;
import org.cloudifysource.dsl.rest.request.UpdateApplicationAttributeRequest;
import org.cloudifysource.dsl.rest.response.ApplicationDescription;
import org.cloudifysource.dsl.rest.response.DeleteApplicationAttributeResponse;
import org.cloudifysource.dsl.rest.response.DeleteServiceAttributeResponse;
import org.cloudifysource.dsl.rest.response.DeleteServiceInstanceAttributeResponse;
import org.cloudifysource.dsl.rest.response.DeploymentEvent;
import org.cloudifysource.dsl.rest.response.DeploymentEvents;
import org.cloudifysource.dsl.rest.response.GetApplicationAttributesResponse;
import org.cloudifysource.dsl.rest.response.GetServiceAttributesResponse;
import org.cloudifysource.dsl.rest.response.GetServiceInstanceAttributesResponse;
import org.cloudifysource.dsl.rest.response.InstallApplicationResponse;
import org.cloudifysource.dsl.rest.response.InstallServiceResponse;
import org.cloudifysource.dsl.rest.response.InvokeInstanceCommandResponse;
import org.cloudifysource.dsl.rest.response.InvokeServiceCommandResponse;
import org.cloudifysource.dsl.rest.response.ServiceDescription;
import org.cloudifysource.dsl.rest.response.ServiceDetails;
import org.cloudifysource.dsl.rest.response.ServiceInstanceDetails;
import org.cloudifysource.dsl.rest.response.ServiceInstanceMetricsData;
import org.cloudifysource.dsl.rest.response.ServiceInstanceMetricsResponse;
import org.cloudifysource.dsl.rest.response.ServiceMetricsResponse;
import org.cloudifysource.dsl.rest.response.UninstallApplicationResponse;
import org.cloudifysource.dsl.rest.response.UninstallServiceResponse;
import org.cloudifysource.dsl.utils.ServiceUtils;
import org.cloudifysource.rest.ResponseConstants;
import org.cloudifysource.rest.RestConfiguration;
import org.cloudifysource.rest.controllers.helpers.ControllerHelper;
import org.cloudifysource.rest.controllers.helpers.PropertiesOverridesMerger;
import org.cloudifysource.rest.deploy.ApplicationDeployerRequest;
import org.cloudifysource.rest.deploy.ApplicationDeployerRunnable;
import org.cloudifysource.rest.deploy.DeploymentConfig;
import org.cloudifysource.rest.deploy.ElasticDeploymentCreationException;
import org.cloudifysource.rest.deploy.ElasticProcessingUnitDeploymentFactory;
import org.cloudifysource.rest.deploy.ElasticProcessingUnitDeploymentFactoryImpl;
import org.cloudifysource.rest.deploy.ServiceApplicationDependentProperties;
import org.cloudifysource.rest.events.EventsUtils;
import org.cloudifysource.rest.events.cache.AdminBasedGridServiceContainerProvider;
import org.cloudifysource.rest.events.cache.EventsCache;
import org.cloudifysource.rest.events.cache.EventsCacheKey;
import org.cloudifysource.rest.events.cache.EventsCacheValue;
import org.cloudifysource.rest.exceptions.ResourceNotFoundException;
import org.cloudifysource.rest.repo.UploadRepo;
import org.cloudifysource.rest.util.ApplicationDescriptionFactory;
import org.cloudifysource.rest.util.IsolationUtils;
import org.cloudifysource.rest.validators.InstallApplicationValidationContext;
import org.cloudifysource.rest.validators.InstallApplicationValidator;
import org.cloudifysource.rest.validators.InstallServiceValidationContext;
import org.cloudifysource.rest.validators.InstallServiceValidator;
import org.cloudifysource.rest.validators.SetServiceInstancesValidationContext;
import org.cloudifysource.rest.validators.SetServiceInstancesValidator;
import org.cloudifysource.rest.validators.UninstallApplicationValidationContext;
import org.cloudifysource.rest.validators.UninstallApplicationValidator;
import org.cloudifysource.rest.validators.UninstallServiceValidationContext;
import org.cloudifysource.rest.validators.UninstallServiceValidator;
import org.cloudifysource.security.CloudifyAuthorizationDetails;
import org.cloudifysource.security.CustomPermissionEvaluator;
import org.cloudifysource.utilitydomain.data.ServiceInstanceAttemptData;
import org.cloudifysource.utilitydomain.kvstorage.spaceentries.ApplicationCloudifyAttribute;
import org.cloudifysource.utilitydomain.kvstorage.spaceentries.InstanceCloudifyAttribute;
import org.cloudifysource.utilitydomain.kvstorage.spaceentries.ServiceCloudifyAttribute;
import org.jgrapht.DirectedGraph;
import org.jgrapht.alg.CycleDetector;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.traverse.TopologicalOrderIterator;
import org.openspaces.admin.Admin;
import org.openspaces.admin.AdminException;
import org.openspaces.admin.gsc.GridServiceContainer;
import org.openspaces.admin.gsm.GridServiceManager;
import org.openspaces.admin.internal.admin.InternalAdmin;
import org.openspaces.admin.internal.pu.DefaultProcessingUnitInstance;
import org.openspaces.admin.internal.pu.InternalProcessingUnit;
import org.openspaces.admin.internal.pu.InternalProcessingUnitInstance;
import org.openspaces.admin.internal.pu.elastic.GridServiceContainerConfig;
import org.openspaces.admin.pu.ProcessingUnit;
import org.openspaces.admin.pu.ProcessingUnitInstance;
import org.openspaces.admin.pu.ProcessingUnits;
import org.openspaces.admin.pu.elastic.ElasticStatefulProcessingUnitDeployment;
import org.openspaces.admin.pu.elastic.ElasticStatelessProcessingUnitDeployment;
import org.openspaces.admin.pu.elastic.config.ManualCapacityScaleConfigurer;
import org.openspaces.admin.pu.elastic.topology.ElasticDeploymentTopology;
import org.openspaces.admin.space.ElasticSpaceDeployment;
import org.openspaces.core.GigaSpace;
import org.openspaces.core.util.MemoryUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
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 org.springframework.web.bind.annotation.RequestParam;
/**
* This controller is responsible for retrieving information about deployments. It is also the entry point for deploying
* services and application. <br>
* <br>
* The response body will always return in a JSON representation of the
* {@link org.cloudifysource.dsl.rest.response.Response} Object. <br>
* A controller method may return the {@link org.cloudifysource.dsl.rest.response.Response} Object directly. in this
* case this return value will be used as the response body. Otherwise, an implicit wrapping will occur. the return
* value will be inserted into {@code Response#setResponse(Object)}. other fields of the
* {@link org.cloudifysource.dsl.rest.response.Response} object will be filled with default values. <br>
* <h1>Important</h1> {@code @ResponseBody} annotations are not permitted. <br>
* <br>
* <h1>Possible return values</h1> 200 - OK<br>
* 400 - controller throws an exception<br>
* 500 - Unexpected exception<br>
* <br>
*
* @see {@link org.cloudifysource.rest.interceptors.ApiVersionValidationAndRestResponseBuilderInterceptor}
* @author elip , ahmadm
* @since 2.5.0
*
*/
@Controller
@RequestMapping(value = "/{version}/deployments")
public class DeploymentsController extends BaseRestController {
private static final Logger logger = Logger.getLogger(DeploymentsController.class.getName());
private static final int MAX_NUMBER_OF_EVENTS = 100;
private static final int REFRESH_INTERVAL_MILLIS = 500;
private static final long WAIT_FOR_PU_SECONDS = 30;
private static final int WAIT_FOR_MANAGED_TIMEOUT_SECONDS = 10;
private static final int PU_DISCOVERY_TIMEOUT_SEC = 8;
private static final int LOCAL_CLOUD_INSTANCE_MEMORY_MB = 512;
@Autowired
private RestConfiguration restConfig;
@Autowired
private UploadRepo repo;
@Autowired
private InstallServiceValidator[] installServiceValidators = new InstallServiceValidator[0];
@Autowired
private UninstallServiceValidator[] uninstallServiceValidators = new UninstallServiceValidator[0];
@Autowired
private InstallApplicationValidator[] installApplicationValidators = new InstallApplicationValidator[0];
@Autowired
private UninstallApplicationValidator[] uninstallApplicationValidators = new UninstallApplicationValidator[0];
@Autowired
private SetServiceInstancesValidator[] setServiceInstancesValidators = new SetServiceInstancesValidator[0];
protected GigaSpace gigaSpace;
private Admin admin;
private CustomPermissionEvaluator permissionEvaluator;
private final ExecutorService serviceUndeployExecutor = Executors.newFixedThreadPool(10);
private EventsCache eventsCache;
private ControllerHelper controllerHelper;
private File extractedFodler;
/**
* Initialization.
* @throws RestErrorException
* If failed to create upload directory.
*/
@PostConstruct
public void init() throws RestErrorException {
gigaSpace = restConfig.getGigaSpace();
permissionEvaluator = restConfig.getPermissionEvaluator();
File restTempFolder = restConfig.getRestTempFolder();
repo.setBaseDir(restTempFolder);
repo.init();
this.admin = restConfig.getAdmin();
this.eventsCache = new EventsCache(admin);
this.controllerHelper = new ControllerHelper(gigaSpace, admin);
this.extractedFodler = new File(restTempFolder, CloudifyConstants.EXTRACTED_FILES_FOLDER_NAME);
extractedFodler.mkdirs();
extractedFodler.deleteOnExit();
}
/**
* Provides various meta data about the service.
*
* @param appName
* The application name.
* @param serviceName
* The service name.
* @return Meta data about the service.
* @throws ResourceNotFoundException
* Thrown in case the requested service does not exist.
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/metadata", method = RequestMethod.GET)
public ServiceDetails getServiceDetails(
@PathVariable final String appName,
@PathVariable final String serviceName)
throws ResourceNotFoundException {
final ProcessingUnit processingUnit = controllerHelper.getService(appName, serviceName);
final ServiceDetails serviceDetails = new ServiceDetails();
serviceDetails.setName(serviceName);
serviceDetails.setApplicationName(appName);
serviceDetails.setNumberOfInstances(processingUnit.getInstances().length);
final List<String> instanceNames = new ArrayList<String>();
for (final ProcessingUnitInstance instance : processingUnit.getInstances()) {
instanceNames.add(instance.getProcessingUnitInstanceName());
}
serviceDetails.setInstanceNames(instanceNames);
return serviceDetails;
}
/**
* Basic test to see if the rest is connected properly.
*
* @throws RestErrorException
* Thrown in case the rest does not identify any lookup services.
*/
@RequestMapping(value = "/testrest", method = RequestMethod.GET)
public void test() throws RestErrorException {
if (restConfig.getAdmin().getLookupServices().getSize() > 0) {
return;
}
final String groups = Arrays.toString(restConfig.getAdmin().getGroups());
final String locators = Arrays.toString(restConfig.getAdmin().getLocators());
throw new RestErrorException(FAILED_TO_LOCATE_LUS, groups, locators);
}
/******
* Waits for a single instance of a service to become available. NOTE: currently only uses service name as
* processing unit name.
*
* @param applicationName
* not used.
* @param serviceName
* the service name.
* @param timeout
* the timeout period to wait for the processing unit, and then the PU instance.
* @param timeUnit
* the time unit used to wait for the processing unit, and then the PU instance.
* @return true if instance is found, false if instance is not found in the specified period.
*/
public boolean waitForServiceInstance(
final String applicationName,
final String serviceName, final long timeout,
final TimeUnit timeUnit) {
// this should be a very fast lookup, since the service was already
// successfully deployed
final String absolutePUName = ServiceUtils.getAbsolutePUName(
applicationName, serviceName);
final ProcessingUnit pu = restConfig.getAdmin().getProcessingUnits().waitFor(
absolutePUName, timeout, timeUnit);
if (pu == null) {
return false;
}
// ignore the time spent on PU lookup, as it should be failry short.
return pu.waitFor(1, timeout, timeUnit);
}
/**
* Retrieves events based on deployment id. The deployment id may be of service or application. In the case of an
* application deployment id, all services events will be returned.
*
* @param deploymentId
* The deployment id given at install time.
* @param from
* The starting index.
* @param to
* The finish index.
* @return {@link org.cloudifysource.dsl.rest.response.DeploymentEvents} - The deployment events.
* @throws Throwable
* Thrown in case of any error.
*/
@RequestMapping(value = "{deploymentId}/events", method = RequestMethod.GET)
public DeploymentEvents getDeploymentEvents(@PathVariable final String deploymentId,
@RequestParam(required = false, defaultValue = "1") final int from,
@RequestParam(required = false, defaultValue = "-1") final int to)
throws Throwable {
if (deploymentId == null) {
throw new RestErrorException(CloudifyErrorMessages.MISSING_DEPLOYMENT_ID.getName(), "getDeploymentEvents");
}
verifyDeploymentIdExists(deploymentId);
// limit the default number of events returned to the client.
int actualTo = to;
if (to == -1) {
actualTo = from + MAX_NUMBER_OF_EVENTS;
}
EventsCacheKey key = new EventsCacheKey(deploymentId);
logger.fine(EventsUtils.getThreadId() + " Received request for events [" + from + "]-[" + to + "] . key : " +
key);
EventsCacheValue value;
try {
value = eventsCache.get(key);
} catch (final ExecutionException e) {
throw e.getCause();
}
// we don't want another request to modify our object during this calculation.
synchronized (value.getMutex()) {
if (!EventsUtils.eventsPresent(value.getEvents(), from, actualTo)) {
// enforce time restriction on refresh operations.
long now = System.currentTimeMillis();
if (now - value.getLastRefreshedTimestamp() > REFRESH_INTERVAL_MILLIS) {
// refresh the cache for this deployment.
eventsCache.refresh(key);
}
} else {
logger.fine(EventsUtils.getThreadId() + " Found all relevant events in cache.");
}
// return the events. this MAY or MAY NOT be the complete set of events requested.
// request for specific events is treated as best effort. no guarantees all events are returned.
DeploymentEvents deploymentEvents = EventsUtils.extractDesiredEvents(value.getEvents(), from, actualTo);
logger.finest("Returning events " + deploymentEvents + " for deployment id " + deploymentId + " to the " +
"client");
return deploymentEvents;
}
}
/********************************
* Returns the last event for a specific operation.
*
* @param deploymentId
* the operation ID.
* @return the last event received for this operation. May be an empty set.
* @throws Throwable
* in case of an error while retrieving events.
*/
@RequestMapping(value = "{deploymentId}/events/last", method = RequestMethod.GET)
public DeploymentEvent getLastDeploymentEvent(@PathVariable final String deploymentId)
throws Throwable {
verifyDeploymentIdExists(deploymentId);
logger.fine("Received request for last deployment event for deployment " + deploymentId);
EventsCacheKey key = new EventsCacheKey(deploymentId);
EventsCacheValue value = eventsCache.get(key);
int lastEventIndex = value.getLastEventIndex();
List<DeploymentEvent> events =
getDeploymentEvents(deploymentId, lastEventIndex, lastEventIndex + 1).getEvents();
switch (events.size()) {
case 0:
// no events. return empty
return new DeploymentEvent();
case 1:
// we only have the old last event. return this one.
return events.get(0);
case 2:
// we have the old last event plus a new one. return the new one.
return events.get(1);
default:
throw new IllegalStateException("Unexpected event list size for request of events [" + lastEventIndex +
"]-[" + (lastEventIndex + 1) + "] : " + events.size());
}
}
private void verifyDeploymentIdExists(final String deploymentId)
throws ResourceNotFoundException {
if (logger.isLoggable(Level.FINE)) {
logger.fine("[validateDeploymentIdExists] validating deploymentId:" + deploymentId);
}
/*
* events cache.
*/
EventsCacheKey key = new EventsCacheKey(deploymentId);
EventsCacheValue value = eventsCache.getIfExists(key);
if (value == null) {
/*
* processing units.
*/
if (logger.isLoggable(Level.FINE)) {
logger.fine("[validateDeploymentIdExists] - "
+ "The deploymentId doesn't exist in the eventsCache, "
+ "searching for a processing unit with that deployment id [" + deploymentId + "]");
}
ProcessingUnits processingUnits = admin.getProcessingUnits();
for (ProcessingUnit pu : processingUnits) {
String puDeploymentId = (String) pu.getBeanLevelProperties().getContextProperties()
.get(CloudifyConstants.CONTEXT_PROPERTY_DEPLOYMENT_ID);
if (deploymentId.equals(puDeploymentId)) {
return;
}
}
throw new ResourceNotFoundException("Deployment id " + deploymentId);
}
}
/**
* Sets application level attributes for the given application.
*
* @param appName
* The application name.
* @param attributesRequest
* Request body, specifying the attributes names and values.
* @throws RestErrorException
* Thrown in case the request body is empty.
* @throws ResourceNotFoundException
* Thrown in case the application does not exist.
*/
@RequestMapping(value = "/{appName}/attributes", method = RequestMethod.POST)
public void setApplicationAttributes(
@PathVariable final String appName,
@RequestBody final SetApplicationAttributesRequest attributesRequest)
throws RestErrorException, ResourceNotFoundException {
// valid application
controllerHelper.getApplication(appName);
if (attributesRequest == null || attributesRequest.getAttributes() == null) {
throw new RestErrorException(CloudifyMessageKeys.EMPTY_REQUEST_BODY_ERROR.getName());
}
// set attributes
controllerHelper.setAttributes(appName, null, null, attributesRequest.getAttributes());
}
/**
* Sets the number of instances of a service to a given value. This operation is only allowed for services that are
* marked as Elastic (defined in the recipe). Note: set instances does NOT support multi-tenancy. You should only
* use set-instances with dedicated machine SLA. Manually scaling a multi-tenant service will cause unexpected
* side-effect.
*
* @param applicationName
* the application name.
*
* @param serviceName
* the service name.
*
* @param request
* the request details.
*
* @throws RestErrorException
* When the service is not elastic.
* @throws ResourceNotFoundException
* If the service is not found.
*/
@RequestMapping(value = "/{applicationName}/services/{serviceName}/count",
method = RequestMethod.POST)
@PreAuthorize("isFullyAuthenticated() and hasPermission(#authGroups, 'deploy')")
public void setServiceInstances(
@PathVariable final String applicationName,
@PathVariable final String serviceName,
@RequestBody final SetServiceInstancesRequest request)
throws RestErrorException, ResourceNotFoundException {
if (logger.isLoggable(Level.INFO)) {
logger.info("Scaling request for service: " + applicationName + "." + serviceName
+ " to " + request.getCount() + " instances");
}
final ProcessingUnit pu = this.controllerHelper.getService(applicationName, serviceName);
if (pu == null) {
throw new RestErrorException(
ResponseConstants.FAILED_TO_LOCATE_SERVICE, serviceName);
}
validateSetInstances(applicationName, serviceName, request, pu);
if (permissionEvaluator != null) {
final String puAuthGroups = pu.getBeanLevelProperties().getContextProperties().
getProperty(CloudifyConstants.CONTEXT_PROPERTY_AUTH_GROUPS);
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
final CloudifyAuthorizationDetails authDetails = new CloudifyAuthorizationDetails(authentication);
permissionEvaluator.verifyPermission(authDetails, puAuthGroups, "deploy");
}
final int count = request.getCount();
logger.info("Scaling " + pu.getName() + " to " + count + " instances");
Cloud cloud = restConfig.getCloud();
if (cloud == null) {
// Manual scale by number of instances
pu.scale(new ManualCapacityScaleConfigurer().memoryCapacity(
LOCAL_CLOUD_INSTANCE_MEMORY_MB * count, MemoryUnit.MEGABYTES).create());
} else {
final Map<String, String> properties = ((InternalProcessingUnit) pu).getElasticProperties();
final GridServiceContainerConfig gscConfig = new GridServiceContainerConfig(properties);
final long cloudExternalProcessMemoryInMB = gscConfig.getMaximumMemoryCapacityInMB();
pu.scale(ElasticScaleConfigFactory.createManualCapacityScaleConfig(
(int) (cloudExternalProcessMemoryInMB * count), 0,
request.isLocationAware(), true));
}
}
private void validateSetInstances(final String applicationName, final String serviceName,
final SetServiceInstancesRequest request, final ProcessingUnit pu) throws RestErrorException {
SetServiceInstancesValidationContext validationContext = new SetServiceInstancesValidationContext();
validationContext.setApplicationName(applicationName);
validationContext.setServiceName(serviceName);
validationContext.setCloud(this.restConfig.getCloud());
validationContext.setAdmin(admin);
validationContext.setRequest(request);
validationContext.setProcessingUnit(pu);
for (SetServiceInstancesValidator validator : setServiceInstancesValidators) {
validator.validate(validationContext);
}
}
/**
* Delete an instance level attribute.
*
* @param appName
* The application name.
* @param serviceName
* The service name.
* @param instanceId
* The instance id.
* @param attributeName
* The attribute name.
* @return The previous value for this attribute in the response.
* @throws ResourceNotFoundException
* Thrown in case the requested service or service instance does not exist.
* @throws RestErrorException
* Thrown in case the requested attribute name is empty.
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/instances/{instanceId}/attributes/{attributeName}",
method = RequestMethod.DELETE)
public DeleteServiceInstanceAttributeResponse deleteServiceInstanceAttribute(
@PathVariable final String appName,
@PathVariable final String serviceName,
@PathVariable final Integer instanceId,
@PathVariable final String attributeName)
throws ResourceNotFoundException, RestErrorException {
// valid service
controllerHelper.getService(appName, serviceName);
// logger - request to delete attributes
if (logger.isLoggable(Level.FINER)) {
logger.finer("received request to delete attribute "
+ attributeName + " of instance Id " + instanceId + " of service "
+ ServiceUtils.getAbsolutePUName(appName, serviceName)
+ " of application " + appName);
}
// get delete attribute returned previous value
final Object previous = controllerHelper.deleteAttribute(appName, serviceName, instanceId, attributeName);
// create response object
final DeleteServiceInstanceAttributeResponse siar = new DeleteServiceInstanceAttributeResponse();
// set previous value
siar.setPreviousValue(previous);
// return response object
return siar;
}
/**
* Provides various meta data about the service instance.
*
* @param appName
* The application name.
* @param serviceName
* The service name.
* @param instanceId
* The instance id.
* @return Meta data about the service instance.
* @throws ResourceNotFoundException
* Thrown in case the service or service instance does not exist.
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/instances/{instanceId}/metadata",
method = RequestMethod.GET)
public ServiceInstanceDetails getServiceInstanceDetails(
@PathVariable final String appName,
@PathVariable final String serviceName,
@PathVariable final Integer instanceId)
throws ResourceNotFoundException {
// get processingUnit instance
final ProcessingUnitInstance pui = controllerHelper.getServiceInstance(appName, serviceName, instanceId);
// get USM details
final org.openspaces.pu.service.ServiceDetails usmDetails = pui.getServiceDetailsByServiceId("USM");
// get attributes details
final Map<String, Object> puiAttributes = usmDetails.getAttributes();
// get private ,public IP
final String privateIp =
controllerHelper.getServiceInstanceEnvVariable(pui, CloudifyConstants.GIGASPACES_AGENT_ENV_PRIVATE_IP);
final String publicIp =
controllerHelper.getServiceInstanceEnvVariable(pui, CloudifyConstants.GIGASPACES_AGENT_ENV_PUBLIC_IP);
// machine details
final String hardwareId =
controllerHelper.getServiceInstanceEnvVariable(pui, CloudifyConstants.GIGASPACES_CLOUD_HARDWARE_ID);
final String machineId =
controllerHelper.getServiceInstanceEnvVariable(pui, CloudifyConstants.GIGASPACES_CLOUD_MACHINE_ID);
final String imageId =
controllerHelper.getServiceInstanceEnvVariable(pui, CloudifyConstants.GIGASPACES_CLOUD_IMAGE_ID);
final String templateName =
controllerHelper.getServiceInstanceEnvVariable(pui, CloudifyConstants.GIGASPACES_CLOUD_TEMPLATE_NAME);
// return new instance
final ServiceInstanceDetails sid = new ServiceInstanceDetails();
// set service instance details
sid.setApplicationName(appName);
sid.setServiceName(serviceName);
sid.setServiceInstanceName(pui.getName());
// set service instance machine details
sid.setHardwareId(hardwareId);
sid.setImageId(imageId);
sid.setInstanceId(instanceId);
sid.setMachineId(machineId);
sid.setPrivateIp(privateIp);
sid.setProcessDetails(puiAttributes);
sid.setPublicIp(publicIp);
sid.setTemplateName(templateName);
return sid;
}
/**
* Deploys an application to the service grid. An application is consisted of a group of services that might have
* dependencies between themselves. The application will be deployed according to the dependency order defined in
* the application file and deployed asynchronously if possible.
*
* Prior to calling this method, the application recipe must be packed and uploaded through the upload controller.
* Each upload yields a unique key, used by this method.
* Uploading the recipe is mandatory.
* Optional uploads allowing to customize the deployment:
* cloud configuration – File or directory containing cloud configuration to be used for this application
* overrides – File containing properties to be used to override the properties of the application and its services
* cloud overrides – File containing properties to be used to override the current cloud configuration for this
* application and its services
*
* The request body values are:
* Application name – mandatory – The application name
* applcationFileUploadKey – mandatory – key of the uploaded packed recipe file
* cloudConfigurationUploadKey – optional – the key of the uploaded cloud configuration
* applicationOverridesUploadKey – optional – the key of the uploaded overrides file
* cloudOverridesUploadKey – optional – the key of the uploaded cloud overrides file
* authGroups – optional – Used when bootstrapped in secured mode. Set the auth-groups the application will be
* exposed to. If empty will use the calling user's auth-groups
* selfHealing – mandatory – if true, there will be an attempt to restart the recipe in case a problem occurred in
* its life-cycle. Otherwise, no attempt to recover will made if the recipe fails to execute
* debugAll – optional – defaults to False. This flag is used for recipe debugging. It indicates whether all
* lifecycle events should have a breakpoint.
* debugEvents – optional. This flag is used for recipe debugging. Indicates which lifecycle events should have a
* breakpoint.
* debugMode – optional. This flag is used for recipe debugging. It indicates the debug mode to use (either
* 'instead', 'after' or 'onError')
*
* @param appName
* The application name.
* @param request
* install application request.
* @return an install application response.
* @throws RestErrorException
* indicates a failure to install the application
*
*/
@RequestMapping(value = "/{appName}", method = RequestMethod.POST)
@PreAuthorize("isFullyAuthenticated() and hasPermission(#request.getAuthGroups(), 'deploy')")
public InstallApplicationResponse installApplication(
@PathVariable final String appName,
@RequestBody final InstallApplicationRequest request)
throws RestErrorException {
if (logger.isLoggable(Level.FINE)) {
logger.fine("[installApplication] starting deployment of application: " + appName);
}
// get the application file
final String applcationFileUploadKey = request.getApplcationFileUploadKey();
final File applicationFile = getFromRepo(applcationFileUploadKey,
CloudifyMessageKeys.WRONG_APPLICTION_FILE_UPLOAD_KEY.getName(), appName);
// get the application overrides file
final String applicationOverridesFileKey = request.getApplicationOverridesUploadKey();
final File applicationOverridesFile = getFromRepo(applicationOverridesFileKey,
CloudifyMessageKeys.WRONG_APPLICTION_OVERRIDES_FILE_UPLOAD_KEY.getName(), appName);
// construct application object
DSLApplicationCompilationResult appReaderResult;
try {
appReaderResult = ServiceReader.getApplicationFromFile(applicationFile, applicationOverridesFile);
} catch (final Exception e) {
throw new RestErrorException("Failed reading application file." + " Reason: " + e.getMessage(), e);
}
Application application = appReaderResult.getApplication();
application.setName(appName);
// perform validations
validateInstallApplication(application, request, appReaderResult.getApplicationOverridesFile());
// update effective authGroups
String effectiveAuthGroups = getEffectiveAuthGroups(request.getAuthGroups());
request.setAuthGroups(effectiveAuthGroups);
// create install dependency order.
final List<Service> services = createServiceDependencyOrder(application);
// create a deployment ID that would be used across all services.
final String deploymentID = UUID.randomUUID().toString();
// remove any existing attributes for this application.
deleteApplicationScopeAttributes(request.getApplicationName());
// create the installer
ApplicationDeployerRequest applicationDeployerRequest = new ApplicationDeployerRequest();
applicationDeployerRequest.setAppDir(appReaderResult.getApplicationDir());
applicationDeployerRequest.setAppFile(appReaderResult.getApplicationFile());
applicationDeployerRequest.setAppPropertiesFile(appReaderResult.getApplicationPropertiesFile());
applicationDeployerRequest.setAppOverridesFile(appReaderResult.getApplicationOverridesFile());
applicationDeployerRequest.setController(this);
applicationDeployerRequest.setDeploymentID(deploymentID);
applicationDeployerRequest.setRequest(request);
applicationDeployerRequest.setServices(services);
final ApplicationDeployerRunnable installer = new ApplicationDeployerRunnable(applicationDeployerRequest);
// install
if (installer.isAsyncInstallPossibleForApplication()) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("[installApplication] Async install is possible for the application, running the installer");
}
installer.run();
} else {
if (logger.isLoggable(Level.FINE)) {
logger.fine("[installApplication] Async install not possible for the application, calling the installer on another thread");
}
restConfig.getExecutorService().execute(installer);
}
// we wait for the pu so that after this method returns
// we would be able to poll for events safely using the deployment-id.
String firstServiceName = services.get(0).getName();
String firstPuName = ServiceUtils.getAbsolutePUName(appName, firstServiceName);
if (logger.isLoggable(Level.FINE)) {
logger.fine("[installApplication] waiting for PU: " + firstPuName);
}
final boolean firstPuCreated = waitForPu(appName, firstServiceName, WAIT_FOR_PU_SECONDS, TimeUnit.SECONDS);
if (!firstPuCreated) {
throw new RestErrorException(CloudifyErrorMessages.FAILED_WAIT_FOR_PU.getName(), firstPuName);
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("[installApplication] found PU: " + firstPuName);
}
// creating response
final InstallApplicationResponse response = new InstallApplicationResponse();
response.setDeploymentID(deploymentID);
return response;
}
private boolean waitForPu(final String applicationName,
final String serviceName,
final long timeout,
final TimeUnit timeUnit) {
final String absolutePuName = ServiceUtils.getAbsolutePUName(applicationName, serviceName);
if (logger.isLoggable(Level.FINE)) {
logger.fine("[waitForPu] waiting for processing unit with name " + absolutePuName
+ " to be created.");
}
return admin.getProcessingUnits().waitFor(absolutePuName, timeout, timeUnit) != null;
}
private void validateInstallApplication(
final Application application,
final InstallApplicationRequest request,
final File applicationOverridesFile)
throws RestErrorException {
// get cloud overrides file
final File cloudOverridesFile = getFromRepo(
request.getCloudOverridesUploadKey(),
CloudifyMessageKeys.WRONG_CLOUD_OVERRIDES_UPLOAD_KEY.getName(),
application.getName());
// get cloud configuration file and content
final File cloudConfigurationFile = getFromRepo(
request.getCloudConfigurationUploadKey(),
CloudifyMessageKeys.WRONG_CLOUD_CONFIGURATION_UPLOAD_KEY.getName(),
application.getName());
// create validation context
final InstallApplicationValidationContext validationContext = new InstallApplicationValidationContext();
validationContext.setApplication(application);
validationContext.setCloud(restConfig.getCloud());
validationContext.setAdmin(admin);
validationContext.setApplicationOverridesFile(applicationOverridesFile);
validationContext.setCloudConfigurationFile(cloudConfigurationFile);
validationContext.setCloudOverridesFile(cloudOverridesFile);
validationContext.setDebugMode(request.getDebugMode());
validationContext.setDebugEvents(request.getDebugEvents());
validationContext.setDebugAll(request.isDebugAll());
// call validate for each install application validator.
for (final InstallApplicationValidator validator : installApplicationValidators) {
validator.validate(validationContext);
}
}
/**
*
* @param appName
* .
* @return {@link org.cloudifysource.dsl.rest.response.ApplicationDescription}.
* @throws ResourceNotFoundException .
*/
@RequestMapping(value = "/applications/{appName}/description", method = RequestMethod.GET)
public ApplicationDescription getApplicationDescription(
@PathVariable final String appName)
throws ResourceNotFoundException {
final ApplicationDescriptionFactory appDescriptionFactory =
new ApplicationDescriptionFactory(restConfig.getAdmin());
// Check that Application exists
final org.openspaces.admin.application.Application app = this.restConfig.getAdmin().getApplications()
.waitFor(appName, 10, TimeUnit.SECONDS);
if (app == null) {
throw new ResourceNotFoundException(appName);
}
// if security is turned on - verify the current user is allowed to view this application
if (permissionEvaluator != null) {
final ProcessingUnit[] pus = app.getProcessingUnits().getProcessingUnits();
if (pus.length > 0) {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
final CloudifyAuthorizationDetails authDetails =
new CloudifyAuthorizationDetails(authentication);
// all the application PUs are supposed to have the same auth-groups setting so we use the first.
final String puAuthGroups = pus[0].getBeanLevelProperties().getContextProperties().
getProperty(CloudifyConstants.CONTEXT_PROPERTY_AUTH_GROUPS);
try {
permissionEvaluator.verifyPermission(authDetails, puAuthGroups, "view");
} catch (AccessDeniedException e) {
// since this is request to "view" an object, access denied should appear as "resource not found"
throw new ResourceNotFoundException(appName);
}
}
}
return appDescriptionFactory.getApplicationDescription(appName);
}
/**
* @return List of {@link org.cloudifysource.dsl.rest.response.ApplicationDescription} objects.
*/
@RequestMapping(value = "/applications/description", method = RequestMethod.GET)
@PostFilter("hasPermission(filterObject, 'view')")
public List<ApplicationDescription> getApplicationDescriptions() {
final ApplicationDescriptionFactory appDescriptionFactory =
new ApplicationDescriptionFactory(restConfig.getAdmin());
return appDescriptionFactory.getApplicationDescriptions();
}
private List<ProcessingUnit> createUninstallOrder(
final ProcessingUnit[] pus,
final String applicationName) {
// TODO: Refactor this - merge with createServiceOrder, as methods are
// very similar
final DirectedGraph<ProcessingUnit, DefaultEdge> graph = new DefaultDirectedGraph<ProcessingUnit, DefaultEdge>(
DefaultEdge.class);
for (final ProcessingUnit processingUnit : pus) {
graph.addVertex(processingUnit);
}
final Map<String, ProcessingUnit> puByName = new HashMap<String, ProcessingUnit>();
for (final ProcessingUnit processingUnit : pus) {
puByName.put(processingUnit.getName(), processingUnit);
}
for (final ProcessingUnit processingUnit : pus) {
final String dependsOn = (String) processingUnit
.getBeanLevelProperties().getContextProperties()
.get(CloudifyConstants.CONTEXT_PROPERTY_DEPENDS_ON);
if (dependsOn == null) {
logger.warning("Could not find the "
+ CloudifyConstants.CONTEXT_PROPERTY_DEPENDS_ON
+ " property for processing unit "
+ processingUnit.getName());
} else {
final String[] dependencies = dependsOn.replace("[", "")
.replace("]", "").split(",");
for (final String puName : dependencies) {
final String normalizedPuName = puName.trim();
if (normalizedPuName.length() > 0) {
final ProcessingUnit dependency = puByName
.get(normalizedPuName);
if (dependency == null) {
logger.severe("Could not find Processing Unit "
+ normalizedPuName
+ " that Processing Unit "
+ processingUnit.getName() + " depends on");
} else {
// the reverse to the install order.
graph.addEdge(processingUnit, dependency);
}
}
}
}
}
final CycleDetector<ProcessingUnit, DefaultEdge> cycleDetector =
new CycleDetector<ProcessingUnit, DefaultEdge>(
graph);
final boolean containsCycle = cycleDetector.detectCycles();
if (containsCycle) {
logger.warning("Detected a cycle in the dependencies of application: "
+ applicationName
+ " while preparing to uninstall."
+ " The service in this application will be uninstalled in a random order");
return Arrays.asList(pus);
}
final TopologicalOrderIterator<ProcessingUnit, DefaultEdge> iterator =
new TopologicalOrderIterator<ProcessingUnit, DefaultEdge>(graph);
final List<ProcessingUnit> orderedList = new ArrayList<ProcessingUnit>();
while (iterator.hasNext()) {
final ProcessingUnit nextPU = iterator.next();
if (!orderedList.contains(nextPU)) {
orderedList.add(nextPU);
}
}
// Collections.reverse(orderedList);
return orderedList;
}
private List<Service> createServiceDependencyOrder(final org.cloudifysource.domain.Application application) {
final DirectedGraph<Service, DefaultEdge> graph = new DefaultDirectedGraph<Service, DefaultEdge>(
DefaultEdge.class);
final Map<String, Service> servicesByName = new HashMap<String, Service>();
final List<Service> services = application.getServices();
for (final Service service : services) {
// keep a map of names to services
servicesByName.put(service.getName(), service);
// and create the graph node
graph.addVertex(service);
}
for (final Service service : services) {
final List<String> dependsList = service.getDependsOn();
if (dependsList != null) {
for (final String depends : dependsList) {
final Service dependency = servicesByName.get(depends);
if (dependency == null) {
throw new IllegalArgumentException("Dependency '"
+ depends + "' of service: "
+ service.getName() + " was not found");
}
graph.addEdge(dependency, service);
}
}
}
final CycleDetector<Service, DefaultEdge> cycleDetector = new CycleDetector<Service, DefaultEdge>(
graph);
final boolean containsCycle = cycleDetector.detectCycles();
if (containsCycle) {
final Set<Service> servicesInCycle = cycleDetector.findCycles();
final StringBuilder sb = new StringBuilder();
boolean first = true;
for (final Service service : servicesInCycle) {
if (!first) {
sb.append(",");
} else {
first = false;
}
sb.append(service.getName());
}
final String cycleString = sb.toString();
// NOTE: This is not exactly how the cycle detector works. The
// returned list is the vertex set for the subgraph of all cycles.
// So if there are multiple cycles, the list will contain the
// members of all of them.
throw new IllegalArgumentException(
"The dependency graph of application: "
+ application.getName()
+ " contains one or more cycles. "
+ "The services that form a cycle are part of the following group: "
+ cycleString);
}
final TopologicalOrderIterator<Service, DefaultEdge> iterator =
new TopologicalOrderIterator<Service, DefaultEdge>(graph);
final List<Service> orderedList = new ArrayList<Service>();
while (iterator.hasNext()) {
orderedList.add(iterator.next());
}
return orderedList;
}
/**
* Executes an install service request onto the grid. This method is not synchronous, it does not wait for the
* installation to complete.
*
* @param appName
* The application name this service belongs to.
* @param serviceName
* The service name.
* @param request
* Request body, specifying all the needed parameters for the service.
* @return An instance of {@link InstallServiceResponse} containing a deployment id, with which you can query for
* installation events and status.
* @throws RestErrorException
* Thrown in case an error happened before installation begins.
*/
@RequestMapping(value = "/{appName}/services/{serviceName}", method = RequestMethod.POST)
@PreAuthorize("isFullyAuthenticated() and hasPermission(#request.getAuthGroups(), 'deploy')")
public InstallServiceResponse installService(
@PathVariable final String appName,
@PathVariable final String serviceName,
@RequestBody final InstallServiceRequest request)
throws RestErrorException {
logger.info("[installService] - installing service " + serviceName);
final String absolutePuName = ServiceUtils.getAbsolutePUName(appName, serviceName);
// validate upload key
String uploadKey = request.getServiceFolderUploadKey();
if (StringUtils.isBlank(uploadKey)) {
throw new RestErrorException(CloudifyErrorMessages.UPLOAD_KEY_PARAMETER_MISSING.getName());
}
// get service packed folder
final File servicePackedFolder = getFromRepo(uploadKey,
CloudifyMessageKeys.WRONG_SERVICE_FOLDER_UPLOAD_KEY.getName(), absolutePuName);
// extract the service folder and working directory
File serviceDir;
try {
serviceDir = ServiceReader.extractProjectFileToDir(servicePackedFolder, absolutePuName, extractedFodler);
} catch (final IOException e) {
logger.log(Level.WARNING, "Failed to extract project file ["
+ servicePackedFolder.getAbsolutePath() + "] to new directory " + absolutePuName
+ " [under " + extractedFodler.getAbsolutePath() + "]", e);
throw new RestErrorException(CloudifyMessageKeys.FAILED_TO_EXTRACT_PROJECT_FILE.getName(), absolutePuName);
}
// get overrides file
final File serviceOverridesFile = getFromRepo(request.getServiceOverridesUploadKey(),
CloudifyMessageKeys.WRONG_SERVICE_OVERRIDES_UPLOAD_KEY.getName(), absolutePuName);
// read the service
File workingProjectDir = new File(serviceDir, "ext");
final DSLServiceCompilationResult readServiceResult = readService(
workingProjectDir,
request.getServiceFileName(),
absolutePuName,
null,
serviceOverridesFile);
Service service = readServiceResult.getService();
// perform validations
logger.finest("[installService] - performing validations for service " + serviceName);
File overridesFile = readServiceResult.getServiceOverridesFile();
validateInstallService(
request,
service,
absolutePuName,
overridesFile);
// merge properties with overrides files
File propertiesFile = readServiceResult.getServicePropertiesFile();
if (propertiesFile == null) {
propertiesFile = DSLUtils.getPropertiesFile(workingProjectDir);
}
PropertiesOverridesMerger merger = new PropertiesOverridesMerger(
propertiesFile,
null /* application properties file */,
propertiesFile,
overridesFile);
merger.merge();
File updatedPackedFile = servicePackedFolder;
if (merger.isMerged()) {
// re-pack the service folder after merge
try {
logger.finest("[installService] - re-pack directory with the updated properties file after merge.");
updatedPackedFile = Packager.createZipFile(absolutePuName, serviceDir);
} catch (IOException e) {
logger.log(Level.WARNING, "Failed to re-pack service folder [" + serviceDir.getAbsolutePath()
+ "]. Reason: " + e.getMessage());
throw new RestErrorException(CloudifyErrorMessages.FAILED_PACKING_SERVICE_FOLDER.getName(), serviceDir);
}
}
// set the new authentication groups
String effectiveAuthGroups = getEffectiveAuthGroups(request.getAuthGroups());
request.setAuthGroups(effectiveAuthGroups);
// install the service
final String deploymentID = UUID.randomUUID().toString();
return installServiceInternal(
appName,
serviceName,
request,
deploymentID,
null,
service,
updatedPackedFile);
}
/**
* An internal implementation for installing a service.
*
* @param appName
* Application name.
* @param serviceName
* Service name.
* @param request
* Install service request.
* @param deploymentID
* the application deployment ID.
* @param serviceProps
* service properties dependent on the application.
* @param service
* the service object.
* @param packedFile
* the service packed file.
* @return an install service response.
* @throws RestErrorException .
*/
public InstallServiceResponse installServiceInternal(
final String appName,
final String serviceName,
final InstallServiceRequest request,
final String deploymentID,
final ServiceApplicationDependentProperties serviceProps,
final Service service,
final File packedFile)
throws RestErrorException {
logger.fine("[installServiceInternal] started internal service install of: " + serviceName);
final String absolutePuName = ServiceUtils.getAbsolutePUName(appName, serviceName);
// update template name
final String templateName = getTempalteNameFromService(service);
// get cloud overrides file
final File cloudOverridesFile = getFromRepo(
request.getCloudOverridesUploadKey(),
CloudifyMessageKeys.WRONG_CLOUD_OVERRIDES_UPLOAD_KEY.getName(),
absolutePuName);
// get cloud configuration file and content
final File cloudConfigurationFile = getFromRepo(
request.getCloudConfigurationUploadKey(),
CloudifyMessageKeys.WRONG_CLOUD_CONFIGURATION_UPLOAD_KEY.getName(),
absolutePuName);
final byte[] cloudConfigurationContents = getCloudConfigurationContent(cloudConfigurationFile, absolutePuName);
// update effective authGroups
String effectiveAuthGroups = getEffectiveAuthGroups(request.getAuthGroups());
request.setAuthGroups(effectiveAuthGroups);
String cloudOverrides = null;
try {
if (cloudOverridesFile != null) {
cloudOverrides = FileUtils.readFileToString(cloudOverridesFile);
}
} catch (IOException e) {
throw new RestErrorException("Failed reading cloud overrides file.", e);
}
// remove any leftover service attributes.
deleteServiceAttributes(appName, serviceName);
// deploy
final DeploymentConfig deployConfig = new DeploymentConfig();
final String locators = extractLocators(restConfig.getAdmin());
final Cloud cloud = restConfig.getCloud();
deployConfig.setCloudConfig(cloudConfigurationContents);
deployConfig.setDeploymentId(deploymentID);
deployConfig.setAuthGroups(effectiveAuthGroups);
deployConfig.setAbsolutePUName(absolutePuName);
deployConfig.setCloudOverrides(cloudOverrides);
deployConfig.setCloud(cloud);
deployConfig.setPackedFile(packedFile);
deployConfig.setTemplateName(templateName);
deployConfig.setApplicationName(appName);
deployConfig.setInstallRequest(request);
deployConfig.setLocators(locators);
deployConfig.setService(service);
// create elastic deployment object.
final ElasticProcessingUnitDeploymentFactory fac =
new ElasticProcessingUnitDeploymentFactoryImpl(this.restConfig);
final ElasticDeploymentTopology deployment;
try {
deployment = fac.create(deployConfig);
} catch (ElasticDeploymentCreationException e) {
throw new RestErrorException("Failed creating deployment object.", e);
}
try {
logger.fine("[installServiceInternal] deploying and waiting for : " + serviceName);
ProcessingUnit processingUnit = deployAndWait(serviceName, deployment);
// save a reference to the processing unit in the events cache.
// this is for easy container discovery during events polling from clients.
logger.fine("[installServiceInternal] populating events cache with deployment ID: " + deploymentID);
populateEventsCache(deploymentID, processingUnit);
} catch (final TimeoutException e) {
throw new RestErrorException("Timed out waiting for deployment.", e);
}
final InstallServiceResponse installServiceResponse = new InstallServiceResponse();
installServiceResponse.setDeploymentID(deploymentID);
logger.fine("[installServiceInternal] installServiceInternal completed PU deployment for " + serviceName
+ ", the service deployment is just starting!");
return installServiceResponse;
}
/**
*
* @param appName
* .
* @param serviceName
* .
* @return .
* @throws ResourceNotFoundException .
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/description", method = RequestMethod.GET)
public ServiceDescription getServiceDescription(
@PathVariable final String appName,
@PathVariable final String serviceName)
throws ResourceNotFoundException {
final ApplicationDescriptionFactory appDescriptionFactory =
new ApplicationDescriptionFactory(restConfig.getAdmin());
return appDescriptionFactory.
getServiceDescription(ServiceUtils.getAbsolutePUName(appName, serviceName));
}
/**
* Retrieves a list of service descriptions belonging to a specific deployment.
*
* @param deploymentId
* The deployment id.
* @return The services description.
* @throws RestErrorException If the deployment id is null.
* @throws ResourceNotFoundException .
*/
@RequestMapping(value = "/{deploymentId}/description", method = RequestMethod.GET)
public List<ServiceDescription> getServiceDescriptionListByDeploymentId(
@PathVariable final String deploymentId) throws ResourceNotFoundException, RestErrorException {
if (deploymentId == null) {
throw new RestErrorException(CloudifyErrorMessages.MISSING_DEPLOYMENT_ID.getName(),
"getServiceDescriptionListByDeploymentId");
}
verifyDeploymentIdExists(deploymentId);
final ApplicationDescriptionFactory appDescriptionFactory =
new ApplicationDescriptionFactory(restConfig.getAdmin());
List<ServiceDescription> descriptions = new ArrayList<ServiceDescription>();
EventsCacheValue value = eventsCache.getIfExists(new EventsCacheKey(deploymentId));
if (value != null) {
for (ProcessingUnit pu : value.getProcessingUnits()) {
ServiceDescription serviceDescription = appDescriptionFactory.getServiceDescription(pu);
if (!serviceDescription.getInstancesDescription().isEmpty()) {
descriptions.add(serviceDescription);
} else {
// pu is stale. no instances
}
}
}
return descriptions;
}
/**
*
* @param appName
* The application name.
* @param timeoutInMinutes
* The timeout in minutes.
* @return uninstall response.
* @throws RestErrorException .
*/
@RequestMapping(value = "/{appName}", method = RequestMethod.DELETE)
@PreAuthorize("isFullyAuthenticated()")
public UninstallApplicationResponse uninstallApplication(
@PathVariable final String appName,
@RequestParam(required = false, defaultValue = "15") final Integer timeoutInMinutes)
throws RestErrorException {
validateUninstallApplication(appName);
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// Check that Application exists
final org.openspaces.admin.application.Application app = this.restConfig.getAdmin().getApplications().waitFor(
appName, 10, TimeUnit.SECONDS);
final ProcessingUnit[] pus = app.getProcessingUnits()
.getProcessingUnits();
if (pus.length > 0) {
if (permissionEvaluator != null) {
final CloudifyAuthorizationDetails authDetails = new CloudifyAuthorizationDetails(authentication);
// all the application PUs are supposed to have the same auth-groups setting
final String puAuthGroups = pus[0].getBeanLevelProperties().getContextProperties().
getProperty(CloudifyConstants.CONTEXT_PROPERTY_AUTH_GROUPS);
permissionEvaluator.verifyPermission(authDetails, puAuthGroups, "deploy");
}
}
final StringBuilder sb = new StringBuilder();
final List<ProcessingUnit> uninstallOrder = createUninstallOrder(pus,
appName);
final String deploymentId = uninstallOrder.get(0).getBeanLevelProperties()
.getContextProperties().getProperty(CloudifyConstants.CONTEXT_PROPERTY_DEPLOYMENT_ID);
logger.info("Uninstalling application " + appName + " . DeploymentId is " + deploymentId);
logger.fine("Uninstall order is " + orderToNames(uninstallOrder));
FutureTask<Boolean> undeployTask;
logger.log(Level.INFO, "Starting to poll for" + appName + " uninstall lifecycle events.");
if (uninstallOrder.size() > 0) {
undeployTask = new FutureTask<Boolean>(new Runnable() {
private final long startTime = System.currentTimeMillis();
@Override
public void run() {
final List<String> failedTasks = new ArrayList<String>();
for (final ProcessingUnit processingUnit : uninstallOrder) {
if (permissionEvaluator != null) {
final CloudifyAuthorizationDetails authDetails =
new CloudifyAuthorizationDetails(authentication);
final String puAuthGroups = processingUnit.getBeanLevelProperties().getContextProperties().
getProperty(CloudifyConstants.CONTEXT_PROPERTY_AUTH_GROUPS);
permissionEvaluator.verifyPermission(authDetails, puAuthGroups, "deploy");
}
final long undeployTimeout =
startTime + TimeUnit.MINUTES.toMillis(timeoutInMinutes) - System.currentTimeMillis();
try {
if (processingUnit.waitForManaged(WAIT_FOR_MANAGED_TIMEOUT_SECONDS,
TimeUnit.SECONDS) == null) {
logger.log(Level.WARNING,
"Failed to locate GSM that is managing Processing Unit "
+ processingUnit.getName());
} else {
logger.log(Level.INFO, "Undeploying Processing Unit " + processingUnit.getName()
+ " . Timeout is " + undeployTimeout);
populateEventsCache(deploymentId, processingUnit);
boolean undeployed = processingUnit.undeployAndWait(undeployTimeout,
TimeUnit.MILLISECONDS);
if (!undeployed) {
logger.warning("Failed to undeploy processing unit: " + processingUnit.getName());
failedTasks.add(ServiceUtils.getApplicationServiceName(processingUnit.getName(),
appName));
}
logger.log(Level.INFO, "Processing Unit " + processingUnit.getName() + " was "
+ "undeployed successfully");
final String serviceName = ServiceUtils.getApplicationServiceName(
processingUnit.getName(), appName);
logger.info("Removing application service scope attributes for service " + serviceName);
deleteServiceAttributes(appName,
serviceName);
}
} catch (final Exception e) {
failedTasks.add(ServiceUtils.getApplicationServiceName(processingUnit.getName(), appName));
final String msg = "Failed to undeploy processing unit: "
+ processingUnit.getName()
+ " while uninstalling application "
+ appName
+ ". Uninstall will continue, but service "
+ processingUnit.getName()
+ " may remain in an unstable state";
logger.log(Level.SEVERE, msg, e);
}
}
if (failedTasks.isEmpty()) {
DeploymentEvent undeployFinishedEvent = new DeploymentEvent();
undeployFinishedEvent.setDescription(CloudifyConstants.UNDEPLOYED_SUCCESSFULLY_EVENT);
eventsCache.add(new EventsCacheKey(deploymentId), undeployFinishedEvent);
logger.log(Level.INFO, "Application " + appName + " uninstalled successfully");
} else {
for (String serviceName : failedTasks) {
DeploymentEvent failureEvent = new DeploymentEvent();
failureEvent.setDescription(MessageFormat.format(
"Service undeployment was interrupted for service: <{0}>", serviceName));
eventsCache.add(new EventsCacheKey(deploymentId), failureEvent);
}
}
}
}, Boolean.TRUE);
((InternalAdmin) this.restConfig.getAdmin()).scheduleAdminOperation(undeployTask);
}
final String errors = sb.toString();
if (errors.length() == 0) {
logger.info("Removing all application scope attributes for application " + appName);
deleteApplicationScopeAttributes(appName);
final UninstallApplicationResponse response = new UninstallApplicationResponse();
response.setDeploymentID(deploymentId);
return response;
}
throw new RestErrorException(errors);
}
private List<String> orderToNames(final List<ProcessingUnit> order) {
List<String> names = new ArrayList<String>();
for (ProcessingUnit pu : order) {
names.add(pu.getName());
}
return names;
}
private void deleteApplicationScopeAttributes(final String applicationName) {
final ApplicationCloudifyAttribute applicationAttributeTemplate =
new ApplicationCloudifyAttribute(applicationName, null, null);
gigaSpace.takeMultiple(applicationAttributeTemplate);
}
private void validateUninstallApplication(final String appName)
throws RestErrorException {
final UninstallApplicationValidationContext validationContext =
new UninstallApplicationValidationContext();
validationContext.setCloud(restConfig.getCloud());
validationContext.setAdmin(admin);
validationContext.setApplicationName(appName);
for (final UninstallApplicationValidator validator : uninstallApplicationValidators) {
validator.validate(validationContext);
}
}
/**
* Uninstalls a service.
*
* @param appName
* the application name
* @param serviceName
* the service name
* @param timeoutInMinutes
* timeout in minutes
* @return UUID of this action, can be used to follow the relevant events
* @throws ResourceNotFoundException
* Indicates the service could not be found
* @throws RestErrorException
* Indicates the uninstall operation could not be performed
*/
@RequestMapping(value = "/{appName}/services/{serviceName}", method = RequestMethod.DELETE)
@PreAuthorize("isFullyAuthenticated()")
public UninstallServiceResponse uninstallService(
@PathVariable final String appName,
@PathVariable final String serviceName,
@RequestParam(required = false, defaultValue = "5") final Integer timeoutInMinutes)
throws ResourceNotFoundException, RestErrorException {
// validations
validateUninstallService(ServiceUtils.getAbsolutePUName(appName, serviceName));
final ProcessingUnit processingUnit = controllerHelper.getService(appName, serviceName);
final String deploymentId = processingUnit.getBeanLevelProperties()
.getContextProperties().getProperty(CloudifyConstants.CONTEXT_PROPERTY_DEPLOYMENT_ID);
if (permissionEvaluator != null) {
final String puAuthGroups = processingUnit.getBeanLevelProperties().getContextProperties().
getProperty(CloudifyConstants.CONTEXT_PROPERTY_AUTH_GROUPS);
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
final CloudifyAuthorizationDetails authDetails = new CloudifyAuthorizationDetails(authentication);
try {
permissionEvaluator.verifyPermission(authDetails, puAuthGroups, "view");
} catch (AccessDeniedException e) {
// if the user is not allowed to view the object, access denied should appear as "resource not found"
throw new ResourceNotFoundException(appName);
}
permissionEvaluator.verifyPermission(authDetails, puAuthGroups, "deploy");
}
populateEventsCache(deploymentId, processingUnit);
final FutureTask<Boolean> undeployTask = new FutureTask<Boolean>(
new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
boolean result = processingUnit.undeployAndWait(timeoutInMinutes,
TimeUnit.MINUTES);
if (!result) { // undeploy was not succesfull
logger.warning("Failed undeploying processing unit " + processingUnit.getName()
+ "in " + timeoutInMinutes + " minutes. Please consult the logs.");
} else { // undeploy was succesfull
deleteServiceAttributes(appName, serviceName);
// write to events cache
DeploymentEvent undeployFinishedEvent = new DeploymentEvent();
undeployFinishedEvent.setDescription(CloudifyConstants.UNDEPLOYED_SUCCESSFULLY_EVENT);
eventsCache.add(new EventsCacheKey(deploymentId), undeployFinishedEvent);
}
return result;
}
});
serviceUndeployExecutor.execute(undeployTask);
final UninstallServiceResponse uninstallServiceResponse = new UninstallServiceResponse();
uninstallServiceResponse.setDeploymentID(deploymentId);
return uninstallServiceResponse;
}
private void deleteServiceAttributes(
final String applicationName,
final String serviceName) {
deleteServiceInstanceAttributes(applicationName, serviceName, null);
final ServiceCloudifyAttribute serviceAttributeTemplate =
new ServiceCloudifyAttribute(applicationName, serviceName, null, null);
gigaSpace.takeMultiple(serviceAttributeTemplate);
// Delete instance attempt data related to this service
ServiceInstanceAttemptData template = new ServiceInstanceAttemptData();
template.setApplicationName(applicationName);
template.setServiceName(serviceName);
ServiceInstanceAttemptData[] attempts = gigaSpace.takeMultiple(template);
logger.info("Removed " + attempts.length + " instance attempts from management space");
}
private void deleteServiceInstanceAttributes(
final String applicationName,
final String serviceName,
final Integer instanceId) {
final InstanceCloudifyAttribute instanceAttributesTemplate =
new InstanceCloudifyAttribute(applicationName, serviceName, instanceId, null, null);
gigaSpace.takeMultiple(instanceAttributesTemplate);
}
private void validateUninstallService(final String puName) throws RestErrorException {
final UninstallServiceValidationContext validationContext = new UninstallServiceValidationContext();
validationContext.setCloud(restConfig.getCloud());
validationContext.setAdmin(admin);
validationContext.setPuName(puName);
for (final UninstallServiceValidator validator : uninstallServiceValidators) {
validator.validate(validationContext);
}
}
private void populateEventsCache(final String deploymentId,
final ProcessingUnit processingUnit) {
EventsCacheKey key = new EventsCacheKey(deploymentId);
EventsCacheValue value = eventsCache.getIfExists(key);
if (value == null) {
// first time populating the cache with this deployment id.
value = new EventsCacheValue();
value.getProcessingUnits().add(processingUnit);
eventsCache.put(key, value);
} else {
// a value already exists for this deployment id.
// just add the reference for the current pu.
logger.fine("Adding processing unit " + processingUnit.getName() + " to events cache value with " +
"deployment id " + deploymentId);
value.getProcessingUnits().add(processingUnit);
}
final AdminBasedGridServiceContainerProvider provider = new AdminBasedGridServiceContainerProvider(admin);
// save reference to the containers if exits.
// will exist only if this is uninstall process.
Set<GridServiceContainer> containersForDeployment = provider.getContainersForDeployment(deploymentId);
logger.fine("Adding containers " + containersForDeployment + " for events cache key with deployment id " + deploymentId);
value.getContainers().addAll(containersForDeployment);
}
private String getEffectiveAuthGroups(final String authGroups) {
String effectiveAuthGroups = authGroups;
if (StringUtils.isBlank(effectiveAuthGroups)) {
if (permissionEvaluator != null) {
effectiveAuthGroups = permissionEvaluator.getUserAuthGroupsString();
} else {
effectiveAuthGroups = "";
}
}
return effectiveAuthGroups;
}
private static String extractLocators(final Admin admin) {
final LookupLocator[] locatorsArray = admin.getLocators();
final StringBuilder locators = new StringBuilder();
for (final LookupLocator locator : locatorsArray) {
locators.append(locator.getHost()).append(':').append(locator.getPort()).append(',');
}
if (locators.length() > 0) {
locators.setLength(locators.length() - 1);
}
return locators.toString();
}
private ProcessingUnit deployAndWait(
final String serviceName,
final ElasticDeploymentTopology deployment)
throws TimeoutException {
GridServiceManager gsm = getGridServiceManager();
ProcessingUnit pu = null;
Integer serviceDiscoveryTimeoutInSeconds = getServiceDiscoveryTimeout();
if (deployment instanceof ElasticStatelessProcessingUnitDeployment) {
pu = gsm.deploy((ElasticStatelessProcessingUnitDeployment) deployment,
serviceDiscoveryTimeoutInSeconds, TimeUnit.SECONDS);
} else if (deployment instanceof ElasticStatefulProcessingUnitDeployment) {
pu = gsm.deploy((ElasticStatefulProcessingUnitDeployment) deployment,
serviceDiscoveryTimeoutInSeconds, TimeUnit.SECONDS);
} else if (deployment instanceof ElasticSpaceDeployment) {
pu = gsm.deploy((ElasticSpaceDeployment) deployment, serviceDiscoveryTimeoutInSeconds, TimeUnit.SECONDS);
}
if (pu == null) {
throw new TimeoutException("Timed out waiting for Service "
+ serviceName + " deployment.");
}
return pu;
}
/**
* Gets the service discovery timeout from the cloud configuration.
* If the cloud object is null (e.g. localcloud), uses the default timeout (60 seconds)
* @return service discovery timeout in seconds
*/
private Integer getServiceDiscoveryTimeout() {
Integer serviceDiscoveryTimeoutInSeconds = new Integer(CloudifyConstants.DEFAULT_SERVICE_DISCOVERY_TIMEOUT_SEC);
Cloud cloud = restConfig.getCloud();
if (cloud != null) {
serviceDiscoveryTimeoutInSeconds = cloud.getConfiguration().getComponents().getRest()
.getServiceDiscoveryTimeoutInSeconds();
}
logger.finest("serviceDiscoveryTimeoutInSeconds value is set to: " + serviceDiscoveryTimeoutInSeconds);
return serviceDiscoveryTimeoutInSeconds;
}
private GridServiceManager getGridServiceManager() {
if (restConfig.getAdmin().getGridServiceManagers().isEmpty()) {
throw new AdminException("Cannot locate Grid Service Manager");
}
return restConfig.getAdmin().getGridServiceManagers().iterator().next();
}
private DSLServiceCompilationResult readService(final File workingProjectDir,
final String serviceFileName,
final String absolutePuName,
final ServiceApplicationDependentProperties serviceProps,
final File overridesFile)
throws RestErrorException {
DSLServiceCompilationResult result;
try {
if (serviceFileName != null) {
result = ServiceReader.getServiceFromFile(
new File(workingProjectDir, serviceFileName),
workingProjectDir,
null /* propertiesFileName */,
true /* isRunningInGSC */,
overridesFile);
} else {
result = ServiceReader.getServiceFromDirectory(workingProjectDir, overridesFile);
}
Service service = result.getService();
// Setting application dependent properties
if (serviceProps != null) {
service.setDependsOn(serviceProps.getDependsOn());
}
} catch (final Exception e) {
throw new RestErrorException(CloudifyMessageKeys.FAILED_TO_READ_SERVICE.getName(), absolutePuName);
}
return result;
}
private byte[] getCloudConfigurationContent(final File serviceCloudConfigurationFile, final String absolutePuName)
throws RestErrorException {
byte[] serviceCloudConfigurationContents = null;
if (serviceCloudConfigurationFile != null) {
try {
serviceCloudConfigurationContents = FileUtils.readFileToByteArray(serviceCloudConfigurationFile);
} catch (final IOException e) {
throw new RestErrorException(CloudifyMessageKeys.FAILED_TO_READ_SERVICE_CLOUD_CONFIGURATION.getName(),
absolutePuName);
}
}
return serviceCloudConfigurationContents;
}
private String getTempalteNameFromService(final Service service) {
final Cloud cloud = restConfig.getCloud();
if (cloud == null) {
return null;
}
final ComputeDetails compute = service.getCompute();
String templateName = restConfig.getDefaultTemplateName();
if (compute != null) {
templateName = compute.getTemplate();
}
if (IsolationUtils.isGlobal(service) && IsolationUtils.isUseManagement(service)) {
final String managementTemplateName = cloud.getConfiguration().getManagementMachineTemplate();
if (compute != null) {
if (!StringUtils.isBlank(templateName)) {
if (!templateName.equals(managementTemplateName)) {
// this is just a clarification log.
// the service wont be installed on a management machine(even if there is enough memory)
// because the management machine template does not match the desired template
logger.warning("Installation of service " + service.getName() + " on a management machine "
+ "will not be attempted since the specified template(" + templateName + ")"
+ " is different than the management machine template(" + managementTemplateName + ")");
}
}
} else {
templateName = restConfig.getManagementTemplateName();
}
}
return templateName;
}
private File getFromRepo(final String uploadKey, final String errorDesc, final String absolutePuName)
throws RestErrorException {
if (StringUtils.isBlank(uploadKey)) {
return null;
}
final File file = repo.get(uploadKey);
if (file == null) {
throw new RestErrorException(errorDesc, absolutePuName);
}
return file;
}
private void validateInstallService(final InstallServiceRequest request,
final Service service, final String absolutePuName,
final File serviceOverridesFile)
throws RestErrorException {
// get cloud overrides file
final File cloudOverridesFile = getFromRepo(
request.getCloudOverridesUploadKey(),
CloudifyMessageKeys.WRONG_CLOUD_OVERRIDES_UPLOAD_KEY.getName(), absolutePuName);
// get cloud configuration file and content
final File cloudConfigurationFile = getFromRepo(
request.getCloudConfigurationUploadKey(),
CloudifyMessageKeys.WRONG_CLOUD_CONFIGURATION_UPLOAD_KEY.getName(), absolutePuName);
// create validation context
final InstallServiceValidationContext validationContext = new InstallServiceValidationContext();
validationContext.setCloud(restConfig.getCloud());
validationContext.setAdmin(admin);
validationContext.setService(service);
validationContext.setCloudOverridesFile(cloudOverridesFile);
validationContext.setServiceOverridesFile(serviceOverridesFile);
validationContext.setCloudConfigurationFile(cloudConfigurationFile);
validationContext.setPuName(absolutePuName);
// call validate for each install service validator.
for (final InstallServiceValidator validator : installServiceValidators) {
validator.validate(validationContext);
}
}
/**
*
* @param appName
* .
*/
@RequestMapping(value = "/{appName}", method = RequestMethod.GET)
public void getApplicationStatus(@PathVariable final String appName) {
throw new UnsupportedOperationException("getApplicationStatus");
}
/**
*
* @param appName
* .
* @param serviceName
* .
*/
@RequestMapping(value = "/{appName}/services/{serviceName}", method = RequestMethod.GET)
public void getServiceStatus(
@PathVariable final String appName,
@PathVariable final String serviceName) {
throw new UnsupportedOperationException("getServiceStatus");
}
/**
*
* @param appName
* .
*/
@RequestMapping(value = "/{appName}", method = RequestMethod.PUT)
public void updateApplication(@PathVariable final String appName) {
throw new UnsupportedOperationException("updateApplication");
}
/**
*
* @param appName
* .
* @param serviceName
* .
*/
@RequestMapping(value = "/{appName}/services/{serviceName}", method = RequestMethod.PUT)
public void updateService(
@PathVariable final String appName,
@PathVariable final String serviceName) {
throw new UnsupportedOperationException("updateService");
}
/**
*
* @param appName
* .
* @param attributeName
* .
* @param updateApplicationAttributeRequest
* .
*/
@RequestMapping(value = "/{appName}/attributes/{attributeName}", method = RequestMethod.PUT)
public void updateApplicationAttribute(
@PathVariable final String appName,
@PathVariable final String attributeName,
@RequestBody final UpdateApplicationAttributeRequest updateApplicationAttributeRequest) {
throw new UnsupportedOperationException("updateApplicationAttribute");
}
/**
* Retrieves application level attributes.
*
* @param appName
* The application name.
* @return An instance of {@link GetApplicationAttributesResponse} containing all the application attributes names
* and values.
* @throws ResourceNotFoundException
* Thrown in case the application does not exist.
*/
@RequestMapping(value = "/{appName}/attributes", method = RequestMethod.GET)
public GetApplicationAttributesResponse getApplicationAttributes(
@PathVariable final String appName)
throws ResourceNotFoundException {
// valid application if exist
controllerHelper.getApplication(appName);
if (logger.isLoggable(Level.FINER)) {
logger.finer("received request to get all attributes of application " + appName);
}
// get attributes
final Map<String, Object> attributes = controllerHelper.getAttributes(appName, null, null);
// create response object
final GetApplicationAttributesResponse aar = new GetApplicationAttributesResponse();
// set attributes
aar.setAttributes(attributes);
return aar;
}
/**
* Deletes an application level attribute.
*
* @param appName
* The application name.
* @param attributeName
* The attribute name.
* @return The previous value of the attribute.
* @throws ResourceNotFoundException
* Thrown in case the application does not exist.
* @throws RestErrorException
* Thrown in case the attribute name is empty.
*/
@RequestMapping(value = "/{appName}/attributes/{attributeName}", method = RequestMethod.DELETE)
public DeleteApplicationAttributeResponse deleteApplicationAttribute(
@PathVariable final String appName,
@PathVariable final String attributeName)
throws ResourceNotFoundException, RestErrorException {
// valid application if exist
controllerHelper.getApplication(appName);
if (logger.isLoggable(Level.FINER)) {
logger.finer("received request to delete attributes "
+ attributeName + " of application " + appName);
}
// delete attribute returned previous value
final Object previousValue = controllerHelper.deleteAttribute(appName, null, null, attributeName);
final DeleteApplicationAttributeResponse daar = new DeleteApplicationAttributeResponse();
daar.setPreviousValue(previousValue);
return daar;
}
/**
* Retrieves service level attributes.
*
* @param appName
* The application name.
* @param serviceName
* The service name.
* @return An instance of {@link GetServiceAttributesResponse} containing all the service attributes names and
* values.
* @throws ResourceNotFoundException
* Thrown in case the service does not exist.
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/attributes", method = RequestMethod.GET)
public GetServiceAttributesResponse getServiceAttributes(
@PathVariable final String appName,
@PathVariable final String serviceName)
throws ResourceNotFoundException {
// valid exist service
controllerHelper.getService(appName, serviceName);
// logger - request to get all attributes
if (logger.isLoggable(Level.FINER)) {
logger.finer("received request to get all attributes of service "
+ ServiceUtils.getAbsolutePUName(appName, serviceName)
+ " of application " + appName);
}
// get attributes
final Map<String, Object> attributes = controllerHelper.getAttributes(appName, serviceName,
null);
// create response object
final GetServiceAttributesResponse sar = new GetServiceAttributesResponse();
// set attributes
sar.setAttributes(attributes);
// return response object
return sar;
}
/**
* Sets service level attributes for the given application.
*
* @param appName
* The application name.
* @param serviceName
* The service name.
* @param request
* Request body, specifying the attributes names and values.
* @throws RestErrorException
* Thrown in case the request body is empty.
* @throws ResourceNotFoundException
* Thrown in case the service does not exist.
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/attributes", method = RequestMethod.POST)
public void setServiceAttribute(
@PathVariable final String appName,
@PathVariable final String serviceName,
@RequestBody final SetServiceAttributesRequest request)
throws ResourceNotFoundException, RestErrorException {
// valid service
controllerHelper.getService(appName, serviceName);
// validate request object
if (request == null || request.getAttributes() == null) {
throw new RestErrorException(CloudifyMessageKeys.EMPTY_REQUEST_BODY_ERROR.getName());
}
if (logger.isLoggable(Level.FINER)) {
logger.finer("received request to set attributes "
+ request.getAttributes().keySet() + " of service "
+ ServiceUtils.getAbsolutePUName(appName, serviceName)
+ " of application " + appName + " to: "
+ request.getAttributes().values());
}
// set attributes
controllerHelper.setAttributes(appName, serviceName, null, request.getAttributes());
}
/**
*
* @param appName
* .
* @param serviceName
* .
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/attributes", method = RequestMethod.PUT)
public void updateServiceAttribute(
@PathVariable final String appName,
@PathVariable final String serviceName) {
throw new UnsupportedOperationException("updateServiceAttribute");
}
/**
* Deletes a service level attribute.
*
* @param appName
* The application name.
* @param serviceName
* The service name.
* @param attributeName
* The attribute name.
* @return The previous value of the attribute.
* @throws ResourceNotFoundException
* Thrown in case the service does not exist.
* @throws RestErrorException
* Thrown in case the attribute name is empty.
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/attributes/{attributeName}",
method = RequestMethod.DELETE)
public DeleteServiceAttributeResponse deleteServiceAttribute(
@PathVariable final String appName,
@PathVariable final String serviceName,
@PathVariable final String attributeName)
throws ResourceNotFoundException, RestErrorException {
// valid service
controllerHelper.getService(appName, serviceName);
// logger - request to delete attributes
if (logger.isLoggable(Level.FINER)) {
logger.finer("received request to delete attribute "
+ attributeName + " of service "
+ ServiceUtils.getAbsolutePUName(appName, serviceName)
+ " of application " + appName);
}
// get delete attribute returned previous value
final Object previous = controllerHelper.deleteAttribute(appName, serviceName, null, attributeName);
// create response object
final DeleteServiceAttributeResponse sar = new DeleteServiceAttributeResponse();
// set previous value
sar.setPreviousValue(previous);
// return response object
return sar;
}
/**
* Retrieves service instance level attributes.
*
* @param appName
* The application name.
* @param serviceName
* The service name.
* @param instanceId
* The instance id.
* @return An instance of {@link GetServiceInstanceAttributesResponse} containing all the service instance
* attributes names and values.
* @throws ResourceNotFoundException
* Thrown in case the service instance does not exist.
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/instances/{instanceId}/attributes",
method = RequestMethod.GET)
public GetServiceInstanceAttributesResponse getServiceInstanceAttributes(
@PathVariable final String appName,
@PathVariable final String serviceName,
@PathVariable final Integer instanceId)
throws ResourceNotFoundException {
// valid service
controllerHelper.getService(appName, serviceName);
// logger - request to get all attributes
if (logger.isLoggable(Level.FINER)) {
logger.finer("received request to get all attributes of instance number "
+ instanceId
+ " of service "
+ ServiceUtils.getAbsolutePUName(appName, serviceName)
+ " of application " + appName);
}
// get attributes
final Map<String, Object> attributes = controllerHelper.getAttributes(appName, serviceName, instanceId);
// create response object
final GetServiceInstanceAttributesResponse siar = new GetServiceInstanceAttributesResponse();
// set attributes
siar.setAttributes(attributes);
// return response object
return siar;
}
/**
* Sets service instance level attributes for the given application.
*
* @param appName
* The application name.
* @param serviceName
* The service name.
* @param instanceId
* The instance id.
* @param request
* Request body, specifying the attributes names and values.
* @throws RestErrorException
* Thrown in case the request body is empty.
* @throws ResourceNotFoundException
* Thrown in case the service instance does not exist.
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/instances/{instanceId}/attributes",
method = RequestMethod.POST)
public void setServiceInstanceAttributes(
@PathVariable final String appName,
@PathVariable final String serviceName,
@PathVariable final Integer instanceId,
@RequestBody final SetServiceInstanceAttributesRequest request)
throws ResourceNotFoundException, RestErrorException {
// valid service
controllerHelper.getService(appName, serviceName);
// validate request object
if (request == null || request.getAttributes() == null) {
throw new RestErrorException(
CloudifyMessageKeys.EMPTY_REQUEST_BODY_ERROR.getName());
}
if (logger.isLoggable(Level.FINER)) {
logger.finer("received request to set attribute "
+ request.getAttributes().keySet() + " of instance number "
+ instanceId + " of service "
+ ServiceUtils.getAbsolutePUName(appName, serviceName)
+ " of application " + appName + " to: "
+ request.getAttributes().values());
}
// set attributes
controllerHelper.setAttributes(appName, serviceName, instanceId, request.getAttributes());
}
/**
*
* @param appName
* .
* @param serviceName
* .
* @param instanceId
* .
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/instances/{instanceId}/attributes",
method = RequestMethod.PUT)
public void updateServiceInstanceAttribute(
@PathVariable final String appName,
@PathVariable final String serviceName,
@PathVariable final String instanceId) {
throw new UnsupportedOperationException("updateServiceInstanceAttribute");
}
/**
* Retrieves USM metric details about the service.
*
* @param appName
* The application name.
* @param serviceName
* The service name.
* @return Various USM metric details about the service
* @throws ResourceNotFoundException .
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/metrics", method = RequestMethod.GET)
public ServiceMetricsResponse getServiceMetrics(
@PathVariable final String appName,
@PathVariable final String serviceName)
throws ResourceNotFoundException {
// service instances metrics data
final List<ServiceInstanceMetricsData> serviceInstanceMetricsDatas =
new ArrayList<ServiceInstanceMetricsData>();
// get service
final ProcessingUnit service = controllerHelper.getService(appName, serviceName);
// set metrics for every instance
for (final ProcessingUnitInstance serviceInstance : service.getInstances()) {
final Map<String, Object> metrics = serviceInstance.getStatistics()
.getMonitors().get("USM").getMonitors();
serviceInstanceMetricsDatas.add(new ServiceInstanceMetricsData(
serviceInstance.getInstanceId(), metrics));
}
// create response instance
final ServiceMetricsResponse smr = new ServiceMetricsResponse();
smr.setAppName(appName);
smr.setServiceInstaceMetricsData(serviceInstanceMetricsDatas);
smr.setServiceName(serviceName);
return smr;
}
/**
* Retrieves USM metric details about the service instance.
*
* @param appName
* The application name.
* @param serviceName
* The service name.
* @param instanceId
* The instance id.
* @return Various USM metric details about the service instance.
* @throws ResourceNotFoundException .
*/
@RequestMapping(value = "{appName}/service/{serviceName}/instances/{instanceId}/metrics",
method = RequestMethod.GET)
public ServiceInstanceMetricsResponse getServiceInstanceMetrics(
@PathVariable final String appName,
@PathVariable final String serviceName,
@PathVariable final Integer instanceId)
throws ResourceNotFoundException {
// get service instance
final ProcessingUnitInstance serviceInstance =
controllerHelper.getServiceInstance(appName, serviceName, instanceId);
// get metrics data
final Map<String, Object> metrics = serviceInstance.getStatistics().getMonitors().get("USM").getMonitors();
final ServiceInstanceMetricsData serviceInstanceMetricsData =
new ServiceInstanceMetricsData(instanceId, metrics);
// create response object
final ServiceInstanceMetricsResponse simr = new ServiceInstanceMetricsResponse();
// set response data
simr.setAppName(appName);
simr.setServiceName(serviceName);
simr.setServiceInstanceMetricsData(serviceInstanceMetricsData);
return simr;
}
/**
*
* @param appName
* .
* @param serviceName
* .
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/metadata", method = RequestMethod.POST)
public void setServiceDetails(
@PathVariable final String appName,
@PathVariable final String serviceName) {
throw new UnsupportedOperationException("setServiceDetails");
}
/**
*
* @param appName
* .
* @param serviceName
* .
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/metadata", method = RequestMethod.PUT)
public void updateServiceDetails(
@PathVariable final String appName,
@PathVariable final String serviceName) {
throw new UnsupportedOperationException("updateServiceDetails");
}
/**
*
* @param appName
* .
* @param serviceName
* .
* @param instanceId
* .
*/
@RequestMapping(value = "/{appName}/service/{serviceName}/instances/{instanceId}/metadata",
method = RequestMethod.POST)
public void setServiceInstanceDetails(
@PathVariable final String appName,
@PathVariable final String serviceName,
@PathVariable final String instanceId) {
throw new UnsupportedOperationException("setServiceInstanceDetails");
}
/**
*
* @param appName
* .
* @param serviceName
* .
*/
@RequestMapping(value = "/appName}/service/{serviceName}/alerts", method = RequestMethod.GET)
public void getServiceAlerts(
@PathVariable final String appName,
@PathVariable final String serviceName) {
throw new UnsupportedOperationException("getServiceAlerts");
}
public File getExtractedFodler() {
return this.extractedFodler;
}
public RestConfiguration getRestConfig() {
return restConfig;
}
public void setRestConfig(final RestConfiguration restConfig) {
this.restConfig = restConfig;
}
public UploadRepo getRepo() {
return repo;
}
public void setRepo(final UploadRepo repo) {
this.repo = repo;
}
public InstallServiceValidator[] getInstallServiceValidators() {
return installServiceValidators;
}
public void setInstallServiceValidators(final InstallServiceValidator[] installServiceValidators) {
this.installServiceValidators = installServiceValidators;
}
public UninstallServiceValidator[] getUninstallServiceValidators() {
return uninstallServiceValidators;
}
public void setUninstallServiceValidators(final UninstallServiceValidator[] uninstallServiceValidators) {
this.uninstallServiceValidators = uninstallServiceValidators;
}
public InstallApplicationValidator[] getInstallApplicationValidators() {
return installApplicationValidators;
}
public void setInstallApplicationValidators(final InstallApplicationValidator[] installApplicationValidators) {
this.installApplicationValidators = installApplicationValidators;
}
public UninstallApplicationValidator[] getUninstallApplicationValidators() {
return uninstallApplicationValidators;
}
public void setUninstallApplicationValidators(
final UninstallApplicationValidator[] uninstallApplicationValidators) {
this.uninstallApplicationValidators = uninstallApplicationValidators;
}
public SetServiceInstancesValidator[] getSetServiceInstancesValidators() {
return setServiceInstancesValidators;
}
public void setSetServiceInstancesValidators(final SetServiceInstancesValidator[] setServiceInstancesValidators) {
this.setServiceInstancesValidators = setServiceInstancesValidators;
}
/**
* Invokes a custom command on all of the specified service instances. Custom parameters are passed as a map using
* the POST method and contain the command name and parameter values for the specified command.
*
* @param applicationName
* The application name.
* @param serviceName
* The service name.
* @param request
* InvokeCustomCommandRequest the request containing the relevant parameters.
* @return a Map containing the result of each invocation on a service instance.
* @throws RestErrorException
* When the invocation failed.
* @throws ResourceNotFoundException
* When failed to locate service/service instance.
*/
@RequestMapping(value = "applications/{applicationName}/services/{serviceName}/invoke",
method = RequestMethod.POST)
@PreAuthorize("isFullyAuthenticated()")
public InvokeServiceCommandResponse invoke(@PathVariable final String applicationName,
@PathVariable final String serviceName,
@RequestBody final InvokeCustomCommandRequest request)
throws RestErrorException, ResourceNotFoundException {
InvokeServiceCommandResponse response = new InvokeServiceCommandResponse();
final String absolutePuName = ServiceUtils.getAbsolutePUName(applicationName, serviceName);
if (logger.isLoggable(Level.FINER)) {
logger.finer("received request to invoke command of service " + absolutePuName + " of application "
+ applicationName);
}
// Get the PU
final ProcessingUnit pu = admin.getProcessingUnits().waitFor(
absolutePuName, PU_DISCOVERY_TIMEOUT_SEC, TimeUnit.SECONDS);
if (pu == null) {
// TODO: Consider telling the user he might be using the wrong
// application name.
logger.severe("Could not find service " + absolutePuName);
throw new ResourceNotFoundException(absolutePuName);
}
if (permissionEvaluator != null) {
final String puAuthGroups = pu.getBeanLevelProperties().getContextProperties().
getProperty(CloudifyConstants.CONTEXT_PROPERTY_AUTH_GROUPS);
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
final CloudifyAuthorizationDetails authDetails = new CloudifyAuthorizationDetails(authentication);
permissionEvaluator.verifyPermission(authDetails, puAuthGroups, "deploy");
}
// result, mapping service instances to results
// final Map<String, Object> invocationResult = new HashMap<String, Object>();
final ProcessingUnitInstance[] instances = pu.getInstances();
if (instances.length == 0) {
throw new RestErrorException(
ResponseConstants.NO_PROCESSING_UNIT_INSTANCES_FOUND_FOR_INVOCATION,
serviceName);
}
// Why a map? TODO: Use an array here instead.
// map between service name and its future
final Map<String, Future<Object>> futures = new HashMap<String, Future<Object>>(
instances.length);
for (final ProcessingUnitInstance instance : instances) {
// key includes instance ID and host name
final String serviceInstanceName = buildServiceInstanceName(instance);
try {
Map<String, Object> invocationArgs = preProcessInvocationRequest(request.getCommandName(),
request.getParameters());
final Future<Object> future = ((DefaultProcessingUnitInstance) instance)
.invoke(CloudifyConstants.INVOCATION_PARAMETER_BEAN_NAME_USM, invocationArgs);
futures.put(serviceInstanceName, future);
} catch (final Exception e) {
// we log the error message and add it to the response but carry on to the following instances
String errorMessage = "Error occurred while invoking custom command '" + request.getCommandName()
+ "' on service " + serviceName + ":" + instance.getInstanceId() + " on host "
+ instance.getVirtualMachine().getMachine().getHostName() + ". Reported error: "
+ e.getMessage();
logger.severe(errorMessage);
Map<String, String> processedResult = postProcessInvocationResult(serviceInstanceName, errorMessage);
response.setInvocationResult(serviceInstanceName, processedResult);
}
}
for (final Map.Entry<String, Future<Object>> entry : futures.entrySet()) {
String serviceInstanceName = entry.getKey();
Object invocationResult;
try {
invocationResult = entry.getValue().get();
} catch (final Exception e) {
// we log the error message and add it to the response but carry on to the following instances
String errorMessage = "Error occurred while invoking custom command on service "
+ serviceName + ":" + serviceInstanceName + ". Reported error: " + e.getMessage();
logger.severe(errorMessage);
invocationResult = errorMessage;
}
// use only tostring of collection values, to avoid serialization problems
Map<String, String> processedResult = postProcessInvocationResult(serviceInstanceName, invocationResult);
response.setInvocationResult(serviceInstanceName, processedResult);
}
return response;
}
/**
* Invokes a custom command on a specific service instance. Custom parameters are passed as a map using POST method
* and contain the command name and parameter values for the specified command.
*
* @param applicationName
* The application name.
* @param serviceName
* The service name
* @param instanceId
* The service instance number to be invoked.
* @param request
* InvokeCustomCommandRequest the request containing the relevant parameters.
* @return a Map containing the invocation result on the specified instance.
* @throws RestErrorException
* When the invocation failed.
* @throws ResourceNotFoundException
* When failed to locate service/service instance.
*/
@RequestMapping(value = "applications/{applicationName}/services/{serviceName}/instances"
+ "/{instanceId}/invoke", method = RequestMethod.POST)
@PreAuthorize("isFullyAuthenticated()")
public InvokeInstanceCommandResponse invokeInstance(
@PathVariable final String applicationName,
@PathVariable final String serviceName,
@PathVariable final int instanceId,
@RequestBody final InvokeCustomCommandRequest request)
throws RestErrorException, ResourceNotFoundException {
InvokeInstanceCommandResponse response = new InvokeInstanceCommandResponse();
final String absolutePuName = ServiceUtils.getAbsolutePUName(applicationName, serviceName);
if (logger.isLoggable(Level.FINER)) {
logger.finer("received request to invoke command of service " + serviceName + " of application "
+ applicationName);
}
// Get PU
final ProcessingUnit pu = admin.getProcessingUnits().waitFor(
absolutePuName, PU_DISCOVERY_TIMEOUT_SEC, TimeUnit.SECONDS);
if (pu == null) {
// TODO: Consider telling the user he might be using the wrong
// application name.
logger.severe("Could not find service " + absolutePuName);
throw new ResourceNotFoundException(absolutePuName);
}
if (permissionEvaluator != null) {
final String puAuthGroups = pu.getBeanLevelProperties().getContextProperties().
getProperty(CloudifyConstants.CONTEXT_PROPERTY_AUTH_GROUPS);
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
final CloudifyAuthorizationDetails authDetails = new CloudifyAuthorizationDetails(authentication);
permissionEvaluator.verifyPermission(authDetails, puAuthGroups, "deploy");
}
final InternalProcessingUnitInstance pui = findInstanceById(pu, instanceId);
if (pui == null) {
logger.severe("Could not find service instance " + instanceId
+ " for service " + absolutePuName);
throw new RestErrorException(
ResponseConstants.SERVICE_INSTANCE_UNAVAILABLE,
applicationName, absolutePuName,
Integer.toString(instanceId));
}
final String instanceName = buildServiceInstanceName(pui);
// Invoke the remote service
Object invocationResult;
try {
Map<String, Object> invocationArgs = preProcessInvocationRequest(request.getCommandName(),
request.getParameters());
final Future<?> future = pui.invoke(CloudifyConstants.INVOCATION_PARAMETER_BEAN_NAME_USM, invocationArgs);
invocationResult = future.get();
Map<String, String> finalResult = postProcessInvocationResult(instanceName, invocationResult);
response.setInvocationResult(finalResult);
} catch (final Exception e) {
String errorMessage = "Error occurred while invoking custom command on pu instance "
+ absolutePuName + ":" + instanceId + " on host "
+ pui.getVirtualMachine().getMachine().getHostName() + ". Reported error: " + e.getMessage();
logger.severe(errorMessage);
throw new RestErrorException(
ResponseConstants.FAILED_TO_INVOKE_INSTANCE,
absolutePuName, Integer.toString(instanceId),
e.getMessage());
}
return response;
}
private Map<String, Object> preProcessInvocationRequest(final String commandName, final List<String> parameters) {
int index = 0;
final Map<String, Object> invocationParamsMap = new HashMap<String, Object>();
// add command
invocationParamsMap.put(CloudifyConstants.INVOCATION_PARAMETER_COMMAND_NAME, commandName);
if (parameters != null) {
for (final String param : parameters) {
invocationParamsMap.put(CloudifyConstants.INVOCATION_PARAMETERS_KEY + index, param);
++index;
}
}
return invocationParamsMap;
}
private Map<String, String> postProcessInvocationResult(final String instanceName, final Object result) {
final Map<String, String> resultsMap = new HashMap<String, String>();
resultsMap.put(CloudifyConstants.INVOCATION_RESPONSE_INSTANCE_NAME, instanceName);
if (result instanceof Map<?, ?>) {
@SuppressWarnings("unchecked")
final Set<Entry<String, Object>> entries = ((Map<String, Object>) result).entrySet();
for (final Entry<String, Object> subEntry : entries) {
String key = subEntry.getKey();
String safeValue = subEntry.getValue() == null ? null : subEntry.getValue().toString();
if (key.equalsIgnoreCase(CloudifyConstants.INVOCATION_RESPONSE_STATUS)) {
if (Boolean.parseBoolean(safeValue)) {
resultsMap.put(key, InvocationStatus.SUCCESS.toString());
} else {
resultsMap.put(key, InvocationStatus.FAILURE.toString());
}
} else {
resultsMap.put(key, safeValue);
}
}
} else {
resultsMap.put(CloudifyConstants.INVOCATION_RESPONSE_STATUS, InvocationStatus.UNEXPECTED.toString());
resultsMap.put(CloudifyConstants.INVOCATION_RESPONSE_RESULT, result == null ? null : result.toString());
}
return resultsMap;
}
private String buildServiceInstanceName(
final ProcessingUnitInstance instance) {
return "instance #" + instance.getInstanceId() + "@"
+ instance.getVirtualMachine().getMachine().getHostName();
}
private InternalProcessingUnitInstance findInstanceById(
final ProcessingUnit pu, final int id) {
final ProcessingUnitInstance[] instances = pu.getInstances();
for (final ProcessingUnitInstance instance : instances) {
if (instance.getInstanceId() == id) {
return (InternalProcessingUnitInstance) instance;
}
}
return null;
}
}