diff --git a/operator/api/loki/v1/v1.go b/operator/api/loki/v1/v1.go index 3c5fa54c60621..58d7f9aa987a6 100644 --- a/operator/api/loki/v1/v1.go +++ b/operator/api/loki/v1/v1.go @@ -104,4 +104,6 @@ var ( ErrSummaryAnnotationMissing = errors.New("rule requires annotation: summary") // ErrDescriptionAnnotationMissing indicates that an alerting rule is missing the description annotation ErrDescriptionAnnotationMissing = errors.New("rule requires annotation: description") + // ErrRuleExclusiveNamespaceLabel indicates that a rule must not specify both kubernetes_namespace_name and k8s_namespace_name labels + ErrRuleExclusiveNamespaceLabel = errors.New("rule must not specify both kubernetes_namespace_name and k8s_namespace_name labels") ) diff --git a/operator/internal/manifests/gateway_tenants_test.go b/operator/internal/manifests/gateway_tenants_test.go index 051b6a653aab8..57f17e7f31daf 100644 --- a/operator/internal/manifests/gateway_tenants_test.go +++ b/operator/internal/manifests/gateway_tenants_test.go @@ -716,7 +716,7 @@ func TestConfigureDeploymentForMode(t *testing.T) { { Name: gatewayContainerName, Args: []string{ - "--logs.auth.extract-selectors=kubernetes_namespace_name", + "--logs.auth.extract-selectors=kubernetes_namespace_name,k8s_namespace_name", }, }, { @@ -730,7 +730,8 @@ func TestConfigureDeploymentForMode(t *testing.T) { "--opa.skip-tenants=audit,infrastructure", "--opa.package=lokistack", "--opa.admin-groups=system:cluster-admins,cluster-admin,dedicated-admin", - "--opa.matcher=kubernetes_namespace_name", + "--opa.matcher=kubernetes_namespace_name,k8s_namespace_name", + "--opa.viaq-to-otel-migration=true", `--openshift.mappings=application=loki.grafana.com`, `--openshift.mappings=infrastructure=loki.grafana.com`, `--openshift.mappings=audit=loki.grafana.com`, @@ -825,7 +826,7 @@ func TestConfigureDeploymentForMode(t *testing.T) { { Name: gatewayContainerName, Args: []string{ - "--logs.auth.extract-selectors=kubernetes_namespace_name", + "--logs.auth.extract-selectors=kubernetes_namespace_name,k8s_namespace_name", }, }, { @@ -839,7 +840,8 @@ func TestConfigureDeploymentForMode(t *testing.T) { "--opa.skip-tenants=audit,infrastructure", "--opa.package=lokistack", "--opa.admin-groups=system:cluster-admins,cluster-admin,dedicated-admin", - "--opa.matcher=kubernetes_namespace_name", + "--opa.matcher=kubernetes_namespace_name,k8s_namespace_name", + "--opa.viaq-to-otel-migration=true", "--tls.internal.server.cert-file=/var/run/tls/http/server/tls.crt", "--tls.internal.server.key-file=/var/run/tls/http/server/tls.key", "--tls.min-version=min-version", @@ -1162,7 +1164,7 @@ func TestConfigureDeploymentForMode(t *testing.T) { { Name: gatewayContainerName, Args: []string{ - "--logs.auth.extract-selectors=kubernetes_namespace_name", + "--logs.auth.extract-selectors=kubernetes_namespace_name,k8s_namespace_name", }, }, { @@ -1176,7 +1178,8 @@ func TestConfigureDeploymentForMode(t *testing.T) { "--opa.skip-tenants=audit,infrastructure", "--opa.package=lokistack", "--opa.admin-groups=custom-admins,other-admins", - "--opa.matcher=kubernetes_namespace_name", + "--opa.matcher=kubernetes_namespace_name,k8s_namespace_name", + "--opa.viaq-to-otel-migration=true", `--openshift.mappings=application=loki.grafana.com`, `--openshift.mappings=infrastructure=loki.grafana.com`, `--openshift.mappings=audit=loki.grafana.com`, @@ -1259,7 +1262,7 @@ func TestConfigureDeploymentForMode(t *testing.T) { { Name: gatewayContainerName, Args: []string{ - "--logs.auth.extract-selectors=kubernetes_namespace_name", + "--logs.auth.extract-selectors=kubernetes_namespace_name,k8s_namespace_name", }, }, { @@ -1272,7 +1275,8 @@ func TestConfigureDeploymentForMode(t *testing.T) { "--web.healthchecks.url=http://localhost:8082", "--opa.skip-tenants=audit,infrastructure", "--opa.package=lokistack", - "--opa.matcher=kubernetes_namespace_name", + "--opa.matcher=kubernetes_namespace_name,k8s_namespace_name", + "--opa.viaq-to-otel-migration=true", `--openshift.mappings=application=loki.grafana.com`, `--openshift.mappings=infrastructure=loki.grafana.com`, `--openshift.mappings=audit=loki.grafana.com`, diff --git a/operator/internal/manifests/gateway_test.go b/operator/internal/manifests/gateway_test.go index 02ad588974858..0bd637bc0c12b 100644 --- a/operator/internal/manifests/gateway_test.go +++ b/operator/internal/manifests/gateway_test.go @@ -726,7 +726,7 @@ func TestBuildGateway_WithRulesEnabled(t *testing.T) { wantArgs: []string{ "--logs.rules.endpoint=https://abcd-ruler-http.efgh.svc.cluster.local:3100", "--logs.rules.read-only=true", - "--logs.rules.label-filters=application:kubernetes_namespace_name", + "--logs.rules.label-filters=application:kubernetes_namespace_name,k8s_namespace_name", }, }, { diff --git a/operator/internal/manifests/openshift/alertingrule.go b/operator/internal/manifests/openshift/alertingrule.go index 5d03db6d0f924..9f7ee11da2d38 100644 --- a/operator/internal/manifests/openshift/alertingrule.go +++ b/operator/internal/manifests/openshift/alertingrule.go @@ -1,14 +1,22 @@ package openshift -import lokiv1 "github.com/grafana/loki/operator/api/loki/v1" +import ( + "strings" + + lokiv1 "github.com/grafana/loki/operator/api/loki/v1" +) func AlertingRuleTenantLabels(ar *lokiv1.AlertingRule) { switch ar.Spec.TenantID { case tenantApplication: - appendAlertingRuleLabels(ar, map[string]string{ - opaDefaultLabelMatcher: ar.Namespace, + labels := map[string]string{ ocpMonitoringGroupByLabel: ar.Namespace, - }) + } + labelMatchers := strings.Split(opaDefaultLabelMatchers, ",") + for _, label := range labelMatchers { + labels[label] = ar.Namespace + } + appendAlertingRuleLabels(ar, labels) case tenantInfrastructure, tenantAudit, tenantNetwork: appendAlertingRuleLabels(ar, map[string]string{ ocpMonitoringGroupByLabel: ar.Namespace, diff --git a/operator/internal/manifests/openshift/alertingrule_test.go b/operator/internal/manifests/openshift/alertingrule_test.go index afd50dc3d2d73..6349c9fec89b8 100644 --- a/operator/internal/manifests/openshift/alertingrule_test.go +++ b/operator/internal/manifests/openshift/alertingrule_test.go @@ -46,8 +46,9 @@ func TestAlertingRuleTenantLabels(t *testing.T) { { Alert: "alert", Labels: map[string]string{ - opaDefaultLabelMatcher: "test-ns", - ocpMonitoringGroupByLabel: "test-ns", + "kubernetes_namespace_name": "test-ns", + "k8s_namespace_name": "test-ns", + ocpMonitoringGroupByLabel: "test-ns", }, }, }, diff --git a/operator/internal/manifests/openshift/configure.go b/operator/internal/manifests/openshift/configure.go index 75ecc65973bf5..5403c89afa944 100644 --- a/operator/internal/manifests/openshift/configure.go +++ b/operator/internal/manifests/openshift/configure.go @@ -81,7 +81,7 @@ func ConfigureGatewayDeployment( } d.Spec.Template.Spec.Containers[i].Args = append(d.Spec.Template.Spec.Containers[i].Args, - fmt.Sprintf("--logs.auth.extract-selectors=%s", opaDefaultLabelMatcher), + fmt.Sprintf("--logs.auth.extract-selectors=%s", opaDefaultLabelMatchers), ) } } @@ -102,7 +102,7 @@ func ConfigureGatewayDeploymentRulesAPI(d *appsv1.Deployment, containerName stri container := corev1.Container{ Args: []string{ - fmt.Sprintf("--logs.rules.label-filters=%s:%s", tenantApplication, opaDefaultLabelMatcher), + fmt.Sprintf("--logs.rules.label-filters=%s:%s", tenantApplication, opaDefaultLabelMatchers), }, } diff --git a/operator/internal/manifests/openshift/opa_openshift.go b/operator/internal/manifests/openshift/opa_openshift.go index bc804c11b11ed..fefaaf4b00db8 100644 --- a/operator/internal/manifests/openshift/opa_openshift.go +++ b/operator/internal/manifests/openshift/opa_openshift.go @@ -19,7 +19,7 @@ const ( opaDefaultPackage = "lokistack" opaDefaultAPIGroup = "loki.grafana.com" opaMetricsPortName = "opa-metrics" - opaDefaultLabelMatcher = "kubernetes_namespace_name" + opaDefaultLabelMatchers = "kubernetes_namespace_name,k8s_namespace_name" opaNetworkLabelMatchers = "SrcK8S_Namespace,DstK8S_Namespace" ocpMonitoringGroupByLabel = "namespace" ) @@ -53,7 +53,8 @@ func newOPAOpenShiftContainer(mode lokiv1.ModeType, secretVolumeName, tlsDir, mi if mode != lokiv1.OpenshiftNetwork { args = append(args, []string{ - fmt.Sprintf("--opa.matcher=%s", opaDefaultLabelMatcher), + fmt.Sprintf("--opa.matcher=%s", opaDefaultLabelMatchers), + "--opa.viaq-to-otel-migration=true", }...) } else { args = append(args, []string{ diff --git a/operator/internal/manifests/openshift/recordingrule.go b/operator/internal/manifests/openshift/recordingrule.go index 8e8a0eccfa6de..8ab1938d528d4 100644 --- a/operator/internal/manifests/openshift/recordingrule.go +++ b/operator/internal/manifests/openshift/recordingrule.go @@ -1,14 +1,22 @@ package openshift -import lokiv1 "github.com/grafana/loki/operator/api/loki/v1" +import ( + "strings" + + lokiv1 "github.com/grafana/loki/operator/api/loki/v1" +) func RecordingRuleTenantLabels(r *lokiv1.RecordingRule) { switch r.Spec.TenantID { case tenantApplication: - appendRecordingRuleLabels(r, map[string]string{ - opaDefaultLabelMatcher: r.Namespace, + labels := map[string]string{ ocpMonitoringGroupByLabel: r.Namespace, - }) + } + labelMatchers := strings.Split(opaDefaultLabelMatchers, ",") + for _, label := range labelMatchers { + labels[label] = r.Namespace + } + appendRecordingRuleLabels(r, labels) case tenantInfrastructure, tenantAudit, tenantNetwork: appendRecordingRuleLabels(r, map[string]string{ ocpMonitoringGroupByLabel: r.Namespace, diff --git a/operator/internal/manifests/openshift/recordingrule_test.go b/operator/internal/manifests/openshift/recordingrule_test.go index 901913dac2944..93c4f8b2d8482 100644 --- a/operator/internal/manifests/openshift/recordingrule_test.go +++ b/operator/internal/manifests/openshift/recordingrule_test.go @@ -46,8 +46,9 @@ func TestRecordingRuleTenantLabels(t *testing.T) { { Record: "record", Labels: map[string]string{ - opaDefaultLabelMatcher: "test-ns", - ocpMonitoringGroupByLabel: "test-ns", + "kubernetes_namespace_name": "test-ns", + "k8s_namespace_name": "test-ns", + ocpMonitoringGroupByLabel: "test-ns", }, }, }, diff --git a/operator/internal/validation/openshift/alertingrule_test.go b/operator/internal/validation/openshift/alertingrule_test.go index 1644f7e10bd3e..77701f8695bd5 100644 --- a/operator/internal/validation/openshift/alertingrule_test.go +++ b/operator/internal/validation/openshift/alertingrule_test.go @@ -511,6 +511,42 @@ func TestAlertingRuleValidator(t *testing.T) { }, }, }, + { + desc: "AlertingRule expression using both namespace labels", + spec: &lokiv1.AlertingRule{ + ObjectMeta: metav1.ObjectMeta{ + Name: "alerting-rule", + Namespace: "example", + }, + Spec: lokiv1.AlertingRuleSpec{ + TenantID: "application", + Groups: []*lokiv1.AlertingRuleGroup{ + { + Rules: []*lokiv1.AlertingRuleGroupSpec{ + { + Expr: `sum(rate({kubernetes_namespace_name="example", k8s_namespace_name="example", level="error"}[5m])) by (job) > 0.1`, + Labels: map[string]string{ + severityLabelName: "warning", + }, + Annotations: map[string]string{ + summaryAnnotationName: "alert summary", + descriptionAnnotationName: "alert description", + }, + }, + }, + }, + }, + }, + }, + wantErrors: []*field.Error{ + { + Type: field.ErrorTypeInvalid, + Field: "spec.groups[0].rules[0].expr", + BadValue: `sum(rate({kubernetes_namespace_name="example", k8s_namespace_name="example", level="error"}[5m])) by (job) > 0.1`, + Detail: lokiv1.ErrRuleExclusiveNamespaceLabel.Error(), + }, + }, + }, } for _, tc := range tt { diff --git a/operator/internal/validation/openshift/common.go b/operator/internal/validation/openshift/common.go index a41161cbc54f6..a5aab7704bb8d 100644 --- a/operator/internal/validation/openshift/common.go +++ b/operator/internal/validation/openshift/common.go @@ -19,6 +19,7 @@ const ( descriptionAnnotationName = "description" namespaceLabelName = "kubernetes_namespace_name" + namespaceOTLPLabelName = "k8s_namespace_name" namespaceOpenshiftLogging = "openshift-logging" tenantAudit = "audit" @@ -64,8 +65,13 @@ func validateRuleExpression(namespace, tenantID, rawExpr string) error { } matchers := selector.Matchers() - if tenantID != tenantAudit && !validateIncludesNamespace(namespace, matchers) { - return lokiv1.ErrRuleMustMatchNamespace + if tenantID != tenantAudit { + if !validateIncludesNamespace(namespace, matchers) { + return lokiv1.ErrRuleMustMatchNamespace + } + if !validateExclusiveNamespaceLabels(matchers) { + return lokiv1.ErrRuleExclusiveNamespaceLabel + } } return nil @@ -73,7 +79,7 @@ func validateRuleExpression(namespace, tenantID, rawExpr string) error { func validateIncludesNamespace(namespace string, matchers []*labels.Matcher) bool { for _, m := range matchers { - if m.Name == namespaceLabelName && m.Type == labels.MatchEqual && m.Value == namespace { + if (m.Name == namespaceLabelName || m.Name == namespaceOTLPLabelName) && m.Type == labels.MatchEqual && m.Value == namespace { return true } } @@ -81,6 +87,22 @@ func validateIncludesNamespace(namespace string, matchers []*labels.Matcher) boo return false } +func validateExclusiveNamespaceLabels(matchers []*labels.Matcher) bool { + var namespaceLabelSet, otlpLabelSet bool + + for _, m := range matchers { + if m.Name == namespaceLabelName && m.Type == labels.MatchEqual { + namespaceLabelSet = true + } + if m.Name == namespaceOTLPLabelName && m.Type == labels.MatchEqual { + otlpLabelSet = true + } + } + + // Only one of the labels should be set, not both + return (namespaceLabelSet || otlpLabelSet) && !(namespaceLabelSet && otlpLabelSet) +} + func tenantForNamespace(namespace string) []string { if strings.HasPrefix(namespace, "openshift") || strings.HasPrefix(namespace, "kube-") || diff --git a/operator/internal/validation/openshift/recordingrule_test.go b/operator/internal/validation/openshift/recordingrule_test.go index e51ab748c82cf..88f400e0be4c0 100644 --- a/operator/internal/validation/openshift/recordingrule_test.go +++ b/operator/internal/validation/openshift/recordingrule_test.go @@ -241,6 +241,35 @@ func TestRecordingRuleValidator(t *testing.T) { }, }, }, + { + desc: "query contains both namespace labels", + spec: &lokiv1.RecordingRule{ + ObjectMeta: metav1.ObjectMeta{ + Name: "recording-rule", + Namespace: "example", + }, + Spec: lokiv1.RecordingRuleSpec{ + TenantID: "application", + Groups: []*lokiv1.RecordingRuleGroup{ + { + Rules: []*lokiv1.RecordingRuleGroupSpec{ + { + Expr: `sum(rate({kubernetes_namespace_name="example", k8s_namespace_name="example", level="error"}[5m])) by (job) > 0.1`, + }, + }, + }, + }, + }, + }, + wantErrors: []*field.Error{ + { + Type: field.ErrorTypeInvalid, + Field: "spec.groups[0].rules[0].expr", + BadValue: `sum(rate({kubernetes_namespace_name="example", k8s_namespace_name="example", level="error"}[5m])) by (job) > 0.1`, + Detail: lokiv1.ErrRuleExclusiveNamespaceLabel.Error(), + }, + }, + }, } for _, tc := range tt {