Skip to content

fix: support new wekactl interface group response - #2798

Closed
assafgi wants to merge 1 commit into
09-03-fix_add_wekactl_as_default_ff_and_fix_ensure_weka_versionfrom
09-06-fix_support_new_wekactl_interface_group_response
Closed

fix: support new wekactl interface group response#2798
assafgi wants to merge 1 commit into
09-03-fix_add_wekactl_as_default_ff_and_fix_ensure_weka_versionfrom
09-06-fix_support_new_wekactl_interface_group_response

Conversation

@assafgi

@assafgi assafgi commented Sep 6, 2026

Copy link
Copy Markdown
Member

No description provided.

@assafgi
assafgi marked this pull request as ready for review September 6, 2026 11:59
@assafgi
assafgi requested a review from a team as a code owner September 6, 2026 11:59
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@graphite-app
graphite-app Bot requested review from kristina-solovyova and tigrawap and removed request for tigrawap September 6, 2026 11:59

assafgi commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more


How to use the Graphite Merge Queue

Add the label main-merge-queue to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has required the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown

Claude finished @assafgi's task in 3m 0s —— View job


Review: fix: support new wekactl interface group response

  • Gather context (diff + parent PR fix: add wekactl_as_default ff and fix ensure_weka_version #2794)
  • Verify helpers/imports (resources.HostIdToContainerId, instrumentation.CurrentSpanLogger)
  • Check impact of removed SubnetMask field and other HostId parsing sites
  • Check whether the new WekactlAsDefault flag is wired anywhere
  • Build + run tests — blocked, see note at the bottom
  • Post review (4 inline comments)

Verdict

The core change is sound. Accepting both schemas unconditionally rather than branching on WekactlAsDefault is the better call — it survives mixed-version clusters and rollbacks, where a flag-gated decoder would not. The *int on Container is a real bug avoided (container 0 is a valid id), and the decision to error rather than return an empty slice on an unknown shape is correct: the whole failure being fixed here is a silent "owns nothing" reading, and a second silent reading would just relocate it.

Verified along the way:

  • resources.HostIdToContainerIdWekaIdToInteger (internal/controllers/resources/cluster.go:64) also tolerates a bare "13", so a future numeric-string host_id degrades gracefully rather than erroring.
  • Removing SubnetMask is safe — no reader anywhere in the tree, and encoding/json ignores the field either way. (wekactl emits netmask: 32 as an int, so the old string field would have failed to decode; dropping it is the right move, not just cleanup.)
  • The cmd =cmd := changes in the add/remove loops are required by the extraction, not incidental.
  • The new test file matches the style already established by weka_cluster_container_test.go (stdlib testing, verbatim live payloads, comments explaining the production consequence of a decode drift). Nice.

Findings (4 inline comments)

# Where Severity Issue
1 weka.go:1093-1116 medium Extraction missed its second caller: EnsureNfsIpRanges (weka.go:1428-1458) still has the identical duplicated block. Also interfaceGroups[0] is taken without matching on Name.
2 weka.go:264-276 medium "container": null decodes to nil and is indistinguishable from an absent field — one orphaned port would fail EnsureNfsInterfaceGroupPorts for every container in the group, re-creating the wedge via the delete flow. Error message also interpolates a possibly-empty p.Port.
3 feature_flags.go:19-22 nit WekactlAsDefault has no Go consumer; worth saying so in the comment.
4 weka_nfs_interface_group_test.go nit No "container": null case to pin the decision above.

Broader risk worth a look on a wekactl cluster (out of scope for this PR)

If wekactl changed port identity in nfs interface-group, the same switch plausibly touches the other HostId<N>-shaped contracts the operator depends on. None are covered by this PR or its tests:

  • ListS3ClusterContainers (weka.go:1268) parses ["HostId<0>", ...] and hard-errors on any other shape.
  • ListSmbwClusterContainers (weka.go:991) parses sambaHosts the same way.
  • Several control-flow decisions match on stderr text containing HostId<N>: "Unrecognized host ID HostId<%d>" (weka.go:828, weka.go:1074), "Host HostId<%d> not found" (weka.go:1647, weka.go:1758). A go-CLI reword turns each of these idempotency guards back into a hard failure.
  • Same class: the "is not part of group" match in the port-removal loop (weka.go:1204) — the idempotency escape hatch for the exact code path this PR touches.

Not blocking, but a quick pass over these against a wekactl_as_default=true cluster would be worth more than the fix here in isolation.

Not verified

I could not run go build, go vet, or go test in this environment — those commands are outside the allowed tool set for this job, so the review is static-analysis only. The new tests are pure encoding/json decode assertions with no external dependencies, so they should be cheap for CI to confirm.
09-06-fix_support_new_wekactl_interface_group_response

@graphite-app

graphite-app Bot commented Sep 6, 2026

Copy link
Copy Markdown

Graphite Automations

"Add anton/matt/sergey/kristina as reviwers on operator PRs" took an action on this PR • (09/06/26)

2 reviewers were added to this PR based on Anton Bykov's automation.

Comment thread internal/services/weka.go
Comment on lines +1093 to +1116
func (c *CliWekaService) getNfsInterfaceGroup(ctx context.Context, executor podexec.Exec, interfaceGroupName string) (*NfsInterfaceGroup, error) {
logger := instrumentation.CurrentSpanLogger(ctx)

// Fetch current interface group configuration
cmd := []string{
"weka", "nfs", "interface-group", "--name", interfaceGroupName, "--json",
}
stdout, stderr, err := executor.ExecNamed(ctx, "GetNfsInterfaceGroup", cmd)
if err != nil {
logger.SetError(err, "Failed to get NFS interface group", "interfaceGroup", interfaceGroupName, "stderr", stderr.String())
return err
return nil, err
}

// Parse the response
var interfaceGroups []NfsInterfaceGroup
if parseErr := json.Unmarshal(stdout.Bytes(), &interfaceGroups); parseErr != nil {
logger.SetError(parseErr, "Failed to parse NFS interface group JSON", "stdout", stdout.String())
return parseErr
return nil, parseErr
}

if len(interfaceGroups) == 0 {
return errors.Errorf("NFS interface group %s not found", interfaceGroupName)
return nil, errors.Errorf("NFS interface group %s not found", interfaceGroupName)
}

return &interfaceGroups[0], nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The extraction missed its second caller. EnsureNfsIpRanges (weka.go:1428–1458, unchanged by this PR) still contains a verbatim copy of this exact fetch → json.Unmarshal([]NfsInterfaceGroup)len == 0[0] block, down to the same "GetNfsInterfaceGroup" span name. Now that the helper exists, that copy should call it, otherwise the next schema change has two places to fix and only one of them is covered by the new test.

Separately, two robustness gaps in the helper itself — both relevant precisely because the premise of this PR is "the CLI changed behaviour under us":

  1. interfaceGroups[0] is returned without checking Name == interfaceGroupName. If a wekactl generation ever ignores/renames --name, this silently reconciles ports against the wrong interface group — a much worse failure than the one being fixed here, and a silent one.
  2. len(interfaceGroups) > 1 is discarded without a word in the log.

Both are cheap to close by selecting the match by name:

for i := range interfaceGroups {
    if interfaceGroups[i].Name == interfaceGroupName {
        return &interfaceGroups[i], nil
    }
}
return nil, errors.Errorf("NFS interface group %s not found (got %d groups)", interfaceGroupName, len(interfaceGroups))

Fix this →

Comment thread internal/services/weka.go
Comment on lines +264 to 276
// OwnerContainerId resolves the weka container owning this port under either CLI schema.
// It errors rather than defaulting, so an unrecognized schema cannot be silently read as
// "this port belongs to nobody" — that reading previously let port removal no-op and wedge
// container deletion behind a failing deactivate.
func (p NfsInterfaceGroupPort) OwnerContainerId() (int, error) {
if p.Container != nil {
return *p.Container, nil
}
if p.HostId != "" {
return resources.HostIdToContainerId(p.HostId)
}
return 0, errors.Errorf("interface group port %q has neither a 'container' nor a 'host_id' field", p.Port)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The fail-loud choice is the right default and the rationale in the comment is convincing. Two things worth pinning down before merge:

"container": null would be indistinguishable from an absent field. Go decodes an explicit JSON null into a *int as nil, so an orphaned port (owner container already removed) that wekactl renders as "container": null with no host_id lands in the error branch. Because ContainerPorts fails the whole group scan, one such orphan makes EnsureNfsInterfaceGroupPorts fail for every container in the group — including the RemoveFromNfs step of flow_deleting_state.go:138. That reproduces exactly the wedge this PR is fixing, just triggered by a different payload. If you can confirm wekactl never emits a null/absent container for a live port, a one-line note here would be enough; if you can't, consider skipping ports that resolve to "no owner at all" only when they also carry no uid, and erroring on genuinely unrecognized shapes.

Diagnosability: the error interpolates p.Port, which is itself empty on a badly-shaped entry, yielding interface group port "" has neither a 'container' nor a 'host_id' field. Including ContainerUid/HostUid (at least one is populated in both schemas) would make the message actionable from logs alone.

Comment on lines +19 to +22
// flag 13 is not used by the operator
// WekactlAsDefault means the go CLI (wekactl) backs the `weka` command. It changes some JSON
// schemas, notably `weka nfs interface-group` port identity — see NfsInterfaceGroupPort.
WekactlAsDefault bool `json:"wekactl_as_default"` // 14

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WekactlAsDefault is never read anywhere in Go — the only Go reference is the doc comment on NfsInterfaceGroupPort. That's consistent with the design (the decoder accepts both schemas unconditionally, which is the more robust choice than branching on a flag), but it means the field exists purely so the flag decodes and so the comment has something to point at.

That's fine to keep, just make it explicit in the comment that the operator does not branch on this flag today, so the next reader doesn't go hunting for the consumer or assume the schema handling is gated on it.

Comment on lines +109 to +129
}
}

// TestContainerZeroIsDistinguishableFromAbsent guards the pointer on NfsInterfaceGroupPort.Container.
// With a plain int, container 0 and "field missing" are the same value, so an unparseable payload
// would silently claim to own container 0's ports.
func TestContainerZeroIsDistinguishableFromAbsent(t *testing.T) {
group := parseGroup(t, `[{"name": "g", "ports": [{"container": 0, "port": "eth0"}]}]`)
ports, err := group.ContainerPorts(0)
if err != nil {
t.Fatalf("ContainerPorts(0): %v", err)
}
if len(ports) != 1 {
t.Fatalf("container 0 must own eth0, got %v", ports)
}

missing := parseGroup(t, `[{"name": "g", "ports": [{"port": "eth0"}]}]`)
if _, err := missing.ContainerPorts(0); err == nil {
t.Fatal("an absent container field must not be read as container 0")
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Good coverage — the container-0 / absent-field distinction is exactly the trap the *int exists for, and pinning it with a test is the right call.

One gap: there's no case for {"container": null, "port": "eth0"}. That decodes identically to the absent-field case, so today it errors — which may or may not be what you want for an orphaned port (see the note on OwnerContainerId). Adding it either way pins the decision rather than leaving it to encoding/json semantics that a future reader has to re-derive.

@assafgi
assafgi force-pushed the 09-06-fix_support_new_wekactl_interface_group_response branch 2 times, most recently from a1b3530 to 3b65d50 Compare September 8, 2026 05:51
@assafgi
assafgi force-pushed the 09-06-fix_support_new_wekactl_interface_group_response branch from 3b65d50 to ce9a482 Compare September 8, 2026 06:02
@graphite-app

graphite-app Bot commented Sep 8, 2026

Copy link
Copy Markdown

Merge activity

  • Sep 8, 8:12 AM UTC: assafgi added this pull request to the Graphite merge queue.
  • Sep 8, 8:13 AM UTC: CI is running for this pull request on a draft pull request (#2802) due to your merge queue CI optimization settings.
  • Sep 8, 9:05 AM UTC: Merged by the Graphite merge queue via draft PR: #2802.

@graphite-app graphite-app Bot closed this Sep 8, 2026
@graphite-app
graphite-app Bot deleted the 09-06-fix_support_new_wekactl_interface_group_response branch September 8, 2026 09:05
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.

2 participants