Skip to content

Commit

Permalink
Testing
Browse files Browse the repository at this point in the history
  • Loading branch information
willie-yao committed Oct 16, 2023
1 parent dff2a72 commit 831ac19
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 239 deletions.
2 changes: 1 addition & 1 deletion azure/scope/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1613,7 +1613,7 @@ func TestAzureBastionSpec(t *testing.T) {
tests := []struct {
name string
clusterScope ClusterScope
want azure.ResourceSpecGetter
want azure.ASOResourceSpecGetter[*asonetworkv1.BastionHost]
}{
{
name: "returns nil if no subnets are specified",
Expand Down
63 changes: 12 additions & 51 deletions azure/services/bastionhosts/bastionhosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,38 @@ package bastionhosts
import (
"context"

"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4"
asonetworkv1 "github.com/Azure/azure-service-operator/v2/api/network/v1api20220701"
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
"sigs.k8s.io/cluster-api-provider-azure/azure"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/async"
"sigs.k8s.io/cluster-api-provider-azure/util/reconciler"
"sigs.k8s.io/cluster-api-provider-azure/util/tele"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/aso"
)

const serviceName = "bastionhosts"

// BastionScope defines the scope interface for a bastion host service.
type BastionScope interface {
azure.ClusterScoper
azure.AsyncStatusUpdater
AzureBastionSpec() azure.ResourceSpecGetter
aso.Scope
AzureBastionSpec() azure.ASOResourceSpecGetter[*asonetworkv1.BastionHost]
}

// Service provides operations on Azure resources.
type Service struct {
Scope BastionScope
async.Reconciler
*aso.Service[*asonetworkv1.BastionHost, BastionScope]
}

// New creates a new service.
func New(scope BastionScope) (*Service, error) {
client, err := newClient(scope)
if err != nil {
return nil, err
svc := aso.NewService[*asonetworkv1.BastionHost, BastionScope](serviceName, scope)
spec := scope.AzureBastionSpec()
if spec != nil {
svc.Specs = []azure.ASOResourceSpecGetter[*asonetworkv1.BastionHost]{spec}
}
svc.ConditionType = infrav1.BastionHostReadyCondition
return &Service{
Scope: scope,
Reconciler: async.New[armnetwork.BastionHostsClientCreateOrUpdateResponse,
armnetwork.BastionHostsClientDeleteResponse](scope, client, client),
Scope: scope,
Service: svc,
}, nil
}

Expand All @@ -60,44 +59,6 @@ func (s *Service) Name() string {
return serviceName
}

// Reconcile idempotently creates or updates a bastion host.
func (s *Service) Reconcile(ctx context.Context) error {
ctx, _, done := tele.StartSpanWithLogger(ctx, "bastionhosts.Service.Reconcile")
defer done()

ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultAzureServiceReconcileTimeout)
defer cancel()

var resultingErr error
if bastionSpec := s.Scope.AzureBastionSpec(); bastionSpec != nil {
_, resultingErr = s.CreateOrUpdateResource(ctx, bastionSpec, serviceName)
} else {
return nil
}

s.Scope.UpdatePutStatus(infrav1.BastionHostReadyCondition, serviceName, resultingErr)
return resultingErr
}

// Delete deletes the bastion host with the provided scope.
func (s *Service) Delete(ctx context.Context) error {
ctx, _, done := tele.StartSpanWithLogger(ctx, "bastionhosts.Service.Delete")
defer done()

ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultAzureServiceReconcileTimeout)
defer cancel()

var resultingErr error
if bastionSpec := s.Scope.AzureBastionSpec(); bastionSpec != nil {
resultingErr = s.DeleteResource(ctx, bastionSpec, serviceName)
} else {
return nil
}

s.Scope.UpdateDeleteStatus(infrav1.BastionHostReadyCondition, serviceName, resultingErr)
return resultingErr
}

// IsManaged returns always returns true as CAPZ does not support BYO bastion.
func (s *Service) IsManaged(ctx context.Context) (bool, error) {
return true, nil
Expand Down
84 changes: 56 additions & 28 deletions azure/services/bastionhosts/bastionhosts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,85 +21,102 @@ import (
"net/http"
"testing"

asonetworkv1 "github.com/Azure/azure-service-operator/v2/api/network/v1api20220701"
"github.com/Azure/go-autorest/autorest"
. "github.com/onsi/gomega"
"go.uber.org/mock/gomock"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/scheme"
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/async/mock_async"
mock_aso "sigs.k8s.io/cluster-api-provider-azure/azure/services/aso/mock_aso"
mock_bastionhosts "sigs.k8s.io/cluster-api-provider-azure/azure/services/bastionhosts/mocks_bastionhosts"
gomockinternal "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers/gomock"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
)

var (
fakeSubnetID = "my-subnet-id"
fakePublicIPID = "my-public-ip-id"
fakeAzureBastionSpec = AzureBastionSpec{
Name: "my-bastion",
Namespace: "default",
Location: "westus",
ClusterName: "my-cluster",
SubnetID: fakeSubnetID,
PublicIPID: fakePublicIPID,
}
conditionType = clusterv1.ConditionType(infrav1.BastionHostReadyCondition)

Check failure on line 49 in azure/services/bastionhosts/bastionhosts_test.go

View workflow job for this annotation

GitHub Actions / coverage

unnecessary conversion (unconvert)
internalError = autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: http.StatusInternalServerError}, "Internal Server Error")
)

func init() {
_ = clusterv1.AddToScheme(scheme.Scheme)
_ = asonetworkv1.AddToScheme(scheme.Scheme)
}

func TestReconcileBastionHosts(t *testing.T) {
g := NewWithT(t)
sch := runtime.NewScheme()
g.Expect(asonetworkv1.AddToScheme(sch)).To(Succeed())
client := fakeclient.NewClientBuilder().
WithScheme(sch).
Build()
testcases := []struct {
name string
expectedError string
expect func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder)
expect func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_aso.MockReconcilerMockRecorder[*asonetworkv1.BastionHost])
}{
{
name: "bastion successfully created",
expectedError: "",
expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_aso.MockReconcilerMockRecorder[*asonetworkv1.BastionHost]) {
s.AzureBastionSpec().Return(&fakeAzureBastionSpec)
r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeAzureBastionSpec, serviceName).Return(nil, nil)
s.UpdatePutStatus(infrav1.BastionHostReadyCondition, serviceName, nil)
r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeAzureBastionSpec, serviceName).Return(&fakeBastionHost, nil)
s.UpdatePutStatus(conditionType, serviceName, nil)
s.GetClient().Return(client)
s.ClusterName().Return("my-cluster")
},
},
{
name: "no bastion spec found",
expectedError: "",
expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_aso.MockReconcilerMockRecorder[*asonetworkv1.BastionHost]) {
s.AzureBastionSpec().Return(nil)
s.UpdatePutStatus(conditionType, serviceName, nil)
s.GetClient().Return(client)
s.ClusterName().Return("my-cluster")
},
},
{
name: "fail to create a bastion",
expectedError: internalError.Error(),
expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_aso.MockReconcilerMockRecorder[*asonetworkv1.BastionHost]) {
s.AzureBastionSpec().Return(&fakeAzureBastionSpec)
r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeAzureBastionSpec, serviceName).Return(nil, internalError)
s.UpdatePutStatus(infrav1.BastionHostReadyCondition, serviceName, internalError)
s.UpdatePutStatus(conditionType, serviceName, internalError)
s.GetClient().Return(client)
s.ClusterName().Return("my-cluster")
},
},
}

