Skip to content

Commit

Permalink
Emit AMPL code when called with json app creation message in a file
Browse files Browse the repository at this point in the history
Validate and use variable mappings, including data types and bounds.

Currently does not handle array (mapping) parameters in KubeVela.

Change-Id: I5864d3ac1e0e29bd1df393bc95e049a957a4e55a
  • Loading branch information
rudi committed Jan 4, 2024
1 parent b7e3123 commit 18ebf26
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 44 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
__pycache__/
.nox/
.DS_Store

# Gradle project-specific cache directory
.gradle
# Gradle build output directory
Expand All @@ -15,3 +17,7 @@ __pycache__/

# IntelliJ IDEA configuration files
/.idea/

# Visual Studio Code files
/.vscode/
/.dir-locals.el
3 changes: 3 additions & 0 deletions optimiser-controller/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ dependencies {
// YAML parsing: https://github.com/decorators-squad/eo-yaml/tree/master
implementation 'com.amihaiemil.web:eo-yaml:7.0.10'

// JSON parsing: https://github.com/stleary/JSON-java
implementation 'org.json:json:20231013'

// Command-line parsing: https://picocli.info
implementation 'info.picocli:picocli:4.7.5'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

import java.io.IOException;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONPointer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -12,19 +15,34 @@ public class AppParser {
private static final Logger log = LoggerFactory.getLogger(AppParser.class);

/**
* Parse a KubeVela file and mapping file.
* Location of the kubevela yaml file in the app creation message. Should
* point to a string.
*/
private static final JSONPointer kubevela_path
= JSONPointer.builder().append("kubevela").append("original").build();

/**
* Location of the modifiable locations of the kubevela file in the app
* creation message. Should point to an array of objects.
*/
private static final JSONPointer variables_path
= JSONPointer.builder().append("kubevela").append("variables").build();

/**
* Parse an incoming app creation message in json format.
*
* @param kubevela a deployable KubeVela file
* @param mappings parameter mappings for the KubeVela file
* @param json_message the app creation message
* @return a {@code NebulousApp} instance, or {@code null} if there was an
* error parsing the app creation message
* error parsing the app creation message
*/
public static NebulousApp parseAppCreationMessage(String kubevela, String mappings) {
public static NebulousApp parseAppCreationMessage(JSONObject json_message) {
String kubevela_string = (String)kubevela_path.queryFrom(json_message);
JSONArray parameters = (JSONArray)variables_path.queryFrom(json_message);
try {
return new NebulousApp(Yaml.createYamlInput(kubevela).readYamlMapping(),
Yaml.createYamlInput(mappings).readYamlMapping());
return new NebulousApp(Yaml.createYamlInput(kubevela_string).readYamlMapping(),
parameters);
} catch (IOException e) {
log.error("Could not read app creation data: ", e);
log.error("Could not read app creation message: ", e);
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ private class MyConsumerHandler extends Handler {
// `body` is of type `Map<String, Object>` by default, so can be
// handled by various JSON libraries directly.
@Override
public void onMessage(String key, String address, Map body, Message message,
AtomicReference<Context> context)
public void onMessage(String key, String address, Map body, Message message, Context context)
{
log.info("Message delivered for key {} => {} ({}) = {}",
key, address, body, message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import eu.nebulouscloud.exn.core.Context;
import eu.nebulouscloud.exn.handlers.ConnectorHandler;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
Expand All @@ -31,7 +32,7 @@ public class Main implements Callable<Integer> {
@Option(names = {"-s", "--sal-url"},
description = "The URL of the SAL server (including URL scheme http:// or https://). Can also be set via the @|bold SAL_URL|@ environment variable.",
paramLabel = "SAL_URL",
defaultValue = "${SAL_URL:-http://158.37.63.90:8880/}")
defaultValue = "${SAL_URL:-http://localhost:8880/}")
private java.net.URI sal_uri;

@Option(names = {"--sal-user"},
Expand Down Expand Up @@ -70,13 +71,9 @@ public class Main implements Callable<Integer> {
defaultValue = "${ACTIVEMQ_PASSWORD}")
private String activemq_password;

@Option(names = {"--kubevela-file"},
description = "The name of a deployable KubeVela yaml file (used for testing purposes)")
private Path kubevela_file;

@Option(names = {"--kubevela-parameters"},
description = "The name of a parameter file referencing the deployable model (used for testing purposes)")
private Path kubevela_parameters;
@Option(names = {"--app-creation-message-file", "-f"},
description = "The name of a file containing a JSON app creation message (used for testing purposes)")
private Path json_app_creation_file;

private static final Logger log = LoggerFactory.getLogger(Main.class);

Expand All @@ -97,11 +94,11 @@ public Integer call() {
connector.connect(sal_user, sal_password);
}

if (kubevela_file != null && kubevela_parameters!= null) {
if (json_app_creation_file != null) {
try {
NebulousApp app
= AppParser.parseAppCreationMessage(Files.readString(kubevela_file, StandardCharsets.UTF_8),
Files.readString(kubevela_parameters, StandardCharsets.UTF_8));
JSONObject msg = new JSONObject(Files.readString(json_app_creation_file, StandardCharsets.UTF_8));
NebulousApp app = AppParser.parseAppCreationMessage(msg);
app.printAMPL();
} catch (IOException e) {
log.error("Could not read an input file: ", e);
success = 1;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,47 @@
package eu.nebulouscloud.optimiser.controller;

import com.amihaiemil.eoyaml.*;
import org.json.JSONArray;
import org.json.JSONObject;

/**
* Internal representation of a NebulOus app.
*/
public class NebulousApp {
private YamlMapping original_kubevela;
private YamlMapping parameters;
private JSONArray parameters;

/**
* Creates a NebulousApp object.
*
* Example KubeVela and parameter files can be found below {@code
* optimiser-controller/src/test/resources}
*
* @param kubevela A parsed representation of the deployable KubeVela App model
* @param parameters A parameter mapping
* @param parameters A parameter mapping as a sequence of JSON objects.
*/
public NebulousApp(YamlMapping kubevela, YamlMapping parameters) {
// Note that example KubeVela and parameter files can be found at
// optimiser-controller/src/test/resources/
public NebulousApp(YamlMapping kubevela, JSONArray parameters) {
this.original_kubevela = kubevela;
this.parameters = parameters;
}

/**
* Check that the target paths of all parameters can be found in the
* original KubeVela file.
* Check that all parameters have a name, type and path, and that the
* target path can be found in the original KubeVela file.
*
* @return true if all parameter paths match, false otherwise
* @return true if all requirements hold, false otherwise
*/
public boolean validateMapping() {
YamlMapping params = parameters.yamlMapping("nebulous_metadata").yamlMapping("optimisation_variables");
for (final YamlNode param : params.keys()) {
YamlMapping param_data = params.yamlMapping(param);
String param_name = param.asScalar().value();
String target_path = param_data.value("target").asScalar().value();
public boolean validatePaths() {
for (final Object p : parameters) {
JSONObject param = (JSONObject) p;
String param_name = param.optString("key");
if (param_name.equals("")) return false;
String param_type = param.optString("type");
if (param_type.equals("")) return false;
// TODO: also validate types, upper and lower bounds, etc.
String target_path = param.optString("path");
if (target_path.equals("")) return false;
YamlNode target = findPathInKubevela(target_path);
if (target == null) return false;
if (target == null) return false; // must exist
}
return true;
}
Expand Down Expand Up @@ -75,4 +80,52 @@ private YamlNode findPathInKubevela(String path) {
return currentNode;
}

/**
* Print AMPL code for the app, based on the parameter definition(s).
*/
public void printAMPL() {
for (final Object p : parameters) {
JSONObject param = (JSONObject) p;
String param_name = param.getString("key");
String param_type = param.getString("type");
JSONObject value = param.optJSONObject("value");
if (param_type.equals("float")) {
System.out.format("var %s", param_name);
if (value != null) {
String separator = "";
double lower = value.optDouble("lower_bound");
double upper = value.optDouble("upper_bound");
if (!Double.isNaN(lower)) {
System.out.format (" >= %s", lower);
separator = ", ";
}
if (!Double.isNaN(upper)) {
System.out.format("%s<= %s", separator, upper);
}
}
System.out.println(";");
} else if (param_type.equals("int")) {
System.out.format("var %s integer", param_name);
if (value != null) {
String separator = "";
Integer lower = value.optIntegerObject("lower_bound", null);
Integer upper = value.optIntegerObject("upper_bound", null);
if (lower != null) {
System.out.format (" >= %s", lower);
separator = ", ";
}
if (upper != null) {
System.out.format("%s<= %s", separator, upper);
}
}
System.out.println(";");
} else if (param_type.equals("string")) {
System.out.println("# TODO not sure how to specify a string variable");
System.out.format("var %s symbolic;%n");
} else if (param_type.equals("array")) {
System.out.format("# TODO generate entries for map '%s'%n", param_name);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;

import org.json.JSONObject;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

Expand All @@ -22,12 +23,11 @@ private Path getResourcePath(String name) throws URISyntaxException {

@Test
void readValidAppCreationMessage() throws URISyntaxException, IOException {
String kubevela = Files.readString(getResourcePath("vela-deployment.yaml"),
StandardCharsets.UTF_8);
String parameters = Files.readString(getResourcePath("vela-deployment-parameters.yaml"),
StandardCharsets.UTF_8);
NebulousApp app = AppParser.parseAppCreationMessage(kubevela, parameters);
String app_message_string = Files.readString(getResourcePath("vela-deployment-app-message.json"),
StandardCharsets.UTF_8);
JSONObject msg = new JSONObject(app_message_string);
NebulousApp app = AppParser.parseAppCreationMessage(msg);
assertNotNull(app);
assertTrue(app.validateMapping());
assertTrue(app.validatePaths());
}
}
Loading

0 comments on commit 18ebf26

Please sign in to comment.