Back to Repositories

Validating Webserver Ingress Configuration in Apache Airflow

This test suite validates the Airflow webserver ingress configuration in Helm charts, ensuring proper handling of ingress settings, TLS configurations, and host routing rules. The tests cover various ingress deployment scenarios and configuration options.

Test Coverage Overview

The test suite provides comprehensive coverage of ingress configurations for Airflow’s webserver component.

Key areas tested include:
  • Basic ingress enablement validation
  • Multiple Kubernetes API version support (v1 and v1beta1)
  • Annotation handling
  • Host configuration and TLS settings
  • Service backend naming

Implementation Analysis

The testing approach utilizes pytest framework with Helm chart rendering and JMESPath queries for validation.

Key implementation patterns include:
  • Parametrized testing for feature flags
  • Template variable interpolation verification
  • Configuration override validation
  • Backwards compatibility testing

Technical Details

Testing tools and components:
  • pytest as the testing framework
  • JMESPath for JSON/YAML parsing
  • Helm template generator for chart rendering
  • Custom chart validation utilities
  • Kubernetes version compatibility testing

Best Practices Demonstrated

The test suite exhibits several testing best practices for Helm charts.

Notable practices include:
  • Isolated test cases with clear purposes
  • Comprehensive validation of configuration options
  • Edge case handling for different Kubernetes versions
  • Structured assertion patterns
  • Clear test naming conventions

apache/airflow

helm_tests/webserver/test_ingress_web.py

            
# 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 tests.charts.helm_template_generator import render_chart


