Skip to content

Commit 435e6c9

Browse files
committed
Merge remote-tracking branch 'origin/main' into hl_svm_ex
# Conflicts: # docs/examples.md
2 parents 5b68e47 + 633d944 commit 435e6c9

8 files changed

Lines changed: 652 additions & 0 deletions

File tree

descriptions/descriptions.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ const CreateLUN = `Create a LUN on a specified volume and SVM with a given size
102102
const UpdateLUN = `Update a LUN: resize, rename, or toggle enabled/disabled state (online/offline).`
103103
const DeleteLUN = `Delete a LUN from a specified volume and SVM.`
104104

105+
const CreateFCPService = `Create FCP service on a cluster by cluster name.`
106+
const UpdateFCPService = `Update FCP service on a cluster by cluster name.`
107+
const DeleteFCPService = `Delete FCP service on a cluster by cluster name.`
108+
109+
const CreateFCInterface = `Create FC interface on a cluster by cluster name.`
110+
const UpdateFCInterface = `Update FC interface on a cluster by cluster name.`
111+
const DeleteFCInterface = `Delete FC interface on a cluster by cluster name.`
112+
105113
const ListOntapEndpoints = `List ONTAP REST collection endpoints in the catalog.
106114
The catalog contains all endpoints — can be large. Prefer search_ontap_endpoints for targeted discovery.
107115
Use the optional 'match' parameter to filter by substring or regex pattern (e.g. "snapshot", "lun", ".*nfs.*export.*").

docs/examples.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,26 @@ Expected Response: LUN has been deleted successfully.
345345

346346
---
347347

348+
### Manage FCP
349+
350+
- On the umeng-aff300-05-06 cluster, enable fcp service in marketing svm
351+
352+
Expected Response: The fcp service has been successfully created.
353+
354+
- 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
355+
356+
Expected Response: The fc interface has been successfully created.
357+
358+
- On the umeng-aff300-05-06 cluster, delete fc interface fc1 in marketing svm
359+
360+
Expected Response: The fc interface has been successfully deleted.
361+
362+
- On the umeng-aff300-05-06 cluster, update fcp service to disable on the marketing svm
363+
364+
Expected Response: The fcp service has been successfully updated.
365+
366+
---
367+
348368
### SVM Provisioning
349369

350370
**Create a SVM**

integration/test/fcp_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"crypto/tls"
6+
"log/slog"
7+
"net/http"
8+
"testing"
9+
"time"
10+
11+
"github.com/netapp/ontap-mcp/config"
12+
)
13+
14+
const SarCluster = "sar"
15+
const SarClusterStr = "On the " + SarCluster + " cluster, "
16+
17+
func TestFCP(t *testing.T) {
18+
SkipIfMissing(t, CheckTools)
19+
20+
tests := []struct {
21+
name string
22+
input string
23+
expectedOntapErr string
24+
verifyAPI ontapVerifier
25+
}{
26+
{
27+
name: "Clean FC Interface",
28+
input: SarClusterStr + "delete fc interface " + rn("fc1") + " in marketing svm",
29+
expectedOntapErr: "because it does not exist",
30+
verifyAPI: ontapVerifier{api: "api/network/fc/interfaces?name=" + rn("fc1") + "&svm.name=marketing", validationFunc: deleteObject},
31+
},
32+
{
33+
name: "Create FC Interface",
34+
input: SarClusterStr + "create fc interface " + rn("fc1") + " in marketing svm at port 0e in node umeng-aff300-01 of fcp data protocol",
35+
expectedOntapErr: "",
36+
verifyAPI: ontapVerifier{api: "api/network/fc/interfaces?name=" + rn("fc1") + "&svm.name=marketing", validationFunc: createObject},
37+
},
38+
{
39+
name: "Update FC Interface",
40+
input: SarClusterStr + "disable fc interface " + rn("fc1") + " in marketing svm",
41+
expectedOntapErr: "",
42+
verifyAPI: ontapVerifier{},
43+
},
44+
{
45+
name: "Clean FC Interface",
46+
input: SarClusterStr + "delete fc interface " + rn("fc1") + " in marketing svm",
47+
expectedOntapErr: "",
48+
verifyAPI: ontapVerifier{api: "api/network/fc/interfaces?name=" + rn("fc1") + "&svm.name=marketing", validationFunc: deleteObject},
49+
},
50+
}
51+
52+
cfg, err := config.ReadConfig(ConfigFile)
53+
if err != nil {
54+
t.Fatalf("Error parsing the config: %v", err)
55+
}
56+
57+
poller := cfg.Pollers[SarCluster]
58+
if poller == nil {
59+
t.Skipf("Cluster %q not found in %s, skipping FCP tests", SarCluster, ConfigFile)
60+
}
61+
transport := &http.Transport{
62+
TLSClientConfig: &tls.Config{
63+
InsecureSkipVerify: poller.UseInsecureTLS, // #nosec G402
64+
},
65+
}
66+
client := &http.Client{Transport: transport, Timeout: 10 * time.Second}
67+
68+
for _, tt := range tests {
69+
t.Run(tt.name, func(t *testing.T) {
70+
slog.Debug("", slog.String("Input", tt.input))
71+
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
72+
defer cancel()
73+
if _, err := testAgent.ChatWithResponse(ctx, t, tt.input, tt.expectedOntapErr); err != nil {
74+
t.Fatalf("Error processing input %q: %v", tt.input, err)
75+
}
76+
if tt.verifyAPI.api != "" && !tt.verifyAPI.validationFunc(t, tt.verifyAPI.api, poller, client) {
77+
t.Errorf("Error while accessing the object via prompt %q", tt.input)
78+
}
79+
})
80+
}
81+
}

