Skip to content

Commit f8fb3dc

Browse files
committed
Add Version to ROSA Control Plane Status
1 parent 1dfb164 commit f8fb3dc

File tree

6 files changed

+344
-18
lines changed

6 files changed

+344
-18
lines changed

config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,9 @@ spec:
911911
description: Ready denotes that the ROSAControlPlane API Server is
912912
ready to receive requests.
913913
type: boolean
914+
version:
915+
description: OpenShift semantic version, for example "4.14.5".
916+
type: string
914917
required:
915918
- ready
916919
type: object

controlplane/rosa/api/v1beta2/rosacontrolplane_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,10 @@ type RosaControlPlaneStatus struct {
749749
// OIDCEndpointURL is the endpoint url for the managed OIDC provider.
750750
OIDCEndpointURL string `json:"oidcEndpointURL,omitempty"`
751751

752+
// OpenShift semantic version, for example "4.14.5".
753+
// +optional
754+
Version string `json:"version"`
755+
752756
// Available upgrades for the ROSA hosted control plane.
753757
AvailableUpgrades []string `json:"availableUpgrades,omitempty"`
754758
}

controlplane/rosa/controllers/rosacontrolplane_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc
252252
rosaScope.ControlPlane.Status.ConsoleURL = cluster.Console().URL()
253253
rosaScope.ControlPlane.Status.OIDCEndpointURL = cluster.AWS().STS().OIDCEndpointURL()
254254
rosaScope.ControlPlane.Status.Ready = false
255+
rosaScope.ControlPlane.Status.Version = rosa.RawVersionID(cluster.Version())
255256

