Skip to content
Merged
14 changes: 14 additions & 0 deletions descriptions/descriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ const CreateNetworkIPInterface = `Create Network IP interface on a cluster by cl
const UpdateNetworkIPInterface = `Update Network IP interface on a cluster by cluster name.`
const DeleteNetworkIPInterface = `Delete Network IP interface 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 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.`
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.*").
Expand Down
22 changes: 21 additions & 1 deletion docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -261,6 +261,26 @@ 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.
Comment thread
Hardikl marked this conversation as resolved.

- 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 in marketing svm

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.

---

### Manage iSCSI Service
Expand Down
81 changes: 0 additions & 81 deletions integration/test/nvme_service_test.go

This file was deleted.

199 changes: 199 additions & 0 deletions integration/test/nvme_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package main

import (
"context"
"crypto/tls"
"github.com/carlmjohnson/requests"
"github.com/netapp/ontap-mcp/ontap"
"log/slog"
"net/http"
"testing"
"time"

"github.com/netapp/ontap-mcp/config"
)

const NvmeCluster = "aff"
const NvmeClusterStr = "On the " + NvmeCluster + " cluster, "

func TestNVMe(t *testing.T) {
SkipIfMissing(t, CheckTools)

tests := []struct {
name string
input string
expectedOntapErr string
verifyAPI ontapVerifier
}{
{
name: "Clean NVMe subsystem",
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: "Create NVMe subsystem",
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=" + rn("sys2"), validationFunc: createObject},
Comment thread
Hardikl marked this conversation as resolved.
},
{
name: "Update NVMe subsystem",
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 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 in marketing svm",
expectedOntapErr: "",
verifyAPI: ontapVerifier{},
},
{
name: "Clean NVMe subsystem",
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",
Comment thread
Hardikl marked this conversation as resolved.
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 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 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: "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 namespace",
input: NvmeClusterStr + "update nvme namespace '" + rn("/vol/docns/ns1") + "' with to 40mb size in nvmevs1 svm",
expectedOntapErr: "",
verifyAPI: ontapVerifier{},
},
{
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",
Comment thread
Hardikl marked this conversation as resolved.
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 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 in nvmevs1 svm",
expectedOntapErr: "because it does not exist",
Comment thread
Hardikl marked this conversation as resolved.
verifyAPI: ontapVerifier{api: "api/protocols/nvme/subsystems?svm.name=nvmevs1&name=" + rn("sys1"), validationFunc: deleteObject},
},
}

cfg, err := config.ReadConfig(ConfigFile)
if err != nil {
t.Fatalf("Error parsing the config: %v", err)
}

poller := cfg.Pollers[NvmeCluster]
if poller == nil {
t.Skipf("Cluster %q not found in %s, skipping NVMe tests", NvmeCluster, 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 {
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)
}
})
}
}

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("subsystem map does 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 exists")
}
Comment thread
Hardikl marked this conversation as resolved.
return false
}
}
37 changes: 37 additions & 0 deletions ontap/ontap.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,43 @@ type NetworkIPInterface struct {
ServicePolicy NameAndUUID `json:"service_policy,omitzero" jsonschema:"service policy"`
}

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 []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 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 host"`
Records []NVMeHost `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"`
Subsystem NameAndUUID `json:"subsystem" jsonschema:"subsystem name"`
Namespace NameAndUUID `json:"namespace" jsonschema:"namespace name"`
}

const (
ASAr2 = "asar2"
CDOT = "cdot"
Expand Down
Loading
Loading