Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions cmd/hyperconverged-cluster-operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"maps"
"os"

"github.com/go-logr/logr"
netattdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
openshiftconfigv1 "github.com/openshift/api/config/v1"
consolev1 "github.com/openshift/api/console/v1"
Expand Down Expand Up @@ -143,6 +144,7 @@ func main() {

// Detect OpenShift version
ctx := context.Background()
ctx = logr.NewContext(ctx, logger)
err = cmdcommon.ClusterInitializations(ctx, apiClient, scheme, logger)
cmdHelper.ExitOnError(err, "Cannot detect cluster type")

Expand All @@ -153,6 +155,7 @@ func main() {

// Determine Perses availability before creating the manager so we can shape the cache accordingly
persesAvailable := hcoutil.IsPersesAvailable(ctx, apiClient)
logger.Info("Perses CRD availability at startup", "available", persesAvailable)

// Create a new Cmd to provide shared dependencies and start components
mgr, err := manager.New(cfg, getManagerOptions(operatorNamespace, needLeaderElection, ci, scheme, persesAvailable))
Expand Down Expand Up @@ -237,12 +240,9 @@ func main() {
logger.Error(err, "unable to create controller", "controller", "Observability")
os.Exit(1)
}
// Register Perses controller only if CRDs are available; otherwise avoid watching unknown types.
if persesAvailable {
if err = perses.SetupPersesWithManager(mgr, ownresources.GetDeploymentRef()); err != nil {
logger.Error(err, "unable to create controller", "controller", "ObservabilityPerses")
os.Exit(1)
}
if err = perses.SetupPersesWithManager(ctx, mgr, ownresources.GetDeploymentRef()); err != nil {
logger.Error(err, "unable to create controller", "controller", "ObservabilityPerses")
os.Exit(1)
}
}

Expand Down
14 changes: 9 additions & 5 deletions controllers/crd/crd_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (r *ReconcileCRD) operatorRestart() {
// Reconcile refreshes KubeDesheduler view on ClusterInfo singleton
func (r *ReconcileCRD) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {

log.Info("Triggered by a CRD")
log.Info("Triggered by a CRD", "CRD", req.Name)
if !hcoutil.GetClusterInfo().IsDeschedulerAvailable() {
if hcoutil.GetClusterInfo().IsDeschedulerCRDDeployed(ctx, r.client) {
log.Info("KubeDescheduler CRD got deployed, restarting the operator to reconfigure the operator for the new kind")
Expand All @@ -103,10 +103,14 @@ func (r *ReconcileCRD) Reconcile(ctx context.Context, req reconcile.Request) (re
}

// If Perses CRDs became available after boot, restart once to register Perses controller and cache the new GVKs.
if !r.persesAvailableOnBoot && hcoutil.IsPersesAvailable(ctx, r.client) {
log.Info("Perses CRDs detected, restarting the operator to register the Perses controller")
r.eventEmitter.EmitEvent(nil, corev1.EventTypeNormal, "Perses CRDs detected", "Restarting the operator to register the Perses controller")
r.operatorRestart()
if !r.persesAvailableOnBoot {
available := hcoutil.IsPersesAvailable(ctx, r.client)
log.Info("Perses CRDs availability after boot", "available", available, "CRD", req.Name)
if available {
log.Info("Perses CRDs detected, restarting the operator to register the Perses controller")
r.eventEmitter.EmitEvent(nil, corev1.EventTypeNormal, "Perses CRDs detected", "Restarting the operator to register the Perses controller")
r.operatorRestart()
}
}

return reconcile.Result{}, nil
Expand Down
6 changes: 4 additions & 2 deletions controllers/perses/perses_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,16 @@ func (r *PersesReconciler) Reconcile(ctx context.Context, req reconcile.Request)
return reconcile.Result{}, err
}

func SetupPersesWithManager(mgr manager.Manager, ownerRef metav1.OwnerReference) error {
func SetupPersesWithManager(ctx context.Context, mgr manager.Manager, ownerRef metav1.OwnerReference) error {
persesLog.Info("Setting up Perses controller")

// Skip registration cleanly when Perses CRDs are not installed (e.g., unit/CI envs)
if !checkPersesAvailable(context.Background(), mgr.GetClient()) {
available := checkPersesAvailable(ctx, mgr.GetClient())
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this phase, the mgr.Client cache is not populate yet. You'll need to use the apiClient from main (pass it as a parameter to SetupPersesWithManager; e.g.

func SetupPersesWithManager(ctx context.Context, uncachedClient client.Client, mgr manager.Manager, ownerRef metav1.OwnerReference) error {

And in main:

if err = perses.SetupPersesWithManager(ctx, apiClient, mgr, ownresources.GetDeploymentRef()); err != nil {

then use it here:

Suggested change
available := checkPersesAvailable(ctx, mgr.GetClient())
available := checkPersesAvailable(ctx, uncachedClient)

if !available {
persesLog.Info("Perses CRDs not found; skipping Perses controller registration")
return nil
}
persesLog.Info("Perses CRDs detected; registering Perses controller")

namespace := hcoutil.GetOperatorNamespaceFromEnv()
dashboards, err := initDashboards(namespace, persesLog)
Expand Down
5 changes: 3 additions & 2 deletions controllers/perses/perses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ var _ = Describe("Perses controller", func() {
})

Context("SetupPersesWithManager guard", func() {
It("should skip controller registration when Perses CRDs are not available", func() {
It("should skip controller registration when Perses CRDs are not available", func(ctx context.Context) {
ctx = logr.NewContext(ctx, GinkgoLogr)
old := checkPersesAvailable
checkPersesAvailable = func(_ context.Context, _ client.Client) bool { return false }
defer func() { checkPersesAvailable = old }()
Expand All @@ -174,7 +175,7 @@ var _ = Describe("Perses controller", func() {
mgr, err := commontestutils.NewManagerMock(nil, manager.Options{Scheme: scheme.Scheme}, cl, GinkgoLogr)
Expect(err).ToNot(HaveOccurred())

err = SetupPersesWithManager(mgr, metav1.OwnerReference{})
err = SetupPersesWithManager(ctx, mgr, metav1.OwnerReference{})
Expect(err).ToNot(HaveOccurred())
})
})
Expand Down
24 changes: 18 additions & 6 deletions pkg/util/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package util
import (
"context"
"errors"
"fmt"
"os"
"slices"

Expand Down Expand Up @@ -228,26 +229,37 @@ func isNADExists(ctx context.Context, cl client.Client, logger logr.Logger) bool
// IsPersesAvailable returns true when the Perses CRDs are installed in the cluster.
func IsPersesAvailable(ctx context.Context, cl client.Client) bool {
logger := logr.FromContextOrDiscard(ctx)
return isCRDExists(ctx, cl, PersesDashboardsCRDName, logger) &&
isCRDExists(ctx, cl, PersesDatasourcesCRDName, logger)
dashboardsAvailable := isCRDExists(ctx, cl, PersesDashboardsCRDName, logger)
datasourcesAvailable := isCRDExists(ctx, cl, PersesDatasourcesCRDName, logger)
logger.Info("Perses CRD availability check", "dashboards", dashboardsAvailable, "datasources", datasourcesAvailable)
return dashboardsAvailable && datasourcesAvailable
}

func isCRDExists(ctx context.Context, cl client.Client, crdName string, logger logr.Logger) bool {
found := &apiextensionsv1.CustomResourceDefinition{}
key := client.ObjectKey{Name: crdName}
err := cl.Get(ctx, key, found)
if err != nil {
if !apierrors.IsNotFound(err) {
logger.Error(err, "cannot find CRD", "CRD", crdName)
if apierrors.IsNotFound(err) || meta.IsNoMatchError(err) {
logger.Info("CRD not found", "CRD", crdName, "notFound", apierrors.IsNotFound(err), "noMatch", meta.IsNoMatchError(err))
} else {
logger.Info("CRD not found", "CRD", crdName)
logger.Error(err, "cannot find CRD", "CRD", crdName)
}
} else {
logger.Info("CRD found", "CRD", crdName)
logger.Info("CRD found", "CRD", crdName, "resourceVersion", found.ResourceVersion, "conditions", summarizeCRDConditions(found))
}
return err == nil
}

func summarizeCRDConditions(crd *apiextensionsv1.CustomResourceDefinition) []string {
summary := make([]string, 0, len(crd.Status.Conditions))
for _, cond := range crd.Status.Conditions {
entry := fmt.Sprintf("%s=%s(%s)", cond.Type, cond.Status, cond.Reason)
summary = append(summary, entry)
}
return summary
}

func init() {
clusterInfo = &ClusterInfoImp{
runningLocally: IsRunModeLocal(),
Expand Down
Loading