-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add reflector utility #408
Changes from 2 commits
9765b3d
15c897a
215b631
35514b1
c235c8e
f5eed7e
15733d1
f66dab4
d095b88
c6f5b32
aa4cef8
9c8900e
35073cb
8ad1fb4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#!/bin/bash -e | ||
|
||
ROOT=$(realpath $(dirname $0)/..) | ||
|
||
if [[ $# != 3 ]]; then | ||
echo Usage: $0 site_dir project_id device_id | ||
false | ||
fi | ||
|
||
site_dir=$(realpath $1) | ||
project_id=$2 | ||
device_id=$3 | ||
shift 3 | ||
cd $ROOT | ||
|
||
device_config=/tmp/${device_id}_config.json | ||
cp $site_dir/devices/${device_id}/out/generated_config.json $device_config | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Be consistent and ${var} everywhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
now_date=$(date -Ins -u | sed -E 's/.{6}\+.*/Z/' | tr , .) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I love shell manipulation but we have to worry about OSX compat (if we target that) and one needs to run it to see what it does. We already depend on python. What do you think of this oneliner? We could throw it into bin/util_output_utcnow_8601 or whatever.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
echo Setting config timestamp $now_date | ||
jq .timestamp=\"$now_date\" < $device_config | sponge $device_config | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OSX, if we care. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update: Just comment sponge is a Linuxism or whatever There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added runtime check and warning message, would have to fix actual OSX compat in another round (with QA) |
||
|
||
echo Resetting device $device_id config... | ||
validator/bin/reflector $site_dir $project_id $device_id update/config:$device_config |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
#!/bin/bash | ||
|
||
sudo apt-get install -y hxtools | ||
sudo apt-get install -y hxtools moreutils | ||
|
||
python3 -m venv venv | ||
|
||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#!/bin/bash -e | ||
|
||
ROOT=$(realpath $(dirname $0)/../..) | ||
|
||
if [[ $# < 4 ]]; then | ||
echo Usage: $0 site_dir project_id device_id directive [directives...] | ||
echo " Directive is something like update/config:sites/udmi_site_model/devices/AHU-1/out/generated_config.json" | ||
false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. exit 1? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as other comment... not sure if there's a clear reason for one over the other because they're just "different" in how they handle different edge use cases... |
||
fi | ||
|
||
site_dir=$(realpath $1) | ||
project_id=$2 | ||
device_id=$3 | ||
shift 3 | ||
cd $ROOT | ||
|
||
validator/bin/build | ||
|
||
jarfile=validator/build/libs/validator-1.0-SNAPSHOT-all.jar | ||
mainclass=com.google.daq.mqtt.validator.Reflector | ||
|
||
cmd="java -cp $jarfile $mainclass -p $project_id -s $site_dir -d $device_id $*" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just invoke directly and "$*" on end? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there was supposed to be another diagnostic "echo" in there... added! |
||
$cmd |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.google.daq.mqtt.util; | ||
|
||
import com.fasterxml.jackson.annotation.JsonInclude.Include; | ||
import com.fasterxml.jackson.core.JsonParser.Feature; | ||
import com.fasterxml.jackson.databind.DeserializationFeature; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.fasterxml.jackson.databind.SerializationFeature; | ||
import com.fasterxml.jackson.databind.util.ISO8601DateFormat; | ||
import java.util.List; | ||
import java.util.MissingFormatArgumentException; | ||
|
||
/** | ||
* Collection of common constants and minor utilities. | ||
*/ | ||
public abstract class Common { | ||
|
||
public static final String STATE_QUERY_TOPIC = "query/state"; | ||
public static final String TIMESTAMP_ATTRIBUTE = "timestamp"; | ||
public static final String NO_SITE = "--"; | ||
public static final ObjectMapper OBJECT_MAPPER = | ||
new ObjectMapper() | ||
.enable(Feature.ALLOW_COMMENTS) | ||
.enable(SerializationFeature.INDENT_OUTPUT) | ||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) | ||
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) | ||
.setDateFormat(new ISO8601DateFormat()) | ||
.setSerializationInclusion(Include.NON_NULL); | ||
public static final String JSON_SUFFIX = ".json"; | ||
public static final String GCP_REFLECT_KEY_PKCS8 = "validator/rsa_private.pkcs8"; | ||
|
||
/** | ||
* Remove the next item from the list in an exception-safe way. | ||
* | ||
* @param argList list of arguments | ||
* @return removed argument | ||
*/ | ||
public static String removeNextArg(List<String> argList) { | ||
if (argList.isEmpty()) { | ||
throw new MissingFormatArgumentException("Missing argument"); | ||
} | ||
return argList.remove(0); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package com.google.daq.mqtt.validator; | ||
|
||
import static com.google.daq.mqtt.util.Common.GCP_REFLECT_KEY_PKCS8; | ||
import static com.google.daq.mqtt.util.Common.NO_SITE; | ||
import static com.google.daq.mqtt.util.Common.removeNextArg; | ||
|
||
import com.google.bos.iot.core.proxy.IotReflectorClient; | ||
import com.google.daq.mqtt.util.CloudIotConfig; | ||
import com.google.daq.mqtt.util.CloudIotManager; | ||
import com.google.daq.mqtt.util.ConfigUtil; | ||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
/** | ||
* General utility for working with UDMI Reflector messages. | ||
*/ | ||
public class Reflector { | ||
|
||
private final List<String> reflectCommands; | ||
private String projectId; | ||
private String siteDir; | ||
private CloudIotConfig cloudIotConfig; | ||
private File baseDir; | ||
private IotReflectorClient client; | ||
private String deviceId; | ||
|
||
/** | ||
* Create an instance of the Reflector class. | ||
* | ||
* @param argsList command-line arguments | ||
*/ | ||
public Reflector(List<String> argsList) { | ||
reflectCommands = parseArgs(argsList); | ||
} | ||
|
||
/** | ||
* Let's go. | ||
* | ||
* @param args command-line arguments | ||
*/ | ||
public static void main(String[] args) { | ||
Reflector reflector = new Reflector(Arrays.asList(args)); | ||
reflector.initialize(); | ||
reflector.reflect(); | ||
reflector.shutdown(); | ||
} | ||
|
||
private void shutdown() { | ||
try { | ||
Thread.sleep(5000); | ||
} catch (Exception e) { | ||
throw new RuntimeException("While sleeping", e); | ||
} | ||
client.close(); | ||
} | ||
|
||
private void reflect() { | ||
System.err.printf("Reflecting %d directives...%n", reflectCommands.size()); | ||
while (!reflectCommands.isEmpty()) { | ||
reflect(reflectCommands.remove(0)); | ||
} | ||
System.err.println("Done with all reflective directives!"); | ||
} | ||
|
||
private void reflect(String directive) { | ||
System.err.printf("Reflecting %s%n", directive); | ||
String[] parts = directive.split(":", 2); | ||
if (parts.length != 2) { | ||
throw new RuntimeException("Expected topic:file format reflect directive " + directive); | ||
} | ||
File file = new File(parts[1]); | ||
reflect(parts[0], file); | ||
} | ||
|
||
private void reflect(String topic, File dataFile) { | ||
try (FileInputStream fis = new FileInputStream(dataFile)) { | ||
String data = new String(fis.readAllBytes()); | ||
reflect(topic, data); | ||
} catch (Exception e) { | ||
throw new RuntimeException("While processing input file " + dataFile.getAbsolutePath(), e); | ||
} | ||
} | ||
|
||
private void reflect(String topic, String data) { | ||
client.publish(deviceId, topic, data); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this going to verify that the json input is well-formed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No -- it's not actually even strictly JSON. This was meant as a reasonably low-level utility that wasn't really opinionated about formats. Maybe future iterations could add a "validate schema" flag or something... E.g. this tool can be used to validate that a borked garbage config does not tank the system. |
||
} | ||
|
||
private void initialize() { | ||
String keyFile = new File(siteDir, GCP_REFLECT_KEY_PKCS8).getAbsolutePath(); | ||
System.err.println("Loading reflector key file from " + keyFile); | ||
client = new IotReflectorClient(projectId, cloudIotConfig, keyFile); | ||
} | ||
|
||
private List<String> parseArgs(List<String> argsList) { | ||
List<String> listCopy = new ArrayList<>(argsList); | ||
while (!listCopy.isEmpty()) { | ||
String option = removeNextArg(listCopy); | ||
try { | ||
switch (option) { | ||
case "-p": | ||
projectId = removeNextArg(listCopy); | ||
break; | ||
case "-s": | ||
setSiteDir(removeNextArg(listCopy)); | ||
break; | ||
case "-d": | ||
deviceId = removeNextArg(listCopy); | ||
break; | ||
default: | ||
listCopy.add(option); | ||
return listCopy; // default case is the remaining list of reflection directives | ||
} | ||
} catch (Exception e) { | ||
throw new RuntimeException("While processing option " + option, e); | ||
} | ||
} | ||
throw new IllegalArgumentException("No reflect directives specified!"); | ||
} | ||
|
||
/** | ||
* Set the site directory to use for this run. | ||
* | ||
* @param siteDir site model directory | ||
*/ | ||
public void setSiteDir(String siteDir) { | ||
if (NO_SITE.equals(siteDir)) { | ||
siteDir = null; | ||
baseDir = new File("."); | ||
} else { | ||
this.siteDir = siteDir; | ||
baseDir = new File(siteDir); | ||
File cloudConfig = new File(siteDir, "cloud_iot_config.json"); | ||
cloudIotConfig = CloudIotManager.validate(ConfigUtil.readCloudIotConfig(cloudConfig), | ||
projectId); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want exec false? or just exit 1?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I avoid "exit 1" because sometimes I'll end up doing "source" on a script and then "exit 1" will totally bork the shell session... the behavior of "false" is only that if the -e flag is specified. So, in this particular case they are functionally equivalent, but I'm not sure if there's really a compelling reason for one over the other in the big picture.