Skip to content

Commit

Permalink
Merge pull request #148 from NetApp/49-new-resource-clusterpeers
Browse files Browse the repository at this point in the history
New resource Cluster Peers
  • Loading branch information
carchi8py authored Mar 15, 2024
2 parents ace2f90 + 506555f commit e07f787
Show file tree
Hide file tree
Showing 16 changed files with 779 additions and 16 deletions.
126 changes: 126 additions & 0 deletions docs/resources/cluster_peers_resource.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
page_title: "ONTAP: Cluster Peers"
subcategory: "Cluster"
description: |-
Cluster peers resource
---

# Resource Cluster Peers

Create/Modify/Delete a cluster peer.

### Related ONTAP commands
* cluster peer create
* cluster peer modify
* cluster peer delete

## Example Usage

```
resource "netapp-ontap_cluster_peers_resource" "cluster_peers" {
# required to know which system to interface with
cx_profile_name = "cluster3"
name = "testme"
remote = {
ip_addresses = ["10.10.10.10", "10.10.10.11"]
}
source_details = {
ip_addresses = ["10.10.10.12"]
}
peer_cx_profile_name = "cluster2"
passphrase = "12345678"
peer_applications = ["snapmirror"]
}
```

## Argument Reference

### Required

- `cx_profile_name` (String) Connection profile name
- `remote` (Attributes) (see [below for nested schema](#nestedatt--remote))
- `source_details` (Attributes) (see [below for nested schema](#nestedatt--source_details))

### Optional

- `passphrase` (String) User generated passphrase for use in authentication
- `generate_passphrase` (String) When true, ONTAP automatically generates a passphrase to authenticate cluster peers
- `name` (String) Name of the peering relationship or name of the remote peer
- `peer_applications` (String) SVM peering applications
- `peer_cx_profile_name` (String) Peer connection profile name, to be accepted from peer side to make the status OK

### Read-Only

- `id` (String) Cluster peer relation source identifier
- `peer_id` (String) Cluster peer relation destination identifier
- `state` (String) Cluster peering state

<a id="nestedatt--remote"></a>
### Nested Schema for `remote`

Required:

- `ip_addresses` (Set of String) list of the remote ip addresses

<a id="nestedatt--source_details"></a>
### Nested Schema for `remote`

Required:

- `ip_addresses` (Set of String) list of the remote ip addresses


## Import
This Resource supports import, which allows you to import existing cluster peer relation into the state of this resoruce.
Import require a unique ID composed of the cluster name and cx_profile_name, separated by a comma.

id = `name`,`cx_profile_name`

### Terraform Import

For example
```shell
terraform import netapp-ontap_cluster_peers_resource.example clutername-1,cluster4
```

!> The terraform import CLI command can only import resources into the state. Importing via the CLI does not generate configuration. If you want to generate the accompanying configuration for imported resources, use the import block instead.

### Terrafomr Import Block
This requires Terraform 1.5 or higher, and will auto create the configuration for you

First create the block
```terraform
import {
to = netapp-ontap_cluster_peers_resource.example.cluster_import
id = "clutername-1,cluster4"
}
```
Next run, this will auto create the configuration for you
```shell
terraform plan -generate-config-out=generated.tf
```
This will generate a file called generated.tf, which will contain the configuration for the imported resource
```terraform
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.
# __generated__ by Terraform from "clutername-1,cluster4"
resource "netapp-ontap_cluster_peers_resource.example" "cluster_peers_import" {
cx_profile_name = "cluster3"
name = "test"
generate_passphrase = false
passphrase = "12345678"
peer_applications = ["snapmirror"]
peer_cx_profile_name = "cluster2"
remote = {
ip_addresses = [
"10.10.10.10"
]
}
source_details = {
ip_addresses = [
"10.10.10.11"
]
}
state = "pending"
}
```
3 changes: 1 addition & 2 deletions docs/resources/cluster_schedule_resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ resource "netapp-ontap_cluster_schedule_resource" "cs_example2" {
}
```

<!-- schema generated by tfplugindocs -->
### Related ONTAP commands
## Argument Reference

### Required

Expand Down
10 changes: 7 additions & 3 deletions docs/resources/snapmirror_resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ description: |-

# Resource Snapmirror

Create/Delete a snapmirror resource

~> **NOTE:** This module currently does not support modifying an existing snapmirror relationship. This will be added in a future release.
Create/Modify/Delete a snapmirror resource

### Related ONTAP commands
* snapmirror create
* snapmirror modify
* snapmirror delete

## Supported Platforms
* On-perm ONTAP system 9.6 or higher

~> **NOTE:** Amazon FSx for NetApp ONTAP is not supported

## Example Usage
```
# Create a snapmirror
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/storage_lun_resource.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "netapp-ontap_storage_lun_resource Resource - terraform-provider-netapp-ontap"
subcategory: ""
subcategory: "storage"
description: |-
StorageLun resource
---
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/svm_peers_resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ resource "netapp-ontap_svm_peers_resource" "example" {

### Read-Only

- `id` (String) svm peeer identifier
- `id` (String) svm peer identifier

<a id="nestedatt--peer"></a>
### Nested Schema for `peer`
Expand Down
1 change: 1 addition & 0 deletions examples/resources/netapp-ontap_cluster_peers/provider.tf
15 changes: 15 additions & 0 deletions examples/resources/netapp-ontap_cluster_peers/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
resource "netapp-ontap_cluster_peers_resource" "cluster_peers" {
# required to know which system to interface with
cx_profile_name = "cluster3"
# name = "testme"
remote = {
ip_addresses = ["10.10.10.10"]
}
source_details = {
ip_addresses = ["10.10.10.11"]
}
peer_cx_profile_name = "cluster2"
# generate_passphrase = true
passphrase = "12345678"
peer_applications = ["snapmirror"]
}
1 change: 1 addition & 0 deletions examples/resources/netapp-ontap_cluster_peers/variables.tf
112 changes: 106 additions & 6 deletions internal/interfaces/cluster_peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package interfaces

import (
"fmt"

"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/mitchellh/mapstructure"
"github.com/netapp/terraform-provider-netapp-ontap/internal/restclient"
Expand All @@ -14,21 +15,49 @@ type ClusterPeerGetDataModelONTAP struct {
UUID string `mapstructure:"uuid"`
Remote Remote `mapstructure:"remote"`
Status Status `mapstructure:"status"`
Authentication Authentication `mapstructure:"authentication"`
PeerApplications []string `mapstructure:"peer_applications"`
Encryption ClusterPeerEncryption `mapstructure:"encryption"`
IPAddress string `mapstructure:"ip_address"`
Ipspace ClusterPeerIpspace `mapstructure:"ipspace"`
}

// ClusterPeersGetDataModelONTAP describes the GET record data model using go types for mapping.
type ClusterPeersGetDataModelONTAP struct {
Name string `mapstructure:"name"`
UUID string `mapstructure:"uuid"`
Authentication AuthenticationCreateResponse `mapstructure:"authentication"`
}

// ClusterPeersResourceBodyDataModelONTAP describes the body data model using go types for mapping.
type ClusterPeersResourceBodyDataModelONTAP struct {
Name string `mapstructure:"name,omitempty"`
Remote RemoteBody `mapstructure:"remote"`
PeerApplications []string `mapstructure:"peer_applications,omitempty"`
Authentication Authentication `mapstructure:"authentication"`
}

// ClusterPeerIpspace describes the GET record data model using go types for mapping.
type ClusterPeerIpspace struct {
Name string `mapstructure:"name"`
}

// Authentication describes the POST record body model using go types for mapping.
type Authentication struct {
State string `mapstructure:"state,omitempty"`
GeneratePassphrase bool `mapstructure:"generate_passphrase,omitempty"`
Passphrase string `mapstructure:"passphrase,omitempty"`
}

// AuthenticationCreateResponse describes the POST record response model using go types for mapping.
type AuthenticationCreateResponse struct {
Passphrase string `mapstructure:"passphrase,omitempty"`
}

// ClusterPeerEncryption describes the GET record data model using go types for mapping.
type ClusterPeerEncryption struct {
Propsed string `mapstructure:"proposed"`
State string `mapstructure:"state"`
Proposed string `mapstructure:"proposed"`
State string `mapstructure:"state"`
}

// Remote describes the GET record data model using go types for mapping.
Expand All @@ -37,6 +66,11 @@ type Remote struct {
Name string `mapstructure:"name"`
}

// RemoteBody describes the POST record body model using go types for mapping.
type RemoteBody struct {
IPAddress []string `mapstructure:"ip_addresses"`
}

// Status describes the GET record data model using go types for mapping.
type Status struct {
State string `mapstructure:"state"`
Expand All @@ -60,10 +94,10 @@ func GetClusterPeerByName(errorHandler *utils.ErrorHandler, r restclient.RestCli
query.Fields([]string{"name", "uuid", "remote", "status", "peer_applications", "encryption", "ip_address", "ipspace"})
statusCode, response, err := r.GetNilOrOneRecord("cluster/peers", query, nil)
if err != nil {
return nil, errorHandler.MakeAndReportError("Error getting cluster peer", fmt.Sprint("error on get cluster/peer: #{err}"))
return nil, errorHandler.MakeAndReportError("Error getting cluster peer", fmt.Sprintf("error on get cluster/peer: %s, statusCode %d", err, statusCode))
}
if response == nil {
return nil, errorHandler.MakeAndReportError("No cluster peer found", fmt.Sprint("no cluster peer found with name: #{name}"))
return nil, errorHandler.MakeAndReportError("No cluster peer found", fmt.Sprintf("no cluster peer found with name: %s, statusCode %d", name, statusCode))
}
var dataONTAP *ClusterPeerGetDataModelONTAP
if error := mapstructure.Decode(response, &dataONTAP); error != nil {
Expand All @@ -73,6 +107,21 @@ func GetClusterPeerByName(errorHandler *utils.ErrorHandler, r restclient.RestCli
return dataONTAP, nil
}

// GetClusterPeer to get ClusterPeer info by uuid
func GetClusterPeer(errorHandler *utils.ErrorHandler, r restclient.RestClient, uuid string) (*ClusterPeerGetDataModelONTAP, error) {
statusCode, response, err := r.GetNilOrOneRecord("cluster/peers/"+uuid, nil, nil)
if err != nil {
return nil, errorHandler.MakeAndReportError("error reading cluster peer info", fmt.Sprintf("error on GET cluster/peers: %s, statusCode %d", err, statusCode))
}

var dataONTAP *ClusterPeerGetDataModelONTAP
if err := mapstructure.Decode(response, &dataONTAP); err != nil {
return nil, errorHandler.MakeAndReportError("failed to decode response from GET cluster peer", fmt.Sprintf("error: %s, statusCode %d, response %#v", err, statusCode, response))
}
tflog.Debug(errorHandler.Ctx, fmt.Sprintf("Read cluster peer info: %#v", dataONTAP))
return dataONTAP, nil
}

// GetClusterPeers gets all cluster peers.
func GetClusterPeers(errorHandler *utils.ErrorHandler, r restclient.RestClient, filter *ClusterPeerDataSourceFilterModel) ([]ClusterPeerGetDataModelONTAP, error) {
query := r.NewQuery()
Expand All @@ -86,10 +135,10 @@ func GetClusterPeers(errorHandler *utils.ErrorHandler, r restclient.RestClient,
}
statusCode, response, err := r.GetZeroOrMoreRecords("cluster/peers", query, nil)
if err != nil {
return nil, errorHandler.MakeAndReportError("Error getting cluster peers", fmt.Sprint("error on get cluster/peers: #{err}"))
return nil, errorHandler.MakeAndReportError("Error getting cluster peers", fmt.Sprintf("error on get cluster/peers: %s, statusCode %d", err, statusCode))
}
if response == nil {
return nil, errorHandler.MakeAndReportError("No cluster peers found", fmt.Sprint("no cluster peers found with name: #{name}"))
return nil, errorHandler.MakeAndReportError("No cluster peers found", "no cluster peers fouund")
}
var dataONTAP []ClusterPeerGetDataModelONTAP
for _, info := range response {
Expand All @@ -102,3 +151,54 @@ func GetClusterPeers(errorHandler *utils.ErrorHandler, r restclient.RestClient,
tflog.Debug(errorHandler.Ctx, fmt.Sprintf("Read Cluster/peers source - udata: %#v", dataONTAP))
return dataONTAP, nil
}

// CreateClusterPeers to create cluster_peers
func CreateClusterPeers(errorHandler *utils.ErrorHandler, r restclient.RestClient, body ClusterPeersResourceBodyDataModelONTAP) (*ClusterPeersGetDataModelONTAP, error) {
api := "cluster/peers"
var bodyMap map[string]interface{}
if err := mapstructure.Decode(body, &bodyMap); err != nil {
return nil, errorHandler.MakeAndReportError("error encoding cluster_peers body", fmt.Sprintf("error on encoding %s body: %s, body: %#v", api, err, body))
}
query := r.NewQuery()
query.Add("return_records", "true")
query.Add("return_timeout", "15")
statusCode, response, err := r.CallCreateMethod(api, query, bodyMap)
if err != nil {
return nil, errorHandler.MakeAndReportError("error creating cluster_peers", fmt.Sprintf("error on POST %s: %s, statusCode %d", api, err, statusCode))
}

var dataONTAP ClusterPeersGetDataModelONTAP
if err := mapstructure.Decode(response.Records[0], &dataONTAP); err != nil {
return nil, errorHandler.MakeAndReportError("error decoding cluster_peers info", fmt.Sprintf("error on decode storage/cluster_peerss info: %s, statusCode %d, response %#v", err, statusCode, response))
}
tflog.Debug(errorHandler.Ctx, fmt.Sprintf("Create cluster_peers source - udata: %#v", dataONTAP))
return &dataONTAP, nil
}

// UpdateClusterPeers updates Cluster Peer
func UpdateClusterPeers(errorHandler *utils.ErrorHandler, r restclient.RestClient, data any, uuid string) error {
api := "cluster/peers/" + uuid
var body map[string]interface{}
if err := mapstructure.Decode(data, &body); err != nil {
return errorHandler.MakeAndReportError("error encoding cluster_peers body", fmt.Sprintf("error on encoding cluster/peers body: %s, body: %#v", err, data))
}
query := r.NewQuery()
query.Add("return_records", "true")
query.Add("return_timeout", "15")
// API has no option to return records
statusCode, _, err := r.CallUpdateMethod(api, query, body)
if err != nil {
return errorHandler.MakeAndReportError("error updating cluster_peers", fmt.Sprintf("error on PATCH cluster/peers: %s, statusCode %d", err, statusCode))
}
return nil
}

// DeleteClusterPeers to delete cluster_peers
func DeleteClusterPeers(errorHandler *utils.ErrorHandler, r restclient.RestClient, uuid string) error {
api := "cluster/peers"
statusCode, _, err := r.CallDeleteMethod(api+"/"+uuid, nil, nil)
if err != nil {
return errorHandler.MakeAndReportError("error deleting cluster_peers", fmt.Sprintf("error on DELETE %s: %s, statusCode %d", api, err, statusCode))
}
return nil
}
2 changes: 1 addition & 1 deletion internal/interfaces/svm_peers.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func CreateSVMPeers(errorHandler *utils.ErrorHandler, r restclient.RestClient, b
return &dataONTAP, nil
}

// UpdateSVMPeers updates Snapmirror
// UpdateSVMPeers updates svm peers
func UpdateSVMPeers(errorHandler *utils.ErrorHandler, r restclient.RestClient, data any, uuid string) error {
api := "svm/peers/" + uuid
var body map[string]interface{}
Expand Down
2 changes: 1 addition & 1 deletion internal/provider/cluster_peer_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func (d *ClusterPeerDataSource) Read(ctx context.Context, req datasource.ReadReq
data.PeerApplications[index] = types.StringValue(peerApplication)
}
data.Encryption = &ClusterPeerDataSourceEncryption{
Proposed: types.StringValue(restInfo.Encryption.Propsed),
Proposed: types.StringValue(restInfo.Encryption.Proposed),
State: types.StringValue(restInfo.Encryption.State),
}
data.IPAddress = types.StringValue(restInfo.IPAddress)
Expand Down
Loading

0 comments on commit e07f787

Please sign in to comment.