Skip to content

Commit

Permalink
Simplify update request flags. Switch up short flag names.
Browse files Browse the repository at this point in the history
  • Loading branch information
thbrown committed Nov 26, 2022
1 parent f31607f commit 22d260e
Show file tree
Hide file tree
Showing 12 changed files with 86 additions and 87 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ This CLI tool expects data to be in softball.app JSON format. You can either edi
# Production Build (Note: this runs tests, formats code, optimizes jar)
./gradlew build
# Development Build without running tests (skips test and jar optimizations)
./gradlew build -x test -x proguard
# Development Build without optimizing the jar (output jar will work as a command line tool but gcp functions will break) or running tests
./gradlew build -x proguard -x test
```

Expand Down
21 changes: 6 additions & 15 deletions src/com/github/thbrown/softballsim/CommandLineOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,10 @@ public class CommandLineOptions {
public final static String VERBOSE = "v"; // Should this be 'B' and use 'V' for version?
public final static String LINEUP = "l";
public final static String FORCE = "f";
public final static String UPDATE_INTERVAL = "u";
public final static String ESTIMATE_ONLY = "e";
public final static String ID = "i";
public final static String UPDATE_URL = "r";
public final static String UPDATE_API_KEY = "k";
public final static String UPDATE_STUFF = "s";
public final static String UPDATE_INTERVAL = "i";
public final static String UPDATE_URL = "u";
public final static String UPDATE_BODY = "b";

public final static String DATA_SOURCE_DEFAULT = "FILE_SYSTEM";
public final static String TYPE_LINEUP_DEFAULT = "STANDARD";
Expand Down Expand Up @@ -117,20 +115,13 @@ public List<Option> getCommonOptions() {
.hasArg(false).required(false).build());

// Options for HTTP post request on update (these should maybe be hidden since
// they are confusing if you just want ot run a lineup optimization)
// they are confusing if you just want to run a lineup optimization)
commonOptions
.add(Option.builder(UPDATE_URL).longOpt("url").desc("URL to make an HTTP POST when an update is available.")
.hasArg(true).required(false).build());
commonOptions.add(Option.builder(UPDATE_API_KEY).longOpt("api-key")
.desc("Api key to include in the body of the HTTP POST when an update is available")
commonOptions.add(Option.builder(UPDATE_BODY).longOpt("update-body")
.desc("JSON string to be sent as the HTTP POST body to the update-url endpoint")
.hasArg(true).required(false).build());
commonOptions.add(Option.builder(UPDATE_STUFF).longOpt("stuff")
.desc("Additional information to be sent in the HTTP POST payload")
.hasArg(true).required(false).build());
commonOptions.add(Option.builder(ID).longOpt("id").desc(DataSourceEnum.GCP_BUCKETS
+ ": Required. An arbitrary id associated with this request. The same id can be used to query for intermediate results.")
.hasArg(true).required(false).build());

return commonOptions;
}

Expand Down
1 change: 0 additions & 1 deletion src/com/github/thbrown/softballsim/SoftballSim.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ public static Result mainInternal(String[] args, Runnable cleanup) throws Except
if (estimateOnly) {
Result result = optimizer.estimate(playersIdsOnly, lineupType, stats, arguments, null);
dataSource.onComplete(allCmd, stats, result);

// Almost done, just run the cleanup procedure supplied on invocation (if any)
if (cleanup != null) {
cleanup.run();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class GcpFunctionsEntryPointCompute implements HttpFunction {
final String SNAPSHOT_NAME = "optimization-base-2";

public static final String ARGS_KEY = "args";
public static final String ID_KEY = "id";
public static final String NAME_KEY = "name";
public static final String ZONES_KEY = "zones";

@Override
Expand Down Expand Up @@ -74,17 +74,17 @@ public void service(HttpRequest request, HttpResponse response) throws Exception
Logger.log("Body: " + map.toString());

String args = map.get(ARGS_KEY).replace("\\\"", "\"");
String id = map.get(ID_KEY);
String name = map.get(NAME_KEY);
String[] zones = map.get(ZONES_KEY).split(",");
zones = Arrays.stream(zones).filter(x -> !StringUtils.isBlank(x)).toArray(String[]::new); // Filter empty strings

Logger.log("Args: " + args);
Logger.log("Id: " + id);
Logger.log("Name: " + name);
Logger.log("Zones: " + Arrays.toString(zones) + " " + zones.length);

// Don't start a new compute instance if the result is in a terminal state (e.g.
// COMPLETE, PAUSED, etc...)
String resultJson = CloudUtils.readBlob(id, DataSourceGcpBuckets.CACHED_RESULTS_BUCKET);
String resultJson = CloudUtils.readBlob(name, DataSourceGcpBuckets.CACHED_RESULTS_BUCKET);
Result result = gson.fromJson(resultJson, Result.class);
if (result != null && result.getStatus().isTerminal()) {
Logger.log("Not starting compute instance, ");
Expand All @@ -96,10 +96,10 @@ public void service(HttpRequest request, HttpResponse response) throws Exception
// We've tried all the compute zones? mark the current result as ERROR
if (zones.length == 0) {
Logger.log("ERROR - Zones exhausted");
String resultJsonOriginal = CloudUtils.readBlob(id, DataSourceGcpBuckets.CACHED_RESULTS_BUCKET);
String resultJsonOriginal = CloudUtils.readBlob(name, DataSourceGcpBuckets.CACHED_RESULTS_BUCKET);
String updatedResult = Result.copyWithNewStatusStringOnly(resultJsonOriginal, ResultStatusEnum.ERROR,
"Cloud resources unavailable, try again later");
CloudUtils.upsertBlob(updatedResult, id, DataSourceGcpBuckets.CACHED_RESULTS_BUCKET);
CloudUtils.upsertBlob(updatedResult, name, DataSourceGcpBuckets.CACHED_RESULTS_BUCKET);
throw new RuntimeException("Zones Exhausted");
}

Expand All @@ -110,13 +110,13 @@ public void service(HttpRequest request, HttpResponse response) throws Exception
// 1) lowercased optimization id
// 2) a random string so unpauses don't fail before the shutdown
// 3) remaining zones so we can see some basic info about the instance
final String intanceName = "opt-" + id.toLowerCase() + "-" + MiscUtils.getRandomString(10) + "-"
final String intanceName = "opt-" + name.toLowerCase() + "-" + MiscUtils.getRandomString(10) + "-"
+ futureZones.length;

// TODO: retry on failure?
Operation operation = makeInstance(id, nextZone, PROJECT, MACHINE_TYPE, intanceName,
Operation operation = makeInstance(name, nextZone, PROJECT, MACHINE_TYPE, intanceName,
HOME_DIRECTORY, SNAPSHOT_NAME, args, futureZones, pwd);
Logger.log(id + " operation: " + operation.getSelfLink());
Logger.log(name + " operation: " + operation.getSelfLink());

// Send the response
CloudUtils.send200Success(response, "Success - sent compute request start command.");
Expand Down Expand Up @@ -159,7 +159,7 @@ private Operation makeInstance(String optimizationId, String zone, String projec

JsonObject jsonObject = new JsonObject();
jsonObject.add(GcpFunctionsEntryPointCompute.ARGS_KEY, new JsonPrimitive(applicationArguments));
jsonObject.add(GcpFunctionsEntryPointCompute.ID_KEY, new JsonPrimitive(optimizationId));
jsonObject.add(GcpFunctionsEntryPointCompute.NAME_KEY, new JsonPrimitive(optimizationId));
jsonObject.add(GcpFunctionsEntryPointCompute.ZONES_KEY, new JsonPrimitive(String.join(",", futureZones)));
jsonObject.add(GcpFunctionsEntryPointStart.PASSWORD_KEY, new JsonPrimitive(pwd)); // TODO: this is pretty sloppy, switch to use service accounts
Gson gson = GsonAccessor.getInstance().getCustom();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,16 @@ public void service(HttpRequest request, HttpResponse response) throws Exception
map.remove(GcpFunctionsEntryPointStart.PASSWORD_KEY);

// Some error checking for the id
String id = map.get(CommandLineOptions.ID);
if (id == null) {
String name = map.get(DataSourceGcpBuckets.NAME);
if (name == null) {
CloudUtils.send400Error(response,
"Required json field " + CommandLineOptions.ID + " was not specified in the body");
"Required json field " + DataSourceGcpBuckets.NAME + " was not specified in the body");
return;
}

// We'll add flag to the control bucket to indicate that a currently running
// optimization should stop
CloudUtils.upsertBlob("HALT", id, DataSourceGcpBuckets.CONTROL_FLAGS_BUCKET);
CloudUtils.upsertBlob("HALT", name, DataSourceGcpBuckets.CONTROL_FLAGS_BUCKET);

// Return success
CloudUtils.send200Success(response, "Successfully sent pause request");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ public void service(HttpRequest request, HttpResponse response) throws Exception
map.remove(GcpFunctionsEntryPointStart.PASSWORD_KEY);

// Some error checking for the id
String id = map.get(CommandLineOptions.ID);
Logger.log("ID " + id + " " + map);
String name = map.get(DataSourceGcpBuckets.NAME);
Logger.log("NAME " + name + " " + map);

if (id == null) {
CloudUtils.send400Error(response, "Missing required field '" + CommandLineOptions.ID + "' (Id)");
if (name == null) {
CloudUtils.send400Error(response, "Missing required field '" + DataSourceGcpBuckets.NAME + "' (Name)");
return;
}

String contentString = CloudUtils.readBlob(id, DataSourceGcpBuckets.CACHED_RESULTS_BUCKET);
String contentString = CloudUtils.readBlob(name, DataSourceGcpBuckets.CACHED_RESULTS_BUCKET);
CloudUtils.send200Success(response, contentString);
} catch (Exception e) {
// Log stack
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class GcpFunctionsEntryPointStart implements HttpFunction {
private static final String ZONES = "us-central1-a,us-central1-b,us-central1-c,us-central1-f";

private static final String DATA_KEY = "data";
private static final String ID_KEY = "-" + CommandLineOptions.ID;
private static final String NAME_KEY = "-" + DataSourceGcpBuckets.NAME;
private static final String DATA_SOURCE_KEY = "-" + CommandLineOptions.DATA_SOURCE;
private static final String ESTIMATE_ONLY_KEY = "-" + CommandLineOptions.ESTIMATE_ONLY;
private static final String OPTIMIZER_KEY = "-" + CommandLineOptions.OPTIMIZER;
Expand Down Expand Up @@ -84,14 +84,15 @@ public void service(HttpRequest request, HttpResponse response) throws Exception
Logger.log("Map " + map);

// Error checking
String id = map.get(ID_KEY);
String id = map.get(NAME_KEY);
if (id == null) {
new RuntimeException("Required json field " + ID_KEY + " (id) was not specified in the request body");
new RuntimeException("Required json field " + NAME_KEY + " (name) was not specified in the request body");
}
int length = id.length();
if (length < 15 || length > 63) {
new RuntimeException(
"Please provide an Id (-I) that is longer than 15 characters and shorter than 64 characters. Was " + length
"Please provide an Name (-n) that is longer than 15 characters and shorter than 64 characters. Was "
+ length
+ " characters");
}

Expand Down Expand Up @@ -185,7 +186,7 @@ public void service(HttpRequest request, HttpResponse response) throws Exception
// Start the job on a compute instance
JsonObject jsonObject = new JsonObject();
jsonObject.add(GcpFunctionsEntryPointCompute.ARGS_KEY, new JsonPrimitive(stringArguments));
jsonObject.add(GcpFunctionsEntryPointCompute.ID_KEY, new JsonPrimitive(map.get(ID_KEY)));
jsonObject.add(GcpFunctionsEntryPointCompute.NAME_KEY, new JsonPrimitive(map.get(NAME_KEY)));
jsonObject.add(GcpFunctionsEntryPointCompute.ZONES_KEY, new JsonPrimitive(ZONES));
jsonObject.add(PASSWORD_KEY, new JsonPrimitive(pwd));
String jsonPayload = gson.toJson(jsonObject);
Expand Down
52 changes: 24 additions & 28 deletions src/com/github/thbrown/softballsim/datasource/DataSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@
import com.github.thbrown.softballsim.CommandLineOptions;
import com.github.thbrown.softballsim.Result;
import com.github.thbrown.softballsim.data.gson.DataStats;
import com.github.thbrown.softballsim.util.GsonAccessor;
import com.github.thbrown.softballsim.util.Logger;
import com.github.thbrown.softballsim.util.StringUtils;
import com.google.gson.Gson;
import com.google.gson.JsonObject;

public interface DataSource {

Expand Down Expand Up @@ -59,9 +56,7 @@ public default void onUpdate(CommandLine cmd, DataStats stats, ProgressTracker t
// Call the update urls if specified
if (cmd.hasOption(CommandLineOptions.UPDATE_URL)) {
boolean success = signalUpdate(cmd.getOptionValue(CommandLineOptions.UPDATE_URL),
cmd.getOptionValue(CommandLineOptions.ID),
cmd.getOptionValue(CommandLineOptions.UPDATE_API_KEY),
cmd.getOptionValue(CommandLineOptions.UPDATE_STUFF));
cmd.getOptionValue(CommandLineOptions.UPDATE_BODY));
if (!success) {
Logger.warn("UPDATE: Call to update url failed");
}
Expand All @@ -72,16 +67,14 @@ public default void onUpdate(CommandLine cmd, DataStats stats, ProgressTracker t
* Called once after an optimizer has completed (whether is ends successfully or in error).
*/
public default void onComplete(CommandLine cmd, DataStats stats, Result finalResult) {
// Call the update url, if specified
if (cmd.hasOption(CommandLineOptions.UPDATE_URL)) {
// Call the update url, if one was specified and this is not an estimate only
if (!cmd.hasOption(CommandLineOptions.ESTIMATE_ONLY) && cmd.hasOption(CommandLineOptions.UPDATE_URL)) {
int retryCount = 0;
boolean success = false;
while (true) {
success = signalUpdate(
cmd.getOptionValue(CommandLineOptions.UPDATE_URL),
cmd.getOptionValue(CommandLineOptions.ID),
cmd.getOptionValue(CommandLineOptions.UPDATE_API_KEY),
cmd.getOptionValue(CommandLineOptions.UPDATE_STUFF));
cmd.getOptionValue(CommandLineOptions.UPDATE_BODY));

if (success) {
break;
Expand Down Expand Up @@ -116,46 +109,49 @@ public default void onComplete(CommandLine cmd, DataStats stats, Result finalRes
*/
public String getControlFlag(CommandLine cmd, DataStats stats);

private boolean signalUpdate(String inputUrl, String optimizationId, String apiKey, String stuff) {
/**
* Send HTTP post to the endpoint indicated by the input parameters. Returns false if API call
* failed and can be retried, returns true otherwise.
*/
private boolean signalUpdate(String inputUrl, String body) {
try {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("optimizationId", optimizationId);
jsonObject.addProperty("apiKey", apiKey);
jsonObject.addProperty("stuff", stuff);
Gson gson = GsonAccessor.getInstance().getCustom();
String jsonPayload = gson.toJson(jsonObject);
Logger.log("Sending " + jsonPayload + " to " + inputUrl);
byte[] out = jsonPayload.getBytes(StandardCharsets.UTF_8);
// Send an empty json object if no body is defined
if (body == null) {
body = "{}";
}
byte[] out = body.getBytes(StandardCharsets.UTF_8);
int length = out.length;

URL url = new URL(inputUrl);
URLConnection con = url.openConnection();
HttpURLConnection http = (HttpURLConnection) con;
http.setRequestMethod("POST"); // PUT is another valid option
http.setRequestMethod("POST");
http.setDoOutput(true);
http.setFixedLengthStreamingMode(length);
http.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
http.connect();
OutputStream os = http.getOutputStream();
os.write(out);
int code = http.getResponseCode();
http.disconnect();

// TODO: don't retry 400 or 403
if (code != 204 && code != 200) {
Logger.error("Bad response from update URL");
if (code == 204 && code == 200) {
Logger.log("Update call succeeded");
return true;
} else if (code == 400 || code != 403) {
Logger.log("Update call API error " + code + " not attempting retry");
return true;
} else {
Logger.log("Update call API error " + code + " attempting retry");
Logger.error(String.valueOf(code));
Logger.error(http.getResponseMessage());
return false;
} else {
Logger.log("Update call succeeded");
}
http.disconnect();
} catch (Exception e) {
Logger.error("Failed to call update URL");
Logger.error(e);
return false;
}
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@
* update thread function into separate classes.
*/
public final class ProgressTracker {
// This should be at least as big as the number of threads, more will give
// better time estimation at
// the expense of a bigger memory footprint
// This should be at least as big as the number of threads, a larger value will give a more
// accurate time estimation at the expense of a bigger memory footprint
private final static int RESULTS_BUFFER_SIZE = 512;

// Multiple threads have read/write access to 'results' all use must sync on
Expand Down
Loading

0 comments on commit 22d260e

Please sign in to comment.