mirror of https://github.com/hashicorp/terraform
parent
acb79a7545
commit
d61d3e9fd8
@ -1,168 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
|
||||
"go.etcd.io/etcd/client/pkg/v3/transport"
|
||||
etcdv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
endpointsKey = "endpoints"
|
||||
usernameKey = "username"
|
||||
usernameEnvVarName = "ETCDV3_USERNAME"
|
||||
passwordKey = "password"
|
||||
passwordEnvVarName = "ETCDV3_PASSWORD"
|
||||
maxRequestBytesKey = "max_request_bytes"
|
||||
prefixKey = "prefix"
|
||||
lockKey = "lock"
|
||||
cacertPathKey = "cacert_path"
|
||||
certPathKey = "cert_path"
|
||||
keyPathKey = "key_path"
|
||||
)
|
||||
|
||||
func New() backend.Backend {
|
||||
s := &schema.Backend{
|
||||
Schema: map[string]*schema.Schema{
|
||||
endpointsKey: &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
MinItems: 1,
|
||||
Required: true,
|
||||
Description: "Endpoints for the etcd cluster.",
|
||||
},
|
||||
|
||||
usernameKey: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "Username used to connect to the etcd cluster.",
|
||||
DefaultFunc: schema.EnvDefaultFunc(usernameEnvVarName, ""),
|
||||
},
|
||||
|
||||
passwordKey: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "Password used to connect to the etcd cluster.",
|
||||
DefaultFunc: schema.EnvDefaultFunc(passwordEnvVarName, ""),
|
||||
},
|
||||
|
||||
maxRequestBytesKey: &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Description: "The max request size to send to etcd.",
|
||||
Default: 0,
|
||||
},
|
||||
|
||||
prefixKey: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "An optional prefix to be added to keys when to storing state in etcd.",
|
||||
Default: "",
|
||||
},
|
||||
|
||||
lockKey: &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Description: "Whether to lock state access.",
|
||||
Default: true,
|
||||
},
|
||||
|
||||
cacertPathKey: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "The path to a PEM-encoded CA bundle with which to verify certificates of TLS-enabled etcd servers.",
|
||||
Default: "",
|
||||
},
|
||||
|
||||
certPathKey: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "The path to a PEM-encoded certificate to provide to etcd for secure client identification.",
|
||||
Default: "",
|
||||
},
|
||||
|
||||
keyPathKey: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "The path to a PEM-encoded key to provide to etcd for secure client identification.",
|
||||
Default: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := &Backend{Backend: s}
|
||||
result.Backend.ConfigureFunc = result.configure
|
||||
return result
|
||||
}
|
||||
|
||||
type Backend struct {
|
||||
*schema.Backend
|
||||
|
||||
// The fields below are set from configure.
|
||||
client *etcdv3.Client
|
||||
data *schema.ResourceData
|
||||
lock bool
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (b *Backend) configure(ctx context.Context) error {
|
||||
var err error
|
||||
// Grab the resource data.
|
||||
b.data = schema.FromContextBackendConfig(ctx)
|
||||
// Store the lock information.
|
||||
b.lock = b.data.Get(lockKey).(bool)
|
||||
// Store the prefix information.
|
||||
b.prefix = b.data.Get(prefixKey).(string)
|
||||
// Initialize a client to test config.
|
||||
b.client, err = b.rawClient()
|
||||
// Return err, if any.
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Backend) rawClient() (*etcdv3.Client, error) {
|
||||
config := etcdv3.Config{}
|
||||
tlsInfo := transport.TLSInfo{}
|
||||
|
||||
if v, ok := b.data.GetOk(endpointsKey); ok {
|
||||
config.Endpoints = retrieveEndpoints(v)
|
||||
}
|
||||
if v, ok := b.data.GetOk(usernameKey); ok && v.(string) != "" {
|
||||
config.Username = v.(string)
|
||||
}
|
||||
if v, ok := b.data.GetOk(passwordKey); ok && v.(string) != "" {
|
||||
config.Password = v.(string)
|
||||
}
|
||||
if v, ok := b.data.GetOk(maxRequestBytesKey); ok && v.(int) != 0 {
|
||||
config.MaxCallSendMsgSize = v.(int)
|
||||
}
|
||||
if v, ok := b.data.GetOk(cacertPathKey); ok && v.(string) != "" {
|
||||
tlsInfo.TrustedCAFile = v.(string)
|
||||
}
|
||||
if v, ok := b.data.GetOk(certPathKey); ok && v.(string) != "" {
|
||||
tlsInfo.CertFile = v.(string)
|
||||
}
|
||||
if v, ok := b.data.GetOk(keyPathKey); ok && v.(string) != "" {
|
||||
tlsInfo.KeyFile = v.(string)
|
||||
}
|
||||
|
||||
if tlsCfg, err := tlsInfo.ClientConfig(); err != nil {
|
||||
return nil, err
|
||||
} else if !tlsInfo.Empty() {
|
||||
config.TLS = tlsCfg // Assign TLS configuration only if it valid and non-empty.
|
||||
}
|
||||
|
||||
return etcdv3.New(config)
|
||||
}
|
||||
|
||||
func retrieveEndpoints(v interface{}) []string {
|
||||
var endpoints []string
|
||||
list := v.([]interface{})
|
||||
for _, ep := range list {
|
||||
endpoints = append(endpoints, ep.(string))
|
||||
}
|
||||
return endpoints
|
||||
}
|
||||
@ -1,109 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
etcdv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
func (b *Backend) Workspaces() ([]string, error) {
|
||||
res, err := b.client.Get(context.TODO(), b.prefix, etcdv3.WithPrefix(), etcdv3.WithKeysOnly())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]string, 1, len(res.Kvs)+1)
|
||||
result[0] = backend.DefaultStateName
|
||||
for _, kv := range res.Kvs {
|
||||
if strings.TrimPrefix(string(kv.Key), b.prefix) != backend.DefaultStateName {
|
||||
result = append(result, strings.TrimPrefix(string(kv.Key), b.prefix))
|
||||
}
|
||||
}
|
||||
sort.Strings(result[1:])
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (b *Backend) DeleteWorkspace(name string) error {
|
||||
if name == backend.DefaultStateName || name == "" {
|
||||
return fmt.Errorf("Can't delete default state.")
|
||||
}
|
||||
|
||||
key := b.determineKey(name)
|
||||
|
||||
_, err := b.client.Delete(context.TODO(), key)
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
|
||||
var stateMgr statemgr.Full = &remote.State{
|
||||
Client: &RemoteClient{
|
||||
Client: b.client,
|
||||
DoLock: b.lock,
|
||||
Key: b.determineKey(name),
|
||||
},
|
||||
}
|
||||
|
||||
if !b.lock {
|
||||
stateMgr = &statemgr.LockDisabled{Inner: stateMgr}
|
||||
}
|
||||
|
||||
lockInfo := statemgr.NewLockInfo()
|
||||
lockInfo.Operation = "init"
|
||||
lockUnlock := func(parent error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := stateMgr.RefreshState(); err != nil {
|
||||
err = lockUnlock(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v := stateMgr.State(); v == nil {
|
||||
lockId, err := stateMgr.Lock(lockInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to lock state in etcd: %s.", err)
|
||||
}
|
||||
|
||||
lockUnlock = func(parent error) error {
|
||||
if err := stateMgr.Unlock(lockId); err != nil {
|
||||
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
if err := stateMgr.WriteState(states.NewState()); err != nil {
|
||||
err = lockUnlock(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := stateMgr.PersistState(); err != nil {
|
||||
err = lockUnlock(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := lockUnlock(nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stateMgr, nil
|
||||
}
|
||||
|
||||
func (b *Backend) determineKey(name string) string {
|
||||
return b.prefix + name
|
||||
}
|
||||
|
||||
const errStateUnlock = `
|
||||
Error unlocking etcd state. Lock ID: %s
|
||||
|
||||
Error: %s
|
||||
|
||||
You may have to force-unlock this state in order to use it again.
|
||||
`
|
||||
@ -1,107 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
etcdv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
etcdv3Endpoints = strings.Split(os.Getenv("TF_ETCDV3_ENDPOINTS"), ",")
|
||||
)
|
||||
|
||||
const (
|
||||
keyPrefix = "tf-unit"
|
||||
)
|
||||
|
||||
func TestBackend_impl(t *testing.T) {
|
||||
var _ backend.Backend = new(Backend)
|
||||
}
|
||||
|
||||
func cleanupEtcdv3(t *testing.T) {
|
||||
client, err := etcdv3.New(etcdv3.Config{
|
||||
Endpoints: etcdv3Endpoints,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := client.KV.Delete(context.TODO(), keyPrefix, etcdv3.WithPrefix())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("Cleaned up %d keys.", res.Deleted)
|
||||
}
|
||||
|
||||
func prepareEtcdv3(t *testing.T) {
|
||||
skip := os.Getenv("TF_ACC") == "" && os.Getenv("TF_ETCDV3_TEST") == ""
|
||||
if skip {
|
||||
t.Log("etcd server tests require setting TF_ACC or TF_ETCDV3_TEST")
|
||||
t.Skip()
|
||||
}
|
||||
if reflect.DeepEqual(etcdv3Endpoints, []string{""}) {
|
||||
t.Fatal("etcd server tests require setting TF_ETCDV3_ENDPOINTS")
|
||||
}
|
||||
cleanupEtcdv3(t)
|
||||
}
|
||||
|
||||
func TestBackend(t *testing.T) {
|
||||
prepareEtcdv3(t)
|
||||
defer cleanupEtcdv3(t)
|
||||
|
||||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend. We need two to test locking.
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
}))
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
}))
|
||||
|
||||
// Test
|
||||
backend.TestBackendStates(t, b1)
|
||||
backend.TestBackendStateLocks(t, b1, b2)
|
||||
backend.TestBackendStateForceUnlock(t, b1, b2)
|
||||
}
|
||||
|
||||
func TestBackend_lockDisabled(t *testing.T) {
|
||||
prepareEtcdv3(t)
|
||||
defer cleanupEtcdv3(t)
|
||||
|
||||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend. We need two to test locking.
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
"lock": false,
|
||||
}))
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix + "/" + "different", // Diff so locking test would fail if it was locking
|
||||
"lock": false,
|
||||
}))
|
||||
|
||||
// Test
|
||||
backend.TestBackendStateLocks(t, b1, b2)
|
||||
}
|
||||
|
||||
func stringsToInterfaces(strSlice []string) []interface{} {
|
||||
var interfaceSlice []interface{}
|
||||
for _, v := range strSlice {
|
||||
interfaceSlice = append(interfaceSlice, v)
|
||||
}
|
||||
return interfaceSlice
|
||||
}
|
||||
@ -1,211 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
etcdv3 "go.etcd.io/etcd/client/v3"
|
||||
etcdv3sync "go.etcd.io/etcd/client/v3/concurrency"
|
||||
)
|
||||
|
||||
const (
|
||||
lockAcquireTimeout = 2 * time.Second
|
||||
lockInfoSuffix = ".lockinfo"
|
||||
)
|
||||
|
||||
// RemoteClient is a remote client that will store data in etcd.
|
||||
type RemoteClient struct {
|
||||
Client *etcdv3.Client
|
||||
DoLock bool
|
||||
Key string
|
||||
|
||||
etcdMutex *etcdv3sync.Mutex
|
||||
etcdSession *etcdv3sync.Session
|
||||
info *statemgr.LockInfo
|
||||
mu sync.Mutex
|
||||
modRevision int64
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Get() (*remote.Payload, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
res, err := c.Client.KV.Get(context.TODO(), c.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Count == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if res.Count >= 2 {
|
||||
return nil, fmt.Errorf("Expected a single result but got %d.", res.Count)
|
||||
}
|
||||
|
||||
c.modRevision = res.Kvs[0].ModRevision
|
||||
|
||||
payload := res.Kvs[0].Value
|
||||
md5 := md5.Sum(payload)
|
||||
|
||||
return &remote.Payload{
|
||||
Data: payload,
|
||||
MD5: md5[:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Put(data []byte) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
res, err := etcdv3.NewKV(c.Client).Txn(context.TODO()).If(
|
||||
etcdv3.Compare(etcdv3.ModRevision(c.Key), "=", c.modRevision),
|
||||
).Then(
|
||||
etcdv3.OpPut(c.Key, string(data)),
|
||||
etcdv3.OpGet(c.Key),
|
||||
).Commit()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !res.Succeeded {
|
||||
return fmt.Errorf("The transaction did not succeed.")
|
||||
}
|
||||
if len(res.Responses) != 2 {
|
||||
return fmt.Errorf("Expected two responses but got %d.", len(res.Responses))
|
||||
}
|
||||
|
||||
c.modRevision = res.Responses[1].GetResponseRange().Kvs[0].ModRevision
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Delete() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
_, err := c.Client.KV.Delete(context.TODO(), c.Key)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if !c.DoLock {
|
||||
return "", nil
|
||||
}
|
||||
if c.etcdSession != nil {
|
||||
return "", fmt.Errorf("state %q already locked", c.Key)
|
||||
}
|
||||
|
||||
c.info = info
|
||||
return c.lock()
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Unlock(id string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if !c.DoLock {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.unlock(id)
|
||||
}
|
||||
|
||||
func (c *RemoteClient) deleteLockInfo(info *statemgr.LockInfo) error {
|
||||
res, err := c.Client.KV.Delete(context.TODO(), c.Key+lockInfoSuffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Deleted == 0 {
|
||||
return fmt.Errorf("No keys deleted for %s when deleting lock info.", c.Key+lockInfoSuffix)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
|
||||
res, err := c.Client.KV.Get(context.TODO(), c.Key+lockInfoSuffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Count == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
li := &statemgr.LockInfo{}
|
||||
err = json.Unmarshal(res.Kvs[0].Value, li)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error unmarshaling lock info: %s.", err)
|
||||
}
|
||||
|
||||
return li, nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) putLockInfo(info *statemgr.LockInfo) error {
|
||||
c.info.Path = c.etcdMutex.Key()
|
||||
c.info.Created = time.Now().UTC()
|
||||
|
||||
_, err := c.Client.KV.Put(context.TODO(), c.Key+lockInfoSuffix, string(c.info.Marshal()))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *RemoteClient) lock() (string, error) {
|
||||
session, err := etcdv3sync.NewSession(c.Client)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), lockAcquireTimeout)
|
||||
defer cancel()
|
||||
|
||||
mutex := etcdv3sync.NewMutex(session, c.Key)
|
||||
if err1 := mutex.Lock(ctx); err1 != nil {
|
||||
lockInfo, err2 := c.getLockInfo()
|
||||
if err2 != nil {
|
||||
return "", &statemgr.LockError{Err: err2}
|
||||
}
|
||||
return "", &statemgr.LockError{Info: lockInfo, Err: err1}
|
||||
}
|
||||
|
||||
c.etcdMutex = mutex
|
||||
c.etcdSession = session
|
||||
|
||||
err = c.putLockInfo(c.info)
|
||||
if err != nil {
|
||||
if unlockErr := c.unlock(c.info.ID); unlockErr != nil {
|
||||
err = multierror.Append(err, unlockErr)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
return c.info.ID, nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) unlock(id string) error {
|
||||
if c.etcdMutex == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs error
|
||||
|
||||
if err := c.deleteLockInfo(c.info); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
if err := c.etcdMutex.Unlock(context.TODO()); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
if err := c.etcdSession.Close(); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
|
||||
c.etcdMutex = nil
|
||||
c.etcdSession = nil
|
||||
|
||||
return errs
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
)
|
||||
|
||||
func TestRemoteClient_impl(t *testing.T) {
|
||||
var _ remote.Client = new(RemoteClient)
|
||||
}
|
||||
|
||||
func TestRemoteClient(t *testing.T) {
|
||||
prepareEtcdv3(t)
|
||||
defer cleanupEtcdv3(t)
|
||||
|
||||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
}))
|
||||
|
||||
// Grab the client
|
||||
state, err := b.StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %s.", err)
|
||||
}
|
||||
|
||||
// Test
|
||||
remote.TestClient(t, state.(*remote.State).Client)
|
||||
}
|
||||
|
||||
func TestEtcdv3_stateLock(t *testing.T) {
|
||||
prepareEtcdv3(t)
|
||||
defer cleanupEtcdv3(t)
|
||||
|
||||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend
|
||||
s1, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
})).StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s2, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
})).StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
remote.TestRemoteLocks(t, s1.(*remote.State).Client, s2.(*remote.State).Client)
|
||||
}
|
||||
|
||||
func TestEtcdv3_destroyLock(t *testing.T) {
|
||||
prepareEtcdv3(t)
|
||||
defer cleanupEtcdv3(t)
|
||||
|
||||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
}))
|
||||
|
||||
// Grab the client
|
||||
s, err := b.StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c := s.(*remote.State).Client.(*RemoteClient)
|
||||
|
||||
info := statemgr.NewLockInfo()
|
||||
id, err := c.Lock(info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := c.Unlock(id); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := c.Client.KV.Get(context.TODO(), c.info.Path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.Count != 0 {
|
||||
t.Fatalf("lock key not cleaned up at: %s", string(res.Kvs[0].Key))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue