Skip to content

Commit

Permalink
Add architecture analysis endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
franksn90 committed Aug 24, 2024
1 parent 41694d6 commit bed6fc2
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package restAPI.architecture_analysis;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import restAPI.util.TempFileUtils;

import java.io.IOException;
import java.nio.file.Path;

@RestController
public class ArchitectureAnalysisController {
public static final String architectureFieldName = "architecture";
private final ArchitectureAnalysisService architectureAnalysisService;

Logger logger = LoggerFactory.getLogger(ArchitectureAnalysisController.class);

@Autowired
public ArchitectureAnalysisController(ArchitectureAnalysisService architectureAnalysisService) {
this.architectureAnalysisService = architectureAnalysisService;
}

@PostMapping("/analyze/upload")
public ResponseEntity<String> handleMultipleFilesUpload(
@RequestParam("architectures") MultipartFile[] architectures) throws IOException {
return runSimulation(architectures);
}

// TODO: Handle this call in a non-blocking manner, taking into account that this implementation is not
// client friendly as it can time-out the request due to the long processing time.
private ResponseEntity<String> runSimulation(MultipartFile[] architectures) throws IOException {
Path tmpFolder = null;
try {
tmpFolder = TempFileUtils.createDefaultTempDir("architecture-analysis");
Multimap<String, String> savedFiles = saveArchitectureFile(architectures, tmpFolder);
var response = architectureAnalysisService.runAnalysis(savedFiles);
return new ResponseEntity<>(response.toJSON(), HttpStatus.OK);
} catch (Exception e) {
String errorMessage = e.getMessage();
logger.error(errorMessage);
return new ResponseEntity<>(errorMessage, HttpStatus.INTERNAL_SERVER_ERROR);
} finally {
// Do the clean-up
if (tmpFolder != null) {
FileUtils.deleteDirectory(tmpFolder.toFile());
}
}
}

private Multimap<String, String> saveArchitectureFile(MultipartFile[] architectures, Path tmpFolder) {
Multimap<String, String> savedFiles = ArrayListMultimap.create();
savedFiles = TempFileUtils.saveFile(savedFiles, architectureFieldName, architectures, tmpFolder);
return savedFiles;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package restAPI.architecture_analysis;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class ArchitectureAnalysisExperimentFileGenerator {
private static final String experimentJSON = """
{
"simulation_metadata": {
"experiment_name": "Architecture-Only-Experiment",
"model_name": "Architecture-Only-Model",
"duration": 0
}
}""";

public File generateExperimentFile() throws IOException {
File file = File.createTempFile("architecture-only-experiment", ".json");
FileWriter writer = new FileWriter(file);
writer.write(experimentJSON);
writer.close();
return file;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package restAPI.architecture_analysis;

import cambio.simulator.ExperimentCreator;
import cambio.simulator.ExperimentStartupConfig;
import cambio.simulator.entities.NamedEntity;
import cambio.simulator.entities.microservice.Microservice;
import cambio.simulator.entities.microservice.Operation;
import cambio.simulator.models.ArchitectureModel;
import cambio.simulator.models.MiSimModel;
import com.google.common.collect.Multimap;
import desmoj.core.simulator.Experiment;
import org.springframework.stereotype.Service;
import restAPI.data_objects.ArchitectureAnalysisResponse;
import restAPI.data_objects.ArchitectureAnalysisResponseImpl;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import static java.util.stream.Collectors.toSet;

@Service
public class ArchitectureAnalysisService {
private final File experiment;

public ArchitectureAnalysisService() throws IOException {
this.experiment = (new ArchitectureAnalysisExperimentFileGenerator()).generateExperimentFile();
}

public ArchitectureAnalysisResponse runAnalysis(Multimap<String, String> inputFiles) throws Exception {
String archDescPath = getArchtectureDescPath(inputFiles);
ArchitectureModelAdapter architectureModel = new ArchitectureModelAdapter(getArchitectureModel(archDescPath));
return new ArchitectureAnalysisResponseImpl(architectureModel.getServiceNames(), architectureModel.getEndpointNames());
}

private String getArchtectureDescPath(Multimap<String, String> inputFiles) throws Exception {
Collection<String> archDescPathCollection = inputFiles.get(ArchitectureAnalysisController.architectureFieldName);
if (archDescPathCollection.isEmpty()) {
throw new Exception("You have to provide an architecture description file.");
}
return archDescPathCollection.iterator().next();
}

private ArchitectureModel getArchitectureModel(final String archDescPath) {
ExperimentStartupConfig config = new ExperimentStartupConfig(archDescPath, experiment.getAbsolutePath(),
null, null, null, false,
false, false, null);
Experiment experiment = (new ExperimentCreator()).createSimulationExperiment(config);
MiSimModel model = (MiSimModel) experiment.getModel();
return model.getArchitectureModel();
}

private static class ArchitectureModelAdapter {
private final ArchitectureModel architectureModel;

public ArchitectureModelAdapter(ArchitectureModel architectureModel) {
this.architectureModel = architectureModel;
}

public Set<String> getServiceNames() {
return architectureModel.getMicroservices().stream().map(NamedEntity::getPlainName).collect(toSet());
}

public Set<String> getEndpointNames() {
Set<String> names = new HashSet<>();
for (Microservice service : architectureModel.getMicroservices()) {
for (Operation operation : service.getOperations()) {
names.add(operation.getFullyQualifiedPlainName());
}
}
return names;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package restAPI.data_objects;

import com.fasterxml.jackson.core.JsonProcessingException;

public interface ArchitectureAnalysisResponse {
String toJSON() throws JsonProcessingException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package restAPI.data_objects;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Set;

public final class ArchitectureAnalysisResponseImpl implements ArchitectureAnalysisResponse {

private final Set<String> serviceNames;
private final Set<String> endpointNames;

public ArchitectureAnalysisResponseImpl(Set<String> serviceNames, Set<String> endpointNames) {
this.serviceNames = serviceNames;
this.endpointNames = endpointNames;
}

@Override
public String toJSON() throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(this);
}

public Set<String> getServiceNames() {
return serviceNames;
}

public Set<String> getEndpointNames() {
return endpointNames;
}
}

0 comments on commit bed6fc2

Please sign in to comment.