Repository navigation
Conversation
|
/gcbrun |
There was a problem hiding this comment.
Code Review
This pull request introduces two new initialization scripts, conda-install-cached.sh and pip-install-cached.sh, to support caching of Conda and PyPI package environments in GCS, along with corresponding documentation in README.md. The review feedback identifies several critical and high-severity issues, including the risk of caching system-wide directories (like /usr) when target environment paths are inferred, potential script crashes under set -e during date parsing or cache unpacking failures, and pathname expansion (globbing) bugs when passing unquoted package lists. The reviewer suggests adding safety checks to prevent system directory caching, wrapping unpacking commands in conditional blocks for graceful fallbacks, using Bash arrays to handle package lists safely, and removing unused helper functions.
…peed up Python package installation by caching environments in GCS.
- **GCS Caching:** Packages are installed once, and the resulting environment is tarred and uploaded to GCS for reuse.
- **Deterministic Cache Keys:** Cache keys are generated based on requested packages, OS, Dataproc version, and architecture.
- **Concurrency Lock:** Uses a `.building` sentinel file in GCS to prevent multiple nodes from simultaneously building the same environment.
- **Robust Fallback:** If GCS is unreachable or errors occur during caching, the scripts fall back to standard non-cached installation.
- **Customization Options:**
- `CACHE_BUCKET`: Specify custom GCS bucket (defaults to `dataproc-temp-bucket`).
- `CACHE_KEY_OVERRIDE`: Manually set cache key.
- `CACHE_TIMEOUT`: Set cache expiration (defaults to 10 years/infinite).
- `TARGET_ENV_PATH`: Specify target environment directory.
- Updated `python/README.md` with usage instructions and examples for both cached installers.
c5dc9c0 to
c596cda
Compare
- Refuse to cache system-wide directories (/usr, /usr/local, /) to prevent system corruption and excessive cache sizes. - Handle date parsing failures gracefully during cache age verification. - Wrap tar unpacking in conditional blocks to allow graceful fallback to rebuilding if cache is corrupted. - Use Bash arrays to pass package lists, preventing pathname expansion (globbing) bugs with wildcards. - Remove unused version comparison helper functions.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces two new initialization scripts, conda-install-cached.sh and pip-install-cached.sh, along with documentation, to support caching Conda and PyPI environments in GCS for faster Dataproc cluster creation. Feedback on these changes highlights a race condition in concurrent lock acquisition for both scripts, which can be resolved by using gsutil with the x-goog-if-generation-match:0 header. Additionally, pip-install-cached.sh should be updated to install python3-pip instead of the deprecated python-pip, and its fallback behavior when TARGET_ENV_PATH is omitted needs to be addressed, as it currently fails on system-wide environments.
- Introduce GCS helper functions to abstract the underlying tool (gcloud storage or gsutil). - Prefer `gcloud storage` for better performance if available. - Fall back to `gsutil` on older environments transparently. - Maintain atomic uploads and generation matching across both tools. - Also includes previous fixes for safety warnings, error handling, and atomic locks.
- Prevent caching system-wide directories like `/usr` by switching to a temporary isolated virtual environment (`/tmp/pip-isolated-env`) when system Python is detected. - Add `ensure_venv` helper to install `python3-venv` or `python3-virtualenv` if missing. - Add `unpack_and_merge` helper to handle unpacking and merging isolated environments back to the original system path. - Merge isolated environment contents back to the system path after installation or unpacking to maintain backward compatibility.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces two new initialization action scripts, conda-install-cached.sh and pip-install-cached.sh, along with updated documentation in README.md to support cached installation of Conda and PyPI packages on Dataproc clusters using Google Cloud Storage. The code review identified several critical and high-severity issues: first, copying the entire isolated virtual environment back to /usr in the pip script can corrupt the system Python interpreter via circular symlinks, which should be resolved by only copying the site-packages directory; second, the lock-waiting mechanism in both scripts can lead to infinite loops or indefinite hangs if a build sentinel file becomes stale; and finally, the pip script is missing the remove_old_backports function required to prevent package update failures on older Debian-based Dataproc images.
…ility in cached installers - **Fix System Python Corruption (pip):** Replaced dangerous `cp -a` of entire isolated venv to `/usr` with `merge_isolated_env`, which only copies `site-packages`. This avoids creating circular symlinks and corrupting system binaries. - **Fix Stale Sentinel Hangs (pip/conda):** Added checks to detect and remove stale `.building` sentinel files (older than 20 minutes) in GCS. Capped wait timeouts to 20 minutes and ensured stale sentinels are removed on timeout. - **Fix Older Image Compatibility (pip):** Re-introduced `remove_old_backports` to fix `apt update` failures on Debian <= 2.1 images. - **Improve Persistence (pip):** Moved isolated venv path from `/tmp` to `/opt/pip-isolated-env` to prevent loss of python files on node reboot. - **Clarify Warnings:** Improved phrasing of warnings related to failed unpacking of newly available caches.
- Use a Bash array (`chan_arr`) to safely iterate over space-separated `CONDA_CHANNELS`, avoiding potential globbing bugs if a channel name or specifier contains wildcards.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces two new initialization scripts, conda-install-cached.sh and pip-install-cached.sh, along with updated documentation in README.md to support installing Conda and PyPI packages with GCS-based environment caching. Feedback on pip-install-cached.sh highlights three key improvements: adding timeouts to curl to prevent hangs in air-gapped environments, copying executable binaries from the isolated virtual environment's bin/ directory to /usr/local/bin/ during merging, and verifying that DATAPROC_IMAGE_VERSION is non-empty before running bc to avoid script crashes.
- **Add Curl Timeouts:** Added `--connect-timeout 5 --max-time 10` to `curl` commands in `remove_old_backports` to prevent hanging in air-gapped environments. - **Merge Binaries:** Updated `merge_isolated_env` to copy executable binaries from isolated venv's `bin/` to `/usr/local/bin/`, making command-line tools available. - **Robust Version Check:** Verified `DATAPROC_IMAGE_VERSION` is non-empty before running `bc` comparison to avoid script crashes.
|
/gcbrun |
|
there are no gcbrun tests for the python/ directory so we'll skip dependence on the running of those tests. |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces two new initialization-action scripts, pip-install-cached.sh and conda-install-cached.sh, along with updated documentation in README.md to support installing and caching Python and Conda environments in GCS. The review feedback identifies several critical and high-severity issues in the scripts: the virtualenv at ${isolated_env_path} is never initialized, failures in merge_isolated_env are silently ignored, copied scripts retain shebangs pointing to the isolated virtualenv instead of the system python, and both scripts lack a fallback for OS detection when lsb_release is missing on minimal images.
| ensure_venv | ||
| original_env_path="${env_path}" | ||
| env_path="${isolated_env_path}" |
There was a problem hiding this comment.
The virtualenv at ${isolated_env_path} is never actually created. Although ensure_venv is called to install the system packages for venv, the virtualenv directory itself is never initialized. This will cause the script to fail on any cache miss when attempting to run pip from the non-existent virtualenv path. We should explicitly create the virtualenv after ensuring venv is available.
| ensure_venv | |
| original_env_path="${env_path}" | |
| env_path="${isolated_env_path}" | |
| ensure_venv | |
| python3 -m venv "${isolated_env_path}" || virtualenv "${isolated_env_path}" | |
| original_env_path="${env_path}" | |
| env_path="${isolated_env_path}" |
| if gcs_cat "${tarball}" | tar -C "${target_path}" -xz; then | ||
| if [[ "${is_isolated}" == "true" ]]; then | ||
| merge_isolated_env "${target_path}" | ||
| fi | ||
| return 0 | ||
| fi |
There was a problem hiding this comment.
If merge_isolated_env fails during unpack_and_merge, the failure is silently ignored and the function returns 0 (success). This happens because merge_isolated_env is called inside an if statement, which disables set -e behavior for the entire function call, and the function then unconditionally returns 0. We should explicitly propagate the failure by returning 1 if merge_isolated_env fails.
| if gcs_cat "${tarball}" | tar -C "${target_path}" -xz; then | |
| if [[ "${is_isolated}" == "true" ]]; then | |
| merge_isolated_env "${target_path}" | |
| fi | |
| return 0 | |
| fi | |
| if gcs_cat "${tarball}" | tar -C "${target_path}" -xz; then | |
| if [[ "${is_isolated}" == "true" ]]; then | |
| merge_isolated_env "${target_path}" || return 1 | |
| fi | |
| return 0 | |
| fi |
| if [[ -d "${venv_path}/bin" ]]; then | ||
| find "${venv_path}/bin" -type f ! -name "python*" ! -name "pip*" ! -name "easy_install*" ! -name "activate*" -exec cp -a {} /usr/local/bin/ \; | ||
| fi |
There was a problem hiding this comment.
When copying executable scripts from the isolated virtualenv to /usr/local/bin/, their shebangs will still point to the virtualenv's python interpreter (e.g., #!/opt/pip-isolated-env/bin/python3). While this works as long as the virtualenv exists, it is cleaner and more robust to rewrite these shebangs to use the system python (/usr/bin/env python3), making the scripts independent of the virtualenv path.
| if [[ -d "${venv_path}/bin" ]]; then | |
| find "${venv_path}/bin" -type f ! -name "python*" ! -name "pip*" ! -name "easy_install*" ! -name "activate*" -exec cp -a {} /usr/local/bin/ \; | |
| fi | |
| if [[ -d "${venv_path}/bin" ]]; then | |
| find "${venv_path}/bin" -type f ! -name "python*" ! -name "pip*" ! -name "easy_install*" ! -name "activate*" -exec cp -a {} /usr/local/bin/ \\; | |
| # Rewrite shebangs of copied scripts to use system python | |
| for f in $(find "${venv_path}/bin" -type f ! -name "python*" ! -name "pip*" ! -name "easy_install*" ! -name "activate*" -exec basename {} \\;); do | |
| if head -n 1 "/usr/local/bin/${f}" | grep -q "python"; then | |
| sed -i "1s|^#!.*python.*|#!/usr/bin/env python3|" "/usr/local/bin/${f}" | |
| fi | |
| done | |
| fi |
| fi | ||
| readonly CACHE_TIMEOUT | ||
| readonly TARGET_ENV_PATH=$(/usr/share/google/get_metadata_value attributes/TARGET_ENV_PATH || true) | ||
| readonly OS_NAME=$(lsb_release -is | tr '[:upper:]' '[:lower:]') |
There was a problem hiding this comment.
On some minimal images (such as Rocky Linux), lsb_release is not installed by default. If lsb_release is missing, the command substitution will fail and cause the script to exit immediately due to set -e. We should add a fallback to /etc/os-release to make the OS detection more robust.
| readonly OS_NAME=$(lsb_release -is | tr '[:upper:]' '[:lower:]') | |
| readonly OS_NAME=$(if command -v lsb_release >/dev/null 2>&1; then lsb_release -is | tr '[:upper:]' '[:lower:]'; else . /etc/os-release; echo "${ID}" | tr '[:upper:]' '[:lower:]'; fi) |
| fi | ||
| readonly CACHE_TIMEOUT | ||
| readonly TARGET_ENV_PATH=$(/usr/share/google/get_metadata_value attributes/TARGET_ENV_PATH || true) | ||
| readonly OS_NAME=$(lsb_release -is | tr '[:upper:]' '[:lower:]') |
There was a problem hiding this comment.
On some minimal images (such as Rocky Linux), lsb_release is not installed by default. If lsb_release is missing, the command substitution will fail and cause the script to exit immediately due to set -e. We should add a fallback to /etc/os-release to make the OS detection more robust.
| readonly OS_NAME=$(lsb_release -is | tr '[:upper:]' '[:lower:]') | |
| readonly OS_NAME=$(if command -v lsb_release >/dev/null 2>&1; then lsb_release -is | tr '[:upper:]' '[:lower:]'; else . /etc/os-release; echo "${ID}" | tr '[:upper:]' '[:lower:]'; fi) |
- **Fix Isolated Venv Initialization (pip):** Explicitly create the isolated virtual environment using `python3 -m venv` or `virtualenv` after ensuring its capabilities are installed. - **Improve Error Handling (pip):** Propagate failures from `merge_isolated_env` in `unpack_and_merge` instead of silently ignoring them. - **Robust Shebangs (pip):** Rewrite shebangs of executable scripts copied from isolated venv to `/usr/local/bin/` to use system python (`#!/usr/bin/env python3`), making them independent of the venv path. - **Robust OS Detection (pip/conda):** Added fallback to `/etc/os-release` when `lsb_release` is missing on minimal images.
… OS detection ## Summary Introduces integration tests for `pip-install-cached.sh` and `conda-install-cached.sh` to verify GCS-based caching behavior. Also improves the robustness of OS detection in both scripts by directly parsing `/etc/os-release`. ## Details - **Tests**: Added `test_pip_cached.py` and `test_conda_cached.py` in the `python/` directory. These tests verify that packages are installed correctly and that cache tarballs are generated in GCS. - **Build System**: Created `python/BUILD` to define test targets and a `test_python` suite. Integrated this suite into the root `DataprocInitActionsTestSuite`. - **Robustness**: Replaced `lsb_release` dependency in installer scripts with direct file parsing of `/etc/os-release` via `grep` and `cut` for better compatibility across minimal images. ## Changes - `BUILD`: Added `//python:test_python` to `DataprocInitActionsTestSuite`. - `python/BUILD`: New file defining test targets. - `python/conda-install-cached.sh`: Updated `OS_NAME` detection. - `python/pip-install-cached.sh`: Updated `OS_NAME` detection. - `python/test_conda_cached.py`: New integration test for cached conda installs. - `python/test_pip_cached.py`: New integration test for cached pip installs.
|
/gcbrun |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces initialization actions and integration tests for installing PyPI and Conda packages with caching support in Google Cloud Storage (GCS) to speed up Dataproc cluster creations. Feedback on the changes highlights three main areas of improvement: ensuring the target environment directory is created before unpacking the Conda cache, handling potential network failures gracefully when querying Debian repositories in private clusters, and verifying that the conda binary is available in the PATH before attempting to configure channels.
…vironments ## Summary Addressed critical feedback to improve the reliability of `conda-install-cached.sh` and `pip-install-cached.sh`, particularly in private clusters or when custom target environments are used. ## Details - **Conda Installer**: - Ensured target environment path is created (`mkdir -p`) before unpacking, preventing failure when `TARGET_ENV_PATH` points to a non-existent directory. - Moved channel configuration to after `conda` binary detection to avoid crashes if `conda` is not yet available in `PATH`. - Added graceful handling and warnings if `conda` is missing but `TARGET_ENV_PATH` is specified. - **Pip Installer**: - Added fallbacks and error silencing (`2>/dev/null`) to `curl` commands in `remove_old_backports`. This prevents script failure in air-gapped or private clusters where `deb.debian.org` is unreachable. ## Changes - `python/conda-install-cached.sh`: Fixed channel config order, added directory creation, improved `conda` binary detection. - `python/pip-install-cached.sh`: Added fallbacks for `curl` calls.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces caching mechanisms for installing PyPI and Conda packages on Dataproc clusters using GCS, including the scripts pip-install-cached.sh and conda-install-cached.sh, documentation, and integration tests. Feedback suggests several improvements: using --system-site-packages in the isolated virtual environment to avoid redundant downloads, warning users against caching the base /opt/conda directory, sanitizing DATAPROC_IMAGE_VERSION to prevent bc parsing errors, and using run_command instead of assert_command in test tearDown methods to avoid masking setup failures.
…prove test cleanup ## Summary Addressed further review feedback to optimize performance, prevent script crashes, and improve test stability. ## Details - **Pip Installer Optimization**: Added `--system-site-packages` to isolated `venv` creation. This avoids redundant downloads of packages already present in the base image, speeding up installations and reducing cache size. - **Version Sanitization**: Added logic to sanitize `DATAPROC_IMAGE_VERSION` to `major.minor` format before passing to `bc`, preventing syntax errors on versions with non-numeric suffixes. - **Safety Warnings**: Added `/opt/conda` to the list of unsafe directories to cache in `conda-install-cached.sh`, warning users against overwriting the base environment. - **Test Stability**: Refactored `tearDown` in integration tests to use `run_command` instead of `assert_command` and added existence checks for buckets. This prevents cleanup failures from masking actual test errors. ## Changes - `python/conda-install-cached.sh`: Added `/opt/conda` to unsafe directories list. - `python/pip-install-cached.sh`: Added `--system-site-packages` to venv creation, sanitized version for `bc`. - `python/test_conda_cached.py`: Robust `tearDown` implementation. - `python/test_pip_cached.py`: Robust `tearDown` implementation.
This PR introduces cached variants of the standard Python package installation scripts:
pip-install-cached.shandconda-install-cached.sh. These scripts are designed to significantly reduce cluster initialization time and improve reliability in constrained environments (e.g., behind proxies or with limited egress bandwidth).Resolves #1408
Problem Solved
Traditional package installation on every node startup can become a bottleneck due to:
Solution Approach: "Check-Cache-or-Build"
The new scripts leverage Google Cloud Storage (GCS) to cache pre-packaged Python environments.
.buildingsentinel file in GCS to prevent concurrent builds from other nodes.Key Features & Customizations
dataproc-temp-bucketif no specificCACHE_BUCKETis provided.CACHE_BUCKET: Override default storage bucket.CACHE_KEY_OVERRIDE: Force a manual cache key name.CACHE_TIMEOUT: Set cache validity period (Defaults to effectively infinite/10 years).TARGET_ENV_PATH: Explicitly define destination environment directory.Testing & Usage
Documentation has been added to
python/README.mdcovering options and examples.Example Usage:
gcloud dataproc clusters create my-cluster \ --metadata 'PIP_PACKAGES=pandas scipy' \ --initialization-actions gs://<my-bucket>/python/pip-install-cached.sh