55 "flag"
66 "fmt"
77 "log"
8+ "os"
89
910 k8errors "k8s.io/apimachinery/pkg/api/errors"
1011 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2627)
2728
2829const (
29- testEvictionNameFormat = "test-eviction-%s-%s"
30+ testEvictionNameFormat = "test-eviction-%s-%s"
31+ resourceIdentifierKeyFormat = "%s/%s/%s/%s/%s"
3032)
3133
34+ type DrainCluster struct {
35+ hubClient client.Client
36+ ClusterName string
37+ ClusterResourcePlacementResourcesMap map [string ][]placementv1beta1.ResourceIdentifier
38+ }
39+
3240func main () {
3341 hubClusterContext := flag .String ("hubClusterContext" , "" , "the kubectl context for the hub cluster" )
3442 clusterName := flag .String ("clusterName" , "" , "name of the cluster to cordon" )
@@ -53,65 +61,43 @@ func main() {
5361 log .Fatalf ("failed to create hub cluster client: %v" , err )
5462 }
5563
56- var mc clusterv1beta1.MemberCluster
57- if err = hubClient .Get (ctx , types.NamespacedName {Name : * clusterName }, & mc ); err != nil {
58- log .Fatalf ("failed to get member cluster %s: %v" , * clusterName , err )
64+ drainCluster := DrainCluster {
65+ hubClient : hubClient ,
66+ ClusterName : * clusterName ,
67+ ClusterResourcePlacementResourcesMap : make (map [string ][]placementv1beta1.ResourceIdentifier ),
68+ }
69+
70+ if err = drainCluster .cordonCluster (); err != nil {
71+ log .Fatalf ("failed to cordon member cluster %s: %v" , drainCluster .ClusterName , err )
5972 }
6073
6174 var crpList placementv1beta1.ClusterResourcePlacementList
62- if err = hubClient .List (ctx , & crpList ); err != nil {
75+ if err = drainCluster . hubClient .List (ctx , & crpList ); err != nil {
6376 log .Fatalf ("failed to list cluster resource placements: %v" , err )
6477 }
6578
6679 // find all unique CRP names for which eviction needs to occur.
67- crpNameMap := make (map [string ]bool )
6880 for i := range crpList .Items {
69- for j := range crpList .Items [i ].Status .PlacementStatuses {
70- if crpList .Items [i ].Status .PlacementStatuses [j ].ClusterName == * clusterName {
71- crpNameMap [crpList .Items [i ].Name ] = true
81+ crpStatus := crpList .Items [i ].Status
82+ for j := range crpStatus .PlacementStatuses {
83+ placementStatus := crpStatus .PlacementStatuses [j ]
84+ if placementStatus .ClusterName == * clusterName {
85+ drainCluster .ClusterResourcePlacementResourcesMap [crpList .Items [i ].Name ] = resourcePropagatedByCRP (crpStatus .SelectedResources , placementStatus .FailedPlacements )
7286 break
7387 }
7488 }
7589 }
7690
77- if len (crpNameMap ) == 0 {
78- log .Fatalf ("failed to find any CRP which has propagated resource to cluster %s" , * clusterName )
79- }
80-
81- // add taint to member cluster to ensure resources aren't scheduled on it.
82- err = retry .RetryOnConflict (retry .DefaultRetry , func () error {
83- var mc clusterv1beta1.MemberCluster
84- if err := hubClient .Get (ctx , types.NamespacedName {Name : * clusterName }, & mc ); err != nil {
85- return err
86- }
87- cordonTaint := clusterv1beta1.Taint {
88- Key : "cordon-key" ,
89- Value : "cordon-value" ,
90- Effect : "NoSchedule" ,
91- }
92-
93- // search to see cordonTaint already exists on the cluster.
94- for i := range mc .Spec .Taints {
95- if mc .Spec .Taints [i ].Key == cordonTaint .Key {
96- return nil
97- }
98- }
99-
100- // add taint to member cluster to cordon.
101- mc .Spec .Taints = append (mc .Spec .Taints , cordonTaint )
102-
103- return hubClient .Update (ctx , & mc )
104- })
105-
106- if err != nil {
107- log .Fatalf ("failed to update member cluster with taint: %v" , err )
91+ if len (drainCluster .ClusterResourcePlacementResourcesMap ) == 0 {
92+ log .Printf ("There are no resources propagated to %s from fleet using ClusterResourcePlacement resources" , * clusterName )
93+ os .Exit (0 )
10894 }
10995
11096 // create eviction objects for all <crpName, targetCluster>.
111- for crpName := range crpNameMap {
97+ for crpName := range drainCluster . ClusterResourcePlacementResourcesMap {
11298 evictionName := fmt .Sprintf (testEvictionNameFormat , crpName , * clusterName )
11399
114- if err = removeExistingEviction (ctx , hubClient , evictionName ); err != nil {
100+ if err = removeExistingEviction (ctx , drainCluster . hubClient , evictionName ); err != nil {
115101 log .Fatalf ("failed to remove existing eviction for CRP %s" , crpName )
116102 }
117103
@@ -127,7 +113,7 @@ func main() {
127113 ClusterName : * clusterName ,
128114 },
129115 }
130- return hubClient .Create (ctx , & eviction )
116+ return drainCluster . hubClient .Create (ctx , & eviction )
131117 })
132118
133119 if err != nil {
@@ -137,11 +123,11 @@ func main() {
137123
138124 // TODO: move isEvictionInTerminalState to a function in the pkg/utils package.
139125 // wait until all evictions reach a terminal state.
140- for crpName := range crpNameMap {
126+ for crpName := range drainCluster . ClusterResourcePlacementResourcesMap {
141127 err = wait .ExponentialBackoffWithContext (ctx , retry .DefaultBackoff , func (ctx context.Context ) (bool , error ) {
142128 evictionName := fmt .Sprintf (testEvictionNameFormat , crpName , * clusterName )
143129 eviction := placementv1beta1.ClusterResourcePlacementEviction {}
144- if err := hubClient .Get (ctx , types.NamespacedName {Name : evictionName }, & eviction ); err != nil {
130+ if err := drainCluster . hubClient .Get (ctx , types.NamespacedName {Name : evictionName }, & eviction ); err != nil {
145131 return false , err
146132 }
147133 validCondition := eviction .GetCondition (string (placementv1beta1 .PlacementEvictionConditionTypeValid ))
@@ -160,7 +146,82 @@ func main() {
160146 }
161147 }
162148
163- log .Printf ("Issued evictions to cordon member cluster %s, please verify to ensure evictions were successfully executed" , * clusterName )
149+ isDrainSuccessful := true
150+ // check if all evictions have been executed.
151+ for crpName := range drainCluster .ClusterResourcePlacementResourcesMap {
152+ evictionName := fmt .Sprintf (testEvictionNameFormat , crpName , * clusterName )
153+ eviction := placementv1beta1.ClusterResourcePlacementEviction {}
154+ if err := drainCluster .hubClient .Get (ctx , types.NamespacedName {Name : evictionName }, & eviction ); err != nil {
155+ log .Fatalf ("failed to get eviction %s: %v" , evictionName , err )
156+ }
157+ executedCondition := eviction .GetCondition (string (placementv1beta1 .PlacementEvictionConditionTypeExecuted ))
158+ if executedCondition == nil || executedCondition .Status == metav1 .ConditionFalse {
159+ isDrainSuccessful = false
160+ log .Fatalf ("eviction %s was not executed successfully for CRP %s" , evictionName , crpName )
161+ }
162+ log .Printf ("eviction %s was executed successfully for CRP %s" , evictionName , crpName )
163+ // log each resource evicted by CRP.
164+ for i := range drainCluster .ClusterResourcePlacementResourcesMap [crpName ] {
165+ resourceIdentifier := drainCluster.ClusterResourcePlacementResourcesMap [crpName ][i ]
166+ log .Printf ("evicted resource %s propagated by CRP %s" , fmt .Sprintf (resourceIdentifierKeyFormat , resourceIdentifier .Group , resourceIdentifier .Version , resourceIdentifier .Kind , resourceIdentifier .Name , resourceIdentifier .Namespace ), crpName )
167+ }
168+ }
169+
170+ if isDrainSuccessful {
171+ log .Printf ("drain was successful for cluster %s" , * clusterName )
172+ } else {
173+ log .Printf ("drain was not successful for cluster %s, some eviction were not successfully executed" , * clusterName )
174+ }
175+ }
176+
177+ func (d * DrainCluster ) cordonCluster () error {
178+ // add taint to member cluster to ensure resources aren't scheduled on it.
179+ return retry .RetryOnConflict (retry .DefaultRetry , func () error {
180+ var mc clusterv1beta1.MemberCluster
181+ if err := d .hubClient .Get (ctx , types.NamespacedName {Name : d .ClusterName }, & mc ); err != nil {
182+ return err
183+ }
184+ cordonTaint := clusterv1beta1.Taint {
185+ Key : "cordon-key" ,
186+ Value : "cordon-value" ,
187+ Effect : "NoSchedule" ,
188+ }
189+
190+ // search to see cordonTaint already exists on the cluster.
191+ for i := range mc .Spec .Taints {
192+ if mc .Spec .Taints [i ].Key == cordonTaint .Key {
193+ return nil
194+ }
195+ }
196+
197+ // add taint to member cluster to cordon.
198+ mc .Spec .Taints = append (mc .Spec .Taints , cordonTaint )
199+
200+ return d .hubClient .Update (ctx , & mc )
201+ })
202+ }
203+
204+ func resourcePropagatedByCRP (selectedResources []placementv1beta1.ResourceIdentifier , failedPlacements []placementv1beta1.FailedResourcePlacement ) []placementv1beta1.ResourceIdentifier {
205+ selectedResourcesMap := make (map [string ]placementv1beta1.ResourceIdentifier , len (selectedResources ))
206+ for i := range selectedResources {
207+ selectedResourcesMap [generateResourceIdentifierKey (selectedResources [i ])] = selectedResources [i ]
208+ }
209+ for i := range failedPlacements {
210+ if failedPlacements [i ].Condition .Type == placementv1beta1 .WorkConditionTypeApplied {
211+ delete (selectedResourcesMap , generateResourceIdentifierKey (failedPlacements [i ].ResourceIdentifier ))
212+ }
213+ }
214+ resourcesPropagated := make ([]placementv1beta1.ResourceIdentifier , len (selectedResourcesMap ))
215+ i := 0
216+ for key := range selectedResourcesMap {
217+ resourcesPropagated [i ] = selectedResourcesMap [key ]
218+ i ++
219+ }
220+ return resourcesPropagated
221+ }
222+
223+ func generateResourceIdentifierKey (resourceIdentifier placementv1beta1.ResourceIdentifier ) string {
224+ return fmt .Sprintf (resourceIdentifierKeyFormat , resourceIdentifier .Group , resourceIdentifier .Version , resourceIdentifier .Kind , resourceIdentifier .Name , resourceIdentifier .Namespace )
164225}
165226
166227func removeExistingEviction (ctx context.Context , client client.Client , evictionName string ) error {
0 commit comments