Skip to content

Commit b5bc29e

Browse files
committed
refactor
Signed-off-by: Wantong Jiang <[email protected]>
1 parent 7a76d28 commit b5bc29e

File tree

6 files changed

+260
-197
lines changed

6 files changed

+260
-197
lines changed
Lines changed: 111 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package main
17+
package drain
1818

1919
import (
2020
"context"
2121
"fmt"
2222
"log"
2323

24+
"github.com/spf13/cobra"
2425
k8errors "k8s.io/apimachinery/pkg/api/errors"
2526
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/runtime"
2628
"k8s.io/apimachinery/pkg/types"
2729
"k8s.io/apimachinery/pkg/util/uuid"
2830
"k8s.io/apimachinery/pkg/util/validation"
@@ -43,30 +45,54 @@ const (
4345
resourceIdentifierKeyFormat = "%s/%s/%s/%s/%s"
4446
)
4547

48+
var (
49+
hubClusterContext string
50+
clusterName string
51+
)
52+
53+
// setupClient creates and configures the Kubernetes client
54+
func setupClient() (*runtime.Scheme, client.Client, error) {
55+
scheme := runtime.NewScheme()
56+
57+
if err := clusterv1beta1.AddToScheme(scheme); err != nil {
58+
log.Fatalf("failed to add custom APIs (cluster) to the runtime scheme: %v", err)
59+
}
60+
if err := placementv1beta1.AddToScheme(scheme); err != nil {
61+
log.Fatalf("failed to add custom APIs (placement) to the runtime scheme: %v", err)
62+
}
63+
64+
hubClient, err := toolsutils.GetClusterClientFromClusterContext(hubClusterContext, scheme)
65+
if err != nil {
66+
log.Fatalf("failed to create hub cluster client: %v", err)
67+
}
68+
69+
return scheme, hubClient, nil
70+
}
71+
4672
type drainHelper struct {
47-
hubClient client.Client
48-
clusterName string
73+
HubClient client.Client
74+
ClusterName string
4975
}
5076

5177
func (h *drainHelper) Drain(ctx context.Context) (bool, error) {
5278
if err := h.cordon(ctx); err != nil {
53-
return false, fmt.Errorf("failed to cordon member cluster %s: %w", h.clusterName, err)
79+
return false, fmt.Errorf("failed to cordon member cluster %s: %w", h.ClusterName, err)
5480
}
55-
log.Printf("Successfully cordoned member cluster %s by adding cordon taint", h.clusterName)
81+
log.Printf("Successfully cordoned member cluster %s by adding cordon taint", h.ClusterName)
5682

5783
crpNameMap, err := h.fetchClusterResourcePlacementNamesToEvict(ctx)
5884
if err != nil {
5985
return false, err
6086
}
6187

6288
if len(crpNameMap) == 0 {
63-
log.Printf("There are currently no resources propagated to %s from fleet using ClusterResourcePlacement resources", h.clusterName)
89+
log.Printf("There are currently no resources propagated to %s from fleet using ClusterResourcePlacement resources", h.ClusterName)
6490
return true, nil
6591
}
6692

6793
isDrainSuccessful := true
6894
for crpName := range crpNameMap {
69-
evictionName, err := generateDrainEvictionName(crpName, h.clusterName)
95+
evictionName, err := generateDrainEvictionName(crpName, h.ClusterName)
7096
if err != nil {
7197
return false, err
7298
}
@@ -80,54 +106,54 @@ func (h *drainHelper) Drain(ctx context.Context) (bool, error) {
80106
},
81107
Spec: placementv1beta1.PlacementEvictionSpec{
82108
PlacementName: crpName,
83-
ClusterName: h.clusterName,
109+
ClusterName: h.ClusterName,
84110
},
85111
}
86-
return h.hubClient.Create(ctx, &eviction)
112+
return h.HubClient.Create(ctx, &eviction)
87113
})
88114

89115
if err != nil {
90-
return false, fmt.Errorf("failed to create eviction %s for CRP %s targeting member cluster %s: %w", evictionName, crpName, h.clusterName, err)
116+
return false, fmt.Errorf("failed to create eviction %s for CRP %s targeting member cluster %s: %w", evictionName, crpName, h.ClusterName, err)
91117
}
92118

93-
log.Printf("Created eviction %s for CRP %s targeting member cluster %s", evictionName, crpName, h.clusterName)
119+
log.Printf("Created eviction %s for CRP %s targeting member cluster %s", evictionName, crpName, h.ClusterName)
94120

