From d5b84d8b7684ba743eb306d0fa1747954b224b56 Mon Sep 17 00:00:00 2001 From: hardikl Date: Tue, 31 Mar 2026 18:09:49 +0530 Subject: [PATCH 1/7] feat: adding nvme subsystem with host tools --- descriptions/descriptions.go | 7 + docs/examples.md | 12 + integration/test/nvme_service_test.go | 78 ------ integration/test/nvme_test.go | 138 ++++++++++ ontap/ontap.go | 17 ++ rest/nvme.go | 281 +++++++++++++++++++ rest/nvmeService.go | 96 ------- server/nvme.go | 370 ++++++++++++++++++++++++++ server/nvmeService.go | 126 --------- server/server.go | 9 + tool/tool.go | 18 ++ 11 files changed, 852 insertions(+), 300 deletions(-) delete mode 100644 integration/test/nvme_service_test.go create mode 100644 integration/test/nvme_test.go create mode 100644 rest/nvme.go delete mode 100644 rest/nvmeService.go create mode 100644 server/nvme.go delete mode 100644 server/nvmeService.go diff --git a/descriptions/descriptions.go b/descriptions/descriptions.go index 0da2bd7..d70f15b 100644 --- a/descriptions/descriptions.go +++ b/descriptions/descriptions.go @@ -76,6 +76,13 @@ const CreateNVMeService = `Create NVMe service on a cluster by cluster name.` const UpdateNVMeService = `Update NVMe service on a cluster by cluster name.` const DeleteNVMeService = `Delete NVMe service on a cluster by cluster name.` +const CreateNVMeSubsystem = `Create NVMe subsystem on a cluster by cluster name.` +const UpdateNVMeSubsystem = `Update NVMe subsystem on a cluster by cluster name.` +const DeleteNVMeSubsystem = `Delete NVMe subsystem on a cluster by cluster name.` + +const AddNVMeSubsystemHost = `Add a host NQN to a NVMe subsystem on a cluster by cluster name.` +const RemoveNVMeSubsystemHost = `Remove a host NQN from a NVMe subsystem on a cluster by cluster name.` + const ListOntapEndpoints = `List ONTAP REST collection endpoints in the catalog. The catalog contains all endpoints — can be large. Prefer search_ontap_endpoints for targeted discovery. Use the optional 'match' parameter to filter by substring or regex pattern (e.g. "snapshot", "lun", ".*nfs.*export.*"). diff --git a/docs/examples.md b/docs/examples.md index 4a9dde7..090a977 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -261,6 +261,18 @@ Expected Response: The nvme service has been successfully created. Expected Response: The nvme service has been successfully updated. +- On the umeng-aff300-05-06 cluster, create nvme subsystem sys1 with linux os on the marketing svm + +Expected Response: The nvme subsystem has been successfully created. + +- On the umeng-aff300-05-06 cluster, add host nqn as nqn.1992-01.example.com:host1 in sys1 nvme subsystem linux os in marketing svm + +Expected Response: The nvme subsystem Host added successfully. + +- On the umeng-aff300-05-06 cluster, delete nvme subsystem sys2 with linux os in marketing svm + +Expected Response: The nvme subsystem deleted successfully. + --- ### Querying Specific Fields diff --git a/integration/test/nvme_service_test.go b/integration/test/nvme_service_test.go deleted file mode 100644 index dd95902..0000000 --- a/integration/test/nvme_service_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "context" - "crypto/tls" - "log/slog" - "net/http" - "testing" - "time" - - "github.com/netapp/ontap-mcp/config" -) - -const SarCluster = "sar" -const SarClusterStr = "On the " + SarCluster + " cluster, " - -func TestNVMeService(t *testing.T) { - SkipIfMissing(t, CheckTools) - - tests := []struct { - name string - input string - expectedOntapErr string - verifyAPI ontapVerifier - }{ - { - name: "Clean NVMe service", - input: SarClusterStr + "delete nvme service in marketing svm", - expectedOntapErr: "because it does not exist", - verifyAPI: ontapVerifier{api: "api/protocols/nvme/services?svm.name=marketing", validationFunc: deleteObject}, - }, - { - name: "Create NVMe service", - input: SarClusterStr + "create nvme service on the marketing svm", - expectedOntapErr: "", - verifyAPI: ontapVerifier{api: "api/protocols/nvme/services?svm.name=marketing", validationFunc: createObject}, - }, - { - name: "Update NVMe service", - input: SarClusterStr + "update nvme service to disable on the marketing svm", - expectedOntapErr: "", - verifyAPI: ontapVerifier{}, - }, - { - name: "Clean NVMe service", - input: SarClusterStr + "delete nvme service in marketing svm", - expectedOntapErr: "because it does not exist", - verifyAPI: ontapVerifier{api: "api/protocols/nvme/services?svm.name=marketing", validationFunc: deleteObject}, - }, - } - - cfg, err := config.ReadConfig(ConfigFile) - if err != nil { - t.Fatalf("Error parsing the config: %v", err) - } - - poller := cfg.Pollers[SarCluster] - transport := &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: poller.UseInsecureTLS, // #nosec G402 - }, - } - client := &http.Client{Transport: transport, Timeout: 10 * time.Second} - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - slog.Debug("", slog.String("Input", tt.input)) - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) - defer cancel() - if _, err = testAgent.ChatWithResponse(ctx, t, tt.input, tt.expectedOntapErr); err != nil { - slog.Error("Error processing input", slog.Any("error", err)) - } - if tt.verifyAPI.api != "" && !tt.verifyAPI.validationFunc(t, tt.verifyAPI.api, poller, client) { - t.Errorf("Error while accessing the object via prompt %s", tt.input) - } - }) - } -} diff --git a/integration/test/nvme_test.go b/integration/test/nvme_test.go new file mode 100644 index 0000000..75e0465 --- /dev/null +++ b/integration/test/nvme_test.go @@ -0,0 +1,138 @@ +package main + +import ( + "context" + "crypto/tls" + "log/slog" + "net/http" + "testing" + "time" + + "github.com/netapp/ontap-mcp/config" +) + +const SarCluster = "sar" +const SarClusterStr = "On the " + SarCluster + " cluster, " + +func TestNVMeService(t *testing.T) { + SkipIfMissing(t, CheckTools) + + tests := []struct { + name string + input string + expectedOntapErr string + verifyAPI ontapVerifier + }{ + { + name: "Update NVMe service", + input: SarClusterStr + "update nvme service to disable on the marketing svm", + expectedOntapErr: "because it does not exist", + verifyAPI: ontapVerifier{}, + }, + { + name: "Clean NVMe service", + input: SarClusterStr + "delete nvme service in marketing svm", + expectedOntapErr: "because it does not exist", + verifyAPI: ontapVerifier{api: "api/protocols/nvme/services?svm.name=marketing", validationFunc: deleteObject}, + }, + { + name: "Create NVMe service", + input: SarClusterStr + "create nvme service on the marketing svm", + expectedOntapErr: "", + verifyAPI: ontapVerifier{api: "api/protocols/nvme/services?svm.name=marketing", validationFunc: createObject}, + }, + { + name: "Create NVMe subsystem", + input: SarClusterStr + "create nvme subsystem sys1 with linux os on the marketing svm", + expectedOntapErr: "", + verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=sys1", validationFunc: createObject}, + }, + { + name: "Create NVMe subsystem", + input: SarClusterStr + "create nvme subsystem sys2 with linux os and with host nqns as nqn.1992-01.example.com:host1, nqn.1992-01.example.com:host2 on the marketing svm", + expectedOntapErr: "", + verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=sys2", validationFunc: createObject}, + }, + { + name: "Update NVMe subsystem", + input: SarClusterStr + "add comment as `comment about the` in sys1 nvme subsystem linux os on the marketing svm", + expectedOntapErr: "", + verifyAPI: ontapVerifier{}, + }, + { + name: "Add host in NVMe subsystem", + input: SarClusterStr + "add host nqn as nqn.1992-01.example.com:host1 in sys1 nvme subsystem linux os in marketing svm", + expectedOntapErr: "", + verifyAPI: ontapVerifier{}, + }, + { + name: "Remove host in NVMe subsystem", + input: SarClusterStr + "remove host nqn as nqn.1992-01.example.com:host1 in sys1 nvme subsystem linux os in marketing svm", + expectedOntapErr: "", + verifyAPI: ontapVerifier{}, + }, + { + name: "Remove host in NVMe subsystem", + input: SarClusterStr + "remove host nqn as nqn.1992-01.example.com:host1 in sys2 nvme subsystem linux os in marketing svm", + expectedOntapErr: "", + verifyAPI: ontapVerifier{}, + }, + { + name: "Remove host in NVMe subsystem", + input: SarClusterStr + "remove host nqn as nqn.1992-01.example.com:host2 in sys2 nvme subsystem linux os in marketing svm", + expectedOntapErr: "", + verifyAPI: ontapVerifier{}, + }, + { + name: "Clean NVMe subsystem", + input: SarClusterStr + "delete nvme subsystem sys1 with linux os in marketing svm", + expectedOntapErr: "because it does not exist", + verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=sys1", validationFunc: deleteObject}, + }, + { + name: "Clean NVMe subsystem", + input: SarClusterStr + "delete nvme subsystem sys2 with linux os in marketing svm", + expectedOntapErr: "because it does not exist", + verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=sys2", validationFunc: deleteObject}, + }, + { + name: "Update NVMe service", + input: SarClusterStr + "update nvme service to disable on the marketing svm", + expectedOntapErr: "", + verifyAPI: ontapVerifier{}, + }, + { + name: "Clean NVMe service", + input: SarClusterStr + "delete nvme service in marketing svm", + expectedOntapErr: "because it does not exist", + verifyAPI: ontapVerifier{api: "api/protocols/nvme/services?svm.name=marketing", validationFunc: deleteObject}, + }, + } + + cfg, err := config.ReadConfig(ConfigFile) + if err != nil { + t.Fatalf("Error parsing the config: %v", err) + } + + poller := cfg.Pollers[SarCluster] + transport := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: poller.UseInsecureTLS, // #nosec G402 + }, + } + client := &http.Client{Transport: transport, Timeout: 10 * time.Second} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + slog.Debug("", slog.String("Input", tt.input)) + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) + defer cancel() + if _, err = testAgent.ChatWithResponse(ctx, t, tt.input, tt.expectedOntapErr); err != nil { + slog.Error("Error processing input", slog.Any("error", err)) + } + if tt.verifyAPI.api != "" && !tt.verifyAPI.validationFunc(t, tt.verifyAPI.api, poller, client) { + t.Errorf("Error while accessing the object via prompt %s", tt.input) + } + }) + } +} diff --git a/ontap/ontap.go b/ontap/ontap.go index 958fd3e..af476c6 100644 --- a/ontap/ontap.go +++ b/ontap/ontap.go @@ -194,6 +194,23 @@ type NVMeService struct { Enabled string `json:"enabled,omitzero" jsonschema:"admin state of the NVMe service"` } +type NVMeSubsystem struct { + SVM NameAndUUID `json:"svm,omitzero" jsonschema:"svm name"` + Name string `json:"name,omitzero" jsonschema:"name for NVMe subsystem"` + OSType string `json:"os_type,omitzero" jsonschema:"operating system of the NVMe subsystem's hosts"` + Hosts []Hosts `json:"hosts,omitzero" jsonschema:"NVMe hosts configured for access to the NVMe subsystem"` + Comment string `json:"comment,omitzero" jsonschema:"configurable comment for the NVMe subsystem"` +} + +type Hosts struct { + NQN string `json:"nqn,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe storage target"` +} + +type NVMeSubsystemHost struct { + NQN string `json:"nqn,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe storage target"` + Records []Hosts `json:"records,omitzero" jsonschema:"array of NVMe hosts specified to add multiple NVMe hosts to an NVMe subsystem"` +} + const ( ASAr2 = "asar2" CDOT = "cdot" diff --git a/rest/nvme.go b/rest/nvme.go new file mode 100644 index 0000000..63d26a1 --- /dev/null +++ b/rest/nvme.go @@ -0,0 +1,281 @@ +package rest + +import ( + "context" + "fmt" + "github.com/netapp/ontap-mcp/ontap" + "net/http" + "net/url" +) + +func (c *Client) CreateNVMeService(ctx context.Context, nvmeService ontap.NVMeService) error { + var ( + statusCode int + ) + responseHeaders := http.Header{} + + builder := c.baseRequestBuilder(`/api/protocols/nvme/services`, &statusCode, responseHeaders). + BodyJSON(nvmeService) + + if err := c.buildAndExecuteRequest(ctx, builder); err != nil { + return err + } + + return c.checkStatus(statusCode) +} + +func (c *Client) UpdateNVMeService(ctx context.Context, svmName string, nvmeService ontap.NVMeService) error { + var ( + statusCode int + nvmeSr ontap.GetData + ) + + responseHeaders := http.Header{} + + params := url.Values{} + params.Set("svm.name", svmName) + + builder := c.baseRequestBuilder(`/api/protocols/nvme/services`, &statusCode, responseHeaders). + Params(params). + ToJSON(&nvmeSr) + + err := c.buildAndExecuteRequest(ctx, builder) + + if err != nil { + return err + } + + if nvmeSr.NumRecords == 0 { + return fmt.Errorf("failed to get detail of nvme service in svm %s because it does not exist", svmName) + } + + builder = c.baseRequestBuilder(`/api/protocols/nvme/services/`+nvmeSr.Records[0].Svm.UUID, &statusCode, responseHeaders). + BodyJSON(nvmeService). + Patch() + + if err := c.buildAndExecuteRequest(ctx, builder); err != nil { + return err + } + + return c.checkStatus(statusCode) +} + +func (c *Client) DeleteNVMeService(ctx context.Context, svmName string) error { + var ( + statusCode int + nvmeSr ontap.GetData + ) + + responseHeaders := http.Header{} + + params := url.Values{} + params.Set("svm.name", svmName) + + builder := c.baseRequestBuilder(`/api/protocols/nvme/services`, &statusCode, responseHeaders). + Params(params). + ToJSON(&nvmeSr) + + err := c.buildAndExecuteRequest(ctx, builder) + + if err != nil { + return err + } + + if nvmeSr.NumRecords == 0 { + return fmt.Errorf("failed to get detail of nvme service in svm %s because it does not exist", svmName) + } + + builder = c.baseRequestBuilder(`/api/protocols/nvme/services/`+nvmeSr.Records[0].Svm.UUID, &statusCode, responseHeaders). + Delete() + + if err := c.buildAndExecuteRequest(ctx, builder); err != nil { + return err + } + + return c.checkStatus(statusCode) +} + +func (c *Client) CreateNVMeSubsystem(ctx context.Context, nvmeSubsystem ontap.NVMeSubsystem) error { + var ( + statusCode int + ) + responseHeaders := http.Header{} + + builder := c.baseRequestBuilder(`/api/protocols/nvme/subsystems`, &statusCode, responseHeaders). + BodyJSON(nvmeSubsystem) + + if err := c.buildAndExecuteRequest(ctx, builder); err != nil { + return err + } + + return c.checkStatus(statusCode) +} + +func (c *Client) UpdateNVMeSubsystem(ctx context.Context, svmName string, name string, osType string, nvmeSubsystem ontap.NVMeSubsystem) error { + var ( + statusCode int + nvmeSs ontap.GetData + ) + + responseHeaders := http.Header{} + + params := url.Values{} + params.Set("svm.name", svmName) + params.Set("name", name) + params.Set("os_type", osType) + + builder := c.baseRequestBuilder(`/api/protocols/nvme/subsystems`, &statusCode, responseHeaders). + Params(params). + ToJSON(&nvmeSs) + + err := c.buildAndExecuteRequest(ctx, builder) + + if err != nil { + return err + } + + if nvmeSs.NumRecords == 0 { + return fmt.Errorf("failed to get detail of nvme subsystem of name %s in svm %s because it does not exist", name, svmName) + } + + if nvmeSs.NumRecords != 1 { + return fmt.Errorf("failed to update NVMe subsystem %s in svm=%s because there are %d matching records", + name, svmName, nvmeSs.NumRecords) + } + + builder = c.baseRequestBuilder(`/api/protocols/nvme/subsystems/`+nvmeSs.Records[0].UUID, &statusCode, responseHeaders). + BodyJSON(nvmeSubsystem). + Patch() + + if err := c.buildAndExecuteRequest(ctx, builder); err != nil { + return err + } + + return c.checkStatus(statusCode) +} + +func (c *Client) DeleteNVMeSubsystem(ctx context.Context, svmName string, name string, osType string) error { + var ( + statusCode int + nvmeSs ontap.GetData + ) + + responseHeaders := http.Header{} + + params := url.Values{} + params.Set("svm.name", svmName) + params.Set("name", name) + params.Set("os_type", osType) + + builder := c.baseRequestBuilder(`/api/protocols/nvme/subsystems`, &statusCode, responseHeaders). + Params(params). + ToJSON(&nvmeSs) + + err := c.buildAndExecuteRequest(ctx, builder) + + if err != nil { + return err + } + + if nvmeSs.NumRecords == 0 { + return fmt.Errorf("failed to get detail of nvme subsystem of name %s in svm %s because it does not exist", name, svmName) + } + + if nvmeSs.NumRecords != 1 { + return fmt.Errorf("failed to delete NVMe subsystem %s in svm=%s because there are %d matching records", + name, svmName, nvmeSs.NumRecords) + } + + builder = c.baseRequestBuilder(`/api/protocols/nvme/subsystems/`+nvmeSs.Records[0].UUID, &statusCode, responseHeaders). + Delete() + + if err := c.buildAndExecuteRequest(ctx, builder); err != nil { + return err + } + + return c.checkStatus(statusCode) +} + +func (c *Client) AddNVMeSubsystemHost(ctx context.Context, svmName string, name string, osType string, nvmeSubsystemHost ontap.NVMeSubsystemHost) error { + var ( + statusCode int + nvmeSs ontap.GetData + ) + + responseHeaders := http.Header{} + + params := url.Values{} + params.Set("svm.name", svmName) + params.Set("name", name) + params.Set("os_type", osType) + + builder := c.baseRequestBuilder(`/api/protocols/nvme/subsystems`, &statusCode, responseHeaders). + Params(params). + ToJSON(&nvmeSs) + + err := c.buildAndExecuteRequest(ctx, builder) + + if err != nil { + return err + } + + if nvmeSs.NumRecords == 0 { + return fmt.Errorf("failed to get detail of nvme subsystem of name %s in svm %s because it does not exist", name, svmName) + } + + if nvmeSs.NumRecords != 1 { + return fmt.Errorf("failed to get NVMe subsystem %s in svm=%s because there are %d matching records", + name, svmName, nvmeSs.NumRecords) + } + + builder2 := c.baseRequestBuilder(`/api/protocols/nvme/subsystems/`+nvmeSs.Records[0].UUID+`/hosts`, &statusCode, responseHeaders). + BodyJSON(nvmeSubsystemHost) + + if err := c.buildAndExecuteRequest(ctx, builder2); err != nil { + return err + } + + return c.checkStatus(statusCode) +} + +func (c *Client) RemoveNVMeSubsystemHost(ctx context.Context, svmName string, name string, osType string, nqn string) error { + var ( + statusCode int + nvmeSs ontap.GetData + ) + + responseHeaders := http.Header{} + + params := url.Values{} + params.Set("svm.name", svmName) + params.Set("name", name) + params.Set("os_type", osType) + + builder := c.baseRequestBuilder(`/api/protocols/nvme/subsystems`, &statusCode, responseHeaders). + Params(params). + ToJSON(&nvmeSs) + + err := c.buildAndExecuteRequest(ctx, builder) + + if err != nil { + return err + } + + if nvmeSs.NumRecords == 0 { + return fmt.Errorf("failed to get detail of nvme subsystem of name %s in svm %s because it does not exist", name, svmName) + } + + if nvmeSs.NumRecords != 1 { + return fmt.Errorf("failed to get NVMe subsystem %s in svm=%s because there are %d matching records", + name, svmName, nvmeSs.NumRecords) + } + + builder2 := c.baseRequestBuilder(`/api/protocols/nvme/subsystems/`+nvmeSs.Records[0].UUID+`/hosts/`+nqn, &statusCode, responseHeaders). + Delete() + + if err := c.buildAndExecuteRequest(ctx, builder2); err != nil { + return err + } + + return c.checkStatus(statusCode) +} diff --git a/rest/nvmeService.go b/rest/nvmeService.go deleted file mode 100644 index 15f9e40..0000000 --- a/rest/nvmeService.go +++ /dev/null @@ -1,96 +0,0 @@ -package rest - -import ( - "context" - "fmt" - "github.com/netapp/ontap-mcp/ontap" - "net/http" - "net/url" -) - -func (c *Client) CreateNVMeService(ctx context.Context, nvmeService ontap.NVMeService) error { - var ( - statusCode int - ) - responseHeaders := http.Header{} - - builder := c.baseRequestBuilder(`/api/protocols/nvme/services`, &statusCode, responseHeaders). - BodyJSON(nvmeService) - - if err := c.buildAndExecuteRequest(ctx, builder); err != nil { - return err - } - - return c.checkStatus(statusCode) -} - -func (c *Client) UpdateNVMeService(ctx context.Context, svmName string, nvmeService ontap.NVMeService) error { - var ( - statusCode int - nvmeSr ontap.GetData - ) - - responseHeaders := http.Header{} - - params := url.Values{} - params.Set("svm.name", svmName) - - builder := c.baseRequestBuilder(`/api/protocols/nvme/services`, &statusCode, responseHeaders). - Params(params). - ToJSON(&nvmeSr) - - err := c.buildAndExecuteRequest(ctx, builder) - - if err != nil { - return err - } - - if nvmeSr.NumRecords == 0 { - return fmt.Errorf("failed to get detail of nvme service in svm %s because it does not exist", svmName) - } - - builder = c.baseRequestBuilder(`/api/protocols/nvme/services/`+nvmeSr.Records[0].Svm.UUID, &statusCode, responseHeaders). - BodyJSON(nvmeService). - Patch() - - if err := c.buildAndExecuteRequest(ctx, builder); err != nil { - return err - } - - return c.checkStatus(statusCode) -} - -func (c *Client) DeleteNVMeService(ctx context.Context, svmName string) error { - var ( - statusCode int - nvmeSr ontap.GetData - ) - - responseHeaders := http.Header{} - - params := url.Values{} - params.Set("svm.name", svmName) - - builder := c.baseRequestBuilder(`/api/protocols/nvme/services`, &statusCode, responseHeaders). - Params(params). - ToJSON(&nvmeSr) - - err := c.buildAndExecuteRequest(ctx, builder) - - if err != nil { - return err - } - - if nvmeSr.NumRecords == 0 { - return fmt.Errorf("failed to get detail of nvme service in svm %s because it does not exist", svmName) - } - - builder = c.baseRequestBuilder(`/api/protocols/nvme/services/`+nvmeSr.Records[0].Svm.UUID, &statusCode, responseHeaders). - Delete() - - if err := c.buildAndExecuteRequest(ctx, builder); err != nil { - return err - } - - return c.checkStatus(statusCode) -} diff --git a/server/nvme.go b/server/nvme.go new file mode 100644 index 0000000..72bbde0 --- /dev/null +++ b/server/nvme.go @@ -0,0 +1,370 @@ +package server + +import ( + "context" + "errors" + "fmt" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/netapp/ontap-mcp/ontap" + "github.com/netapp/ontap-mcp/tool" +) + +func (a *App) CreateNVMeService(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeService) (*mcp.CallToolResult, any, error) { + if !a.locks.TryLock(parameters.Cluster) { + return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil + } + defer a.locks.Unlock(parameters.Cluster) + + nvmeServiceCreate, err := newCreateNVMeService(parameters) + if err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.CreateNVMeService(ctx, nvmeServiceCreate) + + if err != nil { + return errorResult(err), nil, err + } + + responseText := "NVMe Service created successfully" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: responseText}, + }, + }, nil, nil +} + +func (a *App) UpdateNVMeService(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeService) (*mcp.CallToolResult, any, error) { + if !a.locks.TryLock(parameters.Cluster) { + return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil + } + defer a.locks.Unlock(parameters.Cluster) + + nvmeServiceUpdate, err := newUpdateNVMeService(parameters) + if err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.UpdateNVMeService(ctx, parameters.SVM, nvmeServiceUpdate) + + if err != nil { + return errorResult(err), nil, err + } + + responseText := "NVMe Service updated successfully" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: responseText}, + }, + }, nil, nil +} + +func (a *App) DeleteNVMeService(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeService) (*mcp.CallToolResult, any, error) { + if !a.locks.TryLock(parameters.Cluster) { + return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil + } + defer a.locks.Unlock(parameters.Cluster) + + if err := newDeleteNVMeService(parameters); err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.DeleteNVMeService(ctx, parameters.SVM) + + if err != nil { + return errorResult(err), nil, err + } + + responseText := "NVMe Service deleted successfully" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: responseText}, + }, + }, nil, nil +} + +func newCreateNVMeService(in tool.NVMeService) (ontap.NVMeService, error) { + out := ontap.NVMeService{} + if in.SVM == "" { + return out, errors.New("SVM name is required") + } + + out.SVM = ontap.NameAndUUID{Name: in.SVM} + out.Enabled = in.Enabled + return out, nil +} + +func newUpdateNVMeService(in tool.NVMeService) (ontap.NVMeService, error) { + out := ontap.NVMeService{} + if in.SVM == "" { + return out, errors.New("SVM name is required") + } + out.Enabled = in.Enabled + return out, nil +} + +func newDeleteNVMeService(in tool.NVMeService) error { + if in.SVM == "" { + return errors.New("SVM name is required") + } + return nil +} + +func (a *App) CreateNVMeSubsystem(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeSubsystem) (*mcp.CallToolResult, any, error) { + if !a.locks.TryLock(parameters.Cluster) { + return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil + } + defer a.locks.Unlock(parameters.Cluster) + + nvmeSubsystemCreate, err := newCreateNVMeSubsystem(parameters) + if err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.CreateNVMeSubsystem(ctx, nvmeSubsystemCreate) + + if err != nil { + return errorResult(err), nil, err + } + + responseText := "NVMe Subsystem created successfully" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: responseText}, + }, + }, nil, nil +} + +func (a *App) UpdateNVMeSubsystem(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeSubsystem) (*mcp.CallToolResult, any, error) { + if !a.locks.TryLock(parameters.Cluster) { + return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil + } + defer a.locks.Unlock(parameters.Cluster) + + nvmeSubsystemUpdate, err := newUpdateNVMeSubsystem(parameters) + if err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.UpdateNVMeSubsystem(ctx, parameters.SVM, parameters.Name, parameters.OSType, nvmeSubsystemUpdate) + + if err != nil { + return errorResult(err), nil, err + } + + responseText := "NVMe Subsystem updated successfully" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: responseText}, + }, + }, nil, nil +} + +func (a *App) DeleteNVMeSubsystem(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeSubsystem) (*mcp.CallToolResult, any, error) { + if !a.locks.TryLock(parameters.Cluster) { + return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil + } + defer a.locks.Unlock(parameters.Cluster) + + if err := newDeleteNVMeSubsystem(parameters); err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.DeleteNVMeSubsystem(ctx, parameters.SVM, parameters.Name, parameters.OSType) + + if err != nil { + return errorResult(err), nil, err + } + + responseText := "NVMe Subsystem deleted successfully" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: responseText}, + }, + }, nil, nil +} + +func newCreateNVMeSubsystem(in tool.NVMeSubsystem) (ontap.NVMeSubsystem, error) { + out := ontap.NVMeSubsystem{} + if in.SVM == "" { + return out, errors.New("SVM name is required") + } + if in.Name == "" { + return out, errors.New("NVMe subsystem name is required") + } + if in.OSType == "" { + return out, errors.New("OS type is required") + } + + out.SVM = ontap.NameAndUUID{Name: in.SVM} + out.Name = in.Name + out.OSType = in.OSType + + for _, nqn := range in.HostNQNs { + out.Hosts = append(out.Hosts, ontap.Hosts{NQN: nqn}) + } + + return out, nil +} + +func newUpdateNVMeSubsystem(in tool.NVMeSubsystem) (ontap.NVMeSubsystem, error) { + out := ontap.NVMeSubsystem{} + + if in.SVM == "" { + return out, errors.New("SVM name is required") + } + if in.Name == "" { + return out, errors.New("NVMe subsystem name is required") + } + if in.OSType == "" { + return out, errors.New("OS type is required") + } + + if in.Comment != "" { + out.Comment = in.Comment + } + return out, nil +} + +func newDeleteNVMeSubsystem(in tool.NVMeSubsystem) error { + if in.SVM == "" { + return errors.New("SVM name is required") + } + if in.Name == "" { + return errors.New("NVMe subsystem name is required") + } + if in.OSType == "" { + return errors.New("OS type is required") + } + return nil +} + +func (a *App) AddNVMeSubsystemHost(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeSubsystemHost) (*mcp.CallToolResult, any, error) { + if !a.locks.TryLock(parameters.Cluster) { + return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil + } + defer a.locks.Unlock(parameters.Cluster) + + nvmeSubsystemHostAdd, err := newAddNVMeSubsystemHost(parameters) + if err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.AddNVMeSubsystemHost(ctx, parameters.SVM, parameters.Name, parameters.OSType, nvmeSubsystemHostAdd) + + if err != nil { + return errorResult(err), nil, err + } + + responseText := "NVMe Subsystem Host added successfully" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: responseText}, + }, + }, nil, nil +} + +func (a *App) RemoveNVMeSubsystemHost(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeSubsystemHost) (*mcp.CallToolResult, any, error) { + if !a.locks.TryLock(parameters.Cluster) { + return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil + } + defer a.locks.Unlock(parameters.Cluster) + + if err := newRemoveNVMeSubsystemHost(parameters); err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.RemoveNVMeSubsystemHost(ctx, parameters.SVM, parameters.Name, parameters.OSType, parameters.NQN) + + if err != nil { + return errorResult(err), nil, err + } + + responseText := "NVMe Subsystem Host removed successfully" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: responseText}, + }, + }, nil, nil +} + +func newAddNVMeSubsystemHost(in tool.NVMeSubsystemHost) (ontap.NVMeSubsystemHost, error) { + out := ontap.NVMeSubsystemHost{} + if in.SVM == "" { + return out, errors.New("SVM name is required") + } + if in.Name == "" { + return out, errors.New("NVMe subsystem name is required") + } + if in.OSType == "" { + return out, errors.New("OS type is required") + } + + if in.NQN == "" && len(in.Records) == 0 { + return out, errors.New("NVMe subsystem host NQN OR Array of NQNs are required") + } + if in.NQN != "" { + out.NQN = in.NQN + } + + for _, nqn := range in.Records { + out.Records = append(out.Records, ontap.Hosts{NQN: nqn}) + } + return out, nil +} + +func newRemoveNVMeSubsystemHost(in tool.NVMeSubsystemHost) error { + if in.SVM == "" { + return errors.New("SVM name is required") + } + if in.Name == "" { + return errors.New("NVMe subsystem name is required") + } + if in.OSType == "" { + return errors.New("OS type is required") + } + if in.NQN == "" { + return errors.New("NQN is required") + } + return nil +} diff --git a/server/nvmeService.go b/server/nvmeService.go deleted file mode 100644 index e23c3c9..0000000 --- a/server/nvmeService.go +++ /dev/null @@ -1,126 +0,0 @@ -package server - -import ( - "context" - "errors" - "fmt" - "github.com/modelcontextprotocol/go-sdk/mcp" - "github.com/netapp/ontap-mcp/ontap" - "github.com/netapp/ontap-mcp/tool" -) - -func (a *App) CreateNVMeService(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeService) (*mcp.CallToolResult, any, error) { - if !a.locks.TryLock(parameters.Cluster) { - return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil - } - defer a.locks.Unlock(parameters.Cluster) - - nvmeServiceCreate, err := newCreateNVMeService(parameters) - if err != nil { - return nil, nil, err - } - - client, err := a.getClient(parameters.Cluster) - if err != nil { - return errorResult(err), nil, err - } - err = client.CreateNVMeService(ctx, nvmeServiceCreate) - - if err != nil { - return errorResult(err), nil, err - } - - responseText := "NVMe Service created successfully" - - return &mcp.CallToolResult{ - Content: []mcp.Content{ - &mcp.TextContent{Text: responseText}, - }, - }, nil, nil -} - -func (a *App) UpdateNVMeService(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeService) (*mcp.CallToolResult, any, error) { - if !a.locks.TryLock(parameters.Cluster) { - return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil - } - defer a.locks.Unlock(parameters.Cluster) - - nvmeServiceUpdate, err := newUpdateNVMeService(parameters) - if err != nil { - return nil, nil, err - } - - client, err := a.getClient(parameters.Cluster) - if err != nil { - return errorResult(err), nil, err - } - err = client.UpdateNVMeService(ctx, parameters.SVM, nvmeServiceUpdate) - - if err != nil { - return errorResult(err), nil, err - } - - responseText := "NVMe Service updated successfully" - - return &mcp.CallToolResult{ - Content: []mcp.Content{ - &mcp.TextContent{Text: responseText}, - }, - }, nil, nil -} - -func (a *App) DeleteNVMeService(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeService) (*mcp.CallToolResult, any, error) { - if !a.locks.TryLock(parameters.Cluster) { - return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil - } - defer a.locks.Unlock(parameters.Cluster) - - if err := newDeleteNVMeService(parameters); err != nil { - return nil, nil, err - } - - client, err := a.getClient(parameters.Cluster) - if err != nil { - return errorResult(err), nil, err - } - err = client.DeleteNVMeService(ctx, parameters.SVM) - - if err != nil { - return errorResult(err), nil, err - } - - responseText := "NVMe Service deleted successfully" - - return &mcp.CallToolResult{ - Content: []mcp.Content{ - &mcp.TextContent{Text: responseText}, - }, - }, nil, nil -} - -func newCreateNVMeService(in tool.NVMeService) (ontap.NVMeService, error) { - out := ontap.NVMeService{} - if in.SVM == "" { - return out, errors.New("SVM name is required") - } - - out.SVM = ontap.NameAndUUID{Name: in.SVM} - out.Enabled = in.Enabled - return out, nil -} - -func newUpdateNVMeService(in tool.NVMeService) (ontap.NVMeService, error) { - out := ontap.NVMeService{} - if in.SVM == "" { - return out, errors.New("SVM name is required") - } - out.Enabled = in.Enabled - return out, nil -} - -func newDeleteNVMeService(in tool.NVMeService) error { - if in.SVM == "" { - return errors.New("SVM name is required") - } - return nil -} diff --git a/server/server.go b/server/server.go index fdf9040..8ce0a00 100644 --- a/server/server.go +++ b/server/server.go @@ -133,6 +133,15 @@ func (a *App) createMCPServer() *mcp.Server { addTool(a, server, "update_nvme_service", descriptions.UpdateNVMeService, updateAnnotation, a.UpdateNVMeService) addTool(a, server, "delete_nvme_service", descriptions.DeleteNVMeService, deleteAnnotation, a.DeleteNVMeService) + // operation on NVMe subsystem object + addTool(a, server, "create_nvme_subsystem", descriptions.CreateNVMeSubsystem, createAnnotation, a.CreateNVMeSubsystem) + addTool(a, server, "update_nvme_subsystem", descriptions.UpdateNVMeSubsystem, updateAnnotation, a.UpdateNVMeSubsystem) + addTool(a, server, "delete_nvme_subsystem", descriptions.DeleteNVMeSubsystem, deleteAnnotation, a.DeleteNVMeSubsystem) + + // operation on NVMe subsystem host object + addTool(a, server, "add_nvme_subsystem_host", descriptions.AddNVMeSubsystemHost, createAnnotation, a.AddNVMeSubsystemHost) + addTool(a, server, "remove_nvme_subsystem_host", descriptions.RemoveNVMeSubsystemHost, deleteAnnotation, a.RemoveNVMeSubsystemHost) + if a.catalog != nil { addTool(a, server, "list_ontap_endpoints", descriptions.ListOntapEndpoints, readOnlyAnnotation, a.ListOntapEndpoints) addTool(a, server, "search_ontap_endpoints", descriptions.SearchOntapEndpoints, readOnlyAnnotation, a.SearchOntapEndpoints) diff --git a/tool/tool.go b/tool/tool.go index 2a82c12..8928917 100644 --- a/tool/tool.go +++ b/tool/tool.go @@ -115,6 +115,24 @@ type NVMeService struct { Enabled string `json:"enabled,omitzero" jsonschema:"admin state of the NVMe service"` } +type NVMeSubsystem struct { + Cluster string `json:"cluster_name" jsonschema:"cluster name"` + SVM string `json:"svm_name" jsonschema:"SVM name"` + Name string `json:"name" jsonschema:"name for NVMe subsystem"` + OSType string `json:"os_type" jsonschema:"operating system of the NVMe subsystem's hosts"` + HostNQNs []string `json:"hosts_nqns,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe storage target"` + Comment string `json:"comment,omitzero" jsonschema:"configurable comment for the NVMe subsystem"` +} + +type NVMeSubsystemHost struct { + Cluster string `json:"cluster_name" jsonschema:"cluster name"` + SVM string `json:"svm_name" jsonschema:"SVM name"` + Name string `json:"name" jsonschema:"name for NVMe subsystem"` + OSType string `json:"os_type" jsonschema:"operating system of the NVMe subsystem's hosts"` + NQN string `json:"nqn,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe storage target"` + Records []string `json:"records,omitzero" jsonschema:"array of NVMe hosts specified to add multiple NVMe hosts to an NVMe subsystem"` +} + type OntapGetParams struct { Cluster string `json:"cluster_name" jsonschema:"cluster name, from list_registered_clusters"` Fields string `json:"fields,omitzero" jsonschema:"comma-separated dot-notation fields to return, e.g. \"name,svm.name,space.size\" — use space.* to expand all space sub-fields"` From 1923d9bc0a33a22fd4daf927892a95f976fd6c04 Mon Sep 17 00:00:00 2001 From: hardikl Date: Wed, 1 Apr 2026 17:35:51 +0530 Subject: [PATCH 2/7] feat: adding nvme namespace and subsystem-map tools --- descriptions/descriptions.go | 7 + integration/test/nvme_test.go | 14 +- ontap/ontap.go | 30 ++++- rest/nvme.go | 185 +++++++++++++++++++++++++- server/nvme.go | 237 +++++++++++++++++++++++++++++++++- server/server.go | 9 ++ tool/tool.go | 30 ++++- 7 files changed, 485 insertions(+), 27 deletions(-) diff --git a/descriptions/descriptions.go b/descriptions/descriptions.go index d70f15b..6a2faaf 100644 --- a/descriptions/descriptions.go +++ b/descriptions/descriptions.go @@ -83,6 +83,13 @@ const DeleteNVMeSubsystem = `Delete NVMe subsystem on a cluster by cluster name. const AddNVMeSubsystemHost = `Add a host NQN to a NVMe subsystem on a cluster by cluster name.` const RemoveNVMeSubsystemHost = `Remove a host NQN from a NVMe subsystem on a cluster by cluster name.` +const CreateNVMeNamespace = `Create NVMe namespace on a cluster by cluster name.` +const UpdateNVMeNamespace = `Update NVMe namespace on a cluster by cluster name.` +const DeleteNVMeNamespace = `Delete NVMe namespace on a cluster by cluster name.` + +const CreateNVMeSubsystemMap = `Create NVMe subsystem map on a cluster by cluster name.` +const DeleteNVMeSubsystemMap = `Delete NVMe subsystem map on a cluster by cluster name.` + const ListOntapEndpoints = `List ONTAP REST collection endpoints in the catalog. The catalog contains all endpoints — can be large. Prefer search_ontap_endpoints for targeted discovery. Use the optional 'match' parameter to filter by substring or regex pattern (e.g. "snapshot", "lun", ".*nfs.*export.*"). diff --git a/integration/test/nvme_test.go b/integration/test/nvme_test.go index 75e0465..96561ed 100644 --- a/integration/test/nvme_test.go +++ b/integration/test/nvme_test.go @@ -71,18 +71,6 @@ func TestNVMeService(t *testing.T) { expectedOntapErr: "", verifyAPI: ontapVerifier{}, }, - { - name: "Remove host in NVMe subsystem", - input: SarClusterStr + "remove host nqn as nqn.1992-01.example.com:host1 in sys2 nvme subsystem linux os in marketing svm", - expectedOntapErr: "", - verifyAPI: ontapVerifier{}, - }, - { - name: "Remove host in NVMe subsystem", - input: SarClusterStr + "remove host nqn as nqn.1992-01.example.com:host2 in sys2 nvme subsystem linux os in marketing svm", - expectedOntapErr: "", - verifyAPI: ontapVerifier{}, - }, { name: "Clean NVMe subsystem", input: SarClusterStr + "delete nvme subsystem sys1 with linux os in marketing svm", @@ -91,7 +79,7 @@ func TestNVMeService(t *testing.T) { }, { name: "Clean NVMe subsystem", - input: SarClusterStr + "delete nvme subsystem sys2 with linux os in marketing svm", + input: SarClusterStr + "delete nvme subsystem sys2 with linux os in marketing svm with allow_delete_while_mapped and allow_delete_with_hosts", expectedOntapErr: "because it does not exist", verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=sys2", validationFunc: deleteObject}, }, diff --git a/ontap/ontap.go b/ontap/ontap.go index af476c6..8d5558a 100644 --- a/ontap/ontap.go +++ b/ontap/ontap.go @@ -195,11 +195,13 @@ type NVMeService struct { } type NVMeSubsystem struct { - SVM NameAndUUID `json:"svm,omitzero" jsonschema:"svm name"` - Name string `json:"name,omitzero" jsonschema:"name for NVMe subsystem"` - OSType string `json:"os_type,omitzero" jsonschema:"operating system of the NVMe subsystem's hosts"` - Hosts []Hosts `json:"hosts,omitzero" jsonschema:"NVMe hosts configured for access to the NVMe subsystem"` - Comment string `json:"comment,omitzero" jsonschema:"configurable comment for the NVMe subsystem"` + SVM NameAndUUID `json:"svm,omitzero" jsonschema:"svm name"` + Name string `json:"name,omitzero" jsonschema:"name for NVMe subsystem"` + OSType string `json:"os_type,omitzero" jsonschema:"operating system of the NVMe subsystem's hosts"` + Hosts []Hosts `json:"hosts,omitzero" jsonschema:"NVMe hosts configured for access to the NVMe subsystem"` + Comment string `json:"comment,omitzero" jsonschema:"configurable comment for the NVMe subsystem"` + AllowDeleteWhileMapped bool `json:"allow_delete_while_mapped,omitzero" jsonschema:"Allows for the deletion of a mapped NVMe subsystem. This parameter should be used with caution."` + AllowDeleteWithHosts bool `json:"allow_delete_with_hosts,omitzero" jsonschema:"Allows for the deletion of an NVMe subsystem with NVMe hosts. This parameter should be used with caution."` } type Hosts struct { @@ -211,6 +213,24 @@ type NVMeSubsystemHost struct { Records []Hosts `json:"records,omitzero" jsonschema:"array of NVMe hosts specified to add multiple NVMe hosts to an NVMe subsystem"` } +type NVMeNamespace struct { + SVM NameAndUUID `json:"svm,omitzero" jsonschema:"svm name"` + Name string `json:"name,omitzero" jsonschema:"name for NVMe namespace"` + OSType string `json:"os_type,omitzero" jsonschema:"operating system type of the NVMe namespace"` + Space Space `json:"space,omitzero" jsonschema:"space of NVMe namespace"` + AllowDeleteWhileMapped bool `json:"allow_delete_while_mapped,omitzero" jsonschema:"Allows deletion of a mapped NVMe namespace. This parameter should be used with caution."` +} + +type Space struct { + Size string `json:"size,omitzero" jsonschema:"total provisioned size of the NVMe namespace (e.g., '100GB', '1TB')"` +} + +type NVMeSubsystemMap struct { + SVM NameAndUUID `json:"svm" jsonschema:"svm name"` + Subsystemn NameAndUUID `json:"subsystem" jsonschema:"subsystem name"` + Namespace NameAndUUID `json:"namespace" jsonschema:"namespace name"` +} + const ( ASAr2 = "asar2" CDOT = "cdot" diff --git a/rest/nvme.go b/rest/nvme.go index 63d26a1..d5a909c 100644 --- a/rest/nvme.go +++ b/rest/nvme.go @@ -6,6 +6,7 @@ import ( "github.com/netapp/ontap-mcp/ontap" "net/http" "net/url" + "strconv" ) func (c *Client) CreateNVMeService(ctx context.Context, nvmeService ontap.NVMeService) error { @@ -154,7 +155,7 @@ func (c *Client) UpdateNVMeSubsystem(ctx context.Context, svmName string, name s return c.checkStatus(statusCode) } -func (c *Client) DeleteNVMeSubsystem(ctx context.Context, svmName string, name string, osType string) error { +func (c *Client) DeleteNVMeSubsystem(ctx context.Context, svmName string, name string, osType string, allowDeleteWhileMapped bool, allowDeleteWithHosts bool) error { var ( statusCode int nvmeSs ontap.GetData @@ -186,7 +187,7 @@ func (c *Client) DeleteNVMeSubsystem(ctx context.Context, svmName string, name s name, svmName, nvmeSs.NumRecords) } - builder = c.baseRequestBuilder(`/api/protocols/nvme/subsystems/`+nvmeSs.Records[0].UUID, &statusCode, responseHeaders). + builder = c.baseRequestBuilder(`/api/protocols/nvme/subsystems/`+nvmeSs.Records[0].UUID+`?allow_delete_while_mapped=`+strconv.FormatBool(allowDeleteWhileMapped)+`&allow_delete_with_hosts=`+strconv.FormatBool(allowDeleteWithHosts), &statusCode, responseHeaders). Delete() if err := c.buildAndExecuteRequest(ctx, builder); err != nil { @@ -279,3 +280,183 @@ func (c *Client) RemoveNVMeSubsystemHost(ctx context.Context, svmName string, na return c.checkStatus(statusCode) } + +func (c *Client) CreateNVMeNamespace(ctx context.Context, nvmeNamespace ontap.NVMeNamespace) error { + var ( + statusCode int + ) + responseHeaders := http.Header{} + + builder := c.baseRequestBuilder(`/api/storage/namespaces`, &statusCode, responseHeaders). + BodyJSON(nvmeNamespace) + + if err := c.buildAndExecuteRequest(ctx, builder); err != nil { + return err + } + + return c.checkStatus(statusCode) +} + +func (c *Client) UpdateNVMeNamespace(ctx context.Context, svmName string, name string, osType string, nvmeNamespace ontap.NVMeNamespace) error { + var ( + statusCode int + nvmeNs ontap.GetData + ) + + responseHeaders := http.Header{} + + params := url.Values{} + params.Set("svm.name", svmName) + params.Set("name", name) + params.Set("os_type", osType) + + builder := c.baseRequestBuilder(`/api/storage/namespaces`, &statusCode, responseHeaders). + Params(params). + ToJSON(&nvmeNs) + + err := c.buildAndExecuteRequest(ctx, builder) + + if err != nil { + return err + } + + if nvmeNs.NumRecords == 0 { + return fmt.Errorf("failed to get detail of nvme namespace of name %s in svm %s because it does not exist", name, svmName) + } + + if nvmeNs.NumRecords != 1 { + return fmt.Errorf("failed to update NVMe namespace %s in svm=%s because there are %d matching records", + name, svmName, nvmeNs.NumRecords) + } + + builder = c.baseRequestBuilder(`/api/storage/namespaces/`+nvmeNs.Records[0].UUID, &statusCode, responseHeaders). + BodyJSON(nvmeNamespace). + Patch() + + if err := c.buildAndExecuteRequest(ctx, builder); err != nil { + return err + } + + return c.checkStatus(statusCode) +} + +func (c *Client) DeleteNVMeNamespace(ctx context.Context, svmName string, name string, osType string, allowDeleteWhileMapped bool) error { + var ( + statusCode int + nvmeNs ontap.GetData + ) + + responseHeaders := http.Header{} + + params := url.Values{} + params.Set("svm.name", svmName) + params.Set("name", name) + params.Set("os_type", osType) + + builder := c.baseRequestBuilder(`/api/storage/namespaces`, &statusCode, responseHeaders). + Params(params). + ToJSON(&nvmeNs) + + err := c.buildAndExecuteRequest(ctx, builder) + + if err != nil { + return err + } + + if nvmeNs.NumRecords == 0 { + return fmt.Errorf("failed to get detail of nvme namespace of name %s in svm %s because it does not exist", name, svmName) + } + + if nvmeNs.NumRecords != 1 { + return fmt.Errorf("failed to delete NVMe namespace %s in svm=%s because there are %d matching records", + name, svmName, nvmeNs.NumRecords) + } + + builder = c.baseRequestBuilder(`/api/storage/namespaces/`+nvmeNs.Records[0].UUID+`?allow_delete_while_mapped=`+strconv.FormatBool(allowDeleteWhileMapped), &statusCode, responseHeaders). + Delete() + + if err := c.buildAndExecuteRequest(ctx, builder); err != nil { + return err + } + + return c.checkStatus(statusCode) +} + +func (c *Client) CreateNVMeSubsystemMap(ctx context.Context, nvmeSubsystemMap ontap.NVMeSubsystemMap) error { + var ( + statusCode int + ) + + responseHeaders := http.Header{} + + builder := c.baseRequestBuilder(`/api/protocols/nvme/subsystem-maps`, &statusCode, responseHeaders). + BodyJSON(nvmeSubsystemMap) + + if err := c.buildAndExecuteRequest(ctx, builder); err != nil { + return err + } + + return c.checkStatus(statusCode) +} + +func (c *Client) DeleteNVMeSubsystemMap(ctx context.Context, svmName string, subsystemName string, namespaceName string) error { + var ( + statusCode int + nvmeSs ontap.GetData + nvmeNs ontap.GetData + ) + + responseHeaders := http.Header{} + + params := url.Values{} + params.Set("svm.name", svmName) + params.Set("name", subsystemName) + + builder := c.baseRequestBuilder(`/api/protocols/nvme/subsystems`, &statusCode, responseHeaders). + Params(params). + ToJSON(&nvmeSs) + + if err := c.buildAndExecuteRequest(ctx, builder); err != nil { + return err + } + + if nvmeSs.NumRecords == 0 { + return fmt.Errorf("failed to get detail of nvme subsystem of name %s in svm %s because it does not exist", subsystemName, svmName) + } + + if nvmeSs.NumRecords != 1 { + return fmt.Errorf("failed to get NVMe subsystem %s in svm=%s because there are %d matching records", + subsystemName, svmName, nvmeSs.NumRecords) + } + + responseHeaders = http.Header{} + params = url.Values{} + params.Set("svm.name", svmName) + params.Set("name", namespaceName) + + builder2 := c.baseRequestBuilder(`/api/storage/namespaces`, &statusCode, responseHeaders). + Params(params). + ToJSON(&nvmeNs) + + if err := c.buildAndExecuteRequest(ctx, builder2); err != nil { + return err + } + + if nvmeNs.NumRecords == 0 { + return fmt.Errorf("failed to get detail of nvme namespace of name %s in svm %s because it does not exist", namespaceName, svmName) + } + + if nvmeNs.NumRecords != 1 { + return fmt.Errorf("failed to get NVMe namespace %s in svm=%s because there are %d matching records", + namespaceName, svmName, nvmeNs.NumRecords) + } + + builder3 := c.baseRequestBuilder(`/api/protocols/nvme/subsystem-maps/`+nvmeSs.Records[0].UUID+`/`+nvmeNs.Records[0].UUID, &statusCode, responseHeaders). + Delete() + + if err := c.buildAndExecuteRequest(ctx, builder3); err != nil { + return err + } + + return c.checkStatus(statusCode) +} diff --git a/server/nvme.go b/server/nvme.go index 72bbde0..27f326d 100644 --- a/server/nvme.go +++ b/server/nvme.go @@ -199,7 +199,7 @@ func (a *App) DeleteNVMeSubsystem(ctx context.Context, _ *mcp.CallToolRequest, p if err != nil { return errorResult(err), nil, err } - err = client.DeleteNVMeSubsystem(ctx, parameters.SVM, parameters.Name, parameters.OSType) + err = client.DeleteNVMeSubsystem(ctx, parameters.SVM, parameters.Name, parameters.OSType, parameters.AllowDeleteWhileMapped, parameters.AllowDeleteWithHosts) if err != nil { return errorResult(err), nil, err @@ -368,3 +368,238 @@ func newRemoveNVMeSubsystemHost(in tool.NVMeSubsystemHost) error { } return nil } + +func (a *App) CreateNVMeNamespace(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeNamespace) (*mcp.CallToolResult, any, error) { + if !a.locks.TryLock(parameters.Cluster) { + return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil + } + defer a.locks.Unlock(parameters.Cluster) + + nvmeNamespaceCreate, err := newCreateNVMeNamespace(parameters) + if err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.CreateNVMeNamespace(ctx, nvmeNamespaceCreate) + + if err != nil { + return errorResult(err), nil, err + } + + responseText := "NVMe Namespace created successfully" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: responseText}, + }, + }, nil, nil +} + +func (a *App) UpdateNVMeNamespace(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeNamespace) (*mcp.CallToolResult, any, error) { + if !a.locks.TryLock(parameters.Cluster) { + return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil + } + defer a.locks.Unlock(parameters.Cluster) + + nvmeNamespaceUpdate, err := newUpdateNVMeNamespace(parameters) + if err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.UpdateNVMeNamespace(ctx, parameters.SVM, parameters.Name, parameters.OSType, nvmeNamespaceUpdate) + + if err != nil { + return errorResult(err), nil, err + } + + responseText := "NVMe Namespace updated successfully" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: responseText}, + }, + }, nil, nil +} + +func (a *App) DeleteNVMeNamespace(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeNamespace) (*mcp.CallToolResult, any, error) { + if !a.locks.TryLock(parameters.Cluster) { + return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil + } + defer a.locks.Unlock(parameters.Cluster) + + if err := newDeleteNVMeNamespace(parameters); err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.DeleteNVMeNamespace(ctx, parameters.SVM, parameters.Name, parameters.OSType, parameters.AllowDeleteWhileMapped) + + if err != nil { + return errorResult(err), nil, err + } + + responseText := "NVMe Namespace deleted successfully" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: responseText}, + }, + }, nil, nil +} + +func newCreateNVMeNamespace(in tool.NVMeNamespace) (ontap.NVMeNamespace, error) { + out := ontap.NVMeNamespace{} + if in.SVM == "" { + return out, errors.New("SVM name is required") + } + if in.Name == "" { + return out, errors.New("NVMe subsystem name is required") + } + if in.OSType == "" { + return out, errors.New("OS type is required") + } + if in.Size == "" { + return out, errors.New("size of namespace is required") + } + + out.SVM = ontap.NameAndUUID{Name: in.SVM} + out.Name = in.Name + out.OSType = in.OSType + out.Space.Size = in.Size + + return out, nil +} + +func newUpdateNVMeNamespace(in tool.NVMeNamespace) (ontap.NVMeNamespace, error) { + out := ontap.NVMeNamespace{} + + if in.SVM == "" { + return out, errors.New("SVM name is required") + } + if in.Name == "" { + return out, errors.New("NVMe subsystem name is required") + } + if in.OSType == "" { + return out, errors.New("OS type is required") + } + if in.Size != "" { + out.Space.Size = in.Size + } + + return out, nil +} + +func newDeleteNVMeNamespace(in tool.NVMeNamespace) error { + if in.SVM == "" { + return errors.New("SVM name is required") + } + if in.Name == "" { + return errors.New("NVMe subsystem name is required") + } + if in.OSType == "" { + return errors.New("OS type is required") + } + + return nil +} + +func (a *App) CreateNVMeSubsystemMap(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeSubsystemMap) (*mcp.CallToolResult, any, error) { + if !a.locks.TryLock(parameters.Cluster) { + return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil + } + defer a.locks.Unlock(parameters.Cluster) + + nvmeSubsystemMapCreate, err := newCreateNVMeSubsystemMap(parameters) + if err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.CreateNVMeSubsystemMap(ctx, nvmeSubsystemMapCreate) + + if err != nil { + return errorResult(err), nil, err + } + + responseText := "NVMe Subsystem Map create successfully" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: responseText}, + }, + }, nil, nil +} + +func (a *App) DeleteNVMeSubsystemMap(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.NVMeSubsystemMap) (*mcp.CallToolResult, any, error) { + if !a.locks.TryLock(parameters.Cluster) { + return errorResult(fmt.Errorf("another write operation is in progress on cluster %s, please try again", parameters.Cluster)), nil, nil + } + defer a.locks.Unlock(parameters.Cluster) + + if err := newDeleteNVMeSubsystemMap(parameters); err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.DeleteNVMeSubsystemMap(ctx, parameters.SVM, parameters.Subsystem, parameters.Namespace) + + if err != nil { + return errorResult(err), nil, err + } + + responseText := "NVMe Subsystem Map deleted successfully" + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: responseText}, + }, + }, nil, nil +} + +func newCreateNVMeSubsystemMap(in tool.NVMeSubsystemMap) (ontap.NVMeSubsystemMap, error) { + out := ontap.NVMeSubsystemMap{} + if in.SVM == "" { + return out, errors.New("SVM name is required") + } + if in.Subsystem == "" { + return out, errors.New("NVMe subsystem name is required") + } + if in.Namespace == "" { + return out, errors.New("NVMe namespace name is required") + } + + out.SVM.Name = in.SVM + out.Subsystemn.Name = in.Subsystem + out.Namespace.Name = in.Namespace + return out, nil +} + +func newDeleteNVMeSubsystemMap(in tool.NVMeSubsystemMap) error { + if in.SVM == "" { + return errors.New("SVM name is required") + } + if in.Subsystem == "" { + return errors.New("NVMe subsystem name is required") + } + if in.Namespace == "" { + return errors.New("NVMe namespace name is required") + } + return nil +} diff --git a/server/server.go b/server/server.go index 8ce0a00..dcd7e95 100644 --- a/server/server.go +++ b/server/server.go @@ -142,6 +142,15 @@ func (a *App) createMCPServer() *mcp.Server { addTool(a, server, "add_nvme_subsystem_host", descriptions.AddNVMeSubsystemHost, createAnnotation, a.AddNVMeSubsystemHost) addTool(a, server, "remove_nvme_subsystem_host", descriptions.RemoveNVMeSubsystemHost, deleteAnnotation, a.RemoveNVMeSubsystemHost) + // operation on NVMe namespace object + addTool(a, server, "create_nvme_namespace", descriptions.CreateNVMeNamespace, createAnnotation, a.CreateNVMeNamespace) + addTool(a, server, "update_nvme_namespace", descriptions.UpdateNVMeNamespace, updateAnnotation, a.UpdateNVMeNamespace) + addTool(a, server, "delete_nvme_namespace", descriptions.DeleteNVMeNamespace, deleteAnnotation, a.DeleteNVMeNamespace) + + // operation on NVMe subsystem map object + addTool(a, server, "create_nvme_subsystem_map", descriptions.CreateNVMeSubsystemMap, createAnnotation, a.CreateNVMeSubsystemMap) + addTool(a, server, "delete_nvme_subsystem_map", descriptions.DeleteNVMeSubsystemMap, deleteAnnotation, a.DeleteNVMeSubsystemMap) + if a.catalog != nil { addTool(a, server, "list_ontap_endpoints", descriptions.ListOntapEndpoints, readOnlyAnnotation, a.ListOntapEndpoints) addTool(a, server, "search_ontap_endpoints", descriptions.SearchOntapEndpoints, readOnlyAnnotation, a.SearchOntapEndpoints) diff --git a/tool/tool.go b/tool/tool.go index 8928917..9c034fd 100644 --- a/tool/tool.go +++ b/tool/tool.go @@ -116,12 +116,14 @@ type NVMeService struct { } type NVMeSubsystem struct { - Cluster string `json:"cluster_name" jsonschema:"cluster name"` - SVM string `json:"svm_name" jsonschema:"SVM name"` - Name string `json:"name" jsonschema:"name for NVMe subsystem"` - OSType string `json:"os_type" jsonschema:"operating system of the NVMe subsystem's hosts"` - HostNQNs []string `json:"hosts_nqns,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe storage target"` - Comment string `json:"comment,omitzero" jsonschema:"configurable comment for the NVMe subsystem"` + Cluster string `json:"cluster_name" jsonschema:"cluster name"` + SVM string `json:"svm_name" jsonschema:"SVM name"` + Name string `json:"name" jsonschema:"name for NVMe subsystem"` + OSType string `json:"os_type" jsonschema:"operating system of the NVMe subsystem's hosts"` + HostNQNs []string `json:"hosts_nqns,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe storage target"` + Comment string `json:"comment,omitzero" jsonschema:"configurable comment for the NVMe subsystem"` + AllowDeleteWhileMapped bool `json:"allow_delete_while_mapped,omitzero" jsonschema:"Allows for the deletion of a mapped NVMe subsystem. This parameter should be used with caution."` + AllowDeleteWithHosts bool `json:"allow_delete_with_hosts,omitzero" jsonschema:"Allows for the deletion of an NVMe subsystem with NVMe hosts. This parameter should be used with caution."` } type NVMeSubsystemHost struct { @@ -133,6 +135,22 @@ type NVMeSubsystemHost struct { Records []string `json:"records,omitzero" jsonschema:"array of NVMe hosts specified to add multiple NVMe hosts to an NVMe subsystem"` } +type NVMeNamespace struct { + Cluster string `json:"cluster_name" jsonschema:"cluster name"` + SVM string `json:"svm_name" jsonschema:"SVM name"` + Name string `json:"name" jsonschema:"name for NVMe namespace"` + OSType string `json:"os_type" jsonschema:"operating system type of the NVMe namespace"` + Size string `json:"space.size" jsonschema:"total provisioned size of the NVMe namespace (e.g., '100GB', '1TB')"` + AllowDeleteWhileMapped bool `json:"allow_delete_while_mapped,omitzero" jsonschema:"Allows deletion of a mapped NVMe namespace. This parameter should be used with caution."` +} + +type NVMeSubsystemMap struct { + Cluster string `json:"cluster_name" jsonschema:"cluster name"` + SVM string `json:"svm_name" jsonschema:"SVM name"` + Subsystem string `json:"subsystem_name" jsonschema:"name for NVMe subsystem"` + Namespace string `json:"namespace_name" jsonschema:"name for NVMe namespace"` +} + type OntapGetParams struct { Cluster string `json:"cluster_name" jsonschema:"cluster name, from list_registered_clusters"` Fields string `json:"fields,omitzero" jsonschema:"comma-separated dot-notation fields to return, e.g. \"name,svm.name,space.size\" — use space.* to expand all space sub-fields"` From 06c03af1f10e72a7a640464a7619770e244206cf Mon Sep 17 00:00:00 2001 From: hardikl Date: Wed, 1 Apr 2026 21:47:18 +0530 Subject: [PATCH 3/7] feat: handled copilot comments --- descriptions/descriptions.go | 4 +- docs/examples.md | 6 +- integration/test/nvme_service_test.go | 81 --------------------------- integration/test/nvme_test.go | 5 +- ontap/ontap.go | 16 +++--- rest/nvme.go | 2 +- server/nvme.go | 26 +++++++-- tool/tool.go | 4 +- 8 files changed, 40 insertions(+), 104 deletions(-) delete mode 100644 integration/test/nvme_service_test.go diff --git a/descriptions/descriptions.go b/descriptions/descriptions.go index 6a2faaf..ac0376e 100644 --- a/descriptions/descriptions.go +++ b/descriptions/descriptions.go @@ -80,8 +80,8 @@ const CreateNVMeSubsystem = `Create NVMe subsystem on a cluster by cluster name. const UpdateNVMeSubsystem = `Update NVMe subsystem on a cluster by cluster name.` const DeleteNVMeSubsystem = `Delete NVMe subsystem on a cluster by cluster name.` -const AddNVMeSubsystemHost = `Add a host NQN to a NVMe subsystem on a cluster by cluster name.` -const RemoveNVMeSubsystemHost = `Remove a host NQN from a NVMe subsystem on a cluster by cluster name.` +const AddNVMeSubsystemHost = `Add a host NQN to an NVMe subsystem on a cluster by cluster name.` +const RemoveNVMeSubsystemHost = `Remove a host NQN from an NVMe subsystem on a cluster by cluster name.` const CreateNVMeNamespace = `Create NVMe namespace on a cluster by cluster name.` const UpdateNVMeNamespace = `Update NVMe namespace on a cluster by cluster name.` diff --git a/docs/examples.md b/docs/examples.md index 090a977..c83b390 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -267,11 +267,11 @@ Expected Response: The nvme subsystem has been successfully created. - On the umeng-aff300-05-06 cluster, add host nqn as nqn.1992-01.example.com:host1 in sys1 nvme subsystem linux os in marketing svm -Expected Response: The nvme subsystem Host added successfully. +Expected Response: The nvme subsystem Host has been successfully added. -- On the umeng-aff300-05-06 cluster, delete nvme subsystem sys2 with linux os in marketing svm +- On the umeng-aff300-05-06 cluster, delete nvme subsystem sys1 with linux os in marketing svm -Expected Response: The nvme subsystem deleted successfully. +Expected Response: The nvme subsystem has been successfully deleted. --- diff --git a/integration/test/nvme_service_test.go b/integration/test/nvme_service_test.go deleted file mode 100644 index 59f5747..0000000 --- a/integration/test/nvme_service_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "context" - "crypto/tls" - "log/slog" - "net/http" - "testing" - "time" - - "github.com/netapp/ontap-mcp/config" -) - -const SarCluster = "sar" -const SarClusterStr = "On the " + SarCluster + " cluster, " - -func TestNVMeService(t *testing.T) { - SkipIfMissing(t, CheckTools) - - tests := []struct { - name string - input string - expectedOntapErr string - verifyAPI ontapVerifier - }{ - { - name: "Clean NVMe service", - input: SarClusterStr + "delete nvme service in marketing svm", - expectedOntapErr: "because it does not exist", - verifyAPI: ontapVerifier{api: "api/protocols/nvme/services?svm.name=marketing", validationFunc: deleteObject}, - }, - { - name: "Create NVMe service", - input: SarClusterStr + "create nvme service on the marketing svm", - expectedOntapErr: "", - verifyAPI: ontapVerifier{api: "api/protocols/nvme/services?svm.name=marketing", validationFunc: createObject}, - }, - { - name: "Update NVMe service", - input: SarClusterStr + "update nvme service to disable on the marketing svm", - expectedOntapErr: "", - verifyAPI: ontapVerifier{}, - }, - { - name: "Clean NVMe service", - input: SarClusterStr + "delete nvme service in marketing svm", - expectedOntapErr: "because it does not exist", - verifyAPI: ontapVerifier{api: "api/protocols/nvme/services?svm.name=marketing", validationFunc: deleteObject}, - }, - } - - cfg, err := config.ReadConfig(ConfigFile) - if err != nil { - t.Fatalf("Error parsing the config: %v", err) - } - - poller := cfg.Pollers[SarCluster] - if poller == nil { - t.Skipf("Cluster %q not found in %s, skipping NVMe tests", SarCluster, ConfigFile) - } - transport := &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: poller.UseInsecureTLS, // #nosec G402 - }, - } - client := &http.Client{Transport: transport, Timeout: 10 * time.Second} - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - slog.Debug("", slog.String("Input", tt.input)) - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) - defer cancel() - if _, err = testAgent.ChatWithResponse(ctx, t, tt.input, tt.expectedOntapErr); err != nil { - slog.Error("Error processing input", slog.Any("error", err)) - } - if tt.verifyAPI.api != "" && !tt.verifyAPI.validationFunc(t, tt.verifyAPI.api, poller, client) { - t.Errorf("Error while accessing the object via prompt %s", tt.input) - } - }) - } -} diff --git a/integration/test/nvme_test.go b/integration/test/nvme_test.go index 96561ed..61cf9c7 100644 --- a/integration/test/nvme_test.go +++ b/integration/test/nvme_test.go @@ -103,6 +103,9 @@ func TestNVMeService(t *testing.T) { } poller := cfg.Pollers[SarCluster] + if poller == nil { + t.Skipf("Cluster %q not found in %s, skipping NVMe tests", SarCluster, ConfigFile) + } transport := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: poller.UseInsecureTLS, // #nosec G402 @@ -116,7 +119,7 @@ func TestNVMeService(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) defer cancel() if _, err = testAgent.ChatWithResponse(ctx, t, tt.input, tt.expectedOntapErr); err != nil { - slog.Error("Error processing input", slog.Any("error", err)) + t.Fatalf("Error processing input %q: %v", tt.input, err) } if tt.verifyAPI.api != "" && !tt.verifyAPI.validationFunc(t, tt.verifyAPI.api, poller, client) { t.Errorf("Error while accessing the object via prompt %s", tt.input) diff --git a/ontap/ontap.go b/ontap/ontap.go index 8d5558a..8e49970 100644 --- a/ontap/ontap.go +++ b/ontap/ontap.go @@ -198,19 +198,19 @@ type NVMeSubsystem struct { SVM NameAndUUID `json:"svm,omitzero" jsonschema:"svm name"` Name string `json:"name,omitzero" jsonschema:"name for NVMe subsystem"` OSType string `json:"os_type,omitzero" jsonschema:"operating system of the NVMe subsystem's hosts"` - Hosts []Hosts `json:"hosts,omitzero" jsonschema:"NVMe hosts configured for access to the NVMe subsystem"` + Hosts []NVMeHost `json:"hosts,omitzero" jsonschema:"NVMe hosts configured for access to the NVMe subsystem"` Comment string `json:"comment,omitzero" jsonschema:"configurable comment for the NVMe subsystem"` AllowDeleteWhileMapped bool `json:"allow_delete_while_mapped,omitzero" jsonschema:"Allows for the deletion of a mapped NVMe subsystem. This parameter should be used with caution."` AllowDeleteWithHosts bool `json:"allow_delete_with_hosts,omitzero" jsonschema:"Allows for the deletion of an NVMe subsystem with NVMe hosts. This parameter should be used with caution."` } -type Hosts struct { - NQN string `json:"nqn,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe storage target"` +type NVMeHost struct { + NQN string `json:"nqn,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe host"` } type NVMeSubsystemHost struct { - NQN string `json:"nqn,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe storage target"` - Records []Hosts `json:"records,omitzero" jsonschema:"array of NVMe hosts specified to add multiple NVMe hosts to an NVMe subsystem"` + NQN string `json:"nqn,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe storage target"` + Records []NVMeHost `json:"records,omitzero" jsonschema:"array of NVMe hosts specified to add multiple NVMe hosts to an NVMe subsystem"` } type NVMeNamespace struct { @@ -226,9 +226,9 @@ type Space struct { } type NVMeSubsystemMap struct { - SVM NameAndUUID `json:"svm" jsonschema:"svm name"` - Subsystemn NameAndUUID `json:"subsystem" jsonschema:"subsystem name"` - Namespace NameAndUUID `json:"namespace" jsonschema:"namespace name"` + SVM NameAndUUID `json:"svm" jsonschema:"svm name"` + Subsystem NameAndUUID `json:"subsystem" jsonschema:"subsystem name"` + Namespace NameAndUUID `json:"namespace" jsonschema:"namespace name"` } const ( diff --git a/rest/nvme.go b/rest/nvme.go index d5a909c..26bce44 100644 --- a/rest/nvme.go +++ b/rest/nvme.go @@ -271,7 +271,7 @@ func (c *Client) RemoveNVMeSubsystemHost(ctx context.Context, svmName string, na name, svmName, nvmeSs.NumRecords) } - builder2 := c.baseRequestBuilder(`/api/protocols/nvme/subsystems/`+nvmeSs.Records[0].UUID+`/hosts/`+nqn, &statusCode, responseHeaders). + builder2 := c.baseRequestBuilder(`/api/protocols/nvme/subsystems/`+nvmeSs.Records[0].UUID+`/hosts/`+url.PathEscape(nqn), &statusCode, responseHeaders). Delete() if err := c.buildAndExecuteRequest(ctx, builder2); err != nil { diff --git a/server/nvme.go b/server/nvme.go index 27f326d..196e5a8 100644 --- a/server/nvme.go +++ b/server/nvme.go @@ -231,7 +231,11 @@ func newCreateNVMeSubsystem(in tool.NVMeSubsystem) (ontap.NVMeSubsystem, error) out.OSType = in.OSType for _, nqn := range in.HostNQNs { - out.Hosts = append(out.Hosts, ontap.Hosts{NQN: nqn}) + out.Hosts = append(out.Hosts, ontap.NVMeHost{NQN: nqn}) + } + + if in.Comment != "" { + out.Comment = in.Comment } return out, nil @@ -341,14 +345,24 @@ func newAddNVMeSubsystemHost(in tool.NVMeSubsystemHost) (ontap.NVMeSubsystemHost } if in.NQN == "" && len(in.Records) == 0 { - return out, errors.New("NVMe subsystem host NQN OR Array of NQNs are required") + return out, errors.New("either NVMe subsystem host NQN OR one or more host NQNs (records) must be provided") } + + // Enforce mutual exclusivity: cannot specify both a single NQN and an array + if in.NQN != "" && len(in.Records) > 0 { + return out, errors.New("specify either a single NVMe subsystem host NQN or an array of NQNs, but not both") + } + if in.NQN != "" { out.NQN = in.NQN + return out, nil } for _, nqn := range in.Records { - out.Records = append(out.Records, ontap.Hosts{NQN: nqn}) + if nqn == "" { + return out, errors.New("all NQNs in the array must be non-empty") + } + out.Records = append(out.Records, ontap.NVMeHost{NQN: nqn}) } return out, nil } @@ -464,7 +478,7 @@ func newCreateNVMeNamespace(in tool.NVMeNamespace) (ontap.NVMeNamespace, error) return out, errors.New("SVM name is required") } if in.Name == "" { - return out, errors.New("NVMe subsystem name is required") + return out, errors.New("NVMe namespace name is required") } if in.OSType == "" { return out, errors.New("OS type is required") @@ -535,7 +549,7 @@ func (a *App) CreateNVMeSubsystemMap(ctx context.Context, _ *mcp.CallToolRequest return errorResult(err), nil, err } - responseText := "NVMe Subsystem Map create successfully" + responseText := "NVMe Subsystem Map created successfully" return &mcp.CallToolResult{ Content: []mcp.Content{ @@ -586,7 +600,7 @@ func newCreateNVMeSubsystemMap(in tool.NVMeSubsystemMap) (ontap.NVMeSubsystemMap } out.SVM.Name = in.SVM - out.Subsystemn.Name = in.Subsystem + out.Subsystem.Name = in.Subsystem out.Namespace.Name = in.Namespace return out, nil } diff --git a/tool/tool.go b/tool/tool.go index 9c034fd..66b58b4 100644 --- a/tool/tool.go +++ b/tool/tool.go @@ -131,7 +131,7 @@ type NVMeSubsystemHost struct { SVM string `json:"svm_name" jsonschema:"SVM name"` Name string `json:"name" jsonschema:"name for NVMe subsystem"` OSType string `json:"os_type" jsonschema:"operating system of the NVMe subsystem's hosts"` - NQN string `json:"nqn,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe storage target"` + NQN string `json:"nqn,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe host"` Records []string `json:"records,omitzero" jsonschema:"array of NVMe hosts specified to add multiple NVMe hosts to an NVMe subsystem"` } @@ -140,7 +140,7 @@ type NVMeNamespace struct { SVM string `json:"svm_name" jsonschema:"SVM name"` Name string `json:"name" jsonschema:"name for NVMe namespace"` OSType string `json:"os_type" jsonschema:"operating system type of the NVMe namespace"` - Size string `json:"space.size" jsonschema:"total provisioned size of the NVMe namespace (e.g., '100GB', '1TB')"` + Size string `json:"space.size,omitzero" jsonschema:"total provisioned size of the NVMe namespace (e.g., '100GB', '1TB')"` AllowDeleteWhileMapped bool `json:"allow_delete_while_mapped,omitzero" jsonschema:"Allows deletion of a mapped NVMe namespace. This parameter should be used with caution."` } From 1976fd0edfedd2eff2ef20aee9c7cf1e83d51325 Mon Sep 17 00:00:00 2001 From: hardikl Date: Tue, 7 Apr 2026 21:48:08 +0530 Subject: [PATCH 4/7] feat: add nvme test cases in aff cluster --- docs/examples.md | 10 ++- integration/test/nvme_test.go | 148 +++++++++++++++++++++++++--------- 2 files changed, 118 insertions(+), 40 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index c83b390..18ab9c5 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -251,7 +251,7 @@ Expected Response: The qtree has been successfully renamed. --- -### Manage NVMe Service +### Manage NVMe - On the umeng-aff300-05-06 cluster, create nvme service on the marketing svm @@ -273,6 +273,14 @@ Expected Response: The nvme subsystem Host has been successfully added. Expected Response: The nvme subsystem has been successfully deleted. +- On the umeng-aff300-05-06 cluster, create nvme namespace /vol/docns/ns1 with linux os and 20mb size in nvmevs1 svm + +Expected Response: The nvme namespace has been successfully created. + +- On the umeng-aff300-05-06 cluster, create subsystem map of sys1 subsystem and /vol/docns/ns1 namespace in nvmevs1 svm + +Expected Response: The nvme subsystem map has been successfully created. + --- ### Querying Specific Fields diff --git a/integration/test/nvme_test.go b/integration/test/nvme_test.go index 61cf9c7..98e0942 100644 --- a/integration/test/nvme_test.go +++ b/integration/test/nvme_test.go @@ -3,6 +3,8 @@ package main import ( "context" "crypto/tls" + "github.com/carlmjohnson/requests" + "github.com/netapp/ontap-mcp/ontap" "log/slog" "net/http" "testing" @@ -11,8 +13,8 @@ import ( "github.com/netapp/ontap-mcp/config" ) -const SarCluster = "sar" -const SarClusterStr = "On the " + SarCluster + " cluster, " +const NvmeCluster = "aff" +const NvmeClusterStr = "On the " + NvmeCluster + " cluster, " func TestNVMeService(t *testing.T) { SkipIfMissing(t, CheckTools) @@ -24,76 +26,94 @@ func TestNVMeService(t *testing.T) { verifyAPI ontapVerifier }{ { - name: "Update NVMe service", - input: SarClusterStr + "update nvme service to disable on the marketing svm", - expectedOntapErr: "because it does not exist", - verifyAPI: ontapVerifier{}, - }, - { - name: "Clean NVMe service", - input: SarClusterStr + "delete nvme service in marketing svm", + name: "Clean NVMe subsystem", + input: NvmeClusterStr + "delete nvme subsystem " + rn("sys2") + " with linux os in marketing svm with allow_delete_while_mapped and allow_delete_with_hosts", expectedOntapErr: "because it does not exist", - verifyAPI: ontapVerifier{api: "api/protocols/nvme/services?svm.name=marketing", validationFunc: deleteObject}, - }, - { - name: "Create NVMe service", - input: SarClusterStr + "create nvme service on the marketing svm", - expectedOntapErr: "", - verifyAPI: ontapVerifier{api: "api/protocols/nvme/services?svm.name=marketing", validationFunc: createObject}, - }, - { - name: "Create NVMe subsystem", - input: SarClusterStr + "create nvme subsystem sys1 with linux os on the marketing svm", - expectedOntapErr: "", - verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=sys1", validationFunc: createObject}, + verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=" + rn("sys2"), validationFunc: deleteObject}, }, { name: "Create NVMe subsystem", - input: SarClusterStr + "create nvme subsystem sys2 with linux os and with host nqns as nqn.1992-01.example.com:host1, nqn.1992-01.example.com:host2 on the marketing svm", + input: NvmeClusterStr + "create nvme subsystem " + rn("sys2") + " with linux os and with host nqns as nqn.1992-01.example.com:host1, nqn.1992-01.example.com:host2 on the marketing svm", expectedOntapErr: "", - verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=sys2", validationFunc: createObject}, + verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=" + rn("sys2"), validationFunc: createObject}, }, { name: "Update NVMe subsystem", - input: SarClusterStr + "add comment as `comment about the` in sys1 nvme subsystem linux os on the marketing svm", + input: NvmeClusterStr + "add comment as `comment about the` in " + rn("sys2") + " nvme subsystem linux os on the marketing svm", expectedOntapErr: "", verifyAPI: ontapVerifier{}, }, { name: "Add host in NVMe subsystem", - input: SarClusterStr + "add host nqn as nqn.1992-01.example.com:host1 in sys1 nvme subsystem linux os in marketing svm", + input: NvmeClusterStr + "add host nqn as nqn.1992-01.example.com:host3 in " + rn("sys2") + " nvme subsystem linux os in marketing svm", expectedOntapErr: "", verifyAPI: ontapVerifier{}, }, { name: "Remove host in NVMe subsystem", - input: SarClusterStr + "remove host nqn as nqn.1992-01.example.com:host1 in sys1 nvme subsystem linux os in marketing svm", + input: NvmeClusterStr + "remove host nqn as nqn.1992-01.example.com:host3 in " + rn("sys2") + " nvme subsystem linux os in marketing svm", expectedOntapErr: "", verifyAPI: ontapVerifier{}, }, { name: "Clean NVMe subsystem", - input: SarClusterStr + "delete nvme subsystem sys1 with linux os in marketing svm", + input: NvmeClusterStr + "delete nvme subsystem " + rn("sys2") + " with linux os in marketing svm with allow_delete_while_mapped and allow_delete_with_hosts", expectedOntapErr: "because it does not exist", - verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=sys1", validationFunc: deleteObject}, + verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=" + rn("sys2"), validationFunc: deleteObject}, }, { name: "Clean NVMe subsystem", - input: SarClusterStr + "delete nvme subsystem sys2 with linux os in marketing svm with allow_delete_while_mapped and allow_delete_with_hosts", + input: NvmeClusterStr + "delete nvme subsystem " + rn("sys1") + " with linux os in nvmevs1 svm", + expectedOntapErr: "because it does not exist", + verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=nvmevs1&name=" + rn("sys1"), validationFunc: deleteObject}, + }, + { + name: "Create NVMe subsystem", + input: NvmeClusterStr + "create nvme subsystem " + rn("sys1") + " with linux os on the nvmevs1 svm", + expectedOntapErr: "", + verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=nvmevs1&name=" + rn("sys1"), validationFunc: createObject}, + }, + { + name: "Clean NVMe namespace", + input: NvmeClusterStr + "delete nvme namespace '" + rn("/vol/docns/ns1") + "' with linux os in nvmevs1 svm", expectedOntapErr: "because it does not exist", - verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=sys2", validationFunc: deleteObject}, + verifyAPI: ontapVerifier{api: "api/storage/namespaces?svm.name=nvmevs1&name=" + rn(`/vol/docns/ns1`), validationFunc: deleteObject}, + }, + { + name: "Create NVMe namespace", + input: NvmeClusterStr + "create nvme namespace '" + rn("/vol/docns/ns1") + "' with linux os and 20mb size in nvmevs1 svm", + expectedOntapErr: "", + verifyAPI: ontapVerifier{api: "api/storage/namespaces?svm.name=nvmevs1&name=" + rn(`/vol/docns/ns1`), validationFunc: createObject}, }, { - name: "Update NVMe service", - input: SarClusterStr + "update nvme service to disable on the marketing svm", + name: "Update NVMe namespace", + input: NvmeClusterStr + "update nvme namespace '" + rn("/vol/docns/ns1") + "' with linux os to 40mb size in nvmevs1 svm", expectedOntapErr: "", verifyAPI: ontapVerifier{}, }, { - name: "Clean NVMe service", - input: SarClusterStr + "delete nvme service in marketing svm", + name: "Create NVMe subsystem map", + input: NvmeClusterStr + "create subsystem map of " + rn("sys1") + " subsystem and '" + rn("/vol/docns/ns1") + "' namespace in nvmevs1 svm", + expectedOntapErr: "", + verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystem-maps?svm.name=nvmevs1", validationFunc: verifySubsystemMaps(rn("sys1"), rn(`/vol/docns/ns1`), true)}, + }, + { + name: "Clean NVMe subsystem map", + input: NvmeClusterStr + "delete subsystem map of " + rn("sys1") + " subsystem and namespace '" + rn("/vol/docns/ns1") + "' in nvmevs1 svm", + expectedOntapErr: "because it does not exist", + verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystem-maps?svm.name=nvmevs1", validationFunc: verifySubsystemMaps(rn("sys1"), rn(`/vol/docns/ns1`), false)}, + }, + { + name: "Clean NVMe namespace", + input: NvmeClusterStr + "delete nvme namespace '" + rn("/vol/docns/ns1") + "' with linux os in nvmevs1 svm", + expectedOntapErr: "because it does not exist", + verifyAPI: ontapVerifier{api: "api/storage/namespaces?svm.name=nvmevs1&name=" + rn(`/vol/docns/ns1`), validationFunc: deleteObject}, + }, + { + name: "Clean NVMe subsystem", + input: NvmeClusterStr + "delete nvme subsystem " + rn("sys1") + " with linux os in nvmevs1 svm", expectedOntapErr: "because it does not exist", - verifyAPI: ontapVerifier{api: "api/protocols/nvme/services?svm.name=marketing", validationFunc: deleteObject}, + verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=nvmevs1&name=" + rn("sys1"), validationFunc: deleteObject}, }, } @@ -102,9 +122,9 @@ func TestNVMeService(t *testing.T) { t.Fatalf("Error parsing the config: %v", err) } - poller := cfg.Pollers[SarCluster] + poller := cfg.Pollers[NvmeCluster] if poller == nil { - t.Skipf("Cluster %q not found in %s, skipping NVMe tests", SarCluster, ConfigFile) + t.Skipf("Cluster %q not found in %s, skipping NVMe tests", NvmeCluster, ConfigFile) } transport := &http.Transport{ TLSClientConfig: &tls.Config{ @@ -127,3 +147,53 @@ func TestNVMeService(t *testing.T) { }) } } + +func verifySubsystemMaps(subsystemName, namespaceName string, exist bool) func(t *testing.T, api string, poller *config.Poller, client *http.Client) bool { //nolint:unparam + return func(t *testing.T, api string, poller *config.Poller, client *http.Client) bool { + type subsystemMapRecord struct { + Namespace ontap.NameAndUUID `json:"namespace"` + Subsystem ontap.NameAndUUID `json:"subsystem"` + } + type response struct { + NumRecords int `json:"num_records"` + Records []subsystemMapRecord `json:"records"` + } + + var data response + err := requests.URL("https://"+poller.Addr+"/"+api). + BasicAuth(poller.Username, poller.Password). + Client(client). + ToJSON(&data). + Fetch(context.Background()) + if err != nil { + t.Errorf("verifySubsystemMaps: request failed: %v", err) + return false + } + + if exist { + for _, record := range data.Records { + gotSubsystem := record.Subsystem.Name + gotNamespace := record.Namespace.Name + if gotSubsystem == subsystemName && gotNamespace == namespaceName { + return true + } + } + t.Errorf("sybsystem map is not exist") + } else { + sbsMapRecord := false + for _, record := range data.Records { + gotSubsystem := record.Subsystem.Name + gotNamespace := record.Namespace.Name + if gotSubsystem == subsystemName && gotNamespace == namespaceName { + sbsMapRecord = true + break + } + } + if !sbsMapRecord { + return true + } + t.Errorf("subsystem map is exist") + } + return false + } +} From 1298e1922324aeed09da01d99261024efa7ca085 Mon Sep 17 00:00:00 2001 From: hardikl Date: Wed, 8 Apr 2026 15:08:50 +0530 Subject: [PATCH 5/7] feat: handled review comments with go mode update --- go.mod | 2 +- integration/test/nvme_test.go | 6 +++--- ontap/ontap.go | 2 +- rest/nvme.go | 11 +++++++++-- server/nvme.go | 4 ++-- tool/tool.go | 2 +- 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 099dfb4..e6c6fb1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/netapp/ontap-mcp -go 1.26.1 +go 1.26.2 require ( github.com/alecthomas/kong v1.14.0 diff --git a/integration/test/nvme_test.go b/integration/test/nvme_test.go index 98e0942..433abc4 100644 --- a/integration/test/nvme_test.go +++ b/integration/test/nvme_test.go @@ -16,7 +16,7 @@ import ( const NvmeCluster = "aff" const NvmeClusterStr = "On the " + NvmeCluster + " cluster, " -func TestNVMeService(t *testing.T) { +func TestNVMe(t *testing.T) { SkipIfMissing(t, CheckTools) tests := []struct { @@ -178,7 +178,7 @@ func verifySubsystemMaps(subsystemName, namespaceName string, exist bool) func(t return true } } - t.Errorf("sybsystem map is not exist") + t.Errorf("subsystem map does not exist") } else { sbsMapRecord := false for _, record := range data.Records { @@ -192,7 +192,7 @@ func verifySubsystemMaps(subsystemName, namespaceName string, exist bool) func(t if !sbsMapRecord { return true } - t.Errorf("subsystem map is exist") + t.Errorf("subsystem map exists") } return false } diff --git a/ontap/ontap.go b/ontap/ontap.go index 8e49970..ede7f1d 100644 --- a/ontap/ontap.go +++ b/ontap/ontap.go @@ -209,7 +209,7 @@ type NVMeHost struct { } type NVMeSubsystemHost struct { - NQN string `json:"nqn,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe storage target"` + NQN string `json:"nqn,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe host"` Records []NVMeHost `json:"records,omitzero" jsonschema:"array of NVMe hosts specified to add multiple NVMe hosts to an NVMe subsystem"` } diff --git a/rest/nvme.go b/rest/nvme.go index 26bce44..5d5d5e7 100644 --- a/rest/nvme.go +++ b/rest/nvme.go @@ -187,7 +187,11 @@ func (c *Client) DeleteNVMeSubsystem(ctx context.Context, svmName string, name s name, svmName, nvmeSs.NumRecords) } - builder = c.baseRequestBuilder(`/api/protocols/nvme/subsystems/`+nvmeSs.Records[0].UUID+`?allow_delete_while_mapped=`+strconv.FormatBool(allowDeleteWhileMapped)+`&allow_delete_with_hosts=`+strconv.FormatBool(allowDeleteWithHosts), &statusCode, responseHeaders). + deleteParams := url.Values{} + deleteParams.Set("allow_delete_while_mapped", strconv.FormatBool(allowDeleteWhileMapped)) + deleteParams.Set("allow_delete_with_hosts", strconv.FormatBool(allowDeleteWithHosts)) + builder = c.baseRequestBuilder(`/api/protocols/nvme/subsystems/`+nvmeSs.Records[0].UUID, &statusCode, responseHeaders). + Params(deleteParams). Delete() if err := c.buildAndExecuteRequest(ctx, builder); err != nil { @@ -372,7 +376,10 @@ func (c *Client) DeleteNVMeNamespace(ctx context.Context, svmName string, name s name, svmName, nvmeNs.NumRecords) } - builder = c.baseRequestBuilder(`/api/storage/namespaces/`+nvmeNs.Records[0].UUID+`?allow_delete_while_mapped=`+strconv.FormatBool(allowDeleteWhileMapped), &statusCode, responseHeaders). + deleteParams := url.Values{} + deleteParams.Set("allow_delete_while_mapped", strconv.FormatBool(allowDeleteWhileMapped)) + builder = c.baseRequestBuilder(`/api/storage/namespaces/`+nvmeNs.Records[0].UUID, &statusCode, responseHeaders). + Params(deleteParams). Delete() if err := c.buildAndExecuteRequest(ctx, builder); err != nil { diff --git a/server/nvme.go b/server/nvme.go index 196e5a8..cf308dc 100644 --- a/server/nvme.go +++ b/server/nvme.go @@ -502,7 +502,7 @@ func newUpdateNVMeNamespace(in tool.NVMeNamespace) (ontap.NVMeNamespace, error) return out, errors.New("SVM name is required") } if in.Name == "" { - return out, errors.New("NVMe subsystem name is required") + return out, errors.New("NVMe namespace name is required") } if in.OSType == "" { return out, errors.New("OS type is required") @@ -519,7 +519,7 @@ func newDeleteNVMeNamespace(in tool.NVMeNamespace) error { return errors.New("SVM name is required") } if in.Name == "" { - return errors.New("NVMe subsystem name is required") + return errors.New("NVMe namespace name is required") } if in.OSType == "" { return errors.New("OS type is required") diff --git a/tool/tool.go b/tool/tool.go index 66b58b4..79b84c1 100644 --- a/tool/tool.go +++ b/tool/tool.go @@ -120,7 +120,7 @@ type NVMeSubsystem struct { SVM string `json:"svm_name" jsonschema:"SVM name"` Name string `json:"name" jsonschema:"name for NVMe subsystem"` OSType string `json:"os_type" jsonschema:"operating system of the NVMe subsystem's hosts"` - HostNQNs []string `json:"hosts_nqns,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe storage target"` + HostNQNs []string `json:"hosts_nqns,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe hosts"` Comment string `json:"comment,omitzero" jsonschema:"configurable comment for the NVMe subsystem"` AllowDeleteWhileMapped bool `json:"allow_delete_while_mapped,omitzero" jsonschema:"Allows for the deletion of a mapped NVMe subsystem. This parameter should be used with caution."` AllowDeleteWithHosts bool `json:"allow_delete_with_hosts,omitzero" jsonschema:"Allows for the deletion of an NVMe subsystem with NVMe hosts. This parameter should be used with caution."` From 9b2a244121afddb33627fa6c3b72c4c7bd8c11e4 Mon Sep 17 00:00:00 2001 From: hardikl Date: Wed, 8 Apr 2026 20:53:23 +0530 Subject: [PATCH 6/7] feat: update nvme tools validation --- docs/examples.md | 4 ++-- integration/test/nvme_test.go | 20 ++++++++++---------- rest/nvme.go | 18 ++++++------------ server/nvme.go | 35 +++++++++-------------------------- tool/tool.go | 9 ++++----- 5 files changed, 31 insertions(+), 55 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index 18ab9c5..f820e9d 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -265,11 +265,11 @@ Expected Response: The nvme service has been successfully updated. Expected Response: The nvme subsystem has been successfully created. -- On the umeng-aff300-05-06 cluster, add host nqn as nqn.1992-01.example.com:host1 in sys1 nvme subsystem linux os in marketing svm +- On the umeng-aff300-05-06 cluster, add host nqn as nqn.1992-01.example.com:host1 in sys1 nvme subsystem in marketing svm Expected Response: The nvme subsystem Host has been successfully added. -- On the umeng-aff300-05-06 cluster, delete nvme subsystem sys1 with linux os in marketing svm +- On the umeng-aff300-05-06 cluster, delete nvme subsystem sys1 with in marketing svm Expected Response: The nvme subsystem has been successfully deleted. diff --git a/integration/test/nvme_test.go b/integration/test/nvme_test.go index 433abc4..67c58c4 100644 --- a/integration/test/nvme_test.go +++ b/integration/test/nvme_test.go @@ -27,7 +27,7 @@ func TestNVMe(t *testing.T) { }{ { name: "Clean NVMe subsystem", - input: NvmeClusterStr + "delete nvme subsystem " + rn("sys2") + " with linux os in marketing svm with allow_delete_while_mapped and allow_delete_with_hosts", + input: NvmeClusterStr + "delete nvme subsystem " + rn("sys2") + " with in marketing svm with allow_delete_while_mapped and allow_delete_with_hosts", expectedOntapErr: "because it does not exist", verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=" + rn("sys2"), validationFunc: deleteObject}, }, @@ -39,31 +39,31 @@ func TestNVMe(t *testing.T) { }, { name: "Update NVMe subsystem", - input: NvmeClusterStr + "add comment as `comment about the` in " + rn("sys2") + " nvme subsystem linux os on the marketing svm", + input: NvmeClusterStr + "add comment as `comment about the` in " + rn("sys2") + " nvme subsystem on the marketing svm", expectedOntapErr: "", verifyAPI: ontapVerifier{}, }, { name: "Add host in NVMe subsystem", - input: NvmeClusterStr + "add host nqn as nqn.1992-01.example.com:host3 in " + rn("sys2") + " nvme subsystem linux os in marketing svm", + input: NvmeClusterStr + "add host nqn as nqn.1992-01.example.com:host3 in " + rn("sys2") + " nvme subsystem in marketing svm", expectedOntapErr: "", verifyAPI: ontapVerifier{}, }, { name: "Remove host in NVMe subsystem", - input: NvmeClusterStr + "remove host nqn as nqn.1992-01.example.com:host3 in " + rn("sys2") + " nvme subsystem linux os in marketing svm", + input: NvmeClusterStr + "remove host nqn as nqn.1992-01.example.com:host3 in " + rn("sys2") + " nvme subsystem in marketing svm", expectedOntapErr: "", verifyAPI: ontapVerifier{}, }, { name: "Clean NVMe subsystem", - input: NvmeClusterStr + "delete nvme subsystem " + rn("sys2") + " with linux os in marketing svm with allow_delete_while_mapped and allow_delete_with_hosts", + input: NvmeClusterStr + "delete nvme subsystem " + rn("sys2") + " with in marketing svm with allow_delete_while_mapped and allow_delete_with_hosts", expectedOntapErr: "because it does not exist", verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=marketing&name=" + rn("sys2"), validationFunc: deleteObject}, }, { name: "Clean NVMe subsystem", - input: NvmeClusterStr + "delete nvme subsystem " + rn("sys1") + " with linux os in nvmevs1 svm", + input: NvmeClusterStr + "delete nvme subsystem " + rn("sys1") + " with in nvmevs1 svm", expectedOntapErr: "because it does not exist", verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=nvmevs1&name=" + rn("sys1"), validationFunc: deleteObject}, }, @@ -75,7 +75,7 @@ func TestNVMe(t *testing.T) { }, { name: "Clean NVMe namespace", - input: NvmeClusterStr + "delete nvme namespace '" + rn("/vol/docns/ns1") + "' with linux os in nvmevs1 svm", + input: NvmeClusterStr + "delete nvme namespace '" + rn("/vol/docns/ns1") + "' with in nvmevs1 svm", expectedOntapErr: "because it does not exist", verifyAPI: ontapVerifier{api: "api/storage/namespaces?svm.name=nvmevs1&name=" + rn(`/vol/docns/ns1`), validationFunc: deleteObject}, }, @@ -87,7 +87,7 @@ func TestNVMe(t *testing.T) { }, { name: "Update NVMe namespace", - input: NvmeClusterStr + "update nvme namespace '" + rn("/vol/docns/ns1") + "' with linux os to 40mb size in nvmevs1 svm", + input: NvmeClusterStr + "update nvme namespace '" + rn("/vol/docns/ns1") + "' with to 40mb size in nvmevs1 svm", expectedOntapErr: "", verifyAPI: ontapVerifier{}, }, @@ -105,13 +105,13 @@ func TestNVMe(t *testing.T) { }, { name: "Clean NVMe namespace", - input: NvmeClusterStr + "delete nvme namespace '" + rn("/vol/docns/ns1") + "' with linux os in nvmevs1 svm", + input: NvmeClusterStr + "delete nvme namespace '" + rn("/vol/docns/ns1") + "' with in nvmevs1 svm", expectedOntapErr: "because it does not exist", verifyAPI: ontapVerifier{api: "api/storage/namespaces?svm.name=nvmevs1&name=" + rn(`/vol/docns/ns1`), validationFunc: deleteObject}, }, { name: "Clean NVMe subsystem", - input: NvmeClusterStr + "delete nvme subsystem " + rn("sys1") + " with linux os in nvmevs1 svm", + input: NvmeClusterStr + "delete nvme subsystem " + rn("sys1") + " with in nvmevs1 svm", expectedOntapErr: "because it does not exist", verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=nvmevs1&name=" + rn("sys1"), validationFunc: deleteObject}, }, diff --git a/rest/nvme.go b/rest/nvme.go index 5d5d5e7..6631220 100644 --- a/rest/nvme.go +++ b/rest/nvme.go @@ -112,7 +112,7 @@ func (c *Client) CreateNVMeSubsystem(ctx context.Context, nvmeSubsystem ontap.NV return c.checkStatus(statusCode) } -func (c *Client) UpdateNVMeSubsystem(ctx context.Context, svmName string, name string, osType string, nvmeSubsystem ontap.NVMeSubsystem) error { +func (c *Client) UpdateNVMeSubsystem(ctx context.Context, svmName string, name string, nvmeSubsystem ontap.NVMeSubsystem) error { var ( statusCode int nvmeSs ontap.GetData @@ -123,7 +123,6 @@ func (c *Client) UpdateNVMeSubsystem(ctx context.Context, svmName string, name s params := url.Values{} params.Set("svm.name", svmName) params.Set("name", name) - params.Set("os_type", osType) builder := c.baseRequestBuilder(`/api/protocols/nvme/subsystems`, &statusCode, responseHeaders). Params(params). @@ -155,7 +154,7 @@ func (c *Client) UpdateNVMeSubsystem(ctx context.Context, svmName string, name s return c.checkStatus(statusCode) } -func (c *Client) DeleteNVMeSubsystem(ctx context.Context, svmName string, name string, osType string, allowDeleteWhileMapped bool, allowDeleteWithHosts bool) error { +func (c *Client) DeleteNVMeSubsystem(ctx context.Context, svmName string, name string, allowDeleteWhileMapped bool, allowDeleteWithHosts bool) error { var ( statusCode int nvmeSs ontap.GetData @@ -166,7 +165,6 @@ func (c *Client) DeleteNVMeSubsystem(ctx context.Context, svmName string, name s params := url.Values{} params.Set("svm.name", svmName) params.Set("name", name) - params.Set("os_type", osType) builder := c.baseRequestBuilder(`/api/protocols/nvme/subsystems`, &statusCode, responseHeaders). Params(params). @@ -201,7 +199,7 @@ func (c *Client) DeleteNVMeSubsystem(ctx context.Context, svmName string, name s return c.checkStatus(statusCode) } -func (c *Client) AddNVMeSubsystemHost(ctx context.Context, svmName string, name string, osType string, nvmeSubsystemHost ontap.NVMeSubsystemHost) error { +func (c *Client) AddNVMeSubsystemHost(ctx context.Context, svmName string, name string, nvmeSubsystemHost ontap.NVMeSubsystemHost) error { var ( statusCode int nvmeSs ontap.GetData @@ -212,7 +210,6 @@ func (c *Client) AddNVMeSubsystemHost(ctx context.Context, svmName string, name params := url.Values{} params.Set("svm.name", svmName) params.Set("name", name) - params.Set("os_type", osType) builder := c.baseRequestBuilder(`/api/protocols/nvme/subsystems`, &statusCode, responseHeaders). Params(params). @@ -243,7 +240,7 @@ func (c *Client) AddNVMeSubsystemHost(ctx context.Context, svmName string, name return c.checkStatus(statusCode) } -func (c *Client) RemoveNVMeSubsystemHost(ctx context.Context, svmName string, name string, osType string, nqn string) error { +func (c *Client) RemoveNVMeSubsystemHost(ctx context.Context, svmName string, name string, nqn string) error { var ( statusCode int nvmeSs ontap.GetData @@ -254,7 +251,6 @@ func (c *Client) RemoveNVMeSubsystemHost(ctx context.Context, svmName string, na params := url.Values{} params.Set("svm.name", svmName) params.Set("name", name) - params.Set("os_type", osType) builder := c.baseRequestBuilder(`/api/protocols/nvme/subsystems`, &statusCode, responseHeaders). Params(params). @@ -301,7 +297,7 @@ func (c *Client) CreateNVMeNamespace(ctx context.Context, nvmeNamespace ontap.NV return c.checkStatus(statusCode) } -func (c *Client) UpdateNVMeNamespace(ctx context.Context, svmName string, name string, osType string, nvmeNamespace ontap.NVMeNamespace) error { +func (c *Client) UpdateNVMeNamespace(ctx context.Context, svmName string, name string, nvmeNamespace ontap.NVMeNamespace) error { var ( statusCode int nvmeNs ontap.GetData @@ -312,7 +308,6 @@ func (c *Client) UpdateNVMeNamespace(ctx context.Context, svmName string, name s params := url.Values{} params.Set("svm.name", svmName) params.Set("name", name) - params.Set("os_type", osType) builder := c.baseRequestBuilder(`/api/storage/namespaces`, &statusCode, responseHeaders). Params(params). @@ -344,7 +339,7 @@ func (c *Client) UpdateNVMeNamespace(ctx context.Context, svmName string, name s return c.checkStatus(statusCode) } -func (c *Client) DeleteNVMeNamespace(ctx context.Context, svmName string, name string, osType string, allowDeleteWhileMapped bool) error { +func (c *Client) DeleteNVMeNamespace(ctx context.Context, svmName string, name string, allowDeleteWhileMapped bool) error { var ( statusCode int nvmeNs ontap.GetData @@ -355,7 +350,6 @@ func (c *Client) DeleteNVMeNamespace(ctx context.Context, svmName string, name s params := url.Values{} params.Set("svm.name", svmName) params.Set("name", name) - params.Set("os_type", osType) builder := c.baseRequestBuilder(`/api/storage/namespaces`, &statusCode, responseHeaders). Params(params). diff --git a/server/nvme.go b/server/nvme.go index cf308dc..fd024c9 100644 --- a/server/nvme.go +++ b/server/nvme.go @@ -170,7 +170,7 @@ func (a *App) UpdateNVMeSubsystem(ctx context.Context, _ *mcp.CallToolRequest, p if err != nil { return errorResult(err), nil, err } - err = client.UpdateNVMeSubsystem(ctx, parameters.SVM, parameters.Name, parameters.OSType, nvmeSubsystemUpdate) + err = client.UpdateNVMeSubsystem(ctx, parameters.SVM, parameters.Name, nvmeSubsystemUpdate) if err != nil { return errorResult(err), nil, err @@ -199,7 +199,7 @@ func (a *App) DeleteNVMeSubsystem(ctx context.Context, _ *mcp.CallToolRequest, p if err != nil { return errorResult(err), nil, err } - err = client.DeleteNVMeSubsystem(ctx, parameters.SVM, parameters.Name, parameters.OSType, parameters.AllowDeleteWhileMapped, parameters.AllowDeleteWithHosts) + err = client.DeleteNVMeSubsystem(ctx, parameters.SVM, parameters.Name, parameters.AllowDeleteWhileMapped, parameters.AllowDeleteWithHosts) if err != nil { return errorResult(err), nil, err @@ -250,12 +250,10 @@ func newUpdateNVMeSubsystem(in tool.NVMeSubsystem) (ontap.NVMeSubsystem, error) if in.Name == "" { return out, errors.New("NVMe subsystem name is required") } - if in.OSType == "" { - return out, errors.New("OS type is required") - } - if in.Comment != "" { - out.Comment = in.Comment + out.Comment = in.Comment + if out.Comment == "" { + return out, errors.New("no update fields provided; specify at least one of: comment") } return out, nil } @@ -267,9 +265,6 @@ func newDeleteNVMeSubsystem(in tool.NVMeSubsystem) error { if in.Name == "" { return errors.New("NVMe subsystem name is required") } - if in.OSType == "" { - return errors.New("OS type is required") - } return nil } @@ -288,7 +283,7 @@ func (a *App) AddNVMeSubsystemHost(ctx context.Context, _ *mcp.CallToolRequest, if err != nil { return errorResult(err), nil, err } - err = client.AddNVMeSubsystemHost(ctx, parameters.SVM, parameters.Name, parameters.OSType, nvmeSubsystemHostAdd) + err = client.AddNVMeSubsystemHost(ctx, parameters.SVM, parameters.Name, nvmeSubsystemHostAdd) if err != nil { return errorResult(err), nil, err @@ -317,7 +312,7 @@ func (a *App) RemoveNVMeSubsystemHost(ctx context.Context, _ *mcp.CallToolReques if err != nil { return errorResult(err), nil, err } - err = client.RemoveNVMeSubsystemHost(ctx, parameters.SVM, parameters.Name, parameters.OSType, parameters.NQN) + err = client.RemoveNVMeSubsystemHost(ctx, parameters.SVM, parameters.Name, parameters.NQN) if err != nil { return errorResult(err), nil, err @@ -340,9 +335,6 @@ func newAddNVMeSubsystemHost(in tool.NVMeSubsystemHost) (ontap.NVMeSubsystemHost if in.Name == "" { return out, errors.New("NVMe subsystem name is required") } - if in.OSType == "" { - return out, errors.New("OS type is required") - } if in.NQN == "" && len(in.Records) == 0 { return out, errors.New("either NVMe subsystem host NQN OR one or more host NQNs (records) must be provided") @@ -374,9 +366,6 @@ func newRemoveNVMeSubsystemHost(in tool.NVMeSubsystemHost) error { if in.Name == "" { return errors.New("NVMe subsystem name is required") } - if in.OSType == "" { - return errors.New("OS type is required") - } if in.NQN == "" { return errors.New("NQN is required") } @@ -428,7 +417,7 @@ func (a *App) UpdateNVMeNamespace(ctx context.Context, _ *mcp.CallToolRequest, p if err != nil { return errorResult(err), nil, err } - err = client.UpdateNVMeNamespace(ctx, parameters.SVM, parameters.Name, parameters.OSType, nvmeNamespaceUpdate) + err = client.UpdateNVMeNamespace(ctx, parameters.SVM, parameters.Name, nvmeNamespaceUpdate) if err != nil { return errorResult(err), nil, err @@ -457,7 +446,7 @@ func (a *App) DeleteNVMeNamespace(ctx context.Context, _ *mcp.CallToolRequest, p if err != nil { return errorResult(err), nil, err } - err = client.DeleteNVMeNamespace(ctx, parameters.SVM, parameters.Name, parameters.OSType, parameters.AllowDeleteWhileMapped) + err = client.DeleteNVMeNamespace(ctx, parameters.SVM, parameters.Name, parameters.AllowDeleteWhileMapped) if err != nil { return errorResult(err), nil, err @@ -504,9 +493,6 @@ func newUpdateNVMeNamespace(in tool.NVMeNamespace) (ontap.NVMeNamespace, error) if in.Name == "" { return out, errors.New("NVMe namespace name is required") } - if in.OSType == "" { - return out, errors.New("OS type is required") - } if in.Size != "" { out.Space.Size = in.Size } @@ -521,9 +507,6 @@ func newDeleteNVMeNamespace(in tool.NVMeNamespace) error { if in.Name == "" { return errors.New("NVMe namespace name is required") } - if in.OSType == "" { - return errors.New("OS type is required") - } return nil } diff --git a/tool/tool.go b/tool/tool.go index 79b84c1..ae538c0 100644 --- a/tool/tool.go +++ b/tool/tool.go @@ -119,8 +119,8 @@ type NVMeSubsystem struct { Cluster string `json:"cluster_name" jsonschema:"cluster name"` SVM string `json:"svm_name" jsonschema:"SVM name"` Name string `json:"name" jsonschema:"name for NVMe subsystem"` - OSType string `json:"os_type" jsonschema:"operating system of the NVMe subsystem's hosts"` - HostNQNs []string `json:"hosts_nqns,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe hosts"` + OSType string `json:"os_type,omitzero" jsonschema:"operating system of the NVMe subsystem's hosts (e.g., aix, linux, vmware, windows)"` + HostNQNs []string `json:"hosts_nqns,omitzero" jsonschema:"array of NVMe qualified name (NQN) used to identify the NVMe hosts"` Comment string `json:"comment,omitzero" jsonschema:"configurable comment for the NVMe subsystem"` AllowDeleteWhileMapped bool `json:"allow_delete_while_mapped,omitzero" jsonschema:"Allows for the deletion of a mapped NVMe subsystem. This parameter should be used with caution."` AllowDeleteWithHosts bool `json:"allow_delete_with_hosts,omitzero" jsonschema:"Allows for the deletion of an NVMe subsystem with NVMe hosts. This parameter should be used with caution."` @@ -130,16 +130,15 @@ type NVMeSubsystemHost struct { Cluster string `json:"cluster_name" jsonschema:"cluster name"` SVM string `json:"svm_name" jsonschema:"SVM name"` Name string `json:"name" jsonschema:"name for NVMe subsystem"` - OSType string `json:"os_type" jsonschema:"operating system of the NVMe subsystem's hosts"` NQN string `json:"nqn,omitzero" jsonschema:"NVMe qualified name (NQN) used to identify the NVMe host"` - Records []string `json:"records,omitzero" jsonschema:"array of NVMe hosts specified to add multiple NVMe hosts to an NVMe subsystem"` + Records []string `json:"records_nqns,omitzero" jsonschema:"array of NVMe hosts specified to add multiple NVMe hosts to an NVMe subsystem"` } type NVMeNamespace struct { Cluster string `json:"cluster_name" jsonschema:"cluster name"` SVM string `json:"svm_name" jsonschema:"SVM name"` Name string `json:"name" jsonschema:"name for NVMe namespace"` - OSType string `json:"os_type" jsonschema:"operating system type of the NVMe namespace"` + OSType string `json:"os_type,omitzero" jsonschema:"operating system type of the NVMe namespace (e.g., aix, linux, vmware, windows)"` Size string `json:"space.size,omitzero" jsonschema:"total provisioned size of the NVMe namespace (e.g., '100GB', '1TB')"` AllowDeleteWhileMapped bool `json:"allow_delete_while_mapped,omitzero" jsonschema:"Allows deletion of a mapped NVMe namespace. This parameter should be used with caution."` } From 402bc12294f263bba095429eed74479a00d3e163 Mon Sep 17 00:00:00 2001 From: hardikl Date: Mon, 13 Apr 2026 14:50:03 +0530 Subject: [PATCH 7/7] feat: handled copilot comment --- server/nvme.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/nvme.go b/server/nvme.go index fd024c9..c285014 100644 --- a/server/nvme.go +++ b/server/nvme.go @@ -493,9 +493,10 @@ func newUpdateNVMeNamespace(in tool.NVMeNamespace) (ontap.NVMeNamespace, error) if in.Name == "" { return out, errors.New("NVMe namespace name is required") } - if in.Size != "" { - out.Space.Size = in.Size + if in.Size == "" { + return out, errors.New("at least one supported update field must be provided; size is supported for update") } + out.Space.Size = in.Size return out, nil }