Skip to content

Server Native YAML Integration.#96

Open
dwelch-spike wants to merge 15 commits into
mainfrom
server-yaml
Open

Server Native YAML Integration.#96
dwelch-spike wants to merge 15 commits into
mainfrom
server-yaml

Conversation

@dwelch-spike

@dwelch-spike dwelch-spike commented Feb 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds first-class support in asconfig for the server-native (experimental) YAML format introduced in Aerospike 8.1.0. A single context-sensitive --server-yaml flag toggles the format for whichever side of the command is YAML, validation runs against the new experimental schemas before any translation occurs, and the legacy asconfig YAML shape remains the default so existing workflows are unchanged.

All native-format logic lives in a new conf/serveryaml package so it can be lifted into aerospike-management-lib once the library adopts the native shape.

Motivation

Previously the new format was exposed through two clunky flags (--server-yaml and --server-yaml-output), and all "validation" happened against the legacy schema after translation — so the experimental schemas weren't actually enforced and, worse, native-only fields would be rejected by the legacy validator forcing users to reach for --force. We want:

  • A single, predictable flag with context-sensitive meaning.
  • Real schema validation against the experimental schemas (schema/schemas/json/aerospike-server/*.json).
  • Backwards compatibility: the legacy YAML shape stays the default until Aerospike 9.0.0 makes the native format the default on the server side.
  • Portable code that can move into aerospike-management-lib with minimal rework.

What changed

One flag, context-sensitive

--server-yaml is now a single flag available on validate, convert, diff files, diff server, and generate. It applies to whichever side of the operation is YAML:

Scenario Effect
Reading YAML (validate, convert yaml -> conf, diff *) Input is parsed as server-native, validated against the experimental schema, then translated to legacy for the management-lib path.
Writing YAML (convert conf -> yaml, generate -F yaml) Output is emitted in server-native shape (map-keyed namespaces, logging type+contexts, {value, unit} sizes, etc.).
The relevant side isn't YAML The flag is a silent no-op on that side, so e.g. convert --server-yaml conf -> yaml and convert --server-yaml yaml -> conf both work with a single flag.

The old --server-yaml-output flag has been removed.

Version gating

  • validate, convert, diff server, and generate require aerospike-server-version >= 8.1.0 when --server-yaml is set. Patch versions are ignored, so 8.1.0, 8.1.1, 8.1.2, ... all qualify. Missing or below-cutoff versions produce a hard error instead of silently falling back.
  • diff server fetches the cluster version first, then applies the gate to the local file.
  • diff files is version-agnostic and does no schema validation. --server-yaml only signals that the input is in the native shape so asconfig can translate it for the diff.

New conf/serveryaml package

All native-format logic is consolidated into one self-contained package, ready to be lifted into aerospike-management-lib:

  • format.goMinSupportedVersion = "8.1.0" and IsSupportedVersion.
  • schemas.go — embeds the experimental schemas, resolves the best schema for a given version (exact match -> highest version <= target -> lowest in the same minor), and sanitizes gojsonschema-incompatible regex patterns at load time.
  • validate.goValidate(yamlBytes, version) against the experimental schema, returning ValidationError values formatted consistently with existing conf.ValidationErrors.
  • translate.goToLegacy: map-keyed collections -> named slices, {value, unit} -> scalar ints, logging normalization.
  • emit.goFromLegacy: the inverse transform.
  • units.go — unit conversion with overflow checks.

The old cmd/server_yaml_compat.go and its test file were removed; that logic now lives in conf/serveryaml and is reached through thin helpers in cmd/server_yaml.go (prepareYAMLForParse, translateNativeYAMLForDiff, maybeEmitNativeYAML).

Integration coverage

  • New fixtures testdata/cases/server811/server811.experimental.yaml and testdata/cases/server812/server812.experimental.yaml.
  • New conf-tests.json and yaml-tests.json entries for both 8.1.1 and 8.1.2 that round-trip through --server-yaml (conf -> native yaml -> conf) and diff against the hand-written conf source.
  • Optional serverExperimental field on TestData so the harness launches asd --experimental for flagged cases; that same flag also forces the YAML-side diff and the convert-back step to use --server-yaml.
  • schema/schemas submodule bumped to pick up the 8.1.2 native-YAML schema alongside the existing 8.1.1 native schema.

Behavior matrix

Command --server-yaml off --server-yaml on
validate ... .yaml Legacy schema validation. Experimental schema validation only (legacy validator is skipped so native-only fields are accepted).
validate ... .conf Unchanged. No-op (no YAML to validate natively).
convert .conf -> .yaml Emits legacy YAML. Emits server-native YAML (gated on version).
convert .yaml -> .conf Parses legacy YAML, runs legacy validator. Parses + validates server-native YAML (gated on version); legacy validator is skipped.
diff files a b Unchanged. Translates whichever sides are YAML from native -> legacy; no schema validation.
diff server file Unchanged. Translates file if it's YAML, gated on the live cluster's version.
generate -F yaml Emits legacy YAML. Emits server-native YAML (gated on cluster version).
generate -F conf Unchanged. No-op (no YAML output).

Quick rules

  • Reading YAML in 8.1.0+ native format -> pass --server-yaml.
  • Writing YAML in 8.1.0+ native format -> pass --server-yaml.
  • Omit the flag to keep the legacy asconfig YAML shape (the default until server 9.0.0).
  • To run Aerospike with server-native YAML, start asd with --experimental.

Example

Convert testconf.conf to server-native YAML, then run Aerospike against that YAML file:

asconfig convert --aerospike-version 8.1.1 --server-yaml --output testconf.yaml testconf.conf
asd --experimental --foreground --config-file testconf.yaml

Where testconf.conf is:

service  {
	user root
	group root
	proto-fd-max 15000
	debug-allocations true
	indent-allocations true
	cluster-name bob-cluster-a
 }
logging  {
	file /var/log/aerospike/aerospike.log  {
		context any info
	 }
 }
network  {
	service  {
		address any
		port 3000
	 }
	heartbeat  {
		mode multicast
		multicast-group 239.1.99.200
		port 9918
		interval 150
		timeout 10
	 }
	fabric  {
		port 3001
	 }
 }
namespace test  {
	nsup-period 120
	storage-engine memory  {
		evict-used-pct 60
		file /var/lib/aerospike/test.dat
		filesize 4G
		flush-size 4K
	 }
	max-record-size 8M
	replication-factor 2
	default-ttl 5d
 }

And testconf.yaml is:

# *** Aerospike Metadata Generated by Asconfig ***
# aerospike-server-version: 8.1.1
# asconfig-version: development
# *** End Aerospike Metadata ***

logging:
    - contexts:
        any: info
      path: /var/log/aerospike/aerospike.log
      type: file
namespaces:
    test:
        default-ttl: 432000
        max-record-size: 8388608
        nsup-period: 120
        replication-factor: 2
        storage-engine:
            evict-used-pct: 60
            files:
                - /var/lib/aerospike/test.dat
            filesize: 4294967296
            flush-size: 4096
            type: memory
network:
    fabric:
        port: 3001
    heartbeat:
        interval: 150
        mode: multicast
        multicast-groups:
            - 239.1.99.200
        port: 9918
        timeout: 10
    service:
        addresses:
            - any
        port: 3000
service:
    cluster-name: bob-cluster-a
    debug-allocations: true
    group: root
    indent-allocations: true
    proto-fd-max: 15000
    user: root

Test plan

  • go build ./... && go vet ./...
  • go test -tags=unit ./cmd/... ./conf/serveryaml/... (covers new 8.1.2 schema resolution case).
  • Integration suite against server 8.1.1 with the server811.experimental.yaml fixture.
  • Integration suite against server 8.1.2 with the server812.experimental.yaml fixture (both conf-tests and yaml-tests exercise --server-yaml).
  • Spot-check: asconfig convert --server-yaml -a 8.1.2.0 testconf.conf emits native-shape YAML.
  • Spot-check: asconfig convert --server-yaml -a 8.1.2.0 testconf.yaml -o out.conf round-trips back to legacy .conf.
  • Spot-check: asconfig validate --server-yaml -a 8.0.0 file.yaml errors with the version-gate message.
  • Spot-check: asconfig diff files --server-yaml a.yaml b.yaml diffs without schema validation.

Follow-ups

  • Lift conf/serveryaml into aerospike-management-lib once the library can consume the native shape directly; at that point the translation layer in cmd collapses to a thin passthrough.
  • Revisit the default when Aerospike 9.0.0 ships and native becomes the server's default: the flag becomes opt-out (--legacy-yaml) rather than opt-in.

@dwelch-spike dwelch-spike marked this pull request as draft February 26, 2026 00:39
@dwelch-spike

Copy link
Copy Markdown
Contributor Author

Strange, tests are failing on Feb 26 2026 00:41:43 GMT: CRITICAL (config): (cfg.c:2114) error while validating config file: Validation error: At /logging/0 of {"masking":"info","name":"console","namespace":"info"} - validation failed for additional property 'masking': instance invalid as per false-schema masking was added to the schema late, I wonder if that update was not included in this build of the server.

@codecov-commenter

codecov-commenter commented Feb 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.05583% with 173 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.75%. Comparing base (17fac62) to head (0410bac).

Files with missing lines Patch % Lines
conf/serveryaml/emit.go 82.60% 32 Missing and 16 partials ⚠️
conf/serveryaml/translate.go 85.28% 26 Missing and 13 partials ⚠️
conf/serveryaml/schemas.go 77.17% 14 Missing and 7 partials ⚠️
cmd/server_yaml.go 79.54% 12 Missing and 6 partials ⚠️
cmd/diff.go 26.31% 12 Missing and 2 partials ⚠️
cmd/convert.go 58.62% 8 Missing and 4 partials ⚠️
conf/serveryaml/units.go 94.76% 6 Missing and 3 partials ⚠️
cmd/generate.go 66.66% 2 Missing and 1 partial ⚠️
cmd/validate.go 78.57% 2 Missing and 1 partial ⚠️
conf/serveryaml/format.go 72.72% 2 Missing and 1 partial ⚠️
... and 1 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #96      +/-   ##
==========================================
+ Coverage   81.05%   81.75%   +0.69%     
==========================================
  Files          15       22       +7     
  Lines        2101     3113    +1012     
==========================================
+ Hits         1703     2545     +842     
- Misses        296      412     +116     
- Partials      102      156      +54     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dwelch-spike dwelch-spike changed the title 8.1.1 Experimental Server YAML Integration Server Native YAML Integration. Apr 18, 2026
@dwelch-spike dwelch-spike requested a review from Copilot April 18, 2026 00:01
@dwelch-spike

dwelch-spike commented Apr 18, 2026

Copy link
Copy Markdown
Contributor Author

Much of this will be removed from asconfig when the management lib supports the new format, hopefully the server-yaml package can be reused for some of that.

I think it's important that asconfig have support ready early though.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds experimental, first-class support for Aerospike server-native YAML (8.1.0+) across asconfig commands by introducing a dedicated conf/serveryaml package, embedding/using the experimental JSON schemas for validation, and updating integration/unit tests + fixtures to cover round-trips and asd --experimental execution.

Changes:

  • Introduce conf/serveryaml for native-YAML version gating, schema resolution/validation, and legacy<->native translation.
  • Embed experimental schemas via schema.NewExperimentalSchemaMap() and add unit tests to ensure both legacy and experimental schema embedding work.
  • Extend test harness + fixtures (server 8.1.1/8.1.2) to run experimental YAML cases and validate --server-yaml flows.

Reviewed changes

Copilot reviewed 31 out of 31 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
testutils/utils.go Adds ServerExperimental flag to test case struct for experimental server runs.
testdata/cases/server812/yaml-tests.json Adds server-native YAML convert fixture test with ServerExperimental.
testdata/cases/server812/server812.experimental.yaml Adds server-native YAML fixture for 8.1.2.
testdata/cases/server812/conf-tests.json Adds conf->native-yaml test for 8.1.2.
testdata/cases/server811/yaml-tests.json Adds server-native YAML convert fixture test with ServerExperimental.
testdata/cases/server811/server811.experimental.yaml Adds server-native YAML fixture for 8.1.1.
testdata/cases/server811/conf-tests.json Adds conf->native-yaml test for 8.1.1.
schema/schemamap.go Adds embedded experimental schema FS and NewExperimentalSchemaMap.
schema/schemamap_test.go Unit tests for experimental schema embedding + legacy/experimental separation.
integration_test.go Runs asd --experimental for flagged fixtures; wires --server-yaml into diff/convert-back paths.
conf/serveryaml/format.go Defines min supported version + gating helpers for server-native YAML.
conf/serveryaml/schemas.go Loads/chooses best experimental schema per version; sanitizes unsupported regex patterns.
conf/serveryaml/schemas_test.go Unit tests for schema loading, resolution, sanitization, and error cases.
conf/serveryaml/validate.go Validates native YAML against experimental JSON schema via gojsonschema.
conf/serveryaml/translate.go Translates server-native YAML into legacy asconfig YAML shape (incl. units + logging).
conf/serveryaml/units.go Converts {value, unit} objects to scalar integers with overflow checks.
conf/serveryaml/emit.go Translates legacy YAML into server-native YAML shape (incl. logging normalization).
conf/serveryaml/serveryaml_test.go Unit tests for translation, version gating, and schema validation behavior.
conf/serveryaml/translate_test.go Unit tests for structural translation edge cases and determinism helpers.
conf/serveryaml/units_test.go Unit tests for unit conversion behavior, overflow, and numeric decoding.
conf/serveryaml/emit_test.go Unit tests for native emission and logging shape normalization.
cmd/server_yaml.go Adds --server-yaml helpers for parse/emit/diff translation + schema error formatting.
cmd/server_yaml_test.go Unit tests for --server-yaml helper behaviors and end-to-end convert wiring.
cmd/validate.go Adds --server-yaml support to validate flow via prepareYAMLForParse.
cmd/validate_test.go Adds unit tests for server-native YAML validate success, version gating, schema rejection.
cmd/convert.go Adds --server-yaml parse/emit hooks and metadata handling for convert.
cmd/convert_test.go Unit tests for convert --server-yaml gating and no-op behavior on non-YAML side.
cmd/diff.go Adds --server-yaml to diff commands and native->legacy translation for diff files; validate+translate for diff server.
cmd/generate.go Adds --server-yaml output emission hook + gating for YAML generation.
cmd/generate_test.go Unit tests for generate --server-yaml gating and non-YAML no-op.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread conf/serveryaml/emit.go
Comment thread conf/serveryaml/translate_test.go
Comment thread conf/serveryaml/serveryaml_test.go
Comment thread conf/serveryaml/emit_test.go
Comment thread cmd/server_yaml.go Outdated
Comment thread conf/serveryaml/translate.go
@dwelch-spike dwelch-spike requested a review from a-spiker April 18, 2026 00:51
@dwelch-spike dwelch-spike marked this pull request as ready for review April 18, 2026 00:51
@dwelch-spike

Copy link
Copy Markdown
Contributor Author

@a-spiker Let me know what you think. This is a lot of changes for server native yaml but they are mostly contained to their own package. Hopefully we can move that code to the management lib eventually and then remove most of it.

@a-spiker

a-spiker commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

PR Review (version 2026.05.27)

Promises:

  1. A single context-sensitive --server-yaml flag toggles native YAML on the YAML side of validate/convert/diff/generate; old --server-yaml-output removed.
  2. Reading native YAML validates against the experimental schema (legacy validator skipped) then translates to legacy; writing emits native shape.
  3. Version-gated: native YAML requires server >= 8.1.0 (patch ignored); missing/below-cutoff is a hard error. diff files is version-agnostic with no schema validation.
  4. Legacy YAML shape stays the default (backwards compatible).
  5. All native logic self-contained in conf/serveryaml for a future lift into aerospike-management-lib.
  6. Translation round-trips: map-keyed collections <-> named slices, {value, unit} <-> scalar ints, logging normalization, with overflow checks.
  7. Integration + unit coverage for 8.1.1 and 8.1.2.
Gate Status
Contract Pass (with one functional gap, see Promise 6)
Impacted surface Pass
Failure paths Pass
Evidence Pass (suite is green at the committed submodule pointer cced5f2; two coverage gaps noted inline)
Quality Pass

7 findings posted inline (0 Critical, 1 Important, 6 Minor).

The one Important is a real unit-conversion bug: index-stage-size / sindex-stage-size are misclassified as duration fields, so a M unit is read as minutes (x60) instead of mega (x1,000,000). The rest are quality/test-coverage items.

Note on the version gate, overflow checks, validation-skip symmetry, and {value,unit} handling: all verified correct. The package builds, and go test -tags=unit ./conf/serveryaml/... ./cmd/... ./schema/... is green when the schema/schemas submodule is at the committed pointer (cced5f2, which ships both 8.1.1.json and 8.1.2.json).

Comment thread conf/serveryaml/units.go
"interval",
"duration",
"sleep",
"age",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

From Logic agent: Important: isDurationPath uses strings.Contains(field, token) over a token list that includes "age". The namespace byte-size fields index-stage-size and sindex-stage-size contain the substring "stage", so they are misclassified as duration fields. For a m/M unit, getUnitMultiplier then returns unitMinute (60) instead of unitMega (1,000,000).

This is reachable through validated native input: the 8.1.1 schema declares index-stage-size with oneOf allowing {value: 135..17179, unit: "M"} (megabytes). {value: 256, unit: M} translates to the legacy scalar 15360 instead of 268435456 (off by ~16,667x), and 15360 is far below the field’s own integer-form minimum of 134217728, so the resulting legacy config is silently invalid. Verified: getUnitMultiplier("m", [...,"index-stage-size"]) returns 60; the control field data-size correctly returns 1000000.

Fix: match whole --delimited segments instead of substrings (e.g. split the field name and compare tokens, or check suffix tokens), or special-case the *-stage-size family. The other duration tokens (*-period, *-ttl, *-interval, *-duration, tomb-raider-eligible-age, etc.) classify correctly; only the two stage-size fields are wrong.

Comment thread conf/serveryaml/units.go
}

for key, value := range typedNode {
translated, err := translateUnitValues(value, append(path, key))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

From Data Flow agent: Minor: The map branch calls translateUnitValues(value, append(path, key)), while the sibling slice branch (line 50) deliberately uses pathWithIndex(path, i), which allocates a fresh slice so recursion does not stomp the caller’s path backing array. With append, successive sibling iterations can reuse the same backing array and overwrite a slot a prior call’s path still references, so a formatNodePath error message can name the wrong field. The conversion result is unaffected (paths feed only error strings), so this is diagnostics-only, but the inconsistency makes a reader wonder whether the difference is intentional. It is not. Use the same copy discipline in both branches (mirror pathWithIndex, or append(append([]string{}, path...), key)).

return keys
}

func sortStrings(keys []string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

From Style agent: Minor: sortStrings hand-rolls an insertion sort to sort collection keys. The sort package is already imported in this package (schemas.go uses sort.Slice), so sort.Strings(keys) does exactly this in one line, is the idiom every Go reader expects, and avoids making the reader verify the loop bounds and swap logic. Replace the sortStrings(keys) call (line 348) with sort.Strings(keys) and delete this function. Matters more given this package is meant to be lifted into aerospike-management-lib.

Comment thread conf/serveryaml/emit.go
// Collect top-level logging context keys (e.g. `any: info`) and merge them
// into the contexts map. If the same context appears under both the
// explicit `contexts` map and as a top-level field we error, mirroring
// the translate.go direction so the round-trip is symmetric.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

From Style agent: Minor: This comment claims the conflict handling mirrors translate.go "so the round-trip is symmetric," but the two are not mirror images. collectLegacyLoggingContexts errors only when the same context key appears both under contexts and as a top-level field with a different value, whereas flattenServerLoggingContexts (translate.go) errors when a context name collides with any existing sink field regardless of value. The round-trip does not actually require them to match, so the comment sends the reader chasing a symmetry that is not there. Tighten the wording to describe what this function actually guards against, or drop the "mirroring / symmetric" claim.

replacementPattern = `^[a-zA-Z0-9_\\-$]+$`
)

return strings.ReplaceAll(body, negativeLookaheadPattern, replacementPattern)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

From Security agent: Minor: sanitizeExperimentalSchema does a single literal strings.ReplaceAll for exactly one known-incompatible pattern. Today that is correct and it fails closed (an unsanitized lookahead makes gojsonschema fail to compile, which surfaces as a hard validation error rather than a silent skip). The risk is the reverse: when a future submodule bump introduces a new gojsonschema-incompatible construct, this substitution will not catch it and every --server-yaml invocation for that version becomes a hard error until someone updates this literal. Add a guard test that loads every embedded experimental schema, runs it through the sanitizer, and asserts it compiles under gojsonschema. That turns a future silent breakage into a CI failure at submodule-bump time.

Comment thread conf/serveryaml/format.go
// Package serveryaml provides translation, schema validation, and version
// gating for the Aerospike server-native (experimental) YAML format.
//
// The package is self-contained with no dependencies on asconfig/cmd or cobra

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

From Architecture agent: Minor: This doc says the package has "no dependencies on asconfig/cmd or cobra," which is literally true but understates the real lift blocker. schemas.go imports github.com/aerospike/asconfig/schema for SchemaMap / NewExperimentalSchemaMap and the embedded experimental schema FS, so lifting this package into aerospike-management-lib also requires moving (a) the embedded schema files and (b) the NewExperimentalSchemaMap/collector plumbing. The schema package is a clean leaf, so the rework is bounded, but "self-contained" + "minimal rework" should name that one external dependency. Either move the experimental-schema embed + a small loader into conf/serveryaml (true self-containment) or amend the comment to state the asconfig/schema dependency that must accompany the lift.

Comment thread cmd/validate.go
return err
}

nativeValidated, err := serverYAMLValidatesInput(cmd, srcFormat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

From Tests agent: Minor: The convert path has a dedicated regression test that a native-only field is accepted with --server-yaml and no --force (TestConvertServerYAMLAcceptsNativeOnlyFields), proving the legacy validator is skipped. The validate path does not: TestRunEValidateServerYAMLCompat uses a fixture containing only fields that also exist in the legacy schema, so it would pass whether or not this nativeValidated skip branch fires. A regression that re-enabled the legacy validator on the validate path would not be caught. Add a validate-side test using a native-only field (and ideally a sibling assertion that the same file is rejected without --server-yaml). Same gap on the diff live/server path, where prepareYAMLForParse validates the local file (diff.go) but has no --server-yaml test exercising it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants