Skip to content

Commit

Permalink
raft autopilot updates (#1705)
Browse files Browse the repository at this point in the history
* Raft autopilot updates

* add autopilot state to provider DataSourceRegistry

* add autopilot state data source file

* add consts and sort since we are reformatting anyway

* use new consts in data source file

* update fields and schema

* add test scaffolding

* update data src fields and uts

* serialize complex data types and update tests

* remove unnecessary sprintf

* refactor hcl block

* add docs for raft autopilot state data source

* add skip raft tests env

* add json fields to docs and add link

* revert consts sorting
  • Loading branch information
fairclothjm authored Jan 3, 2023
1 parent 10107c4 commit d0d7987
Show file tree
Hide file tree
Showing 8 changed files with 475 additions and 182 deletions.
365 changes: 188 additions & 177 deletions internal/consts/consts.go

Large diffs are not rendered by default.

152 changes: 152 additions & 0 deletions vault/data_source_raft_autopilot_state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package vault

import (
"encoding/json"
"fmt"
"log"

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

"github.com/hashicorp/terraform-provider-vault/internal/consts"
"github.com/hashicorp/terraform-provider-vault/internal/provider"
)

var autopilotStatePath = "sys/storage/raft/autopilot/state"

var raftAutopilotStateFields = []string{
consts.FieldFailureTolerance,
consts.FieldHealthy,
consts.FieldLeader,
consts.FieldOptimisticFailureTolerance,
consts.FieldVoters,
}

// serializeFields is a map of fields that have complex structures that we will
// serialize for convenience instead of defining the schema explicitly
var serializeFields = map[string]string{
consts.FieldRedundancyZones: consts.FieldRedundancyZonesJSON,
consts.FieldServers: consts.FieldServersJSON,
consts.FieldUpgradeInfo: consts.FieldUpgradeInfoJSON,
}

func raftAutopilotStateDataSource() *schema.Resource {
fields := map[string]*schema.Schema{
consts.FieldFailureTolerance: {
Type: schema.TypeInt,
Computed: true,
Description: "How many nodes could fail before the cluster becomes unhealthy",
},
consts.FieldHealthy: {
Type: schema.TypeBool,
Computed: true,
Description: "Health status",
},
consts.FieldLeader: {
Type: schema.TypeString,
Computed: true,
Description: "Current leader of Vault",
},
consts.FieldOptimisticFailureTolerance: {
Type: schema.TypeInt,
Computed: true,
Description: "The cluster-level optimistic failure tolerance.",
},
consts.FieldRedundancyZonesJSON: {
Type: schema.TypeString,
Computed: true,
// we save the subkeys as a JSON string in order to
// cleanly support nested values
Description: "Subkeys for the redundancy zones read from Vault.",
},
consts.FieldRedundancyZones: {
Type: schema.TypeMap,
Computed: true,
Description: "Additional output related to redundancy zones stored as a map of strings.",
},
consts.FieldServersJSON: {
Type: schema.TypeString,
Computed: true,
// we save the subkeys as a JSON string in order to
// cleanly support nested values
Description: "Subkeys for the servers read from Vault.",
},
consts.FieldServers: {
Type: schema.TypeMap,
Computed: true,
Description: "Additional output related to servers stored as a map of strings.",
},
consts.FieldUpgradeInfoJSON: {
Type: schema.TypeString,
Computed: true,
// we save the subkeys as a JSON string in order to
// cleanly support nested values
Description: "Subkeys for the servers read from Vault.",
},
consts.FieldUpgradeInfo: {
Type: schema.TypeMap,
Computed: true,
Description: "Additional output related to upgrade info stored as a map of strings.",
},
consts.FieldVoters: {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Description: "The voters in the Vault cluster.",
},
}
return &schema.Resource{
Read: ReadWrapper(raftAutopilotStateDataSourceRead),
Schema: fields,
}
}

func raftAutopilotStateDataSourceRead(d *schema.ResourceData, meta interface{}) error {
client, e := provider.GetClient(d, meta)
if e != nil {
return e
}

path := autopilotStatePath

log.Printf("[DEBUG] Reading raft autopilot state %q", path)
resp, err := client.Logical().Read(path)
if err != nil {
return fmt.Errorf("error reading raft autopilot state %q: %s", path, err)
}
log.Printf("[DEBUG] Read raft autopilot state %q", path)

if resp == nil {
d.SetId("")
log.Printf("[WARN] unable to read raft autopilot state at %q", path)
return nil
}

d.SetId(path)
for _, k := range raftAutopilotStateFields {
if v, ok := resp.Data[k]; ok {
if err := d.Set(k, v); err != nil {
return fmt.Errorf("error reading %s for raft autopilot state %q: %q", k, path, err)
}
}
}

for k, v := range serializeFields {
if data, ok := resp.Data[k]; ok {
jsonData, err := json.Marshal(data)
if err != nil {
return fmt.Errorf("error marshaling JSON for %q at %q: %s", v, path, err)
}
if err := d.Set(v, string(jsonData)); err != nil {
return err
}

if err := d.Set(k, serializeDataMapToString(data.(map[string]interface{}))); err != nil {
return err
}
}
}

return nil
}
47 changes: 47 additions & 0 deletions vault/data_source_raft_autopilot_state_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package vault

import (
"testing"

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

"github.com/hashicorp/terraform-provider-vault/internal/consts"
"github.com/hashicorp/terraform-provider-vault/testutil"
)

// TestAccDataSourceRaftAutoPilotState assumes that a raft cluster exists with
// autopilot_redundancy_zone configured for each node
// see: https://developer.hashicorp.com/vault/docs/enterprise/redundancy-zones
func TestAccDataSourceRaftAutoPilotState(t *testing.T) {
ds := "data.vault_raft_autopilot_state.test"
resource.Test(t, resource.TestCase{
Providers: testProviders,
PreCheck: func() {
testutil.SkipTestEnvSet(t, "SKIP_RAFT_TESTS")
testutil.TestEntPreCheck(t)
},
Steps: []resource.TestStep{
{
Config: testDataSourceRaftAutoPilotStateConfig(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(ds, consts.FieldFailureTolerance),
resource.TestCheckResourceAttr(ds, consts.FieldHealthy, "true"),
resource.TestCheckResourceAttrSet(ds, consts.FieldLeader),
resource.TestCheckResourceAttrSet(ds, consts.FieldOptimisticFailureTolerance),
resource.TestCheckResourceAttrSet(ds, consts.FieldRedundancyZonesJSON),
resource.TestCheckResourceAttrSet(ds, consts.FieldServersJSON),
resource.TestCheckResourceAttrSet(ds, consts.FieldUpgradeInfoJSON),
resource.TestCheckResourceAttrSet(ds, consts.FieldVoters+".#"),
),
},
},
})
}

func testDataSourceRaftAutoPilotStateConfig() string {
return `
resource "vault_raft_autopilot" "test" {}
data "vault_raft_autopilot_state" "test" {}
`
}
4 changes: 4 additions & 0 deletions vault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,10 @@ var (
Resource: UpdateSchemaResource(kvSecretSubkeysV2DataSource()),
PathInventory: []string{"/secret/subkeys/{path}"},
},
"vault_raft_autopilot_state": {
Resource: UpdateSchemaResource(raftAutopilotStateDataSource()),
PathInventory: []string{"/sys/storage/raft/autopilot/state"},
},
}

