From 60e79bf34ef18b1244512c4604755ad3a949314d Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Tue, 19 Jul 2022 17:20:55 -0700 Subject: [PATCH 1/3] feat: add ready to join condition to MC --- apis/v1alpha1/membercluster_types.go | 13 ++-- ...t.azure.com_clusterresourceplacements.yaml | 33 ++++++---- .../bases/fleet.azure.com_memberclusters.yaml | 2 - config/rbac/role.yaml | 60 +++++++++++++++++++ .../membercluster/membercluster_controller.go | 49 ++++++++++----- ...mbercluster_controller_integration_test.go | 7 +++ 6 files changed, 130 insertions(+), 34 deletions(-) create mode 100644 config/rbac/role.yaml diff --git a/apis/v1alpha1/membercluster_types.go b/apis/v1alpha1/membercluster_types.go index ffb57a0fb..f5409e45b 100644 --- a/apis/v1alpha1/membercluster_types.go +++ b/apis/v1alpha1/membercluster_types.go @@ -57,16 +57,21 @@ type MemberClusterStatus struct { // Capacity represents the total resources of all the nodes within the member cluster. - // +required - Capacity v1.ResourceList `json:"capacity"` + // +optional + Capacity v1.ResourceList `json:"capacity,omitempty"` // Allocatable represents the total resources of all the nodes within the member cluster that are available for scheduling. - // +required - Allocatable v1.ResourceList `json:"allocatable"` + // +optional + Allocatable v1.ResourceList `json:"allocatable,omitempty"` } const ( + // ConditionTypeMemberClusterReadyToJoin is used to track the readiness of the hub cluster + // controller to accept the new member cluster. + // its conditionStatus can only be "True" == ReadyToJoin + ConditionTypeMemberClusterReadyToJoin string = "ReadyToJoin" + // ConditionTypeMemberClusterJoin is used to track the join state of the memberCluster. // its conditionStatus can be "True" == Joined, "Unknown" == Joining/Leaving, "False" == Left ConditionTypeMemberClusterJoin string = "Joined" diff --git a/config/crd/bases/fleet.azure.com_clusterresourceplacements.yaml b/config/crd/bases/fleet.azure.com_clusterresourceplacements.yaml index e611226a9..dc57f6121 100644 --- a/config/crd/bases/fleet.azure.com_clusterresourceplacements.yaml +++ b/config/crd/bases/fleet.azure.com_clusterresourceplacements.yaml @@ -10,6 +10,8 @@ metadata: spec: group: fleet.azure.com names: + categories: + - fleet-workload kind: ClusterResourcePlacement listKind: ClusterResourcePlacementList plural: clusterresourceplacements @@ -48,11 +50,12 @@ spec: policy: description: Policy represents the placement policy to select clusters to place all the selected resources. Default is place to the entire - fleet if this field is omitted + fleet if this field is omitted. properties: Affinity: description: Affinity represents the selected resources' scheduling - constraints If not set, the entire fleet can be scheduling candidate. + constraints. If not set, the entire fleet can be scheduling + candidate. properties: clusterAffinity: description: ClusterAffinity describes cluster affinity scheduling @@ -60,14 +63,14 @@ spec: properties: clusterSelectorTerms: description: ClusterSelectorTerms is a list of cluster - selector terms. The terms are ORed. kubebuilder:validation:MaxItems=10 + selector terms. The terms are `ORed`. kubebuilder:validation:MaxItems=10 items: description: ClusterSelectorTerm represents the requirements - to selected clusters + to selected clusters. properties: labelSelector: description: LabelSelector is a list of cluster - selector requirements by cluster's labels. kubebuilder:validation:MaxItems=10 + requirements by cluster's labels. kubebuilder:validation:MaxItems=10 properties: matchExpressions: description: matchExpressions is a list of label @@ -132,11 +135,12 @@ spec: type: object resourceSelectors: description: ResourceSelectors is used to select cluster scoped resources. - kubebuilder:validation:MaxItems=100 + The selectors are `ORed`. kubebuilder:validation:MaxItems=100 items: description: 'ClusterResourceSelector is used to specify cluster - scoped resources to be selected. Note: When the resource is of - type `namespace`, ALL the resources in it is selected' + scoped resources to be selected. Note: When the cluster resource + is of type `namespace`, ALL the resources in this namespace are + selected. All the fields present in this structure are `ANDed`.' properties: group: description: Group is the group name of the target resource. @@ -201,7 +205,9 @@ spec: description: Version is the version of the target resource. type: string required: + - group - kind + - version type: object type: array required: @@ -289,11 +295,12 @@ spec: resources status. kubebuilder:validation:MaxItems=1000 items: description: FailedResourcePlacement shows the failure details of - a failed resource placement + a failed resource placement. properties: clusterName: - description: ClusterName is the name of the cluster that - type: integer + description: ClusterName is the name of the cluster that this + resource is placed on. + type: string condition: description: Condition contains the failed condition status for this failed to place resource. @@ -366,7 +373,7 @@ spec: type: string namespace: description: Namespace is the namespace of the resource, the - resource is cluster scoped if the value is empty + resource is cluster scoped if the value is empty. type: string version: description: Version is the version of the selected resource. @@ -395,7 +402,7 @@ spec: type: string namespace: description: Namespace is the namespace of the resource, the - resource is cluster scoped if the value is empty + resource is cluster scoped if the value is empty. type: string version: description: Version is the version of the selected resource. diff --git a/config/crd/bases/fleet.azure.com_memberclusters.yaml b/config/crd/bases/fleet.azure.com_memberclusters.yaml index fcf6a146f..f426e09b1 100644 --- a/config/crd/bases/fleet.azure.com_memberclusters.yaml +++ b/config/crd/bases/fleet.azure.com_memberclusters.yaml @@ -178,8 +178,6 @@ spec: type: object type: array required: - - allocatable - - capacity - conditions type: object required: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml new file mode 100644 index 000000000..89d531577 --- /dev/null +++ b/config/rbac/role.yaml @@ -0,0 +1,60 @@ + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: manager-role +rules: +- apiGroups: + - fleet.azure.com + resources: + - internalmemberclusters + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - fleet.azure.com + resources: + - internalmemberclusters/finalizers + verbs: + - update +- apiGroups: + - fleet.azure.com + resources: + - internalmemberclusters/status + verbs: + - get + - patch + - update +- apiGroups: + - fleet.azure.com + resources: + - memberclusters + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - fleet.azure.com + resources: + - memberclusters/finalizers + verbs: + - update +- apiGroups: + - fleet.azure.com + resources: + - memberclusters/status + verbs: + - get + - patch + - update diff --git a/pkg/controllers/membercluster/membercluster_controller.go b/pkg/controllers/membercluster/membercluster_controller.go index 8219ff1ec..3f3732860 100644 --- a/pkg/controllers/membercluster/membercluster_controller.go +++ b/pkg/controllers/membercluster/membercluster_controller.go @@ -30,16 +30,17 @@ import ( ) const ( - eventReasonNamespaceCreated = "NamespaceCreated" - eventReasonNamespaceDeleted = "NamespaceDeleted" - eventReasonRoleCreated = "RoleCreated" - eventReasonRoleUpdated = "RoleUpdated" - eventReasonRoleBindingCreated = "RoleBindingCreated" - eventReasonRoleBindingUpdated = "RoleBindingUpdated" - eventReasonIMCCreated = "InternalMemberClusterCreated" - eventReasonIMCSpecUpdated = "InternalMemberClusterSpecUpdated" - reasonMemberClusterJoined = "MemberClusterJoined" - reasonMemberClusterLeft = "MemberClusterLeft" + eventReasonNamespaceCreated = "NamespaceCreated" + eventReasonNamespaceDeleted = "NamespaceDeleted" + eventReasonRoleCreated = "RoleCreated" + eventReasonRoleUpdated = "RoleUpdated" + eventReasonRoleBindingCreated = "RoleBindingCreated" + eventReasonRoleBindingUpdated = "RoleBindingUpdated" + eventReasonIMCCreated = "InternalMemberClusterCreated" + eventReasonIMCSpecUpdated = "InternalMemberClusterSpecUpdated" + reasonMemberClusterReadyToJoin = "MemberClusterReadyToJoin" + reasonMemberClusterJoined = "MemberClusterJoined" + reasonMemberClusterLeft = "MemberClusterLeft" ) // Reconciler reconciles a MemberCluster object @@ -102,16 +103,17 @@ func (r *Reconciler) join(ctx context.Context, mc *fleetv1alpha1.MemberCluster) return ctrl.Result{}, err } + markMemberClusterReadyToJoin(r.recorder, mc) joinedCond := imc.GetCondition(fleetv1alpha1.ConditionTypeInternalMemberClusterJoin) if joinedCond != nil && joinedCond.Status == metav1.ConditionTrue { r.copyMemberClusterStatusFromInternalMC(mc, imc) - if err := r.updateMemberClusterStatus(ctx, mc); err != nil { - klog.ErrorS(err, "cannot update member cluster status as Joined", - "internalMemberCluster", imc.Name) - return ctrl.Result{}, err - } } + if err := r.updateMemberClusterStatus(ctx, mc); err != nil { + klog.ErrorS(err, "cannot update member cluster status as Joined", + "internalMemberCluster", imc.Name) + return ctrl.Result{}, err + } return ctrl.Result{}, nil } @@ -380,6 +382,23 @@ func (r *Reconciler) deleteNamespace(ctx context.Context, mc *fleetv1alpha1.Memb return nil } +// markMemberClusterJoined is used to the update the status of the member cluster to ready to join condition. +func markMemberClusterReadyToJoin(recorder record.EventRecorder, mc apis.ConditionedObj) { + joinedCond := mc.GetCondition(fleetv1alpha1.ConditionTypeMemberClusterReadyToJoin) + if joinedCond != nil { + return + } + klog.V(2).InfoS("mark the member Cluster as ready to", "memberService", mc.GetName()) + recorder.Event(mc, corev1.EventTypeNormal, reasonMemberClusterReadyToJoin, "member cluster is ready to join") + readyToJoinCondition := &metav1.Condition{ + Type: fleetv1alpha1.ConditionTypeMemberClusterReadyToJoin, + Status: metav1.ConditionTrue, + Reason: reasonMemberClusterReadyToJoin, + ObservedGeneration: mc.GetGeneration(), + } + mc.SetConditions(*readyToJoinCondition) +} + // markMemberClusterJoined is used to the update the status of the member cluster to have the joined condition. func markMemberClusterJoined(recorder record.EventRecorder, mc apis.ConditionedObj) { klog.V(2).InfoS("mark the member Cluster as Joined", "memberService", mc.GetName()) diff --git a/pkg/controllers/membercluster/membercluster_controller_integration_test.go b/pkg/controllers/membercluster/membercluster_controller_integration_test.go index 91198f717..a03628263 100644 --- a/pkg/controllers/membercluster/membercluster_controller_integration_test.go +++ b/pkg/controllers/membercluster/membercluster_controller_integration_test.go @@ -125,11 +125,18 @@ var _ = Describe("Test MemberCluster Controller", func() { var mc fleetv1alpha1.MemberCluster Expect(k8sClient.Get(ctx, memberClusterNamespacedName, &mc)).Should(Succeed()) + readyToJoinCondition := mc.GetCondition(fleetv1alpha1.ConditionTypeMemberClusterReadyToJoin) + Expect(readyToJoinCondition).NotTo(BeNil()) + Expect(readyToJoinCondition.Status).To(Equal(metav1.ConditionTrue)) + Expect(readyToJoinCondition.Reason).To(Equal(reasonMemberClusterReadyToJoin)) + joinCondition := mc.GetCondition(fleetv1alpha1.ConditionTypeMemberClusterJoin) + Expect(joinCondition).NotTo(BeNil()) Expect(joinCondition.Status).To(Equal(metav1.ConditionTrue)) Expect(joinCondition.Reason).To(Equal(reasonMemberClusterJoined)) heartBeatCondition := mc.GetCondition(fleetv1alpha1.ConditionTypeInternalMemberClusterHeartbeat) + Expect(heartBeatCondition).NotTo(BeNil()) Expect(heartBeatCondition.Status).To(Equal(metav1.ConditionTrue)) Expect(heartBeatCondition.Reason).To(Equal("InternalMemberClusterHeartbeatReceived")) }) From 3aec75cd45948cf9dfce6c35d3c7ce229875f7f5 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Wed, 20 Jul 2022 12:28:34 -0700 Subject: [PATCH 2/3] address comments --- config/rbac/role.yaml | 60 ------------------- .../membercluster/membercluster_controller.go | 6 +- 2 files changed, 3 insertions(+), 63 deletions(-) delete mode 100644 config/rbac/role.yaml diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml deleted file mode 100644 index 89d531577..000000000 --- a/config/rbac/role.yaml +++ /dev/null @@ -1,60 +0,0 @@ - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - creationTimestamp: null - name: manager-role -rules: -- apiGroups: - - fleet.azure.com - resources: - - internalmemberclusters - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - fleet.azure.com - resources: - - internalmemberclusters/finalizers - verbs: - - update -- apiGroups: - - fleet.azure.com - resources: - - internalmemberclusters/status - verbs: - - get - - patch - - update -- apiGroups: - - fleet.azure.com - resources: - - memberclusters - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - fleet.azure.com - resources: - - memberclusters/finalizers - verbs: - - update -- apiGroups: - - fleet.azure.com - resources: - - memberclusters/status - verbs: - - get - - patch - - update diff --git a/pkg/controllers/membercluster/membercluster_controller.go b/pkg/controllers/membercluster/membercluster_controller.go index 3f3732860..7f2cf3267 100644 --- a/pkg/controllers/membercluster/membercluster_controller.go +++ b/pkg/controllers/membercluster/membercluster_controller.go @@ -110,8 +110,8 @@ func (r *Reconciler) join(ctx context.Context, mc *fleetv1alpha1.MemberCluster) } if err := r.updateMemberClusterStatus(ctx, mc); err != nil { - klog.ErrorS(err, "cannot update member cluster status as Joined", - "internalMemberCluster", imc.Name) + klog.ErrorS(err, "cannot update the member cluster status", + "internalMemberCluster", klog.KObj(imc)) return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -382,7 +382,7 @@ func (r *Reconciler) deleteNamespace(ctx context.Context, mc *fleetv1alpha1.Memb return nil } -// markMemberClusterJoined is used to the update the status of the member cluster to ready to join condition. +// markMemberClusterReadyToJoin is used to the update the status of the member cluster to ready to join condition. func markMemberClusterReadyToJoin(recorder record.EventRecorder, mc apis.ConditionedObj) { joinedCond := mc.GetCondition(fleetv1alpha1.ConditionTypeMemberClusterReadyToJoin) if joinedCond != nil { From 3eb94f53e99b3e1b6d17b35c0009b5b8de095913 Mon Sep 17 00:00:00 2001 From: Liqian Luo <13264318+circy9@users.noreply.github.com> Date: Thu, 21 Jul 2022 08:22:18 -0700 Subject: [PATCH 3/3] Update membercluster_controller.go --- pkg/controllers/membercluster/membercluster_controller.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/controllers/membercluster/membercluster_controller.go b/pkg/controllers/membercluster/membercluster_controller.go index 7f2cf3267..7fcb54f08 100644 --- a/pkg/controllers/membercluster/membercluster_controller.go +++ b/pkg/controllers/membercluster/membercluster_controller.go @@ -384,8 +384,8 @@ func (r *Reconciler) deleteNamespace(ctx context.Context, mc *fleetv1alpha1.Memb // markMemberClusterReadyToJoin is used to the update the status of the member cluster to ready to join condition. func markMemberClusterReadyToJoin(recorder record.EventRecorder, mc apis.ConditionedObj) { - joinedCond := mc.GetCondition(fleetv1alpha1.ConditionTypeMemberClusterReadyToJoin) - if joinedCond != nil { + readyToJoinCond := mc.GetCondition(fleetv1alpha1.ConditionTypeMemberClusterReadyToJoin) + if readyToJoinCond != nil { return } klog.V(2).InfoS("mark the member Cluster as ready to", "memberService", mc.GetName())