/**
* Copyright 2014 SAP AG
*
* 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.spotter.service;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.ws.rs.core.StreamingOutput;
import javax.xml.bind.JAXBException;
import org.lpe.common.config.ConfigParameterDescription;
import org.lpe.common.extension.ExtensionRegistry;
import org.lpe.common.extension.Extensions;
import org.lpe.common.extension.IExtension;
import org.lpe.common.extension.IExtensionArtifact;
import org.lpe.common.util.LpeFileUtils;
import org.lpe.common.util.LpeStreamUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spotter.core.AbstractSpotterSatelliteExtension;
import org.spotter.core.Spotter;
import org.spotter.core.detection.AbstractDetectionExtension;
import org.spotter.core.instrumentation.AbstractInstrumentationExtension;
import org.spotter.core.measurement.AbstractMeasurmentExtension;
import org.spotter.core.workload.AbstractWorkloadExtension;
import org.spotter.shared.configuration.ConfigKeys;
import org.spotter.shared.configuration.FileManager;
import org.spotter.shared.configuration.JobDescription;
import org.spotter.shared.configuration.SpotterExtensionType;
import org.spotter.shared.hierarchy.model.RawHierarchyFactory;
import org.spotter.shared.hierarchy.model.XPerformanceProblem;
import org.spotter.shared.result.ResultsLocationConstants;
import org.spotter.shared.status.SpotterProgress;
/**
* Wraps Spotter execution in a service layer.
*
* @author Alexander Wert, Denis Knoepfle
*
*/
public class SpotterServiceWrapper {
private static final Logger LOGGER = LoggerFactory.getLogger(SpotterServiceWrapper.class);
/**
* The path to the current working directory.
*/
private static final String WORKING_DIR = System.getProperty("user.dir").replace('\\', '/');
/**
* The name of the folder where to put the configuration and results of
* diagnosis runs.
*/
private static final String RUNTIME_FOLDER = "runtime-diagnosis";
private static final String ZIP_FILE_EXTENSION = ".zip";
private static SpotterServiceWrapper instance;
/**
*
* @return singleton instance
*/
public static synchronized SpotterServiceWrapper getInstance() {
if (instance == null) {
instance = new SpotterServiceWrapper();
}
return instance;
}
private ExecutorService executor = Executors.newFixedThreadPool(1);
private Future<?> futureObject = null;
private long currentJob;
private JobState currentJobState = JobState.FINISHED;
/**
* Executes diagnostics process.
*
* @param jobDescription
* job description object containing the whole DS setup such as
* config values, environment and hierarchy configuration
* @return job id for the started diagnosis task, 0 if currently a diagnosis
* job is already running
*/
public synchronized long startDiagnosis(final JobDescription jobDescription) {
if (getState().equals(JobState.RUNNING)) {
return 0;
}
final long tempJobId = System.currentTimeMillis();
currentJob = tempJobId;
currentJobState = JobState.RUNNING;
futureObject = executor.submit(new Runnable() {
@Override
public void run() {
try {
String configurationFile = createDynamicSpotterConfiguration(tempJobId, jobDescription);
Spotter.getInstance().startDiagnosis(configurationFile, tempJobId);
currentJobState = JobState.FINISHED;
} catch (Throwable e) {
LOGGER.error("Diagnosis failed!", e);
writeDiagnosisErrorFile(Spotter.getInstance().getDiagnosisResultFolder(), e);
currentJobState = JobState.CANCELLED;
throw new RuntimeException(e);
} finally {
currentJob = 0;
}
}
});
return tempJobId;
}
/**
* Requests the results of a the run with the given job id.
*
* @param jobId
* the job id of the diagnosis run
* @return output streaming the zipped run result folder or
* <code>null</code> if none found
*/
public StreamingOutput requestResults(String jobId) {
String location = getRuntimeLocation() + "/" + jobId;
File dir = new File(location);
if (dir.isDirectory()) {
final String resultsFolder = location + "/" + FileManager.DEFAULT_RESULTS_DIR_NAME;
StreamingOutput output = new StreamingOutput() {
@Override
public void write(OutputStream os) {
pipeRunFolderToOutputStream(resultsFolder, os);
}
};
return output;
}
return null;
}
/**
* Returns the current state of the last issued job.
*
* @return the current state of the last issued job
*/
public synchronized JobState getState() {
return currentJobState;
}
/**
* Checks whether a concurrent execution exception has been thrown. If this
* is the case, this method throws a ExecutionException.
*
* @throws InterruptedException
* if job has been interrupted
* @throws ExecutionException
* if concurrent exception occured
*/
public void checkForConcurrentExecutionException() throws InterruptedException, ExecutionException {
futureObject.get();
}
/**
* Returns a report on the progress of the current job.
*
* @return progress report
*/
public SpotterProgress getCurrentProgressReport() {
return Spotter.getInstance().getProgress();
}
/**
* Returns the id of the currently running job.
*
* @return id
*/
public long getCurrentJobId() {
return currentJob;
}
/**
* Returns the root problem of the currently running job.
*
* @return the root problem
*/
public XPerformanceProblem getCurrentRootProblem() {
return Spotter.getInstance().getCurrentRootProblem();
}
/**
*
* @return list of configuration parameter descriptions for Spotter
* configuration.
*/
public synchronized Set<ConfigParameterDescription> getConfigurationParameters() {
return ConfigKeys.getSpotterConfigParamters();
}
/**
* Returns a list of extension names for the given extension type.
*
* @param extType
* extension type of interest
* @return list of names
*/
public Set<String> getAvailableExtensions(SpotterExtensionType extType) {
Class<? extends IExtension<? extends IExtensionArtifact>> extClass = null;
switch (extType) {
case DETECTION_EXTENSION:
extClass = AbstractDetectionExtension.class;
break;
case INSTRUMENTATION_EXTENSION:
extClass = AbstractInstrumentationExtension.class;
break;
case MEASUREMENT_EXTENSION:
extClass = AbstractMeasurmentExtension.class;
break;
case WORKLOAD_EXTENSION:
extClass = AbstractWorkloadExtension.class;
break;
default:
break;
}
Extensions<? extends IExtension<? extends IExtensionArtifact>> extensions = ExtensionRegistry.getSingleton()
.getExtensions(extClass);
Set<String> extensionNames = new HashSet<>();
for (IExtension<? extends IExtensionArtifact> ext : extensions.getList()) {
extensionNames.add(ext.getName());
}
return extensionNames;
}
/**
* Returns a set of available configuration parameters for the given
* extension.
*
* @param extName
* name of the extension of interest
* @return list of configuration parameters
*/
public Set<ConfigParameterDescription> getExtensionConfigParamters(String extName) {
IExtension<? extends IExtensionArtifact> extension = ExtensionRegistry.getSingleton().getExtension(extName);
if (extension == null) {
return null;
}
return extension.getConfigParameters();
}
/**
* Returns the default hierarchy.
*
* @return default hierarchy
*/
public XPerformanceProblem getDefaultHierarchy() {
return RawHierarchyFactory.getInstance().createProblemHierarchyRoot();
}
/**
* Tests connection to the satellite specified by the given extension name,
* host and port. If extension is not a satellite this method returns false!
*
* @param extName
* name of the extension to connect to
* @param host
* host / ip to connect to
* @param port
* port to connect to
* @return true if connection could have been established, otherwise false
*/
public boolean testConnectionToSattelite(String extName, String host, String port) {
IExtension<? extends IExtensionArtifact> extension = ExtensionRegistry.getSingleton().getExtension(extName);
if (extension == null) {
return false;
}
if (extension instanceof AbstractSpotterSatelliteExtension) {
AbstractSpotterSatelliteExtension satellite = (AbstractSpotterSatelliteExtension) extension;
return satellite.testConnection(host, port);
}
return false;
}
/**
* Creates necessary configuration files for the diagnosis from the given
* job description.
*
* @param jobId
* the job id of the diagnosis
* @param jobDescription
* the job description which holds the configuration information
* @return the path to the DS configuration file which is required by DS
*/
private String createDynamicSpotterConfiguration(long jobId, JobDescription jobDescription) {
FileManager fileManager = FileManager.getInstance();
String location = getRuntimeLocation() + "/" + jobId;
LpeFileUtils.createDir(location);
String configurationFile = null;
try {
fileManager.writeEnvironmentConfig(location, jobDescription.getMeasurementEnvironment());
fileManager.writeHierarchyConfig(location, jobDescription.getHierarchy());
configurationFile = fileManager.writeSpotterConfig(location, jobDescription.getDynamicSpotterConfig());
LOGGER.info("Storing configuration for diagnosis run #" + jobId + " in " + location);
} catch (IOException | JAXBException e) {
String message = "Failed to create DS configuration.";
LOGGER.error(message, e);
throw new RuntimeException(message, e);
}
return configurationFile;
}
private void writeDiagnosisErrorFile(String folder, Throwable throwable) {
String errorFile = folder + ResultsLocationConstants.TXT_DIAGNOSIS_ERROR_FILE_NAME;
PrintWriter pw = null;
try {
pw = new PrintWriter(new FileWriter(errorFile));
pw.println("Diagnosis failed!");
pw.println();
if (throwable.getMessage() != null && !throwable.getMessage().isEmpty()) {
pw.println(throwable.getMessage());
}
throwable.printStackTrace(pw);
} catch (IOException e) {
LOGGER.warn("Problem occurred while creating error file!", e);
} finally {
if (pw != null) {
pw.close();
}
}
}
private static String getRuntimeLocation() {
return SpotterServiceWrapper.WORKING_DIR + "/" + SpotterServiceWrapper.RUNTIME_FOLDER;
}
private void pipeRunFolderToOutputStream(String resultsDirLocation, OutputStream os) {
String location = resultsDirLocation;
File resultsDir = new File(location);
if (resultsDir.isDirectory()) {
File[] subdirs = resultsDir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory();
}
});
if (subdirs.length == 1) {
location += "/" + subdirs[0].getName();
// pipe zipped run result folder
FileInputStream fileInputStream = null;
try {
fileInputStream = getZippedRunFolder(location);
LpeStreamUtils.pipe(fileInputStream, os);
} catch (IOException e) {
LOGGER.error("Error while streaming results data.", e);
throw new RuntimeException(e);
} finally {
try {
if (fileInputStream != null) {
fileInputStream.close();
}
} catch (IOException e) {
LOGGER.warn("Error while closing stream '{}'.", location);
}
}
} else {
LOGGER.warn("Expected to find only one results folder, but found " + subdirs.length
+ ", streaming nothing!");
}
}
}
private FileInputStream getZippedRunFolder(String path) throws FileNotFoundException {
String zipFileName = path + ZIP_FILE_EXTENSION;
File zipFile = new File(zipFileName);
File source = new File(path);
if (!zipFile.exists()) {
LOGGER.debug("Packing main results data from '{}' ...", path);
FileFilter fileFilter = new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.isFile()) {
return true;
}
if (pathname.isDirectory() && !pathname.getName().equals(ResultsLocationConstants.CSV_SUB_DIR)) {
return true;
}
return false;
}
};
LpeFileUtils.zip(source, zipFile, fileFilter);
LOGGER.debug("Results data packed!");
}
FileInputStream fis = new FileInputStream(zipFileName);
return fis;
}
}