From efb9f761e4dc419b8225e64e930ec4e658c20b76 Mon Sep 17 00:00:00 2001 From: Nahshon Unna-Tsameret Date: Thu, 3 Nov 2022 18:10:36 +0200 Subject: [PATCH] Allow running the SSP Operator on plain kubernetes SSP can't run on a plain kubernetes, because it requires the `Infrastructure` kind to be present on the cluster. When deploying KubeVirt with HCO using OLM on plain kubernetes, the deployment is never completed because SSP operator never becomes ready. More details and full description of the issue can be found here: https://github.com/kubevirt/hyperconverged-cluster-operator/issues/2129 This commit disables the reconcilers and all the watches, in case the `Infrastructure` is not found on the cluster, instead of killing the process. That way, the SSP operator is runing and responding to the health and ready checks, but does nothing else. The result is that SSP is still not working on plain kubernetest, but the OLM deployment is successfully completed. Signed-off-by: Nahshon Unna-Tsameret --- controllers/setup.go | 52 ++++++++++--------- controllers/ssp_controller.go | 10 ++-- internal/common/environment.go | 6 +++ internal/common/resource.go | 31 +++++++++++ internal/common/resource_test.go | 2 + .../common-templates/reconcile_test.go | 2 + .../operands/data-sources/reconcile_test.go | 2 + internal/operands/metrics/reconcile_test.go | 3 ++ .../template-validator/reconcile_test.go | 2 + 9 files changed, 81 insertions(+), 29 deletions(-) diff --git a/controllers/setup.go b/controllers/setup.go index e4d892ac8..e423dd8f9 100644 --- a/controllers/setup.go +++ b/controllers/setup.go @@ -36,14 +36,19 @@ func CreateAndStartReconciler(ctx context.Context, mgr controllerruntime.Manager node_labeller.New(), } + mgrCtx, cancel := context.WithCancel(ctx) + defer cancel() + + infrastructureTopology, err := common.GetInfrastructureTopology(mgrCtx, mgr.GetAPIReader()) + if err != nil { + return err + } + var requiredCrds []string for i := range sspOperands { requiredCrds = append(requiredCrds, sspOperands[i].RequiredCrds()...) } - mgrCtx, cancel := context.WithCancel(ctx) - defer cancel() - crdWatch := crd_watch.New(requiredCrds...) // Cleanly stops the manager and exit. The pod will be restarted. crdWatch.AllCrdsAddedHandler = cancel @@ -60,33 +65,30 @@ func CreateAndStartReconciler(ctx context.Context, mgr controllerruntime.Manager ) } - err = mgr.Add(crdWatch) - if err != nil { - return err - } - - infrastructureTopology, err := common.GetInfrastructureTopology(mgrCtx, mgr.GetAPIReader()) - if err != nil { - return fmt.Errorf("failed to get infrastructure topology: %w", err) - } - - serviceController, err := CreateServiceController(mgrCtx, mgr) - if err != nil { - return fmt.Errorf("failed to create service controller: %w", err) - } + if len(infrastructureTopology) > 0 { + err = mgr.Add(crdWatch) + if err != nil { + return err + } - err = mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { - err := serviceController.Start(ctx, mgr) + serviceController, err := CreateServiceController(mgrCtx, mgr) if err != nil { - return fmt.Errorf("error starting serviceController: %w", err) + return fmt.Errorf("failed to create service controller: %w", err) } - mgr.GetLogger().Info("Services Controller started") + err = mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { + err := serviceController.Start(ctx, mgr) + if err != nil { + return fmt.Errorf("error starting serviceController: %w", err) + } - return nil - })) - if err != nil { - return fmt.Errorf("error adding service controller: %w", err) + mgr.GetLogger().Info("Services Controller started") + + return nil + })) + if err != nil { + return fmt.Errorf("error adding service controller: %w", err) + } } reconciler := NewSspReconciler(mgr.GetClient(), mgr.GetAPIReader(), infrastructureTopology, sspOperands, crdWatch) diff --git a/controllers/ssp_controller.go b/controllers/ssp_controller.go index b73142365..2fd160acd 100644 --- a/controllers/ssp_controller.go +++ b/controllers/ssp_controller.go @@ -117,11 +117,13 @@ func (r *sspReconciler) setupController(mgr ctrl.Manager) error { builder := ctrl.NewControllerManagedBy(mgr) watchSspResource(builder) - r.areCrdsMissing = len(r.crdWatch.MissingCrds()) > 0 + if len(r.topologyMode) > 0 { + r.areCrdsMissing = len(r.crdWatch.MissingCrds()) > 0 - // Register watches for created objects only if all required CRDs exist - watchClusterResources(builder, r.crdWatch, r.operands, eventHandlerHook) - watchNamespacedResources(builder, r.crdWatch, r.operands, eventHandlerHook) + // Register watches for created objects only if all required CRDs exist + watchClusterResources(builder, r.crdWatch, r.operands, eventHandlerHook) + watchNamespacedResources(builder, r.crdWatch, r.operands, eventHandlerHook) + } return builder.Complete(r) } diff --git a/internal/common/environment.go b/internal/common/environment.go index 6dbdd3ebf..df1b4766a 100644 --- a/internal/common/environment.go +++ b/internal/common/environment.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io/ioutil" + "k8s.io/apimachinery/pkg/api/meta" "os" "strings" @@ -36,6 +37,11 @@ func GetOperatorVersion() string { func GetInfrastructureTopology(ctx context.Context, c client.Reader) (osconfv1.TopologyMode, error) { infraConfig := &osconfv1.Infrastructure{} if err := c.Get(ctx, types.NamespacedName{Name: "cluster"}, infraConfig); err != nil { + if _, ok := err.(*meta.NoKindMatchError); ok { + // if the Infrastructure type is not known in this cluster, then it's a plain kubernetes. + // we'll use the stale reconciler, so the operator will actually do nothing + return "", nil + } return "", err } diff --git a/internal/common/resource.go b/internal/common/resource.go index 5eb0f93c5..c7a5f3a91 100644 --- a/internal/common/resource.go +++ b/internal/common/resource.go @@ -9,6 +9,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -207,6 +208,10 @@ func CreateOrUpdate(request *Request) ReconcileBuilder { panic("Request should not be nil") } + if request.TopologyMode == "" { + return theStaleReconsiler + } + return &reconcileBuilder{ request: request, updateFunc: func(_, _ client.Object) { @@ -431,3 +436,29 @@ func ResourceDeletedResult(resource client.Object, res OperationResult) Reconcil OperationResult: res, } } + +// staleReconsiler is a reconsiler that does nothing, in case the operator is running on a plain kubernetes +type staleReconsiler struct{} + +func (r *staleReconsiler) NamespacedResource(client.Object) ReconcileBuilder { return r } +func (r *staleReconsiler) ClusterResource(client.Object) ReconcileBuilder { return r } +func (r *staleReconsiler) WithAppLabels(name string, component AppComponent) ReconcileBuilder { + return r +} +func (r *staleReconsiler) UpdateFunc(ResourceUpdateFunc) ReconcileBuilder { return r } +func (r *staleReconsiler) StatusFunc(ResourceStatusFunc) ReconcileBuilder { return r } +func (r *staleReconsiler) ImmutableSpec(getter ResourceSpecGetter) ReconcileBuilder { return r } +func (r *staleReconsiler) Options(options ReconcileOptions) ReconcileBuilder { return r } +func (r *staleReconsiler) Reconcile() (ReconcileResult, error) { + return ReconcileResult{ + Status: ResourceStatus{ + Progressing: pointer.StringPtr("false"), + NotAvailable: pointer.StringPtr("true"), + Degraded: pointer.StringPtr("false"), + }, + Resource: nil, + OperationResult: OperationResultNone, + }, nil +} + +var theStaleReconsiler = &staleReconsiler{} diff --git a/internal/common/resource_test.go b/internal/common/resource_test.go index eb1f17cc5..19ba8cde8 100644 --- a/internal/common/resource_test.go +++ b/internal/common/resource_test.go @@ -6,6 +6,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + osconfv1 "github.com/openshift/api/config/v1" libhandler "github.com/operator-framework/operator-lib/handler" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -59,6 +60,7 @@ var _ = Describe("Resource", func() { }, Logger: log, VersionCache: VersionCache{}, + TopologyMode: osconfv1.HighlyAvailableTopologyMode, } }) diff --git a/internal/operands/common-templates/reconcile_test.go b/internal/operands/common-templates/reconcile_test.go index fd3cd3c91..32ed710cd 100644 --- a/internal/operands/common-templates/reconcile_test.go +++ b/internal/operands/common-templates/reconcile_test.go @@ -7,6 +7,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + osconfv1 "github.com/openshift/api/config/v1" templatev1 "github.com/openshift/api/template/v1" libhandler "github.com/operator-framework/operator-lib/handler" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -78,6 +79,7 @@ var _ = Describe("Common-Templates operand", func() { }, Logger: log, VersionCache: common.VersionCache{}, + TopologyMode: osconfv1.HighlyAvailableTopologyMode, } }) diff --git a/internal/operands/data-sources/reconcile_test.go b/internal/operands/data-sources/reconcile_test.go index 34416a4b1..c6f081862 100644 --- a/internal/operands/data-sources/reconcile_test.go +++ b/internal/operands/data-sources/reconcile_test.go @@ -7,6 +7,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + osconfv1 "github.com/openshift/api/config/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -72,6 +73,7 @@ var _ = Describe("Data-Sources operand", func() { }, Logger: log, VersionCache: common.VersionCache{}, + TopologyMode: osconfv1.HighlyAvailableTopologyMode, } }) diff --git a/internal/operands/metrics/reconcile_test.go b/internal/operands/metrics/reconcile_test.go index cde9c8595..2baa9354d 100644 --- a/internal/operands/metrics/reconcile_test.go +++ b/internal/operands/metrics/reconcile_test.go @@ -6,6 +6,8 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + osconfv1 "github.com/openshift/api/config/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" . "kubevirt.io/ssp-operator/internal/test-utils" @@ -53,6 +55,7 @@ var _ = Describe("Metrics operand", func() { }, Logger: log, VersionCache: common.VersionCache{}, + TopologyMode: osconfv1.HighlyAvailableTopologyMode, } _, err := operand.Reconcile(&request) diff --git a/internal/operands/template-validator/reconcile_test.go b/internal/operands/template-validator/reconcile_test.go index 2e3b71934..efbd066d5 100644 --- a/internal/operands/template-validator/reconcile_test.go +++ b/internal/operands/template-validator/reconcile_test.go @@ -9,6 +9,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + osconfv1 "github.com/openshift/api/config/v1" admission "k8s.io/api/admissionregistration/v1" apps "k8s.io/api/apps/v1" core "k8s.io/api/core/v1" @@ -75,6 +76,7 @@ var _ = Describe("Template validator operand", func() { }, Logger: log, VersionCache: common.VersionCache{}, + TopologyMode: osconfv1.HighlyAvailableTopologyMode, } })