Skip to content

Commit

Permalink
Merge branch 'redirect' of github.com:noursaidi/udmi into writeback_t…
Browse files Browse the repository at this point in the history
…est1
  • Loading branch information
noursaidi committed Sep 20, 2022
2 parents 5d1a6b7 + b88b56a commit d3cee30
Show file tree
Hide file tree
Showing 11 changed files with 364 additions and 2 deletions.
2 changes: 1 addition & 1 deletion _includes/head-custom.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<script>
document.addEventListener("DOMContentLoaded", function() {
document.querySelectorAll("pre>code.language-mermaid").forEach($element => {
$element.parentElement.outerHTML = `<div class="mermaid">${$element.textContent}</div>`
$element.parentElement.outerHTML = `<div class="mermaid">${$element.innerHTML}</div>`
});
mermaid.initialize();
});
Expand Down
7 changes: 6 additions & 1 deletion bin/gencode
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@ if [[ $1 == check ]]; then
shift
fi

if [[ -n $check ]]; then
# This check must be run before gencode_docs is run
bin/gencode_docs_examples check
fi

bin/gencode_java
bin/gencode_python gencode/python schema/*.json
bin/gencode_docs
bin/gencode_seq

if [[ -n $check ]]; then
echo Checking gencode docs links...
bin/gencode_docs check_links
bin/gencode_docs_checklinks

echo "Checking gencode hash (dynamic/static)..."
files=`find gencode/ -type f | sort`
Expand Down
File renamed without changes.
142 changes: 142 additions & 0 deletions bin/gencode_docs_examples
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
""" Inline update documents with JSON payloads from tests directory"""

import glob
import re
import sys
import argparse

from datetime import datetime


REGEX_NORMALIZE = r'(?s)(<!--example:\w+\/\w+.json-->\n)(```json.*?```)'
REGEX_INCLUDE = r'<!--example:(\w+)\/(\w+).json-->\n'


def parse_command_line_args():
parser = argparse.ArgumentParser()

parser.add_argument('check', nargs='?', default=False,
help='Check in-line examples match examples in tests directory')

return parser.parse_args()


def validate_code_blocks(file_contents):
""" Run some very primitive checks to validate templated placeholder code
blocks within file to prevent unwanted deletion of parts of documents. Works
by checking the code blocks for lines which start unlike a JSON line (caused
by mismatched ``` which result in the regex match extending into the of the
document) or the letter F(ile not found)
Arguments
file_contents contents of file
Returns
true/false if file is valid
"""
matches = re.findall(REGEX_NORMALIZE, file_contents)
for match in matches:
code_block_removed = re.sub(r'(?s)```json(.*)```', r'\g<1>', match[1])
if re.search(r'(?m)^[^{}"\sF\/]', code_block_removed):
return False
return True


def read_example_file(match):
""" Used as re.sub callback to read example from a file
Arguments
match match object
Returns
json from example file appended to original expression
"""
expression = match.group(0)
schema = match.group(1)
file = match.group(2)

include_path = f'tests/{schema}.tests/{file}.json'

try:
with open(include_path, 'r', encoding='utf-8') as f:
return f'{expression}```json\n{f.read().rstrip()}\n```'
except FileNotFoundError:
# Append time for not found errors so there's always a diff
return f'{expression}```json\nFILE NOT FOUND ({datetime.now()})\n```'


def include_examples(file_contents):
""" Replaces examples within a string
Arguments
file_contents string to replace in (contents of documentation file)
Returns
string contents of file
"""
normalized_file = re.sub(REGEX_NORMALIZE, r'\g<1>', file_contents)
return re.sub(REGEX_INCLUDE, read_example_file, normalized_file)


def diff_examples(original, updated):
""" Compares documentation before and after external sources are updated
and returns a list of JSON code blocks which are different between the two
Arguments:
original original documentation file contents
updated updated (in memory) documentation file contents
Returns:
list list of expressions (<!--example:metadata/tutorial_hvac_min.json-->)
which differ
"""
diffs = []

matches_original = re.findall(REGEX_NORMALIZE, original)
matches_updated = re.findall(REGEX_NORMALIZE, updated)

for match_original, match_updated in zip(matches_original, matches_updated):
if match_original[1] != match_updated[1]:
diffs.append(match_original[0].rstrip())

return diffs


def main():
file_paths = glob.glob('docs/**/*.md', recursive=True)

args = parse_command_line_args()
example_diffs = {}
error = False