ResourceRegistry = map[string]*Description{
Expand Down
14 changes: 14 additions & 0 deletions vault/resource_raft_autopilot.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var (
"max_trailing_logs": 1000,
"min_quorum": 3,
"server_stabilization_time": "10s",
"disable_upgrade_migration": false,
}
)

Expand Down Expand Up @@ -59,6 +60,12 @@ func raftAutopilotConfigResource() *schema.Resource {
Default: autopilotDefaults["server_stabilization_time"],
Optional: true,
},
"disable_upgrade_migration": {
Type: schema.TypeBool,
Description: "Disables automatically upgrading Vault using autopilot. (Enterprise-only)",
Default: autopilotDefaults["disable_upgrade_migration"],
Optional: true,
},
}
return &schema.Resource{
Create: createOrUpdateAutopilotConfigResource,
Expand All @@ -82,6 +89,7 @@ func createOrUpdateAutopilotConfigResource(d *schema.ResourceData, meta interfac
"max_trailing_logs": d.Get("max_trailing_logs").(int),
"min_quorum": d.Get("min_quorum").(int),
"server_stabilization_time": d.Get("server_stabilization_time").(string),
"disable_upgrade_migration": d.Get("disable_upgrade_migration").(bool),
}

log.Print("[DEBUG] Configuring autopilot")
Expand Down Expand Up @@ -143,6 +151,12 @@ func readAutopilotConfigResource(d *schema.ResourceData, meta interface{}) error
}
}

if val, ok := resp.Data["disable_upgrade_migration"]; ok {
if err := d.Set("disable_upgrade_migration", val); err != nil {
return fmt.Errorf("error setting state key 'disable_upgrade_migration': %s", err)
}
}

return nil
}

