Skip to content

Commit

Permalink
ec_deployment: Set snapshot restore strategy to partial on Update (#…
Browse files Browse the repository at this point in the history
…309)

Fixes a bug where the strategy field wasn't set on either of the create
or update operations and it made restoring a snapshot on an existing
deployment impossible since when `transient.snapshot_restore.strategy`
isn't set, the API will make it default to "full", which isn't a valid
restoration strategy for snapshots when a deployment has already been
created and it's running, for the operation to succeed, "partial" must
be used.

Signed-off-by: Marc Lopez <[email protected]>
  • Loading branch information
marclop authored May 13, 2021
1 parent 4d7a25e commit 65e381c
Show file tree
Hide file tree
Showing 8 changed files with 492 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .changelog/309.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/ec_deployment: Fixes a bug which made restoring a snapshot to an existing deployment fail.
```
40 changes: 40 additions & 0 deletions ec/acc/deployment_fixture_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package acc

import (
"fmt"
"os"
"testing"
)

// fixtureDeploymentDefaults takes the default region and default deployment template
// It assumes that the template order is region = %s and deployment_template = %s
func fixtureDeploymentDefaults(t *testing.T, fileName string) string {
t.Helper()
requiresAPIConn(t)

region := getRegion()
deploymentTpl := setDefaultTemplate(region, defaultTemplate)

b, err := os.ReadFile(fileName)
if err != nil {
t.Fatal(err)
}
return fmt.Sprintf(string(b), region, deploymentTpl)
}
119 changes: 119 additions & 0 deletions ec/acc/deployment_snapshot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package acc

import (
"fmt"
"net/http"
"testing"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

// creds is used as a container to pass around ES credentials.
type creds struct {
User string
Pass string
URL string
}

func TestAccDeployment_snapshot_restore(t *testing.T) {
var esCreds creds
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactory,
CheckDestroy: testAccDeploymentDestroy,
Steps: []resource.TestStep{
{
Config: fixtureDeploymentDefaults(t, "testdata/deployment_snapshot_1.tf"),
Check: resource.ComposeAggregateTestCheckFunc(
readEsCredentials(t, &esCreds),
),
},
{
// Creates a deployment restoring from snapshot. For some reason,
// this can take quite a long time. It will have an impact on the
// total run time of the acceptance tests.
PreConfig: triggerSnapshot(t, &esCreds),
Config: fixtureDeploymentDefaults(t, "testdata/deployment_snapshot_2.tf"),
// Since the `snapshot_source` block is never persisted, it'll
// always have a non-empty plan after applying.
ExpectNonEmptyPlan: true,
},
{
// Triggers a new snapshot and restores the last snapshot to the
// running Elasticsearch cluster. This ensures that the snapshot
// is restored and the plan returns with no error.
PreConfig: triggerSnapshot(t, &esCreds),
Config: fixtureDeploymentDefaults(t, "testdata/deployment_snapshot_2.tf"),
// Since the `snapshot_source` block is never persisted, it'll
// always have a non-empty plan after applying.
ExpectNonEmptyPlan: true,
},
},
})
}

func readEsCredentials(t *testing.T, esCreds *creds) resource.TestCheckFunc {
t.Helper()
return func(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "ec_deployment" {
continue
}

esCreds.URL = rs.Primary.Attributes["elasticsearch.0.https_endpoint"]
esCreds.User = rs.Primary.Attributes["elasticsearch_username"]
esCreds.Pass = rs.Primary.Attributes["elasticsearch_password"]
}
return nil
}
}

func triggerSnapshot(t *testing.T, esCreds *creds) func() {
t.Helper()
return func() {
snapshotURL := fmt.Sprintf(
esCreds.URL+"/_snapshot/found-snapshots/snap_%d?wait_for_completion=true",
time.Now().Unix(),
)
req, err := http.NewRequest("PUT", snapshotURL, nil)
if err != nil {
t.Fatal(fmt.Errorf("failed creating snapshot request: %w", err))
return
}
req.SetBasicAuth(esCreds.User, esCreds.Pass)

t.Log("PUT", snapshotURL)

// Create a new client with no timeout, just wait for the call to return.
client := &http.Client{Timeout: 0}
res, err := client.Do(req)
if err != nil {
t.Fatal(fmt.Errorf("failed performing snapshot request: %w", err))
return
}
if res.StatusCode != 200 {
t.Fatal("snapshot create statuscode != 200")
return
}
t.Log("Snapshot created")
}
}
23 changes: 23 additions & 0 deletions ec/acc/testdata/deployment_snapshot_1.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
locals {
region = "%s"
deployment_template = "%s"
}

data "ec_stack" "latest" {
version_regex = "latest"
region = local.region
}

resource "ec_deployment" "snapshot_source" {
name = "terraform_acc_snapshot_source"
region = local.region
version = data.ec_stack.latest.version
deployment_template_id = local.deployment_template

elasticsearch {
topology {
id = "hot_content"
size = "1g"
}
}
}
41 changes: 41 additions & 0 deletions ec/acc/testdata/deployment_snapshot_2.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
locals {
region = "%s"
deployment_template = "%s"
}

data "ec_stack" "latest" {
version_regex = "latest"
region = local.region
}

resource "ec_deployment" "snapshot_source" {
name = "terraform_acc_snapshot_source"
region = local.region
version = data.ec_stack.latest.version
deployment_template_id = local.deployment_template

elasticsearch {
topology {
id = "hot_content"
size = "1g"
}
}
}

resource "ec_deployment" "snapshot_target" {
name = "terraform_acc_snapshot_target"
region = local.region
version = data.ec_stack.latest.version
deployment_template_id = local.deployment_template

elasticsearch {
snapshot_source {
source_elasticsearch_cluster_id = ec_deployment.snapshot_source.elasticsearch.0.resource_id
}

topology {
id = "hot_content"
size = "1g"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,6 @@ func expandSnapshotSource(raw interface{}, restore *models.RestoreSnapshotConfig
if snapshotName, ok := rs["snapshot_name"]; ok {
restore.SnapshotName = ec.String(snapshotName.(string))
}

}
}

Expand Down
15 changes: 15 additions & 0 deletions ec/ecresource/deploymentresource/expanders.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ func updateResourceToModel(d *schema.ResourceData, client *api.API) (*models.Dep
}
result.Resources.Elasticsearch = append(result.Resources.Elasticsearch, esRes...)

// if the restore snapshot operation has been specified, the snapshot restore
// can't be full once the cluster has been created, so the Strategy must be set
// to "partial".
ensurePartialSnapshotStrategy(esRes)

kibanaRes, err := expandKibanaResources(kibana, kibanaResource(template))
if err != nil {
merr = merr.Append(err)
Expand Down Expand Up @@ -264,3 +269,13 @@ func compatibleWithNodeRoles(version string) (bool, error) {
dataTiersVersion := semver.MustParse("7.10.0")
return deploymentVersion.GE(dataTiersVersion), nil
}

func ensurePartialSnapshotStrategy(ess []*models.ElasticsearchPayload) {
for _, es := range ess {
transient := es.Plan.Transient
if transient == nil || transient.RestoreSnapshot == nil {
continue
}
transient.RestoreSnapshot.Strategy = "partial"
}
}
Loading

0 comments on commit 65e381c

Please sign in to comment.