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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ All notable changes to this project will be documented in this file.
- Add `--log-level <LEVEL>` global flag and initialize the `tracing` subscriber at startup. `LEVEL` is one of `off`, `error`, `warn` (default), `info`, `debug`, `trace`. Diagnostic logs go to stderr so `--json` output on stdout remains parseable. Honors the `RUST_LOG` environment variable when set, overriding the CLI-flag level for per-module filtering. Replaces the previous `println!("using keypair: ...")` stdout line with a `tracing::info!` event; the keypair confirmation now appears only at `--log-level info` or higher and no longer pollutes parseable stdout. (Named `--log-level` rather than the RFC-20 §Global-flags suggested `--verbose` / `-v` because the existing `doublezero connect` / `disconnect` subcommands already own a `--verbose` flag with `bool` type; the global flag deviation will be revisited when the daemon-control module crate is carved out.)
- Build a `CliContext` once at binary startup from `--env`, the per-field global overrides (`--url`, `--ws`, `--solana-url`, `--program-id`, `--geo-program-id`, `--keypair`, `--sock-file`), and the persisted `~/.config/doublezero/cli/config.yml` (overridable via `DOUBLEZERO_CONFIG_FILE`), per RFC-20 (§CliContext). Precedence (highest wins): CLI flag > persisted config > env-derived default. When `--env` is not set and the persisted config has a serviceability program ID, the environment is derived from that program ID via `Environment::from_program_id`; otherwise the binary falls back to `Environment::default()`. The legacy `DZClient` is now constructed from the fully resolved `CliContext` URL, WebSocket, and program-ID values directly, so verbs that migrate to read `CliContext` see the same backend as the legacy bridge. Keypair resolution is intentionally left to `DZClient::new`'s internal `load_keypair` precedence (CLI `--keypair` flag > `DOUBLEZERO_KEYPAIR` env var > stdin > persisted config) so the `DOUBLEZERO_KEYPAIR` env var continues to override the persisted keypair path, as relied on by the e2e contributor-auth negative-authz suite. File reads happen only in the binary; module crates remain forbidden from touching the filesystem (RFC-20 §67).
- Centralize top-level error rendering through `doublezero_cli_core::error::render_eyre`. Replaces three ad-hoc `eprintln!("Error: {e}")` sites in `client/doublezero/src/main.rs` (env-parse failure, env-config resolution failure, top-level command failure) with a single helper that prints `Error: <head>` followed by the full chain of causes on stderr.
- SDK (Go)
- Add `CreateUser` (instruction variant 36) and `DeleteUser` (variant 42) to the serviceability executor. Account ordering mirrors the Rust SDK at `smartcontract/sdk/rs/src/commands/user/{create,delete}.rs`; the borsh-encoded payload matches Rust's `UserCreateArgs` / `UserDeleteArgs` exactly. Both methods wait for the user PDA to become visible (or disappear) on-chain after finalization so callers can record a meaningful `t_activate` against the operation. `UserCreateArgs` bundles the borsh-encoded fields with `DevicePubkey` / optional `TenantPubkey` for account derivation. Introduces `GetUserPDA`, `GetAccessPassPDA`, `GetTunnelIdsPDA`, `GetDzPrefixBlockPDA` helpers in `pda.go`. Cross-language wire format is locked down by new Rust-generated `user_create_args.{bin,json}` and `user_delete_args.{bin,json}` fixtures that the Go tests load via the existing fixture pipeline ([#3770](https://github.com/malbeclabs/doublezero/issues/3770)).
- Tools
- Add `tools/stress/device-orchestrator/` — the device-stress orchestrator skeleton for the GRE Tunnel Capacity Study. The binary parses every flag from #3746's CLI list, dumps `orchestrator-config.json` on start, runs a provision-then-reverse-deprovision sweep against a live serviceability program, and emits the runlog row schema `{run_id, user_index, user_pubkey, tunnel_id, event, t_ns, n_after_event}` to `orchestrator-runlog.json` for each `submit | confirm | activate | deprovision_*` event. The agent runner is stubbed behind a `pkg/agent.Runner` interface (no-op impl ships now; the SSH-backed runner that emits `pre_commit_log` / `applied` lands in part 3). The sweep cooperates with an abort sentinel file: when the file appears the in-flight user completes and the orchestrator deprovisions everything it created before exiting non-zero. `PlanReconcile` / `Plan` (lifted from the part-1 SDK PR) now lives at `tools/stress/device-orchestrator/pkg/reconcile/` as orchestrator policy rather than SDK primitive. Part 2 of #3746 ([#3771](https://github.com/malbeclabs/doublezero/issues/3771)).
- Complete the device-stress orchestrator with the SSH agent runner and log parser. `pkg/agent/ssh.go` dials `--dut-ssh-host` with `--dut-ssh-key`, execs `doublezero-agent -verbose` (appending `--controller` when set), and tees remote stdout/stderr into `<working-dir>/orchestrator.agent.log` while feeding the stream through `pkg/agent/parser.go`. The parser tracks two log lines from `controlplane/agent/pkg/arista/eapi.go`: `Committing config session due to diffs detected: <diff>` (extracting `+ interface Tunnel<ID>` matches and emitting one `pre_commit_log` event per ID) and `Configuration session finalized with command '... commit'` (emitting one `applied` event per pending tunnel; the `... abort` variant clears the buffer without emitting). The sweep grows a goroutine that consumes agent events and writes `pre_commit_log` / `applied` runlog rows by looking up each event's tunnel ID against a `tunnelID → user_index` map populated as users are created; unknown tunnels are debug-logged and dropped. `pkg/exec.fetchTunnelID` now reads the on-chain user account post-create to surface the assigned `TunnelId` into the runlog. New CLI flags: `--dut-ssh-user` (default `admin`) and `--no-agent` for offline testing. Host-key verification uses `ssh.InsecureIgnoreHostKey` because the orchestrator targets ephemeral cEOS containers; documented at `pkg/agent/ssh.go:SSH`. Part 3 of #3746, completes the five-event coverage ([#3772](https://github.com/malbeclabs/doublezero/issues/3772)).

