Skip to content
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

Preserve original contents of exception messages #943

Merged
merged 9 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ jobs:
run: bin/start_local sites/udmi_site_model $TARGET_PROJECT
- name: registrar clean
run: bin/test_regclean solo $TARGET_PROJECT
- name: special sequences
if: ${{ !cancelled() }}
run: bin/test_special $TARGET_PROJECT
- name: telemetry validator
if: ${{ !cancelled() }}
run: bin/test_validator $TARGET_PROJECT
Expand Down
2 changes: 1 addition & 1 deletion bin/pubber
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ fi
topic_prefix=null
auth_provider=null
transport=null
target_id=$device_id

if [[ $iot_provider == clearblade ]]; then
broker_hostname=${cloud_region}-mqtt.clearblade.com
Expand All @@ -133,7 +134,6 @@ elif [[ $iot_provider == mqtt ]]; then
broker_hostname=$project_target
gateway_id=$(jq -r .gateway.gateway_id $site_model/devices/$device_id/metadata.json) || true
echo Attached to gateway $gateway_id
target_id=$device_id
[[ -z $gateway_id || $gateway_id == null ]] || target_id=$gateway_id
echo Target is $target_id
client_id=\"/r/$registry_actual/d/$target_id\"
Expand Down
33 changes: 33 additions & 0 deletions bin/test_special
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash -e

UDMI_ROOT=$(dirname $0)/..
cd $UDMI_ROOT

source etc/shell_common.sh