class TestIngressWeb:
    """Tests ingress web."""

    def test_should_pass_validation_with_just_ingress_enabled_v1(self):
        render_chart(
            values={"ingress": {"web": {"enabled": True}}},
            show_only=["templates/webserver/webserver-ingress.yaml"],
        )  # checks that no validation exception is raised

    def test_should_pass_validation_with_just_ingress_enabled_v1beta1(self):
        render_chart(
            values={"ingress": {"web": {"enabled": True}}},
            show_only=["templates/webserver/webserver-ingress.yaml"],
            kubernetes_version="1.16.0",
        )  # checks that no validation exception is raised

    def test_should_allow_more_than_one_annotation(self):
        docs = render_chart(
            values={"ingress": {"web": {"enabled": True, "annotations": {"aa": "bb", "cc": "dd"}}}},
            show_only=["templates/webserver/webserver-ingress.yaml"],
        )
        assert jmespath.search("metadata.annotations", docs[0]) == {"aa": "bb", "cc": "dd"}

    def test_should_set_ingress_class_name(self):
        docs = render_chart(
            values={"ingress": {"web": {"enabled": True, "ingressClassName": "foo"}}},
            show_only=["templates/webserver/webserver-ingress.yaml"],
        )
        assert jmespath.search("spec.ingressClassName", docs[0]) == "foo"

    def test_should_ingress_hosts_objs_have_priority_over_host(self):
        docs = render_chart(
            values={
                "ingress": {
                    "web": {
                        "enabled": True,
                        "tls": {"enabled": True, "secretName": "oldsecret"},
                        "hosts": [
                            {"name": "*.a-host", "tls": {"enabled": True, "secretName": "newsecret1"}},
                            {"name": "b-host", "tls": {"enabled": True, "secretName": "newsecret2"}},
                            {"name": "c-host", "tls": {"enabled": True, "secretName": "newsecret1"}},
                            {"name": "d-host", "tls": {"enabled": False, "secretName": ""}},
                            {"name": "e-host"},
                        ],
                        "host": "old-host",
                    },
                }
            },
            show_only=["templates/webserver/webserver-ingress.yaml"],
        )
        assert jmespath.search("spec.rules[*].host", docs[0]) == [
            "*.a-host",
            "b-host",
            "c-host",
            "d-host",
            "e-host",
        ]
        assert jmespath.search("spec.tls[*]", docs[0]) == [
            {"hosts": ["*.a-host"], "secretName": "newsecret1"},
            {"hosts": ["b-host"], "secretName": "newsecret2"},
            {"hosts": ["c-host"], "secretName": "newsecret1"},
        ]

    def test_should_ingress_hosts_strs_have_priority_over_host(self):
        docs = render_chart(
            values={
                "ingress": {
                    "web": {
                        "enabled": True,
                        "tls": {"enabled": True, "secretName": "secret"},
                        "hosts": ["*.a-host", "b-host", "c-host", "d-host"],
                        "host": "old-host",
                    },
                }
            },
            show_only=["templates/webserver/webserver-ingress.yaml"],
        )
        assert jmespath.search("spec.rules[*].host", docs[0]) == ["*.a-host", "b-host", "c-host", "d-host"]
        assert jmespath.search("spec.tls[*]", docs[0]) == [
            {"hosts": ["*.a-host", "b-host", "c-host", "d-host"], "secretName": "secret"}
        ]

    def test_should_ingress_deprecated_host_and_top_level_tls_still_work(self):
        docs = render_chart(
            values={
                "ingress": {
                    "web": {
                        "enabled": True,
                        "tls": {"enabled": True, "secretName": "supersecret"},
                        "host": "old-host",
                    },
                }
            },
            show_only=["templates/webserver/webserver-ingress.yaml"],
        )
        assert (
            ["old-host"]
            == jmespath.search("spec.rules[*].host", docs[0])
            == jmespath.search("spec.tls[0].hosts", docs[0])
        )

    def test_should_ingress_host_entry_not_exist(self):
        docs = render_chart(
            values={
                "ingress": {
                    "web": {
                        "enabled": True,
                    }
                }
            },
            show_only=["templates/webserver/webserver-ingress.yaml"],
        )
        assert not jmespath.search("spec.rules[*].host", docs[0])

    @pytest.mark.parametrize(
        "global_value, web_value, expected",
        [
            (None, None, False),
            (None, False, False),
            (None, True, True),
            (False, None, False),
            (True, None, True),
            (False, True, True),  # We will deploy it if _either_ are true
            (True, False, True),
        ],
    )
    def test_ingress_created(self, global_value, web_value, expected):
        values = {"ingress": {}}
        if global_value is not None:
            values["ingress"]["enabled"] = global_value
        if web_value is not None:
            values["ingress"]["web"] = {"enabled": web_value}
        if values["ingress"] == {}:
            del values["ingress"]
        docs = render_chart(values=values, show_only=["templates/webserver/webserver-ingress.yaml"])
        assert expected == (len(docs) == 1)

    def test_should_add_component_specific_labels(self):
        docs = render_chart(
            values={
                "ingress": {"enabled": True},
                "webserver": {
                    "labels": {"test_label": "test_label_value"},
                },
            },
            show_only=["templates/webserver/webserver-ingress.yaml"],
        )
        assert "test_label" in jmespath.search("metadata.labels", docs[0])
        assert jmespath.search("metadata.labels", docs[0])["test_label"] == "test_label_value"

    def test_can_ingress_hosts_be_templated(self):
        docs = render_chart(
            values={
                "testValues": {
                    "scalar": "aa",
                    "list": ["bb", "cc"],
                    "dict": {
                        "key": "dd",
                    },
                },
                "ingress": {
                    "web": {
                        "enabled": True,
                        "hosts": [
                            {"name": "*.{{ .Release.Namespace }}.example.com"},
                            {"name": "{{ .Values.testValues.scalar }}.example.com"},
                            {"name": "{{ index .Values.testValues.list 1 }}.example.com"},
                            {"name": "{{ .Values.testValues.dict.key }}.example.com"},
                        ],
                    },
                },
            },
            show_only=["templates/webserver/webserver-ingress.yaml"],
            namespace="airflow",
        )

        assert jmespath.search("spec.rules[*].host", docs[0]) == [
            "*.airflow.example.com",
            "aa.example.com",
            "cc.example.com",
            "dd.example.com",
        ]

    def test_backend_service_name(self):
        docs = render_chart(
            values={"ingress": {"web": {"enabled": True}}},
            show_only=["templates/webserver/webserver-ingress.yaml"],
        )

        assert (
            jmespath.search("spec.rules[0].http.paths[0].backend.service.name", docs[0])
            == "release-name-webserver"
        )

    def test_backend_service_name_with_fullname_override(self):
        docs = render_chart(
            values={
                "fullnameOverride": "test-basic",
                "useStandardNaming": True,
                "ingress": {"web": {"enabled": True}},
            },
            show_only=["templates/webserver/webserver-ingress.yaml"],
        )

        assert (
            jmespath.search("spec.rules[0].http.paths[0].backend.service.name", docs[0])
            == "test-basic-webserver"
        )