for file_path in file_paths:

with open(file_path, 'r', encoding='utf-8') as f:
file_contents = f.read()

if not validate_code_blocks(file_contents):
error = True
print(f'Error processing {file_path}:')
print('\tMismatched code block termination (```) ',
'or invalid JSON in codeblock')
continue

updated_file_contents = include_examples(file_contents)

if updated_file_contents != file_contents:
if args.check:
example_diffs[file_path] = diff_examples(file_contents,
updated_file_contents)
else:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(updated_file_contents)
if args.check:
if example_diffs:
error = True
print('Examples do not match source in following files:')
for file, diffs in example_diffs.items():
print(f'{file}')
for diff in diffs:
print(f'** {diff}')
else:
print('Documentation in line examples match source examples')
if error:
sys.exit(1)


if __name__ == '__main__':
main()
150 changes: 150 additions & 0 deletions docs/specs/sequences/endpoint_reconfiguration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
[**UDMI**](../../../) / [**Docs**](../../) / [**Specs**](../) / [**Sequences**](./) / [Endpoint Reconfiguration](#)

# Endpoint Reconfiguration

Endpoint reconfiguration is the reconfiguration of the UDMI endpoint through the
UDMI blob delivery mechanisms via UDMI config messages.

The [endpoint configuration blob](https://github.com/faucetsdn/udmi/blob/master/tests/configuration_endpoint.tests/simple.json) is a JSON object defined by
[configuration_endpoint.json](https://faucetsdn.github.io/udmi/gencode/docs/configuration_endpoint.html), which is base64 encoded in the config message.


## Tests

### Valid Endpoint (Successful Reconfiguration)

**Notes**
- `<NEW_ENDPOINT>` is a **base64** encoded endpoint object
- `blobset.blobs_iot_endpoint_config` is present in a device's state message if, and only if, the last received config message has a `blobset.blobs_iot_endpoint_config` block.

```mermaid
%%{wrap}%%
sequenceDiagram
autonumber
participant D as Device
participant E as Original Endpoint
participant E' as New Endpoint
E->>D:CONFIG MESSAGE<br/>blobset.blobs._iot_endpoint_config.base64 = <NEW_ENDPOINT><br/>blobset.blobs._iot_endpoint_config.phase = "final"
D->>E:STATE MESSAGE<br/>blobset.blobs._iot_endpoint_config.phase = "apply"
loop Total duration < 30 seconds
D-->>E':CONNECTION ATTEMPT
end
E'->>D:CONFIG MESSAGE<br>timestamp = T<br/>blobset.blobs._iot_endpoint_config.base64 = <NEW_ENDPOINT><br>blobset.blobs._iot_endpoint_config.phase = "final"
D->>E':STATE MESSAGE<br/>system.last_update = T<br/>blobset.blobs._iot_endpoint_config.phase = "final"
E'->>D:CONFIG MESSAGE<br/>blobset.blobs._iot_endpoint_config = null
D->>E':STATE MESSAGE<br/>blobset.blobs._iot_endpoint_config = null
```

### Invalid Endpoint (Unsuccessful Reconfiguration)

```mermaid
%%{wrap}%%
sequenceDiagram
autonumber
participant D as Device
participant E as Original Endpoint
participant E' as New Endpoint
E->>D:CONFIG MESSAGE<br/>blobset.blobs._iot_endpoint_config.blob = <NEW_ENDPOINT><br>blobset.blobs._iot_endpoint_config.blob.phase = "final"
D->>E:STATE MESSAGE<br/>blobset.blobs._iot_endpoint_config.phase = "apply"
loop Total duration < 30 seconds
D-->>E':CONNECTION ATTEMPT
note over D: Failure, e.g. endpoint cannot be reached, incorrect credentials...
end
D-->>E:CONNECTION ATTEMPT
E->>D:CONFIG MESSAGE
D->>E:STATE MESSAGE<br/>blobset.blobs._iot_endpoint_config.phase = "final"<br/>blobset.blobs._iot_endpoint_config.status.level = 500 (ERROR)<br/>blobset.blobs._iot_endpoint_config.status.category = "blobset.blob.apply"
```

## Message Examples

Config message to initiate Reconfiguration (sequence #1 in diagrams above)
<!--example:config/endpoint_reconfiguration.json-->
```json
{
"version": 1,
"blobset": {
"blobs": {
"_iot_endpoint_config": {
"phase": "final",
"content_type": "application/json",
"base64": "ewogICJwcm90b2NvbCI6ICJtcXR0IiwKICAiY2xpZW50X2lkIjogInByb2plY3RzL2Jvcy1zbm9yay1kZXYvbG9jYXRpb25zL3VzLWNlbnRyYWwxL3JlZ2lzdHJpZXMvWlotVFJJLUZFQ1RBL2RldmljZXMvQUhVLTEiLAogICJob3N0bmFtZSI6ICJtcXR0Lmdvb2dsZWFwaXMuY29tIgp9"
}
}
},
"timestamp": "2022-07-13T12:00:00.000Z"
}
```

The base64 encoded value decodes to:
<!--example:configuration_endpoint/simple.json-->
```json
{
"protocol": "mqtt",
"client_id": "projects/bos-snork-dev/locations/us-central1/registries/ZZ-TRI-FECTA/devices/AHU-1",
"hostname": "mqtt.googleapis.com"
}
```

Example successful state message sent to the new endpoint from device following
a successful reconfiguration
<!--example:state/endpoint_reconfiguration.json-->
```json
{
"version": 1,
"timestamp": "2022-07-13T12:00:10.000Z",
"system": {
"hardware": {
"make": "ACME",
"model": "Bird Trap"
},
"software": {
"firmware": "1.2"
},
"serial_no": "000000",
"last_config": "2022-07-13T12:00:00.000Z",
"operational": true
},
"blobset": {
"blobs": {
"_iot_endpoint_config": {
"phase": "final"
}
}
}
}
```

This is an example of the state message sent to the original endpoint after a failure
<!--example:state/endpoint_reconfiguration_failed.json-->
```json
{
"version": 1,
"timestamp": "2022-07-13T12:00:11.000Z",
"system": {
"hardware": {
"make": "ACME",
"model": "Bird Trap"
},
"software": {
"firmware": "1.2"
},
"serial_no": "000000",
"last_config": "2022-07-13T12:00:00.000Z",
"operational": true
},
"blobset": {
"blobs": {
"_iot_endpoint_config": {
"phase": "final",
"status": {
"message": "Could not connect to new endpoint",
"category": "blobset.blob.apply",
"timestamp": "2022-07-13T12:00:11.000Z",
"level": 500
}
}
}
}
}
```
13 changes: 13 additions & 0 deletions tests/config.tests/endpoint_reconfiguration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"blobset": {
"blobs": {
"_iot_endpoint_config": {
"phase": "final",
"content_type": "application/json",
"base64": "ewogICJwcm90b2NvbCI6ICJtcXR0IiwKICAiY2xpZW50X2lkIjogInByb2plY3RzL2Jvcy1zbm9yay1kZXYvbG9jYXRpb25zL3VzLWNlbnRyYWwxL3JlZ2lzdHJpZXMvWlotVFJJLUZFQ1RBL2RldmljZXMvQUhVLTEiLAogICJob3N0bmFtZSI6ICJtcXR0Lmdvb2dsZWFwaXMuY29tIgp9"
}
}
},
"timestamp": "2022-07-13T12:00:00.000Z"
}
Empty file.
23 changes: 23 additions & 0 deletions tests/state.tests/endpoint_reconfiguration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"version": 1,
"timestamp": "2022-07-13T12:00:10.000Z",
"system": {
"hardware": {
"make": "ACME",
"model": "Bird Trap"
},
"software": {
"firmware": "1.2"
},
"serial_no": "000000",
"last_config": "2022-07-13T12:00:00.000Z",
"operational": true
},
"blobset": {
"blobs": {
"_iot_endpoint_config": {
"phase": "final"
}
}
}
}
Empty file.
29 changes: 29 additions & 0 deletions tests/state.tests/endpoint_reconfiguration_failed.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"version": 1,
"timestamp": "2022-07-13T12:00:11.000Z",
"system": {
"hardware": {
"make": "ACME",
"model": "Bird Trap"
},
"software": {
"firmware": "1.2"
},
"serial_no": "000000",
"last_config": "2022-07-13T12:00:00.000Z",
"operational": true
},
"blobset": {
"blobs": {
"_iot_endpoint_config": {
"phase": "final",
"status": {
"message": "Could not connect to new endpoint",
"category": "blobset.blob.apply",
"timestamp": "2022-07-13T12:00:11.000Z",
"level": 500
}
}
}
}
}
Empty file.

0 comments on commit d3cee30

Please sign in to comment.