This is an automated email from the ASF dual-hosted git repository.
Miretpl pushed a commit to branch chart/v1-2x-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/chart/v1-2x-test by this push:
new 6061eb0ca8c [chart/v1-2x-test] Add Flower HTTPRoute support to Helm
chart (#69945) (#70263)
6061eb0ca8c is described below
commit 6061eb0ca8c44faa4af875d4679b9ccdc79417c3
Author: Aaron Chen <[email protected]>
AuthorDate: Fri Jul 24 05:31:49 2026 +0800
[chart/v1-2x-test] Add Flower HTTPRoute support to Helm chart (#69945)
(#70263)
* Add Flower HTTPRoute support to Helm chart
* Update chart/tests/helm_tests/other/test_httproute_flower.py
* Clarify Flower HTTPRoute configuration guidance
* Update chart/docs/production-guide.rst
* Simplify HTTPRoute configuration guidance
---------
(cherry picked from commit 02a956f5cf9467b89a37dbbff9b434d7849a0be7)
Co-authored-by: Przemysław Mirowski
<[email protected]>
---
chart/docs/production-guide.rst | 37 +-
chart/templates/flower/flower-httproute.yaml | 70 ++++
chart/templates/flower/flower-ingress.yaml | 3 +
chart/values.schema.json | 96 +++++
chart/values.yaml | 52 ++-
.../airflow_aux/test_basic_helm_chart.py | 58 ++-
.../helm_tests/other/test_httproute_flower.py | 424 +++++++++++++++++++++
7 files changed, 720 insertions(+), 20 deletions(-)
diff --git a/chart/docs/production-guide.rst b/chart/docs/production-guide.rst
index 2c5994bc22d..8c03421aae5 100644
--- a/chart/docs/production-guide.rst
+++ b/chart/docs/production-guide.rst
@@ -398,14 +398,16 @@ For more information on ``Ingress``, see the
Gateway API (HTTPRoute)
^^^^^^^^^^^^^^^^^^^^^^^
-As an alternative to ``Ingress``, the chart can create a
-`Kubernetes Gateway API <https://gateway-api.sigs.k8s.io/>`_ ``HTTPRoute`` for
the API server.
+As an alternative to ``Ingress``, the chart can create
+`Kubernetes Gateway API <https://gateway-api.sigs.k8s.io/>`_ ``HTTPRoute``
resources for the API server and Flower.
This requires the Gateway API CRDs to be installed in the cluster and a
``Gateway`` to already exist —
-the chart only creates the ``HTTPRoute`` and attaches it to the Gateway via
``parentRefs``.
+the chart only creates the ``HTTPRoute`` resources and attaches them to the
Gateway via ``parentRefs``.
.. code-block:: yaml
:caption: values.yaml
+ executor: CeleryExecutor
+
apiServer:
httpRoute:
enabled: true
@@ -416,16 +418,31 @@ the chart only creates the ``HTTPRoute`` and attaches it
to the Gateway via ``pa
hostnames:
- airflow.example.com
-For fine-grained routing, supply ``apiServer.httpRoute.rules`` directly — the
entry mirrors the
-upstream ``HTTPRouteRule`` schema and overrides the default rule generated
from ``path`` + ``pathType``.
+ flower:
+ enabled: true
+ httpRoute:
+ enabled: true
+ parentRefs:
+ - name: main-gateway
+ namespace: gateway-system
+ sectionName: https
+ hostnames:
+ - flower.example.com
+
+Flower HTTPRoute resources are only created when Flower itself is created, so
``flower.enabled`` must be
+``true`` and the executor must include ``CeleryExecutor`` or
``CeleryKubernetesExecutor``.
+
+For fine-grained routing, supply ``apiServer.httpRoute.rules`` or
``flower.httpRoute.rules`` directly —
+the entry mirrors the upstream ``HTTPRouteRule`` schema and overrides the
default rule generated from
+the corresponding ``apiServer.httpRoute.path`` and
``apiServer.httpRoute.pathType``, or
+``flower.httpRoute.path`` and ``flower.httpRoute.pathType`` values.
.. note::
- ``HTTPRoute`` is an alternative to the API server ``Ingress``, so enable
only one of
- ``ingress.apiServer`` or ``apiServer.httpRoute`` — enabling both at the
same time fails template
- rendering. When ``apiServer.httpRoute.enabled`` is ``true``, the chart also
verifies (via Helm
- ``Capabilities``) that the Gateway API CRDs are installed and fails with a
clear message if they
- are not.
+ ``HTTPRoute`` is an alternative to ``Ingress`` for the same component, so
enable only one routing
+ mechanism for each component. Enabling both for the same component fails
template rendering.
+ When an ``HTTPRoute`` is enabled, the chart also verifies (via Helm
``Capabilities``) that the Gateway
+ API CRDs are installed and fails with a clear message if they are not.
LoadBalancer Service
^^^^^^^^^^^^^^^^^^^^
diff --git a/chart/templates/flower/flower-httproute.yaml
b/chart/templates/flower/flower-httproute.yaml
new file mode 100644
index 00000000000..2b0807b7ddb
--- /dev/null
+++ b/chart/templates/flower/flower-httproute.yaml
@@ -0,0 +1,70 @@
+{{/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/}}
+
+###########################
+## Airflow Flower HTTPRoute
+###########################
+{{- if and .Values.flower.enabled .Values.flower.httpRoute.enabled (or
(contains "CeleryExecutor" .Values.executor) (contains
"CeleryKubernetesExecutor" .Values.executor)) }}
+{{- if or .Values.ingress.flower.enabled .Values.ingress.enabled }}
+{{- fail "`flower.httpRoute.enabled` and
`ingress.flower.enabled`/`ingress.enabled` are both enabled. HTTPRoute (Gateway
API) is an alternative to the Flower Ingress; enable only one of them." }}
+{{- end }}
+{{- if not (.Capabilities.APIVersions.Has "gateway.networking.k8s.io/v1") }}
+{{- fail "`flower.httpRoute.enabled` is `true` but the Gateway API HTTPRoute
CRD (`gateway.networking.k8s.io/v1`) is not installed in the cluster. Install
the Gateway API CRDs
(https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api) before
enabling `flower.httpRoute`." }}
+{{- end }}
+{{- if not .Values.flower.httpRoute.parentRefs }}
+{{- fail "`flower.httpRoute.enabled` is `true` but
`flower.httpRoute.parentRefs` is empty. An HTTPRoute must reference at least
one parent Gateway; set `flower.httpRoute.parentRefs`." }}
+{{- end }}
+{{- $fullname := include "airflow.fullname" . }}
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: {{ $fullname }}-flower-httproute
+ labels:
+ tier: airflow
+ component: flower-httproute
+ release: {{ .Release.Name }}
+ chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
+ heritage: {{ .Release.Service }}
+ {{- if or .Values.labels .Values.flower.labels
.Values.flower.httpRoute.labels }}
+ {{- mustMerge .Values.flower.httpRoute.labels .Values.flower.labels
.Values.labels | toYaml | nindent 4 }}
+ {{- end }}
+ {{- with .Values.flower.httpRoute.annotations }}
+ annotations: {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ parentRefs: {{- toYaml .Values.flower.httpRoute.parentRefs | nindent 4 }}
+ {{- with .Values.flower.httpRoute.hostnames }}
+ hostnames:
+ {{- range . }}
+ - {{ tpl . $ | quote }}
+ {{- end }}
+ {{- end }}
+ rules:
+ {{- if .Values.flower.httpRoute.rules }}
+ {{- toYaml .Values.flower.httpRoute.rules | nindent 4 }}
+ {{- else }}
+ - matches:
+ - path:
+ type: {{ .Values.flower.httpRoute.pathType }}
+ value: {{ .Values.flower.httpRoute.path | quote }}
+ backendRefs:
+ - name: {{ $fullname }}-flower
+ port: {{ .Values.ports.flowerUI }}
+ {{- end }}
+{{- end }}
diff --git a/chart/templates/flower/flower-ingress.yaml
b/chart/templates/flower/flower-ingress.yaml
index 11ba85a0939..1a04d358f45 100644
--- a/chart/templates/flower/flower-ingress.yaml
+++ b/chart/templates/flower/flower-ingress.yaml
@@ -22,6 +22,9 @@
#################################
{{- if .Values.flower.enabled }}
{{- if and (or .Values.ingress.flower.enabled .Values.ingress.enabled) (or
(contains "CeleryExecutor" .Values.executor) (contains
"CeleryKubernetesExecutor" .Values.executor)) }}
+{{- if .Values.flower.httpRoute.enabled }}
+{{- fail "`ingress.flower.enabled`/`ingress.enabled` and
`flower.httpRoute.enabled` are both enabled. The Flower Ingress is an
alternative to HTTPRoute (Gateway API); enable only one of them." }}
+{{- end }}
{{- $fullname := include "airflow.fullname" . }}
apiVersion: networking.k8s.io/v1
kind: Ingress
diff --git a/chart/values.schema.json b/chart/values.schema.json
index 7ed990770d1..e57cacdb7f0 100644
--- a/chart/values.schema.json
+++ b/chart/values.schema.json
@@ -8742,6 +8742,102 @@
"type": "boolean",
"default": false
},
+ "httpRoute": {
+ "description": "Kubernetes Gateway API (HTTPRoute)
configuration for Flower.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "enabled": {
+ "description": "Enable Flower HTTPRoute resource.",
+ "type": "boolean",
+ "default": false
+ },
+ "labels": {
+ "description": "Extra labels for the Flower
HTTPRoute.",
+ "type": "object",
+ "default": {},
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "annotations": {
+ "description": "Annotations for the Flower
HTTPRoute.",
+ "type": "object",
+ "default": {},
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "parentRefs": {
+ "description": "List of parent Gateway references
this HTTPRoute attaches to. Required when enabled.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "default": null,
+ "minItems": 1,
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "Name of the referenced
Gateway.",
+ "type": "string"
+ },
+ "namespace": {
+ "description": "Namespace of the
referenced Gateway. Defaults to the local namespace.",
+ "type": "string"
+ },
+ "sectionName": {
+ "description": "Name of the Gateway
listener section this route attaches to.",
+ "type": "string"
+ },
+ "port": {
+ "description": "Port of the Gateway
listener this route attaches to.",
+ "type": "integer"
+ },
+ "kind": {
+ "description": "Kind of the referenced
resource. Defaults to `Gateway`.",
+ "type": "string"
+ },
+ "group": {
+ "description": "API group of the
referenced resource. Defaults to `gateway.networking.k8s.io`.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "name"
+ ]
+ }
+ },
+ "hostnames": {
+ "description": "Hostnames this HTTPRoute should
match (templated). Empty matches all.",
+ "type": "array",
+ "default": [],
+ "items": {
+ "type": "string"
+ }
+ },
+ "path": {
+ "description": "Default routing rule path (used
only when `flower.httpRoute.rules` is empty).",
+ "type": "string",
+ "default": "/"
+ },
+ "pathType": {
+ "description": "The pathType for the default path:
PathPrefix, Exact, or RegularExpression.",
+ "type": "string",
+ "default": "PathPrefix"
+ },
+ "rules": {
+ "description": "Custom routing rules. When set,
overrides the default rule generated from `path` + `pathType`. Each entry
follows the upstream Gateway API `HTTPRouteRule` schema.",
+ "type": "array",
+ "default": [],
+ "items": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ }
+ }
+ },
"livenessProbe": {
"description": "Liveness probe configuration.",
"type": "object",
diff --git a/chart/values.yaml b/chart/values.yaml
index aef2a7f0256..b9d4b708510 100644
--- a/chart/values.yaml
+++ b/chart/values.yaml
@@ -244,7 +244,8 @@ ingress:
# Configs for the Ingress of the flower Service
flower:
- # Enable web ingress resource
+ # Enable Flower ingress resource.
+ # Mutually exclusive with `flower.httpRoute.enabled` (Gateway API); enable
only one of them.
enabled: false
# Annotations for the flower Ingress
@@ -2278,8 +2279,6 @@ apiServer:
# Requires the Gateway API CRDs to be installed in the cluster
# (https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api).
# The HTTPRoute references an externally managed Gateway via `parentRefs`.
- # This is an alternative to the API server Ingress (`ingress.apiServer`);
- # the two are mutually exclusive and enabling both will fail rendering.
httpRoute:
# Enable API Server HTTPRoute resource.
# Mutually exclusive with `ingress.apiServer.enabled`; enable only one of
them.
@@ -2292,8 +2291,6 @@ apiServer:
annotations: {}
# List of parent Gateway references this HTTPRoute attaches to. Required
when enabled.
- # Defaults to `~` (null) so the schema's `minItems: 1` fails fast on an
explicitly
- # empty list (`[]`); a still-null value while enabled is caught by the
template guard.
# See
https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ParentReference
parentRefs: ~
# - name: my-gateway
@@ -3269,6 +3266,51 @@ flower:
# If True, and using CeleryExecutor/CeleryKubernetesExecutor, will deploy
flower app.
enabled: false
+ # Kubernetes Gateway API (HTTPRoute) configuration for Flower.
+ # Requires the Gateway API CRDs to be installed in the cluster
+ # (https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api).
+ # The HTTPRoute references an externally managed Gateway via `parentRefs`.
+ httpRoute:
+ # Enable Flower HTTPRoute resource.
+ # Mutually exclusive with `ingress.flower.enabled` and the legacy
`ingress.enabled`;
+ # enable only one routing mechanism.
+ enabled: false
+
+ # Extra labels for the Flower HTTPRoute
+ labels: {}
+
+ # Annotations for the Flower HTTPRoute
+ annotations: {}
+
+ # List of parent Gateway references this HTTPRoute attaches to. Required
when enabled.
+ # See
https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ParentReference
+ parentRefs: ~
+ # - name: my-gateway
+ # namespace: gateway-system
+ # sectionName: https
+
+ # Hostnames this HTTPRoute should match (templated). Empty matches all.
+ hostnames: []
+ # - "flower.example.com"
+
+ # Default routing rule path (used only when `flower.httpRoute.rules` is
empty)
+ path: "/"
+
+ # The pathType for the default path. PathPrefix | Exact | RegularExpression
+ pathType: PathPrefix
+
+ # Custom routing rules. When set, overrides the default rule generated
from `path` + `pathType`.
+ # Use this for non-standard Flower service port layouts.
+ # See
https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule
+ rules: []
+ # - matches:
+ # - path:
+ # type: PathPrefix
+ # value: /
+ # backendRefs:
+ # - name: my-flower-service
+ # port: 5555
+
livenessProbe:
initialDelaySeconds: 10
timeoutSeconds: 5
diff --git a/helm-tests/tests/helm_tests/airflow_aux/test_basic_helm_chart.py
b/helm-tests/tests/helm_tests/airflow_aux/test_basic_helm_chart.py
index bf10319c209..22120bc2805 100644
--- a/helm-tests/tests/helm_tests/airflow_aux/test_basic_helm_chart.py
+++ b/helm-tests/tests/helm_tests/airflow_aux/test_basic_helm_chart.py
@@ -371,7 +371,50 @@ class TestBaseChartTest:
["default", "CeleryExecutor,KubernetesExecutor"],
],
)
- def test_labels_are_valid(self, airflow_version, executor):
+ @pytest.mark.parametrize(
+ ("flower_routing_values", "flower_routing_resource", "api_versions"),
+ [
+ pytest.param(
+ {
+ "ingress": {
+ "enabled": True,
+ "flower": {"enabled": True},
+ "apiServer": {"enabled": True},
+ },
+ "flower": {"enabled": True},
+ },
+ ("flower-ingress", "Ingress", "flower-ingress"),
+ [],
+ id="flower-ingress",
+ ),
+ pytest.param(
+ {
+ "ingress": {
+ "web": {"enabled": True},
+ "apiServer": {"enabled": True},
+ },
+ "flower": {
+ "enabled": True,
+ "httpRoute": {
+ "enabled": True,
+ "parentRefs": [{"name": "main-gateway"}],
+ },
+ },
+ },
+ ("flower-httproute", "HTTPRoute", "flower-httproute"),
+ ["gateway.networking.k8s.io/v1"],
+ id="flower-httproute",
+ ),
+ ],
+ )
+ def test_labels_are_valid(
+ self,
+ airflow_version,
+ executor,
+ flower_routing_values,
+ flower_routing_resource,
+ api_versions,
+ ):
"""Test labels are correctly applied on all objects created by this
chart."""
release_name = "test-basic"
@@ -391,11 +434,9 @@ class TestBaseChartTest:
},
"pgbouncer": {"enabled": True},
"redis": {"enabled": True},
- "ingress": {"enabled": True},
"networkPolicies": {"enabled": True},
"cleanup": {"enabled": True},
"databaseCleanup": {"enabled": True},
- "flower": {"enabled": True},
"dagProcessor": {"enabled": True},
"logs": {"persistence": {"enabled": True}},
"dags": {"persistence": {"enabled": True}},
@@ -404,16 +445,19 @@ class TestBaseChartTest:
{"name": "class1", "value": 10000},
],
}
+ values.update(flower_routing_values)
if airflow_version != "default":
values["airflowVersion"] = airflow_version
- k8s_objects = render_chart(name=release_name, values=values)
+ k8s_objects = render_chart(name=release_name, values=values,
api_versions=api_versions)
kind_k8s_obj_labels_tuples = {
(k8s_object["metadata"]["name"], k8s_object["kind"]):
k8s_object["metadata"]["labels"]
for k8s_object in k8s_objects
}
+ flower_routing_name, flower_routing_kind, flower_routing_component =
flower_routing_resource
+
kind_names_tuples = [
(f"{release_name}-airflow-cleanup", "ServiceAccount",
"airflow-cleanup-pods"),
(f"{release_name}-airflow-database-cleanup", "ServiceAccount",
"database-cleanup"),
@@ -442,7 +486,11 @@ class TestBaseChartTest:
(f"{release_name}-flower", "Deployment", "flower"),
(f"{release_name}-flower", "Service", "flower"),
(f"{release_name}-flower-policy", "NetworkPolicy",
"airflow-flower-policy"),
- (f"{release_name}-flower-ingress", "Ingress", "flower-ingress"),
+ (
+ f"{release_name}-{flower_routing_name}",
+ flower_routing_kind,
+ flower_routing_component,
+ ),
(f"{release_name}-pgbouncer", "Deployment", "pgbouncer"),
(f"{release_name}-pgbouncer", "Service", "pgbouncer"),
(f"{release_name}-pgbouncer-config", "Secret", "pgbouncer"),
diff --git a/helm-tests/tests/helm_tests/other/test_httproute_flower.py
b/helm-tests/tests/helm_tests/other/test_httproute_flower.py
new file mode 100644
index 00000000000..80ecbd7498f
--- /dev/null
+++ b/helm-tests/tests/helm_tests/other/test_httproute_flower.py
@@ -0,0 +1,424 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import jmespath
+import pytest
+from chart_utils.helm_template_generator import HelmFailedError, render_chart
+
+SHOW_ONLY = ["templates/flower/flower-httproute.yaml"]
+FLOWER_INGRESS_SHOW_ONLY = ["templates/flower/flower-ingress.yaml"]
+
+GATEWAY_API_VERSIONS = ["gateway.networking.k8s.io/v1"]
+MINIMAL_PARENT_REFS = [{"name": "main-gateway"}]
+
+
+class TestHTTPRouteFlower:
+ """Tests HTTPRoute Flower (Kubernetes Gateway API)."""
+
+ def test_should_pass_validation_with_minimal_config(self):
+ docs = render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {
+ "enabled": True,
+ "httpRoute": {"enabled": True, "parentRefs":
MINIMAL_PARENT_REFS},
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert len(docs) == 1
+ assert jmespath.search("kind", docs[0]) == "HTTPRoute"
+ assert jmespath.search("metadata.name", docs[0]) ==
"release-name-flower-httproute"
+
+ def test_should_set_api_version_and_kind(self):
+ docs = render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {
+ "enabled": True,
+ "httpRoute": {"enabled": True, "parentRefs":
MINIMAL_PARENT_REFS},
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert jmespath.search("apiVersion", docs[0]) ==
"gateway.networking.k8s.io/v1"
+ assert jmespath.search("kind", docs[0]) == "HTTPRoute"
+
+ def test_should_allow_more_than_one_annotation(self):
+ docs = render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {
+ "enabled": True,
+ "httpRoute": {
+ "enabled": True,
+ "parentRefs": MINIMAL_PARENT_REFS,
+ "annotations": {"aa": "bb", "cc": "dd"},
+ },
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert jmespath.search("metadata.annotations", docs[0]) == {"aa":
"bb", "cc": "dd"}
+
+ def test_should_add_extra_labels(self):
+ docs = render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {
+ "enabled": True,
+ "httpRoute": {
+ "enabled": True,
+ "parentRefs": MINIMAL_PARENT_REFS,
+ "labels": {"custom": "value"},
+ },
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert jmespath.search('metadata.labels."custom"', docs[0]) == "value"
+
+ def test_should_pass_parent_refs_through(self):
+ docs = render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {
+ "enabled": True,
+ "httpRoute": {
+ "enabled": True,
+ "parentRefs": [
+ {"name": "main-gateway", "namespace":
"gateway-system"},
+ {"name": "main-gateway", "namespace":
"gateway-system", "sectionName": "https"},
+ ],
+ },
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert jmespath.search("spec.parentRefs", docs[0]) == [
+ {"name": "main-gateway", "namespace": "gateway-system"},
+ {"name": "main-gateway", "namespace": "gateway-system",
"sectionName": "https"},
+ ]
+
+ def test_should_set_hostnames(self):
+ docs = render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {
+ "enabled": True,
+ "httpRoute": {
+ "enabled": True,
+ "parentRefs": MINIMAL_PARENT_REFS,
+ "hostnames": ["flower.example.com",
"flower2.example.com"],
+ },
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert jmespath.search("spec.hostnames", docs[0]) == [
+ "flower.example.com",
+ "flower2.example.com",
+ ]
+
+ def test_hostnames_should_be_templated(self):
+ docs = render_chart(
+ name="airflow",
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {
+ "enabled": True,
+ "httpRoute": {
+ "enabled": True,
+ "parentRefs": MINIMAL_PARENT_REFS,
+ "hostnames": ["{{ .Release.Name }}.example.com"],
+ },
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert jmespath.search("spec.hostnames", docs[0]) ==
["airflow.example.com"]
+
+ def test_should_default_to_path_prefix_and_flower_backend(self):
+ docs = render_chart(
+ name="my-release",
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {
+ "enabled": True,
+ "httpRoute": {"enabled": True, "parentRefs":
MINIMAL_PARENT_REFS},
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert jmespath.search("spec.rules", docs[0]) == [
+ {
+ "matches": [{"path": {"type": "PathPrefix", "value": "/"}}],
+ "backendRefs": [{"name": "my-release-flower", "port": 5555}],
+ },
+ ]
+
+ def test_custom_path_and_path_type_should_apply(self):
+ docs = render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {
+ "enabled": True,
+ "httpRoute": {
+ "enabled": True,
+ "parentRefs": MINIMAL_PARENT_REFS,
+ "path": "/flower",
+ "pathType": "Exact",
+ },
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert jmespath.search("spec.rules[0].matches[0].path.type", docs[0])
== "Exact"
+ assert jmespath.search("spec.rules[0].matches[0].path.value", docs[0])
== "/flower"
+
+ def test_custom_rules_override_default_rule(self):
+ docs = render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {
+ "enabled": True,
+ "httpRoute": {
+ "enabled": True,
+ "parentRefs": MINIMAL_PARENT_REFS,
+ "rules": [
+ {
+ "matches": [{"path": {"type": "PathPrefix",
"value": "/custom-flower"}}],
+ "backendRefs": [{"name": "external-flower",
"port": 8443}],
+ },
+ ],
+ },
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert jmespath.search("spec.rules", docs[0]) == [
+ {
+ "matches": [{"path": {"type": "PathPrefix", "value":
"/custom-flower"}}],
+ "backendRefs": [{"name": "external-flower", "port": 8443}],
+ },
+ ]
+
+ def test_should_use_flower_ui_port_for_default_backend(self):
+ docs = render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "ports": {"flowerUI": 9000},
+ "flower": {
+ "enabled": True,
+ "httpRoute": {"enabled": True, "parentRefs":
MINIMAL_PARENT_REFS},
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert jmespath.search("spec.rules[0].backendRefs[0].port", docs[0])
== 9000
+
+ def test_httproute_not_created_when_unset(self):
+ docs = render_chart(
+ values={"executor": "CeleryExecutor", "flower": {"enabled": True,
"httpRoute": {}}},
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert docs == []
+
+ def test_httproute_not_created_when_disabled(self):
+ docs = render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {"enabled": True, "httpRoute": {"enabled": False}},
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert docs == []
+
+ @pytest.mark.parametrize("executor", ["CeleryExecutor",
"CeleryKubernetesExecutor"])
+ def test_httproute_created_when_enabled(self, executor):
+ docs = render_chart(
+ values={
+ "executor": executor,
+ "flower": {
+ "enabled": True,
+ "httpRoute": {"enabled": True, "parentRefs":
MINIMAL_PARENT_REFS},
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert len(docs) == 1
+
+ def test_should_not_render_when_flower_disabled(self):
+ docs = render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {
+ "enabled": False,
+ "httpRoute": {"enabled": True, "parentRefs":
MINIMAL_PARENT_REFS},
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert docs == []
+
+ @pytest.mark.parametrize(
+ "executor", ["LocalExecutor", "KubernetesExecutor",
"LocalExecutor,KubernetesExecutor"]
+ )
+ def test_should_not_render_without_celery_executor(self, executor):
+ docs = render_chart(
+ values={
+ "executor": executor,
+ "flower": {
+ "enabled": True,
+ "httpRoute": {"enabled": True, "parentRefs":
MINIMAL_PARENT_REFS},
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert docs == []
+
+ def test_should_fail_when_gateway_api_crd_missing(self):
+ with pytest.raises(HelmFailedError) as exc_info:
+ render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {
+ "enabled": True,
+ "httpRoute": {"enabled": True, "parentRefs":
MINIMAL_PARENT_REFS},
+ },
+ },
+ show_only=SHOW_ONLY,
+ )
+ assert "Gateway API HTTPRoute CRD" in exc_info.value.stderr.decode()
+
+ def test_should_fail_when_parent_refs_empty(self):
+ with pytest.raises(HelmFailedError) as exc_info:
+ render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {"enabled": True, "httpRoute": {"enabled":
True}},
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert "parentRefs" in exc_info.value.stderr.decode()
+
+ def test_should_fail_schema_when_parent_refs_explicitly_empty(self):
+ with pytest.raises(HelmFailedError) as exc_info:
+ render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "flower": {"enabled": True, "httpRoute": {"enabled": True,
"parentRefs": []}},
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert "parentRefs" in exc_info.value.stderr.decode()
+
+ @pytest.mark.parametrize(
+ "ingress_values",
+ [{"flower": {"enabled": True}}, {"enabled": True}],
+ ids=["flower-ingress", "legacy-ingress"],
+ )
+ def test_should_fail_when_ingress_and_httproute_both_enabled(self,
ingress_values):
+ with pytest.raises(HelmFailedError) as exc_info:
+ render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "ingress": ingress_values,
+ "flower": {"enabled": True, "httpRoute": {"enabled":
True}},
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert "enable only one of them" in exc_info.value.stderr.decode()
+
+ @pytest.mark.parametrize(
+ "ingress_values",
+ [{"flower": {"enabled": True}}, {"enabled": True}],
+ ids=["flower-ingress", "legacy-ingress"],
+ )
+ def
test_should_fail_when_ingress_template_sees_ingress_and_httproute_both_enabled(self,
ingress_values):
+ with pytest.raises(HelmFailedError) as exc_info:
+ render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "ingress": ingress_values,
+ "flower": {"enabled": True, "httpRoute": {"enabled":
True}},
+ },
+ show_only=FLOWER_INGRESS_SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert "enable only one of them" in exc_info.value.stderr.decode()
+
+ def test_backend_service_name_with_fullname_override(self):
+ docs = render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "fullnameOverride": "airflow-fullname-override",
+ "useStandardNaming": True,
+ "flower": {
+ "enabled": True,
+ "httpRoute": {"enabled": True, "parentRefs":
MINIMAL_PARENT_REFS},
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert (
+ jmespath.search("spec.rules[0].backendRefs[0].name", docs[0])
+ == "airflow-fullname-override-flower"
+ )
+
+ def test_should_add_component_specific_labels(self):
+ docs = render_chart(
+ values={
+ "executor": "CeleryExecutor",
+ "labels": {"label1": "value1", "label2": "value2"},
+ "flower": {
+ "enabled": True,
+ "labels": {"test_label": "test_label_value"},
+ "httpRoute": {
+ "enabled": True,
+ "parentRefs": MINIMAL_PARENT_REFS,
+ "labels": {"route_label": "route_value"},
+ },
+ },
+ },
+ show_only=SHOW_ONLY,
+ api_versions=GATEWAY_API_VERSIONS,
+ )
+ assert jmespath.search('metadata.labels."label1"', docs[0]) == "value1"
+ assert jmespath.search('metadata.labels."test_label"', docs[0]) ==
"test_label_value"
+ assert jmespath.search('metadata.labels."route_label"', docs[0]) ==
"route_value"