diff --git a/internal/controller/consoleplugin/config/config.go b/internal/controller/consoleplugin/config/config.go index ba4eb165c..35068c0ed 100644 --- a/internal/controller/consoleplugin/config/config.go +++ b/internal/controller/consoleplugin/config/config.go @@ -24,6 +24,7 @@ type LokiConfig struct { URL string `yaml:"url" json:"url"` Labels []string `yaml:"labels" json:"labels"` + Status string `yaml:"status,omitempty" json:"status,omitempty"` StatusURL string `yaml:"statusUrl,omitempty" json:"statusUrl,omitempty"` Timeout api.Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"` TenantID string `yaml:"tenantID,omitempty" json:"tenantID,omitempty"` diff --git a/internal/controller/consoleplugin/consoleplugin_objects.go b/internal/controller/consoleplugin/consoleplugin_objects.go index 9b58a7002..bd3802929 100644 --- a/internal/controller/consoleplugin/consoleplugin_objects.go +++ b/internal/controller/consoleplugin/consoleplugin_objects.go @@ -9,6 +9,7 @@ import ( "strconv" "time" + lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" osv1 "github.com/openshift/api/console/v1" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" "gopkg.in/yaml.v2" @@ -493,9 +494,24 @@ func (b *builder) setFrontendConfig(fconf *cfg.FrontendConfig) error { return nil } +func getLokiStatus(lokiStack *lokiv1.LokiStack) string { + if lokiStack != nil { + for _, conditions := range lokiStack.Status.Conditions { + if conditions.Reason == "ReadyComponents" { + if conditions.Status == "True" { + return "ready" + } + break + } + } + return "pending" + } + return "" +} + // returns a configmap with a digest of its configuration contents, which will be used to // detect any configuration change -func (b *builder) configMap(ctx context.Context) (*corev1.ConfigMap, string, error) { +func (b *builder) configMap(ctx context.Context, lokiStack *lokiv1.LokiStack) (*corev1.ConfigMap, string, error) { config := cfg.PluginConfig{ Server: cfg.ServerConfig{ Port: int(*b.advanced.Port), @@ -511,6 +527,10 @@ func (b *builder) configMap(ctx context.Context) (*corev1.ConfigMap, string, err // configure loki var err error config.Loki, err = b.getLokiConfig() + if lokiStack != nil { + config.Loki.Status = getLokiStatus(lokiStack) + config.Loki.StatusURL = "" + } if err != nil { return nil, "", err } diff --git a/internal/controller/consoleplugin/consoleplugin_reconciler.go b/internal/controller/consoleplugin/consoleplugin_reconciler.go index 87698b80f..989cd472b 100644 --- a/internal/controller/consoleplugin/consoleplugin_reconciler.go +++ b/internal/controller/consoleplugin/consoleplugin_reconciler.go @@ -10,15 +10,16 @@ import ( appsv1 "k8s.io/api/apps/v1" ascv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/log" + lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" flowslatest "github.com/netobserv/network-observability-operator/api/flowcollector/v1beta2" "github.com/netobserv/network-observability-operator/internal/controller/constants" "github.com/netobserv/network-observability-operator/internal/controller/reconcilers" "github.com/netobserv/network-observability-operator/internal/pkg/helper" "github.com/netobserv/network-observability-operator/internal/pkg/resources" + apierrors "k8s.io/apimachinery/pkg/api/errors" ) // Type alias @@ -83,7 +84,7 @@ func (r *CPReconciler) Reconcile(ctx context.Context, desired *flowslatest.FlowC } } - cmDigest, err := r.reconcileConfigMap(ctx, &builder) + cmDigest, err := r.reconcileConfigMap(ctx, &builder, &desired.Spec) if err != nil { return err } @@ -158,7 +159,7 @@ func (r *CPReconciler) reconcilePlugin(ctx context.Context, builder *builder, de pluginExists := true err := r.Get(ctx, types.NamespacedName{Name: name}, &oldPlg) if err != nil { - if errors.IsNotFound(err) { + if apierrors.IsNotFound(err) { pluginExists = false } else { return err @@ -179,8 +180,30 @@ func (r *CPReconciler) reconcilePlugin(ctx context.Context, builder *builder, de return nil } -func (r *CPReconciler) reconcileConfigMap(ctx context.Context, builder *builder) (string, error) { - newCM, configDigest, err := builder.configMap(ctx) +func (r *CPReconciler) reconcileConfigMap(ctx context.Context, builder *builder, desired *flowslatest.FlowCollectorSpec) (string, error) { + lokiStack := &lokiv1.LokiStack{} + if desired.Loki.Mode == flowslatest.LokiModeLokiStack { + if r.ClusterInfo.HasLokiStack() { + ns := desired.Loki.LokiStack.Namespace + if ns == "" { + ns = desired.Namespace + } + if err := r.Client.Get(ctx, types.NamespacedName{Name: desired.Loki.LokiStack.Name, Namespace: ns}, lokiStack); err != nil { + lokiStack = nil + if apierrors.IsNotFound(err) { + log.FromContext(ctx).Info("LokiStack resource not found, status will not be available", + "name", desired.Loki.LokiStack.Name, + "namespace", ns) + } else { + log.FromContext(ctx).Error(err, "Failed to get LokiStack resource", + "name", desired.Loki.LokiStack.Name, + "namespace", ns) + } + } + } + } + + newCM, configDigest, err := builder.configMap(ctx, lokiStack) if err != nil { return "", err } diff --git a/internal/controller/consoleplugin/consoleplugin_test.go b/internal/controller/consoleplugin/consoleplugin_test.go index f034bb48c..530b4742f 100644 --- a/internal/controller/consoleplugin/consoleplugin_test.go +++ b/internal/controller/consoleplugin/consoleplugin_test.go @@ -110,7 +110,7 @@ func getAutoScalerSpecs() (ascv2.HorizontalPodAutoscaler, flowslatest.FlowCollec func getBuilder(spec *flowslatest.FlowCollectorSpec, lk *helper.LokiConfig) builder { info := reconcilers.Common{Namespace: testNamespace, Loki: lk, ClusterInfo: &cluster.Info{}} b := newBuilder(info.NewInstance(map[reconcilers.ImageRef]string{reconcilers.MainImage: testImage}, status.Instance{}), spec, constants.PluginName) - _, _, _ = b.configMap(context.Background()) // build configmap to update builder's volumes + _, _, _ = b.configMap(context.Background(), nil) // build configmap to update builder's volumes return b } @@ -223,8 +223,8 @@ func TestConfigMapUpdateCheck(t *testing.T) { } spec := flowslatest.FlowCollectorSpec{ConsolePlugin: plugin} builder := getBuilder(&spec, &loki) - old, _, _ := builder.configMap(context.Background()) - nEw, _, _ := builder.configMap(context.Background()) + old, _, _ := builder.configMap(context.Background(), nil) + nEw, _, _ := builder.configMap(context.Background(), nil) assert.Equal(old.Data, nEw.Data) // update loki @@ -239,7 +239,7 @@ func TestConfigMapUpdateCheck(t *testing.T) { }}, } builder = getBuilder(&spec, &loki) - nEw, _, _ = builder.configMap(context.Background()) + nEw, _, _ = builder.configMap(context.Background(), nil) assert.NotEqual(old.Data, nEw.Data) old = nEw @@ -247,7 +247,7 @@ func TestConfigMapUpdateCheck(t *testing.T) { loki.LokiManualParams.StatusURL = "http://loki.status:3100/" loki.LokiManualParams.StatusTLS.Enable = true builder = getBuilder(&spec, &loki) - nEw, _, _ = builder.configMap(context.Background()) + nEw, _, _ = builder.configMap(context.Background(), nil) assert.NotEqual(old.Data, nEw.Data) old = nEw @@ -258,7 +258,7 @@ func TestConfigMapUpdateCheck(t *testing.T) { CertFile: "status-ca.crt", } builder = getBuilder(&spec, &loki) - nEw, _, _ = builder.configMap(context.Background()) + nEw, _, _ = builder.configMap(context.Background(), nil) assert.NotEqual(old.Data, nEw.Data) old = nEw @@ -270,7 +270,7 @@ func TestConfigMapUpdateCheck(t *testing.T) { CertKey: "tls.key", } builder = getBuilder(&spec, &loki) - nEw, _, _ = builder.configMap(context.Background()) + nEw, _, _ = builder.configMap(context.Background(), nil) assert.NotEqual(old.Data, nEw.Data) } @@ -286,8 +286,8 @@ func TestConfigMapUpdateWithLokistackMode(t *testing.T) { loki := helper.NewLokiConfig(&lokiSpec, "any") spec := flowslatest.FlowCollectorSpec{ConsolePlugin: plugin, Loki: lokiSpec} builder := getBuilder(&spec, &loki) - old, _, _ := builder.configMap(context.Background()) - nEw, _, _ := builder.configMap(context.Background()) + old, _, _ := builder.configMap(context.Background(), nil) + nEw, _, _ := builder.configMap(context.Background(), nil) assert.Equal(old.Data, nEw.Data) // update lokistack name @@ -296,7 +296,7 @@ func TestConfigMapUpdateWithLokistackMode(t *testing.T) { spec = flowslatest.FlowCollectorSpec{ConsolePlugin: plugin, Loki: lokiSpec} builder = getBuilder(&spec, &loki) - nEw, _, _ = builder.configMap(context.Background()) + nEw, _, _ = builder.configMap(context.Background(), nil) assert.NotEqual(old.Data, nEw.Data) old = nEw @@ -306,7 +306,7 @@ func TestConfigMapUpdateWithLokistackMode(t *testing.T) { spec = flowslatest.FlowCollectorSpec{ConsolePlugin: plugin, Loki: lokiSpec} builder = getBuilder(&spec, &loki) - nEw, _, _ = builder.configMap(context.Background()) + nEw, _, _ = builder.configMap(context.Background(), nil) assert.NotEqual(old.Data, nEw.Data) } @@ -331,7 +331,7 @@ func TestConfigMapContent(t *testing.T) { Processor: flowslatest.FlowCollectorFLP{SubnetLabels: flowslatest.SubnetLabels{OpenShiftAutoDetect: ptr.To(false)}}, } builder := getBuilder(&spec, &loki) - cm, _, err := builder.configMap(context.Background()) + cm, _, err := builder.configMap(context.Background(), nil) assert.NotNil(cm) assert.Nil(err) diff --git a/internal/controller/flowcollector_controller.go b/internal/controller/flowcollector_controller.go index bf1b7143b..f746b2f25 100644 --- a/internal/controller/flowcollector_controller.go +++ b/internal/controller/flowcollector_controller.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" osv1 "github.com/openshift/api/console/v1" securityv1 "github.com/openshift/api/security/v1" appsv1 "k8s.io/api/apps/v1" @@ -12,6 +13,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" flowslatest "github.com/netobserv/network-observability-operator/api/flowcollector/v1beta2" @@ -68,6 +70,11 @@ func Start(ctx context.Context, mgr *manager.Manager) (manager.PostCreateHook, e log.Info("CNO not detected: using ovnKubernetes config and reconciler") } + if mgr.ClusterInfo.HasLokiStack() { + builder.Watches(&lokiv1.LokiStack{}, &handler.EnqueueRequestForObject{}) + log.Info("LokiStack CRD detected") + } + ctrl, err := builder.Build(&r) if err != nil { return nil, err diff --git a/internal/pkg/cluster/cluster.go b/internal/pkg/cluster/cluster.go index c1e59f412..7e2624ec4 100644 --- a/internal/pkg/cluster/cluster.go +++ b/internal/pkg/cluster/cluster.go @@ -8,6 +8,7 @@ import ( "sync" "github.com/coreos/go-semver/semver" + lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" configv1 "github.com/openshift/api/config/v1" osv1 "github.com/openshift/api/console/v1" operatorv1 "github.com/openshift/api/operator/v1" @@ -43,12 +44,13 @@ type Info struct { } var ( - consolePlugin = "consoleplugins." + osv1.GroupVersion.String() - cno = "networks." + operatorv1.GroupVersion.String() - svcMonitor = "servicemonitors." + monv1.SchemeGroupVersion.String() - promRule = "prometheusrules." + monv1.SchemeGroupVersion.String() - ocpSecurity = "securitycontextconstraints." + securityv1.SchemeGroupVersion.String() + consolePlugin = "consoleplugins." + osv1.GroupVersion.String() + cno = "networks." + operatorv1.GroupVersion.String() + svcMonitor = "servicemonitors." + monv1.SchemeGroupVersion.String() + promRule = "prometheusrules." + monv1.SchemeGroupVersion.String() + ocpSecurity = "securitycontextconstraints." + securityv1.SchemeGroupVersion.String() endpointSlices = "endpointslices." + discoveryv1.SchemeGroupVersion.String() + lokistacks = "lokistacks." + lokiv1.GroupVersion.String() ) func NewInfo(ctx context.Context, cl client.Client, dcl *discovery.DiscoveryClient, onRefresh func()) (*Info, func(ctx context.Context) error, error) { @@ -74,6 +76,7 @@ func (c *Info) fetchAvailableAPIs(ctx context.Context) error { promRule: false, ocpSecurity: false, endpointSlices: false, + lokistacks: false, } for apiName := range apisMap { if hasAPI(apiName, resources) { @@ -281,9 +284,13 @@ func (c *Info) HasPromRule() bool { return c.apisMap[promRule] } -// HasEndpointSlices returns true if "endpointslices.discovery.k8s.io" API was found func (c *Info) HasEndpointSlices() bool { c.apisMapLock.RLock() defer c.apisMapLock.RUnlock() return c.apisMap[endpointSlices] } + +// HasLokiStack returns true if "lokistack" API was found +func (c *Info) HasLokiStack() bool { + return c.apisMap[lokistacks] +}