95121
var eviction placementv1beta1.ClusterResourcePlacementEviction
96122
err = wait.ExponentialBackoffWithContext(ctx, retry.DefaultBackoff, func(ctx context.Context) (bool, error) {
97-
if err := h.hubClient.Get(ctx, types.NamespacedName{Name: evictionName}, &eviction); err != nil {
98-
return false, fmt.Errorf("failed to get eviction %s for CRP %s targeting member cluster %s: %w", evictionName, crpName, h.clusterName, err)
123+
if err := h.HubClient.Get(ctx, types.NamespacedName{Name: evictionName}, &eviction); err != nil {
124+
return false, fmt.Errorf("failed to get eviction %s for CRP %s targeting member cluster %s: %w", evictionName, crpName, h.ClusterName, err)
99125
}
100126
return evictionutils.IsEvictionInTerminalState(&eviction), nil
101127
})
102128

103129
if err != nil {
104-
return false, fmt.Errorf("failed to wait for eviction %s for CRP %s targeting member cluster %s to reach terminal state: %w", evictionName, crpName, h.clusterName, err)
130+
return false, fmt.Errorf("failed to wait for eviction %s for CRP %s targeting member cluster %s to reach terminal state: %w", evictionName, crpName, h.ClusterName, err)
105131
}
106132

107133
validCondition := eviction.GetCondition(string(placementv1beta1.PlacementEvictionConditionTypeValid))
108134
if validCondition != nil && validCondition.Status == metav1.ConditionFalse {
109135
if validCondition.Reason == condition.EvictionInvalidMissingCRPMessage ||
110136
validCondition.Reason == condition.EvictionInvalidDeletingCRPMessage ||
111137
validCondition.Reason == condition.EvictionInvalidMissingCRBMessage {
112-
log.Printf("eviction %s is invalid with reason %s for CRP %s targeting member cluster %s, but drain will succeed", evictionName, validCondition.Reason, crpName, h.clusterName)
138+
log.Printf("eviction %s is invalid with reason %s for CRP %s targeting member cluster %s, but drain will succeed", evictionName, validCondition.Reason, crpName, h.ClusterName)
113139
continue
114140
}
115141
}
116142
executedCondition := eviction.GetCondition(string(placementv1beta1.PlacementEvictionConditionTypeExecuted))
117143
if executedCondition == nil || executedCondition.Status == metav1.ConditionFalse {
118144
isDrainSuccessful = false
119-
log.Printf("eviction %s was not executed successfully for CRP %s targeting member cluster %s", evictionName, crpName, h.clusterName)
145+
log.Printf("eviction %s was not executed successfully for CRP %s targeting member cluster %s", evictionName, crpName, h.ClusterName)
120146
continue
121147
}
122-
log.Printf("eviction %s was executed successfully for CRP %s targeting member cluster %s", evictionName, crpName, h.clusterName)
123-
148+
log.Printf("eviction %s was executed successfully for CRP %s targeting member cluster %s", evictionName, crpName, h.ClusterName)
149+
124150
clusterScopedResourceIdentifiers, err := h.collectClusterScopedResourcesSelectedByCRP(ctx, crpName)
125151
if err != nil {
126152
log.Printf("failed to collect cluster scoped resources selected by CRP %s: %v", crpName, err)
127153
continue
128154
}
129155
for _, resourceIdentifier := range clusterScopedResourceIdentifiers {
130-
log.Printf("evicted resource %s propagated by CRP %s targeting member cluster %s", generateResourceIdentifierKey(resourceIdentifier), crpName, h.clusterName)
156+
log.Printf("evicted resource %s propagated by CRP %s targeting member cluster %s", generateResourceIdentifierKey(resourceIdentifier), crpName, h.ClusterName)
131157
}
132158
}
133159

