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 java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.persistence.OptimisticLockException;
import javax.persistence.RollbackException;
import com.sixsq.slipstream.dashboard.DashboardResource;
import org.hibernate.StaleObjectStateException;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.Get;
import org.restlet.resource.Post;
import org.restlet.resource.ResourceException;
import com.sixsq.slipstream.configuration.Configuration;
import com.sixsq.slipstream.connector.Connector;
import com.sixsq.slipstream.connector.ConnectorFactory;
import com.sixsq.slipstream.connector.ExecutionControlUserParametersFactory;
import com.sixsq.slipstream.credentials.Credentials;
import com.sixsq.slipstream.exceptions.ConfigurationException;
import com.sixsq.slipstream.exceptions.ServerExecutionEnginePluginException;
import com.sixsq.slipstream.exceptions.SlipStreamClientException;
import com.sixsq.slipstream.exceptions.SlipStreamException;
import com.sixsq.slipstream.exceptions.ValidationException;
import com.sixsq.slipstream.factory.RunFactory;
import com.sixsq.slipstream.persistence.Module;
import com.sixsq.slipstream.persistence.ModuleCategory;
import com.sixsq.slipstream.persistence.ModuleParameter;
import com.sixsq.slipstream.persistence.NodeParameter;
import com.sixsq.slipstream.persistence.Parameter;
import com.sixsq.slipstream.persistence.Run;
import com.sixsq.slipstream.persistence.RunParameter;
import com.sixsq.slipstream.persistence.RunType;
import com.sixsq.slipstream.persistence.RuntimeParameter;
import com.sixsq.slipstream.persistence.ServiceConfiguration;
import com.sixsq.slipstream.persistence.User;
import com.sixsq.slipstream.persistence.UserParameter;
import com.sixsq.slipstream.persistence.Vm;
import com.sixsq.slipstream.resource.BaseResource;
import com.sixsq.slipstream.util.ConfigurationUtil;
import com.sixsq.slipstream.util.HtmlUtil;
import com.sixsq.slipstream.util.RequestUtil;
import com.sixsq.slipstream.util.SerializationUtil;
/**
* Unit test:
*
* @see RunListResourceTest.class
*
*/
public class RunListResource extends BaseResource {
public static final String TYPE = "type";
public static final String REFQNAME = "refqname";
public static final String MUTABLE_RUN_KEY = "mutable";
public static final String IGNORE_ABORT_QUERY = "ignoreabort";
public static final String BYPASS_SSH_CHECK_KEY = "bypass-ssh-check";
public static final String KEEP_RUNNING_KEY = "keep-running";
public static final String TAGS_KEY = "tags";
public static final String REDIRECT_TO_DASHBOARD = "redirect_to_dashboard";
String refqname = null;
@Get("txt")
public Representation toTxt() {
RunViewList runViewList = getRunViewList();
String result = SerializationUtil.toXmlString(runViewList);
return new StringRepresentation(result);
}
@Get("xml")
public Representation toXml() {
RunViewList runViewList = getRunViewList();
String result = SerializationUtil.toXmlString(runViewList);
return new StringRepresentation(result, MediaType.APPLICATION_XML);
}
@Get("html")
public Representation toHtml() {
RunViewList runViewList = getRunViewList();
return new StringRepresentation(HtmlUtil.toHtml(runViewList,
getPageRepresentation(), getUser(), getRequest()),
MediaType.TEXT_HTML);
}
private RunViewList getRunViewList() {
int limit = getLimit(Run.DEFAULT_LIMIT, LIMIT_MAX);
RunViewList list = new RunViewList();
try {
RunsQueryParameters parameters = new RunsQueryParameters(getUser(), getOffset(), limit, getCloud(),
getRunOwner(), getUserFilter(), getModuleResourceUri(), getActiveOnly());
list.populate(parameters);
} catch (ConfigurationException e) {
throwConfigurationException(e);
} catch (ValidationException e) {
throwClientValidationError(e.getMessage());
}
return list;
}
/**
* We need to merge data from different sources: - the form (entity) - the
* default module
*
* In the case of orchestrator + VM(s) (i.e. deployment and build) we also
* need to merge: - the default from each node
*
* The service cloud is the part that causes most trouble, since it can be
* defined at all levels.
*/
@Post("form|txt")
public void createRun(Representation entity) throws ResourceException,
FileNotFoundException, IOException, SQLException,
ClassNotFoundException, SlipStreamException {
Form form = new Form(entity);
setReference(form);
Run run;
try {
Module module = loadReferenceModule();
authorizePost(module);
updateReference(module);
module.validate();
User user = getUser();
user = User.loadByName(user.getName()); // ensure user is loaded from database
RunType type = parseType(form, module);
validateUserPublicKey(user, type, form);
Map<String, List<Parameter<?>>> userChoices = getUserChoicesFromForm(module.getCategory(), form);
run = RunFactory.getRun(module, type, user, userChoices);
run = addCredentials(run);
setRunMutability(run, form);
setKeepRunning(run, form);
setTags(run, form);
if (Configuration.isQuotaEnabled()) {
Quota.validate(user, run.getCloudServiceUsage(), Vm.usage(user.getName()));
}
createRepositoryResource(run);
run.store();
launch(run);
setLastExecute(user);
run.postEventCreated();
} catch (SlipStreamClientException ex) {
throw (new ResourceException(Status.CLIENT_ERROR_CONFLICT, ex.getMessage()));
}
setResponseLocation(run, form);
getResponse().setStatus(Status.SUCCESS_CREATED);
}
private void setResponseLocation(Run run, Form form) {
// if the query parameter 'redirect' is set, then redirect to its value
// this is a gap solution, such that the CLI can retrieve the uuid, while
// the ui can redirect to the dashboard
String redirect = form.getFirstValue(REDIRECT_TO_DASHBOARD);
String location;
if(redirect != null) {
location = "/" + DashboardResource.RESOURCE_URI_PREFIX;
} else {
location = "/" + Run.RESOURCE_URI_PREFIX + run.getName();
}
String absolutePath = RequestUtil.constructAbsolutePath(getRequest(), location);
getResponse().setLocationRef(absolutePath);
}
private void setLastExecute(User user) {
user.setLastExecute();
try {
user = user.store();
} catch (StaleObjectStateException e) {
} catch (RollbackException e) {
} catch (OptimisticLockException e) {
}
}
private void setKeepRunning(Run run, Form form) throws ValidationException {
String keepRunning = form.getFirstValue(KEEP_RUNNING_KEY, null);
if (keepRunning != null) {
List<String> keepRunningOptions = UserParameter.getKeepRunningOptions();
if (! keepRunningOptions.contains(keepRunning)) {
throw new ValidationException("Value of " + KEEP_RUNNING_KEY + " should be one of the following: "
+ keepRunningOptions.toString());
}
String key = RunParameter.constructKey(ExecutionControlUserParametersFactory.CATEGORY,
UserParameter.KEY_KEEP_RUNNING);
RunParameter rp = run.getParameter(key);
if (rp != null) {
rp.setValue(keepRunning);
}
}
}
private void setRunMutability(Run run, Form form) {
String mutable = form.getFirstValue(MUTABLE_RUN_KEY, "");
if (isTrue(mutable)) {
run.setMutable();
}
}
private void validateUserPublicKey(User user, RunType type, Form form) throws ValidationException{
boolean bypassSshCheck = isTrue(form.getFirstValue(BYPASS_SSH_CHECK_KEY, "false"));
if (type != RunType.Machine && !bypassSshCheck) {
RunFactory.validateUserPublicSshKeys(user);
}
}
private void setTags(Run run, Form form) {
RuntimeParameter rp = run.getRuntimeParameters().get(RuntimeParameter.GLOBAL_TAGS_KEY);
if (rp != null){
rp.setValue(form.getFirstValue(TAGS_KEY, ""));
}
}
private void authorizePost(Module module) {
if(!module.getAuthz().canPost(getUser())) {
throwClientForbiddenError("User does not have the rights to execute this module");
}
}
private void setReference(Form form) {
refqname = form.getFirstValue(REFQNAME);
if (refqname == null) {
throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST,
"Missing refqname in POST");
}
refqname = refqname.trim();
}
public static Map<String, List<Parameter<?>>> getUserChoicesFromForm(ModuleCategory category, Form form) throws ValidationException {
Map<String, List<Parameter<?>>> parametersPerNode = new HashMap<String, List<Parameter<?>>>();
for (Entry<String, String> entry : form.getValuesMap().entrySet()) {
if (notUserChoiceForNode(entry)) {
continue;
}
// parameter--node--[nodename]--[paramname]
String[] parts = entry.getKey().split("--");
String nodeName = "";
String parameterName = "";
if (category == ModuleCategory.Deployment) {
if (parts.length != 4) {
throw new ValidationException("Invalid key format for " + category + " module: " + entry.getKey());
}
nodeName = parts[2];
parameterName = parts[3];
} else {
if (parts.length != 2) {
throw new ValidationException("Invalid key format for " + category + " module: " + entry.getKey());
}
nodeName = Run.MACHINE_NAME;
parameterName = parts[1];
}
String value = entry.getValue();
if (category == ModuleCategory.Deployment) {
if (!parametersPerNode.containsKey(nodeName)) {
parametersPerNode.put(nodeName, new ArrayList<Parameter<?>>());
}
Parameter<?> parameter = new NodeParameter(parameterName);
value = NodeParameter.isStringValue(value) ? value : "'" + value + "'";
parameter.setValue(value);
parametersPerNode.get(nodeName).add(parameter);
} else {
if (!parametersPerNode.containsKey(nodeName)) {
parametersPerNode.put(nodeName, new ArrayList<Parameter<?>>());
}
Parameter<?> parameter = new ModuleParameter(parameterName);
parameter.setValue(value);
parametersPerNode.get(nodeName).add(parameter);
}
}
return parametersPerNode;
}
private static boolean notUserChoiceForNode(Entry<String, String> entry) {
List<String> keysToFilter = new ArrayList<String>();
keysToFilter.add(RunListResource.REFQNAME);
keysToFilter.add(RunListResource.MUTABLE_RUN_KEY);
keysToFilter.add(RunListResource.TYPE);
keysToFilter.add(RunListResource.KEEP_RUNNING_KEY);
keysToFilter.add(RunListResource.BYPASS_SSH_CHECK_KEY);
keysToFilter.add(RunListResource.TAGS_KEY);
keysToFilter.add(RunListResource.REDIRECT_TO_DASHBOARD);
if (keysToFilter.contains(entry.getKey())) {
return true;
}
return false;
}
private RunType parseType(Form form, Module module) {
RunType defaultRunType = module.getCategory() == ModuleCategory.Image ? RunType.Run : RunType.Orchestration;
String type = form.getFirstValue(RunListResource.TYPE, true, defaultRunType.toString());
try {
return RunType.valueOf(type);
} catch (IllegalArgumentException e) {
throw (new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Unknown run type: " + type));
}
}
private Run launch(Run run) throws SlipStreamException {
User user = getUser();
user.addSystemParametersIntoUser(Configuration.getInstance().getParameters());
slipstream.async.Launcher.launch(run, user);
return run;
}
private Run addCredentials(Run run) throws ConfigurationException,
ServerExecutionEnginePluginException, ValidationException {
Credentials credentials = loadCredentialsObject();
run.setCredentials(credentials);
return run;
}
private Credentials loadCredentialsObject() throws ConfigurationException,
ValidationException {
Connector connector = ConnectorFactory.getCurrentConnector(getUser());
return connector.getCredentials(getUser());
}
private void createRepositoryResource(Run run)
throws ConfigurationException {
String repositoryLocation;
repositoryLocation = ConfigurationUtil
.getConfigurationFromRequest(getRequest())
.getRequiredProperty(
ServiceConfiguration.RequiredParameters.SLIPSTREAM_REPORTS_LOCATION
.getName());
String absRepositoryLocation = repositoryLocation + "/" + run.getName();
boolean createdOk = new File(absRepositoryLocation).mkdirs();
// Create the repository structure
if (!createdOk) {
throw new ResourceException(Status.SERVER_ERROR_INTERNAL,
"Error creating repository structure: " + absRepositoryLocation);
}
}
private Module loadReferenceModule() throws ValidationException {
Module module = Module.load(refqname);
if (module == null) {
throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND,
"Coudn't find reference module: " + refqname);
}
return module;
}
private void updateReference(Module module) {
refqname = module.getName();
}
@Override
protected String getPageRepresentation() {
return "runs";
}
}