Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions descriptions/descriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ const CreateLUN = `Create a LUN on a specified volume and SVM with a given size
const UpdateLUN = `Update a LUN: resize, rename, or toggle enabled/disabled state (online/offline).`
const DeleteLUN = `Delete a LUN from a specified volume and SVM.`

const CreateFCPService = `Create FCP service on a cluster by cluster name.`
const UpdateFCPService = `Update FCP service on a cluster by cluster name.`
const DeleteFCPService = `Delete FCP service on a cluster by cluster name.`

const CreateFCInterface = `Create FC interface on a cluster by cluster name.`
const UpdateFCInterface = `Update FC interface on a cluster by cluster name.`
const DeleteFCInterface = `Delete FC interface 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
20 changes: 20 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,26 @@ Expected Response: LUN has been deleted successfully.

---

### Manage FCP

- On the umeng-aff300-05-06 cluster, enable fcp service in marketing svm

Expected Response: The fcp service has been successfully created.

- On the umeng-aff300-05-06 cluster, create fc interface fc1 in marketing svm at port 0e in node umeng-aff300-01 of fcp data protocol

Expected Response: The fc interface has been successfully created.

- On the umeng-aff300-05-06 cluster, delete fc interface fc1 in marketing svm

Expected Response: The fc interface has been successfully deleted.

- On the umeng-aff300-05-06 cluster, update fcp service to disable on the marketing svm

Expected Response: The fcp service has been successfully updated.

---

### Querying Specific Fields

**Get volume space and protection details**
Expand Down
81 changes: 81 additions & 0 deletions integration/test/fcp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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 TestFCP(t *testing.T) {
SkipIfMissing(t, CheckTools)

tests := []struct {
name string
input string
expectedOntapErr string
verifyAPI ontapVerifier
}{
{
name: "Clean FC Interface",
input: SarClusterStr + "delete fc interface " + rn("fc1") + " in marketing svm",
expectedOntapErr: "because it does not exist",
Comment thread
Hardikl marked this conversation as resolved.
verifyAPI: ontapVerifier{api: "api/network/fc/interfaces?name=" + rn("fc1") + "&svm.name=marketing", validationFunc: deleteObject},
},
{
name: "Create FC Interface",
input: SarClusterStr + "create fc interface " + rn("fc1") + " in marketing svm at port 0e in node umeng-aff300-01 of fcp data protocol",
expectedOntapErr: "",
verifyAPI: ontapVerifier{api: "api/network/fc/interfaces?name=" + rn("fc1") + "&svm.name=marketing", validationFunc: createObject},
},
{
name: "Update FC Interface",
input: SarClusterStr + "disable fc interface " + rn("fc1") + " in marketing svm",
expectedOntapErr: "",
verifyAPI: ontapVerifier{},
},
{
name: "Clean FC Interface",
input: SarClusterStr + "delete fc interface " + rn("fc1") + " in marketing svm",
expectedOntapErr: "",
verifyAPI: ontapVerifier{api: "api/network/fc/interfaces?name=" + rn("fc1") + "&svm.name=marketing", validationFunc: deleteObject},
},
Comment thread
Hardikl marked this conversation as resolved.
}
Comment thread
Hardikl marked this conversation as resolved.

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 FCP tests", SarCluster, ConfigFile)
}
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: poller.UseInsecureTLS, // #nosec G402
},
}
client := &http.Client{Transport: transport, Timeout: 10 * time.Second}
Comment thread
Hardikl marked this conversation as resolved.

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 %q", tt.input)
}
})
}
}
22 changes: 22 additions & 0 deletions ontap/ontap.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,28 @@ type NVMeSubsystemMap struct {
Namespace NameAndUUID `json:"namespace" jsonschema:"namespace name"`
}

type FCPService struct {
SVM NameAndUUID `json:"svm,omitzero" jsonschema:"svm name"`
Enabled string `json:"enabled,omitzero" jsonschema:"admin state of the FCP service"`
}

type FCInterfacePort struct {
Name string `json:"name,omitzero" jsonschema:"FC port name"`
Node NameAndUUID `json:"node,omitzero" jsonschema:"node on which the FC port is located"`
}

type FCInterfaceLocation struct {
HomePort FCInterfacePort `json:"home_port,omitzero" jsonschema:"home port of the FC interface"`
}

type FCInterface struct {
SVM NameAndUUID `json:"svm,omitzero" jsonschema:"svm name"`
Name string `json:"name,omitzero" jsonschema:"FC interface name"`
DataProtocol string `json:"data_protocol,omitzero" jsonschema:"data protocol of the FC interface (e.g. fcp)"`
Enabled string `json:"enabled,omitzero" jsonschema:"admin state of the FC interface"`
Location FCInterfaceLocation `json:"location,omitzero" jsonschema:"location of the FC interface"`
}

