From c5940b4fdd4a211b7f0d51c485e75ae2409c874a Mon Sep 17 00:00:00 2001 From: John R Date: Fri, 16 Sep 2022 17:19:09 -0400 Subject: [PATCH] Add endpoint_config_connection_error test (#455) This adds the first test that involves blob configs, specifically around endpoint (re)configuration. --- bin/loop_sequences | 6 +++ bin/pubber | 41 ++++++++-------- docs/specs/sequences/generated.md | 10 ++++ pubber/src/main/java/daq/pubber/Pubber.java | 30 ++++++++---- .../sequencer/sequences/BlobsetSequences.java | 48 +++++++++++++++++++ 5 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 validator/src/main/java/com/google/daq/mqtt/sequencer/sequences/BlobsetSequences.java diff --git a/bin/loop_sequences b/bin/loop_sequences index 8cc644663b..8a934ed6b3 100755 --- a/bin/loop_sequences +++ b/bin/loop_sequences @@ -21,7 +21,13 @@ fi export UDMI_VERSION=`git describe --always --dirty` +if [[ ! -f $VALIDATOR_CONFIG ]]; then + echo Missing $VALIDATOR_CONFIG + false +fi + echo Parsing $VALIDATOR_CONFIG: + cat $VALIDATOR_CONFIG project_id=`jq -r .project_id $VALIDATOR_CONFIG` site_model=`jq -r .site_model $VALIDATOR_CONFIG` diff --git a/bin/pubber b/bin/pubber index af202c6b71..7ae0cb4659 100755 --- a/bin/pubber +++ b/bin/pubber @@ -26,26 +26,23 @@ $ROOT_DIR/pubber/bin/build echo Running tools version `(cd $ROOT_DIR; git describe)` -if (($# == 0)); then - $ROOT_DIR/pubber/bin/run $project_id $site_path $device_id $serial_no -else - declare -A options - for option in $*; do - if [[ $option == *"="* ]]; then - k=$(echo $option | cut -d'=' -f1) - v="\"$(echo $option | cut -d'=' -f2)\"" - else - k=$option - v=true - fi - printf -v options_json '%s"%s":%s,' "$options_json" "$k" "$v" - done - options_json="{${options_json%,}}" - - registry_id=`jq -r .registry_id $site_path/cloud_iot_config.json` - cloud_region=`jq -r .cloud_region $site_path/cloud_iot_config.json` - - cat < /tmp/pubber_config.json +declare -A options +for option in $*; do + if [[ $option == *"="* ]]; then + k=$(echo $option | cut -d'=' -f1) + v="\"$(echo $option | cut -d'=' -f2)\"" + else + k=$option + v=true + fi + printf -v options_json '%s"%s":%s,' "$options_json" "$k" "$v" +done +options_json="{${options_json%,}}" + +registry_id=`jq -r .registry_id $site_path/cloud_iot_config.json` +cloud_region=`jq -r .cloud_region $site_path/cloud_iot_config.json` + +cat < /tmp/pubber_config.json { "endpoint": { "protocol": "mqtt", @@ -58,5 +55,5 @@ else "options": $options_json } EOF - $ROOT_DIR/pubber/bin/run /tmp/pubber_config.json -fi + +$ROOT_DIR/pubber/bin/run /tmp/pubber_config.json diff --git a/docs/specs/sequences/generated.md b/docs/specs/sequences/generated.md index 6a2622e8fc..c16e0c191b 100644 --- a/docs/specs/sequences/generated.md +++ b/docs/specs/sequences/generated.md @@ -29,6 +29,7 @@ Some caveats: * [broken_config](#broken_config): Check that the device correctly handles a broken (non-json) config message. * [device_config_acked](#device_config_acked): Check that the device MQTT-acknowledges a sent config. +* [endpoint_config_connection_error](#endpoint_config_connection_error): Push endpoint config message to device that results in a connection error. * [extra_config](#extra_config): Check that the device correctly handles an extra out-of-schema field * [periodic_scan](#periodic_scan) * [self_enumeration](#self_enumeration) @@ -65,6 +66,15 @@ Check that the device MQTT-acknowledges a sent config. 1. Wait for config acked +## endpoint_config_connection_error + +Push endpoint config message to device that results in a connection error. + +1. Update config: + * Add `blobset` = { "blobs": { } } +1. Wait for device tried endpoint config which resulted in connection error +1. Test failed: timeout waiting for device tried endpoint config which resulted in connection error + ## extra_config Check that the device correctly handles an extra out-of-schema field diff --git a/pubber/src/main/java/daq/pubber/Pubber.java b/pubber/src/main/java/daq/pubber/Pubber.java index c34ee4ff73..c8d7713236 100644 --- a/pubber/src/main/java/daq/pubber/Pubber.java +++ b/pubber/src/main/java/daq/pubber/Pubber.java @@ -165,6 +165,7 @@ public class Pubber { private EndpointConfiguration extractedEndpoint; private SiteModel siteModel; private PrintStream logPrintWriter; + private Entry endpointStatus; /** * Start an instance from a configuration file. @@ -829,16 +830,13 @@ private void processConfigUpdate(Config config) { private void extractEndpointBlobConfig() { if (deviceConfig.blobset == null) { - deviceState.blobset = null; + extractedEndpoint = null; return; } try { String iotConfig = extractConfigBlob(IOT_ENDPOINT_CONFIG.value()); - if (iotConfig == null) { - removeBlobsetBlobState(IOT_ENDPOINT_CONFIG); - return; - } - extractedEndpoint = OBJECT_MAPPER.readValue(iotConfig, EndpointConfiguration.class); + extractedEndpoint = iotConfig == null ? null + : OBJECT_MAPPER.readValue(iotConfig, EndpointConfiguration.class); } catch (Exception e) { throw new RuntimeException("While extracting endpoint blob config", e); } @@ -849,6 +847,10 @@ private void removeBlobsetBlobState(SystemBlobsets blobId) { return; } deviceState.blobset.blobs.remove(blobId.value()); + if (deviceState.blobset.blobs.isEmpty()) { + deviceState.blobset = null; + } + markStateDirty(0); } private void maybeRedirectEndpoint() { @@ -857,25 +859,33 @@ private void maybeRedirectEndpoint() { String extractedSignature = redirectRegistry == null ? toJson(extractedEndpoint) : redirectedEndpoint(redirectRegistry); - if (extractedSignature == null || extractedSignature.equals( - currentSignature) || extractedSignature.equals(attemptedEndpoint)) { + if (extractedSignature == null) { + attemptedEndpoint = null; + removeBlobsetBlobState(IOT_ENDPOINT_CONFIG); + return; + } + + BlobBlobsetState endpointState = ensureBlobsetState(IOT_ENDPOINT_CONFIG); + + if (extractedSignature.equals(currentSignature) + || extractedSignature.equals(attemptedEndpoint)) { return; // No need to redirect anything! } info("New config blob endpoint detected"); - BlobBlobsetState endpointState = ensureBlobsetState(IOT_ENDPOINT_CONFIG); try { + attemptedEndpoint = extractedSignature; endpointState.phase = BlobPhase.APPLY; endpointState.status = null; publishSynchronousState(); - attemptedEndpoint = extractedSignature; resetConnection(extractedSignature); endpointState.phase = BlobPhase.FINAL; appliedEndpoint = null; } catch (Exception e) { try { error("Reconfigure failed, attempting connection to last working endpoint", e); + endpointState.phase = BlobPhase.FINAL; endpointState.status = exceptionStatus(e, Category.BLOBSET_BLOB_APPLY); resetConnection(workingEndpoint); publishAsynchronousState(); diff --git a/validator/src/main/java/com/google/daq/mqtt/sequencer/sequences/BlobsetSequences.java b/validator/src/main/java/com/google/daq/mqtt/sequencer/sequences/BlobsetSequences.java new file mode 100644 index 0000000000..425943ba28 --- /dev/null +++ b/validator/src/main/java/com/google/daq/mqtt/sequencer/sequences/BlobsetSequences.java @@ -0,0 +1,48 @@ +package com.google.daq.mqtt.sequencer.sequences; + +import static udmi.schema.Category.BLOBSET_BLOB_APPLY; + +import com.google.daq.mqtt.sequencer.SequenceRunner; +import java.util.Base64; +import java.util.HashMap; +import org.junit.Test; +import udmi.schema.BlobBlobsetConfig; +import udmi.schema.BlobBlobsetConfig.BlobPhase; +import udmi.schema.BlobsetConfig; +import udmi.schema.BlobsetConfig.SystemBlobsets; +import udmi.schema.Entry; +import udmi.schema.Level; + +/** + * Validation tests for instances that involve blobset config messages. + */ + +public class BlobsetSequences extends SequenceRunner { + + private static final String ENDPOINT_CONFIG_CONNECTION_ERROR_PAYLOAD = + "{ " + + " \"protocol\": \"mqtt\",\n" + + " \"client_id\": \"test_project/device\",\n" + + " \"hostname\": \"localhost\"\n" + + "}"; + + @Test + @Description("Push endpoint config message to device that results in a connection error.") + public void endpoint_config_connection_error() { + BlobBlobsetConfig config = new BlobBlobsetConfig(); + config.phase = BlobPhase.FINAL; + config.base64 = String.valueOf( + Base64.getEncoder().encode(ENDPOINT_CONFIG_CONNECTION_ERROR_PAYLOAD.getBytes())); + config.content_type = "application/json"; + deviceConfig.blobset = new BlobsetConfig(); + deviceConfig.blobset.blobs = new HashMap(); + deviceConfig.blobset.blobs.put(SystemBlobsets.IOT_ENDPOINT_CONFIG.value(), config); + + untilTrue("device tried endpoint config which resulted in connection error", () -> { + Entry stateStatus = deviceState.blobset.blobs.get(SystemBlobsets.IOT_ENDPOINT_CONFIG).status; + return stateStatus.category.equals(BLOBSET_BLOB_APPLY) + && stateStatus.level == Level.ERROR.value(); + }); + } + +}