package com.sixsq.slipstream.run;
/*
* +=================================================================+
* SlipStream Server (WAR)
* =====
* Copyright (C) 2013 SixSq Sarl (sixsq.com)
* =====
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* -=================================================================-
*/
import com.sixsq.slipstream.configuration.Configuration;
import com.sixsq.slipstream.exceptions.AbortException;
import com.sixsq.slipstream.exceptions.NotFoundException;
import com.sixsq.slipstream.exceptions.SlipStreamClientException;
import com.sixsq.slipstream.exceptions.SlipStreamException;
import com.sixsq.slipstream.exceptions.ValidationException;
import com.sixsq.slipstream.factory.DeploymentFactory;
import com.sixsq.slipstream.persistence.DeploymentModule;
import com.sixsq.slipstream.persistence.Node;
import com.sixsq.slipstream.persistence.NodeParameter;
import com.sixsq.slipstream.persistence.PersistenceUtil;
import com.sixsq.slipstream.persistence.Run;
import com.sixsq.slipstream.persistence.RunParameter;
import com.sixsq.slipstream.persistence.RuntimeParameter;
import com.sixsq.slipstream.persistence.User;
import com.sixsq.slipstream.persistence.Vm;
import com.sixsq.slipstream.statemachine.StateMachine;
import com.sixsq.slipstream.statemachine.States;
import org.apache.commons.lang.StringUtils;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.Delete;
import org.restlet.resource.Get;
import org.restlet.resource.Post;
import org.restlet.resource.ResourceException;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class RunNodeResource extends RunBaseResource {
public final static String NUMBER_INSTANCES_ADD_FORM_PARAM = "n";
public final static String NUMBER_INSTANCES_ADD_DEFAULT = "1";
private final static String INSTANCE_IDS_REMOVE_FORM_PARAM = "ids";
private final static String DELETE_INSTANCE_IDS_ONLY_FORM_PARAM = "delete-ids-only";
private final static List<Method> IGNORE_ABORT_HTTP_METHODS = new ArrayList<Method>(
Arrays.asList(Method.GET));
private String nodename;
private String nodeMultiplicityRunParam;
private String nodeMultiplicityRuntimeParam;
private String nodeIndicesRuntimeParam;
@Override
public void initializeSubResource() throws ResourceException {
parseRequest();
raiseConflictIfAbortIsSet();
initNodeParameters();
}
private void parseRequest() {
extractAndSetIgnoreAbort();
nodename = getAttribute("node");
}
private void initNodeParameters() {
String multiplicityParamName = DeploymentFactory.constructParamName(nodename, RuntimeParameter.MULTIPLICITY_PARAMETER_NAME);
nodeMultiplicityRunParam = multiplicityParamName;
nodeMultiplicityRuntimeParam = multiplicityParamName;
nodeIndicesRuntimeParam = DeploymentFactory.constructParamName(nodename, RuntimeParameter.IDS_PARAMETER_NAME);
}
@Get
public Representation represent(Representation entity) {
Run run = Run.loadFromUuid(getUuid());
List<String> instanceNames = run.getNodeInstanceNames(nodename);
return new StringRepresentation(StringUtils.join(instanceNames, ","), MediaType.TEXT_PLAIN);
}
@Post
public Representation addNodeInstances(Representation entity)
throws ResourceException {
Representation result = null;
try {
result = addNodeInstancesInTransaction(entity);
} catch (ResourceException e) {
throw e;
} catch (SlipStreamClientException e) {
throwClientConflicError(e.getMessage(), e);
} catch (SlipStreamException e) {
throwServerError(e.getMessage(), e);
} catch (Exception e) {
throwServerError(e.getMessage(), e);
}
return result;
}
private Representation addNodeInstancesInTransaction(Representation entity)
throws Exception {
EntityManager em = PersistenceUtil.createEntityManager();
EntityTransaction transaction = em.getTransaction();
Run run = Run.loadFromUuid(getUuid(), em);
List<String> instanceNames = new ArrayList<String>();
try {
validateRun(run);
transaction.begin();
int noOfInst = getNumberOfInstancesToAdd(new Form(entity));
Node node = getNode(run, nodename);
for (int i = 0; i < noOfInst; i++) {
instanceNames.add(createNodeInstanceOnRun(run, node));
}
run.postEventScaleUp(nodename, instanceNames, noOfInst);
incrementNodeMultiplicityOnRun(noOfInst, run);
StateMachine.createStateMachine(run).tryAdvanceToProvisionning();
if (Configuration.isQuotaEnabled()) {
User user = User.loadByName(run.getUser());
Quota.validate(user, run.getCloudServiceUsage(), Vm.usage(user.getName()));
}
transaction.commit();
} catch (Exception ex) {
if (transaction.isActive()) {
transaction.rollback();
}
throw ex;
} finally {
em.close();
}
getResponse().setStatus(Status.SUCCESS_CREATED);
return new StringRepresentation(StringUtils.join(instanceNames, ","), MediaType.TEXT_PLAIN);
}
@Delete
public void deleteNodeInstances(Representation entity) throws Exception {
try {
deleteNodeInstancesInTransaction(entity);
} catch (ResourceException e) {
throw e;
} catch (SlipStreamClientException e) {
throwClientConflicError(e.getMessage(), e);
} catch (SlipStreamException e) {
throwServerError(e.getMessage(), e);
} catch (Exception e) {
throwServerError(e.getMessage(), e);
}
}
private void deleteNodeInstancesInTransaction(Representation entity) throws Exception {
EntityManager em = PersistenceUtil.createEntityManager();
EntityTransaction transaction = em.getTransaction();
Run run = Run.loadFromUuid(getUuid(), em);
try {
Form form = new Form(entity);
boolean deleteOnly = "true".equals(form.getFirstValue(DELETE_INSTANCE_IDS_ONLY_FORM_PARAM, "").trim()) ? true
: false;
if (!deleteOnly) {
validateRun(run);
}
transaction.begin();
String ids = form.getFirstValue(INSTANCE_IDS_REMOVE_FORM_PARAM, "");
if (ids.isEmpty()) {
throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST,
"Provide list of node instance IDs to be removed from the Run.");
} else {
String cloudServiceName = "";
try{
cloudServiceName = run.getCloudServiceNameForNode(nodename);
} catch (NullPointerException ex) {
throwClientBadRequest("Invalid nodename: " + nodename);
}
List<String> instanceIds = Arrays.asList(ids.split("\\s*,\\s*"));
for (String _id : instanceIds) {
String instanceName = "";
try {
instanceName = getNodeInstanceName(Integer.parseInt(_id));
} catch (NumberFormatException ex) {
throwClientBadRequest("Invalid instance name: " + _id);
}
setRemovingNodeInstance(run, instanceName);
run.removeNodeInstanceName(instanceName, cloudServiceName);
}
run.postEventScaleDown(nodename, instanceIds);
// update instance ids
removeNodeInstanceIndices(run, instanceIds);
decrementNodeMultiplicityOnRun(instanceIds.size(), run);
}
if (!deleteOnly) {
StateMachine.createStateMachine(run).tryAdvanceToProvisionning();
}
transaction.commit();
} catch (Exception ex) {
if (transaction.isActive()) {
transaction.rollback();
}
throw ex;
} finally {
em.close();
}
getResponse().setStatus(Status.SUCCESS_NO_CONTENT);
}
private void raiseConflictIfAbortIsSet() {
// Let certain methods to succeed if 'ignore abort' is set.
if (!isIgnoreAbort() && isAbortSet()) {
throw (new ResourceException(Status.CLIENT_ERROR_CONFLICT,
"Abort flag raised!"));
}
}
private boolean isIgnoreAbort() {
Method httpMethod = getMethod();
if (getIgnoreAbort() && IGNORE_ABORT_HTTP_METHODS.contains(httpMethod)) {
return true;
} else {
return false;
}
}
/*
Empty request form adds NUMBER_INSTANCES_ADD_DEFAULT node instances.
Requesting zero instances is allowed by simply NUMBER_INSTANCES_ADD_FORM_PARAM=0.
Throws ResourceException on
- non-empty request form without NUMBER_INSTANCES_ADD_FORM_PARAM form parameter;
- failure to parse NUMBER_INSTANCES_ADD_FORM_PARAM value.
**/
protected static int getNumberOfInstancesToAdd(Form form) {
Set<String> formParams = form.getNames();
try {
if (formParams.contains(NUMBER_INSTANCES_ADD_FORM_PARAM)) {
return Integer.parseInt(form.getFirstValue(NUMBER_INSTANCES_ADD_FORM_PARAM));
} else if (formParams.isEmpty()) {
return Integer.parseInt(NUMBER_INSTANCES_ADD_DEFAULT);
} else {
throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST,
"No form parameter " + NUMBER_INSTANCES_ADD_FORM_PARAM +
"=# found in the scale-up request.");
}
} catch (NumberFormatException e) {
throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST,
"Number of instances to add should be an integer.");
}
}
private String createNodeInstanceOnRun(Run run, Node node)
throws NotFoundException, AbortException, ValidationException {
int newId = addNodeInstanceIndex(run, node);
createNodeInstanceRuntimeParameters(run, node, newId);
String instanceName = getNodeInstanceName(newId);
return instanceName;
}
private void createNodeInstanceRuntimeParameters(Run run, Node node,
int newId) throws ValidationException, NotFoundException {
DeploymentFactory.initNodeInstanceRuntimeParameters(run, node, newId);
//TODO: LS: check this part
// add mapping parameters
for (NodeParameter param : node.getParameterMappings().values()) {
if (!param.isStringValue()) {
DeploymentFactory.addParameterMapping(run, param, newId);
}
}
}
private Node getNode(Run run, String nodename) {
DeploymentModule deployment = (DeploymentModule) run.getModule();
Node node = deployment.getNode(nodename);
if (node != null) {
return node;
} else {
throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST,
"Node " + nodename + " doesn't exist.");
}
}
private void removeNodeInstanceIndices(Run run, List<String> ids)
throws NotFoundException, AbortException, ValidationException {
List<String> nodeIds = new ArrayList<String>(
Arrays.asList(getNodeInstanceIndices(run).split("\\s*,\\s*")));
nodeIds.removeAll(ids);
String newNodeIds = StringUtils.join(nodeIds, ",");
setNodeInstanceIndices(run, newNodeIds);
}
private void validateRun(Run run) {
if (!run.isMutable()) {
throw new ResourceException(Status.CLIENT_ERROR_CONFLICT,
"Can't add/remove instances. Run is not mutable.");
}
States currentState = run.getState();
if (currentState != States.Ready) {
throw new ResourceException(Status.CLIENT_ERROR_CONFLICT,
"Can't add/remove instances. Incompatible state "
+ currentState.toString() + ". This can only be performed on state "
+ States.Ready.toString());
}
}
private int addNodeInstanceIndex(Run run, Node node) throws NotFoundException,
AbortException, ValidationException {
String ids = getNodeInstanceIndices(run);
String key = DeploymentFactory.constructNodeParamName(node, RunParameter.NODE_INCREMENT_KEY);
RunParameter nodeInscrement = run.getParameter(key);
int newId = Integer.parseInt(nodeInscrement.getValue("0"));
nodeInscrement.setValue(String.valueOf(newId + 1));
if (!ids.isEmpty()) {
ids += ",";
}
ids += newId;
setNodeInstanceIndices(run, ids);
return newId;
}
private void setRemovingNodeInstance(Run run, String instanceName)
throws NotFoundException, ValidationException {
setScaleActionOnNodeInstance(run, instanceName, "removing");
}
private void setScaleActionOnNodeInstance(Run run, String instanceName,
String action) throws NotFoundException, ValidationException {
run.updateRuntimeParameter(RuntimeParameter.constructParamName(
instanceName, RuntimeParameter.SCALE_STATE_KEY), action);
}
private String getNodeInstanceIndices(Run run) throws NotFoundException, AbortException {
return run.getRuntimeParameterValue(nodeIndicesRuntimeParam);
}
private void setNodeInstanceIndices(Run run, String indices)
throws ValidationException, NotFoundException {
run.updateRuntimeParameter(nodeIndicesRuntimeParam, indices);
}
private String getNodeInstanceName(int index) {
return RuntimeParameter.constructNodeInstanceName(nodename, index);
}
private void decrementNodeMultiplicityOnRun(int decrement, Run run)
throws ValidationException, NotFoundException {
incrementNodeMultiplicityOnRun(-1 * decrement, run);
}
private void incrementNodeMultiplicityOnRun(int inc, Run run)
throws ValidationException, NotFoundException {
// node_name--multiplicity - Run Parameter
int newMultiplicity = getNodeGroupMulptilicity(run) + inc;
if (newMultiplicity < 0) {
newMultiplicity = 0;
}
// node_name:multiplicity - Runtime Parameter
run.updateRuntimeParameter(nodeMultiplicityRuntimeParam,
Integer.toString(newMultiplicity));
}
private int getNodeGroupMulptilicity(Run run) throws NumberFormatException, NotFoundException {
return Integer.parseInt(run.getRuntimeParameterValueIgnoreAbort(nodeMultiplicityRunParam));
}
}