Skip to content

feat: add initiators field to create_igroup for atomic create-with-membership#178

Open
dbtinsley wants to merge 3 commits into
NetApp:mainfrom
dbtinsley:cpoc/feat/igroup-initiators
Open

feat: add initiators field to create_igroup for atomic create-with-membership#178
dbtinsley wants to merge 3 commits into
NetApp:mainfrom
dbtinsley:cpoc/feat/igroup-initiators

Conversation

@dbtinsley

Copy link
Copy Markdown

Closes #173

What this PR does

Adds an optional initiators []string field to IGroupCreate so callers can supply the complete FC WWPN / iSCSI IQN / EUI initiator list in a single POST /protocols/san/igroups, matching the ONTAP REST API's native capability.

Creating an igroup and then adding initiators one at a time is not atomic: a partial failure leaves an empty igroup on live storage. This field eliminates that window.

An explicit guard rejects protocol=nvme with an initiator list and directs callers to use an NVMe subsystem with NQN hosts instead.

Supporting change: rest.Client.handleJob refactor

This PR also refactors handleJob to correctly distinguish 202 Accepted (async, requires job polling) from 201 Created (sync, no polling). Previously, handleJob attempted job body parsing for both status codes. Now it calls checkStatus first, returns immediately on 201, and only polls the job queue for 202. A jobPollInterval field is added to Client for test isolation.

