-
Notifications
You must be signed in to change notification settings - Fork 10
ci: add GitHub Actions workflow with smoke + bacnet-sim-ci integration #44
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
Open
ravz
wants to merge
13
commits into
main
Choose a base branch
from
ci/bacnet-sim-integration
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
e9849d3
ci: add GitHub Actions workflow with smoke + bacnet-sim-ci integration
ravz 730bb68
ci: fix integration test (REST shape + Docker network)
ravz e5c9c85
ci: apply missed REST shape fix + use BACnet port 47808
ravz 787f26b
ci: use bacnet-sim-ci action wrapper (their blessed pattern)
ravz e09798f
ci: use @main (no v1 tag exists on the action repo yet)
ravz 570ffd8
ci: inline docker run for sim (avoid third-party action restrictions)
ravz 6394e0c
ci: mark integration as continue-on-error pending UDP debug
ravz 70aba32
ci: replace bacstack-direct integration with Node-RED + edge-bacnet E2E
ravz 3741324
ci: install edge-bacnet into /data so Node-RED finds its runtime deps
ravz ef26b23
ci: don't override WORKDIR (base image entrypoint is relative)
ravz b2a2d14
ci: --install-links so deps resolve (npm install /local symlinks by d…
ravz 6268a10
ci: add deviceRangeRegisters + portRangeRegisters to flows.json
ravz 5843dfc
ci: pin sim and node-red images by digest for deterministic builds
ravz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| node_modules | ||
| .git | ||
| .github | ||
| .vscode | ||
| .idea | ||
| *.log | ||
| coverage | ||
| docs | ||
| examples | ||
| images | ||
| *.md | ||
| .dockerignore | ||
| .gitignore |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| name: CI | ||
|
|
||
| on: | ||
| push: | ||
| branches: [master] | ||
| pull_request: | ||
|
|
||
| concurrency: | ||
| group: ci-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| smoke: | ||
| name: Syntax + module load | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 20 | ||
| cache: npm | ||
|
|
||
| - run: npm ci | ||
|
|
||
| # node --check on every committed JS file. Catches the trivial breakage | ||
| # class (typos, unmatched braces) that has no other guard in this repo. | ||
| - name: Syntax check | ||
| run: | | ||
| set -euo pipefail | ||
| # Exclude node_modules and the vendored bacstack dist (precompiled). | ||
| mapfile -t files < <(git ls-files '*.js' \ | ||
| | grep -v '^node_modules/' \ | ||
| | grep -v '^resources/node-bacstack-ts/dist/') | ||
| echo "checking ${#files[@]} files" | ||
| for f in "${files[@]}"; do node --check "$f"; done | ||
|
|
||
| - name: Smoke (require-safe modules) | ||
| run: npm run test:unit | ||
|
|
||
| integration: | ||
| name: Node-RED + edge-bacnet vs bacnet-sim-ci | ||
| runs-on: ubuntu-latest | ||
| needs: smoke | ||
| # Pattern from rise-building-technology/bacnet-sim-ci-test: a custom Docker | ||
| # network with explicit container IPs for both sim and client. Both run as | ||
| # peers on the same /24 — no docker-proxy NAT, no runner-host bridge IP | ||
| # confusion. The earlier services:/host-runner/docker-run-on-host topologies | ||
| # all lost the BACnet UDP response packet across the docker-bridge boundary. | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Create test network (172.20.0.0/24) | ||
| run: docker network create --subnet=172.20.0.0/24 bacnet-test-net | ||
|
|
||
| # Pinned by digest for deterministic CI. The upstream has no tagged releases yet | ||
| # (only a rolling :latest), so we capture a known-good digest here. Bump after | ||
| # verifying a newer image works locally. | ||
| - name: Pull and start bacnet-sim | ||
| env: | ||
| SIM_IMAGE: ghcr.io/rise-building-technology/bacnet-sim-ci@sha256:29ed481aa6015dc3508a5aec5b2cb5a69c86bdc2ef22bb7ecb0d75c0d7745963 | ||
| run: | | ||
| set -e | ||
| docker pull "$SIM_IMAGE" | ||
| docker run -d \ | ||
| --name bacnet-sim \ | ||
| --network bacnet-test-net \ | ||
| --ip 172.20.0.10 \ | ||
| --cap-add=NET_ADMIN \ | ||
| -e BACNET_DEVICE_ID=1001 \ | ||
| -e BACNET_DEVICE_NAME=TestDevice \ | ||
| "$SIM_IMAGE" | ||
| for i in $(seq 1 60); do | ||
| if docker exec bacnet-sim curl -sf http://localhost:8099/health/ready >/dev/null 2>&1; then | ||
| echo "sim ready (${i}s)" | ||
| docker exec bacnet-sim curl -s http://localhost:8099/api/devices | ||
| break | ||
| fi | ||
| if [ "$i" -eq 60 ]; then | ||
| echo "ERROR: sim never became ready" | ||
| docker logs bacnet-sim | ||
| exit 1 | ||
| fi | ||
| sleep 1 | ||
| done | ||
|
|
||
| - name: Build Node-RED + edge-bacnet image | ||
| run: docker build -f tests/integration/Dockerfile -t nodered-edgebacnet:test . | ||
|
|
||
| - name: Start Node-RED | ||
| run: | | ||
| docker run -d \ | ||
| --name nodered \ | ||
| --network bacnet-test-net \ | ||
| --ip 172.20.0.20 \ | ||
| nodered-edgebacnet:test | ||
|
|
||
| # The pre-loaded flow has a Bacnet-Gateway with discover_polling_schedule=5s | ||
| # and toLogIam=true. After Node-RED finishes booting and the gateway issues | ||
| # its first Who-Is, the sim should respond with I-Am for device 1001 and | ||
| # the gateway will log "BACnet device found: 1001 - 172.20.0.10". | ||
| - name: Wait for edge-bacnet to discover the sim (device 1001) | ||
| run: | | ||
| set -e | ||
| for i in $(seq 1 90); do | ||
| if docker logs nodered 2>&1 | grep -q "BACnet device found: 1001"; then | ||
| echo "==> Discovery succeeded after ${i}s" | ||
| docker logs nodered 2>&1 | grep "BACnet device found" | head -5 | ||
| exit 0 | ||
| fi | ||
| sleep 1 | ||
| done | ||
| echo "==> ERROR: Did not see 'BACnet device found: 1001' in node-red logs after 90s" | ||
| echo "--- node-red logs ---" | ||
| docker logs nodered | ||
| echo "--- bacnet-sim logs ---" | ||
| docker logs bacnet-sim | ||
| exit 1 | ||
|
|
||
| - name: Diagnostics on failure | ||
| if: failure() | ||
| run: | | ||
| echo "=== docker ps ===" | ||
| docker ps -a | ||
| echo "=== node-red logs ===" | ||
| docker logs nodered 2>&1 | tail -200 || true | ||
| echo "=== bacnet-sim logs ===" | ||
| docker logs bacnet-sim 2>&1 | tail -200 || true | ||
|
|
||
| - name: Cleanup | ||
| if: always() | ||
| run: | | ||
| docker rm -f nodered bacnet-sim 2>/dev/null || true | ||
| docker network rm bacnet-test-net 2>/dev/null || true | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # Node-RED with this branch's @bitpoolos/edge-bacnet installed as a palette. | ||
| # Built from the repo root: `docker build -f tests/integration/Dockerfile -t <tag> .` | ||
| # The build context is the whole repo so we can `npm install` edge-bacnet from local source. | ||
| # Pinned by digest for deterministic CI. Bump after verifying a newer image works. | ||
| FROM nodered/node-red:latest@sha256:a5cb1dcdf90a7148b02b9dba4acfe6bd98c7bf3bff1e845dcdecfd7af3a95a29 | ||
|
|
||
| USER root | ||
| # Copy the source we want to install (the .dockerignore at the repo root keeps | ||
| # node_modules / .git / docs out of the layer). | ||
| COPY --chown=node-red:node-red . /tmp/edge-bacnet | ||
| # Pre-loaded flow with a single Bacnet-Gateway configured to discover devices on | ||
| # the test Docker network. With toLogIam=true the gateway will log | ||
| # "BACnet device found: <deviceId> - <ip>" when it sees an I-Am — that's what | ||
| # the integration step asserts on. | ||
| COPY --chown=node-red:node-red tests/integration/flows.json /data/flows.json | ||
|
|
||
| # Install edge-bacnet AND its runtime deps into /data/node_modules — that's the | ||
| # standard Node-RED palette location and avoids permission issues writing to | ||
| # /usr/src/node-red. Run as the node-red user so the resulting tree is owned | ||
| # correctly at runtime. Don't change WORKDIR — the base image's entrypoint | ||
| # (./entrypoint.sh) is relative to /usr/src/node-red. | ||
| USER node-red | ||
| # --install-links forces npm to TAR + COPY the local package (and resolve its | ||
| # runtime deps into /data/node_modules) instead of symlinking to /tmp/edge-bacnet. | ||
| # Without this, Node-RED loads edge-bacnet from /tmp via the symlink, looks for | ||
| # toad-scheduler etc. relative to /tmp/edge-bacnet/node_modules (which doesn't | ||
| # exist), and fails to register the palette. | ||
| RUN cd /data && npm install --no-audit --no-fund --install-links /tmp/edge-bacnet |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| [ | ||
| { | ||
| "id": "f1", | ||
| "type": "tab", | ||
| "label": "Integration Test", | ||
| "disabled": false, | ||
| "info": "" | ||
| }, | ||
| { | ||
| "id": "gateway1", | ||
| "type": "Bacnet-Gateway", | ||
| "z": "f1", | ||
| "name": "test-gateway", | ||
| "local_device_address": "0.0.0.0", | ||
| "local_interface_name": "", | ||
| "apduTimeout": 6000, | ||
| "maxConcurrentRequests": 250, | ||
| "roundDecimal": 2, | ||
| "local_device_port": 47808, | ||
| "apduSize": "5", | ||
| "maxSegments": "0x50", | ||
| "retries": "5", | ||
| "broadCastAddr": "172.20.0.255", | ||
| "toLogIam": true, | ||
| "discover_polling_schedule": "5", | ||
| "discover_polling_schedule_value": "5", | ||
| "discover_polling_schedule_options": "Seconds", | ||
| "deviceId": 9999, | ||
| "logErrorToConsole": true, | ||
| "serverEnabled": false, | ||
| "device_read_schedule": "30", | ||
| "device_read_schedule_value": "30", | ||
| "device_read_schedule_options": "Seconds", | ||
| "deviceRangeRegisters": [ | ||
| { | ||
| "enabled": true, | ||
| "start": "0", | ||
| "end": "4194303" | ||
| } | ||
| ], | ||
| "portRangeRegisters": [ | ||
| { | ||
| "enabled": true, | ||
| "start": "47808", | ||
| "end": "47808" | ||
| } | ||
| ], | ||
| "cacheFileEnabled": false, | ||
| "sanitise_device_schedule": "60", | ||
| "sanitise_device_schedule_value": "1", | ||
| "sanitise_device_schedule_options": "Hours", | ||
| "enable_device_discovery": true, | ||
| "x": 200, | ||
| "y": 100, | ||
| "wires": [[]] | ||
| } | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| /* | ||
| * Lightweight smoke tests for the require-safe modules. Catches breakage that | ||
| * `node --check` (syntax-only) misses — e.g. a require chain that fails at load | ||
| * time, or a renamed export. | ||
| * | ||
| * The Node-RED factory modules (bacnet_gateway, bacnet_read, bacnet_write, | ||
| * bacnet_inspector, bitpool_inject) export `function(RED)` and cannot be | ||
| * exercised here without a fake RED runtime. They are covered by `node --check` | ||
| * in CI for syntax validity only. | ||
| */ | ||
|
|
||
| let pass = 0; | ||
| let fail = 0; | ||
| function ok(name, cond, info) { | ||
| if (cond) { pass++; console.log(` ok ${name}`); return; } | ||
| fail++; | ||
| console.log(` FAIL ${name}${info ? ' ' + info : ''}`); | ||
| } | ||
|
|
||
| // ---- vendored bacstack loads and exposes the constants we depend on --------- | ||
| const bacnet = require('../../resources/node-bacstack-ts/dist/index.js'); | ||
| ok('bacstack: enum.PropertyIdentifier present', typeof bacnet.enum.PropertyIdentifier === 'object'); | ||
| ok('bacstack: PRESENT_VALUE === 85', bacnet.enum.PropertyIdentifier.PRESENT_VALUE === 85); | ||
| ok('bacstack: ObjectType.ANALOG_VALUE present', typeof bacnet.enum.ObjectType.ANALOG_VALUE === 'number'); | ||
| ok('bacstack: Client constructor exported', typeof bacnet.Client === 'function'); | ||
|
|
||
| // ---- common --------------------------------------------------------------- | ||
| const common = require('../../common.js'); | ||
| ok('common: exports object', typeof common === 'object'); | ||
| ok('common: Read_Config_Sync_Server is callable', typeof common.Read_Config_Sync_Server === 'function'); | ||
| ok('common: Store_Config_Server is callable', typeof common.Store_Config_Server === 'function'); | ||
|
|
||
| // ---- BacnetServer can be required and the class shape is what we expect ---- | ||
| const { BacnetServer } = require('../../bacnet_server.js'); | ||
| ok('BacnetServer: class exported', typeof BacnetServer === 'function'); | ||
| ok('BacnetServer: addObject on prototype', typeof BacnetServer.prototype.addObject === 'function'); | ||
| ok('BacnetServer: getObject on prototype', typeof BacnetServer.prototype.getObject === 'function'); | ||
| ok('BacnetServer: getServerPoints on prototype', typeof BacnetServer.prototype.getServerPoints === 'function'); | ||
|
|
||
| // ---- BacnetDevice and treeBuilder load ------------------------------------- | ||
| const { BacnetDevice } = require('../../bacnet_device.js'); | ||
| ok('BacnetDevice: class exported', typeof BacnetDevice === 'function'); | ||
|
|
||
| const { treeBuilder } = require('../../treeBuilder.js'); | ||
| ok('treeBuilder: function exported', typeof treeBuilder === 'function'); | ||
|
|
||
| // ---- BacnetClient class shape --------------------------------------------- | ||
| // Don't construct it (the constructor binds a UDP socket and starts schedulers). | ||
| const { BacnetClient } = require('../../bacnet_client.js'); | ||
| ok('BacnetClient: class exported', typeof BacnetClient === 'function'); | ||
| ok('BacnetClient: doRead on prototype', typeof BacnetClient.prototype.doRead === 'function'); | ||
| ok('BacnetClient: doWrite on prototype', typeof BacnetClient.prototype.doWrite === 'function'); | ||
|
|
||
| console.log(`\n${pass} passed, ${fail} failed`); | ||
| process.exit(fail === 0 ? 0 : 1); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.