mirror of https://github.com/status-im/consul.git
167 lines
4.2 KiB
Go
167 lines
4.2 KiB
Go
|
// Copyright (c) HashiCorp, Inc.
|
||
|
// SPDX-License-Identifier: BUSL-1.1
|
||
|
|
||
|
package tenancy
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"time"
|
||
|
|
||
|
"github.com/stretchr/testify/require"
|
||
|
"google.golang.org/grpc/codes"
|
||
|
"google.golang.org/grpc/metadata"
|
||
|
"google.golang.org/grpc/status"
|
||
|
|
||
|
"github.com/hashicorp/consul/proto-public/pbresource"
|
||
|
"github.com/hashicorp/consul/sdk/testutil"
|
||
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||
|
)
|
||
|
|
||
|
// This duplicates a subset of internal/resource/resourcetest/client.go so
|
||
|
// we're not importing consul internals integration tests.
|
||
|
//
|
||
|
// TODO: Move to a general package if used more widely.
|
||
|
|
||
|
// T represents the subset of testing.T methods that will be used
|
||
|
// by the various functionality in this package
|
||
|
type T interface {
|
||
|
Helper()
|
||
|
Log(args ...interface{})
|
||
|
Logf(format string, args ...interface{})
|
||
|
Errorf(format string, args ...interface{})
|
||
|
Fatalf(format string, args ...interface{})
|
||
|
FailNow()
|
||
|
Cleanup(func())
|
||
|
}
|
||
|
|
||
|
type ClientOption func(*Client)
|
||
|
|
||
|
func WithACLToken(token string) ClientOption {
|
||
|
return func(c *Client) {
|
||
|
c.token = token
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Client decorates a resource service client with helper functions to assist
|
||
|
// with integration testing.
|
||
|
type Client struct {
|
||
|
pbresource.ResourceServiceClient
|
||
|
|
||
|
timeout time.Duration
|
||
|
wait time.Duration
|
||
|
token string
|
||
|
}
|
||
|
|
||
|
func NewClient(client pbresource.ResourceServiceClient, opts ...ClientOption) *Client {
|
||
|
c := &Client{
|
||
|
ResourceServiceClient: client,
|
||
|
timeout: 7 * time.Second,
|
||
|
wait: 50 * time.Millisecond,
|
||
|
}
|
||
|
|
||
|
for _, opt := range opts {
|
||
|
opt(c)
|
||
|
}
|
||
|
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func NewClientWithACLToken(client pbresource.ResourceServiceClient, token string) *Client {
|
||
|
return NewClient(client, WithACLToken(token))
|
||
|
}
|
||
|
|
||
|
func (client *Client) SetRetryerConfig(timeout time.Duration, wait time.Duration) {
|
||
|
client.timeout = timeout
|
||
|
client.wait = wait
|
||
|
}
|
||
|
|
||
|
func (client *Client) retry(t T, fn func(r *retry.R)) {
|
||
|
t.Helper()
|
||
|
retryer := &retry.Timer{Timeout: client.timeout, Wait: client.wait}
|
||
|
retry.RunWith(retryer, t, fn)
|
||
|
}
|
||
|
|
||
|
func (client *Client) Context(t T) context.Context {
|
||
|
ctx := testutil.TestContext(t)
|
||
|
|
||
|
if client.token != "" {
|
||
|
md := metadata.New(map[string]string{
|
||
|
"x-consul-token": client.token,
|
||
|
})
|
||
|
ctx = metadata.NewOutgoingContext(ctx, md)
|
||
|
}
|
||
|
|
||
|
return ctx
|
||
|
}
|
||
|
|
||
|
func (client *Client) RequireResourceNotFound(t T, id *pbresource.ID) {
|
||
|
t.Helper()
|
||
|
|
||
|
rsp, err := client.Read(client.Context(t), &pbresource.ReadRequest{Id: id})
|
||
|
require.Error(t, err)
|
||
|
require.Equal(t, codes.NotFound, status.Code(err))
|
||
|
require.Nil(t, rsp)
|
||
|
}
|
||
|
|
||
|
func (client *Client) RequireResourceExists(t T, id *pbresource.ID) *pbresource.Resource {
|
||
|
t.Helper()
|
||
|
|
||
|
rsp, err := client.Read(client.Context(t), &pbresource.ReadRequest{Id: id})
|
||
|
require.NoError(t, err, "error reading %s with type %s", id.Name, ToGVK(id.Type))
|
||
|
require.NotNil(t, rsp)
|
||
|
return rsp.Resource
|
||
|
}
|
||
|
|
||
|
func ToGVK(resourceType *pbresource.Type) string {
|
||
|
return fmt.Sprintf("%s.%s.%s", resourceType.Group, resourceType.GroupVersion, resourceType.Kind)
|
||
|
}
|
||
|
|
||
|
func (client *Client) WaitForResourceExists(t T, id *pbresource.ID) *pbresource.Resource {
|
||
|
t.Helper()
|
||
|
|
||
|
var res *pbresource.Resource
|
||
|
client.retry(t, func(r *retry.R) {
|
||
|
res = client.RequireResourceExists(r, id)
|
||
|
})
|
||
|
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
func (client *Client) WaitForDeletion(t T, id *pbresource.ID) {
|
||
|
t.Helper()
|
||
|
|
||
|
client.retry(t, func(r *retry.R) {
|
||
|
client.RequireResourceNotFound(r, id)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// MustDelete will delete a resource by its id, retrying if necessary and fail the test
|
||
|
// if it cannot delete it within the timeout. The clients request delay settings are
|
||
|
// taken into account with this operation.
|
||
|
func (client *Client) MustDelete(t T, id *pbresource.ID) {
|
||
|
t.Helper()
|
||
|
client.retryDelete(t, id)
|
||
|
}
|
||
|
|
||
|
func (client *Client) retryDelete(t T, id *pbresource.ID) {
|
||
|
t.Helper()
|
||
|
ctx := client.Context(t)
|
||
|
|
||
|
client.retry(t, func(r *retry.R) {
|
||
|
_, err := client.Delete(ctx, &pbresource.DeleteRequest{Id: id})
|
||
|
if status.Code(err) == codes.NotFound {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// codes.Aborted indicates a CAS failure and that the delete request should
|
||
|
// be retried. Anything else should be considered an unrecoverable error.
|
||
|
if err != nil && status.Code(err) != codes.Aborted {
|
||
|
r.Stop(fmt.Errorf("failed to delete the resource: %w", err))
|
||
|
return
|
||
|
}
|
||
|
|
||
|
require.NoError(r, err)
|
||
|
})
|
||
|
}
|