Add finalizer to link resource (#20321)

* Add finalizer to link resource

* Update internal/hcp/internal/controllers/link/controller.go

Co-authored-by: Semir Patel <semir.patel@hashicorp.com>

* Address PR style feedback

---------

Co-authored-by: Semir Patel <semir.patel@hashicorp.com>
This commit is contained in:
Nick Cellino 2024-01-25 12:27:36 -05:00 committed by GitHub
parent 4801c9cbdc
commit ec0df00fc1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 119 additions and 5 deletions

View File

@ -7,10 +7,9 @@ import (
"context" "context"
"strings" "strings"
gnmmod "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
gnmmod "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models"
"google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/anypb"
"github.com/hashicorp/consul/agent/hcp/bootstrap" "github.com/hashicorp/consul/agent/hcp/bootstrap"
@ -18,6 +17,7 @@ import (
"github.com/hashicorp/consul/agent/hcp/config" "github.com/hashicorp/consul/agent/hcp/config"
"github.com/hashicorp/consul/internal/controller" "github.com/hashicorp/consul/internal/controller"
"github.com/hashicorp/consul/internal/hcp/internal/types" "github.com/hashicorp/consul/internal/hcp/internal/types"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/storage" "github.com/hashicorp/consul/internal/storage"
pbhcp "github.com/hashicorp/consul/proto-public/pbhcp/v2" pbhcp "github.com/hashicorp/consul/proto-public/pbhcp/v2"
"github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/proto-public/pbresource"
@ -108,6 +108,19 @@ func (r *linkReconciler) Reconcile(ctx context.Context, rt controller.Runtime, r
return err return err
} }
if err = addFinalizer(ctx, rt, res); err != nil {
rt.Logger.Error("error adding finalizer to link resource", "error", err)
return err
}
if resource.IsMarkedForDeletion(res) {
if err = cleanup(ctx, rt, res, r.dataDir); err != nil {
rt.Logger.Error("error cleaning up link resource", "error", err)
return err
}
return nil
}
// Validation - Ensure V2 Resource APIs are not enabled unless hcpAllowV2ResourceApis flag is set // Validation - Ensure V2 Resource APIs are not enabled unless hcpAllowV2ResourceApis flag is set
var newStatus *pbresource.Status var newStatus *pbresource.Status
if r.resourceApisEnabled && !r.hcpAllowV2ResourceApis { if r.resourceApisEnabled && !r.hcpAllowV2ResourceApis {

View File

@ -6,24 +6,28 @@ package link
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"path/filepath"
"testing" "testing"
"github.com/hashicorp/go-uuid"
gnmmod "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/hashicorp/go-uuid"
gnmmod "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models"
svctest "github.com/hashicorp/consul/agent/grpc-external/services/resource/testing" svctest "github.com/hashicorp/consul/agent/grpc-external/services/resource/testing"
"github.com/hashicorp/consul/agent/hcp/bootstrap"
hcpclient "github.com/hashicorp/consul/agent/hcp/client" hcpclient "github.com/hashicorp/consul/agent/hcp/client"
"github.com/hashicorp/consul/agent/hcp/config" "github.com/hashicorp/consul/agent/hcp/config"
"github.com/hashicorp/consul/internal/controller" "github.com/hashicorp/consul/internal/controller"
"github.com/hashicorp/consul/internal/hcp/internal/types" "github.com/hashicorp/consul/internal/hcp/internal/types"
"github.com/hashicorp/consul/internal/resource"
rtest "github.com/hashicorp/consul/internal/resource/resourcetest" rtest "github.com/hashicorp/consul/internal/resource/resourcetest"
pbhcp "github.com/hashicorp/consul/proto-public/pbhcp/v2" pbhcp "github.com/hashicorp/consul/proto-public/pbhcp/v2"
"github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/consul/sdk/testutil/retry"
) )
type controllerSuite struct { type controllerSuite struct {
@ -34,6 +38,7 @@ type controllerSuite struct {
rt controller.Runtime rt controller.Runtime
tenancies []*pbresource.Tenancy tenancies []*pbresource.Tenancy
dataDir string
} }
func mockHcpClientFn(t *testing.T) (*hcpclient.MockClient, HCPClientFn) { func mockHcpClientFn(t *testing.T) (*hcpclient.MockClient, HCPClientFn) {
@ -68,6 +73,18 @@ func TestLinkController(t *testing.T) {
func (suite *controllerSuite) deleteResourceFunc(id *pbresource.ID) func() { func (suite *controllerSuite) deleteResourceFunc(id *pbresource.ID) func() {
return func() { return func() {
suite.client.MustDelete(suite.T(), id) suite.client.MustDelete(suite.T(), id)
// Ensure hcp-config directory is removed
retry.Run(suite.T(), func(r *retry.R) {
if suite.dataDir != "" {
file := filepath.Join(suite.dataDir, bootstrap.SubDir)
if _, err := os.Stat(file); !os.IsNotExist(err) {
r.Fatalf("should have removed hcp-config directory")
}
}
})
suite.client.WaitForDeletion(suite.T(), id)
} }
} }
@ -90,6 +107,7 @@ func (suite *controllerSuite) TestController_Ok() {
}, nil).Once() }, nil).Once()
dataDir := testutil.TempDir(suite.T(), "test-link-controller") dataDir := testutil.TempDir(suite.T(), "test-link-controller")
suite.dataDir = dataDir
mgr.Register(LinkController( mgr.Register(LinkController(
false, false,
false, false,
@ -112,6 +130,11 @@ func (suite *controllerSuite) TestController_Ok() {
suite.T().Cleanup(suite.deleteResourceFunc(link.Id)) suite.T().Cleanup(suite.deleteResourceFunc(link.Id))
// Ensure finalizer was added
suite.client.WaitForResourceState(suite.T(), link.Id, func(t rtest.T, res *pbresource.Resource) {
require.True(t, resource.HasFinalizer(res, StatusKey), "link resource does not have finalizer")
})
suite.client.WaitForStatusCondition(suite.T(), link.Id, StatusKey, ConditionLinked(linkData.ResourceId)) suite.client.WaitForStatusCondition(suite.T(), link.Id, StatusKey, ConditionLinked(linkData.ResourceId))
var updatedLink pbhcp.Link var updatedLink pbhcp.Link
updatedLinkResource := suite.client.WaitForNewVersion(suite.T(), link.Id, link.Version) updatedLinkResource := suite.client.WaitForNewVersion(suite.T(), link.Id, link.Version)
@ -138,6 +161,7 @@ func (suite *controllerSuite) TestController_Initialize() {
} }
dataDir := testutil.TempDir(suite.T(), "test-link-controller") dataDir := testutil.TempDir(suite.T(), "test-link-controller")
suite.dataDir = dataDir
mgr.Register(LinkController( mgr.Register(LinkController(
false, false,
@ -176,6 +200,7 @@ func (suite *controllerSuite) TestControllerResourceApisEnabled_LinkDisabled() {
mgr := controller.NewManager(suite.client, suite.rt.Logger) mgr := controller.NewManager(suite.client, suite.rt.Logger)
_, mockClientFunc := mockHcpClientFn(suite.T()) _, mockClientFunc := mockHcpClientFn(suite.T())
dataDir := testutil.TempDir(suite.T(), "test-link-controller") dataDir := testutil.TempDir(suite.T(), "test-link-controller")
suite.dataDir = dataDir
mgr.Register(LinkController( mgr.Register(LinkController(
true, true,
false, false,
@ -217,6 +242,7 @@ func (suite *controllerSuite) TestControllerResourceApisEnabledWithOverride_Link
}, nil).Once() }, nil).Once()
dataDir := testutil.TempDir(suite.T(), "test-link-controller") dataDir := testutil.TempDir(suite.T(), "test-link-controller")
suite.dataDir = dataDir
mgr.Register(LinkController( mgr.Register(LinkController(
true, true,
@ -271,6 +297,7 @@ func (suite *controllerSuite) TestController_GetClusterError() {
mockClient.EXPECT().GetCluster(mock.Anything).Return(nil, tc.expectErr) mockClient.EXPECT().GetCluster(mock.Anything).Return(nil, tc.expectErr)
dataDir := testutil.TempDir(suite.T(), "test-link-controller") dataDir := testutil.TempDir(suite.T(), "test-link-controller")
suite.dataDir = dataDir
mgr.Register(LinkController( mgr.Register(LinkController(
true, true,
true, true,

View File

@ -0,0 +1,74 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package link
import (
"context"
"os"
"path/filepath"
"github.com/hashicorp/consul/agent/hcp/bootstrap"
"github.com/hashicorp/consul/internal/controller"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/proto-public/pbresource"
)
func cleanup(ctx context.Context, rt controller.Runtime, res *pbresource.Resource, dataDir string) error {
rt.Logger.Trace("cleaning up link resource")
if dataDir != "" {
hcpConfigDir := filepath.Join(dataDir, bootstrap.SubDir)
rt.Logger.Debug("deleting hcp-config dir", "dir", hcpConfigDir)
err := os.RemoveAll(hcpConfigDir)
if err != nil {
return err
}
}
err := ensureDeleted(ctx, rt, res)
if err != nil {
return err
}
return nil
}
func addFinalizer(ctx context.Context, rt controller.Runtime, res *pbresource.Resource) error {
// The statusKey doubles as the finalizer name for the link resource
if resource.HasFinalizer(res, StatusKey) {
rt.Logger.Trace("already has finalizer")
return nil
}
// Finalizer hasn't been written, so add it.
resource.AddFinalizer(res, StatusKey)
_, err := rt.Client.Write(ctx, &pbresource.WriteRequest{Resource: res})
if err != nil {
return err
}
rt.Logger.Trace("added finalizer")
return err
}
// ensureDeleted makes sure a link is finally deleted
func ensureDeleted(ctx context.Context, rt controller.Runtime, res *pbresource.Resource) error {
// Remove finalizer if present
if resource.HasFinalizer(res, StatusKey) {
resource.RemoveFinalizer(res, StatusKey)
_, err := rt.Client.Write(ctx, &pbresource.WriteRequest{Resource: res})
if err != nil {
return err
}
rt.Logger.Trace("removed finalizer")
}
// Finally, delete the link
_, err := rt.Client.Delete(ctx, &pbresource.DeleteRequest{Id: res.Id})
if err != nil {
return err
}
// Success
rt.Logger.Trace("finally deleted")
return nil
}