CreateIGroup, CreateLUN, CreateLunMap, CreateNVMeNamespace, CreateNVMeSubsystemMap, and CreateQoSPolicy/DeleteQoSPolicy are updated to use handleJob (with response body capture) so they handle ONTAP async job responses correctly. The same handleJob change appears in the companion PRs (#174, #175, #176) and only needs to be merged once.

Files changed

File Change
tool/tool.go Add Initiators []string to IGroupCreate
ontap/ontap.go Add Initiators []InitiatorName to IGroup
server/igroup.go Propagate initiators in newCreateIGroup; add NVMe protocol guard
server/igroup_typed_fields_test.go New: initiator propagation and NVMe guard tests
rest/client.go Add jobPollInterval; refactor handleJob
rest/igroup.go + lun.go + lunmap.go + nvme.go + qospolicy.go Use handleJob instead of checkStatus
rest/job_wait_test.go New: handleJob unit tests and mutation job-polling tests

Raised by NetApp CPOC.

Made with Cursor

Copilot AI review requested due to automatic review settings July 13, 2026 16:57
@cla-bot

cla-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

Thank you for your interest in contributing to the ontap-mcp project! We require contributors to sign our Corporate contributor license agreement (CCLA), and we don"t have the user(s) @dbtinsley on file. In order for us to review and merge your code, please follow the instructions in step 6 of creating a pull request.
After signing the CCLA, you can ask us to recheck this PR by posting @cla-bot check as a comment to the PR.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the typed igroup creation flow to support atomic “create igroup with initiators” in a single ONTAP REST request, and refactors REST job handling to correctly poll only for async (202 Accepted) responses.

Changes:

  • Add optional initiators input to tool.IGroupCreate, plumbed through server.newCreateIGroup into ontap.IGroup.Initiators.
  • Refactor rest.Client.handleJob to return immediately for synchronous success and poll only for 202 responses; introduce jobPollInterval for test isolation.
  • Update multiple REST mutation calls to use handleJob and add unit tests covering job polling and new igroup initiator behavior.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tool/tool.go Adds Initiators []string to the typed create_igroup input schema.
ontap/ontap.go Adds Initiators []InitiatorName to the ONTAP igroup request/response model.
server/igroup.go Maps initiators into the create body and adds protocol validation/guard logic.
server/igroup_typed_fields_test.go Adds unit tests for initiator mapping and NVMe protocol rejection behavior.
rest/client.go Refactors handleJob and adds configurable polling interval.
rest/igroup.go Captures response body and routes create through handleJob.
rest/lun.go Captures response body and routes create through handleJob.
rest/lunmap.go Captures response body and routes create through handleJob.
rest/nvme.go Captures response body and routes NVMe creates through handleJob.
rest/qospolicy.go Captures response body and routes QoS create/delete through handleJob.
rest/job_wait_test.go Adds tests validating handleJob behavior and polling across common mutations.

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

Comment thread server/igroup.go Outdated
Comment on lines +165 to +170
if in.Protocol == "" {
return out, errors.New("protocol is required")
}
if strings.EqualFold(in.Protocol, "nvme") {
return out, errors.New("NVMe initiators cannot be created with an igroup; use an NVMe subsystem and NQN hosts")
}
Comment thread server/igroup.go
Comment on lines +179 to +184
for _, initiator := range in.Initiators {
if initiator == "" {
return out, errors.New("all initiators must be non-empty")
}
out.Initiators = append(out.Initiators, ontap.InitiatorName{Name: initiator})
}
Comment thread server/igroup_typed_fields_test.go Outdated
Comment on lines +23 to +30
body, err := json.Marshal(got)
if err != nil {
t.Fatalf("json.Marshal() error = %v", err)
}
want := `{"svm":{"name":"genio"},"name":"genio-fcp","os_type":"linux","protocol":"fcp","initiators":[{"name":"10:00:00:00:00:00:00:01"},{"name":"10:00:00:00:00:00:00:02"}]}`
if string(body) != want {
t.Errorf("igroup create body = %s, want %s", body, want)
}
dbtinsley added a commit to dbtinsley/ontap-mcp that referenced this pull request Jul 13, 2026
- NVMe guard is now conditional: provides a distinct error message when
  initiators are supplied (directing to NVMe subsystem + NQN hosts) versus
  when the nvme protocol is used without initiators (general protocol error).
- Initiator validation now trims whitespace before the empty check, rejecting
  values like "   " that would cause confusing ONTAP API errors downstream.
- Test uses struct comparison via json.Marshal(want) instead of a raw string
  literal; adds coverage for NVMe-without-initiators and whitespace-only cases.

Addresses review comments on NetApp#178.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings July 13, 2026 17:11
@cla-bot

cla-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

Thank you for your interest in contributing to the ontap-mcp project! We require contributors to sign our Corporate contributor license agreement (CCLA), and we don"t have the user(s) @dbtinsley on file. In order for us to review and merge your code, please follow the instructions in step 6 of creating a pull request.
After signing the CCLA, you can ask us to recheck this PR by posting @cla-bot check as a comment to the PR.

@dbtinsley

Copy link
Copy Markdown
Author

@cla-bot check

Note for the ontap-mcp team: @dbtinsley is a NetApp employee contributing this work as part of a NetApp-sponsored project (NetApp CPOC). We believe the internal employment agreement covers IP assignment and that the external CCLA process does not apply, but please let us know if there is an internal process we should follow instead.

@cla-bot

cla-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

Thank you for your interest in contributing to the ontap-mcp project! We require contributors to sign our Corporate contributor license agreement (CCLA), and we don"t have the user(s) @dbtinsley on file. In order for us to review and merge your code, please follow the instructions in step 6 of creating a pull request.
After signing the CCLA, you can ask us to recheck this PR by posting @cla-bot check as a comment to the PR.

@cla-bot

cla-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

The cla-bot has been summoned, and re-checked this pull request!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comment thread rest/job_wait_test.go Outdated
if req.Method != http.MethodGet {
t.Fatalf("job poll method = %s, want GET", req.Method)
}
*jobCalls++
Comment thread server/igroup.go
Comment on lines +165 to 172
switch {
case in.Protocol == "":
return out, errors.New("protocol is required; valid values are fcp, iscsi, mixed")
case strings.EqualFold(in.Protocol, "nvme") && len(in.Initiators) > 0:
return out, errors.New("NVMe igroups do not support FC/iSCSI initiators; use an NVMe subsystem with NQN hosts instead")
case strings.EqualFold(in.Protocol, "nvme"):
return out, errors.New("nvme is not a valid igroup protocol; use an NVMe subsystem instead (fcp, iscsi, or mixed are valid igroup protocols)")
}
Comment thread server/igroup.go
Comment on lines +181 to +186
for _, initiator := range in.Initiators {
if strings.TrimSpace(initiator) == "" {
return out, errors.New("all initiators must be non-empty and non-whitespace")
}
out.Initiators = append(out.Initiators, ontap.InitiatorName{Name: initiator})
}
dbtinsley added a commit to dbtinsley/ontap-mcp that referenced this pull request Jul 13, 2026
… before appending

- Validates protocol against the allowed set {fcp, iscsi, mixed} and returns
  a descriptive error for anything else, preventing unsupported values from
  reaching the ONTAP API with a misleading validation message.
- Trims whitespace from each initiator before appending to the request body
  so the validation check and the payload are consistent; a caller passing
  '  wwpn  ' now gets the trimmed value sent to ONTAP rather than the raw
  string.

Addresses second-round review comments on NetApp#178.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings July 13, 2026 17:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment thread server/igroup.go Outdated
Comment on lines +173 to +174
case !validProtocols[strings.ToLower(in.Protocol)]:
return out, fmt.Errorf("unsupported igroup protocol %q; valid values are fcp, iscsi, mixed", in.Protocol)
dbtinsley added a commit to dbtinsley/ontap-mcp that referenced this pull request Jul 13, 2026
… body

Case-insensitive validation accepted inputs like "FCP" but stored the
original casing verbatim, which may be rejected by some ONTAP REST
endpoints. Normalizing to lowercase ensures the stored protocol is
canonical regardless of caller casing.

Addresses review comment on NetApp#178.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings July 13, 2026 17:32
@cla-bot

cla-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

Thank you for your interest in contributing to the ontap-mcp project! We require contributors to sign our Corporate contributor license agreement (CCLA), and we don"t have the user(s) @dbtinsley on file. In order for us to review and merge your code, please follow the instructions in step 6 of creating a pull request.
After signing the CCLA, you can ask us to recheck this PR by posting @cla-bot check as a comment to the PR.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

Comment thread server/igroup.go
}
if in.Protocol == "" {
return out, errors.New("protocol is required")
validProtocols := map[string]bool{"fcp": true, "iscsi": true, "mixed": true}

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.

tools.go says this protocol field has only 3 type. LLM would handle this validation.

Still, explicit validation required, then you move logic !validProtocols[strings.ToLower(in.Protocol)] at top, which remove nvme cases as any protocol value other than 3 are invalid.

Comment thread server/igroup_typed_fields_test.go Outdated
@@ -0,0 +1,106 @@
package server

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.

…mbership

Allows specifying FC/iSCSI initiators at igroup creation time so a single
tool call creates the igroup and populates its membership atomically, rather
than requiring a separate add_igroup_initiator call per initiator.

Changes:
- tool.IGroupCreate gains Initiators []string field
- ontap.IGroup gains Initiators []InitiatorName for REST serialization
- newCreateIGroup validates each initiator (trimmed, non-empty), normalizes
  protocol to lowercase, and validates against the fcp/iscsi/mixed whitelist
  (NVMe rejected as unsupported protocol, no longer a special case)
- rest.CreateIGroup upgraded from checkStatus to handleJob so ONTAP 202
  async responses are polled to completion; same fix applied to CreateLUN,
  CreateLunMap, and CreateNVMeNamespace which can also return 202
- rest.Client.handleJob refactored: only polls when status is 202 Accepted
  (not 201 Created), guards against empty job UUID — fixes a bug in main
  where 201 responses incorrectly attempted job polling
- integration/test/igroup_test.go: existing "Create igroup" test replaced
  with "Create igroup with initiators atomically" that verifies membership
  immediately after create; standalone server/igroup_typed_fields_test.go
  and rest/job_wait_test.go removed per maintainer guidance

Co-authored-by: Cursor <cursoragent@cursor.com>
@dbtinsley
dbtinsley force-pushed the cpoc/feat/igroup-initiators branch from 799968e to 2254e51 Compare July 15, 2026 16:15
Copilot AI review requested due to automatic review settings July 15, 2026 16:15
@cla-bot cla-bot Bot added the cla-signed label Jul 15, 2026
@dbtinsley

Copy link
Copy Markdown
Author

Round 2 update — rebased on main (post-#157), addressed all maintainer and Copilot feedback:

Shared changes across all four PRs:

  • Rebased onto current main which includes the --tool-mode changes from feat: merge tools with tool_mode flag #157
  • rest/client.go handleJob fixed: only polls async jobs on HTTP 202 Accepted (not 201 Created); guards against empty UUID — fixes a bug in main where 201 sync responses incorrectly attempted job polling
  • Standalone server/igroup_typed_fields_test.go and rest/job_wait_test.go removed; test coverage moved to integration/test/igroup_test.go as a prompt-style case per maintainer guidance

PR-specific changes:

  • Protocol validation simplified: general whitelist check {fcp, iscsi, mixed} now at the top of newCreateIGroup; NVMe is no longer a special case — it's just another unsupported protocol value that fails the same check
  • Integration test: existing "Create igroup" case replaced with "Create igroup with initiators atomically" that verifies initiator membership immediately after the single create call

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Comment thread rest/nvme.go Outdated
Comment on lines 3 to 7
import (
"bytes"
"context"
"fmt"
"github.com/netapp/ontap-mcp/ontap"
Comment thread rest/lun.go Outdated
Comment on lines 3 to 7
import (
"bytes"
"context"
"fmt"
"github.com/netapp/ontap-mcp/ontap"
Comment thread server/igroup.go Outdated
Comment on lines +219 to +223
validProtocols := map[string]bool{"fcp": true, "iscsi": true, "mixed": true}
switch {
case in.Protocol == "":
return out, errors.New("protocol is required; valid values are fcp, iscsi, mixed")
case !validProtocols[strings.ToLower(in.Protocol)]:
Comment thread rest/client.go Outdated
Comment on lines +44 to +48
func (c *Client) handleJob(ctx context.Context, statusCode int, buf bytes.Buffer) error {
if statusCode == http.StatusCreated || statusCode == http.StatusAccepted {
var pj ontap.PostJob
err := json.Unmarshal(buf.Bytes(), &pj)
if err != nil {
return err
}
if err := c.checkStatus(statusCode); err != nil {
return err
}
if statusCode != http.StatusAccepted {
…files

- Change handleJob signature to accept *bytes.Buffer to avoid copying
  a potentially large buffer by value; update all call sites in rest/
- Group third-party imports separately in rest/lun.go and rest/nvme.go
  to satisfy goimports ordering requirements

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings July 15, 2026 16:49
@dbtinsley

Copy link
Copy Markdown
Author

Round 3 update — igroup-initiators (#178)

Changes pushed in this update (d7b7d75):

REST / infra

  • rest/client.go: Changed handleJob signature to accept *bytes.Buffer instead of bytes.Buffer by value — avoids copying the buffer and eliminates the static-analysis warning about large value copy.
  • All rest/*.go call sites (igroup.go, lun.go, lunmap.go, nvme.go, qospolicy.go, volume.go, cifsService.go, qtree.go, snapshot.go, svm.go, snapmirror.go): updated to pass &buf.
  • rest/lun.go, rest/nvme.go: Fixed import grouping to separate stdlib from third-party packages (goimports convention).

No behaviour changes — this is purely mechanical cleanup addressing static-analysis and formatting feedback.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Comment thread server/igroup.go Outdated
Comment on lines +219 to +223
validProtocols := map[string]bool{"fcp": true, "iscsi": true, "mixed": true}
switch {
case in.Protocol == "":
return out, errors.New("protocol is required; valid values are fcp, iscsi, mixed")
case !validProtocols[strings.ToLower(in.Protocol)]:
Comment thread rest/client.go
Comment on lines 28 to 35
type Client struct {
poller *config.Poller
httpClient *http.Client
credCache credentialsCache
remote ontap.Remote
initOnce sync.Once
poller *config.Poller
httpClient *http.Client
credCache credentialsCache
remote ontap.Remote
initOnce sync.Once
jobPollInterval time.Duration
}
…ation

Ensure leading/trailing whitespace and case variations like '  FCP  ' are
normalized to their canonical form before the empty check and the whitelist
map lookup, keeping the error message accurate (it echoes the original
caller-supplied value).

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings July 15, 2026 17:06
@dbtinsley

Copy link
Copy Markdown
Author

Round 4 update — igroup-initiators (#178)

Addressing latest Copilot feedback (commit f279507):

Fixed

  • server/igroup.go: Normalize protocol with TrimSpace+ToLower before the empty-string check and whitelist lookup, so values like " FCP " are handled correctly rather than failing the map lookup. The error message still echoes the original caller-supplied value.

Stale / already addressed in round 3

  • Import grouping in rest/lun.go and rest/nvme.go: Fixed in the round 3 commit (d7b7d75) — the files now have stdlib and third-party in separate blocks. Copilot's review appears to have diffed against an earlier commit.
  • handleJob *bytes.Buffer: Fixed in round 3.

By design (no change)

  • NVMe-specific error message: After discussion we intentionally kept a single whitelist check (fcp, iscsi, mixed) rather than a special-cased NVMe guard. The error message clearly lists the valid values. If the maintainers prefer the explicit NVMe guidance, happy to add it back.
  • rest/job_wait_test.go: Removed intentionally per maintainer guidance to move coverage to integration/test/; the PR description was updated in round 2 to reflect this. No unit test file is promised — the integration test covers the prompt flow.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comment thread server/igroup.go
Comment on lines +219 to 226
protocol := strings.ToLower(strings.TrimSpace(in.Protocol))
validProtocols := map[string]bool{"fcp": true, "iscsi": true, "mixed": true}
switch {
case protocol == "":
return out, errors.New("protocol is required; valid values are fcp, iscsi, mixed")
case !validProtocols[protocol]:
return out, fmt.Errorf("unsupported igroup protocol %q; valid values are fcp, iscsi, mixed", in.Protocol)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add initiators field to create_igroup for atomic create-with-membership

3 participants