2019-09-18 12:18:19 +00:00
|
|
|
package e2eutil
|
|
|
|
|
|
|
|
import (
|
2021-01-26 14:24:55 +00:00
|
|
|
"fmt"
|
2021-04-14 23:02:42 +00:00
|
|
|
"testing"
|
2019-09-18 12:18:19 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
capi "github.com/hashicorp/consul/api"
|
2021-01-26 14:24:55 +00:00
|
|
|
"github.com/hashicorp/nomad/testutil"
|
|
|
|
"github.com/kr/pretty"
|
2021-04-16 15:33:32 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2019-09-18 12:18:19 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
2021-01-04 22:06:47 +00:00
|
|
|
// RequireConsulStatus asserts the aggregate health of the service converges to the expected status.
|
2021-04-14 23:02:42 +00:00
|
|
|
func RequireConsulStatus(require *require.Assertions, client *capi.Client, namespace, service, expectedStatus string) {
|
2021-01-26 14:24:55 +00:00
|
|
|
testutil.WaitForResultRetries(30, func() (bool, error) {
|
|
|
|
defer time.Sleep(time.Second) // needs a long time for killing tasks/clients
|
|
|
|
|
2021-04-14 23:02:42 +00:00
|
|
|
_, status := serviceStatus(require, client, namespace, service)
|
|
|
|
return status == expectedStatus, fmt.Errorf("service %s/%s: expected %s but found %s", namespace, service, expectedStatus, status)
|
2021-01-26 14:24:55 +00:00
|
|
|
}, func(err error) {
|
|
|
|
require.NoError(err, "timedout waiting for consul status")
|
|
|
|
})
|
2019-09-18 12:18:19 +00:00
|
|
|
}
|
|
|
|
|
2021-01-04 22:06:47 +00:00
|
|
|
// serviceStatus gets the aggregate health of the service and returns the []ServiceEntry for further checking.
|
2021-04-14 23:02:42 +00:00
|
|
|
func serviceStatus(require *require.Assertions, client *capi.Client, namespace, service string) ([]*capi.ServiceEntry, string) {
|
|
|
|
services, _, err := client.Health().Service(service, "", false, &capi.QueryOptions{Namespace: namespace})
|
|
|
|
require.NoError(err, "expected no error for %s/%s, got %s", namespace, service, err)
|
2019-09-18 12:18:19 +00:00
|
|
|
if len(services) > 0 {
|
|
|
|
return services, services[0].Checks.AggregatedStatus()
|
|
|
|
}
|
|
|
|
return nil, "(unknown status)"
|
|
|
|
}
|
|
|
|
|
2021-01-04 22:06:47 +00:00
|
|
|
// RequireConsulDeregistered asserts that the service eventually is de-registered from Consul.
|
2021-04-14 23:02:42 +00:00
|
|
|
func RequireConsulDeregistered(require *require.Assertions, client *capi.Client, namespace, service string) {
|
2021-01-26 14:24:55 +00:00
|
|
|
testutil.WaitForResultRetries(5, func() (bool, error) {
|
|
|
|
defer time.Sleep(time.Second)
|
|
|
|
|
2021-04-14 23:02:42 +00:00
|
|
|
services, _, err := client.Health().Service(service, "", false, &capi.QueryOptions{Namespace: namespace})
|
2021-01-04 22:06:47 +00:00
|
|
|
require.NoError(err)
|
2021-01-26 14:24:55 +00:00
|
|
|
if len(services) != 0 {
|
|
|
|
return false, fmt.Errorf("service %v: expected empty services but found %v %v", service, len(services), pretty.Sprint(services))
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}, func(err error) {
|
|
|
|
require.NoError(err)
|
|
|
|
})
|
2019-09-18 12:18:19 +00:00
|
|
|
}
|
2021-01-04 22:06:47 +00:00
|
|
|
|
|
|
|
// RequireConsulRegistered assert that the service is registered in Consul.
|
2021-04-14 23:02:42 +00:00
|
|
|
func RequireConsulRegistered(require *require.Assertions, client *capi.Client, namespace, service string, count int) {
|
2021-04-20 20:23:30 +00:00
|
|
|
testutil.WaitForResultRetries(10, func() (bool, error) {
|
|
|
|
defer time.Sleep(2 * time.Second)
|
2021-01-26 14:24:55 +00:00
|
|
|
|
2021-04-14 23:02:42 +00:00
|
|
|
services, _, err := client.Catalog().Service(service, "", &capi.QueryOptions{Namespace: namespace})
|
2021-01-04 22:06:47 +00:00
|
|
|
require.NoError(err)
|
2021-01-26 14:24:55 +00:00
|
|
|
if len(services) != count {
|
|
|
|
return false, fmt.Errorf("service %v: expected %v services but found %v %v", service, count, len(services), pretty.Sprint(services))
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}, func(err error) {
|
|
|
|
require.NoError(err)
|
|
|
|
})
|
2021-01-04 22:06:47 +00:00
|
|
|
}
|
2021-04-14 23:02:42 +00:00
|
|
|
|
2021-04-16 15:33:32 +00:00
|
|
|
// CreateConsulNamespaces will create each namespace in Consul, with a description
|
|
|
|
// containing the namespace name.
|
|
|
|
//
|
|
|
|
// Requires Consul Enterprise.
|
2021-04-14 23:02:42 +00:00
|
|
|
func CreateConsulNamespaces(t *testing.T, client *capi.Client, namespaces []string) {
|
|
|
|
nsClient := client.Namespaces()
|
2021-04-20 20:23:30 +00:00
|
|
|
|
2021-04-14 23:02:42 +00:00
|
|
|
for _, namespace := range namespaces {
|
|
|
|
_, _, err := nsClient.Create(&capi.Namespace{
|
|
|
|
Name: namespace,
|
|
|
|
Description: fmt.Sprintf("An e2e namespace called %q", namespace),
|
|
|
|
}, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-16 15:33:32 +00:00
|
|
|
// DeleteConsulNamespaces will delete each namespace from Consul.
|
|
|
|
//
|
|
|
|
// Requires Consul Enterprise.
|
2021-04-14 23:02:42 +00:00
|
|
|
func DeleteConsulNamespaces(t *testing.T, client *capi.Client, namespaces []string) {
|
|
|
|
nsClient := client.Namespaces()
|
2021-04-20 20:23:30 +00:00
|
|
|
|
2021-04-14 23:02:42 +00:00
|
|
|
for _, namespace := range namespaces {
|
|
|
|
_, err := nsClient.Delete(namespace, nil)
|
2021-04-16 15:33:32 +00:00
|
|
|
assert.NoError(t, err) // be lenient; used in cleanup
|
2021-04-14 23:02:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-16 15:33:32 +00:00
|
|
|
// ListConsulNamespaces will list the namespaces in Consul.
|
|
|
|
//
|
|
|
|
// Requires Consul Enterprise.
|
2021-04-14 23:02:42 +00:00
|
|
|
func ListConsulNamespaces(t *testing.T, client *capi.Client) []string {
|
|
|
|
nsClient := client.Namespaces()
|
2021-04-20 20:23:30 +00:00
|
|
|
|
2021-04-14 23:02:42 +00:00
|
|
|
namespaces, _, err := nsClient.List(nil)
|
|
|
|
require.NoError(t, err)
|
2021-04-20 20:23:30 +00:00
|
|
|
|
2021-04-14 23:02:42 +00:00
|
|
|
result := make([]string, 0, len(namespaces))
|
|
|
|
for _, namespace := range namespaces {
|
|
|
|
result = append(result, namespace.Name)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-04-16 15:33:32 +00:00
|
|
|
// PutConsulKey sets key:value in the Consul KV store under given namespace.
|
|
|
|
//
|
|
|
|
// Requires Consul Enterprise.
|
2021-04-14 23:02:42 +00:00
|
|
|
func PutConsulKey(t *testing.T, client *capi.Client, namespace, key, value string) {
|
|
|
|
kvClient := client.KV()
|
2021-04-20 20:23:30 +00:00
|
|
|
opts := &capi.WriteOptions{Namespace: namespace}
|
|
|
|
|
|
|
|
_, err := kvClient.Put(&capi.KVPair{Key: key, Value: []byte(value)}, opts)
|
2021-04-14 23:02:42 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
2021-04-16 15:33:32 +00:00
|
|
|
// DeleteConsulKey deletes the key from the Consul KV store from given namespace.
|
|
|
|
//
|
|
|
|
// Requires Consul Enterprise.
|
2021-04-14 23:02:42 +00:00
|
|
|
func DeleteConsulKey(t *testing.T, client *capi.Client, namespace, key string) {
|
|
|
|
kvClient := client.KV()
|
2021-04-20 20:23:30 +00:00
|
|
|
opts := &capi.WriteOptions{Namespace: namespace}
|
|
|
|
|
|
|
|
_, err := kvClient.Delete(key, opts)
|
2021-04-14 23:02:42 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
2021-04-16 15:33:32 +00:00
|
|
|
// ReadConsulConfigEntry retrieves the ConfigEntry of the given namespace, kind,
|
|
|
|
// and name.
|
|
|
|
//
|
|
|
|
// Requires Consul Enterprise.
|
2021-04-14 23:02:42 +00:00
|
|
|
func ReadConsulConfigEntry(t *testing.T, client *capi.Client, namespace, kind, name string) capi.ConfigEntry {
|
|
|
|
ceClient := client.ConfigEntries()
|
2021-04-20 20:23:30 +00:00
|
|
|
opts := &capi.QueryOptions{Namespace: namespace}
|
|
|
|
|
|
|
|
ce, _, err := ceClient.Get(kind, name, opts)
|
2021-04-14 23:02:42 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
return ce
|
|
|
|
}
|
|
|
|
|
2021-04-16 15:33:32 +00:00
|
|
|
// DeleteConsulConfigEntry deletes the ConfigEntry of the given namespace, kind,
|
|
|
|
// and name.
|
|
|
|
//
|
|
|
|
// Requires Consul Enterprise.
|
2021-04-14 23:02:42 +00:00
|
|
|
func DeleteConsulConfigEntry(t *testing.T, client *capi.Client, namespace, kind, name string) {
|
|
|
|
ceClient := client.ConfigEntries()
|
2021-04-20 20:23:30 +00:00
|
|
|
opts := &capi.WriteOptions{Namespace: namespace}
|
|
|
|
|
|
|
|
_, err := ceClient.Delete(kind, name, opts)
|
2021-04-14 23:02:42 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
2021-04-20 20:23:30 +00:00
|
|
|
|
|
|
|
// ConsulPolicy is used for create Consul ACL policies that Consul ACL tokens
|
|
|
|
// can make use of.
|
|
|
|
type ConsulPolicy struct {
|
|
|
|
Name string // e.g. nomad-operator
|
|
|
|
Rules string // e.g. service "" { policy="write" }
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateConsulPolicy is used to create a Consul ACL policy backed by the given
|
|
|
|
// ConsulPolicy in the specified namespace.
|
|
|
|
//
|
|
|
|
// Requires Consul Enterprise.
|
|
|
|
func CreateConsulPolicy(t *testing.T, client *capi.Client, namespace string, policy ConsulPolicy) string {
|
|
|
|
aclClient := client.ACL()
|
|
|
|
opts := &capi.WriteOptions{Namespace: namespace}
|
|
|
|
|
|
|
|
result, _, err := aclClient.PolicyCreate(&capi.ACLPolicy{
|
|
|
|
Name: policy.Name,
|
|
|
|
Rules: policy.Rules,
|
|
|
|
Description: fmt.Sprintf("An e2e test policy %q", policy.Name),
|
|
|
|
}, opts)
|
|
|
|
require.NoError(t, err, "failed to create consul acl policy")
|
|
|
|
return result.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteConsulPolicies is used to delete a set Consul ACL policies from Consul.
|
|
|
|
//
|
|
|
|
// Requires Consul Enterprise.
|
|
|
|
func DeleteConsulPolicies(t *testing.T, client *capi.Client, policies map[string][]string) {
|
|
|
|
aclClient := client.ACL()
|
|
|
|
|
|
|
|
for namespace, policyIDs := range policies {
|
|
|
|
opts := &capi.WriteOptions{Namespace: namespace}
|
|
|
|
for _, policyID := range policyIDs {
|
|
|
|
_, err := aclClient.PolicyDelete(policyID, opts)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateConsulToken is used to create a Consul ACL token backed by the policy of
|
|
|
|
// the given policyID in the specified namespace.
|
|
|
|
//
|
|
|
|
// Requires Consul Enterprise.
|
|
|
|
func CreateConsulToken(t *testing.T, client *capi.Client, namespace, policyID string) string {
|
|
|
|
aclClient := client.ACL()
|
|
|
|
opts := &capi.WriteOptions{Namespace: namespace}
|
|
|
|
|
|
|
|
token, _, err := aclClient.TokenCreate(&capi.ACLToken{
|
|
|
|
Policies: []*capi.ACLTokenPolicyLink{{ID: policyID}},
|
|
|
|
Description: "An e2e test token",
|
|
|
|
}, opts)
|
|
|
|
require.NoError(t, err, "failed to create consul acl token")
|
|
|
|
return token.SecretID
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteConsulTokens is used to delete a set of tokens from Consul.
|
|
|
|
//
|
|
|
|
// Requires Consul Enterprise.
|
|
|
|
func DeleteConsulTokens(t *testing.T, client *capi.Client, tokens map[string][]string) {
|
|
|
|
aclClient := client.ACL()
|
|
|
|
|
|
|
|
for namespace, tokenIDs := range tokens {
|
|
|
|
opts := &capi.WriteOptions{Namespace: namespace}
|
|
|
|
for _, tokenID := range tokenIDs {
|
|
|
|
_, err := aclClient.TokenDelete(tokenID, opts)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|