[[ $# == 1 ]] || usage PROJECT_SPEC

project_spec=$1
shift
site_path=sites/udmi_site_model
device_id=AHU-1
serial_no=192842
device_out=$site_path/out/devices/$device_id

pubber_bg $device_id

rm -rf $device_out

# Run with trace enabled to capture all intermediate messages
bin/sequencer -vv $site_path $project_spec $device_id $serial_no broken_config

# Check that the message captures for broken_config contain the raw (extracted) message payload"
echo
echo Checking broken_config capture results...
fgrep "RESULT pass system broken_config" $device_out/RESULT.log
find $device_out -name \*.attr | xargs fgrep "While converting string"
find $device_out -name \*.json | xargs egrep "^{ broken by"

echo
echo Done with special sequence tests.
echo
61 changes: 12 additions & 49 deletions bin/test_validator
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ cd $UDMI_ROOT
source etc/shell_common.sh

if [[ $# != 1 ]]; then
echo Usage: $0 PROJECT_ID
echo Usage: $0 PROJECT_SPEC
false
fi

project_id=$1
project_spec=$1
shift

[[ -n ${GITHUB_RUN_NUMBER:-} ]] && echo "Workflow run number $GITHUB_RUN_NUMBER" || true
echo 'Using target project:' $project_id
echo 'Using target project:' $project_spec

echo "export TARGET_PROJECT=$project_id"
echo "export TARGET_PROJECT=$project_spec"
echo "export UDMI_REGISTRY_SUFFIX=${UDMI_REGISTRY_SUFFIX:-}"
echo "export UDMI_ALT_REGISTRY=${UDMI_ALT_REGISTRY:-}"

Expand All @@ -25,12 +25,11 @@ iot_config=$site_path/cloud_iot_config.json
output_file=$site_path/out/validation_report.json
mkdir -p out

PUBBER_LOG=out/pubber.log
VALIDATOR_LOG=out/validator.log
VALIDATOR_OUT=out/validator.out
GOLDEN_OUT=etc/validator.out
TRACE_DIR=out/validator_trace
WAITING=20
VALIDATOR_WAIT=20

echo Killing running pubber/validator instances...
ps ax | fgrep pubber | fgrep java | awk '{print $1}' | xargs -r kill || true
Expand All @@ -47,30 +46,30 @@ pubber/bin/build

# Have to reset pubber configs before running validator to avoid reflector registry conflict
for device in AHU-1 AHU-22 GAT-123; do
echo bin/reset_config $site_path $project_id $device
bin/reset_config $site_path $project_id $device
echo bin/reset_config $site_path $project_spec $device
bin/reset_config $site_path $project_spec $device
done

touch $site_path/out/validator_start.nop

echo Starting validator, output in $VALIDATOR_LOG, trace in $TRACE_DIR
rm -rf $TRACE_DIR
echo bin/validator $site_path $project_id -w $TRACE_DIR
bin/validator $site_path $project_id -w $TRACE_DIR > $VALIDATOR_LOG 2>&1 &
echo bin/validator $site_path $project_spec -w $TRACE_DIR
bin/validator $site_path $project_spec -w $TRACE_DIR > $VALIDATOR_LOG 2>&1 &
vpid=$!
echo Started validator pid $vpid

echo Waiting for validator to startup...

for i in `seq 1 $WAITING`; do
for i in `seq 1 $VALIDATOR_WAIT`; do
if fgrep "Entering message loop" $VALIDATOR_LOG; then
break
fi
echo Waiting for validator startup $((WAITING - i))...
echo Waiting for validator startup $((VALIDATOR_WAIT - i))...
sleep 1
done

if [[ $i -eq $WAITING ]]; then
if [[ $i -eq $VALIDATOR_WAIT ]]; then
echo validator startup failed:
cat $VALIDATOR_LOG
false
Expand All @@ -79,42 +78,6 @@ fi
echo Checking reported cloud version info
jq .cloud_version.udmi_ref $output_file

function pubber_bg {
device_id=$1
shift
outfile=$PUBBER_LOG.$device_id
serial_no=validator-$RANDOM

device_dir=$site_path/devices/$device_id

echo bin/keygen CERT $device_dir
bin/keygen CERT $device_dir || true

echo Writing pubber output to $outfile, serial no $serial_no
cmd="bin/pubber $site_path $project_id $device_id $serial_no $@"
echo $cmd

date > $outfile
echo $cmd >> $outfile
$cmd >> $outfile 2>&1 &

# Give a little bit of time to settle before deterministic check

for i in `seq 1 $WAITING`; do
if fgrep "Connection complete" $outfile; then
break
fi
echo Waiting for pubber startup $((WAITING - i))...
sleep 1
done

if [[ $i -eq $WAITING ]]; then
echo pubber startup failed:
cat $outfile
return 1
fi
}

pubber_bg AHU-1 extraField=prlagle skewClock
pubber_bg GAT-123 emptyMissing extraPoint=llama

Expand Down
6 changes: 3 additions & 3 deletions common/src/main/java/com/google/udmi/util/GeneralUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -596,11 +596,11 @@ public static <T> T using(T variable, Consumer<T> consumer) {
return variable;
}

public static void writeString(File metadataFile, String metadataString) {
public static void writeString(File file, String string) {
try {
FileUtils.write(metadataFile, metadataString, Charset.defaultCharset());
FileUtils.write(file, string, Charset.defaultCharset());
} catch (IOException e) {
throw new RuntimeException("While writing output file " + metadataFile.getAbsolutePath(), e);
throw new RuntimeException("While writing output file " + file.getAbsolutePath(), e);
}
}

Expand Down
12 changes: 6 additions & 6 deletions docs/udmis/local_docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ After startup, the site model needs to be registered as per standard UDMI practi
needs to be done once per site model (or after any significant changes). The
[sample registrar output](registrar_output.md) shows what a successful run looks like.
```
docker run --rm --net udminet --name registrar -v $(realpath site_model):/root/site \
ghcr.io/faucetsdn/udmi:validator-latest bin/registrar site/cloud_iot_config.json
docker run --rm --net udminet --name registrar -v $(realpath site_model):/root/site_model \
ghcr.io/faucetsdn/udmi:validator-latest bin/registrar site_model/cloud_iot_config.json
```

## Pubber Instance
Expand All @@ -62,17 +62,17 @@ For initial install testing, it's recommended to try first with the standard _pu
See the [sample pubber output](pubber_output.md) for the beginning of what this run looks like.

```
docker run -d --rm --net udminet --name pubber -v $(realpath site_model):/root/site \
ghcr.io/faucetsdn/udmi:pubber-latest bin/pubber site/cloud_iot_config.json
docker run -d --rm --net udminet --name pubber -v $(realpath site_model):/root/site_model \
ghcr.io/faucetsdn/udmi:pubber-latest bin/pubber site_model/cloud_iot_config.json
```

## Sequencer Testing

Sequencer can be run directly as per normal too. See the [sample sequencer output](sequencer_output.md)
for what the beginning of a successful run looks like.
```
docker run --rm --net udminet --name sequencer -v $(realpath $site_model):/root/site \
ghcr.io/faucetsdn/udmi:validator-latest bin/sequencer site/cloud_iot_config.json
docker run --rm --net udminet --name sequencer -v $(realpath $site_model):/root/site_model \
ghcr.io/faucetsdn/udmi:validator-latest bin/sequencer site_model/cloud_iot_config.json
```

The resulting output can be extracted from the site model, See the [sample report output](report_output.md)
Expand Down
38 changes: 38 additions & 0 deletions etc/shell_common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,44 @@ function usage {
false
}

PUBBER_LOG=out/pubber.log
PUBBER_WAIT=30
function pubber_bg {
device_id=$1
shift
outfile=$PUBBER_LOG.$device_id
serial_no=pubber-$RANDOM

device_dir=$site_path/devices/$device_id

echo bin/keygen CERT $device_dir
bin/keygen CERT $device_dir || true

echo Writing pubber output to $outfile, serial no $serial_no
cmd="bin/pubber $site_path $project_spec $device_id $serial_no $@"
echo $cmd

date > $outfile
echo $cmd >> $outfile
$cmd >> $outfile 2>&1 &

# Give a little bit of time to settle before deterministic check

for count in `seq 0 $PUBBER_WAIT`; do
if fgrep "Connection complete" $outfile; then
break
fi
echo Waiting for pubber startup $((PUBBER_WAIT - count))...
sleep 1
done

if [[ $count -eq $PUBBER_WAIT ]]; then
echo pubber startup failed:
cat $outfile
return 1
fi
}

UDMI_ROOT=$(realpath $UDMI_ROOT)

UDMI_JAR=$UDMI_ROOT/validator/build/libs/validator-1.0-SNAPSHOT-all.jar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@
import static com.google.daq.mqtt.util.ConfigManager.configFrom;
import static com.google.daq.mqtt.util.IotReflectorClient.REFLECTOR_PREFIX;
import static com.google.daq.mqtt.util.TimePeriodConstants.TWO_MINUTES_MS;
import static com.google.daq.mqtt.validator.Validator.ATTRIBUTE_FILE_FORMAT;
import static com.google.daq.mqtt.validator.Validator.MESSAGE_FILE_FORMAT;
import static com.google.udmi.util.CleanDateFormat.cleanDate;
import static com.google.udmi.util.CleanDateFormat.dateEquals;
import static com.google.udmi.util.Common.DEVICE_ID_KEY;
import static com.google.udmi.util.Common.EXCEPTION_KEY;
import static com.google.udmi.util.Common.MESSAGE_KEY;
import static com.google.udmi.util.GeneralUtils.CSV_JOINER;
import static com.google.udmi.util.GeneralUtils.catchToElse;
import static com.google.udmi.util.GeneralUtils.changedLines;
import static com.google.udmi.util.GeneralUtils.decodeBase64;
import static com.google.udmi.util.GeneralUtils.friendlyStackTrace;
import static com.google.udmi.util.GeneralUtils.getTimestamp;
import static com.google.udmi.util.GeneralUtils.ifNotNullGet;
Expand All @@ -28,6 +32,7 @@
import static com.google.udmi.util.GeneralUtils.ifTrueThen;
import static com.google.udmi.util.GeneralUtils.isTrue;
import static com.google.udmi.util.GeneralUtils.stackTraceString;
import static com.google.udmi.util.GeneralUtils.writeString;
import static com.google.udmi.util.JsonUtil.convertTo;
import static com.google.udmi.util.JsonUtil.getNowInstant;
import static com.google.udmi.util.JsonUtil.isoConvert;
Expand Down Expand Up @@ -973,6 +978,22 @@ private Bucket getBucket(Feature feature) {
return implicit == UNKNOWN_DEFAULT ? explicit : implicit;
}

private void recordMessageAttributes(Envelope attributes, String messageBase) {
File file = new File(testDir, format(ATTRIBUTE_FILE_FORMAT, messageBase));
try {
JsonUtil.OBJECT_MAPPER.writeValue(file, attributes);
} catch (Exception e) {
throw new RuntimeException("While writing attributes to " + file.getAbsolutePath(), e);
}
}

private void unwrapException(Map<String, Object> message, Envelope attributes) {
Object ex = message.get(EXCEPTION_KEY);
attributes.payload = ifTrueGet(ex instanceof Exception,
(Supplier<String>) () -> ((Exception) ex).getMessage(),
(Supplier<String>) () -> (String) ex);
}

private void recordRawMessage(Envelope attributes, Map<String, Object> message) {
if (testName == null || !recordMessages) {
return;
Expand All @@ -984,15 +1005,9 @@ private void recordRawMessage(Envelope attributes, Map<String, Object> message)
messageBase = messageBase + "_" + timestamp;
}

ifTrueThen(message.containsKey(EXCEPTION_KEY), () -> unwrapException(message, attributes));
recordRawMessage(message, messageBase);

File attributeFile = new File(testDir, messageBase + ".attr");
try {
JsonUtil.OBJECT_MAPPER.writeValue(attributeFile, attributes);
} catch (Exception e) {
throw new RuntimeException("While writing attributes to " + attributeFile.getAbsolutePath(),
e);
}
recordMessageAttributes(attributes, messageBase);
}

private void recordRawMessage(Object message, String messageBase) {
Expand All @@ -1019,7 +1034,15 @@ private void recordRawMessage(Map<String, Object> message, String messageBase) {
boolean syntheticMessage = (configMessage || stateMessage) && !updateMessage;

String prefix = localUpdate ? "local " : "received ";
File messageFile = new File(testDir, messageBase + ".json");
File messageFile = new File(testDir, format(MESSAGE_FILE_FORMAT, messageBase));

if (message.containsKey(EXCEPTION_KEY)) {
String exceptionMessage = (String) message.get(MESSAGE_KEY);
Envelope exceptionWrapper = JsonUtil.fromString(Envelope.class, exceptionMessage);
writeString(messageFile, decodeBase64(exceptionWrapper.payload));
return;
}

Object savedException = message == null ? null : message.get(EXCEPTION_KEY);
try {
// An actual exception here will cause the JSON serializer to barf, so temporarily sanitize.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,11 @@ public class Validator {
public static final String TIMESTAMP_ZULU_SUFFIX = "Z";
public static final String TIMESTAMP_UTC_SUFFIX_1 = "+00:00";
public static final String TIMESTAMP_UTC_SUFFIX_2 = "+0000";
public static final String ATTRIBUTE_FILE_FORMAT = "%s.attr";
public static final String MESSAGE_FILE_FORMAT = "%s.json";
private static final String SCHEMA_VALIDATION_FORMAT = "Validating %d schemas";
private static final String TARGET_VALIDATION_FORMAT = "Validating %d files against %s";
private static final String DEVICE_FILE_FORMAT = "devices/%s";
private static final String ATTRIBUTE_FILE_FORMAT = "%s.attr";
private static final String MESSAGE_FILE_FORMAT = "%s.json";
private static final String SCHEMA_SKIP_FORMAT = "Unknown schema subFolder '%s' for %s";
private static final String ENVELOPE_SCHEMA_ID = "envelope";
private static final String DEVICE_REGISTRY_ID_KEY = "deviceRegistryId";
Expand Down