for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
g := NewWithT(t)
t.Parallel()
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
scopeMock := mock_bastionhosts.NewMockBastionScope(mockCtrl)
asyncMock := mock_async.NewMockReconciler(mockCtrl)
reconcilerMock := mock_aso.NewMockReconciler[*asonetworkv1.BastionHost](mockCtrl)

tc.expect(scopeMock.EXPECT(), asyncMock.EXPECT())
tc.expect(scopeMock.EXPECT(), reconcilerMock.EXPECT())

s := &Service{
Scope: scopeMock,
Reconciler: asyncMock,
}
s, err := New(scopeMock)
g.Expect(err).NotTo(HaveOccurred())
s.Reconciler = reconcilerMock

err := s.Reconcile(context.TODO())
err = s.Reconcile(context.TODO())
if tc.expectedError != "" {
g.Expect(err).To(HaveOccurred())
g.Expect(err).To(MatchError(tc.expectedError))
Expand All @@ -111,56 +128,67 @@ func TestReconcileBastionHosts(t *testing.T) {
}

func TestDeleteBastionHost(t *testing.T) {
g := NewWithT(t)
sch := runtime.NewScheme()
g.Expect(asonetworkv1.AddToScheme(sch)).To(Succeed())
client := fakeclient.NewClientBuilder().
WithScheme(sch).
Build()
testcases := []struct {
name string
expectedError string
expect func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder)
expect func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_aso.MockReconcilerMockRecorder[*asonetworkv1.BastionHost])
}{
{
name: "successfully delete an existing bastion host",
expectedError: "",
expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_aso.MockReconcilerMockRecorder[*asonetworkv1.BastionHost]) {
s.AzureBastionSpec().Return(&fakeAzureBastionSpec)
r.DeleteResource(gomockinternal.AContext(), &fakeAzureBastionSpec, serviceName).Return(nil)
s.UpdateDeleteStatus(infrav1.BastionHostReadyCondition, serviceName, nil)
s.GetClient().Return(client)
s.ClusterName().Return("my-cluster")
},
},
{
name: "bastion host deletion fails",
expectedError: internalError.Error(),
expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_aso.MockReconcilerMockRecorder[*asonetworkv1.BastionHost]) {
s.AzureBastionSpec().Return(&fakeAzureBastionSpec)
r.DeleteResource(gomockinternal.AContext(), &fakeAzureBastionSpec, serviceName).Return(internalError)
s.UpdateDeleteStatus(infrav1.BastionHostReadyCondition, serviceName, internalError)
s.GetClient().Return(client)
s.ClusterName().Return("my-cluster")
},
},
{
name: "no bastion spec found",
expectedError: "",
expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
expect: func(s *mock_bastionhosts.MockBastionScopeMockRecorder, r *mock_aso.MockReconcilerMockRecorder[*asonetworkv1.BastionHost]) {
s.AzureBastionSpec().Return(nil)
s.UpdateDeleteStatus(infrav1.BastionHostReadyCondition, serviceName, nil)
s.GetClient().Return(client)
s.ClusterName().Return("my-cluster")
},
},
}

for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
g := NewWithT(t)
t.Parallel()
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
scopeMock := mock_bastionhosts.NewMockBastionScope(mockCtrl)
asyncMock := mock_async.NewMockReconciler(mockCtrl)
reconcilerMock := mock_aso.NewMockReconciler[*asonetworkv1.BastionHost](mockCtrl)

tc.expect(scopeMock.EXPECT(), asyncMock.EXPECT())
tc.expect(scopeMock.EXPECT(), reconcilerMock.EXPECT())

s := &Service{
Scope: scopeMock,
Reconciler: asyncMock,
}
s, err := New(scopeMock)
g.Expect(err).NotTo(HaveOccurred())
s.Reconciler = reconcilerMock

err := s.Delete(context.TODO())
err = s.Delete(context.TODO())
if tc.expectedError != "" {
g.Expect(err).To(HaveOccurred())
g.Expect(err).To(MatchError(tc.expectedError))
Expand Down
Loading

0 comments on commit 831ac19

Please sign in to comment.