diff --git a/controllers/azuremachine_tags_unit_test.go b/controllers/azuremachine_tags_unit_test.go index a1bfd875782..ffc25674c10 100644 --- a/controllers/azuremachine_tags_unit_test.go +++ b/controllers/azuremachine_tags_unit_test.go @@ -110,7 +110,7 @@ func TestTagsChanged(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - changed, created, deleted, newAnnotation := tagsChanged(test.annotation, test.src) + changed, created, deleted, newAnnotation := TagsChanged(test.annotation, test.src) g.Expect(changed).To(Equal(test.expectedResult)) g.Expect(created).To(Equal(test.expectedCreated)) g.Expect(deleted).To(Equal(test.expectedDeleted)) diff --git a/exp/api/v1alpha3/azuremachinepool_test.go b/exp/api/v1alpha3/azuremachinepool_test.go new file mode 100644 index 00000000000..92c6afb82c0 --- /dev/null +++ b/exp/api/v1alpha3/azuremachinepool_test.go @@ -0,0 +1,94 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha3_test + +import ( + "testing" + + "github.com/onsi/gomega" + + infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha3" + exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1alpha3" +) + +func TestAzureMachinePool_Validate(t *testing.T) { + cases := []struct { + Name string + Factory func(g *gomega.GomegaWithT) *exp.AzureMachinePool + Expect func(g *gomega.GomegaWithT, actual error) + }{ + { + Name: "HasNoImage", + Factory: func(_ *gomega.GomegaWithT) *exp.AzureMachinePool { + return new(exp.AzureMachinePool) + }, + Expect: func(g *gomega.GomegaWithT, actual error) { + g.Expect(actual).ToNot(gomega.HaveOccurred()) + }, + }, + { + Name: "HasValidImage", + Factory: func(_ *gomega.GomegaWithT) *exp.AzureMachinePool { + return &exp.AzureMachinePool{ + Spec: exp.AzureMachinePoolSpec{ + Template: exp.AzureMachineTemplate{ + Image: &infrav1.Image{ + SharedGallery: &infrav1.AzureSharedGalleryImage{ + SubscriptionID: "foo", + ResourceGroup: "blah", + Name: "bin", + Gallery: "bazz", + Version: "1.2.3", + }, + }, + }, + }, + } + }, + Expect: func(g *gomega.GomegaWithT, actual error) { + g.Expect(actual).ToNot(gomega.HaveOccurred()) + }, + }, + { + Name: "HasInvalidImage", + Factory: func(_ *gomega.GomegaWithT) *exp.AzureMachinePool { + return &exp.AzureMachinePool{ + Spec: exp.AzureMachinePoolSpec{ + Template: exp.AzureMachineTemplate{ + Image: new(infrav1.Image), + }, + }, + } + }, + Expect: func(g *gomega.GomegaWithT, actual error) { + g.Expect(actual).To(gomega.HaveOccurred()) + g.Expect(actual.Error()).To(gomega.ContainSubstring("You must supply a ID, Marketplace or SharedGallery image details")) + }, + }, + } + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + t.Parallel() + g := gomega.NewGomegaWithT(t) + amp := c.Factory(g) + actualErr := amp.Validate() + c.Expect(g, actualErr) + }) + } +} diff --git a/exp/cloud/converters/vmss_test.go b/exp/cloud/converters/vmss_test.go new file mode 100644 index 00000000000..20a5886e38d --- /dev/null +++ b/exp/cloud/converters/vmss_test.go @@ -0,0 +1,118 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package converters_test + +import ( + "fmt" + "testing" + + "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute" + "github.com/Azure/go-autorest/autorest/to" + "github.com/onsi/gomega" + + infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1alpha3" + "sigs.k8s.io/cluster-api-provider-azure/exp/cloud/converters" +) + +func Test_SDKToVMSS(t *testing.T) { + cases := []struct { + Name string + SubjectFactory func(*gomega.GomegaWithT) (compute.VirtualMachineScaleSet, []compute.VirtualMachineScaleSetVM) + Expect func(*gomega.GomegaWithT, *infrav1exp.VMSS) + }{ + { + Name: "ShouldPopulateWithData", + SubjectFactory: func(g *gomega.GomegaWithT) (compute.VirtualMachineScaleSet, []compute.VirtualMachineScaleSetVM) { + tags := map[string]*string{ + "foo": to.StringPtr("bazz"), + } + zones := []string{"zone0", "zone1"} + return compute.VirtualMachineScaleSet{ + Sku: &compute.Sku{ + Name: to.StringPtr("skuName"), + Tier: to.StringPtr("skuTier"), + Capacity: to.Int64Ptr(2), + }, + Zones: to.StringSlicePtr(zones), + ID: to.StringPtr("vmssID"), + Name: to.StringPtr("vmssName"), + Location: to.StringPtr("westus2"), + Tags: tags, + VirtualMachineScaleSetProperties: &compute.VirtualMachineScaleSetProperties{ + ProvisioningState: to.StringPtr(string(compute.ProvisioningState1Succeeded)), + }, + }, + []compute.VirtualMachineScaleSetVM{ + { + InstanceID: to.StringPtr("0"), + ID: to.StringPtr("vm/0"), + Name: to.StringPtr("vm0"), + Zones: to.StringSlicePtr([]string{"zone0"}), + VirtualMachineScaleSetVMProperties: &compute.VirtualMachineScaleSetVMProperties{ + ProvisioningState: to.StringPtr(string(compute.ProvisioningState1Succeeded)), + }, + }, + { + InstanceID: to.StringPtr("1"), + ID: to.StringPtr("vm/1"), + Name: to.StringPtr("vm1"), + Zones: to.StringSlicePtr([]string{"zone1"}), + VirtualMachineScaleSetVMProperties: &compute.VirtualMachineScaleSetVMProperties{ + ProvisioningState: to.StringPtr(string(compute.ProvisioningState1Succeeded)), + }, + }, + } + }, + Expect: func(g *gomega.GomegaWithT, actual *infrav1exp.VMSS) { + expected := infrav1exp.VMSS{ + ID: "vmssID", + Name: "vmssName", + Sku: "skuName", + Capacity: 2, + Zones: []string{"zone0", "zone1"}, + State: "Succeeded", + Tags: map[string]string{ + "foo": "bazz", + }, + Instances: make([]infrav1exp.VMSSVM, 2), + } + + for i := 0; i < 2; i++ { + expected.Instances[i] = infrav1exp.VMSSVM{ + ID: fmt.Sprintf("vm/%d", i), + InstanceID: fmt.Sprintf("%d", i), + Name: fmt.Sprintf("vm%d", i), + AvailabilityZone: fmt.Sprintf("zone%d", i), + State: "Succeeded", + } + } + g.Expect(actual).To(gomega.Equal(&expected)) + }, + }, + } + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + t.Parallel() + g := gomega.NewGomegaWithT(t) + vmss, instances := c.SubjectFactory(g) + subject := converters.SDKToVMSS(vmss, instances) + c.Expect(g, subject) + }) + } +} diff --git a/exp/cloud/services/scalesets/vmss_test.go b/exp/cloud/services/scalesets/vmss_test.go new file mode 100644 index 00000000000..7ac75e677de --- /dev/null +++ b/exp/cloud/services/scalesets/vmss_test.go @@ -0,0 +1,466 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scalesets + +import ( + "context" + "fmt" + "testing" + + "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute" + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/to" + "github.com/golang/mock/gomock" + "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" + clusterv1exp "sigs.k8s.io/cluster-api/exp/api/v1alpha3" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha3" + azure "sigs.k8s.io/cluster-api-provider-azure/cloud" + "sigs.k8s.io/cluster-api-provider-azure/cloud/scope" + infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1alpha3" + scopeExp "sigs.k8s.io/cluster-api-provider-azure/exp/cloud/scope" + "sigs.k8s.io/cluster-api-provider-azure/exp/cloud/services/scalesets/mock_scalesets" + "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers" +) + +func init() { + _ = clusterv1.AddToScheme(scheme.Scheme) +} + +func TestNewService(t *testing.T) { + cluster := &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{Name: "test-cluster"}, + } + client := fake.NewFakeClientWithScheme(scheme.Scheme, cluster) + s, err := scope.NewClusterScope(scope.ClusterScopeParams{ + AzureClients: scope.AzureClients{ + SubscriptionID: "123", + Authorizer: autorest.NullAuthorizer{}, + }, + Client: client, + Cluster: cluster, + AzureCluster: &infrav1.AzureCluster{ + Spec: infrav1.AzureClusterSpec{ + Location: "test-location", + ResourceGroup: "my-rg", + NetworkSpec: infrav1.NetworkSpec{ + Vnet: infrav1.VnetSpec{Name: "my-vnet", ResourceGroup: "my-rg"}, + }, + }, + }, + }) + g := gomega.NewGomegaWithT(t) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + mps, err := scopeExp.NewMachinePoolScope(scopeExp.MachinePoolScopeParams{ + AzureClients: s.AzureClients, + Client: client, + Logger: s.Logger, + Cluster: s.Cluster, + MachinePool: new(clusterv1exp.MachinePool), + AzureCluster: s.AzureCluster, + AzureMachinePool: new(infrav1exp.AzureMachinePool), + }) + g.Expect(err).ToNot(gomega.HaveOccurred()) + actual := NewService(s, mps) + g.Expect(actual).ToNot(gomega.BeNil()) + g.Expect(actual.MachinePoolScope).To(gomega.Equal(mps)) + g.Expect(actual.Scope).To(gomega.Equal(s)) +} + +func TestService_Get(t *testing.T) { + cases := []struct { + Name string + SpecFactory func(g *gomega.GomegaWithT, svc *Service) interface{} + Setup func(ctx context.Context, g *gomega.GomegaWithT, svc *Service) + Expect func(ctx context.Context, g *gomega.GomegaWithT, result interface{}, err error) + }{ + { + Name: "WithInvalidSepcType", + SpecFactory: func(g *gomega.GomegaWithT, _ *Service) interface{} { + return "bin" + }, + Expect: func(_ context.Context, g *gomega.GomegaWithT, result interface{}, err error) { + g.Expect(err).To(gomega.MatchError("invalid VMSS specification")) + }, + }, + { + Name: "WithValidSpecBut404FromAzureOnVMSS", + SpecFactory: func(g *gomega.GomegaWithT, svc *Service) interface{} { + return &Spec{ + Name: svc.MachinePoolScope.Name(), + } + }, + Setup: func(ctx context.Context, g *gomega.GomegaWithT, svc *Service) { + mockCtrl := gomock.NewController(t) + vmssMock := mock_scalesets.NewMockClient(mockCtrl) + svc.Client = vmssMock + vmssMock.EXPECT().Get(gomock.Any(), svc.Scope.AzureCluster.Spec.ResourceGroup, svc.MachinePoolScope.Name()).Return(compute.VirtualMachineScaleSet{}, autorest.DetailedError{ + StatusCode: 404, + }) + }, + Expect: func(ctx context.Context, g *gomega.GomegaWithT, result interface{}, err error) { + g.Expect(err).To(gomega.Equal(autorest.DetailedError{ + StatusCode: 404, + })) + }, + }, + { + Name: "WithValidSpecBut404FromAzureOnInstances", + SpecFactory: func(g *gomega.GomegaWithT, svc *Service) interface{} { + return &Spec{ + Name: svc.MachinePoolScope.Name(), + } + }, + Setup: func(ctx context.Context, g *gomega.GomegaWithT, svc *Service) { + mockCtrl := gomock.NewController(t) + vmssMock := mock_scalesets.NewMockClient(mockCtrl) + svc.Client = vmssMock + vmssMock.EXPECT().Get(gomock.Any(), svc.Scope.AzureCluster.Spec.ResourceGroup, svc.MachinePoolScope.Name()).Return(compute.VirtualMachineScaleSet{}, nil) + vmssMock.EXPECT().ListInstances(gomock.Any(), svc.Scope.AzureCluster.Spec.ResourceGroup, svc.MachinePoolScope.Name()).Return([]compute.VirtualMachineScaleSetVM{}, autorest.DetailedError{ + StatusCode: 404, + }) + }, + Expect: func(ctx context.Context, g *gomega.GomegaWithT, result interface{}, err error) { + g.Expect(err).To(gomega.Equal(autorest.DetailedError{ + StatusCode: 404, + })) + }, + }, + { + Name: "WithValidSpecWithVMSSAndInstancesReturned", + SpecFactory: func(g *gomega.GomegaWithT, svc *Service) interface{} { + return &Spec{ + Name: svc.MachinePoolScope.Name(), + } + }, + Setup: func(ctx context.Context, g *gomega.GomegaWithT, svc *Service) { + mockCtrl := gomock.NewController(t) + vmssMock := mock_scalesets.NewMockClient(mockCtrl) + svc.Client = vmssMock + vmssMock.EXPECT().Get(gomock.Any(), svc.Scope.AzureCluster.Spec.ResourceGroup, svc.MachinePoolScope.Name()).Return(compute.VirtualMachineScaleSet{ + Name: to.StringPtr(svc.MachinePoolScope.Name()), + Sku: &compute.Sku{ + Capacity: to.Int64Ptr(1), + Name: to.StringPtr("Standard"), + }, + VirtualMachineScaleSetProperties: &compute.VirtualMachineScaleSetProperties{ + ProvisioningState: to.StringPtr("Succeeded"), + }, + }, nil) + vmssMock.EXPECT().ListInstances(gomock.Any(), svc.Scope.AzureCluster.Spec.ResourceGroup, svc.MachinePoolScope.Name()).Return([]compute.VirtualMachineScaleSetVM{ + { + Name: to.StringPtr("vm0"), + InstanceID: to.StringPtr("0"), + VirtualMachineScaleSetVMProperties: &compute.VirtualMachineScaleSetVMProperties{ + ProvisioningState: to.StringPtr("Succeeded"), + }, + }, + }, nil) + }, + Expect: func(ctx context.Context, g *gomega.GomegaWithT, result interface{}, err error) { + g.Expect(result).To(gomega.Equal(&infrav1exp.VMSS{ + Name: "capz-mp-0", + Sku: "Standard", + Capacity: 1, + Image: infrav1.Image{}, + State: "Succeeded", + Instances: []infrav1exp.VMSSVM{ + { + InstanceID: "0", + Name: "vm0", + State: "Succeeded", + }, + }, + })) + }, + }, + } + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + t.Parallel() + g := gomega.NewGomegaWithT(t) + svc := getNewService(g) + spec := c.SpecFactory(g, svc) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if c.Setup != nil { + c.Setup(ctx, g, svc) + } + res, err := svc.Get(context.Background(), spec) + c.Expect(ctx, g, res, err) + }) + } +} + +func TestService_Reconcile(t *testing.T) { + cases := []struct { + Name string + SpecFactory func(g *gomega.GomegaWithT, svc *Service) interface{} + Setup func(ctx context.Context, g *gomega.GomegaWithT, svc *Service, spec *Spec) + Expect func(ctx context.Context, g *gomega.GomegaWithT, err error) + }{ + { + Name: "WithInvalidSepcType", + SpecFactory: func(g *gomega.GomegaWithT, _ *Service) interface{} { + return "bazz" + }, + Expect: func(_ context.Context, g *gomega.GomegaWithT, err error) { + g.Expect(err).To(gomega.MatchError("invalid VMSS specification")) + }, + }, + { + Name: "WithValidSpec", + SpecFactory: func(g *gomega.GomegaWithT, svc *Service) interface{} { + return &Spec{ + Name: svc.MachinePoolScope.Name(), + Sku: "skuName", + Capacity: 2, + SSHKeyData: "sshKeyData", + OSDisk: infrav1.OSDisk{ + OSType: "Linux", + DiskSizeGB: 120, + ManagedDisk: infrav1.ManagedDisk{ + StorageAccountType: "accountType", + }, + }, + Image: &infrav1.Image{ + ID: to.StringPtr("image"), + }, + CustomData: "customData", + } + }, + Setup: func(ctx context.Context, g *gomega.GomegaWithT, svc *Service, spec *Spec) { + mockCtrl := gomock.NewController(t) + vmssMock := mock_scalesets.NewMockClient(mockCtrl) + svc.Client = vmssMock + + storageProfile, err := generateStorageProfile(*spec) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + vmss := compute.VirtualMachineScaleSet{ + Location: to.StringPtr(svc.Scope.Location()), + Tags: map[string]*string{ + "Name": to.StringPtr("capz-mp-0"), + "kubernetes.io_cluster_capz-mp-0": to.StringPtr("owned"), + "sigs.k8s.io_cluster-api-provider-azure_cluster_test-cluster": to.StringPtr("owned"), + "sigs.k8s.io_cluster-api-provider-azure_role": to.StringPtr("node"), + }, + Sku: &compute.Sku{ + Name: to.StringPtr(spec.Sku), + Tier: to.StringPtr("Standard"), + Capacity: to.Int64Ptr(spec.Capacity), + }, + VirtualMachineScaleSetProperties: &compute.VirtualMachineScaleSetProperties{ + UpgradePolicy: &compute.UpgradePolicy{ + Mode: compute.Manual, + }, + VirtualMachineProfile: &compute.VirtualMachineScaleSetVMProfile{ + OsProfile: &compute.VirtualMachineScaleSetOSProfile{ + ComputerNamePrefix: to.StringPtr(spec.Name), + AdminUsername: to.StringPtr(azure.DefaultUserName), + CustomData: to.StringPtr(spec.CustomData), + LinuxConfiguration: &compute.LinuxConfiguration{ + SSH: &compute.SSHConfiguration{ + PublicKeys: &[]compute.SSHPublicKey{ + { + Path: to.StringPtr(fmt.Sprintf("/home/%s/.ssh/authorized_keys", azure.DefaultUserName)), + KeyData: to.StringPtr(spec.SSHKeyData), + }, + }, + }, + DisablePasswordAuthentication: to.BoolPtr(true), + }, + }, + StorageProfile: storageProfile, + NetworkProfile: &compute.VirtualMachineScaleSetNetworkProfile{ + NetworkInterfaceConfigurations: &[]compute.VirtualMachineScaleSetNetworkConfiguration{ + { + Name: to.StringPtr(spec.Name + "-netconfig"), + VirtualMachineScaleSetNetworkConfigurationProperties: &compute.VirtualMachineScaleSetNetworkConfigurationProperties{ + Primary: to.BoolPtr(true), + EnableIPForwarding: to.BoolPtr(true), + IPConfigurations: &[]compute.VirtualMachineScaleSetIPConfiguration{ + { + Name: to.StringPtr(spec.Name + "-ipconfig"), + VirtualMachineScaleSetIPConfigurationProperties: &compute.VirtualMachineScaleSetIPConfigurationProperties{ + Subnet: &compute.APIEntityReference{ + ID: to.StringPtr(svc.Scope.AzureCluster.Spec.NetworkSpec.Subnets[0].ID), + }, + Primary: to.BoolPtr(true), + PrivateIPAddressVersion: compute.IPv4, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + vmssMock.EXPECT().CreateOrUpdate(gomock.Any(), svc.Scope.AzureCluster.Spec.ResourceGroup, spec.Name, matchers.DiffEq(vmss)).Return(nil) + }, + Expect: func(ctx context.Context, g *gomega.GomegaWithT, err error) { + g.Expect(err).ToNot(gomega.HaveOccurred()) + }, + }, + } + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + t.Parallel() + g := gomega.NewGomegaWithT(t) + svc := getNewService(g) + spec := c.SpecFactory(g, svc) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if c.Setup != nil { + c.Setup(ctx, g, svc, spec.(*Spec)) + } + err := svc.Reconcile(context.Background(), spec) + c.Expect(ctx, g, err) + }) + } +} + +func TestService_Delete(t *testing.T) { + cases := []struct { + Name string + SpecFactory func(g *gomega.GomegaWithT, svc *Service) interface{} + Setup func(ctx context.Context, g *gomega.GomegaWithT, svc *Service) + Expect func(ctx context.Context, g *gomega.GomegaWithT, err error) + }{ + { + Name: "WithInvalidSepcType", + SpecFactory: func(g *gomega.GomegaWithT, _ *Service) interface{} { + return "foo" + }, + Expect: func(_ context.Context, g *gomega.GomegaWithT, err error) { + g.Expect(err).To(gomega.MatchError("invalid VMSS specification")) + }, + }, + { + Name: "WithValidSpecBut404FromAzureOnVMSSAssumeAlreadyDeleted", + SpecFactory: func(g *gomega.GomegaWithT, svc *Service) interface{} { + return &Spec{ + Name: svc.MachinePoolScope.Name(), + } + }, + Setup: func(ctx context.Context, g *gomega.GomegaWithT, svc *Service) { + mockCtrl := gomock.NewController(t) + vmssMock := mock_scalesets.NewMockClient(mockCtrl) + svc.Client = vmssMock + vmssMock.EXPECT().Delete(gomock.Any(), svc.Scope.AzureCluster.Spec.ResourceGroup, svc.MachinePoolScope.Name()).Return(autorest.DetailedError{ + StatusCode: 404, + }) + }, + Expect: func(ctx context.Context, g *gomega.GomegaWithT, err error) { + g.Expect(err).ToNot(gomega.HaveOccurred()) + }, + }, + { + Name: "WithValidSpecAndSuccessfulDelete", + SpecFactory: func(g *gomega.GomegaWithT, svc *Service) interface{} { + return &Spec{ + Name: svc.MachinePoolScope.Name(), + } + }, + Setup: func(ctx context.Context, g *gomega.GomegaWithT, svc *Service) { + mockCtrl := gomock.NewController(t) + vmssMock := mock_scalesets.NewMockClient(mockCtrl) + svc.Client = vmssMock + vmssMock.EXPECT().Delete(gomock.Any(), svc.Scope.AzureCluster.Spec.ResourceGroup, svc.MachinePoolScope.Name()).Return(nil) + }, + Expect: func(ctx context.Context, g *gomega.GomegaWithT, err error) { + g.Expect(err).ToNot(gomega.HaveOccurred()) + }, + }, + } + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + t.Parallel() + g := gomega.NewGomegaWithT(t) + svc := getNewService(g) + spec := c.SpecFactory(g, svc) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if c.Setup != nil { + c.Setup(ctx, g, svc) + } + err := svc.Delete(context.Background(), spec) + c.Expect(ctx, g, err) + }) + } +} + +func getNewService(g *gomega.GomegaWithT) *Service { + cluster := &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{Name: "test-cluster"}, + } + client := fake.NewFakeClientWithScheme(scheme.Scheme, cluster) + s, err := scope.NewClusterScope(scope.ClusterScopeParams{ + AzureClients: scope.AzureClients{ + SubscriptionID: "123", + Authorizer: autorest.NullAuthorizer{}, + }, + Client: client, + Cluster: cluster, + AzureCluster: &infrav1.AzureCluster{ + Spec: infrav1.AzureClusterSpec{ + Location: "test-location", + ResourceGroup: "my-rg", + NetworkSpec: infrav1.NetworkSpec{ + Vnet: infrav1.VnetSpec{Name: "my-vnet", ResourceGroup: "my-rg"}, + Subnets: infrav1.Subnets{ + { + ID: "subnet0.id", + }, + }, + }, + }, + }, + }) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + mps, err := scopeExp.NewMachinePoolScope(scopeExp.MachinePoolScopeParams{ + AzureClients: s.AzureClients, + Client: client, + Logger: s.Logger, + Cluster: s.Cluster, + MachinePool: new(clusterv1exp.MachinePool), + AzureCluster: s.AzureCluster, + AzureMachinePool: &infrav1exp.AzureMachinePool{ + ObjectMeta: metav1.ObjectMeta{ + Name: "capz-mp-0", + }, + }, + }) + g.Expect(err).ToNot(gomega.HaveOccurred()) + + return NewService(s, mps) +} diff --git a/exp/controllers/azuremachinepool_controller_test.go b/exp/controllers/azuremachinepool_controller_test.go new file mode 100644 index 00000000000..8a8cc776971 --- /dev/null +++ b/exp/controllers/azuremachinepool_controller_test.go @@ -0,0 +1,53 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1alpha3" +) + +var _ = Describe("AzureMachinePoolReconciler", func() { + BeforeEach(func() {}) + AfterEach(func() {}) + + Context("Reconcile an AzureMachinePool", func() { + It("should not error with minimal set up", func() { + reconciler := &AzureMachinePoolReconciler{ + Client: k8sClient, + Log: log.Log, + } + By("Calling reconcile") + instance := &infrav1exp.AzureMachinePool{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"}} + result, err := reconciler.Reconcile(ctrl.Request{ + NamespacedName: client.ObjectKey{ + Namespace: instance.Namespace, + Name: instance.Name, + }, + }) + Expect(err).To(BeNil()) + Expect(result.RequeueAfter).To(BeZero()) + }) + }) +}) diff --git a/exp/controllers/azuremachinepool_controller_unit_test.go b/exp/controllers/azuremachinepool_controller_unit_test.go new file mode 100644 index 00000000000..8d0303bc7c4 --- /dev/null +++ b/exp/controllers/azuremachinepool_controller_unit_test.go @@ -0,0 +1,308 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" + clusterv1exp "sigs.k8s.io/cluster-api/exp/api/v1alpha3" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha3" + infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1alpha3" + "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers" + "sigs.k8s.io/cluster-api-provider-azure/internal/test/mock_log" +) + +func Test_machinePoolToInfrastructureMapFunc(t *testing.T) { + cases := []struct { + Name string + Setup func(logMock *mock_log.MockLogger) + MapObjectFactory func(*gomega.GomegaWithT) handler.MapObject + Expect func(*gomega.GomegaWithT, []reconcile.Request) + }{ + { + Name: "MachinePoolToAzureMachinePool", + MapObjectFactory: func(g *gomega.GomegaWithT) handler.MapObject { + return handler.MapObject{ + Object: newMachinePoolWithInfrastructureRef("azureCluster", "machinePool"), + } + }, + Expect: func(g *gomega.GomegaWithT, reqs []reconcile.Request) { + g.Expect(reqs).To(gomega.HaveLen(1)) + g.Expect(reqs[0]).To(gomega.Equal(reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "azuremachinePool", + Namespace: "default", + }, + })) + }, + }, + { + Name: "MachinePoolWithoutMatchingInfraRef", + MapObjectFactory: func(g *gomega.GomegaWithT) handler.MapObject { + return handler.MapObject{ + Object: newMachinePool("azureCluster", "machinePool"), + } + }, + Setup: func(logMock *mock_log.MockLogger) { + ampGK := infrav1exp.GroupVersion.WithKind("AzureMachinePool").GroupKind() + logMock.EXPECT().Info("gk does not match", "gk", ampGK, "infraGK", gomock.Any()) + }, + Expect: func(g *gomega.GomegaWithT, reqs []reconcile.Request) { + g.Expect(reqs).To(gomega.HaveLen(0)) + }, + }, + { + Name: "NotAMachinePool", + MapObjectFactory: func(g *gomega.GomegaWithT) handler.MapObject { + return handler.MapObject{ + Object: newCluster("azureCluster"), + } + }, + Setup: func(logMock *mock_log.MockLogger) { + logMock.EXPECT().Info("attempt to map incorrect type", "type", "*v1alpha3.Cluster") + }, + Expect: func(g *gomega.GomegaWithT, reqs []reconcile.Request) { + g.Expect(reqs).To(gomega.HaveLen(0)) + }, + }, + } + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + t.Parallel() + g := gomega.NewGomegaWithT(t) + log := mock_log.NewMockLogger(gomock.NewController(t)) + if c.Setup != nil { + c.Setup(log) + } + f := machinePoolToInfrastructureMapFunc(infrav1exp.GroupVersion.WithKind("AzureMachinePool"), log) + reqs := f(c.MapObjectFactory(g)) + c.Expect(g, reqs) + }) + } +} + +func Test_azureClusterToAzureMachinePoolsFunc(t *testing.T) { + cases := []struct { + Name string + Setup func(*gomega.GomegaWithT, *testing.T) (*mock_log.MockLogger, client.Client) + MapObjectFactory func(*gomega.GomegaWithT) handler.MapObject + Expect func(*gomega.GomegaWithT, []reconcile.Request) + }{ + { + Name: "NotAnAzureCluster", + MapObjectFactory: func(g *gomega.GomegaWithT) handler.MapObject { + return handler.MapObject{ + Object: newMachinePool("foo", "bar"), + } + }, + Setup: func(g *gomega.GomegaWithT, t *testing.T) (*mock_log.MockLogger, client.Client) { + log := mock_log.NewMockLogger(gomock.NewController(t)) + kClient := fake.NewFakeClientWithScheme(newScheme(g)) + log.EXPECT().Error(matchers.ErrStrEq("expected a AzureCluster but got a *v1alpha3.MachinePool"), "failed to get AzureCluster") + return log, kClient + }, + Expect: func(g *gomega.GomegaWithT, reqs []reconcile.Request) { + g.Expect(reqs).To(gomega.HaveLen(0)) + }, + }, + { + Name: "AzureClusterDoesNotExist", + MapObjectFactory: func(g *gomega.GomegaWithT) handler.MapObject { + return handler.MapObject{ + Object: newAzureCluster("foo"), + } + }, + Setup: func(g *gomega.GomegaWithT, t *testing.T) (*mock_log.MockLogger, client.Client) { + log := mock_log.NewMockLogger(gomock.NewController(t)) + logWithValues := mock_log.NewMockLogger(gomock.NewController(t)) + kClient := fake.NewFakeClientWithScheme(newScheme(g)) + log.EXPECT().WithValues("AzureCluster", "azurefoo", "Namespace", "default").Return(logWithValues) + logWithValues.EXPECT().Info("owning cluster not found") + return log, kClient + }, + Expect: func(g *gomega.GomegaWithT, reqs []reconcile.Request) { + g.Expect(reqs).To(gomega.HaveLen(0)) + }, + }, + { + Name: "AzureClusterExistsButDoesNotHaveMachinePools", + MapObjectFactory: func(g *gomega.GomegaWithT) handler.MapObject { + return handler.MapObject{ + Object: newAzureCluster("foo"), + } + }, + Setup: func(g *gomega.GomegaWithT, t *testing.T) (*mock_log.MockLogger, client.Client) { + log := mock_log.NewMockLogger(gomock.NewController(t)) + logWithValues := mock_log.NewMockLogger(gomock.NewController(t)) + const clusterName = "foo" + initObj := []runtime.Object{ + newCluster(clusterName), + newAzureCluster(clusterName), + } + kClient := fake.NewFakeClientWithScheme(newScheme(g), initObj...) + log.EXPECT().WithValues("AzureCluster", "azurefoo", "Namespace", "default").Return(logWithValues) + return log, kClient + }, + Expect: func(g *gomega.GomegaWithT, reqs []reconcile.Request) { + g.Expect(reqs).To(gomega.HaveLen(0)) + }, + }, + { + Name: "AzureClusterExistsWithMachinePoolsButNoInfraRefs", + MapObjectFactory: func(g *gomega.GomegaWithT) handler.MapObject { + return handler.MapObject{ + Object: newAzureCluster("foo"), + } + }, + Setup: func(g *gomega.GomegaWithT, t *testing.T) (*mock_log.MockLogger, client.Client) { + log := mock_log.NewMockLogger(gomock.NewController(t)) + logWithValues := mock_log.NewMockLogger(gomock.NewController(t)) + const clusterName = "foo" + initObj := []runtime.Object{ + newCluster(clusterName), + newAzureCluster(clusterName), + newMachinePool(clusterName, "pool1"), + newMachinePool(clusterName, "pool2"), + } + kClient := fake.NewFakeClientWithScheme(newScheme(g), initObj...) + log.EXPECT().WithValues("AzureCluster", "azurefoo", "Namespace", "default").Return(logWithValues) + return log, kClient + }, + Expect: func(g *gomega.GomegaWithT, reqs []reconcile.Request) { + g.Expect(reqs).To(gomega.HaveLen(0)) + }, + }, + { + Name: "AzureClusterExistsWithMachinePoolsWithOneInfraRefs", + MapObjectFactory: func(g *gomega.GomegaWithT) handler.MapObject { + return handler.MapObject{ + Object: newAzureCluster("foo"), + } + }, + Setup: func(g *gomega.GomegaWithT, t *testing.T) (*mock_log.MockLogger, client.Client) { + log := mock_log.NewMockLogger(gomock.NewController(t)) + logWithValues := mock_log.NewMockLogger(gomock.NewController(t)) + const clusterName = "foo" + initObj := []runtime.Object{ + newCluster(clusterName), + newAzureCluster(clusterName), + newMachinePool(clusterName, "pool1"), + newMachinePoolWithInfrastructureRef(clusterName, "pool2"), + } + kClient := fake.NewFakeClientWithScheme(newScheme(g), initObj...) + log.EXPECT().WithValues("AzureCluster", "azurefoo", "Namespace", "default").Return(logWithValues) + return log, kClient + }, + Expect: func(g *gomega.GomegaWithT, reqs []reconcile.Request) { + g.Expect(reqs).To(gomega.HaveLen(1)) + g.Expect(reqs[0]).To(gomega.Equal(reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "azurepool2", + Namespace: "default", + }, + })) + }, + }, + } + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + t.Parallel() + g := gomega.NewGomegaWithT(t) + log, kClient := c.Setup(g, t) + f := azureClusterToAzureMachinePoolsFunc(kClient, log) + reqs := f(c.MapObjectFactory(g)) + c.Expect(g, reqs) + }) + } +} + +func newScheme(g *gomega.GomegaWithT) *runtime.Scheme { + scheme := runtime.NewScheme() + for _, f := range []func(*runtime.Scheme) error{ + clusterv1.AddToScheme, + clusterv1exp.AddToScheme, + infrav1.AddToScheme, + infrav1exp.AddToScheme, + } { + g.Expect(f(scheme)).ToNot(gomega.HaveOccurred()) + } + return scheme +} + +func newMachinePool(clusterName, poolName string) *clusterv1exp.MachinePool { + return &clusterv1exp.MachinePool{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + clusterv1.ClusterLabelName: clusterName, + }, + Name: poolName, + Namespace: "default", + }, + } +} + +func newMachinePoolWithInfrastructureRef(clusterName, poolName string) *clusterv1exp.MachinePool { + m := newMachinePool(clusterName, poolName) + m.Spec.Template.Spec.InfrastructureRef = v1.ObjectReference{ + Kind: "AzureMachinePool", + Namespace: m.Namespace, + Name: "azure" + poolName, + APIVersion: infrav1exp.GroupVersion.String(), + } + return m +} + +func newAzureCluster(clusterName string) *infrav1.AzureCluster { + return &infrav1.AzureCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "azure" + clusterName, + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: clusterv1.GroupVersion.String(), + Kind: "Cluster", + Name: clusterName, + }, + }, + }, + } +} + +func newCluster(name string) *clusterv1.Cluster { + return &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "default", + }, + } +} diff --git a/exp/controllers/suite_test.go b/exp/controllers/suite_test.go new file mode 100644 index 00000000000..30641a6f7b6 --- /dev/null +++ b/exp/controllers/suite_test.go @@ -0,0 +1,92 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/klog" + "k8s.io/klog/klogr" + clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha3" + infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1alpha3" + // +kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func init() { + klog.InitFlags(nil) + klog.SetOutput(GinkgoWriter) + logf.SetLogger(klogr.New()) +} + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Controller Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func(done Done) { + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "config", "crd", "bases"), + }, + } + + var err error + cfg, err = testEnv.Start() + Expect(err).ToNot(HaveOccurred()) + Expect(cfg).ToNot(BeNil()) + + Expect(clusterv1.AddToScheme(scheme.Scheme)).To(Succeed()) + Expect(infrav1.AddToScheme(scheme.Scheme)).To(Succeed()) + Expect(infrav1exp.AddToScheme(scheme.Scheme)).To(Succeed()) + + // +kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).ToNot(HaveOccurred()) + Expect(k8sClient).ToNot(BeNil()) + + close(done) +}, 60) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).ToNot(HaveOccurred()) +}) diff --git a/internal/test/matchers/matchers.go b/internal/test/matchers/matchers.go new file mode 100644 index 00000000000..a6cd946f4dd --- /dev/null +++ b/internal/test/matchers/matchers.go @@ -0,0 +1,76 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package matchers + +import ( + "fmt" + + "github.com/golang/mock/gomock" + "github.com/google/go-cmp/cmp" +) + +type ( + cmpMatcher struct { + x interface{} + diff string + } + + errStrEq struct { + expected string + actual string + } +) + +// DiffEq will verify cmp.Diff(expected, actual) == "" using github.com/google/go-cmp/cmp +func DiffEq(x interface{}) gomock.Matcher { + return &cmpMatcher{ + x: x, + } +} + +func (c *cmpMatcher) Matches(x interface{}) bool { + c.diff = cmp.Diff(x, c.x) + return c.diff == "" +} + +func (c *cmpMatcher) String() string { + want := fmt.Sprintf("is equal to %v", c.x) + if c.diff != "" { + want = fmt.Sprintf("%s, but difference is %s", want, c.diff) + } + return want +} + +// ErrStrEq will verify the string matches error.Error() +func ErrStrEq(expected string) gomock.Matcher { + return &errStrEq{ + expected: expected, + } +} + +func (e *errStrEq) Matches(y interface{}) bool { + err, ok := y.(error) + if !ok { + return false + } + e.actual = err.Error() + return e.expected == e.actual +} + +func (e *errStrEq) String() string { + return fmt.Sprintf("error.Error() %q, but got %q", e.expected, e.actual) +} diff --git a/internal/test/mock_log/doc.go b/internal/test/mock_log/doc.go new file mode 100644 index 00000000000..f4332233679 --- /dev/null +++ b/internal/test/mock_log/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Run go generate to regenerate this mock. +//go:generate ../../../hack/tools/bin/mockgen -destination log_mock.go -package mock_log github.com/go-logr/logr Logger +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate/boilerplate.generatego.txt log_mock.go > _log_mock.go && mv _log_mock.go log_mock.go" +package mock_log //nolint diff --git a/internal/test/mock_log/log_mock.go b/internal/test/mock_log/log_mock.go new file mode 100644 index 00000000000..b86bf4e1add --- /dev/null +++ b/internal/test/mock_log/log_mock.go @@ -0,0 +1,144 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/go-logr/logr (interfaces: Logger) + +// Package mock_log is a generated GoMock package. +package mock_log + +import ( + logr "github.com/go-logr/logr" + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockLogger is a mock of Logger interface +type MockLogger struct { + ctrl *gomock.Controller + recorder *MockLoggerMockRecorder +} + +// MockLoggerMockRecorder is the mock recorder for MockLogger +type MockLoggerMockRecorder struct { + mock *MockLogger +} + +// NewMockLogger creates a new mock instance +func NewMockLogger(ctrl *gomock.Controller) *MockLogger { + mock := &MockLogger{ctrl: ctrl} + mock.recorder = &MockLoggerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockLogger) EXPECT() *MockLoggerMockRecorder { + return m.recorder +} + +// Enabled mocks base method +func (m *MockLogger) Enabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Enabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// Enabled indicates an expected call of Enabled +func (mr *MockLoggerMockRecorder) Enabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Enabled", reflect.TypeOf((*MockLogger)(nil).Enabled)) +} + +// Error mocks base method +func (m *MockLogger) Error(arg0 error, arg1 string, arg2 ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Error", varargs...) +} + +// Error indicates an expected call of Error +func (mr *MockLoggerMockRecorder) Error(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), varargs...) +} + +// Info mocks base method +func (m *MockLogger) Info(arg0 string, arg1 ...interface{}) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Info", varargs...) +} + +// Info indicates an expected call of Info +func (mr *MockLoggerMockRecorder) Info(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), varargs...) +} + +// V mocks base method +func (m *MockLogger) V(arg0 int) logr.InfoLogger { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "V", arg0) + ret0, _ := ret[0].(logr.InfoLogger) + return ret0 +} + +// V indicates an expected call of V +func (mr *MockLoggerMockRecorder) V(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "V", reflect.TypeOf((*MockLogger)(nil).V), arg0) +} + +// WithName mocks base method +func (m *MockLogger) WithName(arg0 string) logr.Logger { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WithName", arg0) + ret0, _ := ret[0].(logr.Logger) + return ret0 +} + +// WithName indicates an expected call of WithName +func (mr *MockLoggerMockRecorder) WithName(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithName", reflect.TypeOf((*MockLogger)(nil).WithName), arg0) +} + +// WithValues mocks base method +func (m *MockLogger) WithValues(arg0 ...interface{}) logr.Logger { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range arg0 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "WithValues", varargs...) + ret0, _ := ret[0].(logr.Logger) + return ret0 +} + +// WithValues indicates an expected call of WithValues +func (mr *MockLoggerMockRecorder) WithValues(arg0 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithValues", reflect.TypeOf((*MockLogger)(nil).WithValues), arg0...) +}