diff --git a/operator/api/loki/v1/lokistack_types.go b/operator/api/loki/v1/lokistack_types.go index 0b727e5c548c1..45ab00fbad3e5 100644 --- a/operator/api/loki/v1/lokistack_types.go +++ b/operator/api/loki/v1/lokistack_types.go @@ -441,6 +441,13 @@ type LokiTemplateSpec struct { // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Ruler pods" Ruler *LokiComponentSpec `json:"ruler,omitempty"` + + // PatternIngester defines the pattern-ingester component spec. + // + // +optional + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Pattern-Ingester pods" + PatternIngester *LokiComponentSpec `json:"patternIngester,omitempty"` } // ClusterProxy is the Proxy configuration when the cluster is behind a Proxy. @@ -1092,6 +1099,15 @@ type RulesSpec struct { NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"` } +type PatternIngester struct { + // Enabled defines a flag to enable/disable the pattern-ingester component + // + // +required + // +kubebuilder:validation:Required + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors="urn:alm:descriptor:com.tectonic.ui:booleanSwitch",displayName="Enable" + Enabled bool `json:"enabled"` +} + // LokiStackSpec defines the desired state of LokiStack type LokiStackSpec struct { // ManagementState defines if the CR should be managed by the operator or not. @@ -1190,6 +1206,13 @@ type LokiStackSpec struct { // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Network Policies" NetworkPolicies *NetworkPoliciesSpec `json:"networkPolicies,omitempty"` + + // PatternIngester defines the pattern-ingester configuration. + // + // +optional + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Pattern-Ingester" + PatternIngester *PatternIngester `json:"patternIngester,omitempty"` } type ReplicationSpec struct { @@ -1392,6 +1415,13 @@ type LokiStackComponentStatus struct { // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors="urn:alm:descriptor:com.tectonic.ui:podStatuses",displayName="Ruler",order=6 Ruler PodStatusMap `json:"ruler,omitempty"` + + // PatternIngester is a map to the per pod status of the lokistack pattern ingester deployment. + // + // +optional + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors="urn:alm:descriptor:com.tectonic.ui:podStatuses",displayName="Pattern Ingester",order=6 + PatternIngester PodStatusMap `json:"patternIngester,omitempty"` } // CredentialMode represents the type of authentication used for accessing the object storage. diff --git a/operator/api/loki/v1/zz_generated.deepcopy.go b/operator/api/loki/v1/zz_generated.deepcopy.go index fc1e05178e850..30892428dffa1 100644 --- a/operator/api/loki/v1/zz_generated.deepcopy.go +++ b/operator/api/loki/v1/zz_generated.deepcopy.go @@ -793,6 +793,22 @@ func (in *LokiStackComponentStatus) DeepCopyInto(out *LokiStackComponentStatus) (*out)[key] = outVal } } + if in.PatternIngester != nil { + in, out := &in.PatternIngester, &out.PatternIngester + *out = make(PodStatusMap, len(*in)) + for key, val := range *in { + var outVal []string + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make([]string, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LokiStackComponentStatus. @@ -881,6 +897,11 @@ func (in *LokiStackSpec) DeepCopyInto(out *LokiStackSpec) { *out = new(NetworkPoliciesSpec) **out = **in } + if in.PatternIngester != nil { + in, out := &in.PatternIngester, &out.PatternIngester + *out = new(PatternIngester) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LokiStackSpec. @@ -980,6 +1001,11 @@ func (in *LokiTemplateSpec) DeepCopyInto(out *LokiTemplateSpec) { *out = new(LokiComponentSpec) (*in).DeepCopyInto(*out) } + if in.PatternIngester != nil { + in, out := &in.PatternIngester, &out.PatternIngester + *out = new(LokiComponentSpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LokiTemplateSpec. @@ -1284,6 +1310,21 @@ func (in *OpenshiftTenantSpec) DeepCopy() *OpenshiftTenantSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PatternIngester) DeepCopyInto(out *PatternIngester) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatternIngester. +func (in *PatternIngester) DeepCopy() *PatternIngester { + if in == nil { + return nil + } + out := new(PatternIngester) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PerTenantLimitsTemplateSpec) DeepCopyInto(out *PerTenantLimitsTemplateSpec) { *out = *in diff --git a/operator/bundle/community/manifests/loki-operator.clusterserviceversion.yaml b/operator/bundle/community/manifests/loki-operator.clusterserviceversion.yaml index f4e2fda0173e4..835a9bd32502c 100644 --- a/operator/bundle/community/manifests/loki-operator.clusterserviceversion.yaml +++ b/operator/bundle/community/manifests/loki-operator.clusterserviceversion.yaml @@ -151,8 +151,8 @@ metadata: capabilities: Full Lifecycle categories: OpenShift Optional, Logging & Tracing certified: "false" - containerImage: docker.io/grafana/loki-operator:0.9.0 - createdAt: "2025-12-03T16:22:01Z" + containerImage: docker.io/grafana/loki-operator:0.8.0 + createdAt: "2025-11-28T11:49:45Z" description: The Community Loki Operator provides Kubernetes native deployment and management of Loki and related logging components. operators.operatorframework.io/builder: operator-sdk-unknown @@ -660,6 +660,15 @@ spec: path: networkPolicies.ruleSet x-descriptors: - urn:alm:descriptor:com.tectonic.ui:text + - description: PatternIngester defines the pattern-ingester configuration. + displayName: Pattern-Ingester + path: patternIngesterSpec + - description: Enabled defines a flag to enable/disable the pattern-ingester + component + displayName: Enable + path: patternIngesterSpec.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Proxy defines the spec for the object proxy to configure cluster proxy information. displayName: Cluster Proxy @@ -860,6 +869,21 @@ spec: path: template.ingester.replicas x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden + - description: PatternIngester defines the pattern-ingester component spec. + displayName: Pattern-Ingester pods + path: template.patternIngester + - description: |- + PodAntiAffinity defines the pod anti affinity scheduling rules to schedule pods + of a component. + displayName: PodAntiAffinity + path: template.patternIngester.podAntiAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podAntiAffinity + - description: Replicas defines the number of replica pods of the component. + displayName: Replicas + path: template.patternIngester.replicas + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:hidden - description: Querier defines the querier component spec. displayName: Querier pods path: template.querier diff --git a/operator/bundle/community/manifests/loki.grafana.com_lokistacks.yaml b/operator/bundle/community/manifests/loki.grafana.com_lokistacks.yaml index 1d546901557f8..7d6d4d69a4824 100644 --- a/operator/bundle/community/manifests/loki.grafana.com_lokistacks.yaml +++ b/operator/bundle/community/manifests/loki.grafana.com_lokistacks.yaml @@ -647,6 +647,16 @@ spec: required: - ruleSet type: object + patternIngesterSpec: + description: PatternIngester defines the pattern-ingester configuration. + properties: + enabled: + description: Enabled defines a flag to enable/disable the pattern-ingester + component + type: boolean + required: + - enabled + type: object proxy: description: Proxy defines the spec for the object proxy to configure cluster proxy information. @@ -2636,6 +2646,352 @@ spec: type: object type: array type: object + patternIngester: + description: PatternIngester defines the pattern-ingester component + spec. + properties: + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector defines the labels required by a node to schedule + the component onto it. + type: object + podAntiAffinity: + description: |- + PodAntiAffinity defines the pod anti affinity scheduling rules to schedule pods + of a component. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + replicas: + description: Replicas defines the number of replica pods of + the component. + format: int32 + type: integer + tolerations: + description: |- + Tolerations defines the tolerations required by a node to schedule + the component onto it. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object querier: description: Querier defines the querier component spec. properties: diff --git a/operator/bundle/openshift/manifests/loki-operator.clusterserviceversion.yaml b/operator/bundle/openshift/manifests/loki-operator.clusterserviceversion.yaml index 21b307b95160c..692b0dd921b16 100644 --- a/operator/bundle/openshift/manifests/loki-operator.clusterserviceversion.yaml +++ b/operator/bundle/openshift/manifests/loki-operator.clusterserviceversion.yaml @@ -152,7 +152,7 @@ metadata: categories: OpenShift Optional, Logging & Tracing certified: "false" containerImage: quay.io/openshift-logging/loki-operator:0.1.0 - createdAt: "2025-12-03T16:22:04Z" + createdAt: "2025-12-09T09:29:17Z" description: | The Loki Operator for OCP provides a means for configuring and managing a Loki stack for cluster logging. ## Prerequisites and Requirements @@ -680,6 +680,15 @@ spec: path: networkPolicies.ruleSet x-descriptors: - urn:alm:descriptor:com.tectonic.ui:text + - description: PatternIngester defines the pattern-ingester configuration. + displayName: Pattern-Ingester + path: patternIngester + - description: Enabled defines a flag to enable/disable the pattern-ingester + component + displayName: Enable + path: patternIngester.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Proxy defines the spec for the object proxy to configure cluster proxy information. displayName: Cluster Proxy @@ -880,6 +889,21 @@ spec: path: template.ingester.replicas x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden + - description: PatternIngester defines the pattern-ingester component spec. + displayName: Pattern-Ingester pods + path: template.patternIngester + - description: |- + PodAntiAffinity defines the pod anti affinity scheduling rules to schedule pods + of a component. + displayName: PodAntiAffinity + path: template.patternIngester.podAntiAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podAntiAffinity + - description: Replicas defines the number of replica pods of the component. + displayName: Replicas + path: template.patternIngester.replicas + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:hidden - description: Querier defines the querier component spec. displayName: Querier pods path: template.querier @@ -1120,6 +1144,12 @@ spec: path: components.indexGateway x-descriptors: - urn:alm:descriptor:com.tectonic.ui:podStatuses + - description: PatternIngester is a map to the per pod status of the lokistack + pattern ingester deployment. + displayName: Pattern Ingester + path: components.patternIngester + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podStatuses - description: Ruler is a map to the per pod status of the lokistack ruler statefulset. displayName: Ruler path: components.ruler @@ -1890,10 +1920,10 @@ spec: - name: RELATED_IMAGE_LOKI value: quay.io/openshift-logging/loki:v3.5.5 - name: RELATED_IMAGE_GATEWAY - value: quay.io/observatorium/api:latest + value: quay.io/btaani/api:latest - name: RELATED_IMAGE_OPA value: quay.io/observatorium/opa-openshift:latest - image: quay.io/openshift-logging/loki-operator:0.1.0 + image: quay.io/btaani/loki-operator:amd64 imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -2014,7 +2044,7 @@ spec: relatedImages: - image: quay.io/openshift-logging/loki:v3.5.5 name: loki - - image: quay.io/observatorium/api:latest + - image: quay.io/btaani/api:latest name: gateway - image: quay.io/observatorium/opa-openshift:latest name: opa diff --git a/operator/bundle/openshift/manifests/loki.grafana.com_lokistacks.yaml b/operator/bundle/openshift/manifests/loki.grafana.com_lokistacks.yaml index 3abce8f69b878..f29cfcaff81a6 100644 --- a/operator/bundle/openshift/manifests/loki.grafana.com_lokistacks.yaml +++ b/operator/bundle/openshift/manifests/loki.grafana.com_lokistacks.yaml @@ -646,6 +646,16 @@ spec: required: - ruleSet type: object + patternIngester: + description: PatternIngester defines the pattern-ingester configuration. + properties: + enabled: + description: Enabled defines a flag to enable/disable the pattern-ingester + component + type: boolean + required: + - enabled + type: object proxy: description: Proxy defines the spec for the object proxy to configure cluster proxy information. @@ -2635,6 +2645,352 @@ spec: type: object type: array type: object + patternIngester: + description: PatternIngester defines the pattern-ingester component + spec. + properties: + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector defines the labels required by a node to schedule + the component onto it. + type: object + podAntiAffinity: + description: |- + PodAntiAffinity defines the pod anti affinity scheduling rules to schedule pods + of a component. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + replicas: + description: Replicas defines the number of replica pods of + the component. + format: int32 + type: integer + tolerations: + description: |- + Tolerations defines the tolerations required by a node to schedule + the component onto it. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object querier: description: Querier defines the querier component spec. properties: @@ -3978,6 +4334,14 @@ spec: description: Ingester is a map to the per pod status of the ingester statefulset type: object + patternIngester: + additionalProperties: + items: + type: string + type: array + description: PatternIngester is a map to the per pod status of + the lokistack pattern ingester deployment. + type: object querier: additionalProperties: items: diff --git a/operator/config/crd/bases/loki.grafana.com_lokistacks.yaml b/operator/config/crd/bases/loki.grafana.com_lokistacks.yaml index 7c9fe864d73b2..b847f17b32965 100644 --- a/operator/config/crd/bases/loki.grafana.com_lokistacks.yaml +++ b/operator/config/crd/bases/loki.grafana.com_lokistacks.yaml @@ -628,6 +628,16 @@ spec: required: - ruleSet type: object + patternIngester: + description: PatternIngester defines the pattern-ingester configuration. + properties: + enabled: + description: Enabled defines a flag to enable/disable the pattern-ingester + component + type: boolean + required: + - enabled + type: object proxy: description: Proxy defines the spec for the object proxy to configure cluster proxy information. @@ -2617,6 +2627,352 @@ spec: type: object type: array type: object + patternIngester: + description: PatternIngester defines the pattern-ingester component + spec. + properties: + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector defines the labels required by a node to schedule + the component onto it. + type: object + podAntiAffinity: + description: |- + PodAntiAffinity defines the pod anti affinity scheduling rules to schedule pods + of a component. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + replicas: + description: Replicas defines the number of replica pods of + the component. + format: int32 + type: integer + tolerations: + description: |- + Tolerations defines the tolerations required by a node to schedule + the component onto it. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object querier: description: Querier defines the querier component spec. properties: @@ -3960,6 +4316,14 @@ spec: description: Ingester is a map to the per pod status of the ingester statefulset type: object + patternIngester: + additionalProperties: + items: + type: string + type: array + description: PatternIngester is a map to the per pod status of + the lokistack pattern ingester deployment. + type: object querier: additionalProperties: items: diff --git a/operator/config/manager/kustomization.yaml b/operator/config/manager/kustomization.yaml index d68a66a255237..1d95d02162197 100644 --- a/operator/config/manager/kustomization.yaml +++ b/operator/config/manager/kustomization.yaml @@ -5,5 +5,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: controller - newName: quay.io/openshift-logging/loki-operator - newTag: 0.1.0 + newName: quay.io/btaani/loki-operator + newTag: 0.0.1-29452 diff --git a/operator/config/manifests/community/bases/loki-operator.clusterserviceversion.yaml b/operator/config/manifests/community/bases/loki-operator.clusterserviceversion.yaml index 185167407c55c..8ed381f9b6f95 100644 --- a/operator/config/manifests/community/bases/loki-operator.clusterserviceversion.yaml +++ b/operator/config/manifests/community/bases/loki-operator.clusterserviceversion.yaml @@ -573,6 +573,15 @@ spec: path: networkPolicies.ruleSet x-descriptors: - urn:alm:descriptor:com.tectonic.ui:text + - description: PatternIngester defines the pattern-ingester configuration. + displayName: Pattern-Ingester + path: patternIngesterSpec + - description: Enabled defines a flag to enable/disable the pattern-ingester + component + displayName: Enable + path: patternIngesterSpec.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Proxy defines the spec for the object proxy to configure cluster proxy information. displayName: Cluster Proxy @@ -773,6 +782,21 @@ spec: path: template.ingester.replicas x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden + - description: PatternIngester defines the pattern-ingester component spec. + displayName: Pattern-Ingester pods + path: template.patternIngester + - description: |- + PodAntiAffinity defines the pod anti affinity scheduling rules to schedule pods + of a component. + displayName: PodAntiAffinity + path: template.patternIngester.podAntiAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podAntiAffinity + - description: Replicas defines the number of replica pods of the component. + displayName: Replicas + path: template.patternIngester.replicas + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:hidden - description: Querier defines the querier component spec. displayName: Querier pods path: template.querier diff --git a/operator/config/manifests/openshift/bases/loki-operator.clusterserviceversion.yaml b/operator/config/manifests/openshift/bases/loki-operator.clusterserviceversion.yaml index 5047d424a22f6..8be34b569cd1f 100644 --- a/operator/config/manifests/openshift/bases/loki-operator.clusterserviceversion.yaml +++ b/operator/config/manifests/openshift/bases/loki-operator.clusterserviceversion.yaml @@ -592,6 +592,15 @@ spec: path: networkPolicies.ruleSet x-descriptors: - urn:alm:descriptor:com.tectonic.ui:text + - description: PatternIngester defines the pattern-ingester configuration. + displayName: Pattern-Ingester + path: patternIngester + - description: Enabled defines a flag to enable/disable the pattern-ingester + component + displayName: Enable + path: patternIngester.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Proxy defines the spec for the object proxy to configure cluster proxy information. displayName: Cluster Proxy @@ -792,6 +801,21 @@ spec: path: template.ingester.replicas x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden + - description: PatternIngester defines the pattern-ingester component spec. + displayName: Pattern-Ingester pods + path: template.patternIngester + - description: |- + PodAntiAffinity defines the pod anti affinity scheduling rules to schedule pods + of a component. + displayName: PodAntiAffinity + path: template.patternIngester.podAntiAffinity + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podAntiAffinity + - description: Replicas defines the number of replica pods of the component. + displayName: Replicas + path: template.patternIngester.replicas + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:hidden - description: Querier defines the querier component spec. displayName: Querier pods path: template.querier @@ -1032,6 +1056,12 @@ spec: path: components.indexGateway x-descriptors: - urn:alm:descriptor:com.tectonic.ui:podStatuses + - description: PatternIngester is a map to the per pod status of the lokistack + pattern ingester deployment. + displayName: Pattern Ingester + path: components.patternIngester + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:podStatuses - description: Ruler is a map to the per pod status of the lokistack ruler statefulset. displayName: Ruler path: components.ruler diff --git a/operator/hack/addon_grafana_gateway_ocp.yaml b/operator/hack/addon_grafana_gateway_ocp.yaml index 3451a8e94e23a..fcb69afb68e34 100644 --- a/operator/hack/addon_grafana_gateway_ocp.yaml +++ b/operator/hack/addon_grafana_gateway_ocp.yaml @@ -196,7 +196,7 @@ spec: value: /var/lib/provisioning - name: GF_SECURITY_ADMIN_USER value: kube:admin - image: docker.io/grafana/grafana:8.5.27 + image: docker.io/grafana/grafana:11.6 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 3 diff --git a/operator/internal/certrotation/build_test.go b/operator/internal/certrotation/build_test.go index 0e02d83b5bb94..a08d2c1586c0a 100644 --- a/operator/internal/certrotation/build_test.go +++ b/operator/internal/certrotation/build_test.go @@ -30,7 +30,7 @@ func TestBuildAll(t *testing.T) { objs, err := BuildAll(opts) require.NoError(t, err) - require.Len(t, objs, 17) + require.Len(t, objs, 19) for _, obj := range objs { require.True(t, strings.HasPrefix(obj.GetName(), opts.StackName)) diff --git a/operator/internal/certrotation/target_test.go b/operator/internal/certrotation/target_test.go index dfefb329eea3d..50755a29eb6ad 100644 --- a/operator/internal/certrotation/target_test.go +++ b/operator/internal/certrotation/target_test.go @@ -74,7 +74,7 @@ func TestCertificatesExpired(t *testing.T) { require.Error(t, err) require.ErrorAs(t, err, &expired) - require.Len(t, err.(*CertExpiredError).Reasons, 15) + require.Len(t, err.(*CertExpiredError).Reasons, 17) } func TestBuildTargetCertKeyPairSecrets_Create(t *testing.T) { @@ -102,7 +102,7 @@ func TestBuildTargetCertKeyPairSecrets_Create(t *testing.T) { objs, err := buildTargetCertKeyPairSecrets(opts) require.NoError(t, err) - require.Len(t, objs, 15) + require.Len(t, objs, 17) } func TestBuildTargetCertKeyPairSecrets_Rotate(t *testing.T) { @@ -146,7 +146,7 @@ func TestBuildTargetCertKeyPairSecrets_Rotate(t *testing.T) { objs, err := buildTargetCertKeyPairSecrets(opts) require.NoError(t, err) - require.Len(t, objs, 15) + require.Len(t, objs, 17) // Check serving certificate rotation s := objs[7].(*corev1.Secret) diff --git a/operator/internal/certrotation/var.go b/operator/internal/certrotation/var.go index 1134ebbd135f8..5dadd50726910 100644 --- a/operator/internal/certrotation/var.go +++ b/operator/internal/certrotation/var.go @@ -48,5 +48,7 @@ func ComponentCertSecretNames(stackName string) []string { fmt.Sprintf("%s-query-frontend-grpc", stackName), fmt.Sprintf("%s-ruler-http", stackName), fmt.Sprintf("%s-ruler-grpc", stackName), + fmt.Sprintf("%s-pattern-ingester-grpc", stackName), + fmt.Sprintf("%s-pattern-ingester-http", stackName), } } diff --git a/operator/internal/manifests/build.go b/operator/internal/manifests/build.go index f81b39ed159c0..4883e6ecd2667 100644 --- a/operator/internal/manifests/build.go +++ b/operator/internal/manifests/build.go @@ -108,6 +108,14 @@ func BuildAll(opts Options) ([]client.Object, error) { res = append(res, networkPolicyObjs...) } + if opts.Stack.PatternIngester != nil && opts.Stack.PatternIngester.Enabled { + patternIngesterObj, err := BuildPatternIngester(opts) + if err != nil { + return nil, err + } + res = append(res, patternIngesterObj...) + } + return res, nil } diff --git a/operator/internal/manifests/build_test.go b/operator/internal/manifests/build_test.go index 48944aa30a2cb..a5b2bb82ede5d 100644 --- a/operator/internal/manifests/build_test.go +++ b/operator/internal/manifests/build_test.go @@ -279,7 +279,7 @@ func TestBuildAll_WithFeatureGates_ServiceMonitors(t *testing.T) { }, { desc: "service monitor per component created", - MonitorCount: 8, + MonitorCount: 9, BuildOptions: Options{ Name: "test", Namespace: "test", @@ -474,7 +474,7 @@ func TestBuildAll_WithFeatureGates_ServiceMonitorTLSEndpoints(t *testing.T) { require.NoError(t, err) objects, buildErr := BuildAll(opts) require.NoError(t, buildErr) - require.Equal(t, 8, serviceMonitorCount(objects)) + require.Equal(t, 9, serviceMonitorCount(objects)) for _, obj := range objects { var ( @@ -567,6 +567,9 @@ func TestBuildAll_WithFeatureGates_GRPCEncryption(t *testing.T) { Ruler: &lokiv1.LokiComponentSpec{ Replicas: 1, }, + PatternIngester: &lokiv1.LokiComponentSpec{ + Replicas: 1, + }, }, }, Gates: configv1.FeatureGates{ @@ -610,6 +613,9 @@ func TestBuildAll_WithFeatureGates_GRPCEncryption(t *testing.T) { Ruler: &lokiv1.LokiComponentSpec{ Replicas: 1, }, + PatternIngester: &lokiv1.LokiComponentSpec{ + Replicas: 1, + }, }, }, Gates: configv1.FeatureGates{ @@ -622,9 +628,10 @@ func TestBuildAll_WithFeatureGates_GRPCEncryption(t *testing.T) { secretsMap := map[string]string{ // deployments - "test-distributor": "test-distributor-grpc", - "test-querier": "test-querier-grpc", - "test-query-frontend": "test-query-frontend-grpc", + "test-distributor": "test-distributor-grpc", + "test-querier": "test-querier-grpc", + "test-query-frontend": "test-query-frontend-grpc", + "test-pattern-ingester": "test-pattern-ingester-grpc", // statefulsets "test-ingester": "test-ingester-grpc", "test-compactor": "test-compactor-grpc", @@ -734,6 +741,9 @@ func TestBuildAll_WithFeatureGates_RestrictedPodSecurityStandard(t *testing.T) { Ruler: &lokiv1.LokiComponentSpec{ Replicas: 1, }, + PatternIngester: &lokiv1.LokiComponentSpec{ + Replicas: 1, + }, }, }, Gates: configv1.FeatureGates{ @@ -777,6 +787,9 @@ func TestBuildAll_WithFeatureGates_RestrictedPodSecurityStandard(t *testing.T) { Ruler: &lokiv1.LokiComponentSpec{ Replicas: 1, }, + PatternIngester: &lokiv1.LokiComponentSpec{ + Replicas: 1, + }, }, }, Gates: configv1.FeatureGates{ diff --git a/operator/internal/manifests/config.go b/operator/internal/manifests/config.go index 300f564f78e96..2ef20e7e093a1 100644 --- a/operator/internal/manifests/config.go +++ b/operator/internal/manifests/config.go @@ -151,11 +151,12 @@ func ConfigOptions(opt Options) config.Options { }, ServerNames: config.TLSServerNames{ GRPC: config.GRPCServerNames{ - Compactor: fqdn(serviceNameCompactorGRPC(opt.Name), opt.Namespace), - IndexGateway: fqdn(serviceNameIndexGatewayGRPC(opt.Name), opt.Namespace), - Ingester: fqdn(serviceNameIngesterGRPC(opt.Name), opt.Namespace), - QueryFrontend: fqdn(serviceNameQueryFrontendGRPC(opt.Name), opt.Namespace), - Ruler: fqdn(serviceNameRulerGRPC(opt.Name), opt.Namespace), + Compactor: fqdn(serviceNameCompactorGRPC(opt.Name), opt.Namespace), + IndexGateway: fqdn(serviceNameIndexGatewayGRPC(opt.Name), opt.Namespace), + Ingester: fqdn(serviceNameIngesterGRPC(opt.Name), opt.Namespace), + QueryFrontend: fqdn(serviceNameQueryFrontendGRPC(opt.Name), opt.Namespace), + Ruler: fqdn(serviceNameRulerGRPC(opt.Name), opt.Namespace), + PatternIngester: fqdn(serviceNamePatternIngesterGRPC(opt.Name), opt.Namespace), }, HTTP: config.HTTPServerNames{ Querier: fqdn(serviceNameQuerierHTTP(opt.Name), opt.Namespace), @@ -203,6 +204,10 @@ func ConfigOptions(opt Options) config.Options { AlertManager: amConfig, RemoteWrite: rwConfig, }, + PatternIngester: config.Address{ + FQDN: fqdn(NewPatternIngesterGRPCService(opt).GetName(), opt.Namespace), + Port: grpcPort, + }, Retention: retentionConfig(&opt.Stack), OTLPAttributes: otlpAttributeConfig(&opt.Stack), Overrides: overrides, diff --git a/operator/internal/manifests/internal/config/build.go b/operator/internal/manifests/internal/config/build.go index 277db5b95ce9a..1a92135cea351 100644 --- a/operator/internal/manifests/internal/config/build.go +++ b/operator/internal/manifests/internal/config/build.go @@ -36,7 +36,7 @@ func Build(opts Options) ([]byte, []byte, error) { w := bytes.NewBuffer(nil) err := lokiConfigYAMLTmpl.Execute(w, opts) if err != nil { - return nil, nil, kverrors.Wrap(err, "failed to create loki configuration") + return nil, nil, kverrors.Wrap(err, "failed to create loki configuration", err.Error()) } cfg, err := io.ReadAll(w) if err != nil { diff --git a/operator/internal/manifests/internal/config/loki-config.yaml b/operator/internal/manifests/internal/config/loki-config.yaml index 9e022b59e2623..156d87f3806b2 100644 --- a/operator/internal/manifests/internal/config/loki-config.yaml +++ b/operator/internal/manifests/internal/config/loki-config.yaml @@ -182,6 +182,32 @@ ingester_client: # Values for not exposed fields are taken from the grafana/loki production # configuration manifests. # (See https://github.com/grafana/loki/blob/main/production/ksonnet/loki/config.libsonnet) +{{- if and .Stack.PatternIngester .Stack.PatternIngester.Enabled }} +pattern_ingester: + enabled: true + lifecycler: + ring: + heartbeat_timeout: 1m + replication_factor: {{ .Stack.Replication.Factor }} + kvstore: + store: memberlist + num_tokens: 512 + heartbeat_period: 5s + join_after: 30s + client_config: + grpc_client_config: + max_recv_msg_size: 67108864 +{{- if .Gates.GRPCEncryption }} + tls_enabled: true + tls_cert_path: {{ .TLS.Paths.GRPC.Certificate }} + tls_key_path: {{ .TLS.Paths.GRPC.Key }} + tls_ca_path: {{ .TLS.Paths.CA }} + tls_server_name: {{ .TLS.ServerNames.GRPC.PatternIngester }} + tls_cipher_suites: {{ .TLS.CipherSuitesString }} + tls_min_version: {{ .TLS.MinTLSVersion }} +{{- end }} + remote_timeout: 1s +{{- end }} limits_config: ingestion_rate_strategy: global ingestion_rate_mb: {{ .Stack.Limits.Global.IngestionLimits.IngestionRate }} diff --git a/operator/internal/manifests/internal/config/options.go b/operator/internal/manifests/internal/config/options.go index 5f3d79c80df9c..d5c8fd1f8a08a 100644 --- a/operator/internal/manifests/internal/config/options.go +++ b/operator/internal/manifests/internal/config/options.go @@ -25,6 +25,7 @@ type Options struct { Querier Address IndexGateway Address Ruler Ruler + PatternIngester Address StorageDirectory string MaxConcurrent MaxConcurrent WriteAheadLog WriteAheadLog @@ -302,11 +303,12 @@ type TLSServerNames struct { } type GRPCServerNames struct { - Compactor string - IndexGateway string - Ingester string - QueryFrontend string - Ruler string + Compactor string + IndexGateway string + Ingester string + QueryFrontend string + Ruler string + PatternIngester string } type HTTPServerNames struct { diff --git a/operator/internal/manifests/internal/sizes.go b/operator/internal/manifests/internal/sizes.go index 98847cca0760d..2925dd176eea4 100644 --- a/operator/internal/manifests/internal/sizes.go +++ b/operator/internal/manifests/internal/sizes.go @@ -15,23 +15,25 @@ type ComponentResources struct { Ruler ResourceRequirements WALStorage ResourceRequirements // these two don't need a PVCSize - Querier corev1.ResourceRequirements - Distributor corev1.ResourceRequirements - QueryFrontend corev1.ResourceRequirements - Gateway corev1.ResourceRequirements + Querier corev1.ResourceRequirements + Distributor corev1.ResourceRequirements + QueryFrontend corev1.ResourceRequirements + Gateway corev1.ResourceRequirements + PatternIngester corev1.ResourceRequirements } func (c ComponentResources) DeepCopy() ComponentResources { return ComponentResources{ - IndexGateway: *c.IndexGateway.DeepCopy(), - Ingester: *c.Ingester.DeepCopy(), - Compactor: *c.Compactor.DeepCopy(), - Ruler: *c.Ruler.DeepCopy(), - WALStorage: *c.WALStorage.DeepCopy(), - Querier: *c.Querier.DeepCopy(), - Distributor: *c.Distributor.DeepCopy(), - QueryFrontend: *c.QueryFrontend.DeepCopy(), - Gateway: *c.Gateway.DeepCopy(), + IndexGateway: *c.IndexGateway.DeepCopy(), + Ingester: *c.Ingester.DeepCopy(), + Compactor: *c.Compactor.DeepCopy(), + Ruler: *c.Ruler.DeepCopy(), + WALStorage: *c.WALStorage.DeepCopy(), + Querier: *c.Querier.DeepCopy(), + Distributor: *c.Distributor.DeepCopy(), + QueryFrontend: *c.QueryFrontend.DeepCopy(), + Gateway: *c.Gateway.DeepCopy(), + PatternIngester: *c.PatternIngester.DeepCopy(), } } @@ -128,6 +130,12 @@ var resourceRequirementsTable = map[lokiv1.LokiStackSizeType]ComponentResources{ WALStorage: ResourceRequirements{ PVCSize: resource.MustParse("150Gi"), }, + /* PatternIngester: corev1.ResourceRequirements{ + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("500Mi"), + }, + }, */ }, lokiv1.SizeOneXExtraSmall: { Querier: corev1.ResourceRequirements{ @@ -318,6 +326,7 @@ func ResourceRequirementsForSize(size lokiv1.LokiStackSizeType, useRequestsAsLim resources.Distributor.Limits = resources.Distributor.Requests.DeepCopy() resources.QueryFrontend.Limits = resources.QueryFrontend.Requests.DeepCopy() resources.Gateway.Limits = resources.Gateway.Requests.DeepCopy() + resources.PatternIngester.Limits = resources.PatternIngester.Requests.DeepCopy() } return resources } @@ -379,6 +388,9 @@ var StackSizeTable = map[lokiv1.LokiStackSizeType]lokiv1.LokiStackSpec{ Ruler: &lokiv1.LokiComponentSpec{ Replicas: 1, }, + PatternIngester: &lokiv1.LokiComponentSpec{ + Replicas: 1, + }, }, }, @@ -438,6 +450,9 @@ var StackSizeTable = map[lokiv1.LokiStackSizeType]lokiv1.LokiStackSpec{ Ruler: &lokiv1.LokiComponentSpec{ Replicas: 2, }, + PatternIngester: &lokiv1.LokiComponentSpec{ + Replicas: 1, + }, }, }, diff --git a/operator/internal/manifests/networkpolicy.go b/operator/internal/manifests/networkpolicy.go index 20833212ba877..613be3aafea96 100644 --- a/operator/internal/manifests/networkpolicy.go +++ b/operator/internal/manifests/networkpolicy.go @@ -63,7 +63,7 @@ var ( { Key: "app.kubernetes.io/component", Operator: metav1.LabelSelectorOpIn, - Values: []string{"distributor", "ingester", "query-frontend", "querier", "ruler", "index-gateway", "compactor"}, + Values: []string{"distributor", "ingester", "query-frontend", "querier", "ruler", "index-gateway", "compactor", "pattern-ingester"}, }, }, } @@ -346,7 +346,7 @@ func buildLokiAllowBucketEgress(opts Options) *networkingv1.NetworkPolicy { { Key: "app.kubernetes.io/component", Operator: metav1.LabelSelectorOpIn, - Values: []string{"ingester", "querier", "index-gateway", "compactor", "ruler"}, + Values: []string{"ingester", "querier", "index-gateway", "compactor", "ruler", "pattern-ingester"}, }, }, }, diff --git a/operator/internal/manifests/networkpolicy_test.go b/operator/internal/manifests/networkpolicy_test.go index 608b04ddcdd41..1bb089bed9265 100644 --- a/operator/internal/manifests/networkpolicy_test.go +++ b/operator/internal/manifests/networkpolicy_test.go @@ -417,7 +417,7 @@ func TestBuildLokiAllowBucketEgress(t *testing.T) { require.Len(t, policy.Spec.PodSelector.MatchExpressions, 2) componentExpr := policy.Spec.PodSelector.MatchExpressions[1] require.Equal(t, "app.kubernetes.io/component", componentExpr.Key) - require.ElementsMatch(t, []string{"ingester", "querier", "index-gateway", "compactor", "ruler"}, componentExpr.Values) + require.ElementsMatch(t, []string{"ingester", "querier", "index-gateway", "compactor", "ruler", "pattern-ingester"}, componentExpr.Values) // Verify egress rules require.Len(t, policy.Spec.Egress, 1, "Should have exactly one egress rule") diff --git a/operator/internal/manifests/pattern_ingester.go b/operator/internal/manifests/pattern_ingester.go new file mode 100644 index 0000000000000..7edf6310cf786 --- /dev/null +++ b/operator/internal/manifests/pattern_ingester.go @@ -0,0 +1,255 @@ +package manifests + +import ( + "fmt" + "path" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/grafana/loki/operator/internal/manifests/internal/config" +) + +func BuildPatternIngester(opts Options) ([]client.Object, error) { + deployment := NewPatternIngesterDeployment(opts) + if opts.Gates.HTTPEncryption { + if err := configurePatternIngesterHTTPServicePKI(deployment, opts); err != nil { + return nil, err + } + } + + if opts.Gates.GRPCEncryption { + if err := configurePatternIngesterGRPCServicePKI(deployment, opts); err != nil { + return nil, err + } + } + + if opts.Gates.HTTPEncryption || opts.Gates.GRPCEncryption { + caBundleName := signingCABundleName(opts.Name) + if err := configureServiceCA(&deployment.Spec.Template.Spec, caBundleName); err != nil { + return nil, err + } + } + + if opts.Gates.RestrictedPodSecurityStandard { + if err := configurePodSpecForRestrictedStandard(&deployment.Spec.Template.Spec); err != nil { + return nil, err + } + } + + if err := configureHashRingEnv(&deployment.Spec.Template.Spec, opts); err != nil { + return nil, err + } + + if err := configureProxyEnv(&deployment.Spec.Template.Spec, opts); err != nil { + return nil, err + } + + if err := configureReplication(&deployment.Spec.Template, opts.Stack.Replication, LabelPatternIngesterComponent, opts.Name); err != nil { + return nil, err + } + + return []client.Object{ + deployment, + NewPatternIngesterGRPCService(opts), + NewPatternIngesterHTTPService(opts), + newPatternIngesterPodDisruptionBudget(opts), + }, nil +} + +func NewPatternIngesterDeployment(opts Options) *appsv1.Deployment { + l := ComponentLabels(LabelPatternIngesterComponent, opts.Name) + a := commonAnnotations(opts) + podSpec := corev1.PodSpec{ + ServiceAccountName: opts.Name, + Affinity: configureAffinity(LabelPatternIngesterComponent, opts.Name, opts.Gates.DefaultNodeAffinity, opts.Stack.Template.PatternIngester), + Volumes: []corev1.Volume{ + { + Name: configVolumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + DefaultMode: &defaultConfigMapMode, + LocalObjectReference: corev1.LocalObjectReference{ + Name: lokiConfigMapName(opts.Name), + }, + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Image: opts.Image, + Name: "loki-pattern-ingester", + Resources: corev1.ResourceRequirements{ + Limits: opts.ResourceRequirements.PatternIngester.Limits, + Requests: opts.ResourceRequirements.PatternIngester.Requests, + }, + Args: []string{ + "-target=pattern-ingester", + fmt.Sprintf("-config.file=%s", path.Join(config.LokiConfigMountDir, config.LokiConfigFileName)), + fmt.Sprintf("-runtime-config.file=%s", path.Join(config.LokiConfigMountDir, config.LokiRuntimeConfigFileName)), + "-config.expand-env=true", + }, + ReadinessProbe: lokiReadinessProbe(), + LivenessProbe: lokiLivenessProbe(), + Ports: []corev1.ContainerPort{ + { + Name: lokiHTTPPortName, + ContainerPort: httpPort, + Protocol: protocolTCP, + }, + { + Name: lokiGRPCPortName, + ContainerPort: grpcPort, + Protocol: protocolTCP, + }, + { + Name: lokiGossipPortName, + ContainerPort: gossipPort, + Protocol: protocolTCP, + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: configVolumeName, + ReadOnly: false, + MountPath: config.LokiConfigMountDir, + }, + }, + TerminationMessagePath: "/dev/termination-log", + TerminationMessagePolicy: "File", + ImagePullPolicy: "IfNotPresent", + }, + }, + } + + if opts.Stack.Template != nil && opts.Stack.Template.PatternIngester != nil { + podSpec.Tolerations = opts.Stack.Template.PatternIngester.Tolerations + podSpec.NodeSelector = opts.Stack.Template.PatternIngester.NodeSelector + } + + return &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: PatternIngesterName(opts.Name), + Labels: l, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: ptr.To(opts.Stack.Template.PatternIngester.Replicas), + Selector: &metav1.LabelSelector{ + MatchLabels: labels.Merge(l, GossipLabels()), + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("loki-pattern-ingester-%s", opts.Name), + Labels: labels.Merge(l, GossipLabels()), + Annotations: a, + }, + Spec: podSpec, + }, + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateDeploymentStrategyType, + }, + }, + } +} + +// NewPatternIngesterGRPCService creates a k8s service for the PatternIngester GRPC endpoint +func NewPatternIngesterGRPCService(opts Options) *corev1.Service { + serviceName := serviceNamePatternIngesterGRPC(opts.Name) + labels := ComponentLabels(LabelPatternIngesterComponent, opts.Name) + + return &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Labels: labels, + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "None", + Ports: []corev1.ServicePort{ + { + Name: lokiGRPCPortName, + Port: grpcPort, + Protocol: protocolTCP, + TargetPort: intstr.IntOrString{IntVal: grpcPort}, + }, + }, + Selector: labels, + }, + } +} + +// NewPatternIngesterHTTPService creates a k8s service for the PatternIngester HTTP endpoint +func NewPatternIngesterHTTPService(opts Options) *corev1.Service { + serviceName := serviceNamePatternIngesterHTTP(opts.Name) + labels := ComponentLabels(LabelPatternIngesterComponent, opts.Name) + + return &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Labels: labels, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: lokiHTTPPortName, + Port: httpPort, + Protocol: protocolTCP, + TargetPort: intstr.IntOrString{IntVal: httpPort}, + }, + }, + Selector: labels, + }, + } +} + +func configurePatternIngesterHTTPServicePKI(deployment *appsv1.Deployment, opts Options) error { + serviceName := serviceNamePatternIngesterHTTP(opts.Name) + return configureHTTPServicePKI(&deployment.Spec.Template.Spec, serviceName) +} + +func configurePatternIngesterGRPCServicePKI(deployment *appsv1.Deployment, opts Options) error { + serviceName := serviceNamePatternIngesterGRPC(opts.Name) + return configureGRPCServicePKI(&deployment.Spec.Template.Spec, serviceName) +} + +// newPatternIngesterPodDisruptionBudget returns a PodDisruptionBudget for the LokiStack +// PatternIngester pods. +func newPatternIngesterPodDisruptionBudget(opts Options) *policyv1.PodDisruptionBudget { + l := ComponentLabels(LabelPatternIngesterComponent, opts.Name) + mu := intstr.FromInt(1) + return &policyv1.PodDisruptionBudget{ + TypeMeta: metav1.TypeMeta{ + Kind: "PodDisruptionBudget", + APIVersion: policyv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Labels: l, + Name: PatternIngesterName(opts.Name), + Namespace: opts.Namespace, + }, + Spec: policyv1.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: l, + }, + MinAvailable: &mu, + }, + } +} diff --git a/operator/internal/manifests/pattern_ingester_test.go b/operator/internal/manifests/pattern_ingester_test.go new file mode 100644 index 0000000000000..5e5908e508bdb --- /dev/null +++ b/operator/internal/manifests/pattern_ingester_test.go @@ -0,0 +1,180 @@ +package manifests + +import ( + "testing" + + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + lokiv1 "github.com/grafana/loki/operator/api/loki/v1" + "github.com/grafana/loki/operator/internal/manifests/storage" +) + +func TestNewPatternIngesterDeployment_HasTemplateConfigHashAnnotation(t *testing.T) { + d := NewPatternIngesterDeployment(Options{ + Name: "abcd", + Namespace: "efgh", + ConfigSHA1: "deadbeef", + Stack: lokiv1.LokiStackSpec{ + StorageClassName: "standard", + Template: &lokiv1.LokiTemplateSpec{ + PatternIngester: &lokiv1.LokiComponentSpec{ + Replicas: 1, + }, + }, + }, + }) + + annotations := d.Spec.Template.Annotations + require.Contains(t, annotations, AnnotationLokiConfigHash) + require.Equal(t, annotations[AnnotationLokiConfigHash], "deadbeef") +} + +func TestNewPatternIngesterDeployment_HasTemplateObjectStoreHashAnnotation(t *testing.T) { + d := NewPatternIngesterDeployment(Options{ + Name: "abcd", + Namespace: "efgh", + ObjectStorage: storage.Options{ + SecretSHA1: "deadbeef", + }, + Stack: lokiv1.LokiStackSpec{ + StorageClassName: "standard", + Template: &lokiv1.LokiTemplateSpec{ + PatternIngester: &lokiv1.LokiComponentSpec{ + Replicas: 1, + }, + }, + }, + }) + + annotations := d.Spec.Template.Annotations + require.Contains(t, annotations, AnnotationLokiObjectStoreHash) + require.Equal(t, annotations[AnnotationLokiObjectStoreHash], "deadbeef") +} + +func TestNewPatternIngesterDeployment_HasTemplateCertRotationRequiredAtAnnotation(t *testing.T) { + ss := NewPatternIngesterDeployment(Options{ + Name: "abcd", + Namespace: "efgh", + CertRotationRequiredAt: "deadbeef", + Stack: lokiv1.LokiStackSpec{ + Template: &lokiv1.LokiTemplateSpec{ + PatternIngester: &lokiv1.LokiComponentSpec{ + Replicas: 1, + }, + }, + }, + }) + + annotations := ss.Spec.Template.Annotations + require.Contains(t, annotations, AnnotationCertRotationRequiredAt) + require.Equal(t, annotations[AnnotationCertRotationRequiredAt], "deadbeef") +} + +func TestNewPatternIngesterDeployment_SelectorMatchesLabels(t *testing.T) { + // You must set the .spec.selector field of a Deployment to match the labels of + // its .spec.template.metadata.labels. Prior to Kubernetes 1.8, the + // .spec.selector field was defaulted when omitted. In 1.8 and later versions, + // failing to specify a matching Pod Selector will result in a validation error + // during Deployment creation. + // See https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#pod-selector + ss := NewPatternIngesterDeployment(Options{ + Name: "abcd", + Namespace: "efgh", + Stack: lokiv1.LokiStackSpec{ + StorageClassName: "standard", + Template: &lokiv1.LokiTemplateSpec{ + PatternIngester: &lokiv1.LokiComponentSpec{ + Replicas: 1, + }, + }, + }, + }) + + l := ss.Spec.Template.GetObjectMeta().GetLabels() + for key, value := range ss.Spec.Selector.MatchLabels { + require.Contains(t, l, key) + require.Equal(t, l[key], value) + } +} + +func TestBuildPatternIngester_PodDisruptionBudget(t *testing.T) { + opts := Options{ + Name: "abcd", + Namespace: "efgh", + Stack: lokiv1.LokiStackSpec{ + Template: &lokiv1.LokiTemplateSpec{ + PatternIngester: &lokiv1.LokiComponentSpec{ + Replicas: 1, + }, + }, + }, + } + + objs, err := BuildPatternIngester(opts) + require.NoError(t, err) + require.Len(t, objs, 4) + + pdb := objs[3].(*policyv1.PodDisruptionBudget) + require.Equal(t, "abcd-pattern-ingester", pdb.Name) + require.Equal(t, "efgh", pdb.Namespace) + require.NotNil(t, pdb.Spec.MinAvailable.IntVal) + require.Equal(t, int32(1), pdb.Spec.MinAvailable.IntVal) + require.EqualValues(t, ComponentLabels(LabelPatternIngesterComponent, opts.Name), pdb.Spec.Selector.MatchLabels) +} + +func TestNewPatternIngesterDeployment_TopologySpreadConstraints(t *testing.T) { + obj, _ := BuildPatternIngester(Options{ + Name: "abcd", + Namespace: "efgh", + Stack: lokiv1.LokiStackSpec{ + Template: &lokiv1.LokiTemplateSpec{ + PatternIngester: &lokiv1.LokiComponentSpec{ + Replicas: 1, + }, + }, + Replication: &lokiv1.ReplicationSpec{ + Zones: []lokiv1.ZoneSpec{ + { + TopologyKey: "zone", + MaxSkew: 2, + }, + { + TopologyKey: "region", + MaxSkew: 1, + }, + }, + Factor: 1, + }, + }, + }) + + d := obj[0].(*appsv1.Deployment) + require.Equal(t, []corev1.TopologySpreadConstraint{ + { + MaxSkew: 2, + TopologyKey: "zone", + WhenUnsatisfiable: "DoNotSchedule", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/component": "pattern-ingester", + "app.kubernetes.io/instance": "abcd", + }, + }, + }, + { + MaxSkew: 1, + TopologyKey: "region", + WhenUnsatisfiable: "DoNotSchedule", + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/component": "pattern-ingester", + "app.kubernetes.io/instance": "abcd", + }, + }, + }, + }, d.Spec.Template.Spec.TopologySpreadConstraints) +} diff --git a/operator/internal/manifests/service_monitor.go b/operator/internal/manifests/service_monitor.go index de209a29e5ffc..42605350784ff 100644 --- a/operator/internal/manifests/service_monitor.go +++ b/operator/internal/manifests/service_monitor.go @@ -18,6 +18,7 @@ func BuildServiceMonitors(opts Options) []client.Object { NewIndexGatewayServiceMonitor(opts), NewRulerServiceMonitor(opts), NewGatewayServiceMonitor(opts), + NewPatternIngesterServiceMonitor(opts), } } @@ -118,6 +119,17 @@ func NewGatewayServiceMonitor(opts Options) *monitoringv1.ServiceMonitor { return sm } +// NewIngesterServiceMonitor creates a k8s service monitor for the pattern ingester component +func NewPatternIngesterServiceMonitor(opts Options) *monitoringv1.ServiceMonitor { + l := ComponentLabels(LabelPatternIngesterComponent, opts.Name) + + serviceMonitorName := serviceMonitorName(PatternIngesterName(opts.Name)) + serviceName := serviceNamePatternIngesterHTTP(opts.Name) + lokiEndpoint := lokiServiceMonitorEndpoint(opts.Name, lokiHTTPPortName, serviceName, opts.Namespace, opts.Gates.ServiceMonitorTLSEndpoints) + + return newServiceMonitor(opts.Namespace, serviceMonitorName, l, lokiEndpoint) +} + func newServiceMonitor(namespace, serviceMonitorName string, labels labels.Set, endpoint monitoringv1.Endpoint) *monitoringv1.ServiceMonitor { return &monitoringv1.ServiceMonitor{ TypeMeta: metav1.TypeMeta{ diff --git a/operator/internal/manifests/var.go b/operator/internal/manifests/var.go index 032e3899f336c..31c50cce5d6d6 100644 --- a/operator/internal/manifests/var.go +++ b/operator/internal/manifests/var.go @@ -64,7 +64,7 @@ const ( DefaultContainerImage = "docker.io/grafana/loki:3.5.5" // DefaultLokiStackGatewayImage declares the default image for lokiStack-gateway. - DefaultLokiStackGatewayImage = "quay.io/observatorium/api:latest" + DefaultLokiStackGatewayImage = "quay.io/btaani/api:latest" // PrometheusCAFile declares the path for prometheus CA file for service monitors. PrometheusCAFile string = "/etc/prometheus/configmaps/serving-certs-ca-bundle/service-ca.crt" @@ -99,6 +99,8 @@ const ( LabelRulerComponent string = "ruler" // LabelGatewayComponent is the label value for the lokiStack-gateway component LabelGatewayComponent string = "lokistack-gateway" + // LabelPatternIngesterComponent is the label value for the pattern-ingester component + LabelPatternIngesterComponent string = "pattern-ingester" // httpTLSDir is the path that is mounted from the secret for TLS httpTLSDir = "/var/run/tls/http" @@ -253,6 +255,11 @@ func PrometheusRuleName(stackName string) string { return fmt.Sprintf("%s-prometheus-rule", stackName) } +// PatternIngesterName is the name of the pattern-ingester deployment +func PatternIngesterName(stackName string) string { + return fmt.Sprintf("%s-pattern-ingester", stackName) +} + func lokiConfigMapName(stackName string) string { return fmt.Sprintf("%s-config", stackName) } @@ -385,6 +392,14 @@ func serviceMonitorName(componentName string) string { return fmt.Sprintf("%s-monitor", componentName) } +func serviceNamePatternIngesterGRPC(stackName string) string { + return fmt.Sprintf("%s-pattern-ingester-grpc", stackName) +} + +func serviceNamePatternIngesterHTTP(stackName string) string { + return fmt.Sprintf("%s-pattern-ingester-http", stackName) +} + func signingCABundleName(stackName string) string { return fmt.Sprintf("%s-ca-bundle", stackName) } diff --git a/operator/internal/status/components.go b/operator/internal/status/components.go index 78f26572831ce..9cad867aa7acc 100644 --- a/operator/internal/status/components.go +++ b/operator/internal/status/components.go @@ -56,6 +56,11 @@ func generateComponentStatus(ctx context.Context, k k8s.Client, s *lokiv1.LokiSt return nil, kverrors.Wrap(err, "failed lookup LokiStack component pods status", "name", manifests.LabelRulerComponent) } + result.PatternIngester, err = appendPodStatus(ctx, k, manifests.LabelPatternIngesterComponent, s.Name, s.Namespace) + if err != nil { + return nil, kverrors.Wrap(err, "failed lookup LokiStack component pods status", "name", manifests.LabelPatternIngesterComponent) + } + return result, nil } diff --git a/operator/internal/status/components_test.go b/operator/internal/status/components_test.go index 2606d704c4188..4a7e553eb4e30 100644 --- a/operator/internal/status/components_test.go +++ b/operator/internal/status/components_test.go @@ -80,70 +80,76 @@ func TestGenerateComponentStatus(t *testing.T) { { desc: "no pods", componentPods: map[string]*corev1.PodList{ - manifests.LabelCompactorComponent: {}, - manifests.LabelDistributorComponent: {}, - manifests.LabelIngesterComponent: {}, - manifests.LabelQuerierComponent: {}, - manifests.LabelQueryFrontendComponent: {}, - manifests.LabelIndexGatewayComponent: {}, - manifests.LabelRulerComponent: {}, - manifests.LabelGatewayComponent: {}, + manifests.LabelCompactorComponent: {}, + manifests.LabelDistributorComponent: {}, + manifests.LabelIngesterComponent: {}, + manifests.LabelQuerierComponent: {}, + manifests.LabelQueryFrontendComponent: {}, + manifests.LabelIndexGatewayComponent: {}, + manifests.LabelRulerComponent: {}, + manifests.LabelGatewayComponent: {}, + manifests.LabelPatternIngesterComponent: {}, }, wantComponentStatus: &lokiv1.LokiStackComponentStatus{ - Compactor: empty, - Distributor: empty, - IndexGateway: empty, - Ingester: empty, - Querier: empty, - QueryFrontend: empty, - Gateway: empty, - Ruler: empty, + Compactor: empty, + Distributor: empty, + IndexGateway: empty, + Ingester: empty, + Querier: empty, + QueryFrontend: empty, + Gateway: empty, + Ruler: empty, + PatternIngester: empty, }, }, { desc: "all one pod running", componentPods: map[string]*corev1.PodList{ - manifests.LabelCompactorComponent: createPodList(manifests.LabelCompactorComponent, false, corev1.PodRunning), - manifests.LabelDistributorComponent: createPodList(manifests.LabelDistributorComponent, false, corev1.PodRunning), - manifests.LabelIngesterComponent: createPodList(manifests.LabelIngesterComponent, false, corev1.PodRunning), - manifests.LabelQuerierComponent: createPodList(manifests.LabelQuerierComponent, false, corev1.PodRunning), - manifests.LabelQueryFrontendComponent: createPodList(manifests.LabelQueryFrontendComponent, false, corev1.PodRunning), - manifests.LabelIndexGatewayComponent: createPodList(manifests.LabelIndexGatewayComponent, false, corev1.PodRunning), - manifests.LabelRulerComponent: createPodList(manifests.LabelRulerComponent, false, corev1.PodRunning), - manifests.LabelGatewayComponent: createPodList(manifests.LabelGatewayComponent, false, corev1.PodRunning), + manifests.LabelCompactorComponent: createPodList(manifests.LabelCompactorComponent, false, corev1.PodRunning), + manifests.LabelDistributorComponent: createPodList(manifests.LabelDistributorComponent, false, corev1.PodRunning), + manifests.LabelIngesterComponent: createPodList(manifests.LabelIngesterComponent, false, corev1.PodRunning), + manifests.LabelQuerierComponent: createPodList(manifests.LabelQuerierComponent, false, corev1.PodRunning), + manifests.LabelQueryFrontendComponent: createPodList(manifests.LabelQueryFrontendComponent, false, corev1.PodRunning), + manifests.LabelIndexGatewayComponent: createPodList(manifests.LabelIndexGatewayComponent, false, corev1.PodRunning), + manifests.LabelRulerComponent: createPodList(manifests.LabelRulerComponent, false, corev1.PodRunning), + manifests.LabelGatewayComponent: createPodList(manifests.LabelGatewayComponent, false, corev1.PodRunning), + manifests.LabelPatternIngesterComponent: createPodList(manifests.LabelPatternIngesterComponent, false, corev1.PodRunning), }, wantComponentStatus: &lokiv1.LokiStackComponentStatus{ - Compactor: lokiv1.PodStatusMap{lokiv1.PodRunning: {"compactor-pod-0"}}, - Distributor: lokiv1.PodStatusMap{lokiv1.PodRunning: {"distributor-pod-0"}}, - IndexGateway: lokiv1.PodStatusMap{lokiv1.PodRunning: {"index-gateway-pod-0"}}, - Ingester: lokiv1.PodStatusMap{lokiv1.PodRunning: {"ingester-pod-0"}}, - Querier: lokiv1.PodStatusMap{lokiv1.PodRunning: {"querier-pod-0"}}, - QueryFrontend: lokiv1.PodStatusMap{lokiv1.PodRunning: {"query-frontend-pod-0"}}, - Gateway: lokiv1.PodStatusMap{lokiv1.PodRunning: {"lokistack-gateway-pod-0"}}, - Ruler: lokiv1.PodStatusMap{lokiv1.PodRunning: {"ruler-pod-0"}}, + Compactor: lokiv1.PodStatusMap{lokiv1.PodRunning: {"compactor-pod-0"}}, + Distributor: lokiv1.PodStatusMap{lokiv1.PodRunning: {"distributor-pod-0"}}, + IndexGateway: lokiv1.PodStatusMap{lokiv1.PodRunning: {"index-gateway-pod-0"}}, + Ingester: lokiv1.PodStatusMap{lokiv1.PodRunning: {"ingester-pod-0"}}, + Querier: lokiv1.PodStatusMap{lokiv1.PodRunning: {"querier-pod-0"}}, + QueryFrontend: lokiv1.PodStatusMap{lokiv1.PodRunning: {"query-frontend-pod-0"}}, + Gateway: lokiv1.PodStatusMap{lokiv1.PodRunning: {"lokistack-gateway-pod-0"}}, + Ruler: lokiv1.PodStatusMap{lokiv1.PodRunning: {"ruler-pod-0"}}, + PatternIngester: lokiv1.PodStatusMap{lokiv1.PodRunning: {"pattern-ingester-pod-0"}}, }, }, { desc: "all pods without ruler", componentPods: map[string]*corev1.PodList{ - manifests.LabelCompactorComponent: createPodList(manifests.LabelCompactorComponent, false, corev1.PodRunning), - manifests.LabelDistributorComponent: createPodList(manifests.LabelDistributorComponent, false, corev1.PodRunning), - manifests.LabelIngesterComponent: createPodList(manifests.LabelIngesterComponent, false, corev1.PodRunning), - manifests.LabelQuerierComponent: createPodList(manifests.LabelQuerierComponent, false, corev1.PodRunning), - manifests.LabelQueryFrontendComponent: createPodList(manifests.LabelQueryFrontendComponent, false, corev1.PodRunning), - manifests.LabelIndexGatewayComponent: createPodList(manifests.LabelIndexGatewayComponent, false, corev1.PodRunning), - manifests.LabelRulerComponent: {}, - manifests.LabelGatewayComponent: createPodList(manifests.LabelGatewayComponent, false, corev1.PodRunning), + manifests.LabelCompactorComponent: createPodList(manifests.LabelCompactorComponent, false, corev1.PodRunning), + manifests.LabelDistributorComponent: createPodList(manifests.LabelDistributorComponent, false, corev1.PodRunning), + manifests.LabelIngesterComponent: createPodList(manifests.LabelIngesterComponent, false, corev1.PodRunning), + manifests.LabelQuerierComponent: createPodList(manifests.LabelQuerierComponent, false, corev1.PodRunning), + manifests.LabelQueryFrontendComponent: createPodList(manifests.LabelQueryFrontendComponent, false, corev1.PodRunning), + manifests.LabelIndexGatewayComponent: createPodList(manifests.LabelIndexGatewayComponent, false, corev1.PodRunning), + manifests.LabelRulerComponent: {}, + manifests.LabelGatewayComponent: createPodList(manifests.LabelGatewayComponent, false, corev1.PodRunning), + manifests.LabelPatternIngesterComponent: {}, }, wantComponentStatus: &lokiv1.LokiStackComponentStatus{ - Compactor: lokiv1.PodStatusMap{lokiv1.PodRunning: {"compactor-pod-0"}}, - Distributor: lokiv1.PodStatusMap{lokiv1.PodRunning: {"distributor-pod-0"}}, - IndexGateway: lokiv1.PodStatusMap{lokiv1.PodRunning: {"index-gateway-pod-0"}}, - Ingester: lokiv1.PodStatusMap{lokiv1.PodRunning: {"ingester-pod-0"}}, - Querier: lokiv1.PodStatusMap{lokiv1.PodRunning: {"querier-pod-0"}}, - QueryFrontend: lokiv1.PodStatusMap{lokiv1.PodRunning: {"query-frontend-pod-0"}}, - Gateway: lokiv1.PodStatusMap{lokiv1.PodRunning: {"lokistack-gateway-pod-0"}}, - Ruler: empty, + Compactor: lokiv1.PodStatusMap{lokiv1.PodRunning: {"compactor-pod-0"}}, + Distributor: lokiv1.PodStatusMap{lokiv1.PodRunning: {"distributor-pod-0"}}, + IndexGateway: lokiv1.PodStatusMap{lokiv1.PodRunning: {"index-gateway-pod-0"}}, + Ingester: lokiv1.PodStatusMap{lokiv1.PodRunning: {"ingester-pod-0"}}, + Querier: lokiv1.PodStatusMap{lokiv1.PodRunning: {"querier-pod-0"}}, + QueryFrontend: lokiv1.PodStatusMap{lokiv1.PodRunning: {"query-frontend-pod-0"}}, + Gateway: lokiv1.PodStatusMap{lokiv1.PodRunning: {"lokistack-gateway-pod-0"}}, + Ruler: empty, + PatternIngester: empty, }, }, } @@ -166,7 +172,7 @@ func TestGenerateComponentStatus(t *testing.T) { require.Equal(t, tc.wantComponentStatus, componentStatus) // one list call for each component - require.Equal(t, 8, k.ListCallCount()) + require.Equal(t, 9, k.ListCallCount()) }) } } diff --git a/operator/internal/status/lokistack.go b/operator/internal/status/lokistack.go index 9d4efd06df881..cc624e45ac0b1 100644 --- a/operator/internal/status/lokistack.go +++ b/operator/internal/status/lokistack.go @@ -107,7 +107,8 @@ func generateCondition(ctx context.Context, cs *lokiv1.LokiStackComponentStatus, len(cs.QueryFrontend[lokiv1.PodFailed]) + len(cs.Gateway[lokiv1.PodFailed]) + len(cs.IndexGateway[lokiv1.PodFailed]) + - len(cs.Ruler[lokiv1.PodFailed]) + len(cs.Ruler[lokiv1.PodFailed]) + + len(cs.PatternIngester[lokiv1.PodFailed]) if failed != 0 { return conditionFailed, nil @@ -121,7 +122,8 @@ func generateCondition(ctx context.Context, cs *lokiv1.LokiStackComponentStatus, len(cs.QueryFrontend[lokiv1.PodPending]) + len(cs.Gateway[lokiv1.PodPending]) + len(cs.IndexGateway[lokiv1.PodPending]) + - len(cs.Ruler[lokiv1.PodPending]) + len(cs.Ruler[lokiv1.PodPending]) + + len(cs.PatternIngester[lokiv1.PodPending]) if pending != 0 { if stack.Spec.Replication != nil && len(stack.Spec.Replication.Zones) > 0 { @@ -152,7 +154,8 @@ func generateCondition(ctx context.Context, cs *lokiv1.LokiStackComponentStatus, len(cs.QueryFrontend[lokiv1.PodRunning]) + len(cs.Gateway[lokiv1.PodRunning]) + len(cs.IndexGateway[lokiv1.PodRunning]) + - len(cs.Ruler[lokiv1.PodRunning]) + len(cs.Ruler[lokiv1.PodRunning]) + + len(cs.PatternIngester[lokiv1.PodRunning]) if running > 0 { return conditionRunning, nil diff --git a/operator/internal/status/status_test.go b/operator/internal/status/status_test.go index 1db03c754fd18..ff783268936e6 100644 --- a/operator/internal/status/status_test.go +++ b/operator/internal/status/status_test.go @@ -33,26 +33,28 @@ func TestRefreshSuccess(t *testing.T) { } componentPods := map[string]*corev1.PodList{ - manifests.LabelCompactorComponent: createPodList(manifests.LabelCompactorComponent, true, corev1.PodRunning), - manifests.LabelDistributorComponent: createPodList(manifests.LabelDistributorComponent, true, corev1.PodRunning), - manifests.LabelIngesterComponent: createPodList(manifests.LabelIngesterComponent, true, corev1.PodRunning), - manifests.LabelQuerierComponent: createPodList(manifests.LabelQuerierComponent, true, corev1.PodRunning), - manifests.LabelQueryFrontendComponent: createPodList(manifests.LabelQueryFrontendComponent, true, corev1.PodRunning), - manifests.LabelIndexGatewayComponent: createPodList(manifests.LabelIndexGatewayComponent, true, corev1.PodRunning), - manifests.LabelRulerComponent: createPodList(manifests.LabelRulerComponent, true, corev1.PodRunning), - manifests.LabelGatewayComponent: createPodList(manifests.LabelGatewayComponent, true, corev1.PodRunning), + manifests.LabelCompactorComponent: createPodList(manifests.LabelCompactorComponent, true, corev1.PodRunning), + manifests.LabelDistributorComponent: createPodList(manifests.LabelDistributorComponent, true, corev1.PodRunning), + manifests.LabelIngesterComponent: createPodList(manifests.LabelIngesterComponent, true, corev1.PodRunning), + manifests.LabelQuerierComponent: createPodList(manifests.LabelQuerierComponent, true, corev1.PodRunning), + manifests.LabelQueryFrontendComponent: createPodList(manifests.LabelQueryFrontendComponent, true, corev1.PodRunning), + manifests.LabelIndexGatewayComponent: createPodList(manifests.LabelIndexGatewayComponent, true, corev1.PodRunning), + manifests.LabelRulerComponent: createPodList(manifests.LabelRulerComponent, true, corev1.PodRunning), + manifests.LabelGatewayComponent: createPodList(manifests.LabelGatewayComponent, true, corev1.PodRunning), + manifests.LabelPatternIngesterComponent: createPodList(manifests.LabelPatternIngesterComponent, true, corev1.PodRunning), } wantStatus := lokiv1.LokiStackStatus{ Components: lokiv1.LokiStackComponentStatus{ - Compactor: lokiv1.PodStatusMap{lokiv1.PodReady: {"compactor-pod-0"}}, - Distributor: lokiv1.PodStatusMap{lokiv1.PodReady: {"distributor-pod-0"}}, - IndexGateway: lokiv1.PodStatusMap{lokiv1.PodReady: {"index-gateway-pod-0"}}, - Ingester: lokiv1.PodStatusMap{lokiv1.PodReady: {"ingester-pod-0"}}, - Querier: lokiv1.PodStatusMap{lokiv1.PodReady: {"querier-pod-0"}}, - QueryFrontend: lokiv1.PodStatusMap{lokiv1.PodReady: {"query-frontend-pod-0"}}, - Gateway: lokiv1.PodStatusMap{lokiv1.PodReady: {"lokistack-gateway-pod-0"}}, - Ruler: lokiv1.PodStatusMap{lokiv1.PodReady: {"ruler-pod-0"}}, + Compactor: lokiv1.PodStatusMap{lokiv1.PodReady: {"compactor-pod-0"}}, + Distributor: lokiv1.PodStatusMap{lokiv1.PodReady: {"distributor-pod-0"}}, + IndexGateway: lokiv1.PodStatusMap{lokiv1.PodReady: {"index-gateway-pod-0"}}, + Ingester: lokiv1.PodStatusMap{lokiv1.PodReady: {"ingester-pod-0"}}, + Querier: lokiv1.PodStatusMap{lokiv1.PodReady: {"querier-pod-0"}}, + QueryFrontend: lokiv1.PodStatusMap{lokiv1.PodReady: {"query-frontend-pod-0"}}, + Gateway: lokiv1.PodStatusMap{lokiv1.PodReady: {"lokistack-gateway-pod-0"}}, + Ruler: lokiv1.PodStatusMap{lokiv1.PodReady: {"ruler-pod-0"}}, + PatternIngester: lokiv1.PodStatusMap{lokiv1.PodReady: {"pattern-ingester-pod-0"}}, }, Storage: lokiv1.LokiStackStorageStatus{ CredentialMode: lokiv1.CredentialModeStatic, @@ -79,7 +81,7 @@ func TestRefreshSuccess(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, k.GetCallCount()) - require.Equal(t, 8, k.ListCallCount()) + require.Equal(t, 9, k.ListCallCount()) require.Equal(t, 1, sw.UpdateCallCount()) _, updated, _ := sw.UpdateArgsForCall(0) @@ -145,7 +147,7 @@ func TestRefreshSuccess_ZoneAwarePendingPod(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, k.GetCallCount()) - require.Equal(t, 9, k.ListCallCount()) + require.Equal(t, 10, k.ListCallCount()) require.Equal(t, 1, sw.UpdateCallCount()) _, updated, _ := sw.UpdateArgsForCall(0) updatedStack, ok := updated.(*lokiv1.LokiStack)