mirror of https://github.com/hashicorp/consul
Semir Patel
12 months ago
committed by
GitHub
12 changed files with 209 additions and 20 deletions
@ -0,0 +1,166 @@
|
||||
// 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) |
||||
}) |
||||
} |
Loading…
Reference in new issue