sigs.k8s.io/cluster-api-provider-azure@v1.14.3/controllers/azurejson_machinepool_controller_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package controllers
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  
    24  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
    25  	"github.com/google/go-cmp/cmp"
    26  	. "github.com/onsi/gomega"
    27  	"go.uber.org/mock/gomock"
    28  	corev1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/types"
    32  	"k8s.io/client-go/tools/record"
    33  	"k8s.io/utils/ptr"
    34  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    35  	"sigs.k8s.io/cluster-api-provider-azure/azure"
    36  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/identities"
    37  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/identities/mock_identities"
    38  	infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1"
    39  	"sigs.k8s.io/cluster-api-provider-azure/util/reconciler"
    40  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    41  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    42  	ctrl "sigs.k8s.io/controller-runtime"
    43  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    44  )
    45  
    46  func TestAzureJSONPoolReconciler(t *testing.T) {
    47  	scheme, err := newScheme()
    48  	if err != nil {
    49  		t.Error(err)
    50  	}
    51  
    52  	cluster := &clusterv1.Cluster{
    53  		ObjectMeta: metav1.ObjectMeta{
    54  			Name: "my-cluster",
    55  		},
    56  		Spec: clusterv1.ClusterSpec{
    57  			InfrastructureRef: &corev1.ObjectReference{
    58  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
    59  				Kind:       infrav1.AzureClusterKind,
    60  				Name:       "my-azure-cluster",
    61  			},
    62  		},
    63  	}
    64  
    65  	azureCluster := &infrav1.AzureCluster{
    66  		ObjectMeta: metav1.ObjectMeta{
    67  			Name: "my-azure-cluster",
    68  			OwnerReferences: []metav1.OwnerReference{
    69  				{
    70  					APIVersion: "cluster.x-k8s.io/v1beta1",
    71  					Kind:       "Cluster",
    72  					Name:       "my-cluster",
    73  				},
    74  			},
    75  		},
    76  		Spec: infrav1.AzureClusterSpec{
    77  			AzureClusterClassSpec: infrav1.AzureClusterClassSpec{
    78  				SubscriptionID: "123",
    79  				IdentityRef: &corev1.ObjectReference{
    80  					Name:      "fake-identity",
    81  					Namespace: "default",
    82  					Kind:      "AzureClusterIdentity",
    83  				},
    84  			},
    85  			NetworkSpec: infrav1.NetworkSpec{
    86  				Subnets: infrav1.Subnets{
    87  					{
    88  						SubnetClassSpec: infrav1.SubnetClassSpec{
    89  							Name: "node",
    90  							Role: infrav1.SubnetNode,
    91  						},
    92  					},
    93  				},
    94  			},
    95  		},
    96  	}
    97  
    98  	machinePool := &expv1.MachinePool{
    99  		ObjectMeta: metav1.ObjectMeta{
   100  			Name: "my-machine-pool",
   101  			Labels: map[string]string{
   102  				clusterv1.ClusterNameLabel: "my-cluster",
   103  			},
   104  			OwnerReferences: []metav1.OwnerReference{
   105  				{
   106  					APIVersion: "cluster.x-k8s.io/v1beta1",
   107  					Kind:       "Cluster",
   108  					Name:       "my-cluster",
   109  				},
   110  			},
   111  		},
   112  	}
   113  
   114  	azureMachinePool := &infrav1exp.AzureMachinePool{
   115  		ObjectMeta: metav1.ObjectMeta{
   116  			Name: "my-azure-machine-pool",
   117  			OwnerReferences: []metav1.OwnerReference{
   118  				{
   119  					APIVersion: "cluster.x-k8s.io/v1beta1",
   120  					Kind:       "Cluster",
   121  					Name:       "my-cluster",
   122  				},
   123  				{
   124  					APIVersion: "cluster.x-k8s.io/v1beta1",
   125  					Kind:       "MachinePool",
   126  					Name:       "my-machine-pool",
   127  				},
   128  			},
   129  		},
   130  	}
   131  
   132  	fakeIdentity := &infrav1.AzureClusterIdentity{
   133  		ObjectMeta: metav1.ObjectMeta{
   134  			Name:      "fake-identity",
   135  			Namespace: "default",
   136  		},
   137  		Spec: infrav1.AzureClusterIdentitySpec{
   138  			Type:     infrav1.ServicePrincipal,
   139  			TenantID: "fake-tenantid",
   140  		},
   141  	}
   142  	fakeSecret := &corev1.Secret{Data: map[string][]byte{"clientSecret": []byte("fooSecret")}}
   143  
   144  	cases := map[string]struct {
   145  		objects []runtime.Object
   146  		fail    bool
   147  		err     string
   148  	}{
   149  		"should reconcile normally": {
   150  			objects: []runtime.Object{
   151  				cluster,
   152  				azureCluster,
   153  				machinePool,
   154  				azureMachinePool,
   155  				fakeIdentity,
   156  				fakeSecret,
   157  			},
   158  		},
   159  		"missing azure cluster should return error": {
   160  			objects: []runtime.Object{
   161  				cluster,
   162  				machinePool,
   163  				azureMachinePool,
   164  				fakeIdentity,
   165  				fakeSecret,
   166  			},
   167  			fail: true,
   168  			err:  "failed to create cluster scope for cluster /my-cluster: azureclusters.infrastructure.cluster.x-k8s.io \"my-azure-cluster\" not found",
   169  		},
   170  		"infra ref is nil": {
   171  			objects: []runtime.Object{
   172  				&clusterv1.Cluster{
   173  					ObjectMeta: metav1.ObjectMeta{
   174  						Name: "my-cluster",
   175  					},
   176  					Spec: clusterv1.ClusterSpec{
   177  						InfrastructureRef: nil,
   178  					},
   179  				},
   180  				azureCluster,
   181  				machinePool,
   182  				azureMachinePool,
   183  				fakeIdentity,
   184  				fakeSecret,
   185  			},
   186  			fail: false,
   187  		},
   188  		"infra ref is not an azure cluster": {
   189  			objects: []runtime.Object{
   190  				&clusterv1.Cluster{
   191  					ObjectMeta: metav1.ObjectMeta{
   192  						Name: "my-cluster",
   193  					},
   194  					Spec: clusterv1.ClusterSpec{
   195  						InfrastructureRef: &corev1.ObjectReference{
   196  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
   197  							Kind:       "FooCluster",
   198  							Name:       "my-foo-cluster",
   199  						},
   200  					},
   201  				},
   202  				azureCluster,
   203  				machinePool,
   204  				azureMachinePool,
   205  				fakeIdentity,
   206  				fakeSecret,
   207  			},
   208  			fail: true,
   209  			err:  "failed to create cluster scope for cluster /my-cluster: unsupported infrastructure type \"FooCluster\", should be AzureCluster or AzureManagedCluster",
   210  		},
   211  	}
   212  
   213  	t.Setenv("AZURE_CLIENT_ID", "fooClient")
   214  	t.Setenv("AZURE_CLIENT_SECRET", "fooSecret")
   215  	t.Setenv("AZURE_TENANT_ID", "fooTenant")
   216  
   217  	for name, tc := range cases {
   218  		t.Run(name, func(t *testing.T) {
   219  			client := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(tc.objects...).Build()
   220  
   221  			reconciler := &AzureJSONMachinePoolReconciler{
   222  				Client:   client,
   223  				Recorder: record.NewFakeRecorder(128),
   224  			}
   225  
   226  			_, err := reconciler.Reconcile(context.Background(), ctrl.Request{
   227  				NamespacedName: types.NamespacedName{
   228  					Namespace: "",
   229  					Name:      "my-azure-machine-pool",
   230  				},
   231  			})
   232  
   233  			if tc.fail {
   234  				if diff := cmp.Diff(tc.err, err.Error()); diff != "" {
   235  					t.Error(diff)
   236  				}
   237  			} else {
   238  				if err != nil {
   239  					t.Errorf("expected success, but got error: %s", err.Error())
   240  				}
   241  			}
   242  		})
   243  	}
   244  }
   245  
   246  func TestAzureJSONPoolReconcilerUserAssignedIdentities(t *testing.T) {
   247  	g := NewWithT(t)
   248  	ctrlr := gomock.NewController(t)
   249  	defer ctrlr.Finish()
   250  	req := ctrl.Request{NamespacedName: types.NamespacedName{Name: "fake-machine-pool", Namespace: "fake-ns"}}
   251  	ctx := context.Background()
   252  	scheme, err := newScheme()
   253  	g.Expect(err).NotTo(HaveOccurred())
   254  
   255  	azureMP := &infrav1exp.AzureMachinePool{
   256  		ObjectMeta: metav1.ObjectMeta{
   257  			Name:      "fake-machine-pool",
   258  			Namespace: "fake-ns",
   259  			Labels: map[string]string{
   260  				clusterv1.ClusterNameLabel: "fake-cluster",
   261  			},
   262  			OwnerReferences: []metav1.OwnerReference{
   263  				{
   264  					APIVersion: fmt.Sprintf("%s/%s", expv1.GroupVersion.Group, expv1.GroupVersion.Version),
   265  					Kind:       "MachinePool",
   266  					Name:       "fake-other-machine-pool",
   267  					Controller: to.Ptr(true),
   268  				},
   269  			},
   270  		},
   271  		Spec: infrav1exp.AzureMachinePoolSpec{
   272  			UserAssignedIdentities: []infrav1.UserAssignedIdentity{
   273  				{
   274  					ProviderID: "azure:///subscriptions/123/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/fake-provider-id",
   275  				},
   276  			},
   277  		},
   278  	}
   279  
   280  	cluster := &clusterv1.Cluster{
   281  		ObjectMeta: metav1.ObjectMeta{
   282  			Name:      "fake-cluster",
   283  			Namespace: "fake-ns",
   284  		},
   285  		Spec: clusterv1.ClusterSpec{
   286  			InfrastructureRef: &corev1.ObjectReference{
   287  				Kind:      "AzureCluster",
   288  				Name:      "fake-azure-cluster",
   289  				Namespace: "fake-ns",
   290  			},
   291  		},
   292  	}
   293  
   294  	ownerMP := &expv1.MachinePool{
   295  		ObjectMeta: metav1.ObjectMeta{
   296  			Name:      "fake-other-machine-pool",
   297  			Namespace: "fake-ns",
   298  			Labels: map[string]string{
   299  				clusterv1.ClusterNameLabel: "fake-cluster",
   300  			},
   301  		},
   302  	}
   303  
   304  	azureCluster := &infrav1.AzureCluster{
   305  		ObjectMeta: metav1.ObjectMeta{
   306  			Name:      "fake-azure-cluster",
   307  			Namespace: "fake-ns",
   308  			OwnerReferences: []metav1.OwnerReference{
   309  				{
   310  					APIVersion: "cluster.x-k8s.io/v1beta1",
   311  					Kind:       "Cluster",
   312  					Name:       "my-cluster",
   313  				},
   314  			},
   315  		},
   316  		Spec: infrav1.AzureClusterSpec{
   317  			AzureClusterClassSpec: infrav1.AzureClusterClassSpec{
   318  				SubscriptionID: "123",
   319  				IdentityRef: &corev1.ObjectReference{
   320  					Name:      "fake-identity",
   321  					Namespace: "default",
   322  					Kind:      "AzureClusterIdentity",
   323  				},
   324  			},
   325  			NetworkSpec: infrav1.NetworkSpec{
   326  				Subnets: infrav1.Subnets{
   327  					{
   328  						SubnetClassSpec: infrav1.SubnetClassSpec{
   329  							Name: "node",
   330  							Role: infrav1.SubnetNode,
   331  						},
   332  					},
   333  				},
   334  			},
   335  		},
   336  	}
   337  	apiVersion, kind := infrav1.GroupVersion.WithKind("AzureMachinePool").ToAPIVersionAndKind()
   338  
   339  	fakeIdentity := &infrav1.AzureClusterIdentity{
   340  		ObjectMeta: metav1.ObjectMeta{
   341  			Name:      "fake-identity",
   342  			Namespace: "default",
   343  		},
   344  		Spec: infrav1.AzureClusterIdentitySpec{
   345  			Type: infrav1.ServicePrincipal,
   346  			ClientSecret: corev1.SecretReference{
   347  				Name:      azureMP.Name,
   348  				Namespace: "fake-ns",
   349  			},
   350  			TenantID: "fake-tenantid",
   351  		},
   352  	}
   353  
   354  	sec := &corev1.Secret{
   355  		ObjectMeta: metav1.ObjectMeta{
   356  			Name:      azureMP.Name,
   357  			Namespace: "fake-ns",
   358  			Labels: map[string]string{
   359  				"fake-cluster": string(infrav1.ResourceLifecycleOwned),
   360  			},
   361  			OwnerReferences: []metav1.OwnerReference{
   362  				{
   363  					APIVersion: apiVersion,
   364  					Kind:       kind,
   365  					Name:       azureMP.GetName(),
   366  					Controller: ptr.To(true),
   367  				},
   368  			},
   369  		},
   370  		Data: map[string][]byte{
   371  			"clientSecret": []byte("fooSecret"),
   372  		},
   373  	}
   374  
   375  	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(azureMP, ownerMP, cluster, azureCluster, sec, fakeIdentity).Build()
   376  	rec := AzureJSONMachinePoolReconciler{
   377  		Client:   client,
   378  		Recorder: record.NewFakeRecorder(42),
   379  		Timeouts: reconciler.Timeouts{},
   380  	}
   381  	id := "azure:///subscriptions/123/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/fake-provider-id"
   382  	getClient = func(auth azure.Authorizer) (identities.Client, error) {
   383  		mockClient := mock_identities.NewMockClient(ctrlr)
   384  		mockClient.EXPECT().GetClientID(gomock.Any(), gomock.Any()).Return(id, nil)
   385  		return mockClient, nil
   386  	}
   387  
   388  	_, err = rec.Reconcile(ctx, req)
   389  	g.Expect(err).NotTo(HaveOccurred())
   390  }