Skip to content

Commit cb1a5d6

Browse files
ryanzhang-ossRyan Zhang
andauthored
feat: enable work agent join leave (#292)
* feat: implement work agent join/leave * fix tests * fix the e2e * fix a corner case that a member cluster is deleted during reconcile * disable the work-api e2e test Co-authored-by: Ryan Zhang <zhangryan@microsoft.com>
1 parent 3d09ebe commit cb1a5d6

File tree

14 files changed

+255
-574
lines changed

14 files changed

+255
-574
lines changed

cmd/memberagent/main.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Licensed under the MIT license.
55

66
package main
77

8+
//goland:noinspection ALL
89
import (
910
"context"
1011
"encoding/base64"
@@ -198,18 +199,19 @@ func Start(ctx context.Context, hubCfg, memberConfig *rest.Config, hubOpts, memb
198199
os.Exit(1)
199200
}
200201

201-
if err = workcontrollers.NewApplyWorkReconciler(
202+
// create the work controller, so we can pass it to the internal member cluster reconciler
203+
workController := workcontrollers.NewApplyWorkReconciler(
202204
hubMgr.GetClient(),
203205
spokeDynamicClient,
204206
memberMgr.GetClient(),
205-
restMapper,
206-
hubMgr.GetEventRecorderFor("work_controller"),
207-
5, true).SetupWithManager(hubMgr); err != nil {
207+
restMapper, hubMgr.GetEventRecorderFor("work_controller"), 5, hubOpts.Namespace)
208+
209+
if err = workController.SetupWithManager(hubMgr); err != nil {
208210
klog.ErrorS(err, "unable to create controller", "controller", "Work")
209211
return err
210212
}
211213

212-
if err = internalmembercluster.NewReconciler(hubMgr.GetClient(), memberMgr.GetClient()).SetupWithManager(hubMgr); err != nil {
214+
if err = internalmembercluster.NewReconciler(hubMgr.GetClient(), memberMgr.GetClient(), workController).SetupWithManager(hubMgr); err != nil {
213215
return errors.Wrap(err, "unable to create controller hub_member")
214216
}
215217

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,5 @@ replace (
9696
golang.org/x/crypto => golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b
9797

9898
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.24.2 // weird bug that the goland won't compile without this
99-
sigs.k8s.io/work-api => github.com/Azure/k8s-work-api v0.4.2
99+
sigs.k8s.io/work-api => github.com/Azure/k8s-work-api v0.4.3
100100
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSY
5252
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
5353
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
5454
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
55-
github.com/Azure/k8s-work-api v0.4.2 h1:Kwl8pmBfiykgWws12ud80TpU9gQNveyR7zlwMutGwGc=
56-
github.com/Azure/k8s-work-api v0.4.2/go.mod h1:FOGJkJ+uxjWlvUgmqUlRcmr4Q2ijocrUO/aLJv827y8=
55+
github.com/Azure/k8s-work-api v0.4.3 h1:fxwO/QZftM3CW9FNl/JTHRQmfbQPa83VwOxR0HadECk=
56+
github.com/Azure/k8s-work-api v0.4.3/go.mod h1:FOGJkJ+uxjWlvUgmqUlRcmr4Q2ijocrUO/aLJv827y8=
5757
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 h1:WVsrXCnHlDDX8ls+tootqRE87/hL9S/g4ewig9RsD/c=
5858
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
5959
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

pkg/controllers/internalmembercluster/member_controller.go

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"sigs.k8s.io/controller-runtime/pkg/builder"
2222
"sigs.k8s.io/controller-runtime/pkg/client"
2323
"sigs.k8s.io/controller-runtime/pkg/predicate"
24+
workcontrollers "sigs.k8s.io/work-api/pkg/controllers"
2425

2526
"go.goms.io/fleet/apis"
2627
fleetv1alpha1 "go.goms.io/fleet/apis/v1alpha1"
@@ -31,26 +32,35 @@ import (
3132
type Reconciler struct {
3233
hubClient client.Client
3334
memberClient client.Client
34-
recorder record.EventRecorder
35+
36+
// the join/leave agent maintains the list of controllers in the member cluster
37+
// so that it can make sure that all the agents on the member cluster have joined/left
38+
// before updating the internal member cluster CR status
39+
workController *workcontrollers.ApplyWorkReconciler
40+
41+
recorder record.EventRecorder
3542
}
3643

3744
const (
38-
eventReasonInternalMemberClusterHealthy = "InternalMemberClusterHealthy"
39-
eventReasonInternalMemberClusterUnhealthy = "InternalMemberClusterUnhealthy"
40-
eventReasonInternalMemberClusterJoined = "InternalMemberClusterJoined"
41-
eventReasonInternalMemberClusterLeft = "InternalMemberClusterLeft"
45+
eventReasonInternalMemberClusterHealthy = "InternalMemberClusterHealthy"
46+
eventReasonInternalMemberClusterUnhealthy = "InternalMemberClusterUnhealthy"
47+
eventReasonInternalMemberClusterJoined = "InternalMemberClusterJoined"
48+
eventReasonInternalMemberClusterFailedToJoin = "InternalMemberClusterFailedToJoin"
49+
eventReasonInternalMemberClusterFailedToLeave = "InternalMemberClusterFailedToLeave"
50+
eventReasonInternalMemberClusterLeft = "InternalMemberClusterLeft"
4251
)
4352

4453
// NewReconciler creates a new reconciler for the internalMemberCluster CR
45-
func NewReconciler(hubClient client.Client, memberClient client.Client) *Reconciler {
54+
func NewReconciler(hubClient client.Client, memberClient client.Client, workController *workcontrollers.ApplyWorkReconciler) *Reconciler {
4655
return &Reconciler{
47-
hubClient: hubClient,
48-
memberClient: memberClient,
56+
hubClient: hubClient,
57+
memberClient: memberClient,
58+
workController: workController,
4959
}
5060
}
5161

5262
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
53-
klog.V(3).InfoS("Reconcile", "InternalMemberCluster", req.NamespacedName)
63+
klog.V(2).InfoS("Reconcile", "InternalMemberCluster", req.NamespacedName)
5464

5565
var imc fleetv1alpha1.InternalMemberCluster
5666
if err := r.hubClient.Get(ctx, req.NamespacedName, &imc); err != nil {
@@ -60,6 +70,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
6070

6171
switch imc.Spec.State {
6272
case fleetv1alpha1.ClusterStateJoin:
73+
if err := r.startAgents(ctx, &imc); err != nil {
74+
return ctrl.Result{}, err
75+
}
6376
updateMemberAgentHeartBeat(&imc)
6477
updateHealthErr := r.updateHealth(ctx, &imc)
6578
r.markInternalMemberClusterJoined(&imc)
@@ -74,6 +87,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
7487
return ctrl.Result{RequeueAfter: time.Second * time.Duration(imc.Spec.HeartbeatPeriodSeconds)}, nil
7588

7689
case fleetv1alpha1.ClusterStateLeave:
90+
if err := r.stopAgents(ctx, &imc); err != nil {
91+
return ctrl.Result{}, err
92+
}
7793
r.markInternalMemberClusterLeft(&imc)
7894
if err := r.updateInternalMemberClusterWithRetry(ctx, &imc); err != nil {
7995
klog.ErrorS(err, "failed to update status for %s", klog.KObj(&imc))
@@ -87,9 +103,33 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
87103
}
88104
}
89105

106+
// startAgents start all the member agents running on the member cluster
107+
func (r *Reconciler) startAgents(ctx context.Context, imc *fleetv1alpha1.InternalMemberCluster) error {
108+
// TODO: handle all the controllers uniformly if we have more
109+
if err := r.workController.Join(ctx); err != nil {
110+
r.markInternalMemberClusterJoinFailed(imc, err)
111+
// ignore the update error since we will return an error anyway
112+
_ = r.updateInternalMemberClusterWithRetry(ctx, imc)
113+
return err
114+
}
115+
return nil
116+
}
117+
118+
// stopAgents stops all the member agents running on the member cluster
119+
func (r *Reconciler) stopAgents(ctx context.Context, imc *fleetv1alpha1.InternalMemberCluster) error {
120+
// TODO: handle all the controllers uniformly if we have more
121+
if err := r.workController.Leave(ctx); err != nil {
122+
r.markInternalMemberClusterLeaveFailed(imc, err)
123+
// ignore the update error since we will return an error anyway
124+
_ = r.updateInternalMemberClusterWithRetry(ctx, imc)
125+
return err
126+
}
127+
return nil
128+
}
129+
90130
// updateHealth collects and updates member cluster resource stats and set ConditionTypeInternalMemberClusterHealth.
91131
func (r *Reconciler) updateHealth(ctx context.Context, imc *fleetv1alpha1.InternalMemberCluster) error {
92-
klog.V(3).InfoS("updateHealth", "InternalMemberCluster", klog.KObj(imc))
132+
klog.V(2).InfoS("updateHealth", "InternalMemberCluster", klog.KObj(imc))
93133

94134
if err := r.updateResourceStats(ctx, imc); err != nil {
95135
r.markInternalMemberClusterUnhealthy(imc, errors.Wrapf(err, "failed to update resource stats %s", klog.KObj(imc)))
@@ -102,7 +142,7 @@ func (r *Reconciler) updateHealth(ctx context.Context, imc *fleetv1alpha1.Intern
102142

103143
// updateResourceStats collects and updates resource usage stats of the member cluster.
104144
func (r *Reconciler) updateResourceStats(ctx context.Context, imc *fleetv1alpha1.InternalMemberCluster) error {
105-
klog.V(5).InfoS("updateResourceStats", "InternalMemberCluster", klog.KObj(imc))
145+
klog.V(4).InfoS("updateResourceStats", "InternalMemberCluster", klog.KObj(imc))
106146
var nodes corev1.NodeList
107147
if err := r.memberClient.List(ctx, &nodes); err != nil {
108148
return errors.Wrapf(err, "failed to list nodes for member cluster %s", klog.KObj(imc))
@@ -132,7 +172,7 @@ func (r *Reconciler) updateResourceStats(ctx context.Context, imc *fleetv1alpha1
132172

133173
// updateInternalMemberClusterWithRetry updates InternalMemberCluster status.
134174
func (r *Reconciler) updateInternalMemberClusterWithRetry(ctx context.Context, imc *fleetv1alpha1.InternalMemberCluster) error {
135-
klog.V(5).InfoS("updateInternalMemberClusterWithRetry", "InternalMemberCluster", klog.KObj(imc))
175+
klog.V(4).InfoS("updateInternalMemberClusterWithRetry", "InternalMemberCluster", klog.KObj(imc))
136176
backOffPeriod := retry.DefaultBackoff
137177
backOffPeriod.Cap = time.Second * time.Duration(imc.Spec.HeartbeatPeriodSeconds)
138178

@@ -147,15 +187,15 @@ func (r *Reconciler) updateInternalMemberClusterWithRetry(ctx context.Context, i
147187

148188
// updateMemberAgentHeartBeat is used to update member agent heart beat for Internal member cluster.
149189
func updateMemberAgentHeartBeat(imc *fleetv1alpha1.InternalMemberCluster) {
150-
klog.V(5).InfoS("update Internal member cluster heartbeat", "InternalMemberCluster", klog.KObj(imc))
190+
klog.V(4).InfoS("update Internal member cluster heartbeat", "InternalMemberCluster", klog.KObj(imc))
151191
desiredAgentStatus := imc.GetAgentStatus(fleetv1alpha1.MemberAgent)
152192
if desiredAgentStatus != nil {
153193
desiredAgentStatus.LastReceivedHeartbeat = metav1.Now()
154194
}
155195
}
156196

157197
func (r *Reconciler) markInternalMemberClusterHealthy(imc apis.ConditionedAgentObj) {
158-
klog.V(5).InfoS("markInternalMemberClusterHealthy", "InternalMemberCluster", klog.KObj(imc))
198+
klog.V(4).InfoS("markInternalMemberClusterHealthy", "InternalMemberCluster", klog.KObj(imc))
159199
newCondition := metav1.Condition{
160200
Type: string(fleetv1alpha1.AgentHealthy),
161201
Status: metav1.ConditionTrue,
@@ -174,7 +214,7 @@ func (r *Reconciler) markInternalMemberClusterHealthy(imc apis.ConditionedAgentO
174214
}
175215

176216
func (r *Reconciler) markInternalMemberClusterUnhealthy(imc apis.ConditionedAgentObj, err error) {
177-
klog.V(5).InfoS("markInternalMemberClusterUnhealthy", "InternalMemberCluster", klog.KObj(imc))
217+
klog.V(4).InfoS("markInternalMemberClusterUnhealthy", "InternalMemberCluster", klog.KObj(imc))
178218
newCondition := metav1.Condition{
179219
Type: string(fleetv1alpha1.AgentHealthy),
180220
Status: metav1.ConditionFalse,
@@ -194,7 +234,7 @@ func (r *Reconciler) markInternalMemberClusterUnhealthy(imc apis.ConditionedAgen
194234
}
195235

196236
func (r *Reconciler) markInternalMemberClusterJoined(imc apis.ConditionedAgentObj) {
197-
klog.V(5).InfoS("markInternalMemberClusterJoined", "InternalMemberCluster", klog.KObj(imc))
237+
klog.V(4).InfoS("markInternalMemberClusterJoined", "InternalMemberCluster", klog.KObj(imc))
198238
newCondition := metav1.Condition{
199239
Type: string(fleetv1alpha1.AgentJoined),
200240
Status: metav1.ConditionTrue,
@@ -213,8 +253,28 @@ func (r *Reconciler) markInternalMemberClusterJoined(imc apis.ConditionedAgentOb
213253
imc.SetConditionsWithType(fleetv1alpha1.MemberAgent, newCondition)
214254
}
215255

256+
func (r *Reconciler) markInternalMemberClusterJoinFailed(imc apis.ConditionedAgentObj, err error) {
257+
klog.V(4).InfoS("markInternalMemberCluster join failed", "error", err, "InternalMemberCluster", klog.KObj(imc))
258+
newCondition := metav1.Condition{
259+
Type: string(fleetv1alpha1.AgentJoined),
260+
Status: metav1.ConditionUnknown,
261+
Reason: eventReasonInternalMemberClusterFailedToJoin,
262+
Message: err.Error(),
263+
ObservedGeneration: imc.GetGeneration(),
264+
}
265+
266+
// Joined status changed.
267+
existingCondition := imc.GetConditionWithType(fleetv1alpha1.MemberAgent, newCondition.Type)
268+
if existingCondition == nil || existingCondition.ObservedGeneration != imc.GetGeneration() || existingCondition.Status != newCondition.Status {
269+
r.recorder.Event(imc, corev1.EventTypeNormal, eventReasonInternalMemberClusterFailedToJoin, "internal member cluster failed to join")
270+
klog.ErrorS(err, "agent join failed", "InternalMemberCluster", klog.KObj(imc))
271+
}
272+
273+
imc.SetConditionsWithType(fleetv1alpha1.MemberAgent, newCondition)
274+
}
275+
216276
func (r *Reconciler) markInternalMemberClusterLeft(imc apis.ConditionedAgentObj) {
217-
klog.V(5).InfoS("markInternalMemberClusterLeft", "InternalMemberCluster", klog.KObj(imc))
277+
klog.V(4).InfoS("markInternalMemberClusterLeft", "InternalMemberCluster", klog.KObj(imc))
218278
newCondition := metav1.Condition{
219279
Type: string(fleetv1alpha1.AgentJoined),
220280
Status: metav1.ConditionFalse,
@@ -233,6 +293,26 @@ func (r *Reconciler) markInternalMemberClusterLeft(imc apis.ConditionedAgentObj)
233293
imc.SetConditionsWithType(fleetv1alpha1.MemberAgent, newCondition)
234294
}
235295

296+
func (r *Reconciler) markInternalMemberClusterLeaveFailed(imc apis.ConditionedAgentObj, err error) {
297+
klog.V(4).InfoS("markInternalMemberCluster leave failed", "error", err, "InternalMemberCluster", klog.KObj(imc))
298+
newCondition := metav1.Condition{
299+
Type: string(fleetv1alpha1.AgentJoined),
300+
Status: metav1.ConditionUnknown,
301+
Reason: eventReasonInternalMemberClusterFailedToLeave,
302+
Message: err.Error(),
303+
ObservedGeneration: imc.GetGeneration(),
304+
}
305+
306+
// Joined status changed.
307+
existingCondition := imc.GetConditionWithType(fleetv1alpha1.MemberAgent, newCondition.Type)
308+
if existingCondition == nil || existingCondition.ObservedGeneration != imc.GetGeneration() || existingCondition.Status != newCondition.Status {
309+
r.recorder.Event(imc, corev1.EventTypeNormal, eventReasonInternalMemberClusterFailedToLeave, "internal member cluster failed to leave")
310+
klog.ErrorS(err, "agent leave failed", "InternalMemberCluster", klog.KObj(imc))
311+
}
312+
313+
imc.SetConditionsWithType(fleetv1alpha1.MemberAgent, newCondition)
314+
}
315+
236316
// SetupWithManager sets up the controller with the Manager.
237317
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
238318
r.recorder = mgr.GetEventRecorderFor("InternalMemberClusterController")

pkg/controllers/internalmembercluster/member_controller_integration_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1616
"k8s.io/apimachinery/pkg/types"
1717
ctrl "sigs.k8s.io/controller-runtime"
18+
workcontrollers "sigs.k8s.io/work-api/pkg/controllers"
1819

1920
"go.goms.io/fleet/apis/v1alpha1"
2021
"go.goms.io/fleet/pkg/utils"
@@ -57,7 +58,9 @@ var _ = Describe("Test Internal Member Cluster Controller", func() {
5758
}
5859

5960
By("create the internalMemberCluster reconciler")
60-
r = NewReconciler(k8sClient, k8sClient)
61+
workController := workcontrollers.NewApplyWorkReconciler(
62+
k8sClient, nil, k8sClient, nil, nil, 5, memberClusterNamespace)
63+
r = NewReconciler(k8sClient, k8sClient, workController)
6164
err := r.SetupWithManager(mgr)
6265
Expect(err).ToNot(HaveOccurred())
6366
})

pkg/controllers/internalmembercluster/member_suite_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"sigs.k8s.io/controller-runtime/pkg/envtest"
2121
"sigs.k8s.io/controller-runtime/pkg/log/zap"
2222
"sigs.k8s.io/controller-runtime/pkg/manager"
23+
workv1alpha1 "sigs.k8s.io/work-api/pkg/apis/v1alpha1"
2324

2425
"go.goms.io/fleet/apis/v1alpha1"
2526
)
@@ -60,6 +61,9 @@ var _ = BeforeSuite(func() {
6061
err = v1alpha1.AddToScheme(scheme.Scheme)
6162
Expect(err).NotTo(HaveOccurred())
6263

64+
err = workv1alpha1.AddToScheme(scheme.Scheme)
65+
Expect(err).NotTo(HaveOccurred())
66+
6367
//+kubebuilder:scaffold:scheme
6468
By("construct the k8s client")
6569
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})

pkg/controllers/memberclusterplacement/membercluster_controller.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, key controller.QueueKey) (ct
6363
klog.ErrorS(err, "failed to convert a cluster resource placement", "memberCluster", memberClusterName, "crp", uObj.GetName())
6464
return ctrl.Result{}, err
6565
}
66-
if matchPlacement(&placement, mObj.(*unstructured.Unstructured).DeepCopy()) {
66+
if mObj == nil {
67+
// This is a corner case that the member cluster is deleted before we handle its status change. We can't use match since we don't have its label.
68+
klog.V(3).InfoS("enqueue a placement to reconcile for a deleted member cluster", "memberCluster", memberClusterName, "placement", klog.KObj(&placement))
69+
r.PlacementController.Enqueue(crpList[i])
70+
} else if matchPlacement(&placement, mObj.(*unstructured.Unstructured).DeepCopy()) {
6771
klog.V(3).InfoS("enqueue a placement to reconcile", "memberCluster", memberClusterName, "placement", klog.KObj(&placement))
6872
r.PlacementController.Enqueue(crpList[i])
6973
}

test/e2e/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ make run-e2e
2525
```
2626
or test manually
2727
```shell
28+
kubectl --context=kind-hub-testing delete ns local-path-storage
2829
kubectl --context=kind-hub-testing apply -f examples/fleet_v1alpha1_membercluster.yaml
2930
kubectl --context=kind-hub-testing apply -f test/integration/manifests/resources
3031
kubectl --context=kind-hub-testing apply -f test/integration/manifests/resources
@@ -46,4 +47,5 @@ kubectl --context=kind-member-testing -n fleet-system get pod
4647
5.uninstall the resources
4748
```shell
4849
make uninstall-helm
50+
make clean-e2e-tests
4951
```

test/e2e/e2e_test.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
. "github.com/onsi/gomega"
1515
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1616
"k8s.io/apimachinery/pkg/runtime"
17-
"k8s.io/apimachinery/pkg/runtime/serializer"
1817
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
1918
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
2019
workv1alpha1 "sigs.k8s.io/work-api/pkg/apis/v1alpha1"
@@ -39,10 +38,6 @@ var (
3938
// This namespace in HubCluster will store v1alpha1.Work to simulate Work-related features in Hub Cluster.
4039
workNamespace = testutils.NewNamespace(fmt.Sprintf(utils.NamespaceNameFormat, MemberCluster.ClusterName))
4140

42-
// Used to decode an unstructured object.
43-
genericCodecs = serializer.NewCodecFactory(scheme)
44-
genericCodec = genericCodecs.UniversalDeserializer()
45-
4641
//go:embed manifests
4742
TestManifestFiles embed.FS
4843
)

test/e2e/join_leave_member_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ import (
88
"context"
99

1010
. "github.com/onsi/ginkgo/v2"
11-
"go.goms.io/fleet/apis/v1alpha1"
12-
testutils "go.goms.io/fleet/test/e2e/utils"
1311
corev1 "k8s.io/api/core/v1"
1412
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
14+
"go.goms.io/fleet/apis/v1alpha1"
15+
testutils "go.goms.io/fleet/test/e2e/utils"
1516
)
1617

1718
var _ = Describe("Join/leave member cluster testing", func() {

0 commit comments

Comments
 (0)