diff --git a/descriptions/descriptions.go b/descriptions/descriptions.go index e375a01..a09d216 100644 --- a/descriptions/descriptions.go +++ b/descriptions/descriptions.go @@ -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.*"). diff --git a/docs/examples.md b/docs/examples.md index ba85471..55e70c3 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -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** diff --git a/integration/test/fcp_test.go b/integration/test/fcp_test.go new file mode 100644 index 0000000..2a28aef --- /dev/null +++ b/integration/test/fcp_test.go @@ -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", + 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}, + }, + } + + 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} + + 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) + } + }) + } +} diff --git a/ontap/ontap.go b/ontap/ontap.go index 85b4c62..2617d86 100644 --- a/ontap/ontap.go +++ b/ontap/ontap.go @@ -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" diff --git a/rest/fcp.go b/rest/fcp.go new file mode 100644 index 0000000..3e1f8bb --- /dev/null +++ b/rest/fcp.go @@ -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) + } + + 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) +} diff --git a/server/fcp.go b/server/fcp.go new file mode 100644 index 0000000..84cb421 --- /dev/null +++ b/server/fcp.go @@ -0,0 +1,284 @@ +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) CreateFCPService(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.FCPService) (*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) + + fcpServiceCreate, err := newCreateFCPService(parameters) + if err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.CreateFCPService(ctx, fcpServiceCreate) + + if err != nil { + return errorResult(err), nil, err + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "FCP service created successfully"}, + }, + }, nil, nil +} + +func (a *App) UpdateFCPService(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.FCPServiceUpdate) (*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) + + fcpServiceUpdate, err := newUpdateFCPService(parameters) + if err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.UpdateFCPService(ctx, parameters.SVM, fcpServiceUpdate) + + if err != nil { + return errorResult(err), nil, err + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "FCP service updated successfully"}, + }, + }, nil, nil +} + +func (a *App) DeleteFCPService(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.FCPService) (*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 := newDeleteFCPService(parameters); err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.DeleteFCPService(ctx, parameters.SVM) + + if err != nil { + return errorResult(err), nil, err + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "FCP service deleted successfully"}, + }, + }, nil, nil +} + +func newCreateFCPService(in tool.FCPService) (ontap.FCPService, error) { + out := ontap.FCPService{} + if in.SVM == "" { + return out, errors.New("SVM name is required") + } + + out.SVM = ontap.NameAndUUID{Name: in.SVM} + if in.Enabled != "" { + out.Enabled = in.Enabled + } + return out, nil +} + +func newUpdateFCPService(in tool.FCPServiceUpdate) (ontap.FCPService, error) { + out := ontap.FCPService{} + if in.SVM == "" { + return out, errors.New("SVM name is required") + } + out.Enabled = in.Enabled + if out.Enabled == "" { + return out, errors.New("at least one field must be provided for update (e.g. enabled)") + } + return out, nil +} + +func newDeleteFCPService(in tool.FCPService) error { + if in.SVM == "" { + return errors.New("SVM name is required") + } + return nil +} + +func (a *App) CreateFCInterface(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.FCInterfaceCreate) (*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) + + fcInterfaceCreate, err := newCreateFCInterface(parameters) + if err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.CreateFCInterface(ctx, fcInterfaceCreate) + + if err != nil { + return errorResult(err), nil, err + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "FC interface created successfully"}, + }, + }, nil, nil +} + +func (a *App) UpdateFCInterface(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.FCInterface) (*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) + + fcInterfaceUpdate, err := newUpdateFCInterface(parameters) + if err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.UpdateFCInterface(ctx, parameters.SVM, parameters.Name, fcInterfaceUpdate) + + if err != nil { + return errorResult(err), nil, err + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "FC interface updated successfully"}, + }, + }, nil, nil +} + +func (a *App) DeleteFCInterface(ctx context.Context, _ *mcp.CallToolRequest, parameters tool.FCInterface) (*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 := newDeleteFCInterface(parameters); err != nil { + return nil, nil, err + } + + client, err := a.getClient(parameters.Cluster) + if err != nil { + return errorResult(err), nil, err + } + err = client.DeleteFCInterface(ctx, parameters.SVM, parameters.Name) + + if err != nil { + return errorResult(err), nil, err + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "FC interface deleted successfully"}, + }, + }, nil, nil +} + +func newCreateFCInterface(in tool.FCInterfaceCreate) (ontap.FCInterface, error) { + out := ontap.FCInterface{} + if in.SVM == "" { + return out, errors.New("SVM name is required") + } + if in.Name == "" { + return out, errors.New("FC interface name is required") + } + if in.DataProtocol == "" { + return out, errors.New("data protocol is required") + } + if in.HomeNodeName == "" { + return out, errors.New("home node name is required") + } + if in.HomePortName == "" { + return out, errors.New("home port name is required") + } + + out.SVM = ontap.NameAndUUID{Name: in.SVM} + out.Name = in.Name + out.DataProtocol = in.DataProtocol + out.Location = ontap.FCInterfaceLocation{ + HomePort: ontap.FCInterfacePort{ + Name: in.HomePortName, + Node: ontap.NameAndUUID{Name: in.HomeNodeName}, + }, + } + if in.Enabled != "" { + out.Enabled = in.Enabled + } + return out, nil +} + +func newUpdateFCInterface(in tool.FCInterface) (ontap.FCInterface, error) { + out := ontap.FCInterface{} + if in.SVM == "" { + return out, errors.New("SVM name is required") + } + if in.Name == "" { + return out, errors.New("FC interface name is required") + } + + hasUpdates := false + if in.Enabled != "" { + out.Enabled = in.Enabled + hasUpdates = true + } + if (in.HomeNodeName == "" && in.HomePortName != "") || (in.HomeNodeName != "" && in.HomePortName == "") { + return out, errors.New("both location.home_port.node.name and location.home_port.name must be provided together or both omitted") + } + if in.HomeNodeName != "" && in.HomePortName != "" { + out.Location = ontap.FCInterfaceLocation{ + HomePort: ontap.FCInterfacePort{ + Name: in.HomePortName, + Node: ontap.NameAndUUID{Name: in.HomeNodeName}, + }, + } + hasUpdates = true + } + if !hasUpdates { + return out, errors.New("at least one supported update field must be provided; only enabled and home-node & home-port are supported for update") + } + return out, nil +} + +func newDeleteFCInterface(in tool.FCInterface) error { + if in.SVM == "" { + return errors.New("SVM name is required") + } + if in.Name == "" { + return errors.New("FC interface name is required") + } + return nil +} diff --git a/server/server.go b/server/server.go index a44939b..8afd658 100644 --- a/server/server.go +++ b/server/server.go @@ -170,6 +170,16 @@ func (a *App) createMCPServer() *mcp.Server { addTool(a, server, "create_nvme_subsystem_map", descriptions.CreateNVMeSubsystemMap, createAnnotation, a.CreateNVMeSubsystemMap) addTool(a, server, "delete_nvme_subsystem_map", descriptions.DeleteNVMeSubsystemMap, deleteAnnotation, a.DeleteNVMeSubsystemMap) + // operation on FCP service object + addTool(a, server, "create_fcp_service", descriptions.CreateFCPService, createAnnotation, a.CreateFCPService) + addTool(a, server, "update_fcp_service", descriptions.UpdateFCPService, updateAnnotation, a.UpdateFCPService) + addTool(a, server, "delete_fcp_service", descriptions.DeleteFCPService, deleteAnnotation, a.DeleteFCPService) + + // operation on FC interface object + addTool(a, server, "create_fc_interface", descriptions.CreateFCInterface, createAnnotation, a.CreateFCInterface) + addTool(a, server, "update_fc_interface", descriptions.UpdateFCInterface, updateAnnotation, a.UpdateFCInterface) + addTool(a, server, "delete_fc_interface", descriptions.DeleteFCInterface, deleteAnnotation, a.DeleteFCInterface) + 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 55e5496..26596ee 100644 --- a/tool/tool.go +++ b/tool/tool.go @@ -192,6 +192,37 @@ type NVMeSubsystemMap struct { Namespace string `json:"namespace_name" jsonschema:"name for NVMe namespace"` } +type FCPServiceUpdate struct { + Cluster string `json:"cluster_name" jsonschema:"cluster name"` + SVM string `json:"svm_name" jsonschema:"SVM name"` + Enabled string `json:"enabled" jsonschema:"admin state of the FCP service"` +} + +type FCPService struct { + Cluster string `json:"cluster_name" jsonschema:"cluster name"` + SVM string `json:"svm_name" jsonschema:"SVM name"` + Enabled string `json:"enabled,omitzero" jsonschema:"admin state of the FCP service"` +} + +type FCInterfaceCreate struct { + Cluster string `json:"cluster_name" jsonschema:"cluster name"` + SVM string `json:"svm_name" jsonschema:"SVM name"` + Name string `json:"name" jsonschema:"FC interface name"` + DataProtocol string `json:"data_protocol" jsonschema:"data protocol of the FC interface (e.g. fcp)"` + Enabled string `json:"enabled,omitzero" jsonschema:"admin state of the FC interface"` + HomeNodeName string `json:"location.home_port.node.name" jsonschema:"name of the home node for the FC interface"` + HomePortName string `json:"location.home_port.name" jsonschema:"name of the home port on the home node for the FC interface"` +} + +type FCInterface struct { + Cluster string `json:"cluster_name" jsonschema:"cluster name"` + SVM string `json:"svm_name" jsonschema:"SVM name"` + Name string `json:"name" jsonschema:"FC interface name"` + Enabled string `json:"enabled,omitzero" jsonschema:"admin state of the FC interface"` + HomeNodeName string `json:"location.home_port.node.name,omitzero" jsonschema:"name of the home node for the FC interface"` + HomePortName string `json:"location.home_port.name,omitzero" jsonschema:"name of the home port on the home node for the FC interface"` +} + 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"`