diff --git a/dev/breeze/src/airflow_breeze/global_constants.py b/dev/breeze/src/airflow_breeze/global_constants.py index 0de45d8e1010f..fbf2abc5c11c4 100644 --- a/dev/breeze/src/airflow_breeze/global_constants.py +++ b/dev/breeze/src/airflow_breeze/global_constants.py @@ -122,6 +122,7 @@ PIP_VERSION = "24.0" DEFAULT_UV_HTTP_TIMEOUT = 300 +DEFAULT_WSL2_HTTP_TIMEOUT = 900 # packages that providers docs REGULAR_DOC_PACKAGES = [ diff --git a/dev/breeze/src/airflow_breeze/params/build_ci_params.py b/dev/breeze/src/airflow_breeze/params/build_ci_params.py index 2c02e81f253c7..05179df07b8c4 100644 --- a/dev/breeze/src/airflow_breeze/params/build_ci_params.py +++ b/dev/breeze/src/airflow_breeze/params/build_ci_params.py @@ -67,7 +67,10 @@ def prepare_arguments_for_docker_build_command(self) -> list[str]: self._req_arg("AIRFLOW_IMAGE_REPOSITORY", self.airflow_image_repository) self._req_arg("AIRFLOW_PRE_CACHED_PIP_PACKAGES", self.airflow_pre_cached_pip_packages) self._req_arg("AIRFLOW_USE_UV", self.use_uv) - self._opt_arg("UV_HTTP_TIMEOUT", self.uv_http_timeout) + if self.use_uv: + from airflow_breeze.utils.uv_utils import get_uv_timeout + + self._opt_arg("UV_HTTP_TIMEOUT", get_uv_timeout(self)) self._req_arg("AIRFLOW_VERSION", self.airflow_version) self._req_arg("BUILD_ID", self.build_id) self._req_arg("CONSTRAINTS_GITHUB_REPOSITORY", self.constraints_github_repository) diff --git a/dev/breeze/src/airflow_breeze/params/build_prod_params.py b/dev/breeze/src/airflow_breeze/params/build_prod_params.py index c752b15bff36b..d3a3dbfdbc35a 100644 --- a/dev/breeze/src/airflow_breeze/params/build_prod_params.py +++ b/dev/breeze/src/airflow_breeze/params/build_prod_params.py @@ -208,7 +208,10 @@ def prepare_arguments_for_docker_build_command(self) -> list[str]: self._req_arg("AIRFLOW_IMAGE_REPOSITORY", self.airflow_image_repository) self._req_arg("AIRFLOW_PRE_CACHED_PIP_PACKAGES", self.airflow_pre_cached_pip_packages) self._opt_arg("AIRFLOW_USE_UV", self.use_uv) - self._opt_arg("UV_HTTP_TIMEOUT", self.uv_http_timeout) + if self.use_uv: + from airflow_breeze.utils.uv_utils import get_uv_timeout + + self._req_arg("UV_HTTP_TIMEOUT", get_uv_timeout(self)) self._req_arg("AIRFLOW_VERSION", self.airflow_version) self._req_arg("BUILD_ID", self.build_id) self._req_arg("CONSTRAINTS_GITHUB_REPOSITORY", self.constraints_github_repository) diff --git a/dev/breeze/src/airflow_breeze/utils/platforms.py b/dev/breeze/src/airflow_breeze/utils/platforms.py index c79e486525ecd..f32ccd20a6426 100644 --- a/dev/breeze/src/airflow_breeze/utils/platforms.py +++ b/dev/breeze/src/airflow_breeze/utils/platforms.py @@ -16,6 +16,10 @@ # under the License. from __future__ import annotations +import platform +import sys +from pathlib import Path + def get_real_platform(single_platform: str) -> str: """ @@ -23,3 +27,62 @@ def get_real_platform(single_platform: str) -> str: are using: amd64 and arm64. """ return single_platform.replace("x86_64", "amd64").replace("aarch64", "arm64").replace("/", "-") + + +def _exists_no_permission_error(p: str) -> bool: + try: + return Path(p).exists() + except PermissionError: + return False + + +def message_on_wsl1_detected(release_name: str | None, kernel_version: tuple[int, ...] | None): + from airflow_breeze.utils.console import get_console + + get_console().print("[error]You are running WSL1 - Breeze requires WSL2! Quitting.\n") + get_console().print("[warning]It can also be that our detection mechanism is wrong:[/]\n\n") + if release_name: + get_console().print(f"[info]We based our WSL1 detection on the release name: `{release_name}`\n") + elif kernel_version: + get_console().print(f"[info]We based our WSL1 detection on the kernel version: `{kernel_version}`\n") + get_console().print( + "[info]If you are running WSL2, please report this issue to the maintainers\n" + "of Airflow, so we can improve the detection mechanism.\n" + "You can also try to run the command with `--uv-http-timeout 900` or " + "`--no-use-uv` flag to skip the WSL1 check.\n" + ) + + +def is_wsl2() -> bool: + """ + Check if the current platform is WSL2. This method will exit with error printing appropriate + message if WSL1 is detected as WSL1 is not supported. + + :return: True if the current platform is WSL2, False otherwise (unless it's WSL1 then it exits). + """ + if not sys.platform.startswith("linux"): + return False + release_name = platform.uname().release + has_wsl_interop = _exists_no_permission_error("/proc/sys/fs/binfmt_misc/WSLInterop") + microsoft_in_release = "microsoft" in release_name.lower() + wsl_conf = _exists_no_permission_error("/etc/wsl.conf") + if not has_wsl_interop and not microsoft_in_release and not wsl_conf: + return False + if microsoft_in_release: + # Release name WSL1 detection + if "Microsoft" in release_name: + message_on_wsl1_detected(release_name=release_name, kernel_version=None) + sys.exit(1) + return True + + # Kernel WSL1 detection + kernel_version: tuple[int, ...] = (0, 0) + if len(parts := release_name.split(".", 2)[:2]) == 2: + try: + kernel_version = tuple(map(int, parts)) + except (TypeError, ValueError): + pass + if kernel_version < (4, 19): + message_on_wsl1_detected(release_name=None, kernel_version=kernel_version) + sys.exit(1) + return True diff --git a/dev/breeze/src/airflow_breeze/utils/uv_utils.py b/dev/breeze/src/airflow_breeze/utils/uv_utils.py new file mode 100644 index 0000000000000..796c086e3bf3c --- /dev/null +++ b/dev/breeze/src/airflow_breeze/utils/uv_utils.py @@ -0,0 +1,43 @@ +# 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 + +from typing import TYPE_CHECKING + +from airflow_breeze.global_constants import DEFAULT_UV_HTTP_TIMEOUT, DEFAULT_WSL2_HTTP_TIMEOUT + +if TYPE_CHECKING: + from airflow_breeze.params.common_build_params import CommonBuildParams +from airflow_breeze.utils.platforms import is_wsl2 + + +def get_uv_timeout(build_params: CommonBuildParams) -> int: + """ + Get the timeout for the uvicorn server. We do not want to change the default value to not slow down + the --help and command line in general and also it might be useful to give escape hatch in case our + WSL1 detection is wrong (it will fail default --use-uv build, but you will be able to skip the check by + manually specifying --uv-http-timeout or --no-use-uv). So we only check for wsl2 when default value is + used and when uv is enabled. + """ + + if build_params.uv_http_timeout != DEFAULT_UV_HTTP_TIMEOUT: + # a bit of hack: if you specify 300 in command line it will also be overridden in case of WSL2 + # but this is a corner case + return build_params.uv_http_timeout + if is_wsl2(): + return DEFAULT_WSL2_HTTP_TIMEOUT + return build_params.uv_http_timeout