## [v0.24.0](https://github.com/malbeclabs/doublezero/compare/client/v0.23.0...client/v0.24.0) - 2026-05-22

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ require (
github.com/twmb/franz-go/pkg/kadm v1.17.1
github.com/vishvananda/netlink v1.3.1
github.com/vishvananda/netns v0.0.5
golang.org/x/crypto v0.49.0
golang.org/x/mod v0.33.0
golang.org/x/net v0.52.0
golang.org/x/sync v0.20.0
Expand Down Expand Up @@ -193,7 +194,6 @@ require (
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect
golang.org/x/term v0.41.0 // indirect
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 57 additions & 0 deletions sdk/serviceability/testdata/fixtures/generate-fixtures/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ use borsh::BorshSerialize;

use doublezero_serviceability::id_allocator::IdAllocator;
use doublezero_serviceability::ip_allocator::IpAllocator;
use doublezero_serviceability::processors::user::{
create::UserCreateArgs, delete::UserDeleteArgs,
};
use doublezero_serviceability::programversion::ProgramVersion;
use doublezero_serviceability::state::{
accesspass::{AccessPass, AccessPassStatus, AccessPassType},
Expand Down Expand Up @@ -95,11 +98,65 @@ fn main() {
generate_tenant(&fixtures_dir);
generate_resource_extension_id(&fixtures_dir);
generate_resource_extension_ip(&fixtures_dir);
generate_user_create_args(&fixtures_dir);
generate_user_delete_args(&fixtures_dir);

println!("
all fixtures generated in {}", fixtures_dir.display());
}

/// Borsh-encoded `UserCreateArgs` (the body of instruction variant 36, without the
/// 1-byte discriminant). Field order: user_type, cyoa_type, client_ip, tunnel_endpoint,
/// dz_prefix_count. Non-default IP octets make endianness mistakes detectable.
fn generate_user_create_args(dir: &Path) {
let val = UserCreateArgs {
user_type: UserType::IBRL,
cyoa_type: UserCYOA::GREOverDIA,
client_ip: Ipv4Addr::new(10, 11, 12, 13),
tunnel_endpoint: Ipv4Addr::new(192, 168, 1, 2),
dz_prefix_count: 2,
};

let data = borsh::to_vec(&val).unwrap();

let meta = FixtureMeta {
name: "UserCreateArgs".into(),
// Not an account; account_type=0 since this is an instruction-args fixture.
account_type: 0,
fields: vec![
FieldValue { name: "UserType".into(), value: "0".into(), typ: "u8".into() },
FieldValue { name: "CyoaType".into(), value: "1".into(), typ: "u8".into() },
FieldValue { name: "ClientIp".into(), value: "10.11.12.13".into(), typ: "ipv4".into() },
FieldValue { name: "TunnelEndpoint".into(), value: "192.168.1.2".into(), typ: "ipv4".into() },
FieldValue { name: "DzPrefixCount".into(), value: "2".into(), typ: "u8".into() },
],
};

write_fixture(dir, "user_create_args", &data, &meta);
}

/// Borsh-encoded `UserDeleteArgs` (the body of instruction variant 42, without the
/// 1-byte discriminant). Field order: dz_prefix_count, multicast_publisher_count.
fn generate_user_delete_args(dir: &Path) {
let val = UserDeleteArgs {
dz_prefix_count: 3,
multicast_publisher_count: 1,
};

let data = borsh::to_vec(&val).unwrap();

let meta = FixtureMeta {
name: "UserDeleteArgs".into(),
account_type: 0,
fields: vec![
FieldValue { name: "DzPrefixCount".into(), value: "3".into(), typ: "u8".into() },
FieldValue { name: "MulticastPublisherCount".into(), value: "1".into(), typ: "u8".into() },
],
};

write_fixture(dir, "user_delete_args", &data, &meta);
}

fn generate_global_state(dir: &Path) {
let foundation_pk = pubkey_from_byte(0x01);
let activator_pk = pubkey_from_byte(0x02);
Expand Down
Binary file not shown.
31 changes: 31 additions & 0 deletions sdk/serviceability/testdata/fixtures/user_create_args.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "UserCreateArgs",
"account_type": 0,
"fields": [
{
"name": "UserType",
"value": "0",
"typ": "u8"
},
{
"name": "CyoaType",
"value": "1",
"typ": "u8"
},
{
"name": "ClientIp",
"value": "10.11.12.13",
"typ": "ipv4"
},
{
"name": "TunnelEndpoint",
"value": "192.168.1.2",
"typ": "ipv4"
},
{
"name": "DzPrefixCount",
"value": "2",
"typ": "u8"
}
]
}
1 change: 1 addition & 0 deletions sdk/serviceability/testdata/fixtures/user_delete_args.bin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

16 changes: 16 additions & 0 deletions sdk/serviceability/testdata/fixtures/user_delete_args.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "UserDeleteArgs",
"account_type": 0,
"fields": [
{
"name": "DzPrefixCount",
"value": "3",
"typ": "u8"
},
{
"name": "MulticastPublisherCount",
"value": "1",
"typ": "u8"
}
]
}
Loading
Loading