Skip to content

Commit 73777fd

Browse files
authored
Add e2e checks for remote HCP GC of etcd certs and resources (#1264)
Signed-off-by: kahirokunn <[email protected]>
1 parent 03f2909 commit 73777fd

File tree

1 file changed

+84
-0
lines changed

1 file changed

+84
-0
lines changed

e2e/remote_hcp_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ limitations under the License.
1919
package e2e
2020

2121
import (
22+
"context"
2223
"encoding/base64"
2324
"fmt"
2425
"path/filepath"
@@ -27,14 +28,18 @@ import (
2728

2829
"github.com/k0sproject/k0smotron/e2e/util"
2930
"github.com/stretchr/testify/require"
31+
apierrors "k8s.io/apimachinery/pkg/api/errors"
3032
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3133
"k8s.io/apimachinery/pkg/runtime"
34+
"k8s.io/apimachinery/pkg/util/wait"
3235
"k8s.io/utils/ptr"
3336
"sigs.k8s.io/cluster-api/test/framework"
3437
capiframework "sigs.k8s.io/cluster-api/test/framework"
3538
"sigs.k8s.io/cluster-api/test/framework/bootstrap"
3639
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
3740
capiutil "sigs.k8s.io/cluster-api/util"
41+
"sigs.k8s.io/cluster-api/util/secret"
42+
crclient "sigs.k8s.io/controller-runtime/pkg/client"
3843
"sigs.k8s.io/kind/pkg/apis/config/v1alpha4"
3944
"sigs.k8s.io/kind/pkg/cluster"
4045

@@ -144,6 +149,85 @@ func remoteHCPSpec(t *testing.T) {
144149
})
145150
require.NoError(t, err)
146151
fmt.Println("Worker nodes are reeady!")
152+
153+
// Verify resources in the hosting cluster and their owner references, then validate GC on deletion
154+
ownerName := fmt.Sprintf("%s-root-owner", clusterName)
155+
156+
// Pre-delete: ensure external owner and etcd cert secrets exist with correct OwnerReferences
157+
{
158+
cm := &corev1.ConfigMap{}
159+
require.NoError(t, hostingClusterProxy.GetClient().Get(ctx, crclient.ObjectKey{Namespace: namespace.Name, Name: ownerName}, cm))
160+
161+
apiserverEtcdClientSecret := &corev1.Secret{}
162+
etcdServerSecret := &corev1.Secret{}
163+
etcdPeerSecret := &corev1.Secret{}
164+
165+
require.NoError(t, hostingClusterProxy.GetClient().Get(ctx, crclient.ObjectKey{Namespace: namespace.Name, Name: secret.Name(clusterName, secret.APIServerEtcdClient)}, apiserverEtcdClientSecret))
166+
require.NoError(t, hostingClusterProxy.GetClient().Get(ctx, crclient.ObjectKey{Namespace: namespace.Name, Name: secret.Name(clusterName, "etcd-server")}, etcdServerSecret))
167+
require.NoError(t, hostingClusterProxy.GetClient().Get(ctx, crclient.ObjectKey{Namespace: namespace.Name, Name: secret.Name(clusterName, "etcd-peer")}, etcdPeerSecret))
168+
169+
assertOwnedByRoot := func(obj metav1.Object) {
170+
found := false
171+
for _, or := range obj.GetOwnerReferences() {
172+
if or.Controller != nil && *or.Controller && or.Kind == "ConfigMap" && or.Name == ownerName {
173+
found = true
174+
break
175+
}
176+
}
177+
require.True(t, found, fmt.Sprintf("%s should be controlled by %s", obj.GetName(), ownerName))
178+
}
179+
180+
assertOwnedByRoot(apiserverEtcdClientSecret)
181+
assertOwnedByRoot(etcdServerSecret)
182+
assertOwnedByRoot(etcdPeerSecret)
183+
}
184+
185+
// Delete the management Cluster and verify garbage collection in the hosting cluster
186+
{
187+
fmt.Println("Deleting Cluster and waiting for GC of hosting-cluster resources")
188+
require.NoError(t, bootstrapClusterProxy.GetClient().Delete(ctx, cluster))
189+
190+
// Wait for Cluster to be deleted in the management cluster
191+
require.NoError(t, wait.PollUntilContextTimeout(ctx, 1*time.Second, 3*time.Minute, true, func(ctx context.Context) (bool, error) {
192+
cl := cluster.DeepCopy()
193+
err := bootstrapClusterProxy.GetClient().Get(ctx, crclient.ObjectKeyFromObject(cluster), cl)
194+
return apierrors.IsNotFound(err), nil
195+
}))
196+
197+
// Wait for external owner to be deleted in hosting cluster
198+
require.NoError(t, wait.PollUntilContextTimeout(ctx, 1*time.Second, 3*time.Minute, true, func(ctx context.Context) (bool, error) {
199+
cm := &corev1.ConfigMap{}
200+
err := hostingClusterProxy.GetClient().Get(ctx, crclient.ObjectKey{Namespace: namespace.Name, Name: ownerName}, cm)
201+
return apierrors.IsNotFound(err), nil
202+
}))
203+
204+
// Ensure etcd cert Secrets are garbage collected in hosting cluster
205+
for _, name := range []string{
206+
secret.Name(clusterName, secret.APIServerEtcdClient),
207+
secret.Name(clusterName, "etcd-server"),
208+
secret.Name(clusterName, "etcd-peer"),
209+
} {
210+
require.NoError(t, wait.PollUntilContextTimeout(ctx, 1*time.Second, 2*time.Minute, true, func(ctx context.Context) (bool, error) {
211+
sec := &corev1.Secret{}
212+
err := hostingClusterProxy.GetClient().Get(ctx, crclient.ObjectKey{Namespace: namespace.Name, Name: name}, sec)
213+
return apierrors.IsNotFound(err), nil
214+
}))
215+
}
216+
217+
// Optionally ensure key workload resources are GC'd as well (etcd StatefulSet & Service)
218+
etcdStsName := fmt.Sprintf("kmc-%s-etcd", clusterName)
219+
etcdSvcName := fmt.Sprintf("kmc-%s-etcd", clusterName)
220+
require.NoError(t, wait.PollUntilContextTimeout(ctx, 1*time.Second, 2*time.Minute, true, func(ctx context.Context) (bool, error) {
221+
sts := &appsv1.StatefulSet{}
222+
err := hostingClusterProxy.GetClient().Get(ctx, crclient.ObjectKey{Namespace: namespace.Name, Name: etcdStsName}, sts)
223+
return apierrors.IsNotFound(err), nil
224+
}))
225+
require.NoError(t, wait.PollUntilContextTimeout(ctx, 1*time.Second, 2*time.Minute, true, func(ctx context.Context) (bool, error) {
226+
svc := &corev1.Service{}
227+
err := hostingClusterProxy.GetClient().Get(ctx, crclient.ObjectKey{Namespace: namespace.Name, Name: etcdSvcName}, svc)
228+
return apierrors.IsNotFound(err), nil
229+
}))
230+
}
147231
}
148232

149233
// deployHostingCluster deploys a cluster for hosting control planes.

0 commit comments

Comments
 (0)