From 920a25a02b5d1deacbdece7d233c9f4af41d4f13 Mon Sep 17 00:00:00 2001 From: redpinecube Date: Mon, 4 Aug 2025 10:39:26 -0500 Subject: [PATCH 1/6] implement : VerifyControlPlaneOnHostingCluster Signed-off-by: redpinecube --- pkg/kubeconfig/extensions.go | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/pkg/kubeconfig/extensions.go b/pkg/kubeconfig/extensions.go index 16bb0468..c223b538 100644 --- a/pkg/kubeconfig/extensions.go +++ b/pkg/kubeconfig/extensions.go @@ -19,6 +19,10 @@ package kubeconfig import ( "encoding/json" "fmt" + tenancyv1alpha1 "github.com/kubestellar/kubeflex/api/v1alpha1" + "github.com/kubestellar/kubeflex/cmd/kflex/common" + "github.com/kubestellar/kubeflex/pkg/client" + "k8s.io/client-go/tools/clientcmd" "time" corev1 "k8s.io/api/core/v1" @@ -234,12 +238,32 @@ func CheckHostingClusterContextName(kconf clientcmdapi.Config) string { } } -func VerifyControlPlaneOnHostingCluster(kconf clientcmdapi.Config, ctxName string) string { - // TODO: implement actual control plane verification logic - return DiagnosisStatusOK +func VerifyControlPlaneOnHostingCluster(cp common.CP, ctxName string) string { + c, err := client.GetClient(cp.Kubeconfig) + if err != nil { + return DiagnosisStatusCritical + } + + var controlPlanes tenancyv1alpha1.ControlPlaneList + if err := c.List(cp.Ctx, &controlPlanes); err != nil { + return DiagnosisStatusCritical + } + + for _, controlPlane := range controlPlanes.Items { + if controlPlane.Name == ctxName { + return DiagnosisStatusOK + } + } + + return DiagnosisStatusMissing } -func CheckContextScopeKubeflexExtensionSet(kconf clientcmdapi.Config, ctxName string) string { +func CheckContextScopeKubeflexExtensionSet(cp common.CP, ctxName string) string { + kconf, err := clientcmd.LoadFromFile(cp.Kubeconfig) + if err != nil { + return DiagnosisStatusCritical + } + ctx, ok := kconf.Contexts[ctxName] if !ok { return DiagnosisStatusMissing // Context not found @@ -269,7 +293,7 @@ func CheckContextScopeKubeflexExtensionSet(kconf clientcmdapi.Config, ctxName st return DiagnosisStatusWarning } - status := VerifyControlPlaneOnHostingCluster(kconf, ctxName) + status := VerifyControlPlaneOnHostingCluster(cp, ctxName) if status != DiagnosisStatusOK { return status } From 30be101a7f0856b4d145660b5426097aecdacd5a Mon Sep 17 00:00:00 2001 From: redpinecube Date: Mon, 4 Aug 2025 11:01:43 -0500 Subject: [PATCH 2/6] update tests for CheckContextScopeKubeflexExtension Signed-off-by: redpinecube --- pkg/kubeconfig/extensions_test.go | 42 ++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/pkg/kubeconfig/extensions_test.go b/pkg/kubeconfig/extensions_test.go index ba899649..d8ba6ea3 100644 --- a/pkg/kubeconfig/extensions_test.go +++ b/pkg/kubeconfig/extensions_test.go @@ -2,6 +2,9 @@ package kubeconfig import ( "fmt" + "github.com/kubestellar/kubeflex/cmd/kflex/common" + "k8s.io/client-go/tools/clientcmd" + "os" "testing" "k8s.io/apimachinery/pkg/runtime" @@ -206,6 +209,27 @@ func TestCheckHostingClusterContextNameSingle(t *testing.T) { } } +func writeKubeconfigToTempFile(t *testing.T, kconf *api.Config) string { + data, err := clientcmd.Write(*kconf) + if err != nil { + t.Fatalf("failed to serialize kubeconfig: %v", err) + } + + tmpfile, err := os.CreateTemp("", "kubeconfig-*.yaml") + if err != nil { + t.Fatalf("failed to create temp file: %v", err) + } + + if _, err := tmpfile.Write(data); err != nil { + t.Fatalf("failed to write kubeconfig: %v", err) + } + if err := tmpfile.Close(); err != nil { + t.Fatalf("failed to close temp file: %v", err) + } + + return tmpfile.Name() +} + func TestCheckContextScopeKubeflexExtensionSetNoKubeflexExtensions(t *testing.T) { kconf := api.NewConfig() kconf.Clusters["cluster1"] = &api.Cluster{Server: "https://example.com:6443"} @@ -216,7 +240,11 @@ func TestCheckContextScopeKubeflexExtensionSetNoKubeflexExtensions(t *testing.T) } kconf.CurrentContext = "ctx1" - result := CheckContextScopeKubeflexExtensionSet(*kconf, "ctx1") + tmpFile := writeKubeconfigToTempFile(t, kconf) + defer os.Remove(tmpFile) + + cp := common.NewCP(tmpFile) + result := CheckContextScopeKubeflexExtensionSet(cp, "ctx1") if result != DiagnosisStatusMissing { t.Errorf("Expected %s, got %s", DiagnosisStatusMissing, result) } @@ -237,7 +265,11 @@ func TestCheckContextScopeKubeflexExtensionSetNoData(t *testing.T) { } kconf.CurrentContext = "ctx1" - result := CheckContextScopeKubeflexExtensionSet(*kconf, "ctx1") + tmpFile := writeKubeconfigToTempFile(t, kconf) + defer os.Remove(tmpFile) + + cp := common.NewCP(tmpFile) + result := CheckContextScopeKubeflexExtensionSet(cp, "ctx1") if result != DiagnosisStatusCritical { t.Errorf("Expected %s, got %s", DiagnosisStatusCritical, result) } @@ -258,7 +290,11 @@ func TestCheckContextScopeKubeflexExtensionSetPartialData(t *testing.T) { } kconf.CurrentContext = "ctx1" - result := CheckContextScopeKubeflexExtensionSet(*kconf, "ctx1") + tmpFile := writeKubeconfigToTempFile(t, kconf) + defer os.Remove(tmpFile) + + cp := common.NewCP(tmpFile) + result := CheckContextScopeKubeflexExtensionSet(cp, "ctx1") if result != DiagnosisStatusWarning { t.Errorf("Expected %s, got %s", DiagnosisStatusWarning, result) } From e47904d0a30f7466f4a61ae21e1bf6d3adb9931b Mon Sep 17 00:00:00 2001 From: redpinecube Date: Mon, 4 Aug 2025 13:17:18 -0500 Subject: [PATCH 3/6] implement : CountKubeflexControlPlaneContexts Signed-off-by: redpinecube --- pkg/kubeconfig/extensions.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pkg/kubeconfig/extensions.go b/pkg/kubeconfig/extensions.go index c223b538..781e768f 100644 --- a/pkg/kubeconfig/extensions.go +++ b/pkg/kubeconfig/extensions.go @@ -300,3 +300,30 @@ func CheckContextScopeKubeflexExtensionSet(cp common.CP, ctxName string) string return DiagnosisStatusOK } + +func CountKubeflexControlPlaneContexts(kconf clientcmdapi.Config) int { + count := 0 + + for _, ctx := range kconf.Contexts { + ext := ctx.Extensions + if ext == nil { + continue + } + + runtimeObj, ok := ext[ExtensionKubeflexKey] + if !ok { + continue + } + + var extData RuntimeKubeflexExtension + if err := ConvertRuntimeObjectToRuntimeExtension(runtimeObj, &extData); err != nil { + continue + } + + if cpName, ok := extData.Data[ExtensionControlPlaneName]; ok && cpName != "" { + count++ + } + } + + return count +} From 13256f1f144a55b83f8292330355f0c34fd8d31e Mon Sep 17 00:00:00 2001 From: redpinecube Date: Mon, 4 Aug 2025 13:52:48 -0500 Subject: [PATCH 4/6] test : CountKubeflexControlPlaneContexts - 0 control planes Signed-off-by: redpinecube --- pkg/kubeconfig/extensions_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pkg/kubeconfig/extensions_test.go b/pkg/kubeconfig/extensions_test.go index d8ba6ea3..a8687a87 100644 --- a/pkg/kubeconfig/extensions_test.go +++ b/pkg/kubeconfig/extensions_test.go @@ -299,3 +299,24 @@ func TestCheckContextScopeKubeflexExtensionSetPartialData(t *testing.T) { t.Errorf("Expected %s, got %s", DiagnosisStatusWarning, result) } } + +func TestCountKubeflexControlPlaneContextsZero(t *testing.T) { + kconf := api.NewConfig() + kconf.Clusters["cluster1"] = &api.Cluster{Server: "https://example.com:6443"} + kconf.AuthInfos["user1"] = &api.AuthInfo{Token: "token"} + + ext := NewRuntimeKubeflexExtension() + ext.Data[ExtensionContextsIsHostingCluster] = "true" + + kconf.Contexts["ctx1"] = &api.Context{ + Cluster: "cluster1", + AuthInfo: "user1", + Extensions: map[string]runtime.Object{ExtensionKubeflexKey: ext}, + } + + expected := 0 + got := CountKubeflexControlPlaneContexts(*kconf) + if got != expected { + t.Errorf("Expected %d control plane context(s), got %d", expected, got) + } +} From 5a2814660cbb63c2d4d94aeaea60cc27a075b860 Mon Sep 17 00:00:00 2001 From: redpinecube Date: Mon, 4 Aug 2025 14:00:32 -0500 Subject: [PATCH 5/6] test : CountKubeflexControlPlaneContexts with one control plane Signed-off-by: redpinecube --- pkg/kubeconfig/extensions_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pkg/kubeconfig/extensions_test.go b/pkg/kubeconfig/extensions_test.go index a8687a87..88994895 100644 --- a/pkg/kubeconfig/extensions_test.go +++ b/pkg/kubeconfig/extensions_test.go @@ -320,3 +320,33 @@ func TestCountKubeflexControlPlaneContextsZero(t *testing.T) { t.Errorf("Expected %d control plane context(s), got %d", expected, got) } } + +func TestCountKubeflexControlPlaneContextsOne(t *testing.T) { + kconf := api.NewConfig() + kconf.Clusters["cluster1"] = &api.Cluster{Server: "https://example.com:6443"} + kconf.Clusters["cluster2"] = &api.Cluster{Server: "https://example2.com:6443"} + kconf.AuthInfos["user1"] = &api.AuthInfo{Token: "token"} + + hostExt := NewRuntimeKubeflexExtension() + hostExt.Data[ExtensionContextsIsHostingCluster] = "true" + kconf.Contexts["ctx1"] = &api.Context{ + Cluster: "cluster1", + AuthInfo: "user1", + Extensions: map[string]runtime.Object{ExtensionKubeflexKey: hostExt}, + } + + // Control plane + cpExt := NewRuntimeKubeflexExtension() + cpExt.Data[ExtensionControlPlaneName] = "control-plane-1" + kconf.Contexts["ctx2"] = &api.Context{ + Cluster: "cluster2", + AuthInfo: "user1", + Extensions: map[string]runtime.Object{ExtensionKubeflexKey: cpExt}, + } + + expected := 1 + got := CountKubeflexControlPlaneContexts(*kconf) + if got != expected { + t.Errorf("Expected %d control plane context(s), got %d", expected, got) + } +} From 97edc99adc52267aba53141001ae39d03c8b8fdb Mon Sep 17 00:00:00 2001 From: redpinecube Date: Mon, 4 Aug 2025 14:05:45 -0500 Subject: [PATCH 6/6] test : CountKubeflexControlPlaneContexts with multiple control planes Signed-off-by: redpinecube --- pkg/kubeconfig/extensions_test.go | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/pkg/kubeconfig/extensions_test.go b/pkg/kubeconfig/extensions_test.go index 88994895..d0b224b2 100644 --- a/pkg/kubeconfig/extensions_test.go +++ b/pkg/kubeconfig/extensions_test.go @@ -350,3 +350,53 @@ func TestCountKubeflexControlPlaneContextsOne(t *testing.T) { t.Errorf("Expected %d control plane context(s), got %d", expected, got) } } + +func TestCountKubeflexControlPlaneContextsMultiple(t *testing.T) { + kconf := api.NewConfig() + kconf.Clusters["cluster1"] = &api.Cluster{Server: "https://example.com:6443"} + kconf.Clusters["cluster2"] = &api.Cluster{Server: "https://example2.com:6443"} + kconf.Clusters["cluster3"] = &api.Cluster{Server: "https://example3.com:6443"} + kconf.Clusters["cluster4"] = &api.Cluster{Server: "https://example4.com:6443"} + kconf.AuthInfos["user1"] = &api.AuthInfo{Token: "token"} + + hostExt := NewRuntimeKubeflexExtension() + hostExt.Data[ExtensionContextsIsHostingCluster] = "true" + kconf.Contexts["ctx1"] = &api.Context{ + Cluster: "cluster1", + AuthInfo: "user1", + Extensions: map[string]runtime.Object{ExtensionKubeflexKey: hostExt}, + } + + // Control plane 1 + cpExt1 := NewRuntimeKubeflexExtension() + cpExt1.Data[ExtensionControlPlaneName] = "cp-1" + kconf.Contexts["ctx2"] = &api.Context{ + Cluster: "cluster2", + AuthInfo: "user1", + Extensions: map[string]runtime.Object{ExtensionKubeflexKey: cpExt1}, + } + + // Control plane 2 + cpExt2 := NewRuntimeKubeflexExtension() + cpExt2.Data[ExtensionControlPlaneName] = "cp-2" + kconf.Contexts["ctx3"] = &api.Context{ + Cluster: "cluster3", + AuthInfo: "user1", + Extensions: map[string]runtime.Object{ExtensionKubeflexKey: cpExt2}, + } + + // Control plane 3 + cpExt3 := NewRuntimeKubeflexExtension() + cpExt3.Data[ExtensionControlPlaneName] = "cp-3" + kconf.Contexts["ctx4"] = &api.Context{ + Cluster: "cluster4", + AuthInfo: "user1", + Extensions: map[string]runtime.Object{ExtensionKubeflexKey: cpExt3}, + } + + expected := 3 + got := CountKubeflexControlPlaneContexts(*kconf) + if got != expected { + t.Errorf("Expected %d control plane context(s), got %d", expected, got) + } +}