From 98474277fcb22c6c62798bcb79d929a529f11b03 Mon Sep 17 00:00:00 2001 From: Marta Date: Mon, 8 Apr 2024 13:00:43 +0200 Subject: [PATCH] cost performance indicators Change-Id: If89d229423dee74a6c0a5a61403209b4f7c4a6de --- utility-evaluator/pom.xml | 11 + .../UtilityEvaluatorApplication.java | 5 +- .../UtilityEvaluatorController.java | 74 ++++-- .../UtilityEvaluatorListener.java | 41 --- .../DslGenericMessageHandler.java | 55 ++++ .../exnconnector/ExnConnector.java | 27 +- .../exnconnector/ExnListener.java | 71 ------ .../exnconnector/GeneralMessageHandler.java | 61 ----- .../NodeCandidatesMessageHandler.java | 5 + .../PerformanceIndicatorSendingService.java | 120 +++++++++ .../sal/NodeCandidatesFetchingService.java | 77 +++++- .../communication/sal/ProactiveConnector.java | 2 +- .../NodeCandidateConverter.java | 48 +++- .../converter/VariableConverter.java | 98 +++++++ .../external/KubevelaAnalyzer.java | 208 +++++++++++++++ .../utilityevaluator/model/Application.java | 70 +++++ .../NodeCandidateDTO.java | 2 +- .../utilityevaluator/model/VariableDTO.java | 17 ++ .../utilityevaluator/model/VariableType.java | 26 ++ .../message/FetchNodeCandidatesMessage.java | 2 +- .../model/message/GenericDSLMessage.java | 132 ++++++++++ .../message/NodeCandidatesTensorMessage.java | 2 +- .../model/message/Variable.java | 17 ++ .../regression/SimpleCostRegression.java | 47 ++++ .../UtilityEvaluatorApplicationTests.java | 24 +- ...-419c5ac2-e8cb-4115-8aa1-27d41ba0a08e.ampl | 25 ++ ...-419c5ac2-e8cb-4115-8aa1-27d41ba0a08e.ampl | 32 +++ .../test/java/resources/Robertsmessage.json | 1 + .../app-creation-message-complex (3).json | 239 ++++++++++++++++++ .../app-creation-message-mercabarna.json | 151 +++++++++++ .../resources/complex-example-ampl (2).mod | 31 +++ .../java/resources/complex-metric-model.yaml | 67 +++++ .../src/test/java/resources/metric-model.yaml | 43 ++++ .../java/resources/response-all-clouds.json | 1 + 34 files changed, 1609 insertions(+), 223 deletions(-) delete mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/UtilityEvaluatorListener.java create mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/DslGenericMessageHandler.java delete mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/ExnListener.java delete mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/GeneralMessageHandler.java create mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/NodeCandidatesMessageHandler.java create mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/PerformanceIndicatorSendingService.java rename utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/{nodecandidates => converter}/NodeCandidateConverter.java (56%) create mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/converter/VariableConverter.java create mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/external/KubevelaAnalyzer.java create mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/Application.java rename utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/{nodecandidates => model}/NodeCandidateDTO.java (92%) create mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/VariableDTO.java create mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/VariableType.java rename utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/{communication/activemq => model}/message/FetchNodeCandidatesMessage.java (95%) create mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/message/GenericDSLMessage.java rename utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/{communication/activemq => model}/message/NodeCandidatesTensorMessage.java (83%) create mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/message/Variable.java create mode 100644 utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/regression/SimpleCostRegression.java create mode 100644 utility-evaluator/src/test/java/resources/2024-02-27T12_31_47.372531--to-solver-419c5ac2-e8cb-4115-8aa1-27d41ba0a08e.ampl create mode 100644 utility-evaluator/src/test/java/resources/2024-03-06T15_31_57.251674--to-solver-419c5ac2-e8cb-4115-8aa1-27d41ba0a08e.ampl create mode 100644 utility-evaluator/src/test/java/resources/Robertsmessage.json create mode 100644 utility-evaluator/src/test/java/resources/app-creation-message-complex (3).json create mode 100644 utility-evaluator/src/test/java/resources/app-creation-message-mercabarna.json create mode 100644 utility-evaluator/src/test/java/resources/complex-example-ampl (2).mod create mode 100644 utility-evaluator/src/test/java/resources/complex-metric-model.yaml create mode 100644 utility-evaluator/src/test/java/resources/metric-model.yaml create mode 100644 utility-evaluator/src/test/java/resources/response-all-clouds.json diff --git a/utility-evaluator/pom.xml b/utility-evaluator/pom.xml index 77e27a9..a0acc71 100644 --- a/utility-evaluator/pom.xml +++ b/utility-evaluator/pom.xml @@ -15,6 +15,7 @@ First release of Utility Evaluator component 17 + 2.16.1 @@ -36,6 +37,16 @@ commons-lang3 3.12.0 + + org.apache.commons + commons-math3 + 3.6.1 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson.version} + org.ow2.proactive sal-common diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/UtilityEvaluatorApplication.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/UtilityEvaluatorApplication.java index 2a1c928..adb75d5 100644 --- a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/UtilityEvaluatorApplication.java +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/UtilityEvaluatorApplication.java @@ -4,14 +4,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; - - +/* This is the equvalent of the main class which starts the Utility Evaluator */ @SpringBootApplication public class UtilityEvaluatorApplication { + public static void main(String[] args) { SpringApplication.run(UtilityEvaluatorApplication.class, args); - // The application is listening to messages and sav } } diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/UtilityEvaluatorController.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/UtilityEvaluatorController.java index 2bf8a47..d5c493e 100644 --- a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/UtilityEvaluatorController.java +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/UtilityEvaluatorController.java @@ -2,37 +2,67 @@ package eu.nebulous.utilityevaluator; import org.ow2.proactive.sal.model.NodeCandidate; import java.util.List; -import java.util.Optional; import org.springframework.stereotype.Component; -import eu.nebulous.utilityevaluator.communication.activemq.message.FetchNodeCandidatesMessage; +import eu.nebulous.utilityevaluator.communication.exnconnector.ExnConnector; +import eu.nebulous.utilityevaluator.communication.exnconnector.PerformanceIndicatorSendingService; import eu.nebulous.utilityevaluator.communication.sal.NodeCandidatesFetchingService; -import eu.nebulous.utilityevaluator.nodecandidates.NodeCandidateConverter; -import eu.nebulous.utilityevaluator.nodecandidates.NodeCandidateDTO; -import jline.internal.Log; +import eu.nebulous.utilityevaluator.converter.NodeCandidateConverter; +import eu.nebulous.utilityevaluator.model.Application; +import eu.nebulous.utilityevaluator.model.NodeCandidateDTO; +import eu.nebulous.utilityevaluator.model.VariableDTO; +import eu.nebulous.utilityevaluator.regression.SimpleCostRegression; +import eu.nebulouscloud.exn.core.Publisher; +import eu.nebulouscloud.exn.core.SyncedPublisher; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; - -@Component -@RequiredArgsConstructor +/* The main controlling component. It coordinates the work TODO*/ +@Slf4j +//@Component public class UtilityEvaluatorController { - private final NodeCandidatesFetchingService nodeCandidatesService; - - -//this is the main method of Utiliy Evaluator. It creates a .csv file with available Node Candidates - public Optional createNodeCandidatesTensor(FetchNodeCandidatesMessage message){ - - Log.info("Creating Node Candidates tensor..."); - List nodeCandidates = nodeCandidatesService.getNodeCandidates(message.getCloudProviders()); - - //convert Node Candidates, possibly also filter (in the future) - List convertedNodeCandidates = NodeCandidateConverter.convertToDtoList(nodeCandidates); - String csv = NodeCandidateConverter.convertToCsv(convertedNodeCandidates); - - return Optional.of(csv); + private NodeCandidatesFetchingService nodeCandidatesService; + private PerformanceIndicatorSendingService performanceIndicatorSendingService; + public UtilityEvaluatorController(SyncedPublisher nodeCandidatesGetter, Publisher performanceIndicatorPublisher){ + this.nodeCandidatesService = new NodeCandidatesFetchingService(nodeCandidatesGetter); + this.performanceIndicatorSendingService = new PerformanceIndicatorSendingService(performanceIndicatorPublisher); } + + public Application createInitialCostPerformanceIndicators(Application application){ + /* + * for each component of the application (that has variables), it should: + * + * convert them to DTO + * for types variables that are there, create a list of arguments to the regression + * create regression object + * save it back in the application + * send the parameters via ActiveMQ (maybe in the handler?) + */ + for (String component : application.getVariables().keySet()){ + + List nodeCandidates = nodeCandidatesService.getNodeCandidatesViaMiddleware(application, component); + log.info("Number of Node Candidates: {}", nodeCandidates.size()); + if (nodeCandidates.isEmpty()){ + log.error("SAL returned empty list, it is not possible to create cost performance indicator"); + continue; + } + List convertedNodeCandidates = NodeCandidateConverter.convertToDtoList(nodeCandidates); + List componentVariables = application.getVariables().get(component); + SimpleCostRegression regression = new SimpleCostRegression(component, convertedNodeCandidates, componentVariables); + application.getCostPerformanceIndicators().put(component, regression); + + }; + + log.info("Creating regression for cost performance indicators has been successfully finished"); + + performanceIndicatorSendingService.sendPerformanceIndicators(application); + log.info("Performance indicators sent"); + + return application; + } + } diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/UtilityEvaluatorListener.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/UtilityEvaluatorListener.java deleted file mode 100644 index 992d886..0000000 --- a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/UtilityEvaluatorListener.java +++ /dev/null @@ -1,41 +0,0 @@ -package eu.nebulous.utilityevaluator; - -import java.util.Optional; - -import org.json.JSONObject; -import org.springframework.stereotype.Component; - -import eu.nebulous.utilityevaluator.communication.activemq.message.FetchNodeCandidatesMessage; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - - -@Slf4j -@Component -@RequiredArgsConstructor -public class UtilityEvaluatorListener { -// This is an old component that is going to be removed after tests - - private final UtilityEvaluatorController controller; - - //@JmsListener(destination = "eu.nebulouscloud.dsl.general") - //@JmsListener(destination = "TestTopic") - public void handleGeneralApplicationMessage(JSONObject message) { - // Process the received message - log.info("Received message: {}", message.toString()); - - FetchNodeCandidatesMessage clearedMessage = new FetchNodeCandidatesMessage(message); - log.info("Cleared message: {}", clearedMessage.toString()); - Optional nodeCandidatesTensor = controller.createNodeCandidatesTensor(clearedMessage); - if (nodeCandidatesTensor.isPresent()){ - log.info("Tensor successfully created"); - // If needed, you can also send a response back to another queue or topic - //jmsTemplate.convertAndSend("eu.nebulouslcloud.optimizer.solver.tensor", new NodeCandidatesTensorMessage(clearedMessage.getApplicationID(), nodeCandidatesTensor.get())); - log.info("Tensor was passed via ActiveMQ"); - } - else { - log.error("There was an error during creating the tensor"); - } - } - -} diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/DslGenericMessageHandler.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/DslGenericMessageHandler.java new file mode 100644 index 0000000..45a642c --- /dev/null +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/DslGenericMessageHandler.java @@ -0,0 +1,55 @@ +package eu.nebulous.utilityevaluator.communication.exnconnector; + +import eu.nebulous.utilityevaluator.UtilityEvaluatorController; +import eu.nebulous.utilityevaluator.model.Application; +import eu.nebulouscloud.exn.core.Context; +import eu.nebulouscloud.exn.core.Handler; +import eu.nebulouscloud.exn.core.Publisher; +import eu.nebulouscloud.exn.core.SyncedPublisher; + +import java.util.Map; + +import org.apache.qpid.protonj2.client.Message; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.extern.slf4j.Slf4j; + + +@Slf4j +//@Component +public class DslGenericMessageHandler extends Handler { +// This class handles the dsl.generic message that contains all information needed for application deployment +// The example of this message is provided: TODO + + + private UtilityEvaluatorController controller; + private static final ObjectMapper mapper = new ObjectMapper(); + + + public DslGenericMessageHandler(SyncedPublisher nodeCandidatesGetter, Publisher performanceIndicatorPublisher){ + super(); + this.controller = new UtilityEvaluatorController(nodeCandidatesGetter, performanceIndicatorPublisher); + } + + @Override + public void onMessage(String key, String address, Map body, Message message, Context context) { + log.info("Received by custom handler {} => {} = {}", key,address,String.valueOf(body)); + log.info("Body={}", body.toString()); + + //ObjectMapper objectMapper = new ObjectMapper(); + //GenericDSLMessage genericDSLMessage = objectMapper.readValue(body.toString(), GenericDSLMessage.class); + //Application appFromMessage = new Application(genericDSLMessage); + + JsonNode appMessage = mapper.valueToTree(body); + Application app = new Application(appMessage); + log.info("Application {}, with name {}, has variables: {}", app.getApplicationId(), app.getApplicationName(), app.getVariables().toString()); + + + app = controller.createInitialCostPerformanceIndicators(app); + //todo: send back this cost performance indicators; + } + + + +} diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/ExnConnector.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/ExnConnector.java index ff6c316..59373ba 100644 --- a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/ExnConnector.java +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/ExnConnector.java @@ -9,8 +9,11 @@ import eu.nebulouscloud.exn.Connector; import eu.nebulouscloud.exn.core.Consumer; import eu.nebulouscloud.exn.handlers.ConnectorHandler; import eu.nebulouscloud.exn.settings.StaticExnConfig; - +import lombok.Getter; import eu.nebulouscloud.exn.core.Context; +import eu.nebulouscloud.exn.core.Publisher; +import eu.nebulouscloud.exn.core.SyncedPublisher; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,12 +31,22 @@ public class ExnConnector { String BROKER_PASSWORD; - public static final String GENERAL_APP_CREATION_MESSAGE_TOPIC = "eu.nebulouscloud.ui.dsl.generic.>"; - public final GeneralMessageHandler generalHandler; + private static final String GENERAL_APP_CREATION_MESSAGE_TOPIC = "eu.nebulouscloud.ui.dsl.generic.>"; + private final DslGenericMessageHandler genericDSLHandler; + private static final String PERFOMANCE_INDICATORS_TOPIC = "eu.nebulouscloud.optimiser.controller.ampl.performanceindicators"; + @Getter + private final Publisher performanceIndicatorPublisher; + private static final String GET_NODE_CANDIDATES_TOPIC= "eu.nebulouscloud.exn.sal.nodecandidate.get"; + @Getter + private final SyncedPublisher nodeCandidatesGetter; + + - public ExnConnector(GeneralMessageHandler handler) { + public ExnConnector() { super(); - this.generalHandler = handler; + this.performanceIndicatorPublisher = new Publisher("costPerformanceIndicators", PERFOMANCE_INDICATORS_TOPIC, true, true); + this.nodeCandidatesGetter = new SyncedPublisher("getNodeCandidates", GET_NODE_CANDIDATES_TOPIC, true, true); + this.genericDSLHandler = new DslGenericMessageHandler(nodeCandidatesGetter, performanceIndicatorPublisher); init(); @@ -43,8 +56,8 @@ public class ExnConnector { Connector c = new Connector( "utilityevaluator", new MyConnectorHandler(), - List.of(), - List.of(new Consumer("ui_all", GENERAL_APP_CREATION_MESSAGE_TOPIC, generalHandler ,true,true)), + List.of(performanceIndicatorPublisher, nodeCandidatesGetter), + List.of(new Consumer("ui_generic_message", GENERAL_APP_CREATION_MESSAGE_TOPIC, genericDSLHandler ,true,true)), false, false, new StaticExnConfig( diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/ExnListener.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/ExnListener.java deleted file mode 100644 index cb78348..0000000 --- a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/ExnListener.java +++ /dev/null @@ -1,71 +0,0 @@ -package eu.nebulous.utilityevaluator.communication.exnconnector; - -import eu.nebulouscloud.exn.Connector; -import eu.nebulouscloud.exn.core.Consumer; -import eu.nebulouscloud.exn.core.Context; -import eu.nebulouscloud.exn.core.Handler; -import eu.nebulouscloud.exn.handlers.ConnectorHandler; -import eu.nebulouscloud.exn.settings.StaticExnConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; -import org.apache.qpid.protonj2.client.Message; - -import java.util.List; -import java.util.Map; - -@Component -public class ExnListener extends Handler { - - private static final Logger log = LoggerFactory.getLogger(ExnListener.class); - private String topicName; - private String address; - private Connector connector; - - public ExnListener(String topicName, String address, String brokerHost, int brokerPort, String username, String password) { - this.topicName = topicName; - this.address = address; - - ConnectorHandler connectorHandler = new ConnectorHandler() { - @Override - public void onReady(Context context) { - log.info("Connector ready. Registering consumer for topic: {}", topicName); - // Register this handler as a consumer for the specified topic and address - context.registerConsumer(new Consumer(topicName, address, ExnListener.this, true)); - } - }; - - // Initialize the connector with the connector handler and configuration - this.connector = new Connector("ui", connectorHandler, List.of(), List.of(), false, false, - new StaticExnConfig(brokerHost, brokerPort, username, password)); - } - - @Override - public void onMessage(String key, String address, Map body, Message message, Context context) { - log.info("Received message on topic {}: key={}, address={}, body={}", topicName, key, address, body); - // Implement custom message processing logic here - processMessage(key, address, body, message, context); - } - - public void start() { - try { - connector.start(); - log.info("ExnListener started for topic: {}", topicName); - } catch (Exception e) { - log.error("Error starting ExnListener: {}", e.getMessage()); - } - } - - public void stop() { - try { - connector.stop(); - log.info("ExnListener stopped for topic: {}", topicName); - } catch (Exception e) { - log.error("Error stopping ExnListener: {}", e.getMessage()); - } - } - - protected void processMessage(String key, String address, Map body, Message message, Context context) { - // This method can be overridden in subclasses for custom message processing - } -} \ No newline at end of file diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/GeneralMessageHandler.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/GeneralMessageHandler.java deleted file mode 100644 index 497a0cc..0000000 --- a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/GeneralMessageHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -package eu.nebulous.utilityevaluator.communication.exnconnector; - -import eu.nebulous.utilityevaluator.UtilityEvaluatorController; -import eu.nebulouscloud.exn.core.Context; -import eu.nebulouscloud.exn.core.Handler; - -import java.util.Map; - -import org.apache.qpid.protonj2.client.Message; -import org.apache.qpid.protonj2.client.exceptions.ClientException; -import org.json.JSONObject; -import org.springframework.stereotype.Component; - -import lombok.extern.slf4j.Slf4j; - - -@Slf4j -@Component -public class GeneralMessageHandler extends Handler { - - public final UtilityEvaluatorController controller; - - public GeneralMessageHandler(UtilityEvaluatorController controller){ - super(); - this.controller = controller; - } - - @Override - public void onMessage(String key, String address, Map body, Message message, Context context) { - log.info("Received by custom handler {} => {} = {}", key,address,String.valueOf(body)); - log.info("Received message: {}", message.toString()); - log.info("Body={}", body.toString()); - JSONObject jsonObject = new JSONObject(body); - Map map = jsonObject.toMap(); - try { - String applicationId = message.subject(); - //todo: transform the old code to get it in the right format - } catch (ClientException e) { - - e.printStackTrace(); - log.error(e.getMessage()); - } - - - /*FetchNodeCandidatesMessage clearedMessage = new FetchNodeCandidatesMessage(message); - log.info("Cleared message: {}", clearedMessage.toString()); - Optional nodeCandidatesTensor = controller.createNodeCandidatesTensor(clearedMessage); - if (nodeCandidatesTensor.isPresent()){ - log.info("Tensor successfully created"); - // If needed, you can also send a response back to another queue or topic - //jmsTemplate.convertAndSend("eu.nebulouslcloud.optimizer.solver.tensor", new NodeCandidatesTensorMessage(clearedMessage.getApplicationID(), nodeCandidatesTensor.get())); - log.info("Tensor was passed via ActiveMQ"); - } - else { - log.error("There was an error during creating the tensor"); - - }*/ - - } - -} diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/NodeCandidatesMessageHandler.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/NodeCandidatesMessageHandler.java new file mode 100644 index 0000000..a68d6dd --- /dev/null +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/NodeCandidatesMessageHandler.java @@ -0,0 +1,5 @@ +package eu.nebulous.utilityevaluator.communication.exnconnector; + +public class NodeCandidatesMessageHandler { + +} diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/PerformanceIndicatorSendingService.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/PerformanceIndicatorSendingService.java new file mode 100644 index 0000000..c129003 --- /dev/null +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/exnconnector/PerformanceIndicatorSendingService.java @@ -0,0 +1,120 @@ +package eu.nebulous.utilityevaluator.communication.exnconnector; + +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import eu.nebulous.utilityevaluator.model.Application; +import eu.nebulous.utilityevaluator.model.VariableDTO; +import eu.nebulous.utilityevaluator.regression.SimpleCostRegression; +import eu.nebulouscloud.exn.core.Publisher; +import io.micrometer.common.lang.NonNull; +import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@AllArgsConstructor +public class PerformanceIndicatorSendingService { + + @NonNull + private Publisher performanceIndicatorPublisher; + private static final ObjectMapper jsonMapper = new ObjectMapper(); + + /* Example of the message: + { + "performanceIndicators": + { + "name": "cost_pi_0", + "variables": "spec_components_0_traits_0_properties_cpu", "spec_components_0_traits_0_properties_ram", + "coefficientsName": "COEFFICIENTS_0", + "initialCoefficients": "COEFFICIENTS_0 := 1 0.1 2 0.3;" + }, + { + "name": "cost_pi_1", + "variables": "spec_components_1_traits_0_properties_cpu", + "coefficientsName": "COEFFICIENTS_1", + "initialCoefficients": "COEFFICIENTS_1 := 1 0.1 2 0.3;" + } + + } + */ + + public void sendPerformanceIndicators(Application app){ + ObjectNode msg = jsonMapper.createObjectNode(); + ObjectNode performanceIndicators = msg.withObject("PerformanceIndicators"); + + for (String component: app.getCostPerformanceIndicators().keySet()){ + String piName = "cost_pi"+component; + performanceIndicators.put("name", piName); + //array of variables + List variableNames = app.getVariables().get(component).stream().map(var -> var.getName()).collect(Collectors.toList()); + performanceIndicators.put("variables", variableNames.toString()); + //coefficientsName + String coefficientsName = "COEFFICIENTS_"+component; + performanceIndicators.put("coefficientsName", coefficientsName); + //initial coefficients + String initialCoefficients = mapInitialCoefficientsToString(coefficientsName, app.getCostPerformanceIndicators().get(component).getCoefficients()); + performanceIndicators.put("initialCoefficients", initialCoefficients); + log.info("Prepared performance indicator {}", performanceIndicators); + } + log.info("Message to be sent: {}", msg); + performanceIndicatorPublisher.send(jsonMapper.convertValue(msg, Map.class), app.getApplicationId(), true); + + } + private String mapInitialCoefficientsToString(String coefficientsName, double[] values) { + StringJoiner joiner = new StringJoiner(" "); + joiner.add(coefficientsName + " :="); + for (double num : values) { + joiner.add(Double.toString(num)); + } + return joiner.toString(); + } +/* + private String mapInitialCoefficientsToString(Map map) { + StringJoiner joiner = new StringJoiner(" "); + map.forEach((componentName, value) -> { + joiner.add("COEFFICIENTS_"+componentName + " :="); + for (double num : value) { + joiner.add(Double.toString(num)); + } + }); + return joiner.toString(); + }*/ + /*public void sendAMPL() { + String ampl = AMPLGenerator.generateAMPL(this); + ObjectNode msg = jsonMapper.createObjectNode(); + msg.put("ObjectiveFunction", getObjectiveFunction()); + ObjectNode constants = msg.withObject("Constants"); + // Define initial values for constant utility functions: + // "Constants" : { + // : { + // "Variable" : + // "Value" : + // } + // } + for (final JsonNode function : originalAppMessage.withArray(utility_function_path)) { + if (!(function.get("type").asText().equals("constant"))) + continue; + // NOTE: for a constant function, we rely on the fact that the + // function body is a single variable defined in the "Variables" + // section and pointing to KubeVela, and the + // `functionExpressionVariables` array contains one entry. + JsonNode variable = function.withArray("/expression/variables").get(0); + String variableName = variable.get("value").asText(); + JsonPointer path = kubevelaVariablePaths.get(variableName); + JsonNode value = originalKubevela.at(path); + ObjectNode constant = constants.withObject(function.get("name").asText()); + constant.put("Variable", variableName); + constant.set("Value", value); + } + log.info("Sending AMPL file to solver", keyValue("amplMessage", msg), keyValue("appId", UUID)); + Main.logFile("to-solver-" + getUUID() + ".json", msg.toString()); + Main.logFile("to-solver-" + getUUID() + ".ampl", ampl); + }*/ +} diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/sal/NodeCandidatesFetchingService.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/sal/NodeCandidatesFetchingService.java index ee2582e..e8311ae 100644 --- a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/sal/NodeCandidatesFetchingService.java +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/sal/NodeCandidatesFetchingService.java @@ -1,5 +1,7 @@ package eu.nebulous.utilityevaluator.communication.sal; +import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -8,38 +10,95 @@ import org.ow2.proactive.sal.model.NodeCandidate; import org.ow2.proactive.sal.model.Requirement; import org.springframework.stereotype.Component; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.nebulous.utilityevaluator.communication.exnconnector.ExnConnector; import eu.nebulous.utilityevaluator.communication.sal.error.ProactiveClientException; +import eu.nebulous.utilityevaluator.external.KubevelaAnalyzer; +import eu.nebulous.utilityevaluator.model.Application; +import eu.nebulouscloud.exn.core.SyncedPublisher; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; + @Slf4j @RequiredArgsConstructor -@Component +//@Component public class NodeCandidatesFetchingService { private static final int NUMBER_OF_REPEATS_FOR_NODE_CANDIDATES = 120; private static final int DELAY_BETWEEN_REQUESTS = 5000; - private final ProactiveConnector proactiveClientConnectorService; + @NonNull + private SyncedPublisher nodeCandidatesConnector; + private static final ObjectMapper mapper = new ObjectMapper(); - + //private final ProactiveConnector proactiveClientConnectorService; /*public NodeCandidatesFetchingService(ProactiveClientProperties properties){ ProactiveConnector connector = new ProactiveConnector(properties); this.proactiveClientConnectorService = connector; }*/ - - //https://gitlab.ow2.org/melodic/melodic-upperware/-/tree/morphemic-rc4.0/cp_generator/src/main/java/eu/paasage/upperware/profiler/generator/communication/impl + //https://gitlab.ow2.org/melodic/melodic-upperware/-/tree/morphemic-rc4.0/cp_generator/src/main/java/eu/paasage/upperware/profiler/generator/communication/impl - public List getNodeCandidates(Map cloudProviders){ + public List getNodeCandidatesViaMiddleware(Application app, String componentId){ + /*generate requirements (based on kubevela), and providers, + * call SAL via EXN Middleware + * get node candidates + * */ + Map> requirements = KubevelaAnalyzer.getRequirements(app.getKubevela()); + Map message = new HashMap(); + try { + message = Map.of("metaData", Map.of("user", "admin"), "body", mapper.writeValueAsString(requirements)); + log.info("Sending message to SAL: {}", message); + } catch (JsonProcessingException e) { + log.error("There was an error during converting message {}", e); + e.printStackTrace(); + } + + //Map message = Map.of("metaData", Map.of("user", "admin"), "body", "[]"); + Map response = nodeCandidatesConnector.sendSync(message, app.getApplicationId(), null, false); + log.info("Received a response"); + JsonNode payload = extractPayloadFromExnResponse(response, app.getApplicationId(), "getNodeCandidates"); + log.info("Correctly return SAL response for component {}", componentId); + return Arrays.asList(mapper.convertValue(payload, NodeCandidate[].class)); + } + + //copied from Optimizer Controller: https://opendev.org/nebulous/optimiser-controller/src/branch/master/optimiser-controller/src/main/java/eu/nebulouscloud/optimiser/controller/NebulousApp.java + private static JsonNode extractPayloadFromExnResponse(Map responseMessage, String appID, String caller) { + JsonNode response = mapper.valueToTree(responseMessage); + String salRawResponse = response.at("/body").asText(); // it's already a string, asText() is for the type system + JsonNode metadata = response.at("/metaData"); + JsonNode salResponse = mapper.missingNode(); // the data coming from SAL + try { + salResponse = mapper.readTree(salRawResponse); + } + catch (JsonProcessingException e) { + log.error("Could not read message body as JSON: body = '{}', for app: {}", salRawResponse, appID, e); + return mapper.missingNode(); + } + if (!metadata.at("/status").asText().startsWith("2")) { + // we only accept 200, 202, numbers of that nature + log.error("exn-middleware-sal request failed with error code '{}' and message '{}'", metadata.at("/status"), salResponse.at("/message").asText()); + return mapper.missingNode(); + } + return salResponse; + } + //old method used for SAL + private List getNodeCandidates(Map cloudProviders){ List providerRequirements = convertProviderRequirements(cloudProviders); return findNodeCandidates(providerRequirements); } - + + //to be deleted private List convertProviderRequirements(Map cloudProviders){ //todo: filter based on the chosen cloud providers return List.of(); } + //old method used to connect to SAL directly private List findNodeCandidates(List requirements) { List nodeCandidates = new LinkedList<>(); boolean isAnyAsyncNodeCandidatesProcessesInProgress = true; @@ -47,14 +106,14 @@ public class NodeCandidatesFetchingService { try { while (isAnyAsyncNodeCandidatesProcessesInProgress && (requestNo < NUMBER_OF_REPEATS_FOR_NODE_CANDIDATES)) { log.info("Checking if nodeCandidates downlaod process is finished. Trye: {}", requestNo); - isAnyAsyncNodeCandidatesProcessesInProgress = proactiveClientConnectorService.isAnyAsyncNodeCandidatesProcessesInProgress(); + //isAnyAsyncNodeCandidatesProcessesInProgress = proactiveClientConnectorService.isAnyAsyncNodeCandidatesProcessesInProgress(); Thread.sleep(DELAY_BETWEEN_REQUESTS); requestNo++; } if (isAnyAsyncNodeCandidatesProcessesInProgress) { throw new RuntimeException("NodeCandidates are not yet present inside proactive scheduler"); } - nodeCandidates = proactiveClientConnectorService.fetchNodeCandidates(requirements); + //nodeCandidates = proactiveClientConnectorService.fetchNodeCandidates(requirements); } catch (InterruptedException e1) { e1.printStackTrace(); } catch (ProactiveClientException e2) { diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/sal/ProactiveConnector.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/sal/ProactiveConnector.java index 40bb513..cdc9847 100644 --- a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/sal/ProactiveConnector.java +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/sal/ProactiveConnector.java @@ -30,7 +30,7 @@ import java.util.*; @Slf4j -@Service +//@Service public class ProactiveConnector { private static final String SESSION_HEADER = "sessionid"; diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/nodecandidates/NodeCandidateConverter.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/converter/NodeCandidateConverter.java similarity index 56% rename from utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/nodecandidates/NodeCandidateConverter.java rename to utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/converter/NodeCandidateConverter.java index bb4a964..b1214cc 100644 --- a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/nodecandidates/NodeCandidateConverter.java +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/converter/NodeCandidateConverter.java @@ -1,11 +1,15 @@ -package eu.nebulous.utilityevaluator.nodecandidates; +package eu.nebulous.utilityevaluator.converter; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.ow2.proactive.sal.model.NodeCandidate; - +import eu.nebulous.utilityevaluator.model.NodeCandidateDTO; +import eu.nebulous.utilityevaluator.model.VariableDTO; +import lombok.extern.slf4j.Slf4j; //static class that converts Node Candidates from SAL to NodeCandidatesDTO, which are then directly saved in node Candidates tensor +@Slf4j public class NodeCandidateConverter { public static final String CSV_HEADER = "id;gpu;cpu;ram;location;latitude;longitude;provider;type;price\n"; @@ -61,4 +65,44 @@ public class NodeCandidateConverter { nodeCandidate.getPrice()); } + //TODO: this method should also encode other fields of NodeCandidates: GPU, providertype, location but we need to have them as variables. + //only for variables that are used + public static double[][] convertListToDoubleArray(List nodeList, List variables) { + int size = nodeList.size(); + double[][] dataArray = new double[size][]; + + for (int i = 0; i < size; i++) { + NodeCandidateDTO node = nodeList.get(i); + List usedNodeParameters = new ArrayList<>(); + for (VariableDTO variable : variables){ + switch (variable.getType()){ + case CPU: + usedNodeParameters.add(node.getCpu()); + break; + case RAM: + usedNodeParameters.add(Long.valueOf(node.getRam()).intValue()); + break; + default: + log.info("Variable type {} is not usable in cost performance indicators", variable.getType()); + break; + + + } + } + double[] data = new double[usedNodeParameters.size()]; + for (int j = 0; j < usedNodeParameters.size(); j++){ + data[j]=usedNodeParameters.get(j); + } + /*{ + node.getCpu(), + node.getGpu(), + node.getRam(), + node.getLatitude(), + node.getLongitude() + };*/ + dataArray[i] = data; + } + return dataArray; + } + } diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/converter/VariableConverter.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/converter/VariableConverter.java new file mode 100644 index 0000000..55ac451 --- /dev/null +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/converter/VariableConverter.java @@ -0,0 +1,98 @@ +package eu.nebulous.utilityevaluator.converter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +import com.fasterxml.jackson.databind.JsonNode; + +import eu.nebulous.utilityevaluator.model.VariableDTO; +import eu.nebulous.utilityevaluator.model.VariableType; +import eu.nebulous.utilityevaluator.model.message.Variable; +import lombok.extern.slf4j.Slf4j; + +/* + * This class is responsible for converting variables from generic.dsl message to a map < String, List>, + * where the key is the component name and the value is the list of variables related to this component + */ +@Slf4j + public class VariableConverter{ + + + public static Map> convertAndGroupVariables(JsonNode variables){ + + Map> mapOfVariables = new HashMap<>(); + List componentNames = new ArrayList<>(); + + if (!variables.isArray()) { + log.warn("No variables have been defined, is not possible to perform any optimization"); + return mapOfVariables; + } + + for (JsonNode var : variables) { + //var.get("key").asText(); //to get the name of the variable + //var.get("meaning").asText(); //to get the type + //to get the path + String component = getPrefix(var.get("path").asText()); + log.info("Component: {}", component); + String meaning = getLastPart(var.get("meaning").asText()); + log.info("meaning: {}", meaning); + VariableDTO variableDTO = new VariableDTO(var.get("key").asText(), component, meaning); + + //if it is a first variable related to this component + if (!mapOfVariables.containsKey(component)) { + componentNames.add(component); + ArrayList variablesList = new ArrayList<>(); + variablesList.add(variableDTO); + mapOfVariables.put(component, variablesList); + log.info("Adding new variable: {} for component: {}", variableDTO.getName(), variableDTO.getComponentName()); + + } + else { + mapOfVariables.get(component).add(variableDTO); + } + + } + return mapOfVariables; + } + + private static String getPrefix(String str) { + int endIndex = str.indexOf('/', "/spec/components/".length()); + if (endIndex == -1) { + return str; + } + return str.substring(0, endIndex); + } + + private static String getLastPart(String input) { + String[] parts = input.split("\\."); + return parts[parts.length - 1]; + } + + + + + //old message, to be deleted + public static Map> convertAndGroupVariables(List variables){ + + Map> mapOfVariables = new HashMap<>(); + List componentNames = new ArrayList<>(); + for (Variable v: variables){ + String prefix = getPrefix(v.getPath()); + VariableDTO variableDTO = new VariableDTO(v.getKey(), prefix, v.getMeaning()); + if (!mapOfVariables.containsKey(prefix)) { //if it is a first variable related to this component + componentNames.add(prefix); + ArrayList variablesList = new ArrayList<>(); + variablesList.add(variableDTO); + mapOfVariables.put(prefix, variablesList); + } + mapOfVariables.get(prefix).add(variableDTO); + + } + return mapOfVariables; + + + } +} diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/external/KubevelaAnalyzer.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/external/KubevelaAnalyzer.java new file mode 100644 index 0000000..9bb759e --- /dev/null +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/external/KubevelaAnalyzer.java @@ -0,0 +1,208 @@ +package eu.nebulous.utilityevaluator.external; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.ow2.proactive.sal.model.AttributeRequirement; +import org.ow2.proactive.sal.model.OperatingSystemFamily; +import org.ow2.proactive.sal.model.Requirement; +import org.ow2.proactive.sal.model.RequirementOperator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A collection of methods to extract node requirements from KubeVela files. + */ +public class KubevelaAnalyzer { + + private static Logger log = LoggerFactory.getLogger(KubevelaAnalyzer.class); + + private static final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); + + /** + * Given a KubeVela file, extract how many nodes to deploy for each + * component. Note that this can be zero when the component should not be + * deployed at all. This can happen for example when there is a cloud and + * an edge version of the component and only one of them should run.

+ * + * We currently look for the following component trait: + * + *

{@code
+     * traits:
+     *  - type: scaler
+     *    properties:
+     *      replicas: 2
+     * }
+ * + * If this trait is not found for a component, its count will be 1. + * + * @param kubevela the parsed KubeVela file. + * @return A map from component name to number of instances to generate. + */ + public static Map getNodeCount(JsonNode kubevela) { + Map result = new HashMap<>(); + ArrayNode components = kubevela.withArray("/spec/components"); + for (final JsonNode c : components) { + result.put(c.get("name").asText(), 1); // default value; might get overwritten + for (final JsonNode t : c.withArray("/traits")) { + if (t.at("/type").asText().equals("scaler") + && t.at("/properties/replicas").canConvertToExactIntegral()) + { + result.put(c.get("name").asText(), + t.at("/properties/replicas").asInt()); + } + } + } + return result; + } + + /** + * Extract node count from a KubeVela file. + * + * @see #getNodeCount(JsonNode) + * @param kubevela The KubeVela file, as a YAML string. + * @return A map from component name to number of instances to generate. + * @throws JsonProcessingException if the argument does not contain valid YAML. + */ + public static Map getNodeCount(String kubevela) throws JsonProcessingException { + return getNodeCount(parseKubevela(kubevela)); + } + + /** + * Extract node requirements from a KubeVela file in a form we can send to + * the SAL `findNodeCandidates` endpoint.

+ * + * We read the following attributes for each component: + * + * - `properties.cpu`, `properties.requests.cpu`: round up to next integer + * and generate requirement `hardware.cores` + * + * - `properties.memory`, `properties.requests.memory`: Handle "200Mi", + * "0.2Gi" and bare number, convert to MB and generate requirement + * `hardware.memory` + * + * Notes:

+ * + * - We add the requirement that OS family == Ubuntu.

+ * + * - For the first version, we specify all requirements as "greater or + * equal", i.e., we might not find precisely the node candidates that + * are asked for.

+ * + * - Related, KubeVela specifies "cpu" as a fractional value, while SAL + * wants the number of cores as a whole number. We round up to the + * nearest integer and ask for "this or more" cores, since we might end + * up with needing, e.g., 3 cores, which is not a configuration commonly + * provided by cloud providers.

+ * + * @param kubevela the parsed KubeVela file. + * @return a map of component name to (potentially empty, except for OS + * family) list of requirements for that component. No requirements mean + * any node will suffice. + */ + public static Map> getRequirements(JsonNode kubevela) { + Map> result = new HashMap<>(); + ArrayNode components = kubevela.withArray("/spec/components"); + for (final JsonNode c : components) { + String componentName = c.get("name").asText(); + ArrayList reqs = new ArrayList<>(); + reqs.add(new AttributeRequirement("image", "operatingSystem.family", + RequirementOperator.IN, OperatingSystemFamily.UBUNTU.toString())); + JsonNode cpu = c.at("/properties/cpu"); + if (cpu.isMissingNode()) cpu = c.at("/properties/resources/requests/cpu"); + if (!cpu.isMissingNode()) { + // KubeVela has fractional core /cpu requirements, and the + // value might be given as a string instead of a number, so + // parse string in all cases. + double kubevela_cpu = -1; + try { + kubevela_cpu = Double.parseDouble(cpu.asText()); + } catch (NumberFormatException e) { + log.warn("CPU spec in {} is not a number, value seen is {}", + componentName, cpu.asText()); + } + long sal_cores = Math.round(Math.ceil(kubevela_cpu)); + if (sal_cores > 0) { + reqs.add(new AttributeRequirement("hardware", "cores", + RequirementOperator.GEQ, Long.toString(sal_cores))); + } else { + // floatValue returns 0.0 if node is not numeric + log.warn("CPU of component {} is 0 or not a number, value seen is {}", + componentName, cpu.asText()); + } + } + JsonNode memory = c.at("/properties/memory"); + if (memory.isMissingNode()) cpu = c.at("/properties/resources/requests/memory"); + if (!memory.isMissingNode()) {; + String sal_memory = memory.asText(); + if (sal_memory.endsWith("Mi")) { + sal_memory = sal_memory.substring(0, sal_memory.length() - 2); + } else if (sal_memory.endsWith("Gi")) { + sal_memory = String.valueOf(Integer.parseInt(sal_memory.substring(0, sal_memory.length() - 2)) * 1024); + } else if (!memory.isNumber()) { + log.warn("Unsupported memory specification in component {} :{} (wanted 'Mi' or 'Gi') ", + componentName, + memory.asText()); + sal_memory = null; + } + // Fall-through: we rewrote the KubeVela file and didn't add + // the "Mi" suffix, but it's a number + if (sal_memory != null) { + reqs.add(new AttributeRequirement("hardware", "memory", + RequirementOperator.GEQ, sal_memory)); + } + } + for (final JsonNode t : c.withArray("/traits")) { + // TODO: Check for node affinity / geoLocation / country / + // node type (edge or cloud) + } + // Finally, add requirements for this job to the map + result.put(componentName, reqs); + } + return result; + } + + /** + * Extract node requirements from a KubeVela file. + * + * @see #getRequirements(JsonNode) + * @param kubevela The KubeVela file, as a YAML string. + * @return a map of component name to (potentially empty, except for OS + * family) list of requirements for that component. No requirements mean + * any node will suffice. + * @throws JsonProcessingException if kubevela does not contain valid YAML. + */ + public static Map> getRequirements(String kubevela) throws JsonProcessingException { + return getRequirements(parseKubevela(kubevela)); + } + + /** + * Convert YAML KubeVela into a parsed representation. + * + * @param kubevela The KubeVela YAML. + * @return A parsed representation of the KubeVela file, or null for a parse error. + * @throws JsonProcessingException if kubevela does not contain valid YAML. + */ + public static JsonNode parseKubevela(String kubevela) throws JsonProcessingException { + return yamlMapper.readTree(kubevela); + } + + /** + * Convert the parsed representation of a KubeVela file to yaml. + * + * @param kubevela The KubeVela parsed file. + * @return A YAML representation of the KubeVela file. + * @throws JsonProcessingException if YAML cannot be generated from kubevela. + */ + public static String generateKubevela(JsonNode kubevela) throws JsonProcessingException { + return yamlMapper.writeValueAsString(kubevela); + } +} diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/Application.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/Application.java new file mode 100644 index 0000000..858e7da --- /dev/null +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/Application.java @@ -0,0 +1,70 @@ +package eu.nebulous.utilityevaluator.model; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonPointer; +import com.fasterxml.jackson.databind.JsonNode; + +import eu.nebulous.utilityevaluator.converter.VariableConverter; +import eu.nebulous.utilityevaluator.model.message.GenericDSLMessage; +import eu.nebulous.utilityevaluator.regression.SimpleCostRegression; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +@RequiredArgsConstructor +@ToString +public class Application { + + @NonNull + private String applicationId; + private String applicationName; + @NonNull + private JsonNode kubevela; + private List chosenProviders; + @NonNull + private Map> variables; + @Setter + private Map costPerformanceIndicators; + +/** Location of the kubevela yaml file in the app creation message (String) */ +private static final JsonPointer KUBEVELA_PATH = JsonPointer.compile("/content"); +/** Location of the variables (optimizable locations) of the kubevela file + * in the app creation message. (Array of objects) */ +private static final JsonPointer VARIABLES_PATH = JsonPointer.compile("/variables"); +/** Locations of the UUID and name in the app creation message (String) */ +private static final JsonPointer UUID_PATH = JsonPointer.compile("/uuid"); +private static final JsonPointer NAME_PATH = JsonPointer.compile("/title"); +/** Location of the variables (optimizable locations) of the kubevela file + * in the app creation message. (Array of objects) */ +private static final JsonPointer PROVIDERS_PATH = JsonPointer.compile("/resources"); + + public Application (JsonNode appMessage) { + try { + this.kubevela = appMessage.at(KUBEVELA_PATH); + this.applicationId = appMessage.at(UUID_PATH).textValue(); + this.applicationName = appMessage.at(NAME_PATH).textValue(); + JsonNode variables = appMessage.at(VARIABLES_PATH); + this.variables = VariableConverter.convertAndGroupVariables(variables); + log.info("Application message successfully parsed"); + + } catch (Exception e) { + log.error("Could not read app creation message", e); + } + } + + /*public Application(GenericDSLMessage message){ + this.applicationId = message.getUuid(); + this.kubevela = message.getContent(); + this.applicationName = message.getTitle(); + + message.getVariables(); + message.getResources(); + }*/ +} diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/nodecandidates/NodeCandidateDTO.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/NodeCandidateDTO.java similarity index 92% rename from utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/nodecandidates/NodeCandidateDTO.java rename to utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/NodeCandidateDTO.java index ea8dd77..6444195 100644 --- a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/nodecandidates/NodeCandidateDTO.java +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/NodeCandidateDTO.java @@ -1,4 +1,4 @@ -package eu.nebulous.utilityevaluator.nodecandidates; +package eu.nebulous.utilityevaluator.model; import org.ow2.proactive.sal.model.NodeCandidate; diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/VariableDTO.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/VariableDTO.java new file mode 100644 index 0000000..585ec42 --- /dev/null +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/VariableDTO.java @@ -0,0 +1,17 @@ +package eu.nebulous.utilityevaluator.model; + +import lombok.Getter; + +@Getter +public class VariableDTO { + private String name; + private String componentName; + private VariableType type; + + public VariableDTO(String name, String componentName, String meaning){ + this.name = name; + this.componentName = componentName; + this.type = VariableType.fromValue(meaning); + } + +} diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/VariableType.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/VariableType.java new file mode 100644 index 0000000..78491bb --- /dev/null +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/VariableType.java @@ -0,0 +1,26 @@ +package eu.nebulous.utilityevaluator.model; + + +public enum VariableType { + CPU ("cpu"), + RAM ("memory"), + LOCATION ("location"), + STORAGE("storage"), + REPLICAS ("replicas"); + + private final String value; + + VariableType (String value){ + this.value = value; + } + + public static VariableType fromValue(String value) { + for (VariableType enumValue : VariableType.values()) { + if (enumValue.value.equals(value)) { + return enumValue; + } + } + throw new IllegalArgumentException("No enum constant with value: " + value); + } + +} diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/activemq/message/FetchNodeCandidatesMessage.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/message/FetchNodeCandidatesMessage.java similarity index 95% rename from utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/activemq/message/FetchNodeCandidatesMessage.java rename to utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/message/FetchNodeCandidatesMessage.java index 37508f0..12a74f8 100644 --- a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/communication/activemq/message/FetchNodeCandidatesMessage.java +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/message/FetchNodeCandidatesMessage.java @@ -1,4 +1,4 @@ -package eu.nebulous.utilityevaluator.communication.activemq.message; +package eu.nebulous.utilityevaluator.model.message; import java.io.Serializable; import java.util.HashMap; diff --git a/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/message/GenericDSLMessage.java b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/message/GenericDSLMessage.java new file mode 100644 index 0000000..c5d712d --- /dev/null +++ b/utility-evaluator/src/main/java/eu/nebulous/utilityevaluator/model/message/GenericDSLMessage.java @@ -0,0 +1,132 @@ +package eu.nebulous.utilityevaluator.model.message; + +import java.util.List; + +import lombok.Getter; + +@Getter +public class GenericDSLMessage { + + + private String title; + private String uuid; + private String content; + private List variables; + private List resources; + private List