Skip to content
Open
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
70 changes: 69 additions & 1 deletion Architecture/Methodology.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,56 @@ Deliverables:
- Test data builders / factories
- CI execution hook

### 6.1 Test Categories (Added)
To ensure consistent quality gates, adopt a layered test pyramid for every feature/module:

| Layer | Purpose | Scope | Frequency |
|-------|---------|-------|-----------|
| Static / Lint | Syntax & style correctness | Whole repo | Every push/PR |
| Unit | Fast logic validation | Single function/class | Every push/PR |
| Component | Interactions of 2-3 modules (in-memory) | Narrow slice | Every push/PR |
| Integration | External boundaries (DB, network, broker) | Real infra or container | Nightly + gated merges |
| Contract | Provider/consumer schema & semantics | API/Events | On provider or schema change |
| Smoke | Sanity of deployable artifact start-up | Minimal runtime | Post-build & pre-deploy |
| End-to-End | Real user path & data flow | Full system | Release candidate |
| Performance | Latency/throughput budgets | Hot paths | Release candidate & quarterly |
| Security | AuthZ, injection, secret handling | Sensitive paths | Release candidate & scheduled |
| Chaos/Resilience | Failure handling, recovery | Selected subsystems | Scheduled |

#### Minimum Required Per Feature
- Unit tests for new pure logic (>=80% function coverage for changed lines)
- One component test if cross-module interaction added
- Smoke test ensuring startup of new daemon/CLI entry point
- Contract test when altering externally consumed format (CLI JSON, API, event schema)

#### Test Naming Conventions
```
<scope>/test_<unit_under_test>__<expected_behavior>.py
Examples:
unit/test_role_assigner__selects_first_management_server.py
component/test_peer_identity_flow__consensus_reached.py
smoke/test_agent_startup__exits_zero_help.py
```

#### Execution Profiles
- Quick suite (CI default): unit + component + smoke (<60s target)
- Full suite (nightly): + integration + contract + e2e

#### Skipping & Markers (Pytest)
```python
import pytest
@pytest.mark.slow
@pytest.mark.integration
@pytest.mark.contract
```
CI filters: `pytest -m "not slow"` for quick pass.

#### Flakiness Policy
Any flaky test must be:
1. Quarantined via marker `@pytest.mark.flaky` within 24h
2. Issue created with reproduction details
3. Fixed or removed within 72h

## 7. Core Business Logic (Happy Path)
Implement pure logic first.
- Keep logic side-effect free where possible
Expand Down Expand Up @@ -202,6 +252,16 @@ Deliverables:
- Performance: PASS/FAIL (budget adherence)
- Documentation: PASS/FAIL (operator readiness)

### Automated Quality Gate Mapping (Added)
| Gate | Enforcement | Tooling | Threshold |
|------|-------------|---------|-----------|
| Build | CI job `build` | compiler + shellcheck | 0 errors |
| Unit Coverage | CI job `test` | pytest + coverage | >= 75% changed lines |
| Lint | CI job `lint` | ruff/flake8/shellcheck | 0 errors (warnings allowed) |
| Security | CI job `security` | bandit + secret scan | 0 HIGH findings |
| Smoke | CI job `smoke` | startup script & py_compile | 100% pass |
| Performance (target) | scheduled | locust/k6/JMH | baseline recorded |

---
## CloudStack / VNF Mapping Quick Reference
- API Contract → Commands (`execute()` wraps errors)
Expand Down Expand Up @@ -273,4 +333,12 @@ Always prefer delivering one working end-to-end path over 20 partially complete
If a change cannot be validated (run, test, observed), it isn't progress.

---
*Last updated: 2025-11-07*
## Test Implementation Checklist (Added)
Before merging any feature branch:
- [ ] Unit tests cover new decision branches
- [ ] Smoke test validates daemon/CLI starts (help, py_compile)
- [ ] Negative test included for at least one failure mode
- [ ] Secret handling validated (no accidental print of shared_secret)
- [ ] HMAC logic verified (sign + tamper fail)

*Last updated: 2025-11-08*
9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[project]
name = "build-hive"
version = "0.0.1"
requires-python = ">=3.11"
dependencies = []