@@ -137,7 +163,7 @@ func (h *drainHelper) Drain(ctx context.Context) (bool, error) {
137163
func (h *drainHelper) cordon(ctx context.Context) error {
138164
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
139165
var mc clusterv1beta1.MemberCluster
140-
if err := h.hubClient.Get(ctx, types.NamespacedName{Name: h.clusterName}, &mc); err != nil {
166+
if err := h.HubClient.Get(ctx, types.NamespacedName{Name: h.ClusterName}, &mc); err != nil {
141167
return err
142168
}
143169

@@ -149,20 +175,20 @@ func (h *drainHelper) cordon(ctx context.Context) error {
149175

150176
mc.Spec.Taints = append(mc.Spec.Taints, toolsutils.CordonTaint)
151177

152-
return h.hubClient.Update(ctx, &mc)
178+
return h.HubClient.Update(ctx, &mc)
153179
})
154180
}
155181

156182
func (h *drainHelper) fetchClusterResourcePlacementNamesToEvict(ctx context.Context) (map[string]bool, error) {
157183
var crbList placementv1beta1.ClusterResourceBindingList
158-
if err := h.hubClient.List(ctx, &crbList); err != nil {
184+
if err := h.HubClient.List(ctx, &crbList); err != nil {
159185
return map[string]bool{}, fmt.Errorf("failed to list cluster resource bindings: %w", err)
160186
}
161187

162188
crpNameMap := make(map[string]bool)
163189
for i := range crbList.Items {
164190
crb := crbList.Items[i]
165-
if crb.Spec.TargetCluster == h.clusterName && crb.DeletionTimestamp == nil {
191+
if crb.Spec.TargetCluster == h.ClusterName && crb.DeletionTimestamp == nil {
166192
crpName, ok := crb.GetLabels()[placementv1beta1.PlacementTrackingLabel]
167193
if !ok {
168194
return map[string]bool{}, fmt.Errorf("failed to get CRP name from binding %s", crb.Name)
@@ -176,7 +202,7 @@ func (h *drainHelper) fetchClusterResourcePlacementNamesToEvict(ctx context.Cont
176202

177203
func (h *drainHelper) collectClusterScopedResourcesSelectedByCRP(ctx context.Context, crpName string) ([]placementv1beta1.ResourceIdentifier, error) {
178204
var crp placementv1beta1.ClusterResourcePlacement
179-
if err := h.hubClient.Get(ctx, types.NamespacedName{Name: crpName}, &crp); err != nil {
205+
if err := h.HubClient.Get(ctx, types.NamespacedName{Name: crpName}, &crp); err != nil {
180206
return nil, fmt.Errorf("failed to get ClusterResourcePlacement %s: %w", crpName, err)
181207
}
182208

@@ -209,4 +235,64 @@ func generateResourceIdentifierKey(r placementv1beta1.ResourceIdentifier) string
209235
return fmt.Sprintf(resourceIdentifierKeyFormat, r.Group, r.Version, r.Kind, "''", r.Name)
210236
}
211237
return fmt.Sprintf(resourceIdentifierKeyFormat, r.Group, r.Version, r.Kind, r.Namespace, r.Name)
212-
}
238+
}
239+
240+
// NewCmdDrain creates a new drain command
241+
func NewCmdDrain() *cobra.Command {
242+
cmd := &cobra.Command{
243+
Use: "drain",
244+
Short: "Drain a member cluster",
245+
Long: "Drain a member cluster by cordoning it and removing propagated resources",
246+
RunE: func(command *cobra.Command, args []string) error {
247+
return runDrain()
248+
},
249+
}
250+
251+
// Add flags specific to drain command
252+
cmd.Flags().StringVar(&hubClusterContext, "hubClusterContext", "", "kubectl context for the hub cluster (required)")
253+
cmd.Flags().StringVar(&clusterName, "clusterName", "", "name of the member cluster (required)")
254+
255+
// Mark required flags
256+
_ = cmd.MarkFlagRequired("hubClusterContext")
257+
_ = cmd.MarkFlagRequired("clusterName")
258+
259+
return cmd
260+
}
261+
262+
func runDrain() error {
263+
_, hubClient, err := setupClient()
264+
if err != nil {
265+
return err
266+
}
267+
268+
ctx := context.Background()
269+
drainHelper := &drainHelper{
270+
HubClient: hubClient,
271+
ClusterName: clusterName,
272+
}
273+
274+
isDrainSuccessful, err := drainHelper.Drain(ctx)
275+
if err != nil {
276+
log.Fatalf("failed to drain member cluster %s: %v", clusterName, err)
277+
}
278+
279+
if isDrainSuccessful {
280+
log.Printf("drain was successful for cluster %s", clusterName)
281+
} else {
282+
log.Printf("drain was not successful for cluster %s", clusterName)
283+
}
284+
285+
log.Printf("retrying drain to ensure all resources propagated from hub cluster are evicted")
286+
isDrainRetrySuccessful, err := drainHelper.Drain(ctx)
287+
if err != nil {
288+
log.Fatalf("failed to drain cluster on retry %s: %v", clusterName, err)
289+
}
290+
if isDrainRetrySuccessful {
291+
log.Printf("drain retry was successful for cluster %s", clusterName)
292+
} else {
293+
log.Printf("drain retry was not successful for cluster %s", clusterName)
294+
}
295+
296+
log.Printf("reminder: uncordon the cluster %s to remove cordon taint if needed", clusterName)
297+
return nil
298+
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package main
17+
package drain
1818

1919
import (
2020
"context"
@@ -157,8 +157,8 @@ func TestFetchClusterResourcePlacementNamesToEvict(t *testing.T) {
157157
WithObjects(objects...).
158158
Build()
159159
h := drainHelper{
160-
hubClient: fakeClient,
161-
clusterName: tc.targetCluster,
160+
HubClient: fakeClient,
161+
ClusterName: tc.targetCluster,
162162
}
163163
gotMap, gotErr := h.fetchClusterResourcePlacementNamesToEvict(context.Background())
164164
if tc.wantErr == nil {
@@ -274,7 +274,7 @@ func TestCollectClusterScopedResourcesSelectedByCRP(t *testing.T) {
274274
Build()
275275

276276
h := drainHelper{
277-
hubClient: fakeClient,
277+
HubClient: fakeClient,
278278
}
279279

280280
gotResources, gotErr := h.collectClusterScopedResourcesSelectedByCRP(context.Background(), tc.crpName)
@@ -480,8 +480,8 @@ func TestCordon(t *testing.T) {
480480
Build()
481481

482482
h := drainHelper{
483-
hubClient: fakeClient,
484-
clusterName: "test-cluster",
483+
HubClient: fakeClient,
484+
ClusterName: "test-cluster",
485485
}
486486

487487
gotErr := h.cordon(context.Background())

0 commit comments

Comments
 (0)