Expand Down
13 changes: 8 additions & 5 deletions vault/resource_raft_autopilot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,21 @@ func TestAccRaftAutopilotConfig_basic(t *testing.T) {
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "dead_server_last_contact_threshold", "12h0m0s"),
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "last_contact_threshold", autopilotDefaults["last_contact_threshold"].(string)),
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "max_trailing_logs", strconv.Itoa(autopilotDefaults["max_trailing_logs"].(int))),
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "min_quorum", "3"),
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "min_quorum", strconv.Itoa(autopilotDefaults["min_quorum"].(int))),
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "server_stabilization_time", autopilotDefaults["server_stabilization_time"].(string)),
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "disable_upgrade_migration", "false"),
),
},
{
Config: testAccRaftAutopilotConfig_updated(true, "30s", "20s", 100, 5, "50s"),
Config: testAccRaftAutopilotConfig_updated(true, true, "30s", "20s", "50s", 100, 5),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "cleanup_dead_servers", "true"),
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "dead_server_last_contact_threshold", "30s"),
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "last_contact_threshold", "20s"),
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "max_trailing_logs", "100"),
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "min_quorum", "5"),
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "server_stabilization_time", "50s"),
resource.TestCheckResourceAttr("vault_raft_autopilot.test", "disable_upgrade_migration", "true"),
),
},
},
Expand Down Expand Up @@ -80,14 +82,15 @@ resource "vault_raft_autopilot" "test" {
}`, cleanup, dsThresh, quorum)
}

func testAccRaftAutopilotConfig_updated(cleanup bool, dsThresh string, lcThresh string, logs int, quorum int, stabTime string) string {
func testAccRaftAutopilotConfig_updated(cleanup, disableUpgrade bool, dsThresh, lcThresh, stabTime string, logs, quorum int) string {
return fmt.Sprintf(`
resource "vault_raft_autopilot" "test" {
cleanup_dead_servers = %t
disable_upgrade_migration = %t
dead_server_last_contact_threshold = "%s"
last_contact_threshold = "%s"
server_stabilization_time = "%s"
max_trailing_logs = %d
min_quorum = %d
server_stabilization_time = "%s"
}`, cleanup, dsThresh, lcThresh, logs, quorum, stabTime)
}`, cleanup, disableUpgrade, dsThresh, lcThresh, stabTime, logs, quorum)
}
60 changes: 60 additions & 0 deletions website/docs/d/raft_autopilot_state.html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
layout: "vault"
page_title: "Vault: vault_raft_autopilot_state data"
sidebar_current: "docs-vault-raft-autopilot-state"
description: |-
Retrieve the Raft cluster state.
---

# vault\_raft\_autopilot\_state

Displays the state of the raft cluster under integrated storage as seen by
autopilot. It shows whether autopilot thinks the cluster is healthy or not, and
how many nodes could fail before the cluster becomes unhealthy ("Failure
Tolerance"). For more information, please refer to the
[Vault documentation](https://developer.hashicorp.com/vault/api-docs/system/storage/raftautopilot#get-cluster-state).

## Example Usage

```hcl
data "vault_raft_autopilot_state" "main" {}
output "failure-tolerance" {
value = data.vault_raft_autopilot_state.main.failure_tolerance
}
```

## Argument Reference

The following arguments are supported:

* `namespace` - (Optional) The namespace of the target resource.
The value should not contain leading or trailing forward slashes.
The `namespace` is always relative to the provider's configured [namespace](/docs/providers/vault#namespace).
*Available only for Vault Enterprise*.

## Attributes Reference

In addition to the arguments above, the following attributes are exported:

* `failure_tolerance` - How many nodes could fail before the cluster becomes unhealthy.

* `healthy` - Cluster health status.

* `leader` - The current leader of Vault.

* `optimistic_failure_tolerance` - The cluster-level optimistic failure tolerance.

* `redundancy_zones_json` - Additional output related to redundancy zones.

* `redundancy_zones` - Additional output related to redundancy zones stored as a serialized map of strings.

* `servers_json` - Additionaly output related to servers in the cluster.

* `servers` - Additionaly output related to servers in the cluster stored as a serialized map of strings.

* `upgrade_info_json` - Additional output related to upgrade information.

* `upgrade_info` - Additional output related to upgrade information stored as a serialized map of strings.

* `voters` - The voters in the Vault cluster.
2 changes: 2 additions & 0 deletions website/docs/r/raft_autopilot.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ voting nodes.
- `server_stabilization_time` - (Optional) Minimum amount of time a server must be
stable in the 'healthy' state before being added to the cluster.

- `disable_upgrade_migration` – (Optional) Disables automatically upgrading Vault using autopilot. (Enterprise-only)

## Attributes Reference

No additional attributes are exported by this resource.

0 comments on commit d0d7987

Please sign in to comment.