Skip to content

Commit 18d8da3

Browse files
committed
ROSANetwork: tests
1 parent 56760b6 commit 18d8da3

File tree

3 files changed

+414
-1
lines changed

3 files changed

+414
-1
lines changed
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
/*
2+
Copyright The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package controllers
15+
16+
import (
17+
"context"
18+
"fmt"
19+
"testing"
20+
"time"
21+
22+
awsSdk "github.com/aws/aws-sdk-go-v2/aws"
23+
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
24+
cloudformationtypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
25+
"github.com/aws/aws-sdk-go-v2/service/ec2"
26+
ec2Types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
27+
stsv2 "github.com/aws/aws-sdk-go-v2/service/sts"
28+
. "github.com/onsi/gomega"
29+
rosaAWSClient "github.com/openshift/rosa/pkg/aws"
30+
rosaMocks "github.com/openshift/rosa/pkg/aws/mocks"
31+
"github.com/sirupsen/logrus"
32+
gomock "go.uber.org/mock/gomock"
33+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34+
"k8s.io/apimachinery/pkg/types"
35+
ctrl "sigs.k8s.io/controller-runtime"
36+
37+
infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
38+
expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2"
39+
)
40+
41+
func TestROSANetworkReconciler_Reconcile(t *testing.T) {
42+
g := NewWithT(t)
43+
ns, err := testEnv.CreateNamespace(ctx, "test-namespace")
44+
g.Expect(err).ToNot(HaveOccurred())
45+
46+
mockCtrl := gomock.NewController(t)
47+
ctx := context.TODO()
48+
49+
identity := &infrav1.AWSClusterControllerIdentity{
50+
ObjectMeta: metav1.ObjectMeta{
51+
Name: "default",
52+
},
53+
Spec: infrav1.AWSClusterControllerIdentitySpec{
54+
AWSClusterIdentitySpec: infrav1.AWSClusterIdentitySpec{
55+
AllowedNamespaces: &infrav1.AllowedNamespaces{},
56+
},
57+
},
58+
}
59+
identity.SetGroupVersionKind(infrav1.GroupVersion.WithKind("AWSClusterControllerIdentity"))
60+
61+
rosaNetwork := &expinfrav1.ROSANetwork{
62+
ObjectMeta: metav1.ObjectMeta{
63+
Name: "test-rosa-network",
64+
Namespace: ns.Name},
65+
Spec: expinfrav1.ROSANetworkSpec{
66+
StackName: "test-rosa-network",
67+
CIDRBlock: "10.0.0.0/8",
68+
AvailabilityZoneCount: 1,
69+
Region: "test-region",
70+
IdentityRef: &infrav1.AWSIdentityReference{
71+
Name: identity.Name,
72+
Kind: infrav1.ControllerIdentityKind,
73+
},
74+
},
75+
}
76+
77+
t.Run("Empty result when ROSANetwork object not found", func(t *testing.T) {
78+
_, _, _, reconciler := createMocks(mockCtrl)
79+
80+
req := ctrl.Request{}
81+
req.NamespacedName = types.NamespacedName{Name: rosaNetwork.Name, Namespace: rosaNetwork.Namespace}
82+
reqReconcile, errReconcile := reconciler.Reconcile(ctx, req)
83+
84+
g.Expect(reqReconcile.Requeue).To(BeFalse())
85+
g.Expect(reqReconcile.RequeueAfter).To(Equal(time.Duration(0)))
86+
g.Expect(errReconcile).ToNot(HaveOccurred())
87+
})
88+
89+
t.Run("Error result when CF stack GET returns error", func(t *testing.T) {
90+
_, mockCFClient, mockSTSClient, reconciler := createMocks(mockCtrl)
91+
92+
createObject(g, rosaNetwork, ns.Name)
93+
defer cleanupObject(g, rosaNetwork)
94+
createObject(g, identity, ns.Name)
95+
defer cleanupObject(g, identity)
96+
97+
describeStacksOutput := &cloudformation.DescribeStacksOutput{}
98+
clientErr := fmt.Errorf("test-error")
99+
100+
mockCFClient.EXPECT().DescribeStacks(gomock.Any(), gomock.Any(), gomock.Any()).
101+
DoAndReturn(func(_ context.Context, _ *cloudformation.DescribeStacksInput, _ ...func(*cloudformation.Options)) (*cloudformation.DescribeStacksOutput, error) {
102+
return describeStacksOutput, clientErr
103+
}).AnyTimes()
104+
105+
getCallerIdentityResult := &stsv2.GetCallerIdentityOutput{Account: awsSdk.String("foo"), Arn: awsSdk.String("arn:aws:iam::123456789012:rosa/foo")}
106+
mockSTSClient.EXPECT().GetCallerIdentity(gomock.Any(), gomock.Any()).Return(getCallerIdentityResult, nil).AnyTimes()
107+
108+
req := ctrl.Request{}
109+
req.NamespacedName = types.NamespacedName{Name: rosaNetwork.Name, Namespace: rosaNetwork.Namespace}
110+
reqReconcile, errReconcile := reconciler.Reconcile(ctx, req)
111+
112+
g.Expect(reqReconcile.Requeue).To(BeFalse())
113+
g.Expect(reqReconcile.RequeueAfter).To(Equal(time.Duration(0)))
114+
g.Expect(errReconcile).To(MatchError(ContainSubstring("error fetching CF stack details:")))
115+
})
116+
}
117+
118+
func TestROSANetworkReconciler_updateROSANetworkResources(t *testing.T) {
119+
g := NewWithT(t)
120+
mockCtrl := gomock.NewController(t)
121+
ctx := context.TODO()
122+
123+
rosaNetwork := &expinfrav1.ROSANetwork{
124+
ObjectMeta: metav1.ObjectMeta{
125+
Name: "test-rosa-network",
126+
Namespace: "test-namespace",
127+
},
128+
Spec: expinfrav1.ROSANetworkSpec{},
129+
Status: expinfrav1.ROSANetworkStatus{},
130+
}
131+
132+
t.Run("Handle cloudformation client error", func(t *testing.T) {
133+
_, mockCFClient, _, reconciler := createMocks(mockCtrl)
134+
135+
describeStackResourcesOutput := &cloudformation.DescribeStackResourcesOutput{}
136+
clientErr := fmt.Errorf("test-error")
137+
138+
mockCFClient.EXPECT().DescribeStackResources(gomock.Any(), gomock.Any(), gomock.Any()).
139+
DoAndReturn(func(_ context.Context, _ *cloudformation.DescribeStackResourcesInput, _ ...func(*cloudformation.Options)) (*cloudformation.DescribeStackResourcesOutput, error) {
140+
return describeStackResourcesOutput, clientErr
141+
}).Times(1)
142+
143+
err := reconciler.updateROSANetworkResources(ctx, rosaNetwork)
144+
g.Expect(err).To(HaveOccurred())
145+
g.Expect(len(rosaNetwork.Status.Resources)).To(Equal(0))
146+
})
147+
148+
t.Run("Update ROSANetwork.Status.Resources", func(t *testing.T) {
149+
_, mockCFClient, _, reconciler := createMocks(mockCtrl)
150+
151+
logicalResourceID := "logical-resource-id"
152+
resourceStatus := cloudformationtypes.ResourceStatusCreateComplete
153+
resourceType := "resource-type"
154+
resourceStatusReason := "resource-status-reason"
155+
physicalResourceID := "physical-resource-id"
156+
157+
describeStackResourcesOutput := &cloudformation.DescribeStackResourcesOutput{
158+
StackResources: []cloudformationtypes.StackResource{
159+
{
160+
LogicalResourceId: &logicalResourceID,
161+
ResourceStatus: resourceStatus,
162+
ResourceType: &resourceType,
163+
ResourceStatusReason: &resourceStatusReason,
164+
PhysicalResourceId: &physicalResourceID,
165+
},
166+
},
167+
}
168+
169+
mockCFClient.EXPECT().DescribeStackResources(gomock.Any(), gomock.Any(), gomock.Any()).
170+
DoAndReturn(func(_ context.Context, _ *cloudformation.DescribeStackResourcesInput, _ ...func(*cloudformation.Options)) (*cloudformation.DescribeStackResourcesOutput, error) {
171+
return describeStackResourcesOutput, nil
172+
}).Times(1)
173+
174+
err := reconciler.updateROSANetworkResources(ctx, rosaNetwork)
175+
g.Expect(err).ToNot(HaveOccurred())
176+
g.Expect(rosaNetwork.Status.Resources[0].LogicalID).To(Equal(logicalResourceID))
177+
g.Expect(rosaNetwork.Status.Resources[0].Status).To(Equal(string(resourceStatus)))
178+
g.Expect(rosaNetwork.Status.Resources[0].ResourceType).To(Equal(resourceType))
179+
g.Expect(rosaNetwork.Status.Resources[0].Reason).To(Equal(resourceStatusReason))
180+
g.Expect(rosaNetwork.Status.Resources[0].PhysicalID).To(Equal(physicalResourceID))
181+
})
182+
}
183+
184+
func TestROSANetworkReconciler_parseSubnets(t *testing.T) {
185+
g := NewWithT(t)
186+
mockCtrl := gomock.NewController(t)
187+
188+
subnet1Id := "subnet1-physical-id"
189+
subnet2Id := "subnet2-physical-id"
190+
191+
rosaNetwork := &expinfrav1.ROSANetwork{
192+
ObjectMeta: metav1.ObjectMeta{
193+
Name: "test-rosa-network",
194+
Namespace: "test-namespace",
195+
},
196+
Spec: expinfrav1.ROSANetworkSpec{},
197+
Status: expinfrav1.ROSANetworkStatus{
198+
Resources: []expinfrav1.CFResource{
199+
{
200+
ResourceType: "AWS::EC2::Subnet",
201+
LogicalID: "SubnetPrivate",
202+
PhysicalID: subnet1Id,
203+
Status: "subnet1-status",
204+
Reason: "subnet1-reason",
205+
},
206+
{
207+
ResourceType: "AWS::EC2::Subnet",
208+
LogicalID: "SubnetPublic",
209+
PhysicalID: subnet2Id,
210+
Status: "subnet2-status",
211+
Reason: "subnet2-reason",
212+
},
213+
{
214+
ResourceType: "bogus-type",
215+
LogicalID: "bogus-logical-id",
216+
PhysicalID: "bugus-physical-id",
217+
Status: "bogus-status",
218+
Reason: "bogus-reason",
219+
},
220+
},
221+
},
222+
}
223+
224+
t.Run("Handle EC2 client error", func(t *testing.T) {
225+
mockEC2Client, _, _, reconciler := createMocks(mockCtrl)
226+
227+
describeSubnetsOutput := &ec2.DescribeSubnetsOutput{}
228+
229+
mockEC2Client.EXPECT().DescribeSubnets(gomock.Any(), gomock.Any(), gomock.Any()).
230+
DoAndReturn(func(_ context.Context, _ *ec2.DescribeSubnetsInput, _ ...func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error) {
231+
return describeSubnetsOutput, fmt.Errorf("test-error")
232+
}).Times(1)
233+
234+
err := reconciler.parseSubnets(rosaNetwork)
235+
g.Expect(err).To(HaveOccurred())
236+
g.Expect(len(rosaNetwork.Status.Subnets)).To(Equal(0))
237+
})
238+
239+
t.Run("Update ROSANetwork.Status.Subnets", func(t *testing.T) {
240+
mockEC2Client, _, _, reconciler := createMocks(mockCtrl)
241+
242+
az := "az01"
243+
244+
describeSubnetsOutput := &ec2.DescribeSubnetsOutput{
245+
Subnets: []ec2Types.Subnet{
246+
{
247+
AvailabilityZone: &az,
248+
},
249+
},
250+
}
251+
252+
mockEC2Client.EXPECT().DescribeSubnets(gomock.Any(), gomock.Any(), gomock.Any()).
253+
DoAndReturn(func(_ context.Context, _ *ec2.DescribeSubnetsInput, _ ...func(*ec2.Options)) (*ec2.DescribeSubnetsOutput, error) {
254+
return describeSubnetsOutput, nil
255+
}).Times(2)
256+
257+
err := reconciler.parseSubnets(rosaNetwork)
258+
g.Expect(err).ToNot(HaveOccurred())
259+
g.Expect(rosaNetwork.Status.Subnets[0].AvailabilityZone).To(Equal(az))
260+
g.Expect(rosaNetwork.Status.Subnets[0].PrivateSubnet).To(Equal(subnet1Id))
261+
g.Expect(rosaNetwork.Status.Subnets[0].PublicSubnet).To(Equal(subnet2Id))
262+
})
263+
}
264+
265+
func createMocks(mockCtrl *gomock.Controller) (*rosaMocks.MockEc2ApiClient, *rosaMocks.MockCloudFormationApiClient, *rosaMocks.MockStsApiClient, *ROSANetworkReconciler) {
266+
mockEC2Client := rosaMocks.NewMockEc2ApiClient(mockCtrl)
267+
mockCFClient := rosaMocks.NewMockCloudFormationApiClient(mockCtrl)
268+
mockSTSClient := rosaMocks.NewMockStsApiClient(mockCtrl)
269+
awsClient := rosaAWSClient.New(
270+
awsSdk.Config{},
271+
rosaAWSClient.NewLoggerWrapper(logrus.New(), nil),
272+
rosaMocks.NewMockIamApiClient(mockCtrl),
273+
mockEC2Client,
274+
rosaMocks.NewMockOrganizationsApiClient(mockCtrl),
275+
rosaMocks.NewMockS3ApiClient(mockCtrl),
276+
rosaMocks.NewMockSecretsManagerApiClient(mockCtrl),
277+
mockSTSClient,
278+
mockCFClient,
279+
rosaMocks.NewMockServiceQuotasApiClient(mockCtrl),
280+
rosaMocks.NewMockServiceQuotasApiClient(mockCtrl),
281+
&rosaAWSClient.AccessKey{},
282+
false,
283+
)
284+
285+
reconciler := &ROSANetworkReconciler{
286+
Client: testEnv.Client,
287+
awsClient: awsClient,
288+
}
289+
290+
return mockEC2Client, mockCFClient, mockSTSClient, reconciler
291+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ require (
4949
github.com/spf13/cobra v1.9.1
5050
github.com/spf13/pflag v1.0.6
5151
github.com/zgalor/weberr v0.8.2
52+
go.uber.org/mock v0.5.2
5253
golang.org/x/crypto v0.36.0
5354
golang.org/x/net v0.38.0
5455
golang.org/x/text v0.23.0
@@ -77,7 +78,6 @@ require (
7778
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
7879
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
7980
github.com/robfig/cron/v3 v3.0.1 // indirect
80-
go.uber.org/mock v0.5.2 // indirect
8181
)
8282

8383
require (

0 commit comments

Comments
 (0)