Skip to content

Commit

Permalink
Public access setting (#78)
Browse files Browse the repository at this point in the history
* Public access setting

* Fix docs

* Fix the cluster edit flow without any real edit

* Add example for RF5 cluster

* Revert "Add example for RF5 cluster"

This reverts commit 5f1b52e.

* make doc

---------

Co-authored-by: Arnav Agarwal <[email protected]>
  • Loading branch information
cdavid and Arnav15 authored Oct 5, 2023
1 parent b441437 commit 751184d
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 12 deletions.
72 changes: 70 additions & 2 deletions docs/resources/cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,63 @@ resource "ybm_cluster" "multi_region_cluster" {
}
```

To create a multi-region cluster which supports upto 2 domain faults (RF 5)
To create a single region cluster in a dedicated VPC with public access

```terraform
# Cluster with single region
variable "password" {
type = string
description = "YSQL Password."
sensitive = true
}
resource "ybm_vpc" "example-vpc" {
name = "example-vpc"
cloud = "AWS"
region_cidr_info = [
{
region = "us-east-1"
cidr = "10.231.0.0/24"
}
]
}
resource "ybm_allow_list" "example_allow_list" {
allow_list_name = "allow-nobody"
allow_list_description = "allow 192.168.0.1"
cidr_list = ["192.168.0.1/32"]
}
resource "ybm_cluster" "single_region_cluster" {
cluster_name = "single-region-cluster"
cloud_type = "AWS"
cluster_type = "SYNCHRONOUS"
cluster_region_info = [
{
region = "us-east-1"
num_nodes = 1
vpc_id = ybm_vpc.example-vpc.vpc_id
public_access = true
}
]
cluster_tier = "PAID"
cluster_allow_list_ids = [ybm_allow_list.example_allow_list.allow_list_id]
fault_tolerance = "NONE"
node_config = {
num_cores = 4
disk_size_gb = 50
}
credentials = {
username = "example_ysql_user"
password = var.password
}
}
```

To create a multi-region cluster which supports up to 2 domain faults (RF 5)

```terraform
variable "password" {
Expand Down Expand Up @@ -557,11 +613,12 @@ resource "ybm_private_service_endpoint" "npsenonok-region" {
- `backup_schedules` (Attributes List) (see [below for nested schema](#nestedatt--backup_schedules))
- `cloud_type` (String) The cloud provider where the cluster is deployed: AWS, AZURE or GCP.
- `cluster_allow_list_ids` (List of String) List of IDs of the allow lists assigned to the cluster.
- `cluster_endpoints` (Map of String) The endpoints used to connect to the cluster by region.
- `cluster_endpoints` (Map of String, Deprecated) The endpoints used to connect to the cluster.
- `cluster_id` (String) The ID of the cluster. Created automatically when a cluster is created. Used to get a specific cluster.
- `cmk_spec` (Attributes) KMS Provider Configuration. (see [below for nested schema](#nestedatt--cmk_spec))
- `database_track` (String) The track of the database. Production or Innovation or Preview.
- `desired_state` (String) The desired state of the database, Active or Paused. This parameter can be used to pause/resume a cluster.
- `endpoints` (Attributes List) The endpoints used to connect to the cluster. (see [below for nested schema](#nestedatt--endpoints))
- `fault_tolerance` (String) The fault tolerance of the cluster. NONE, NODE, ZONE or REGION.
- `num_faults_to_tolerate` (Number) The number of domain faults the cluster can tolerate. 0 for NONE, 1 for ZONE and [1-3] for NODE and REGION
- `restore_backup_id` (String) The ID of the backup to be restored to the cluster.
Expand All @@ -584,6 +641,7 @@ Required:

Optional:

- `public_access` (Boolean)
- `vpc_id` (String)
- `vpc_name` (String)

Expand Down Expand Up @@ -684,6 +742,16 @@ Optional:



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

Optional:

- `accessibility_type` (String) The accessibility type of the endpoint. PUBLIC or PRIVATE.
- `host` (String) The host of the endpoint.
- `region` (String) The region of the endpoint.


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

Expand Down
51 changes: 51 additions & 0 deletions examples/resources/ybm_cluster/single-region-public-access.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Cluster with single region

variable "password" {
type = string
description = "YSQL Password."
sensitive = true
}

resource "ybm_vpc" "example-vpc" {
name = "example-vpc"
cloud = "AWS"
region_cidr_info = [
{
region = "us-east-1"
cidr = "10.231.0.0/24"
}
]
}

resource "ybm_allow_list" "example_allow_list" {
allow_list_name = "allow-nobody"
allow_list_description = "allow 192.168.0.1"
cidr_list = ["192.168.0.1/32"]
}


resource "ybm_cluster" "single_region_cluster" {
cluster_name = "single-region-cluster"
cloud_type = "AWS"
cluster_type = "SYNCHRONOUS"
cluster_region_info = [
{
region = "us-east-1"
num_nodes = 1
vpc_id = ybm_vpc.example-vpc.vpc_id
public_access = true
}
]
cluster_tier = "PAID"
cluster_allow_list_ids = [ybm_allow_list.example_allow_list.allow_list_id]
fault_tolerance = "NONE"
node_config = {
num_cores = 4
disk_size_gb = 50
}
credentials = {
username = "example_ysql_user"
password = var.password
}

}
16 changes: 12 additions & 4 deletions managed/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,17 @@ type Cluster struct {
ClusterVersion types.String `tfsdk:"cluster_version"`
BackupSchedules []BackupScheduleInfo `tfsdk:"backup_schedules"`
ClusterEndpoints types.Map `tfsdk:"cluster_endpoints"`
ClusterEndpointsV2 []ClusterEndpoint `tfsdk:"endpoints"`
ClusterCertificate types.String `tfsdk:"cluster_certificate"`
CMKSpec *CMKSpec `tfsdk:"cmk_spec"`
}

type ClusterEndpoint struct {
AccessibilityType types.String `tfsdk:"accessibility_type"`
Host types.String `tfsdk:"host"`
Region types.String `tfsdk:"region"`
}

type CMKSpec struct {
ProviderType types.String `tfsdk:"provider_type"`
AWSCMKSpec *AWSCMKSpec `tfsdk:"aws_cmk_spec"`
Expand Down Expand Up @@ -76,10 +83,11 @@ type BackupScheduleInfo struct {
TimeIntervalInDays types.Int64 `tfsdk:"time_interval_in_days"`
}
type RegionInfo struct {
Region types.String `tfsdk:"region"`
NumNodes types.Int64 `tfsdk:"num_nodes"`
VPCID types.String `tfsdk:"vpc_id"`
VPCName types.String `tfsdk:"vpc_name"`
Region types.String `tfsdk:"region"`
NumNodes types.Int64 `tfsdk:"num_nodes"`
VPCID types.String `tfsdk:"vpc_id"`
VPCName types.String `tfsdk:"vpc_name"`
PublicAccess types.Bool `tfsdk:"public_access"`
}

type NodeConfig struct {
Expand Down
6 changes: 6 additions & 0 deletions managed/resource_associate_me_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,12 @@ func getTaskState(accountId string, projectId string, entityId string, entityTyp

if v, ok := taskList.GetDataOk(); ok && v != nil {
c := taskList.GetData()

if len(c) == 0 {
tflog.Info(ctx, "No task found for this operation")
return "TASK_NOT_FOUND", true, ""
}

if len(c) > 0 {
if status, ok := c[0].GetInfoOk(); ok {
currentStatus = status.GetState()
Expand Down
120 changes: 115 additions & 5 deletions managed/resource_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ and modify the backup schedule of the cluster being created.`,
Optional: true,
Computed: true,
},
"public_access": {
Type: types.BoolType,
Optional: true,
Computed: true,
},
}),
},
"backup_schedules": {
Expand Down Expand Up @@ -404,13 +409,39 @@ and modify the backup schedule of the cluster being created.`,
},
},
"cluster_endpoints": {
Description: "The endpoints used to connect to the cluster by region.",
Description: "The endpoints used to connect to the cluster.",
DeprecationMessage: "This attribute is deprecated. Please use the 'endpoints' attribute instead.",
Type: types.MapType{
ElemType: types.StringType,
},
Optional: true,
Computed: true,
},
"endpoints": {
Description: "The endpoints used to connect to the cluster.",
Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{
"accessibility_type": {
Description: "The accessibility type of the endpoint. PUBLIC or PRIVATE.",
Type: types.StringType,
Computed: true,
Optional: true,
},
"host": {
Description: "The host of the endpoint.",
Type: types.StringType,
Computed: true,
Optional: true,
},
"region": {
Description: "The region of the endpoint.",
Type: types.StringType,
Computed: true,
Optional: true,
},
}),
Computed: true,
Optional: true,
},
"cluster_certificate": {
Description: "The certificate used to connect to the cluster.",
Type: types.StringType,
Expand Down Expand Up @@ -502,9 +533,33 @@ func createClusterSpec(ctx context.Context, apiClient *openapiclient.APIClient,

regionInfo.VPCID.Value = vpcData.Info.Id
}

// Create an array of AccessibilityType and populate it according to
// the following logic:
// if the cluster is in a private VPC, it MUST always have PRIVATE.
// if the cluster is NOT in a private VPC, it MUST always have PUBLIC.
// if the cluster is in a private VPC and customer wants public access, it MUST have PRIVATE and PUBLIC.
accessibilityTypes := []openapiclient.AccessibilityType{}

if vpcID := regionInfo.VPCID.Value; vpcID != "" {
info.PlacementInfo.SetVpcId(vpcID)
accessibilityTypes = append(accessibilityTypes, openapiclient.ACCESSIBILITYTYPE_PRIVATE)

if regionInfo.PublicAccess.Value {
accessibilityTypes = append(accessibilityTypes, openapiclient.ACCESSIBILITYTYPE_PUBLIC)
}
} else {
accessibilityTypes = append(accessibilityTypes, openapiclient.ACCESSIBILITYTYPE_PUBLIC)

if !regionInfo.PublicAccess.Value {
tflog.Debug(ctx, fmt.Sprintf("Cluster %v is in a public VPC and public access is disabled. ", plan.ClusterName.Value))
return nil, false, "Cluster is in a public VPC and public access is disabled. Please enable public access."
}
}

// Set the accessibility type for the region
info.SetAccessibilityTypes(accessibilityTypes)

if clusterType == "SYNCHRONOUS" {
info.PlacementInfo.SetMultiZone(false)
}
Expand Down Expand Up @@ -1361,6 +1416,21 @@ func resourceClusterRead(ctx context.Context, accountId string, projectId string
}
cluster.ClusterEndpoints = clusterEndpoints

// Cluster endpoints v2
var clusterEndpointsV2 []ClusterEndpoint
for _, val := range clusterResp.Data.Info.ClusterEndpoints {

tflog.Debug(ctx, fmt.Sprintf("Cluster Endpoint %v %v %v", val.GetAccessibilityType(), val.GetHost(), val.Region))

clusterEndpoint := ClusterEndpoint{
AccessibilityType: types.String{Value: string(val.GetAccessibilityType())},
Host: types.String{Value: val.GetHost()},
Region: types.String{Value: val.Region},
}
clusterEndpointsV2 = append(clusterEndpointsV2, clusterEndpoint)
}
cluster.ClusterEndpointsV2 = clusterEndpointsV2

// Cluster certificate
certResponse, certHttpResp, err := apiClient.ClusterApi.GetConnectionCertificate(context.Background()).Execute()
if err != nil {
Expand Down Expand Up @@ -1390,11 +1460,24 @@ func resourceClusterRead(ctx context.Context, accountId string, projectId string
}
vpcName = vpcData.Spec.Name
}

// if info.AccessibilityTypes contains "PUBLIC" then set PublicAccess to true
publicAccess := false
for _, accessibilityType := range info.GetAccessibilityTypes() {
if accessibilityType == "PUBLIC" {
publicAccess = true
break
}
}

tflog.Debug(ctx, fmt.Sprintf("For region %v, publicAccess = %v", region, publicAccess))

regionInfo := RegionInfo{
Region: types.String{Value: region},
NumNodes: types.Int64{Value: int64(info.PlacementInfo.GetNumNodes())},
VPCID: types.String{Value: vpcID},
VPCName: types.String{Value: vpcName},
Region: types.String{Value: region},
NumNodes: types.Int64{Value: int64(info.PlacementInfo.GetNumNodes())},
VPCID: types.String{Value: vpcID},
VPCName: types.String{Value: vpcName},
PublicAccess: types.Bool{Value: publicAccess},
}
clusterRegionInfo[destIndex] = regionInfo
}
Expand Down Expand Up @@ -1584,17 +1667,44 @@ func (r resourceCluster) Update(ctx context.Context, req tfsdk.UpdateResourceReq
return
}

// The following code has a pitfall:
// If we change just the cluster_allow_list_ids field, then we will send a cluster edit
// request to the server. The server will see the spec is the same as the current spec,
// so there will be no task submitted.
// If there is no task submitted (EVER), we will get a TASK_NOT_FOUND.
// If there was EVER a task submitted, we will get the status of that task (likely SUCCESS).
//
// Challenges:
// 1. Last EDIT was not successful - the customer should first perform an edit to get out of that state.
// 2. To work around ANY possible race condition in the server side (task created AFTER the response),
// we will try twice to read the task state. If both times we can't find the task, we bail out.
//
// Something similar will happen if changing the backup schedule or the CMK spec.
var retries int
retryPolicy := retry.NewConstant(10 * time.Second)
retryPolicy = retry.WithMaxDuration(3600*time.Second, retryPolicy)
err = retry.Do(ctx, retryPolicy, func(ctx context.Context) error {
asState, readInfoOK, message := getTaskState(accountId, projectId, clusterId, openapiclient.ENTITYTYPEENUM_CLUSTER, openapiclient.TASKTYPEENUM_EDIT_CLUSTER, apiClient, ctx)

tflog.Info(ctx, "Cluster edit operation in progress, state: "+asState)

if readInfoOK {
if asState == string(openapiclient.TASKACTIONSTATEENUM_SUCCEEDED) {
return nil
}
if asState == string(openapiclient.TASKACTIONSTATEENUM_FAILED) {
return ErrFailedTask
}
if asState == "TASK_NOT_FOUND" {
if retries < 2 {
retries++
tflog.Info(ctx, "Cluster edit task not found, retrying...")
return retry.RetryableError(errors.New("Cluster not found, retrying"))
} else {
tflog.Info(ctx, "Cluster edit task not found, giving up...")
return nil
}
}
} else {
return retry.RetryableError(errors.New("Unable to get cluster state: " + message))
}
Expand Down
6 changes: 5 additions & 1 deletion templates/resources/cluster.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ To create a multi region cluster by using common credentials for both YSQL and Y

{{ tffile "examples/resources/ybm_cluster/multi-region-common-credentials.tf" }}

To create a multi-region cluster which supports upto 2 domain faults (RF 5)
To create a single region cluster in a dedicated VPC with public access

{{ tffile "examples/resources/ybm_cluster/single-region-public-access.tf" }}

To create a multi-region cluster which supports up to 2 domain faults (RF 5)

{{ tffile "examples/resources/ybm_cluster/multi-region-rf5.tf" }}

Expand Down

0 comments on commit 751184d

Please sign in to comment.