ontap/ontap.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,28 @@ type NVMeSubsystemMap struct {
284284
Namespace NameAndUUID `json:"namespace" jsonschema:"namespace name"`
285285
}
286286

287+
type FCPService struct {
288+
SVM NameAndUUID `json:"svm,omitzero" jsonschema:"svm name"`
289+
Enabled string `json:"enabled,omitzero" jsonschema:"admin state of the FCP service"`
290+
}
291+
292+
type FCInterfacePort struct {
293+
Name string `json:"name,omitzero" jsonschema:"FC port name"`
294+
Node NameAndUUID `json:"node,omitzero" jsonschema:"node on which the FC port is located"`
295+
}
296+
297+
type FCInterfaceLocation struct {
298+
HomePort FCInterfacePort `json:"home_port,omitzero" jsonschema:"home port of the FC interface"`
299+
}
300+
301+
type FCInterface struct {
302+
SVM NameAndUUID `json:"svm,omitzero" jsonschema:"svm name"`
303+
Name string `json:"name,omitzero" jsonschema:"FC interface name"`
304+
DataProtocol string `json:"data_protocol,omitzero" jsonschema:"data protocol of the FC interface (e.g. fcp)"`
305+
Enabled string `json:"enabled,omitzero" jsonschema:"admin state of the FC interface"`
306+
Location FCInterfaceLocation `json:"location,omitzero" jsonschema:"location of the FC interface"`
307+
}
308+
287309
const (
288310
ASAr2 = "asar2"
289311
CDOT = "cdot"

rest/fcp.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package rest
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"net/url"
8+
9+
"github.com/netapp/ontap-mcp/ontap"
10+
)
11+
12+
func (c *Client) CreateFCPService(ctx context.Context, fcpService ontap.FCPService) error {
13+
var statusCode int
14+
15+
responseHeaders := http.Header{}
16+
17+
builder := c.baseRequestBuilder(`/api/protocols/san/fcp/services`, &statusCode, responseHeaders).
18+
BodyJSON(fcpService)
19+
20+
if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
21+
return err
22+
}
23+
24+
return c.checkStatus(statusCode)
25+
}
26+
27+
func (c *Client) UpdateFCPService(ctx context.Context, svmName string, fcpService ontap.FCPService) error {
28+
var (
29+
statusCode int
30+
fcpSr ontap.GetData
31+
)
32+
33+
responseHeaders := http.Header{}
34+
35+
params := url.Values{}
36+
params.Set("svm.name", svmName)
37+
38+
builder := c.baseRequestBuilder(`/api/protocols/san/fcp/services`, &statusCode, responseHeaders).
39+
Params(params).
40+
ToJSON(&fcpSr)
41+
42+
if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
43+
return err
44+
}
45+
46+
if fcpSr.NumRecords == 0 {
47+
return fmt.Errorf("failed to get detail of fcp service in svm %s because it does not exist", svmName)
48+
}
49+
50+
if fcpSr.NumRecords != 1 {
51+
return fmt.Errorf("failed to get fcp service on svm=%s because there are %d matching records",
52+
svmName, fcpSr.NumRecords)
53+
}
54+
55+
builder = c.baseRequestBuilder(`/api/protocols/san/fcp/services/`+fcpSr.Records[0].Svm.UUID, &statusCode, responseHeaders).
56+
BodyJSON(fcpService).
57+
Patch()
58+
59+
if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
60+
return err
61+
}
62+
63+
return c.checkStatus(statusCode)
64+
}
65+
66+
func (c *Client) DeleteFCPService(ctx context.Context, svmName string) error {
67+
var (
68+
statusCode int
69+
fcpSr ontap.GetData
70+
)
71+
72+
responseHeaders := http.Header{}
73+
74+
params := url.Values{}
75+
params.Set("svm.name", svmName)
76+
77+
builder := c.baseRequestBuilder(`/api/protocols/san/fcp/services`, &statusCode, responseHeaders).
78+
Params(params).
79+
ToJSON(&fcpSr)
80+
81+
if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
82+
return err
83+
}
84+
85+
if fcpSr.NumRecords == 0 {
86+
return fmt.Errorf("failed to get detail of fcp service in svm %s because it does not exist", svmName)
87+
}
88+
89+
if fcpSr.NumRecords != 1 {
90+
return fmt.Errorf("failed to get fcp service on svm=%s because there are %d matching records",
91+
svmName, fcpSr.NumRecords)
92+
}
93+
94+
builder = c.baseRequestBuilder(`/api/protocols/san/fcp/services/`+fcpSr.Records[0].Svm.UUID, &statusCode, responseHeaders).
95+
Delete()
96+
97+
if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
98+
return err
99+
}
100+
101+
return c.checkStatus(statusCode)
102+
}
103+
104+
func (c *Client) CreateFCInterface(ctx context.Context, fcInterface ontap.FCInterface) error {
105+
var statusCode int
106+
107+
responseHeaders := http.Header{}
108+
109+
builder := c.baseRequestBuilder(`/api/network/fc/interfaces`, &statusCode, responseHeaders).
110+
BodyJSON(fcInterface)
111+
112+
if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
113+
return err
114+
}
115+
116+
return c.checkStatus(statusCode)
117+
}
118+
119+
func (c *Client) UpdateFCInterface(ctx context.Context, svmName string, name string, fcInterface ontap.FCInterface) error {
120+
var (
121+
statusCode int
122+
fcIfSr ontap.GetData
123+
)
124+
125+
responseHeaders := http.Header{}
126+
127+
params := url.Values{}
128+
params.Set("svm.name", svmName)
129+
params.Set("name", name)
130+
131+
builder := c.baseRequestBuilder(`/api/network/fc/interfaces`, &statusCode, responseHeaders).
132+
Params(params).
133+
ToJSON(&fcIfSr)
134+
135+
if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
136+
return err
137+
}
138+
139+
if fcIfSr.NumRecords == 0 {
140+
return fmt.Errorf("failed to get detail of fc interface %s in svm %s because it does not exist", name, svmName)
141+
}
142+
143+
if fcIfSr.NumRecords != 1 {
144+
return fmt.Errorf("failed to get unique fc interface %s in svm=%s because there are %d matching records",
145+
name, svmName, fcIfSr.NumRecords)
146+
}
147+
148+
builder = c.baseRequestBuilder(`/api/network/fc/interfaces/`+fcIfSr.Records[0].UUID, &statusCode, responseHeaders).
149+
BodyJSON(fcInterface).
150+
Patch()
151+
152+
if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
153+
return err
154+
}
155+
156+
return c.checkStatus(statusCode)
157+
}
158+
159+
func (c *Client) DeleteFCInterface(ctx context.Context, svmName string, name string) error {
160+
var (
161+
statusCode int
162+
fcIfSr ontap.GetData
163+
)
164+
165+
responseHeaders := http.Header{}
166+
167+
params := url.Values{}
168+
params.Set("svm.name", svmName)
169+
params.Set("name", name)
170+
171+
builder := c.baseRequestBuilder(`/api/network/fc/interfaces`, &statusCode, responseHeaders).
172+
Params(params).
173+
ToJSON(&fcIfSr)
174+
175+
if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
176+
return err
177+
}
178+
179+
if fcIfSr.NumRecords == 0 {
180+
return fmt.Errorf("failed to get detail of fc interface %s in svm %s because it does not exist", name, svmName)
181+
}
182+
183+
if fcIfSr.NumRecords != 1 {
184+
return fmt.Errorf("failed to delete fc interface %s in svm=%s because there are %d matching records",
185+
name, svmName, fcIfSr.NumRecords)
186+
}
187+
188+
builder = c.baseRequestBuilder(`/api/network/fc/interfaces/`+fcIfSr.Records[0].UUID, &statusCode, responseHeaders).
189+
Delete()
190+
191+
if err := c.buildAndExecuteRequest(ctx, builder); err != nil {
192+
return err
193+
}
194+
195+
return c.checkStatus(statusCode)
196+
}

0 commit comments

Comments
 (0)