p2p
This commit is contained in:
@@ -12,7 +12,11 @@
|
||||
wikideck_ingress_host: "{{ invalid_case.host | default('wikideck.example.com') }}"
|
||||
wikideck_public_origin: "{{ invalid_case.origin | default('') }}"
|
||||
wikideck_resources: "{{ invalid_case.resources | default({'requests': {'cpu': '100m', 'memory': '128Mi'}, 'limits': {'cpu': '1', 'memory': '512Mi'}}) }}"
|
||||
wikideck_offline: "{{ invalid_case.offline | default(false) }}"
|
||||
wikideck_peers: "{{ invalid_case.peers | default([]) }}"
|
||||
wikideck_allow_private_peers: "{{ invalid_case.allow_private | default(false) }}"
|
||||
wikideck_startup_probe_failure_threshold: "{{ invalid_case.startup_threshold | default(180) }}"
|
||||
wikideck_network_policy_https_ports: "{{ invalid_case.https_ports | default([443]) }}"
|
||||
wikideck_network_policy_peer_egress: "{{ invalid_case.peer_egress | default([]) }}"
|
||||
wikideck_image_pull_secrets: "{{ invalid_case.pull_secrets | default([]) }}"
|
||||
wikideck_network_policy_ingress_from: "{{ invalid_case.ingress_from | default([{'podSelector': {}}]) }}"
|
||||
block:
|
||||
|
||||
+64
-7
@@ -13,7 +13,9 @@
|
||||
wikideck_image: "{{ render_case.image | default('registry.example.com/wikideck:1.0.0') }}"
|
||||
wikideck_name: "{{ render_case.name | default('wikideck') }}"
|
||||
wikideck_namespace: "{{ render_case.namespace | default('wikideck') }}"
|
||||
wikideck_offline: "{{ render_case.offline | default(false) }}"
|
||||
wikideck_peers: "{{ render_case.peers | default([]) }}"
|
||||
wikideck_allow_private_peers: "{{ render_case.allow_private | default(false) }}"
|
||||
wikideck_startup_probe_failure_threshold: "{{ render_case.startup_threshold | default(180) }}"
|
||||
wikideck_ingress_enabled: "{{ render_case.ingress | default(false) }}"
|
||||
wikideck_ingress_host: wikideck.example.com
|
||||
wikideck_public_origin: "{{ 'https://wikideck.example.com' if render_case.ingress | default(false) else '' }}"
|
||||
@@ -25,9 +27,12 @@
|
||||
wikideck_network_policy_ingress_from: "{{ render_case.ingress_from | default([{'podSelector': {}}]) }}"
|
||||
wikideck_network_policy_dns_to: "{{ render_case.dns_to | default([{'namespaceSelector': {}}]) }}"
|
||||
wikideck_network_policy_https_to: "{{ render_case.https_to | default([{'ipBlock': {'cidr': '0.0.0.0/0'}}]) }}"
|
||||
wikideck_network_policy_https_ports: "{{ render_case.https_ports | default([443]) }}"
|
||||
wikideck_network_policy_peer_egress: "{{ render_case.peer_egress | default([]) }}"
|
||||
loop:
|
||||
- label: defaults
|
||||
- label: isolated validator defaults
|
||||
- label: TLS ingress and existing storage
|
||||
peers: [https://peer-a.example.org, https://peer-b.example.org]
|
||||
ingress: true
|
||||
ingress_class: nginx
|
||||
annotations:
|
||||
@@ -37,15 +42,35 @@
|
||||
pull_secrets: [registry-credentials]
|
||||
name: custom-deck
|
||||
namespace: games
|
||||
- label: offline and no storage class
|
||||
offline: true
|
||||
- label: isolated validator and no storage class
|
||||
storage_class: ""
|
||||
- label: default-deny all traffic
|
||||
ingress_from: []
|
||||
dns_to: []
|
||||
https_to: []
|
||||
- label: DNS-only offline mode
|
||||
offline: true
|
||||
- label: public peers with custom HTTPS port and long startup
|
||||
peers: [https://peer.example.org:8443]
|
||||
https_ports: [443, 8443]
|
||||
startup_threshold: 360
|
||||
- label: private HTTP testnet with restricted egress
|
||||
peers: [http://other-node.games.svc.cluster.local:80]
|
||||
allow_private: true
|
||||
https_to: []
|
||||
peer_egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: games
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/instance: other-node
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 3001
|
||||
- label: public peer with all egress explicitly denied
|
||||
peers: [https://peer.example.org]
|
||||
dns_to: []
|
||||
https_to: []
|
||||
- label: digest-pinned image
|
||||
image: registry.example.com/wikideck@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
loop_control:
|
||||
@@ -84,11 +109,43 @@
|
||||
resources:
|
||||
requests: {cpu: 100m, memory: 128Mi}
|
||||
- label: ambiguous boolean
|
||||
offline: "false"
|
||||
allow_private: "false"
|
||||
- label: wrong list type
|
||||
pull_secrets: not-a-list
|
||||
- label: empty peer allows all traffic
|
||||
ingress_from: [{}]
|
||||
- label: too many peers
|
||||
peers: [https://a.example.org, https://b.example.org, https://c.example.org, https://d.example.org, https://e.example.org, https://f.example.org, https://g.example.org, https://h.example.org, https://i.example.org, https://j.example.org, https://k.example.org, https://l.example.org, https://m.example.org, https://n.example.org, https://o.example.org, https://p.example.org, https://q.example.org]
|
||||
- label: peers must be a list
|
||||
peers: https://peer.example.org
|
||||
- label: HTTP peers need explicit opt-in
|
||||
peers: [http://peer.example.org]
|
||||
- label: credentials in peer URL
|
||||
peers: [https://user:password@peer.example.org]
|
||||
- label: path in peer URL
|
||||
peers: [https://peer.example.org/p2p]
|
||||
- label: query in peer URL
|
||||
peers: ['https://peer.example.org?x=1']
|
||||
- label: comma can inject a second peer
|
||||
peers: ['https://peer.example.org,https://another.example.org']
|
||||
- label: zero startup threshold
|
||||
startup_threshold: 0
|
||||
- label: invalid HTTPS egress port
|
||||
https_ports: [65536]
|
||||
- label: egress rule without destinations
|
||||
peer_egress:
|
||||
- ports: [{protocol: TCP, port: 3001}]
|
||||
- label: egress rule without ports
|
||||
peer_egress:
|
||||
- to: [{podSelector: {}}]
|
||||
- label: egress rule with an empty peer
|
||||
peer_egress:
|
||||
- to: [{}]
|
||||
ports: [{protocol: TCP, port: 3001}]
|
||||
- label: non-TCP peer egress
|
||||
peer_egress:
|
||||
- to: [{podSelector: {}}]
|
||||
ports: [{protocol: UDP, port: 3001}]
|
||||
loop_control:
|
||||
loop_var: invalid_case
|
||||
label: "{{ invalid_case.label }}"
|
||||
|
||||
+33
-2
@@ -36,6 +36,8 @@
|
||||
- container.resources == wikideck_resources
|
||||
- container.ports[0].containerPort == 3001
|
||||
- container.startupProbe.httpGet.path == '/healthz'
|
||||
- container.startupProbe.failureThreshold == wikideck_startup_probe_failure_threshold
|
||||
- container.startupProbe.periodSeconds == 5
|
||||
- container.readinessProbe.httpGet.path == '/healthz'
|
||||
- container.livenessProbe.httpGet.path == '/healthz'
|
||||
- pod.terminationGracePeriodSeconds >= 30
|
||||
@@ -44,7 +46,12 @@
|
||||
- container.volumeMounts[0].mountPath == '/app/data'
|
||||
- env.HOST == '0.0.0.0' and env.PORT == '3001'
|
||||
- env.WIKIDECK_DATA_DIR == '/app/data' and env.NODE_ENV == 'production'
|
||||
- env.WIKIDECK_OFFLINE == ('1' if wikideck_offline else '0')
|
||||
- env.WIKIDECK_PEERS == wikideck_peers | join(',')
|
||||
- env.WIKIDECK_ALLOW_PRIVATE_PEERS == ('1' if wikideck_allow_private_peers else '0')
|
||||
- env.WIKIDECK_OFFLINE is not defined
|
||||
- env.RANDOMX_BINARY is not defined
|
||||
- container.command is not defined and container.args is not defined
|
||||
- pod.containers | length == 1
|
||||
- (env.PUBLIC_ORIGIN | default('')) == wikideck_public_origin
|
||||
- wikideck_test_manifests.pvc.spec.accessModes == wikideck_storage_access_modes
|
||||
- wikideck_test_manifests.ingress.spec.tls[0].secretName == wikideck_tls_secret_name
|
||||
@@ -56,6 +63,8 @@
|
||||
- wikideck_test_manifests.networkpolicy.spec.policyTypes == ['Ingress', 'Egress']
|
||||
- wikideck_test_manifests.networkpolicy.spec.ingress | length == (1 if wikideck_network_policy_ingress_from | length > 0 else 0)
|
||||
- wikideck_test_manifests.networkpolicy.spec.egress | length == expected_egress_count | int
|
||||
- wikideck_resources.requests.memory == '512Mi'
|
||||
- wikideck_resources.limits.memory == '1Gi'
|
||||
vars:
|
||||
deployment: "{{ wikideck_test_manifests.deployment }}"
|
||||
pod: "{{ deployment.spec.template.spec }}"
|
||||
@@ -64,7 +73,29 @@
|
||||
expected_claim: "{{ wikideck_existing_claim if wikideck_existing_claim | length > 0 else wikideck_name ~ '-data' }}"
|
||||
expected_egress_count: >-
|
||||
{{ (1 if wikideck_network_policy_dns_to | length > 0 else 0)
|
||||
+ (1 if not wikideck_offline and wikideck_network_policy_https_to | length > 0 else 0) }}
|
||||
+ (1 if wikideck_peers | length > 0 and wikideck_network_policy_https_to | length > 0
|
||||
and wikideck_network_policy_https_ports | length > 0 else 0)
|
||||
+ (wikideck_network_policy_peer_egress | length if wikideck_peers | length > 0 else 0) }}
|
||||
|
||||
- name: Check public peer egress destinations and ports
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- public_rule.to == wikideck_network_policy_https_to
|
||||
- public_rule.ports | map(attribute='port') | list == wikideck_network_policy_https_ports
|
||||
- public_rule.ports | map(attribute='protocol') | unique | list == ['TCP']
|
||||
vars:
|
||||
public_rule: "{{ wikideck_test_manifests.networkpolicy.spec.egress[1 if wikideck_network_policy_dns_to | length > 0 else 0] }}"
|
||||
when:
|
||||
- wikideck_peers | length > 0
|
||||
- wikideck_network_policy_https_to | length > 0
|
||||
- wikideck_network_policy_https_ports | length > 0
|
||||
|
||||
- name: Check explicit peer egress survives unchanged
|
||||
ansible.builtin.assert:
|
||||
that: wikideck_test_manifests.networkpolicy.spec.egress[-(wikideck_network_policy_peer_egress | length):] == wikideck_network_policy_peer_egress
|
||||
when:
|
||||
- wikideck_peers | length > 0
|
||||
- wikideck_network_policy_peer_egress | length > 0
|
||||
|
||||
- name: Check omitted default StorageClass
|
||||
ansible.builtin.assert:
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Image-level compatibility test only: no mining, wallets, peers, or live-chain writes.
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn, execFileSync } from 'node:child_process';
|
||||
import { once } from 'node:events';
|
||||
import { readFile, access } from 'node:fs/promises';
|
||||
|
||||
assert.equal(process.env.WIKIDECK_DATA_DIR, '/app/data');
|
||||
assert.equal(process.env.WIKIDECK_PEERS, '');
|
||||
assert.ok(process.env.RANDOMX_BINARY, 'Use the updated production image');
|
||||
execFileSync(process.env.RANDOMX_BINARY, ['--self-test'], {
|
||||
timeout: 120_000,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
async function runNode() {
|
||||
const child = spawn(process.execPath, ['--experimental-strip-types', 'server/index.ts'], {
|
||||
cwd: '/app',
|
||||
env: process.env,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
const exited = once(child, 'exit');
|
||||
let output = '';
|
||||
const timeout = setTimeout(() => child.kill('SIGKILL'), 120_000);
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
child.on('error', reject);
|
||||
child.once('exit', (code) => reject(new Error(`Node exited before ready: ${code}\n${output}`)));
|
||||
child.stderr.on('data', (chunk) => { output = (output + chunk).slice(-8192); });
|
||||
child.stdout.on('data', (chunk) => {
|
||||
output = (output + chunk).slice(-8192);
|
||||
if (output.includes('Wikideck RandomX testnet:')) resolve();
|
||||
});
|
||||
});
|
||||
for (const path of ['/healthz', '/p2p/status', '/api/peers']) {
|
||||
const response = await fetch(`http://127.0.0.1:3001${path}`, {
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
});
|
||||
assert.equal(response.status, 200, path);
|
||||
const body = await response.json();
|
||||
if (path === '/healthz') {
|
||||
assert.equal(body.status, 'ok');
|
||||
assert.equal(body.network, 'wikideck-randomx-testnet-2');
|
||||
} else if (path === '/p2p/status') {
|
||||
assert.equal(body.network, 'wikideck-randomx-testnet-2');
|
||||
assert.equal(body.size, 0);
|
||||
assert.match(body.chainId, /^[a-f0-9]{64}$/);
|
||||
} else assert.deepEqual(body.peers, []);
|
||||
}
|
||||
await access('/app/data/testnet-node.lock');
|
||||
const ledger = JSON.parse(await readFile('/app/data/testnet/ledger.json', 'utf8'));
|
||||
assert.equal(ledger.network, 'wikideck-randomx-testnet-2');
|
||||
assert.deepEqual(ledger.blocks, []);
|
||||
child.kill('SIGTERM');
|
||||
assert.deepEqual(await exited, [0, null], output);
|
||||
await assert.rejects(access('/app/data/testnet-node.lock'), { code: 'ENOENT' });
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
if (child.exitCode === null && child.signalCode === null) {
|
||||
child.kill('SIGKILL');
|
||||
await exited;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await runNode();
|
||||
await runNode();
|
||||
console.log('PASS: native RandomX, node API, graceful lock cleanup and empty-ledger restart');
|
||||
Reference in New Issue
Block a user