256257
switch cluster.Status().State() {
257258
case cmv1.ClusterStateReady:

controlplane/rosa/controllers/rosacontrolplane_controller_test.go

Lines changed: 240 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,36 @@ limitations under the License.
1818
package controllers
1919

2020
import (
21+
"context"
2122
"testing"
23+
"time"
2224

25+
"github.com/aws/aws-sdk-go/aws"
26+
sts "github.com/aws/aws-sdk-go/service/sts"
27+
"github.com/aws/aws-sdk-go/service/sts/stsiface"
28+
"github.com/golang/mock/gomock"
2329
. "github.com/onsi/gomega"
24-
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
30+
v1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
31+
rosaaws "github.com/openshift/rosa/pkg/aws"
2532
"github.com/openshift/rosa/pkg/ocm"
33+
corev1 "k8s.io/api/core/v1"
34+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35+
"k8s.io/apimachinery/pkg/runtime"
36+
"k8s.io/apimachinery/pkg/types"
37+
ctrl "sigs.k8s.io/controller-runtime"
38+
"sigs.k8s.io/controller-runtime/pkg/client"
2639

40+
infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
2741
rosacontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/rosa/api/v1beta2"
42+
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud"
43+
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope"
44+
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/s3/mock_stsiface"
45+
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger"
46+
"sigs.k8s.io/cluster-api-provider-aws/v2/pkg/rosa"
47+
"sigs.k8s.io/cluster-api-provider-aws/v2/test/mocks"
48+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
49+
"sigs.k8s.io/cluster-api/util/conditions"
50+
"sigs.k8s.io/cluster-api/util/patch"
2851
)
2952

3053
func TestUpdateOCMClusterSpec(t *testing.T) {
@@ -45,12 +68,12 @@ func TestUpdateOCMClusterSpec(t *testing.T) {
4568
}
4669

4770
// Mock Cluster input
48-
mockCluster, _ := cmv1.NewCluster().
49-
AWS(cmv1.NewAWS().
50-
AuditLog(cmv1.NewAuditLog().RoleArn("arn:aws:iam::123456789012:role/AuditLogRole"))).
51-
RegistryConfig(cmv1.NewClusterRegistryConfig().
71+
mockCluster, _ := v1.NewCluster().
72+
AWS(v1.NewAWS().
73+
AuditLog(v1.NewAuditLog().RoleArn("arn:aws:iam::123456789012:role/AuditLogRole"))).
74+
RegistryConfig(v1.NewClusterRegistryConfig().
5275
AdditionalTrustedCa(map[string]string{"trusted-ca": "-----BEGIN CERTIFICATE----- testcert -----END CERTIFICATE-----"}).
53-
AllowedRegistriesForImport(cmv1.NewRegistryLocation().
76+
AllowedRegistriesForImport(v1.NewRegistryLocation().
5477
DomainName("registry1.com").
5578
Insecure(false))).Build()
5679

@@ -71,9 +94,9 @@ func TestUpdateOCMClusterSpec(t *testing.T) {
7194
},
7295
}
7396

74-
mockCluster, _ := cmv1.NewCluster().
75-
AWS(cmv1.NewAWS().
76-
AuditLog(cmv1.NewAuditLog().RoleArn("arn:aws:iam::123456789012:role/OldAuditLogRole"))).Build()
97+
mockCluster, _ := v1.NewCluster().
98+
AWS(v1.NewAWS().
99+
AuditLog(v1.NewAuditLog().RoleArn("arn:aws:iam::123456789012:role/OldAuditLogRole"))).Build()
77100

78101
expectedOCMSpec := ocm.Spec{
79102
AuditLogRoleARN: &rosaControlPlane.Spec.AuditLogRoleARN,
@@ -102,12 +125,12 @@ func TestUpdateOCMClusterSpec(t *testing.T) {
102125
},
103126
}
104127

105-
mockCluster, _ := cmv1.NewCluster().
106-
RegistryConfig(cmv1.NewClusterRegistryConfig().
128+
mockCluster, _ := v1.NewCluster().
129+
RegistryConfig(v1.NewClusterRegistryConfig().
107130
AdditionalTrustedCa(map[string]string{"old-trusted-ca": "-----BEGIN CERTIFICATE----- testcert -----END CERTIFICATE-----"}).
108-
AllowedRegistriesForImport(cmv1.NewRegistryLocation().
131+
AllowedRegistriesForImport(v1.NewRegistryLocation().
109132
DomainName("old-registry.com").
110-
Insecure(false)).RegistrySources(cmv1.NewRegistrySources().BlockedRegistries([]string{"blocked.io", "blocked.org"}...))).
133+
Insecure(false)).RegistrySources(v1.NewRegistrySources().BlockedRegistries([]string{"blocked.io", "blocked.org"}...))).
111134
Build()
112135

113136
expectedOCMSpec := ocm.Spec{
@@ -132,9 +155,9 @@ func TestUpdateOCMClusterSpec(t *testing.T) {
132155
},
133156
}
134157

135-
mockCluster, _ := cmv1.NewCluster().
136-
RegistryConfig(cmv1.NewClusterRegistryConfig().
137-
AllowedRegistriesForImport(cmv1.NewRegistryLocation().
158+
mockCluster, _ := v1.NewCluster().
159+
RegistryConfig(v1.NewClusterRegistryConfig().
160+
AllowedRegistriesForImport(v1.NewRegistryLocation().
138161
DomainName("old-registry.com").
139162
Insecure(false))).
140163
Build()
@@ -150,3 +173,204 @@ func TestUpdateOCMClusterSpec(t *testing.T) {
150173
g.Expect(ocmSpec).To(Equal(expectedOCMSpec))
151174
})
152175
}
176+
177+
func TestRosaControlPlaneReconcileStatusVersion(t *testing.T) {
178+
g := NewWithT(t)
179+
ns, err := testEnv.CreateNamespace(ctx, "test-namespace")
180+
g.Expect(err).ToNot(HaveOccurred())
181+
182+
secret := &corev1.Secret{
183+
ObjectMeta: metav1.ObjectMeta{
184+
Name: "rosa-secret",
185+
Namespace: ns.Name,
186+
},
187+
Data: map[string][]byte{
188+
"ocmToken": []byte("secret-ocm-token-string"),
189+
},
190+
}
191+
identity := &infrav1.AWSClusterControllerIdentity{
192+
ObjectMeta: metav1.ObjectMeta{
193+
Name: "default",
194+
},
195+
Spec: infrav1.AWSClusterControllerIdentitySpec{
196+
AWSClusterIdentitySpec: infrav1.AWSClusterIdentitySpec{
197+
AllowedNamespaces: &infrav1.AllowedNamespaces{},
198+
},
199+
},
200+
}
201+
identity.SetGroupVersionKind(infrav1.GroupVersion.WithKind("AWSClusterStaticIdentity"))
202+
203+
rosaControlPlane := &rosacontrolplanev1.ROSAControlPlane{
204+
ObjectMeta: metav1.ObjectMeta{
205+
Name: "rosa-control-plane-1",
206+
Namespace: ns.Name,
207+
UID: types.UID("rosa-control-plane-1")},
208+
TypeMeta: metav1.TypeMeta{
209+
Kind: "ROSAControlPlane",
210+
APIVersion: rosacontrolplanev1.GroupVersion.String(),
211+
},
212+
Spec: rosacontrolplanev1.RosaControlPlaneSpec{
213+
RosaClusterName: "rosa-control-plane-1",
214+
Subnets: []string{"subnet-0ac99a6230b408813", "subnet-1ac99a6230b408811"},
215+
AvailabilityZones: []string{"az-1", "az-2"},
216+
Network: &rosacontrolplanev1.NetworkSpec{
217+
MachineCIDR: "10.0.0.0/16",
218+
PodCIDR: "10.128.0.0/14",
219+
ServiceCIDR: "172.30.0.0/16",
220+
},
221+
Region: "us-east-1",
222+
Version: "4.15.20",
223+
ChannelGroup: "stable",
224+
RolesRef: rosacontrolplanev1.AWSRolesRef{},
225+
OIDCID: "iodcid1",
226+
InstallerRoleARN: "arn1",
227+
WorkerRoleARN: "arn2",
228+
SupportRoleARN: "arn3",
229+
CredentialsSecretRef: &corev1.LocalObjectReference{
230+
Name: secret.Name,
231+
},
232+
VersionGate: "Acknowledge",
233+
IdentityRef: &infrav1.AWSIdentityReference{
234+
Name: identity.Name,
235+
Kind: infrav1.ControllerIdentityKind,
236+
},
237+
},
238+
Status: rosacontrolplanev1.RosaControlPlaneStatus{
239+
Ready: true,
240+
ID: "rosa-control-plane-1",
241+
// Version: "4.15.20",
242+
Conditions: clusterv1.Conditions{clusterv1.Condition{
243+
Type: "Paused",
244+
Status: "False",
245+
Severity: "",
246+
Reason: "NotPaused",
247+
Message: "",
248+
}},
249+
},
250+
}
251+
252+
ownerCluster := &clusterv1.Cluster{
253+
ObjectMeta: metav1.ObjectMeta{
254+
Name: "owner-cluster-1",
255+
Namespace: ns.Name,
256+
UID: types.UID("owner-cluster-1"),
257+
},
258+
Spec: clusterv1.ClusterSpec{
259+
ControlPlaneRef: &corev1.ObjectReference{
260+
Name: rosaControlPlane.Name,
261+
Kind: "ROSAControlPlane",
262+
APIVersion: rosacontrolplanev1.GroupVersion.String(),
263+
},
264+
},
265+
}
266+
267+
rosaControlPlane.OwnerReferences = []metav1.OwnerReference{
268+
{
269+
Name: ownerCluster.Name,
270+
UID: ownerCluster.UID,
271+
Kind: "Cluster",
272+
APIVersion: clusterv1.GroupVersion.String(),
273+
},
274+
}
275+
276+
mockCtrl := gomock.NewController(t)
277+
ctx := context.TODO()
278+
ocmMock := mocks.NewMockOCMClient(mockCtrl)
279+
stsMock := mock_stsiface.NewMockSTSAPI(mockCtrl)
280+
281+
getCallerIdentityResult := &sts.GetCallerIdentityOutput{Account: aws.String("foo"), Arn: aws.String("arn:aws:iam::123456789012:rosa/foo")}
282+
stsMock.EXPECT().GetCallerIdentity(gomock.Any()).Return(getCallerIdentityResult, nil).Times(1)
283+
284+
expect := func(m *mocks.MockOCMClientMockRecorder) {
285+
m.ValidateHypershiftVersion(gomock.Any(), gomock.Any()).DoAndReturn(func(clusterId string, nodePoolID string) (bool, error) {
286+
return true, nil
287+
}).Times(1)
288+
m.GetCluster(gomock.Any(), gomock.Any()).DoAndReturn(func(clusterKey string, creator *rosaaws.Creator) (*v1.Cluster, error) {
289+
sts := (&v1.STSBuilder{}).OIDCEndpointURL("oidc.com/oidc1")
290+
aws := v1.NewAWS().AuditLog(v1.NewAuditLog().RoleArn("arn:aws:iam::123456789012:role/AuditLogRole")).STS(sts)
291+
console := (&v1.ClusterConsoleBuilder{}).URL("console.redhat.com/cluster-123")
292+
status := (&v1.ClusterStatusBuilder{}).State(v1.ClusterStateError)
293+
version := (&v1.VersionBuilder{}).RawID(rosaControlPlane.Spec.Version)
294+
mockCluster, _ := v1.NewCluster().AWS(aws).ID("cluster-1").Version(version).Status(status).Console(console).
295+
RegistryConfig(v1.NewClusterRegistryConfig().
296+
AdditionalTrustedCa(map[string]string{"trusted-ca": "-----BEGIN CERTIFICATE----- testcert -----END CERTIFICATE-----"}).
297+
AllowedRegistriesForImport(v1.NewRegistryLocation().
298+
DomainName("registry1.com").
299+
Insecure(false))).
300+
Build()
301+
return mockCluster, nil
302+
}).Times(1)
303+
}
304+
305+
expect(ocmMock.EXPECT())
306+
307+
objects := []client.Object{ownerCluster, rosaControlPlane, secret, identity}
308+
for _, obj := range objects {
309+
createObject(g, obj, ns.Name)
310+
}
311+
312+
// Add conditions, can't do this duirng creation
313+
cpPh, err := patch.NewHelper(rosaControlPlane, testEnv)
314+
g.Expect(err).ShouldNot(HaveOccurred())
315+
rosaControlPlane.Status = rosacontrolplanev1.RosaControlPlaneStatus{
316+
Ready: true,
317+
ID: "rosa-control-plane-1",
318+
Version: "4.15.1",
319+
Conditions: clusterv1.Conditions{clusterv1.Condition{
320+
Type: "Paused",
321+
Status: "False",
322+
Severity: "",
323+
Reason: "NotPaused",
324+
Message: "",
325+
}},
326+
}
327+
328+
g.Expect(cpPh.Patch(ctx, rosaControlPlane)).To(Succeed())
329+
330+
time.Sleep(50 * time.Millisecond)
331+
332+
cp := &rosacontrolplanev1.ROSAControlPlane{}
333+
key := client.ObjectKey{Name: rosaControlPlane.Name, Namespace: rosaControlPlane.Namespace}
334+
errGet := testEnv.Get(ctx, key, cp)
335+
g.Expect(errGet).NotTo(HaveOccurred())
336+
oldCondition := conditions.Get(cp, clusterv1.PausedV1Beta2Condition)
337+
g.Expect(oldCondition).NotTo(BeNil())
338+
339+
r := ROSAControlPlaneReconciler{
340+
WatchFilterValue: "",
341+
Endpoints: []scope.ServiceEndpoint{},
342+
Client: testEnv,
343+
NewStsClient: func(cloud.ScopeUsage, cloud.Session, logger.Wrapper, runtime.Object) stsiface.STSAPI { return stsMock },
344+
NewOCMClient: func(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (rosa.OCMClient, error) {
345+
return ocmMock, nil
346+
},
347+
}
348+
349+
req := ctrl.Request{}
350+
req.NamespacedName = types.NamespacedName{Name: rosaControlPlane.Name, Namespace: rosaControlPlane.Namespace}
351+
_, errReconcile := r.Reconcile(ctx, req)
352+
g.Expect(errReconcile).ToNot(HaveOccurred())
353+
time.Sleep(50 * time.Millisecond)
354+
355+
errGet2 := testEnv.Get(ctx, key, cp)
356+
g.Expect(errGet2).NotTo(HaveOccurred())
357+
g.Expect(cp.Status.Version).To(Equal("4.15.20"))
358+
359+
// cleanup
360+
for _, obj := range objects {
361+
cleanupObject(g, obj)
362+
}
363+
}
364+
365+
func createObject(g *WithT, obj client.Object, namespace string) {
366+
if obj.DeepCopyObject() != nil {
367+
obj.SetNamespace(namespace)
368+
g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
369+
}
370+
}
371+
372+
func cleanupObject(g *WithT, obj client.Object) {
373+
if obj.DeepCopyObject() != nil {
374+
g.Expect(testEnv.Cleanup(ctx, obj)).To(Succeed())
375+
}
376+
}

0 commit comments

Comments
 (0)