[tool.pytest.ini_options]
addopts = "-q --disable-warnings"
pythonpath = ["scripts/bootstrap/ubuntu24"]
73 changes: 73 additions & 0 deletions scripts/bootstrap/ubuntu24/hive_lib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Hive shared utilities for unit testing and logic reuse.
Includes role suggestion logic and message signing/validation helpers.
"""
from collections import Counter
import json, hmac, hashlib

MAGIC = "BUILD/HELLO/v1"


def suggest_cloudstack_role(peers, vnf_available=False):
"""Suggest a role based on current peers.
peers: list of dicts with 'role' or nested identity role
vnf_available: bool indicating if a VNF broker is reachable
Returns dict: {role, packages, config, reason}
"""
roles = Counter((p.get('role') or (p.get('identity') or {}).get('role') or 'unknown') for p in peers)
if roles.get('management-server', 0) == 0:
return {
'role': 'management-server',
'packages': ['mysql-server','nfs-kernel-server','openjdk-17-jdk','git'],
'config': {'db': 'mysql', 'nfs': True},
'reason': 'First management server needed'
}
if roles.get('cloudstack-builder', 0) < 2:
return {
'role': 'cloudstack-builder',
'packages': ['openjdk-17-jdk','maven','git','build-essential'],

Copilot AI Nov 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces after commas in list literal. Per PEP 8, add spaces after commas: ['openjdk-17-jdk', 'maven', 'git', 'build-essential'].

Suggested change
'packages': ['openjdk-17-jdk','maven','git','build-essential'],
'packages': ['openjdk-17-jdk', 'maven', 'git', 'build-essential'],

Copilot uses AI. Check for mistakes.
'config': {'build_threads': 8},
'reason': 'Increase build capacity'
}
if roles.get('kvm-hypervisor', 0) == 0:
return {
'role': 'kvm-hypervisor',
'packages': ['qemu-kvm','libvirt-daemon-system','bridge-utils'],

Copilot AI Nov 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces after commas in list literal. Per PEP 8, add spaces after commas: ['qemu-kvm', 'libvirt-daemon-system', 'bridge-utils'].

Suggested change
'packages': ['qemu-kvm','libvirt-daemon-system','bridge-utils'],
'packages': ['qemu-kvm', 'libvirt-daemon-system', 'bridge-utils'],

Copilot uses AI. Check for mistakes.
'config': {'virt_network': 'default'},
'reason': 'Provide hypervisor for functional tests'
}
if vnf_available and roles.get('vnf-tester', 0) == 0:
return {
'role': 'vnf-tester',
'packages': ['python3-requests','docker.io'],

Copilot AI Nov 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces after commas in list literal. Per PEP 8, add spaces after commas: ['python3-requests', 'docker.io'].

Suggested change
'packages': ['python3-requests','docker.io'],
'packages': ['python3-requests', 'docker.io'],

Copilot uses AI. Check for mistakes.
'config': {'broker_port': 8443},
'reason': 'Validate VNF broker operations'
}
return {
'role': 'test-runner',
'packages': ['python3-pytest','curl','git'],
Comment on lines +21 to +48

Copilot AI Nov 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces after commas in list literal. Per PEP 8, add spaces after commas: ['mysql-server', 'nfs-kernel-server', 'openjdk-17-jdk', 'git'].

Suggested change
'packages': ['mysql-server','nfs-kernel-server','openjdk-17-jdk','git'],
'config': {'db': 'mysql', 'nfs': True},
'reason': 'First management server needed'
}
if roles.get('cloudstack-builder', 0) < 2:
return {
'role': 'cloudstack-builder',
'packages': ['openjdk-17-jdk','maven','git','build-essential'],
'config': {'build_threads': 8},
'reason': 'Increase build capacity'
}
if roles.get('kvm-hypervisor', 0) == 0:
return {
'role': 'kvm-hypervisor',
'packages': ['qemu-kvm','libvirt-daemon-system','bridge-utils'],
'config': {'virt_network': 'default'},
'reason': 'Provide hypervisor for functional tests'
}
if vnf_available and roles.get('vnf-tester', 0) == 0:
return {
'role': 'vnf-tester',
'packages': ['python3-requests','docker.io'],
'config': {'broker_port': 8443},
'reason': 'Validate VNF broker operations'
}
return {
'role': 'test-runner',
'packages': ['python3-pytest','curl','git'],
'packages': ['mysql-server', 'nfs-kernel-server', 'openjdk-17-jdk', 'git'],
'config': {'db': 'mysql', 'nfs': True},
'reason': 'First management server needed'
}
if roles.get('cloudstack-builder', 0) < 2:
return {
'role': 'cloudstack-builder',
'packages': ['openjdk-17-jdk', 'maven', 'git', 'build-essential'],
'config': {'build_threads': 8},
'reason': 'Increase build capacity'
}
if roles.get('kvm-hypervisor', 0) == 0:
return {
'role': 'kvm-hypervisor',
'packages': ['qemu-kvm', 'libvirt-daemon-system', 'bridge-utils'],
'config': {'virt_network': 'default'},
'reason': 'Provide hypervisor for functional tests'
}
if vnf_available and roles.get('vnf-tester', 0) == 0:
return {
'role': 'vnf-tester',
'packages': ['python3-requests', 'docker.io'],
'config': {'broker_port': 8443},
'reason': 'Validate VNF broker operations'
}
return {
'role': 'test-runner',
'packages': ['python3-pytest', 'curl', 'git'],

Copilot uses AI. Check for mistakes.

Copilot AI Nov 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces after commas in list literal. Per PEP 8, add spaces after commas: ['python3-pytest', 'curl', 'git'].

Suggested change
'packages': ['python3-pytest','curl','git'],
'packages': ['python3-pytest', 'curl', 'git'],

Copilot uses AI. Check for mistakes.
'config': {'parallel': 4},
'reason': 'Expand test execution capacity'
}


def sign(payload_bytes: bytes, secret: bytes) -> str:
"""HMAC-SHA256 sign a bytes payload with secret; returns hex digest"""
if not secret:
return ''
return hmac.new(secret, payload_bytes, hashlib.sha256).hexdigest()


def valid(message: dict, secret: bytes, magic: str = MAGIC) -> bool:
"""Validate message envelope of form {magic, payload, sig} using secret."""
if message.get('magic') != magic:
return False
pl = message.get('payload')
sig = message.get('sig', '')
if not isinstance(pl, dict):
return False
raw = json.dumps(pl, separators=(',',':')).encode('utf-8')

Copilot AI Nov 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma in tuple literal. Per PEP 8, add space after comma: separators=(',', ':').

Suggested change
raw = json.dumps(pl, separators=(',',':')).encode('utf-8')
raw = json.dumps(pl, separators=(', ', ':')).encode('utf-8')

Copilot uses AI. Check for mistakes.
if secret:
mac = hmac.new(secret, raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(mac, sig)
return True
5 changes: 5 additions & 0 deletions scripts/test/run_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
python3 -m pip install --upgrade pip >/dev/null 2>&1 || true
python3 -m pip install pytest >/dev/null 2>&1 || true
pytest -q
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# tests package
26 changes: 26 additions & 0 deletions tests/smoke/test_smoke_scripts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import subprocess, sys


def run(cmd):
return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)


def test_py_compile_scripts():
for path in [
'scripts/bootstrap/ubuntu24/peer_agent.py',
'scripts/bootstrap/ubuntu24/advisor.py',
'scripts/bootstrap/ubuntu24/message_bridge.py',
]:
r = run([sys.executable, '-m', 'py_compile', path])
assert r.returncode == 0, f"py_compile failed for {path}: {r.stderr}"


def test_advisor_help_exits_zero():
r = run([sys.executable, 'scripts/bootstrap/ubuntu24/advisor.py', '--help'])
assert r.returncode == 0
assert 'usage' in (r.stdout + r.stderr).lower()


def test_message_bridge_runs():
r = run([sys.executable, 'scripts/bootstrap/ubuntu24/message_bridge.py', 'identity_assigned', '{"hostname":"x","role":"y"}'])
assert r.returncode == 0
42 changes: 42 additions & 0 deletions tests/test_hive_lib_roles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from scripts.bootstrap.ubuntu24.hive_lib import suggest_cloudstack_role


def test_first_management_server():
peers = []
advice = suggest_cloudstack_role(peers)
assert advice['role'] == 'management-server'
assert 'mysql-server' in advice['packages']


def test_second_role_cloudstack_builder_needed():
peers = [ {'role': 'management-server'} ]
advice = suggest_cloudstack_role(peers)
assert advice['role'] == 'cloudstack-builder'
assert 'maven' in advice['packages']


def test_hypervisor_after_two_builders():
peers = [ {'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'} ]
advice = suggest_cloudstack_role(peers)
assert advice['role'] == 'kvm-hypervisor'
assert 'qemu-kvm' in advice['packages']


def test_vnf_tester_when_broker_available():
peers = [ {'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'}, {'role': 'kvm-hypervisor'} ]

Copilot AI Nov 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent spacing in list literal. Remove spaces after [ and before ] for consistency with PEP 8: [{'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'}, {'role': 'kvm-hypervisor'}].

Suggested change
peers = [ {'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'}, {'role': 'kvm-hypervisor'} ]
peers = [{'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'}, {'role': 'kvm-hypervisor'}]

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +26

Copilot AI Nov 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent spacing in list literal. Remove spaces after [ and before ] for consistency with PEP 8: [{'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'}].

Suggested change
peers = [ {'role': 'management-server'} ]
advice = suggest_cloudstack_role(peers)
assert advice['role'] == 'cloudstack-builder'
assert 'maven' in advice['packages']
def test_hypervisor_after_two_builders():
peers = [ {'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'} ]
advice = suggest_cloudstack_role(peers)
assert advice['role'] == 'kvm-hypervisor'
assert 'qemu-kvm' in advice['packages']
def test_vnf_tester_when_broker_available():
peers = [ {'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'}, {'role': 'kvm-hypervisor'} ]
peers = [{'role': 'management-server'}]
advice = suggest_cloudstack_role(peers)
assert advice['role'] == 'cloudstack-builder'
assert 'maven' in advice['packages']
def test_hypervisor_after_two_builders():
peers = [{'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'}]
advice = suggest_cloudstack_role(peers)
assert advice['role'] == 'kvm-hypervisor'
assert 'qemu-kvm' in advice['packages']
def test_vnf_tester_when_broker_available():
peers = [{'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'}, {'role': 'kvm-hypervisor'}]

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +26

Copilot AI Nov 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent spacing in list literals. Lines 12, 19, and 26 have spaces after [ and before ] (e.g., [ {...} ]), while line 5 uses no spaces ([]). For consistency with PEP 8 and the multi-line list at lines 33-39, remove the extra spaces: [{'role': 'management-server'}].

Suggested change
peers = [ {'role': 'management-server'} ]
advice = suggest_cloudstack_role(peers)
assert advice['role'] == 'cloudstack-builder'
assert 'maven' in advice['packages']
def test_hypervisor_after_two_builders():
peers = [ {'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'} ]
advice = suggest_cloudstack_role(peers)
assert advice['role'] == 'kvm-hypervisor'
assert 'qemu-kvm' in advice['packages']
def test_vnf_tester_when_broker_available():
peers = [ {'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'}, {'role': 'kvm-hypervisor'} ]
peers = [{'role': 'management-server'}]
advice = suggest_cloudstack_role(peers)
assert advice['role'] == 'cloudstack-builder'
assert 'maven' in advice['packages']
def test_hypervisor_after_two_builders():
peers = [{'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'}]
advice = suggest_cloudstack_role(peers)
assert advice['role'] == 'kvm-hypervisor'
assert 'qemu-kvm' in advice['packages']
def test_vnf_tester_when_broker_available():
peers = [{'role': 'management-server'}, {'role': 'cloudstack-builder'}, {'role': 'cloudstack-builder'}, {'role': 'kvm-hypervisor'}]

Copilot uses AI. Check for mistakes.
advice = suggest_cloudstack_role(peers, vnf_available=True)
assert advice['role'] == 'vnf-tester'
assert 'docker.io' in advice['packages']


def test_default_test_runner_when_all_present():
peers = [
{'role': 'management-server'},
{'role': 'cloudstack-builder'},
{'role': 'cloudstack-builder'},
{'role': 'kvm-hypervisor'},
{'role': 'vnf-tester'},
]
advice = suggest_cloudstack_role(peers)
assert advice['role'] == 'test-runner'
assert any('pytest' in p for p in advice['packages'])
33 changes: 33 additions & 0 deletions tests/test_hive_lib_signing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from scripts.bootstrap.ubuntu24.hive_lib import sign, valid, MAGIC
import json

SECRET = b'supersecret'


def make_payload():
return {'kind': 'HELLO', 'node_id': 'x', 'hostname': 'h', 'primary_ip': '1.2.3.4', 'candidate_ip': None, 'ts': '2025-01-01T00:00:00Z'}


def test_sign_and_valid_roundtrip():
pl = make_payload()
raw = json.dumps(pl, separators=(',',':')).encode('utf-8')

Copilot AI Nov 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma in tuple literal. Per PEP 8, add space after comma: separators=(',', ':').

Copilot uses AI. Check for mistakes.
sig = sign(raw, SECRET)
envelope = {'magic': MAGIC, 'payload': pl, 'sig': sig}
assert valid(envelope, SECRET) is True


def test_invalid_magic():
pl = make_payload()
raw = json.dumps(pl, separators=(',',':')).encode('utf-8')

Copilot AI Nov 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma in tuple literal. Per PEP 8, add space after comma: separators=(',', ':').

Copilot uses AI. Check for mistakes.
sig = sign(raw, SECRET)
envelope = {'magic': 'WRONG', 'payload': pl, 'sig': sig}
assert valid(envelope, SECRET) is False


def test_tampered_payload():
pl = make_payload()
raw = json.dumps(pl, separators=(',',':')).encode('utf-8')

Copilot AI Nov 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma in tuple literal. Per PEP 8, add space after comma: separators=(',', ':').

Copilot uses AI. Check for mistakes.
sig = sign(raw, SECRET)
pl['hostname'] = 'evil'
envelope = {'magic': MAGIC, 'payload': pl, 'sig': sig}
assert valid(envelope, SECRET) is False
Loading