const (
ASAr2 = "asar2"
CDOT = "cdot"
Expand Down
196 changes: 196 additions & 0 deletions rest/fcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package rest

import (
"context"
"fmt"
"net/http"
"net/url"

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

func (c *Client) CreateFCPService(ctx context.Context, fcpService ontap.FCPService) error {
var statusCode int

responseHeaders := http.Header{}

builder := c.baseRequestBuilder(`/api/protocols/san/fcp/services`, &statusCode, responseHeaders).
BodyJSON(fcpService)

if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
return err
}

return c.checkStatus(statusCode)
}

func (c *Client) UpdateFCPService(ctx context.Context, svmName string, fcpService ontap.FCPService) error {
var (
statusCode int
fcpSr ontap.GetData
)

responseHeaders := http.Header{}

params := url.Values{}
params.Set("svm.name", svmName)

builder := c.baseRequestBuilder(`/api/protocols/san/fcp/services`, &statusCode, responseHeaders).
Params(params).
ToJSON(&fcpSr)

if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
return err
}

if fcpSr.NumRecords == 0 {
return fmt.Errorf("failed to get detail of fcp service in svm %s because it does not exist", svmName)
}

if fcpSr.NumRecords != 1 {
return fmt.Errorf("failed to get fcp service on svm=%s because there are %d matching records",
svmName, fcpSr.NumRecords)
}

builder = c.baseRequestBuilder(`/api/protocols/san/fcp/services/`+fcpSr.Records[0].Svm.UUID, &statusCode, responseHeaders).
BodyJSON(fcpService).
Patch()

if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
return err
}

return c.checkStatus(statusCode)
}

func (c *Client) DeleteFCPService(ctx context.Context, svmName string) error {
var (
statusCode int
fcpSr ontap.GetData
)

responseHeaders := http.Header{}

params := url.Values{}
params.Set("svm.name", svmName)

builder := c.baseRequestBuilder(`/api/protocols/san/fcp/services`, &statusCode, responseHeaders).
Params(params).
ToJSON(&fcpSr)

if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
return err
}

if fcpSr.NumRecords == 0 {
return fmt.Errorf("failed to get detail of fcp service in svm %s because it does not exist", svmName)
}

if fcpSr.NumRecords != 1 {
return fmt.Errorf("failed to get fcp service on svm=%s because there are %d matching records",
svmName, fcpSr.NumRecords)
}

builder = c.baseRequestBuilder(`/api/protocols/san/fcp/services/`+fcpSr.Records[0].Svm.UUID, &statusCode, responseHeaders).
Delete()

if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
return err
}

return c.checkStatus(statusCode)
}

func (c *Client) CreateFCInterface(ctx context.Context, fcInterface ontap.FCInterface) error {
var statusCode int

responseHeaders := http.Header{}

builder := c.baseRequestBuilder(`/api/network/fc/interfaces`, &statusCode, responseHeaders).
BodyJSON(fcInterface)

if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
return err
}

return c.checkStatus(statusCode)
}

func (c *Client) UpdateFCInterface(ctx context.Context, svmName string, name string, fcInterface ontap.FCInterface) error {
var (
statusCode int
fcIfSr ontap.GetData
)

responseHeaders := http.Header{}

params := url.Values{}
params.Set("svm.name", svmName)
params.Set("name", name)

builder := c.baseRequestBuilder(`/api/network/fc/interfaces`, &statusCode, responseHeaders).
Params(params).
ToJSON(&fcIfSr)

if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
return err
}

if fcIfSr.NumRecords == 0 {
return fmt.Errorf("failed to get detail of fc interface %s in svm %s because it does not exist", name, svmName)
}

if fcIfSr.NumRecords != 1 {
return fmt.Errorf("failed to get unique fc interface %s in svm=%s because there are %d matching records",
name, svmName, fcIfSr.NumRecords)
}
Comment thread
Hardikl marked this conversation as resolved.

builder = c.baseRequestBuilder(`/api/network/fc/interfaces/`+fcIfSr.Records[0].UUID, &statusCode, responseHeaders).
BodyJSON(fcInterface).
Patch()

if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
return err
}

return c.checkStatus(statusCode)
}

func (c *Client) DeleteFCInterface(ctx context.Context, svmName string, name string) error {
var (
statusCode int
fcIfSr ontap.GetData
)

responseHeaders := http.Header{}

params := url.Values{}
params.Set("svm.name", svmName)
params.Set("name", name)

builder := c.baseRequestBuilder(`/api/network/fc/interfaces`, &statusCode, responseHeaders).
Params(params).
ToJSON(&fcIfSr)

if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
return err
}

if fcIfSr.NumRecords == 0 {
return fmt.Errorf("failed to get detail of fc interface %s in svm %s because it does not exist", name, svmName)
}

if fcIfSr.NumRecords != 1 {
return fmt.Errorf("failed to delete fc interface %s in svm=%s because there are %d matching records",
name, svmName, fcIfSr.NumRecords)
}

builder = c.baseRequestBuilder(`/api/network/fc/interfaces/`+fcIfSr.Records[0].UUID, &statusCode, responseHeaders).
Delete()

if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
return err
}

return c.checkStatus(statusCode)
}
Loading
Loading