From e1289db39fbbad3bb7fd9763ed7be895c752bcc7 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 08:40:40 +0300 Subject: [PATCH 01/41] CI2M: lint and build-check are deleted, test std2-all/py3.12/pg17 only (temporarily) --- .github/workflows/ci.yml | 116 --------------------------------------- 1 file changed, 116 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c89118c..9826b134 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,131 +15,15 @@ on: - "**/*.md" jobs: - lint: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] - - steps: - - name: Checkout - uses: actions/checkout@v7 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - cache: 'pip' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install flake8 flake8-pyproject ruff - - name: Lint with flake8 - run: | - flake8 . - - name: Lint with ruff - run: | - ruff check . - - build-check: - runs-on: ubuntu-latest - needs: lint - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Set up Python 3.12 - uses: actions/setup-python@v6 - with: - python-version: "3.12" - cache: 'pip' - - - name: Install build tools - run: | - python -m pip install --upgrade pip - python -m pip install build twine - - - name: Build package - run: | - python -m build - - - name: Check package metadata - run: | - twine check dist/* - test: runs-on: ubuntu-latest - needs: build-check strategy: fail-fast: false matrix: include: - - platform: "std2-all" - python: "3.7" - postgres: "17" - - platform: "std2-all" - python: "3.8.0" - postgres: "17" - - platform: "std2-all" - python: "3.8" - postgres: "17" - - platform: "std2-all" - python: "3.9" - postgres: "17" - - platform: "std2-all" - python: "3.10" - postgres: "17" - - platform: "std2-all" - python: "3.11" - postgres: "17" - platform: "std2-all" python: "3.12" postgres: "17" - - platform: "std2-all" - python: "3.13" - postgres: "17" - - platform: "std2-all" - python: "3.14" - postgres: "17" - - platform: "std" - python: "3" - postgres: "10" - - platform: "std" - python: "3" - postgres: "11" - - platform: "std" - python: "3" - postgres: "12" - - platform: "std" - python: "3" - postgres: "13" - - platform: "std" - python: "3" - postgres: "14" - - platform: "std" - python: "3" - postgres: "15" - - platform: "std" - python: "3" - postgres: "16" - - platform: "std-all" - python: "3" - postgres: "17" - - platform: "std-all" - python: "3" - postgres: "18" - - platform: "ubuntu_24_04" - python: "3" - postgres: "17" - - platform: "altlinux_10" - python: "3" - postgres: "17" - - platform: "altlinux_11" - python: "3" - postgres: "17" - - platform: "astralinux_1_7" - python: "3" - postgres: "17" env: BASE_SIGN: "${{ matrix.platform }}-py${{ matrix.python }}-pg${{ matrix.postgres }}" From 0876b5bd97e09d324543d2682ef4e88ddf66b811 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 08:42:01 +0300 Subject: [PATCH 02/41] CI2M: Dockerfile--std2-all.tmpl is refactored NOTE: TEST_FILTER is "init or kill or app" (temporarily). --- Dockerfile--std2-all.tmpl | 126 +++++++++++++++++++++++--------------- 1 file changed, 77 insertions(+), 49 deletions(-) diff --git a/Dockerfile--std2-all.tmpl b/Dockerfile--std2-all.tmpl index 8a3309b5..d43dd9ab 100644 --- a/Dockerfile--std2-all.tmpl +++ b/Dockerfile--std2-all.tmpl @@ -4,24 +4,38 @@ ARG PYTHON_VERSION=3.12 # --------------------------------------------- base1 FROM postgres:${PG_VERSION}-alpine AS base1 -RUN apk add --no-cache coreutils +RUN apk add --no-cache \ + coreutils \ + bash \ + mc \ + procps \ + openssh \ + sshpass \ + sudo \ + git # --------------------------------------------- base2_with_python-3 FROM base1 AS base2_with_python-3 + +RUN apk add --no-cache \ + curl \ + python3 \ + python3-dev \ + build-base \ + musl-dev \ + linux-headers \ + # For pyenv \ + patch \ + xz-dev \ + zip \ + zlib-dev \ + libffi-dev \ + readline-dev \ + openssl openssl-dev \ + sqlite-dev \ + bzip2-dev + ENV PYTHON_BINARY=python3 -RUN apk add --no-cache curl python3 python3-dev build-base musl-dev linux-headers - -# For pyenv -RUN apk add patch -RUN apk add git -RUN apk add xz-dev -RUN apk add zip -RUN apk add zlib-dev -RUN apk add libffi-dev -RUN apk add readline-dev -RUN apk add openssl openssl-dev -RUN apk add sqlite-dev -RUN apk add bzip2-dev # --------------------------------------------- base3_with_python-3.7 FROM base2_with_python-3 AS base3_with_python-3.7 @@ -62,23 +76,11 @@ ENV PYTHON_VERSION=3.14 # --------------------------------------------- final FROM base3_with_python-${PYTHON_VERSION} AS final -#RUN apk add --no-cache mc - -# Full version of "ps" command -RUN apk add --no-cache procps - -RUN apk add --no-cache openssh -RUN apk add --no-cache sudo - -ENV LANG=C.UTF-8 - -RUN addgroup -S sudo -RUN adduser -D test -RUN addgroup test sudo - EXPOSE 22 RUN ssh-keygen -A +RUN adduser -D test && addgroup -S sudo && adduser test sudo + # It allows to use sudo without password RUN echo "test ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers @@ -86,33 +88,59 @@ RUN echo "test ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers RUN echo "test:*" | chpasswd -e USER test - RUN curl https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash RUN ~/.pyenv/bin/pyenv install ${PYTHON_VERSION} +USER root -ADD --chown=test:test . /home/test/testgres +COPY --chown=test:test . /home/test/testgres WORKDIR /home/test/testgres -RUN mkdir /home/test/testgres/logs -RUN chown -R test:test /home/test/testgres/logs -# THIS CMD IS NEEDED TO CONNECT THROUGH SSH WITHOUT PASSWORD -RUN chmod 700 ~/ - -RUN mkdir -p ~/.ssh +ENV LANG=C.UTF-8 -ENTRYPOINT sh -c " \ +RUN chmod 700 /home/test/ && \ + mkdir -p /home/test/.ssh && \ + chown -R test:test /home/test/.ssh && \ + mkdir /home/test/testgres/logs && \ + chown -R test:test /home/test/testgres/logs + +# +# \"$@\" +# - quote is important! +# - "DUMMY-DUMMY-DUMMY" will be ignored. Do not ask me "why?". AXEZ. +# +ENTRYPOINT ["sh", "-c", " \ + set -eux; \ + echo 'SYSTEM START: PREPARING SSH'; \ + /usr/sbin/sshd; \ + ls -la /home/test/.ssh/; \ + \"$@\" \ +", "DUMMY-DUMMY-DUMMY"] + +# Run tests by default (master machine role) +CMD ["bash", "-c", " \ set -eux; \ -echo HELLO FROM ENTRYPOINT; \ -echo HOME DIR IS [`realpath ~/`]; \ -ls -la .; \ -ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -N ''; \ -cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys; \ -chmod 600 ~/.ssh/authorized_keys; \ -ls -la ~/.ssh/; \ -sudo /usr/sbin/sshd; \ +echo \"HOME DIR IS [`realpath ~/`]\"; \ +echo \"WORK DIR IS [$(pwd)]\"; \ +if [ ! -f /home/test/.ssh/id_rsa ]; then \ + su test -c \"ssh-keygen -t rsa -f /home/test/.ssh/id_rsa -q -N ''\"; \ + su test -c \"cat /home/test/.ssh/id_rsa.pub >> /home/test/.ssh/authorized_keys\"; \ + chmod 600 /home/test/.ssh/authorized_keys; \ +fi; \ +ls -la /home/test/.ssh/; \ sudo chmod 777 /home/test/testgres/logs; \ ls -la . | grep logs; \ -ssh-keyscan -H localhost >> ~/.ssh/known_hosts; \ -ssh-keyscan -H 127.0.0.1 >> ~/.ssh/known_hosts; \ -export PATH=\"~/.pyenv/bin:$PATH\"; \ -TEST_FILTER=\"\" bash run_tests2.sh;" +su test -c \"ssh-keyscan -H localhost >> /home/test/.ssh/known_hosts\"; \ +su test -c \"ssh-keyscan -H 127.0.0.1 >> /home/test/.ssh/known_hosts\"; \ +if [ -n \"${TEST_CFG__REMOTE_HOST:-}\" ]; then \ + su test -c \"ssh-keyscan -H ${TEST_CFG__REMOTE_HOST} >> /home/test/.ssh/known_hosts\"; \ +fi; \ +if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ + cp \"${TEST_CFG__REMOTE_SSH_KEY}\" \"${TEST_CFG__REMOTE_SSH_KEY}_ci\"; \ + export TEST_CFG__REMOTE_SSH_KEY=\"${TEST_CFG__REMOTE_SSH_KEY}_ci\"; \ + chown test:test \"${TEST_CFG__REMOTE_SSH_KEY}\"; \ + chmod 600 \"${TEST_CFG__REMOTE_SSH_KEY}\"; \ + ls -la /home/test/.ssh/; \ +fi; \ +ls -la ./; \ +su test -c \"TEST_FILTER='init or kill or app' PATH=\"/home/test/.pyenv/bin:$PATH\" bash ./run_tests2.sh\"; \ +"] From 6ce2e1cb3732552a2b32c7ad4f0b7142b1ecc98f Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 08:53:48 +0300 Subject: [PATCH 03/41] CI2M: Dockerfile--std2-all.tmpl is minimized (test app only) --- Dockerfile--std2-all.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile--std2-all.tmpl b/Dockerfile--std2-all.tmpl index d43dd9ab..d09ec326 100644 --- a/Dockerfile--std2-all.tmpl +++ b/Dockerfile--std2-all.tmpl @@ -142,5 +142,5 @@ if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ ls -la /home/test/.ssh/; \ fi; \ ls -la ./; \ -su test -c \"TEST_FILTER='init or kill or app' PATH=\"/home/test/.pyenv/bin:$PATH\" bash ./run_tests2.sh\"; \ +su test -c \"TEST_FILTER='app' PATH=\"/home/test/.pyenv/bin:$PATH\" bash ./run_tests2.sh\"; \ "] From 59bf867d4821dc98193da93fbf0b17105b3180b1 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 08:55:34 +0300 Subject: [PATCH 04/41] CI2M: Dockerfile--std2-all.tmpl does not correct /home/test/testgres/logs It is an experiment. os_ops does not do it. --- Dockerfile--std2-all.tmpl | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dockerfile--std2-all.tmpl b/Dockerfile--std2-all.tmpl index d09ec326..5a16154a 100644 --- a/Dockerfile--std2-all.tmpl +++ b/Dockerfile--std2-all.tmpl @@ -127,8 +127,6 @@ if [ ! -f /home/test/.ssh/id_rsa ]; then \ chmod 600 /home/test/.ssh/authorized_keys; \ fi; \ ls -la /home/test/.ssh/; \ -sudo chmod 777 /home/test/testgres/logs; \ -ls -la . | grep logs; \ su test -c \"ssh-keyscan -H localhost >> /home/test/.ssh/known_hosts\"; \ su test -c \"ssh-keyscan -H 127.0.0.1 >> /home/test/.ssh/known_hosts\"; \ if [ -n \"${TEST_CFG__REMOTE_HOST:-}\" ]; then \ From e6444bda4b5c862717380d4c1e1869912da428bf Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 09:10:22 +0300 Subject: [PATCH 05/41] CI2M: cy.yml creates two machines, tests/helpers/global_data.py is updated Code was copied from testgres.os_ops package. --- .github/workflows/ci.yml | 79 ++++++++++++++++++++++++++++++++++-- tests/helpers/global_data.py | 14 +++++-- 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9826b134..20f4aff0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,10 @@ jobs: echo "RUN_CFG__NOW=$(date +'%Y%m%d_%H%M%S')" >> $GITHUB_ENV echo "RUN_CFG__LOGS_DIR=logs-${{ env.BASE_SIGN }}" >> $GITHUB_ENV echo "RUN_CFG__DOCKER_IMAGE_NAME=tests-${{ env.BASE_SIGN }}" >> $GITHUB_ENV + echo "RUN_CFG__NETWORK_NAME=network-${{ env.BASE_SIGN }}" >> $GITHUB_ENV + echo "RUN_CFG__MACHINE1_NAME=machine1-${{ env.BASE_SIGN }}" >> $GITHUB_ENV + echo "RUN_CFG__MACHINE2_NAME=machine2-${{ env.BASE_SIGN }}" >> $GITHUB_ENV + echo "RUN_CFG__SSH_KEY_NAME=id_ed25519-${{ env.BASE_SIGN }}" >> $GITHUB_ENV echo "---------- [$GITHUB_ENV]" cat $GITHUB_ENV - name: Checkout @@ -42,10 +46,79 @@ jobs: run: mkdir -p "${{ env.RUN_CFG__LOGS_DIR }}" - name: Adjust logs folder permission run: chmod -R 777 "${{ env.RUN_CFG__LOGS_DIR }}" - - name: Build local image ${{ matrix.alpine }} + - name: Build local image run: docker build --build-arg PG_VERSION="${{ matrix.postgres }}" --build-arg PYTHON_VERSION="${{ matrix.python }}" -t "${{ env.RUN_CFG__DOCKER_IMAGE_NAME }}" -f Dockerfile--${{ matrix.platform }}.tmpl . - - name: Run - run: docker run $(bash <(curl -s https://codecov.io/env)) -t -v ${{ github.workspace }}/${{ env.RUN_CFG__LOGS_DIR }}:/home/test/testgres/logs "${{ env.RUN_CFG__DOCKER_IMAGE_NAME }}" + - name: Generate temporary SSH Key pair for CI + run: | + # Generate a key without a password directly on the GitHub Actions host + ssh-keygen -q -t ed25519 -N "" -f ${{ github.workspace }}/${{ env.RUN_CFG__SSH_KEY_NAME }} + + - name: Setup network + run: | + docker network create ${{ env.RUN_CFG__NETWORK_NAME }} + + - name: Run machine2 (for remote operations) + run: | + # + # "- sleep infinity" is a right command. Docker ignores "-" and runs "sleep infinity". + # + docker run -d -t \ + --network ${{ env.RUN_CFG__NETWORK_NAME }} \ + --name ${{ env.RUN_CFG__MACHINE2_NAME }} \ + "${{ env.RUN_CFG__DOCKER_IMAGE_NAME }}" \ + sleep infinity + + echo "Waiting for container ${{ env.RUN_CFG__MACHINE2_NAME }} to start ..." + + # Set the timeout using the attempt counter + MAX_ATTEMPTS=50 + ATTEMPT=0 + + until [ "$(docker container inspect --format '{{.State.Running}}' "${{ env.RUN_CFG__MACHINE2_NAME }}")" = "true" ]; do + ATTEMPT=$((ATTEMPT + 1)) + if [ $ATTEMPT -ge $MAX_ATTEMPTS ]; then + echo "Error: Container did not start within the allotted time (timeout)!" + exit 1 + fi + sleep 0.2 + done + + echo "Container successfully launched in $((ATTEMPT * 2 / 10)) second(s)!" + + # + # Setup authorized_keys + # + cat ${{ github.workspace }}/${{ env.RUN_CFG__SSH_KEY_NAME }}.pub | \ + docker exec -i -u test ${{ env.RUN_CFG__MACHINE2_NAME }} sh -c " \ + cat >> /home/test/.ssh/authorized_keys && \ + chmod 600 /home/test/.ssh/authorized_keys \ + " + + # + # Log status + # + docker exec -u root ${{ env.RUN_CFG__MACHINE2_NAME }} sh -c " + echo '--- INSIDE CONTAINER OS ---' && \ + cat /etc/os-release && \ + echo '--- INSIDE CONTAINER .SSH ---' && \ + ls -la /home/test/.ssh/ \ + " + + - name: Run machine1 (main test container) + run: | + # Launch the main container, passing the private key to the .ssh folder of the test user. + # Pass environment variables so Python tests know where to start. + # Added the --rm flag so that the container is automatically deleted after the tests are completed + docker run --rm -t \ + --network ${{ env.RUN_CFG__NETWORK_NAME }} \ + --name ${{ env.RUN_CFG__MACHINE1_NAME }} \ + -v ${{ github.workspace }}/${{ env.RUN_CFG__LOGS_DIR }}:/home/test/testgres/logs \ + -v ${{ github.workspace }}/${{ env.RUN_CFG__SSH_KEY_NAME }}:/home/test/testgres/id_ed25519_test:ro \ + -e TEST_CFG__REMOTE_HOST="${{ env.RUN_CFG__MACHINE2_NAME }}" \ + -e TEST_CFG__REMOTE_PORT="22" \ + -e TEST_CFG__REMOTE_USERNAME="test" \ + -e TEST_CFG__REMOTE_SSH_KEY="/home/test/testgres/id_ed25519_test" \ + "${{ env.RUN_CFG__DOCKER_IMAGE_NAME }}" - name: Upload Logs uses: actions/upload-artifact@v7 if: always() # IT IS IMPORTANT! diff --git a/tests/helpers/global_data.py b/tests/helpers/global_data.py index fa49b24d..111b6e17 100644 --- a/tests/helpers/global_data.py +++ b/tests/helpers/global_data.py @@ -8,6 +8,7 @@ from src.node import PortManager__Generic import os +import typing class OsOpsDescr: @@ -21,11 +22,18 @@ def __init__(self, sign: str, os_ops: OsOperations): self.os_ops = os_ops +def _to_int_or_none(v: typing.Any) -> typing.Optional[int]: + return v if v is None else int(v) + + class OsOpsDescrs: sm_remote_conn_params = ConnectionParams( - host=os.getenv('RDBMS_TESTPOOL1_HOST') or '127.0.0.1', - username=os.getenv('USER'), - ssh_key=os.getenv('RDBMS_TESTPOOL_SSHKEY')) + host=os.getenv('TEST_CFG__REMOTE_HOST', '127.0.0.1'), + port=_to_int_or_none(os.getenv('TEST_CFG__REMOTE_PORT')), + username=os.getenv('TEST_CFG__REMOTE_USERNAME'), + ssh_key=os.getenv('TEST_CFG__REMOTE_SSH_KEY'), + password=os.getenv('TEST_CFG__REMOTE_PASSWORD'), + ) sm_remote_os_ops = RemoteOperations(sm_remote_conn_params) From f27e09471ecb347ac7b0f18fc8299c63f3305dda Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 09:18:19 +0300 Subject: [PATCH 06/41] CI2M: Dockerfile--std2-all.tmpl runs prove of 2m tests, too --- Dockerfile--std2-all.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile--std2-all.tmpl b/Dockerfile--std2-all.tmpl index 5a16154a..acb1c849 100644 --- a/Dockerfile--std2-all.tmpl +++ b/Dockerfile--std2-all.tmpl @@ -140,5 +140,5 @@ if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ ls -la /home/test/.ssh/; \ fi; \ ls -la ./; \ -su test -c \"TEST_FILTER='app' PATH=\"/home/test/.pyenv/bin:$PATH\" bash ./run_tests2.sh\"; \ +su test -c \"TEST_FILTER='app or prove' PATH=\"/home/test/.pyenv/bin:$PATH\" bash ./run_tests2.sh\"; \ "] From 2fd15b1c2b56c709f7afdfea43048e381ef82d8a Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 09:21:33 +0300 Subject: [PATCH 07/41] CI2M: All "std2-all" are tested (in minimal form) --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20f4aff0..218aa75c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,9 +21,33 @@ jobs: fail-fast: false matrix: include: + - platform: "std2-all" + python: "3.7" + postgres: "17" + - platform: "std2-all" + python: "3.8.0" + postgres: "17" + - platform: "std2-all" + python: "3.8" + postgres: "17" + - platform: "std2-all" + python: "3.9" + postgres: "17" + - platform: "std2-all" + python: "3.10" + postgres: "17" + - platform: "std2-all" + python: "3.11" + postgres: "17" - platform: "std2-all" python: "3.12" postgres: "17" + - platform: "std2-all" + python: "3.13" + postgres: "17" + - platform: "std2-all" + python: "3.14" + postgres: "17" env: BASE_SIGN: "${{ matrix.platform }}-py${{ matrix.python }}-pg${{ matrix.postgres }}" From a8b021fceaaf4ec924bdeb0e770c4ddf6af1b726 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 09:38:32 +0300 Subject: [PATCH 08/41] CI2M: Try to use std2-all for all the PG versions (unification) Ofcourse, py3.12/pg17 is excluded. --- .github/workflows/ci.yml | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 218aa75c..8af60b74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,32 +22,29 @@ jobs: matrix: include: - platform: "std2-all" - python: "3.7" - postgres: "17" - - platform: "std2-all" - python: "3.8.0" - postgres: "17" + python: "3.12" + postgres: "10" - platform: "std2-all" - python: "3.8" - postgres: "17" + python: "3.12" + postgres: "11" - platform: "std2-all" - python: "3.9" - postgres: "17" + python: "3.12" + postgres: "12" - platform: "std2-all" - python: "3.10" - postgres: "17" + python: "3.12" + postgres: "13" - platform: "std2-all" - python: "3.11" - postgres: "17" + python: "3.12" + postgres: "14" - platform: "std2-all" python: "3.12" - postgres: "17" + postgres: "15" - platform: "std2-all" - python: "3.13" - postgres: "17" + python: "3.12" + postgres: "16" - platform: "std2-all" - python: "3.14" - postgres: "17" + python: "3.12" + postgres: "18" env: BASE_SIGN: "${{ matrix.platform }}-py${{ matrix.python }}-pg${{ matrix.postgres }}" From 548047708543884bca13d85d59ed916c0c30f28a Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 09:46:34 +0300 Subject: [PATCH 09/41] CI2M: std amd std-all are deleted (replaced with std2-all) --- Dockerfile--std-all.tmpl | 70 ---------------------------------------- Dockerfile--std.tmpl | 43 ------------------------ 2 files changed, 113 deletions(-) delete mode 100644 Dockerfile--std-all.tmpl delete mode 100644 Dockerfile--std.tmpl diff --git a/Dockerfile--std-all.tmpl b/Dockerfile--std-all.tmpl deleted file mode 100644 index c6dabd43..00000000 --- a/Dockerfile--std-all.tmpl +++ /dev/null @@ -1,70 +0,0 @@ -ARG PG_VERSION=17 -ARG PYTHON_VERSION=3 - -# --------------------------------------------- base1 -FROM postgres:${PG_VERSION}-alpine AS base1 - -RUN apk add --no-cache coreutils - -# --------------------------------------------- base2_with_python-3 -FROM base1 AS base2_with_python-3 -RUN apk add --no-cache curl python3 python3-dev build-base musl-dev linux-headers -ENV PYTHON_BINARY=python3 - -# --------------------------------------------- final -FROM base2_with_python-${PYTHON_VERSION} AS final - -#RUN apk add --no-cache mc -RUN apk add --no-cache git - -# Full version of "ps" command -RUN apk add --no-cache procps - -RUN apk add --no-cache openssh -RUN apk add --no-cache sudo - -ENV LANG=C.UTF-8 - -RUN addgroup -S sudo -RUN adduser -D test -RUN addgroup test sudo - -EXPOSE 22 -RUN ssh-keygen -A - -ADD --chown=test:test . /home/test/testgres -WORKDIR /home/test/testgres -RUN mkdir /home/test/testgres/logs -RUN chown -R test:test /home/test/testgres/logs - -# It allows to use sudo without password -RUN echo "test ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers - -# THIS CMD IS NEEDED TO CONNECT THROUGH SSH WITHOUT PASSWORD -RUN echo "test:*" | chpasswd -e - -USER test - -# THIS CMD IS NEEDED TO CONNECT THROUGH SSH WITHOUT PASSWORD -RUN chmod 700 ~/ - -RUN mkdir -p ~/.ssh -#RUN chmod 700 ~/.ssh - -#ENTRYPOINT PYTHON_VERSION=${PYTHON_VERSION} bash run_tests.sh - -ENTRYPOINT sh -c " \ -set -eux; \ -echo HELLO FROM ENTRYPOINT; \ -echo HOME DIR IS [`realpath ~/`]; \ -ls -la .; \ -ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -N ''; \ -cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys; \ -chmod 600 ~/.ssh/authorized_keys; \ -ls -la ~/.ssh/; \ -sudo /usr/sbin/sshd; \ -sudo chmod 777 /home/test/testgres/logs; \ -ls -la . | grep logs; \ -ssh-keyscan -H localhost >> ~/.ssh/known_hosts; \ -ssh-keyscan -H 127.0.0.1 >> ~/.ssh/known_hosts; \ -TEST_FILTER=\"\" bash run_tests.sh;" diff --git a/Dockerfile--std.tmpl b/Dockerfile--std.tmpl deleted file mode 100644 index 5c5807ec..00000000 --- a/Dockerfile--std.tmpl +++ /dev/null @@ -1,43 +0,0 @@ -ARG PG_VERSION=17 -ARG PYTHON_VERSION=3 - -# --------------------------------------------- base1 -FROM postgres:${PG_VERSION}-alpine AS base1 - -RUN apk add --no-cache coreutils - -# --------------------------------------------- base2_with_python-3 -FROM base1 AS base2_with_python-3 -RUN apk add --no-cache curl python3 python3-dev build-base musl-dev linux-headers -ENV PYTHON_BINARY=python3 - -# --------------------------------------------- final -FROM base2_with_python-${PYTHON_VERSION} AS final - -RUN apk add --no-cache git - -RUN apk add --no-cache sudo - -ENV LANG=C.UTF-8 - -RUN addgroup -S sudo -RUN adduser -D test -RUN addgroup test sudo - -ADD --chown=test:test . /home/test/testgres -WORKDIR /home/test/testgres -RUN mkdir /home/test/testgres/logs -RUN chown -R test:test /home/test/testgres/logs - -# It allows to use sudo without password -RUN echo "test ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers - -USER test -ENTRYPOINT sh -c " \ -set -eux; \ -echo HELLO FROM ENTRYPOINT; \ -echo HOME DIR IS [`realpath ~/`]; \ -ls -la .; \ -sudo chmod 777 /home/test/testgres/logs; \ -ls -la . | grep logs; \ -bash run_tests.sh;" From 4a869afe6f51f288e6153583d686b9754a87e240 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 10:07:24 +0300 Subject: [PATCH 10/41] CI2M: Dockerfile--ubuntu_24_04.tmpl is updated --- .github/workflows/ci.yml | 27 +------- Dockerfile--ubuntu_24_04.tmpl | 121 ++++++++++++++++++++-------------- 2 files changed, 76 insertions(+), 72 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8af60b74..4b6affe1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,30 +21,9 @@ jobs: fail-fast: false matrix: include: - - platform: "std2-all" - python: "3.12" - postgres: "10" - - platform: "std2-all" - python: "3.12" - postgres: "11" - - platform: "std2-all" - python: "3.12" - postgres: "12" - - platform: "std2-all" - python: "3.12" - postgres: "13" - - platform: "std2-all" - python: "3.12" - postgres: "14" - - platform: "std2-all" - python: "3.12" - postgres: "15" - - platform: "std2-all" - python: "3.12" - postgres: "16" - - platform: "std2-all" - python: "3.12" - postgres: "18" + - platform: "ubuntu_24_04" + python: "3" + postgres: "17" env: BASE_SIGN: "${{ matrix.platform }}-py${{ matrix.python }}-pg${{ matrix.postgres }}" diff --git a/Dockerfile--ubuntu_24_04.tmpl b/Dockerfile--ubuntu_24_04.tmpl index 0f2a7834..a18d3399 100644 --- a/Dockerfile--ubuntu_24_04.tmpl +++ b/Dockerfile--ubuntu_24_04.tmpl @@ -1,78 +1,103 @@ -ARG PG_VERSION +ARG PG_VERSION=17 ARG PYTHON_VERSION=3 # --------------------------------------------- base1 FROM ubuntu:24.04 AS base1 ARG PG_VERSION -RUN apt update -RUN apt install -y sudo curl ca-certificates -RUN apt update -RUN apt install -y openssh-server -RUN apt install -y time -RUN apt install -y netcat-traditional - -RUN apt install -y git -RUN apt install -y iproute2 - -RUN apt update -RUN apt install -y postgresql-common +# Disable interactive apt questions so that the build doesn't hang when setting time zones +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y --no-install-recommends \ + sudo \ + curl \ + ca-certificates \ + openssh-server \ + sshpass \ + time \ + netcat-traditional \ + iproute2 \ + git \ + postgresql-common \ + libpq-dev \ + build-essential \ + && rm -rf /var/lib/apt/lists/* RUN bash /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y RUN install -d /usr/share/postgresql-common/pgdg RUN curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc -RUN apt update -RUN apt install -y postgresql-${PG_VERSION} +RUN apt-get update && apt-get install -y --no-install-recommends \ + postgresql-${PG_VERSION} # --------------------------------------------- base2_with_python-3 FROM base1 AS base2_with_python-3 -RUN apt install -y python3 python3-dev python3-venv libpq-dev build-essential + +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 \ + python3-dev \ + python3-venv \ + && rm -rf /var/lib/apt/lists/* + ENV PYTHON_BINARY=python3 # --------------------------------------------- final FROM base2_with_python-${PYTHON_VERSION} AS final EXPOSE 22 - RUN ssh-keygen -A -RUN adduser test -RUN chown postgres:postgres /var/run/postgresql -RUN chmod 775 /var/run/postgresql -RUN usermod -aG postgres test +RUN useradd -m test # It enables execution of "sudo service ssh start" without password -# RUN echo "test ALL=NOPASSWD:/usr/sbin/service ssh start" >> /etc/sudoers -# RUN echo "test ALL=NOPASSWD:ALL" >> /etc/sudoers -RUN echo "test ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers +RUN echo "test ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers -ADD --chown=test:test . /home/test/testgres +COPY --chown=test:test . /home/test/testgres WORKDIR /home/test/testgres -RUN mkdir /home/test/testgres/logs -RUN chown -R test:test /home/test/testgres/logs ENV LANG=C.UTF-8 -USER test - -RUN chmod 700 ~/ -RUN mkdir -p ~/.ssh - -ENTRYPOINT sh -c " \ -#set -eux; \ -echo HELLO FROM ENTRYPOINT; \ -echo HOME DIR IS [`realpath ~/`]; \ -ls -la .; \ -service ssh enable; \ -sudo service ssh start; \ -sudo chmod 777 /home/test/testgres/logs; \ -ls -la . | grep logs; \ -ssh-keyscan -H localhost >> ~/.ssh/known_hosts; \ -ssh-keyscan -H 127.0.0.1 >> ~/.ssh/known_hosts; \ -ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -N ''; \ -cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys; \ -chmod 600 ~/.ssh/authorized_keys; \ -ls -la ~/.ssh/; \ -TEST_FILTER=\"\" bash ./run_tests.sh;" +RUN chmod 700 /home/test/ && \ + mkdir -p /home/test/.ssh && \ + chown -R test:test /home/test/.ssh + +# +# \"$@\" +# - quote is important! +# - "DUMMY-DUMMY-DUMMY" will be ignored. Do not ask me "why?". AXEZ. +# +ENTRYPOINT ["sh", "-c", " \ + set -eux; \ + echo 'SYSTEM START: PREPARING SSH'; \ + service ssh start; \ + ls -la /home/test/.ssh/; \ + \"$@\" \ +", "DUMMY-DUMMY-DUMMY"] + +# Run tests by default (master machine role) +CMD ["bash", "-c", " \ +set -eux; \ +echo \"HOME DIR IS [`realpath ~/`]\"; \ +echo \"WORK DIR IS [$(pwd)]\"; \ +if [ ! -f /home/test/.ssh/id_rsa ]; then \ + su test -c \"ssh-keygen -t rsa -f /home/test/.ssh/id_rsa -q -N ''\"; \ + su test -c \"cat /home/test/.ssh/id_rsa.pub >> /home/test/.ssh/authorized_keys\"; \ + chmod 600 /home/test/.ssh/authorized_keys; \ +fi; \ +ls -la /home/test/.ssh/; \ +su test -c \"ssh-keyscan -H localhost >> /home/test/.ssh/known_hosts\"; \ +su test -c \"ssh-keyscan -H 127.0.0.1 >> /home/test/.ssh/known_hosts\"; \ +if [ -n \"${TEST_CFG__REMOTE_HOST:-}\" ]; then \ + su test -c \"ssh-keyscan -H ${TEST_CFG__REMOTE_HOST} >> /home/test/.ssh/known_hosts\"; \ +fi; \ +if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ + cp \"${TEST_CFG__REMOTE_SSH_KEY}\" \"${TEST_CFG__REMOTE_SSH_KEY}_ci\"; \ + export TEST_CFG__REMOTE_SSH_KEY=\"${TEST_CFG__REMOTE_SSH_KEY}_ci\"; \ + chown test:test \"${TEST_CFG__REMOTE_SSH_KEY}\"; \ + chmod 600 \"${TEST_CFG__REMOTE_SSH_KEY}\"; \ + ls -la /home/test/.ssh/; \ +fi; \ +ls -la ./; \ +su test -c \"TEST_FILTER='app or prove' bash ./run_tests.sh\"; \ +"] From b98920b6d606338a4e14b692fc68126ea3a9322a Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 10:28:22 +0300 Subject: [PATCH 11/41] CI2M: Dockerfile--astralinux_1_7.tmpl is updated --- .github/workflows/ci.yml | 3 + Dockerfile--astralinux_1_7.tmpl | 136 +++++++++++++++++++------------- 2 files changed, 84 insertions(+), 55 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b6affe1..17f0727d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,9 @@ jobs: - platform: "ubuntu_24_04" python: "3" postgres: "17" + - platform: "astralinux_1_7" + python: "3" + postgres: "17" env: BASE_SIGN: "${{ matrix.platform }}-py${{ matrix.python }}-pg${{ matrix.postgres }}" diff --git a/Dockerfile--astralinux_1_7.tmpl b/Dockerfile--astralinux_1_7.tmpl index 881f3081..fb0cb943 100644 --- a/Dockerfile--astralinux_1_7.tmpl +++ b/Dockerfile--astralinux_1_7.tmpl @@ -4,37 +4,39 @@ ARG PYTHON_VERSION=3 # --------------------------------------------- base1 FROM packpack/packpack:astra-1.7 AS base1 -RUN apt update -RUN apt install -y sudo curl ca-certificates -RUN apt update -RUN apt install -y openssh-server -RUN apt install -y time -# RUN apt install -y netcat-traditional - -RUN apt install -y git -RUN apt install -y iproute2 +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y --no-install-recommends \ + sudo \ + curl \ + ca-certificates \ + openssh-server \ + sshpass \ + iproute2 \ + git \ + time \ + && rm -rf /var/lib/apt/lists/* # --------------------------------------------- postgres FROM base1 AS base1_with_dev_tools -RUN apt-get update - -RUN apt-get install -y git -RUN apt-get install -y gcc -RUN apt-get install -y make - -RUN apt-get install -y meson -RUN apt-get install -y flex -RUN apt-get install -y bison - -RUN apt-get install -y pkg-config -RUN apt-get install -y libssl-dev -RUN apt-get install -y libicu-dev -RUN apt-get install -y libzstd-dev -RUN apt-get install -y zlib1g-dev -RUN apt-get install -y liblz4-dev -RUN apt-get install -y libzstd-dev -RUN apt-get install -y libxml2-dev +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + make \ + meson \ + flex \ + bison \ + pkg-config \ + libssl-dev \ + libicu-dev \ + libzstd-dev \ + zlib1g-dev \ + liblz4-dev \ + libzstd-dev \ + libxml2-dev \ + && rm -rf /var/lib/apt/lists/* # --------------------------------------------- postgres FROM base1_with_dev_tools AS base1_with_pg-17 @@ -60,7 +62,12 @@ RUN ln -s /pg/postgres/install/lib/libpq.so.5.17 /usr/lib/libpq.so.5 # --------------------------------------------- base2_with_python-3 FROM base1_with_pg-${PG_VERSION} AS base2_with_python-3 -RUN apt install -y python3 python3-dev python3-venv + +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 \ + python3-dev \ + python3-venv \ + && rm -rf /var/lib/apt/lists/* ENV PYTHON_BINARY=python3 @@ -68,7 +75,6 @@ ENV PYTHON_BINARY=python3 FROM base2_with_python-${PYTHON_VERSION} AS final EXPOSE 22 - RUN ssh-keygen -A RUN useradd -m test @@ -77,37 +83,57 @@ RUN useradd -m test # MY OLD: # RUN sh -c "echo test ALL=NOPASSWD:ALL" >> /etc/sudoers # AI: -RUN echo "test ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers +RUN echo "test ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers # THIS CMD IS NEEDED TO CONNECT THROUGH SSH WITHOUT PASSWORD -RUN sh -c "echo "test:*" | chpasswd -e" -RUN sed -i 's/UsePAM yes/UsePAM no/' /etc/ssh/sshd_config +RUN echo "test:*" | chpasswd -e && \ + sed -i 's/UsePAM yes/UsePAM no/' /etc/ssh/sshd_config -ADD --chown=test:test . /home/test/testgres +COPY --chown=test:test . /home/test/testgres WORKDIR /home/test/testgres -RUN mkdir /home/test/testgres/logs -RUN chown -R test:test /home/test/testgres/logs ENV LANG=C.UTF-8 -USER test - -RUN chmod 700 ~/ -RUN mkdir -p ~/.ssh -RUN chmod 700 ~/.ssh - -ENTRYPOINT sh -c " \ +RUN chmod 700 /home/test/ && \ + mkdir -p /home/test/.ssh && \ + chown -R test:test /home/test/.ssh + +# +# \"$@\" +# - quote is important! +# - "DUMMY-DUMMY-DUMMY" will be ignored. Do not ask me "why?". AXEZ. +# +ENTRYPOINT ["sh", "-c", " \ + set -eux; \ + echo 'SYSTEM START: PREPARING SSH'; \ + service ssh start; \ + ls -la /home/test/.ssh/; \ + \"$@\" \ +", "DUMMY-DUMMY-DUMMY"] + +# Run tests by default (master machine role) +CMD ["bash", "-c", " \ set -eux; \ -echo HELLO FROM ENTRYPOINT; \ -echo HOME DIR IS [`realpath ~/`]; \ -ls -la .; \ -sudo service ssh start; \ -sudo chmod 777 /home/test/testgres/logs; \ -ls -la . | grep logs; \ -ssh-keyscan -H localhost >> ~/.ssh/known_hosts; \ -ssh-keyscan -H 127.0.0.1 >> ~/.ssh/known_hosts; \ -ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -N ''; \ -cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys; \ -chmod 600 ~/.ssh/authorized_keys; \ -ls -la ~/.ssh/; \ -TEST_FILTER=\"\" bash ./run_tests.sh;" +echo \"HOME DIR IS [`realpath ~/`]\"; \ +echo \"WORK DIR IS [$(pwd)]\"; \ +if [ ! -f /home/test/.ssh/id_rsa ]; then \ + su test -c \"ssh-keygen -t rsa -f /home/test/.ssh/id_rsa -q -N ''\"; \ + su test -c \"cat /home/test/.ssh/id_rsa.pub >> /home/test/.ssh/authorized_keys\"; \ + chmod 600 /home/test/.ssh/authorized_keys; \ +fi; \ +ls -la /home/test/.ssh/; \ +su test -c \"ssh-keyscan -H localhost >> /home/test/.ssh/known_hosts\"; \ +su test -c \"ssh-keyscan -H 127.0.0.1 >> /home/test/.ssh/known_hosts\"; \ +if [ -n \"${TEST_CFG__REMOTE_HOST:-}\" ]; then \ + su test -c \"ssh-keyscan -H ${TEST_CFG__REMOTE_HOST} >> /home/test/.ssh/known_hosts\"; \ +fi; \ +if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ + cp \"${TEST_CFG__REMOTE_SSH_KEY}\" \"${TEST_CFG__REMOTE_SSH_KEY}_ci\"; \ + export TEST_CFG__REMOTE_SSH_KEY=\"${TEST_CFG__REMOTE_SSH_KEY}_ci\"; \ + chown test:test \"${TEST_CFG__REMOTE_SSH_KEY}\"; \ + chmod 600 \"${TEST_CFG__REMOTE_SSH_KEY}\"; \ + ls -la /home/test/.ssh/; \ +fi; \ +ls -la ./; \ +su test -c \"TEST_FILTER='app or prove' bash ./run_tests.sh\"; \ +"] From 58fd495f2b4b65f0a2d6c7348ca28821fafe3fd5 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 11:06:32 +0300 Subject: [PATCH 12/41] CI2M: altlinux-10/11 are updated --- .github/workflows/ci.yml | 6 ++ Dockerfile--altlinux_10.tmpl | 145 ++++++++++++++++++++-------------- Dockerfile--altlinux_11.tmpl | 149 +++++++++++++++++++++-------------- 3 files changed, 183 insertions(+), 117 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17f0727d..2e71e2ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,12 @@ jobs: - platform: "ubuntu_24_04" python: "3" postgres: "17" + - platform: "altlinux_10" + python: "3" + postgres: "17" + - platform: "altlinux_11" + python: "3" + postgres: "17" - platform: "astralinux_1_7" python: "3" postgres: "17" diff --git a/Dockerfile--altlinux_10.tmpl b/Dockerfile--altlinux_10.tmpl index d5fc5d7d..31733dc5 100644 --- a/Dockerfile--altlinux_10.tmpl +++ b/Dockerfile--altlinux_10.tmpl @@ -4,41 +4,37 @@ ARG PYTHON_VERSION=3 # --------------------------------------------- base1 FROM alt:p10 AS base1 -RUN apt-get update -RUN apt-get install -y sudo curl ca-certificates -RUN apt-get update -RUN apt-get install -y openssh-server openssh-clients -RUN apt-get install -y time - -# RUN apt-get install -y mc - -RUN apt-get install -y libsqlite3-devel - -EXPOSE 22 - -RUN ssh-keygen -A +RUN apt-get update && apt-get install -y \ + sudo \ + curl \ + ca-certificates \ + openssh-server \ + openssh-clients \ + sshpass \ + time \ + su \ + git \ + libsqlite3-devel \ + && apt-get clean # --------------------------------------------- postgres FROM base1 AS base1_with_dev_tools -RUN apt-get update - -RUN apt-get install -y git -RUN apt-get install -y gcc -RUN apt-get install -y make - -RUN apt-get install -y meson -RUN apt-get install -y flex -RUN apt-get install -y bison - -RUN apt-get install -y pkg-config -RUN apt-get install -y libssl-devel -RUN apt-get install -y libicu-devel -RUN apt-get install -y libzstd-devel -RUN apt-get install -y zlib-devel -RUN apt-get install -y liblz4-devel -RUN apt-get install -y libzstd-devel -RUN apt-get install -y libxml2-devel +RUN apt-get update && apt-get install -y \ + gcc \ + make \ + meson \ + flex \ + bison \ + pkg-config \ + libssl-devel \ + libicu-devel \ + libzstd-devel \ + zlib-devel \ + liblz4-devel \ + libzstd-devel \ + libxml2-devel \ + && apt-get clean # --------------------------------------------- postgres FROM base1_with_dev_tools AS base1_with_pg-17 @@ -61,48 +57,83 @@ RUN ln -s /pg/postgres/install/lib/libpq.so.5.17 /usr/lib64/libpq.so.5 # --------------------------------------------- base2_with_python-3 FROM base1_with_pg-${PG_VERSION} AS base2_with_python-3 -RUN apt-get install -y python3 -RUN apt-get install -y python3-dev -RUN apt-get install -y python3-modules-sqlite3 + +RUN apt-get update && apt-get install -y \ + python3 \ + python3-dev \ + python3-modules-sqlite3 \ + && apt-get clean ENV PYTHON_BINARY=python3 # --------------------------------------------- final FROM base2_with_python-${PYTHON_VERSION} AS final +EXPOSE 22 +RUN ssh-keygen -A + RUN adduser test -G wheel # It enables execution of "sudo service ssh start" without password RUN echo "test ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers -ADD --chown=test:test . /home/test/testgres +# +# Altlinux 10 and 11 too slowly create a new SSH connection (x6). +# +# AI: SPEED UP SSH 6 TIMES (REMOVE REVERSE DNS LOOKUP TIMEOUTS) +# +RUN echo "UseDNS no" >> /etc/openssh/sshd_config + +COPY --chown=test:test . /home/test/testgres WORKDIR /home/test/testgres -RUN mkdir /home/test/testgres/logs -RUN chown -R test:test /home/test/testgres/logs ENV LANG=C.UTF-8 -USER test - -RUN chmod 700 ~/ -RUN mkdir -p ~/.ssh +RUN chmod 700 /home/test/ && \ + mkdir -p /home/test/.ssh && \ + chown -R test:test /home/test/.ssh # -# Altlinux 10 and 11 too slowly create a new SSH connection (x6). +# \"$@\" +# - quote is important! +# - "DUMMY-DUMMY-DUMMY" will be ignored. Do not ask me "why?". AXEZ. # - -ENTRYPOINT sh -c " \ +ENTRYPOINT ["sh", "-c", " \ + set -eux; \ + echo 'SYSTEM START: PREPARING SSH'; \ + /usr/sbin/sshd; \ + ls -la /home/test/.ssh/; \ + \"$@\" \ +", "DUMMY-DUMMY-DUMMY"] + +# Run tests by default (master machine role) +CMD ["bash", "-c", " \ set -eux; \ -echo HELLO FROM ENTRYPOINT; \ -echo HOME DIR IS [`realpath ~/`]; \ -ls -la .; \ -sudo /usr/sbin/sshd; \ -sudo chmod 777 /home/test/testgres/logs; \ -ls -la . | grep logs; \ -ssh-keyscan -H localhost >> ~/.ssh/known_hosts; \ -ssh-keyscan -H 127.0.0.1 >> ~/.ssh/known_hosts; \ -ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -N ''; \ -cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys; \ -chmod 600 ~/.ssh/authorized_keys; \ -ls -la ~/.ssh/; \ -TEST_FILTER=\"\" bash ./run_tests.sh;" +echo \"HOME DIR IS [`realpath ~/`]\"; \ +echo \"WORK DIR IS [$(pwd)]\"; \ +if [ ! -f /home/test/.ssh/id_rsa ]; then \ + su -c \"ssh-keygen -t rsa -f /home/test/.ssh/id_rsa -q -N ''\" test; \ + su -c \"cat /home/test/.ssh/id_rsa.pub >> /home/test/.ssh/authorized_keys\" test; \ + chmod 600 /home/test/.ssh/authorized_keys; \ +fi; \ +ls -la /home/test/.ssh/; \ +su -c \"ssh-keyscan -H localhost >> /home/test/.ssh/known_hosts\" test; \ +su -c \"ssh-keyscan -H 127.0.0.1 >> /home/test/.ssh/known_hosts\" test; \ +if [ -n \"${TEST_CFG__REMOTE_HOST:-}\" ]; then \ + su -c \"ssh-keyscan -H ${TEST_CFG__REMOTE_HOST} >> /home/test/.ssh/known_hosts\" test; \ +fi; \ +if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ + cp \"${TEST_CFG__REMOTE_SSH_KEY}\" \"${TEST_CFG__REMOTE_SSH_KEY}_ci\"; \ + export TEST_CFG__REMOTE_SSH_KEY=\"${TEST_CFG__REMOTE_SSH_KEY}_ci\"; \ + chown test:test \"${TEST_CFG__REMOTE_SSH_KEY}\"; \ + chmod 600 \"${TEST_CFG__REMOTE_SSH_KEY}\"; \ + ls -la /home/test/.ssh/; \ +fi; \ +ls -la ./; \ +# AUTOMATIC TRANSFER: Save all TEST_CFG__xxx variables to a temporary file \ +(env | grep -E '^(TEST_CFG__|PYTHON_BINARY)' | sed 's/^/export /' || echo '') > ./test_cfg_env.sh; \ +chown test:test ./test_cfg_env.sh; \ +cat ./test_cfg_env.sh; \ +# Run su -, pull in the file with variables and start the tests \ +su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='app or prove' bash ./run_tests.sh\"; \ +"] diff --git a/Dockerfile--altlinux_11.tmpl b/Dockerfile--altlinux_11.tmpl index 485f42f3..6685cab9 100644 --- a/Dockerfile--altlinux_11.tmpl +++ b/Dockerfile--altlinux_11.tmpl @@ -4,44 +4,38 @@ ARG PYTHON_VERSION=3 # --------------------------------------------- base1 FROM alt:p11 AS base1 -RUN apt-get update -RUN apt-get install -y sudo curl ca-certificates -RUN apt-get update -RUN apt-get install -y openssh-server openssh-clients -RUN apt-get install -y time - -# pgrep (for testgres.os_ops) -RUN apt-get install -y procps - -# RUN apt-get install -y mc - -RUN apt-get install -y libsqlite3-devel - -EXPOSE 22 - -RUN ssh-keygen -A +RUN apt-get update && apt-get install -y \ + sudo \ + curl \ + ca-certificates \ + openssh-server \ + openssh-clients \ + sshpass \ + time \ + su \ + git \ + procps \ + libsqlite3-devel \ + && apt-get clean # --------------------------------------------- postgres FROM base1 AS base1_with_dev_tools -RUN apt-get update - -RUN apt-get install -y git -RUN apt-get install -y gcc -RUN apt-get install -y make - -RUN apt-get install -y meson -RUN apt-get install -y flex -RUN apt-get install -y bison - -RUN apt-get install -y pkg-config -RUN apt-get install -y libssl-devel -RUN apt-get install -y libicu-devel -RUN apt-get install -y libzstd-devel -RUN apt-get install -y zlib-devel -RUN apt-get install -y liblz4-devel -RUN apt-get install -y libzstd-devel -RUN apt-get install -y libxml2-devel +RUN apt-get update && apt-get install -y \ + gcc \ + make \ + meson \ + flex \ + bison \ + pkg-config \ + libssl-devel \ + libicu-devel \ + libzstd-devel \ + zlib-devel \ + liblz4-devel \ + libzstd-devel \ + libxml2-devel \ + && apt-get clean # --------------------------------------------- postgres FROM base1_with_dev_tools AS base1_with_pg-17 @@ -64,48 +58,83 @@ RUN ln -s /pg/postgres/install/lib/libpq.so.5.17 /usr/lib64/libpq.so.5 # --------------------------------------------- base2_with_python-3 FROM base1_with_pg-${PG_VERSION} AS base2_with_python-3 -RUN apt-get install -y python3 -RUN apt-get install -y python3-dev -RUN apt-get install -y python3-modules-sqlite3 + +RUN apt-get update && apt-get install -y \ + python3 \ + python3-dev \ + python3-modules-sqlite3 \ + && apt-get clean ENV PYTHON_BINARY=python3 # --------------------------------------------- final FROM base2_with_python-${PYTHON_VERSION} AS final +EXPOSE 22 +RUN ssh-keygen -A + RUN adduser test -G wheel # It enables execution of "sudo service ssh start" without password RUN echo "test ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers -ADD --chown=test:test . /home/test/testgres +# +# Altlinux 10 and 11 too slowly create a new SSH connection (x6). +# +# AI: SPEED UP SSH 6 TIMES (REMOVE REVERSE DNS LOOKUP TIMEOUTS) +# +RUN echo "UseDNS no" >> /etc/openssh/sshd_config + +COPY --chown=test:test . /home/test/testgres WORKDIR /home/test/testgres -RUN mkdir /home/test/testgres/logs -RUN chown -R test:test /home/test/testgres/logs ENV LANG=C.UTF-8 -USER test - -RUN chmod 700 ~/ -RUN mkdir -p ~/.ssh +RUN chmod 700 /home/test/ && \ + mkdir -p /home/test/.ssh && \ + chown -R test:test /home/test/.ssh # -# Altlinux 10 and 11 too slowly create a new SSH connection (x6). +# \"$@\" +# - quote is important! +# - "DUMMY-DUMMY-DUMMY" will be ignored. Do not ask me "why?". AXEZ. # - -ENTRYPOINT sh -c " \ +ENTRYPOINT ["sh", "-c", " \ + set -eux; \ + echo 'SYSTEM START: PREPARING SSH'; \ + /usr/sbin/sshd; \ + ls -la /home/test/.ssh/; \ + \"$@\" \ +", "DUMMY-DUMMY-DUMMY"] + +# Run tests by default (master machine role) +CMD ["bash", "-c", " \ set -eux; \ -echo HELLO FROM ENTRYPOINT; \ -echo HOME DIR IS [`realpath ~/`]; \ -ls -la .; \ -sudo /usr/sbin/sshd; \ -sudo chmod 777 /home/test/testgres/logs; \ -ls -la . | grep logs; \ -ssh-keyscan -H localhost >> ~/.ssh/known_hosts; \ -ssh-keyscan -H 127.0.0.1 >> ~/.ssh/known_hosts; \ -ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -N ''; \ -cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys; \ -chmod 600 ~/.ssh/authorized_keys; \ -ls -la ~/.ssh/; \ -TEST_FILTER=\"\" bash ./run_tests.sh;" +echo \"HOME DIR IS [`realpath ~/`]\"; \ +echo \"WORK DIR IS [$(pwd)]\"; \ +if [ ! -f /home/test/.ssh/id_rsa ]; then \ + su -c \"ssh-keygen -t rsa -f /home/test/.ssh/id_rsa -q -N ''\" test; \ + su -c \"cat /home/test/.ssh/id_rsa.pub >> /home/test/.ssh/authorized_keys\" test; \ + chmod 600 /home/test/.ssh/authorized_keys; \ +fi; \ +ls -la /home/test/.ssh/; \ +su -c \"ssh-keyscan -H localhost >> /home/test/.ssh/known_hosts\" test; \ +su -c \"ssh-keyscan -H 127.0.0.1 >> /home/test/.ssh/known_hosts\" test; \ +if [ -n \"${TEST_CFG__REMOTE_HOST:-}\" ]; then \ + su -c \"ssh-keyscan -H ${TEST_CFG__REMOTE_HOST} >> /home/test/.ssh/known_hosts\" test; \ +fi; \ +if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ + cp \"${TEST_CFG__REMOTE_SSH_KEY}\" \"${TEST_CFG__REMOTE_SSH_KEY}_ci\"; \ + export TEST_CFG__REMOTE_SSH_KEY=\"${TEST_CFG__REMOTE_SSH_KEY}_ci\"; \ + chown test:test \"${TEST_CFG__REMOTE_SSH_KEY}\"; \ + chmod 600 \"${TEST_CFG__REMOTE_SSH_KEY}\"; \ + ls -la /home/test/.ssh/; \ +fi; \ +ls -la ./; \ +# AUTOMATIC TRANSFER: Save all TEST_CFG__xxx variables to a temporary file \ +(env | grep -E '^(TEST_CFG__|PYTHON_BINARY)' | sed 's/^/export /' || echo '') > ./test_cfg_env.sh; \ +chown test:test ./test_cfg_env.sh; \ +cat ./test_cfg_env.sh; \ +# Run su -, pull in the file with variables and start the tests \ +su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='app or prove' bash ./run_tests.sh\"; \ +"] From 2d715ff5c91fe9080582f1b2470067efa0e93bc6 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 11:10:22 +0300 Subject: [PATCH 13/41] CI2M: all the stages and platforms are restored --- .github/workflows/ci.yml | 104 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e71e2ca..5fc1625a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,12 +15,116 @@ on: - "**/*.md" jobs: + lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + + steps: + - name: Checkout + uses: actions/checkout@v7 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flake8 flake8-pyproject ruff + - name: Lint with flake8 + run: | + flake8 . + - name: Lint with ruff + run: | + ruff check . + + build-check: + runs-on: ubuntu-latest + needs: lint + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + cache: 'pip' + + - name: Install build tools + run: | + python -m pip install --upgrade pip + python -m pip install build twine + + - name: Build package + run: | + python -m build + + - name: Check package metadata + run: | + twine check dist/* + test: runs-on: ubuntu-latest + needs: build-check strategy: fail-fast: false matrix: include: + - platform: "std2-all" + python: "3.7" + postgres: "17" + - platform: "std2-all" + python: "3.8.0" + postgres: "17" + - platform: "std2-all" + python: "3.8" + postgres: "17" + - platform: "std2-all" + python: "3.9" + postgres: "17" + - platform: "std2-all" + python: "3.10" + postgres: "17" + - platform: "std2-all" + python: "3.11" + postgres: "17" + - platform: "std2-all" + python: "3.12" + postgres: "17" + - platform: "std2-all" + python: "3.13" + postgres: "17" + - platform: "std2-all" + python: "3.14" + postgres: "17" + - platform: "std2-all" + python: "3.12" + postgres: "10" + - platform: "std2-all" + python: "3.12" + postgres: "11" + - platform: "std2-all" + python: "3.12" + postgres: "12" + - platform: "std2-all" + python: "3.12" + postgres: "13" + - platform: "std2-all" + python: "3.12" + postgres: "14" + - platform: "std2-all" + python: "3.12" + postgres: "15" + - platform: "std2-all" + python: "3.12" + postgres: "16" + - platform: "std2-all" + python: "3.12" + postgres: "18" - platform: "ubuntu_24_04" python: "3" postgres: "17" From a941463628185eceb2dec21aa271842b40f248cd Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 11:16:07 +0300 Subject: [PATCH 14/41] CI2M: std2-all -> alpine --- .github/workflows/ci.yml | 34 +++++++++---------- ...--std2-all.tmpl => Dockerfile--alpine.tmpl | 0 2 files changed, 17 insertions(+), 17 deletions(-) rename Dockerfile--std2-all.tmpl => Dockerfile--alpine.tmpl (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5fc1625a..66d22d8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,55 +74,55 @@ jobs: fail-fast: false matrix: include: - - platform: "std2-all" + - platform: "alpine" python: "3.7" postgres: "17" - - platform: "std2-all" + - platform: "alpine" python: "3.8.0" postgres: "17" - - platform: "std2-all" + - platform: "alpine" python: "3.8" postgres: "17" - - platform: "std2-all" + - platform: "alpine" python: "3.9" postgres: "17" - - platform: "std2-all" + - platform: "alpine" python: "3.10" postgres: "17" - - platform: "std2-all" + - platform: "alpine" python: "3.11" postgres: "17" - - platform: "std2-all" + - platform: "alpine" python: "3.12" postgres: "17" - - platform: "std2-all" + - platform: "alpine" python: "3.13" postgres: "17" - - platform: "std2-all" + - platform: "alpine" python: "3.14" postgres: "17" - - platform: "std2-all" + - platform: "alpine" python: "3.12" postgres: "10" - - platform: "std2-all" + - platform: "alpine" python: "3.12" postgres: "11" - - platform: "std2-all" + - platform: "alpine" python: "3.12" postgres: "12" - - platform: "std2-all" + - platform: "alpine" python: "3.12" postgres: "13" - - platform: "std2-all" + - platform: "alpine" python: "3.12" postgres: "14" - - platform: "std2-all" + - platform: "alpine" python: "3.12" postgres: "15" - - platform: "std2-all" + - platform: "alpine" python: "3.12" postgres: "16" - - platform: "std2-all" + - platform: "alpine" python: "3.12" postgres: "18" - platform: "ubuntu_24_04" diff --git a/Dockerfile--std2-all.tmpl b/Dockerfile--alpine.tmpl similarity index 100% rename from Dockerfile--std2-all.tmpl rename to Dockerfile--alpine.tmpl From 001cbd15ed3724ae12e2ca8a38708d36d2451ca8 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 11:35:09 +0300 Subject: [PATCH 15/41] CI2M: case_suffix is added (to human names in CI console) --- .github/workflows/ci.yml | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66d22d8b..39076c41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,69 +77,92 @@ jobs: - platform: "alpine" python: "3.7" postgres: "17" + case_suffix: "py3_07_xx-pg17_xx" - platform: "alpine" python: "3.8.0" postgres: "17" + case_suffix: "py3_08_88-pg17_xx" - platform: "alpine" python: "3.8" postgres: "17" + case_suffix: "py3_08_xx-pg17_xx" - platform: "alpine" python: "3.9" postgres: "17" + case_suffix: "py3_09_xx-pg17_xx" - platform: "alpine" python: "3.10" postgres: "17" + case_suffix: "py3_10_xx-pg17_xx" - platform: "alpine" python: "3.11" postgres: "17" + case_suffix: "py3_11_xx-pg17_xx" - platform: "alpine" python: "3.12" postgres: "17" + case_suffix: "py3_12_xx-pg17_xx" - platform: "alpine" python: "3.13" postgres: "17" + case_suffix: "py3_13_xx-pg17_xx" - platform: "alpine" python: "3.14" postgres: "17" + case_suffix: "py3_14_xx-pg17_xx" - platform: "alpine" python: "3.12" postgres: "10" + case_suffix: "py3_12_xx-pg10_xx" - platform: "alpine" python: "3.12" postgres: "11" + case_suffix: "py3_12_xx-pg11_xx" - platform: "alpine" python: "3.12" postgres: "12" + case_suffix: "py3_12_xx-pg12_xx" - platform: "alpine" python: "3.12" postgres: "13" + case_suffix: "py3_12_xx-pg13_xx" - platform: "alpine" python: "3.12" postgres: "14" + case_suffix: "py3_12_xx-pg14_xx" - platform: "alpine" python: "3.12" postgres: "15" + case_suffix: "py3_12_xx-pg15_xx" - platform: "alpine" python: "3.12" postgres: "16" + case_suffix: "py3_12_xx-pg16_xx" - platform: "alpine" python: "3.12" postgres: "18" + case_suffix: "py3_12_xx-pg18_xx" - platform: "ubuntu_24_04" python: "3" postgres: "17" + case_suffix: "py3_xx_xx-pg17_xx" - platform: "altlinux_10" python: "3" postgres: "17" + case_suffix: "py3_xx_xx-pg17_xx" - platform: "altlinux_11" python: "3" postgres: "17" + case_suffix: "py3_xx_xx-pg17_xx" - platform: "astralinux_1_7" python: "3" postgres: "17" + case_suffix: "py3_xx_xx-pg17_xx" + + name: "test: ${{ matrix.platform }} | ${{ matrix.case_suffix }}" env: - BASE_SIGN: "${{ matrix.platform }}-py${{ matrix.python }}-pg${{ matrix.postgres }}" + BASE_SIGN: "${{ matrix.platform }}-${{ matrix.case_suffix }}" steps: - name: Prepare variables From 7f0248f2f5b9631221c229339e78b3baf6b7fd48 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 11:45:26 +0300 Subject: [PATCH 16/41] CI2M: all the tests are restored --- Dockerfile--alpine.tmpl | 2 +- Dockerfile--altlinux_10.tmpl | 2 +- Dockerfile--altlinux_11.tmpl | 2 +- Dockerfile--astralinux_1_7.tmpl | 2 +- Dockerfile--ubuntu_24_04.tmpl | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Dockerfile--alpine.tmpl b/Dockerfile--alpine.tmpl index acb1c849..f2e41dd0 100644 --- a/Dockerfile--alpine.tmpl +++ b/Dockerfile--alpine.tmpl @@ -140,5 +140,5 @@ if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ ls -la /home/test/.ssh/; \ fi; \ ls -la ./; \ -su test -c \"TEST_FILTER='app or prove' PATH=\"/home/test/.pyenv/bin:$PATH\" bash ./run_tests2.sh\"; \ +su test -c \"TEST_FILTER='' PATH=\"/home/test/.pyenv/bin:$PATH\" bash ./run_tests2.sh\"; \ "] diff --git a/Dockerfile--altlinux_10.tmpl b/Dockerfile--altlinux_10.tmpl index 31733dc5..b834ee30 100644 --- a/Dockerfile--altlinux_10.tmpl +++ b/Dockerfile--altlinux_10.tmpl @@ -135,5 +135,5 @@ ls -la ./; \ chown test:test ./test_cfg_env.sh; \ cat ./test_cfg_env.sh; \ # Run su -, pull in the file with variables and start the tests \ -su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='app or prove' bash ./run_tests.sh\"; \ +su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='' bash ./run_tests.sh\"; \ "] diff --git a/Dockerfile--altlinux_11.tmpl b/Dockerfile--altlinux_11.tmpl index 6685cab9..21bd4ae6 100644 --- a/Dockerfile--altlinux_11.tmpl +++ b/Dockerfile--altlinux_11.tmpl @@ -136,5 +136,5 @@ ls -la ./; \ chown test:test ./test_cfg_env.sh; \ cat ./test_cfg_env.sh; \ # Run su -, pull in the file with variables and start the tests \ -su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='app or prove' bash ./run_tests.sh\"; \ +su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='' bash ./run_tests.sh\"; \ "] diff --git a/Dockerfile--astralinux_1_7.tmpl b/Dockerfile--astralinux_1_7.tmpl index fb0cb943..e6f71cf5 100644 --- a/Dockerfile--astralinux_1_7.tmpl +++ b/Dockerfile--astralinux_1_7.tmpl @@ -135,5 +135,5 @@ if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ ls -la /home/test/.ssh/; \ fi; \ ls -la ./; \ -su test -c \"TEST_FILTER='app or prove' bash ./run_tests.sh\"; \ +su test -c \"TEST_FILTER='' bash ./run_tests.sh\"; \ "] diff --git a/Dockerfile--ubuntu_24_04.tmpl b/Dockerfile--ubuntu_24_04.tmpl index a18d3399..a7aec09d 100644 --- a/Dockerfile--ubuntu_24_04.tmpl +++ b/Dockerfile--ubuntu_24_04.tmpl @@ -99,5 +99,5 @@ if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ ls -la /home/test/.ssh/; \ fi; \ ls -la ./; \ -su test -c \"TEST_FILTER='app or prove' bash ./run_tests.sh\"; \ +su test -c \"TEST_FILTER='' bash ./run_tests.sh\"; \ "] From 577f9ba4c5ec4673d7b5928d0e75b0a3e2a1d483 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 15:06:37 +0300 Subject: [PATCH 17/41] fix: Detect and process zombie state of postmaster CI: runs only 'kill' tests. --- Dockerfile--alpine.tmpl | 2 +- Dockerfile--altlinux_10.tmpl | 2 +- Dockerfile--altlinux_11.tmpl | 2 +- Dockerfile--astralinux_1_7.tmpl | 2 +- Dockerfile--ubuntu_24_04.tmpl | 2 +- src/enums.py | 2 +- src/impl/platforms/internal_platform_utils.py | 9 ++++ .../linux/internal_platform_utils.py | 51 +++++++++++++++++++ .../win32/internal_platform_utils.py | 10 ++++ src/utils.py | 31 ++++++++--- tests/test_testgres_common.py | 20 +++++++- 11 files changed, 117 insertions(+), 16 deletions(-) diff --git a/Dockerfile--alpine.tmpl b/Dockerfile--alpine.tmpl index f2e41dd0..9db8c046 100644 --- a/Dockerfile--alpine.tmpl +++ b/Dockerfile--alpine.tmpl @@ -140,5 +140,5 @@ if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ ls -la /home/test/.ssh/; \ fi; \ ls -la ./; \ -su test -c \"TEST_FILTER='' PATH=\"/home/test/.pyenv/bin:$PATH\" bash ./run_tests2.sh\"; \ +su test -c \"TEST_FILTER='kill' PATH=\"/home/test/.pyenv/bin:$PATH\" bash ./run_tests2.sh\"; \ "] diff --git a/Dockerfile--altlinux_10.tmpl b/Dockerfile--altlinux_10.tmpl index b834ee30..46650b56 100644 --- a/Dockerfile--altlinux_10.tmpl +++ b/Dockerfile--altlinux_10.tmpl @@ -135,5 +135,5 @@ ls -la ./; \ chown test:test ./test_cfg_env.sh; \ cat ./test_cfg_env.sh; \ # Run su -, pull in the file with variables and start the tests \ -su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='' bash ./run_tests.sh\"; \ +su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='kill' bash ./run_tests.sh\"; \ "] diff --git a/Dockerfile--altlinux_11.tmpl b/Dockerfile--altlinux_11.tmpl index 21bd4ae6..c21eb2c8 100644 --- a/Dockerfile--altlinux_11.tmpl +++ b/Dockerfile--altlinux_11.tmpl @@ -136,5 +136,5 @@ ls -la ./; \ chown test:test ./test_cfg_env.sh; \ cat ./test_cfg_env.sh; \ # Run su -, pull in the file with variables and start the tests \ -su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='' bash ./run_tests.sh\"; \ +su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='kill' bash ./run_tests.sh\"; \ "] diff --git a/Dockerfile--astralinux_1_7.tmpl b/Dockerfile--astralinux_1_7.tmpl index e6f71cf5..2e7ece18 100644 --- a/Dockerfile--astralinux_1_7.tmpl +++ b/Dockerfile--astralinux_1_7.tmpl @@ -135,5 +135,5 @@ if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ ls -la /home/test/.ssh/; \ fi; \ ls -la ./; \ -su test -c \"TEST_FILTER='' bash ./run_tests.sh\"; \ +su test -c \"TEST_FILTER='kill' bash ./run_tests.sh\"; \ "] diff --git a/Dockerfile--ubuntu_24_04.tmpl b/Dockerfile--ubuntu_24_04.tmpl index a7aec09d..230bb478 100644 --- a/Dockerfile--ubuntu_24_04.tmpl +++ b/Dockerfile--ubuntu_24_04.tmpl @@ -99,5 +99,5 @@ if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ ls -la /home/test/.ssh/; \ fi; \ ls -la ./; \ -su test -c \"TEST_FILTER='' bash ./run_tests.sh\"; \ +su test -c \"TEST_FILTER='kill' bash ./run_tests.sh\"; \ "] diff --git a/src/enums.py b/src/enums.py index d07d8068..a483c5b4 100644 --- a/src/enums.py +++ b/src/enums.py @@ -29,7 +29,7 @@ class NodeStatus(IntEnum): Status of a PostgresNode """ - Running, Stopped, Uninitialized = range(3) + Running, Stopped, Uninitialized, Zombie = range(4) # for Python 3.x def __bool__(self): diff --git a/src/impl/platforms/internal_platform_utils.py b/src/impl/platforms/internal_platform_utils.py index 6af2b7b0..3ae684ed 100644 --- a/src/impl/platforms/internal_platform_utils.py +++ b/src/impl/platforms/internal_platform_utils.py @@ -60,3 +60,12 @@ def FindPostmaster( assert type(bin_dir) is str assert type(data_dir) is str raise NotImplementedError("InternalPlatformUtils::FindPostmaster is not implemented.") + + def ProcessIsZombi_soft_check( + self, + os_ops: OsOperations, + pid: int, + ) -> typing.Optional[bool]: + assert isinstance(os_ops, OsOperations) + assert type(pid) is int + raise NotImplementedError("InternalPlatformUtils::ProcessIsZombi_soft_ver is not implemented.") diff --git a/src/impl/platforms/linux/internal_platform_utils.py b/src/impl/platforms/linux/internal_platform_utils.py index b8ca8588..b0ed64c3 100644 --- a/src/impl/platforms/linux/internal_platform_utils.py +++ b/src/impl/platforms/linux/internal_platform_utils.py @@ -8,6 +8,7 @@ import re import shlex +import typing class InternalPlatformUtils(base.InternalPlatformUtils): @@ -118,3 +119,53 @@ def is_space_or_tab(ch) -> bool: assert type(pid) is int return __class__.FindPostmasterResult.create_ok(pid) + + def ProcessIsZombi_soft_check( + self, + os_ops: OsOperations, + pid: int, + ) -> typing.Optional[bool]: + assert isinstance(os_ops, OsOperations) + assert type(pid) is int + + proc_stat_file = os_ops.build_path("/proc", str(pid), "stat") + + if not os_ops.path_exists(proc_stat_file): + return False + + result: typing.Optional[bool] = None + + try: + # Read one line from /proc/PID/stat + stat_content = os_ops.read_binary(proc_stat_file, 0).decode("utf-8", errors="ignore") + + # We look for the closing parenthesis of the process name to ensure that + # we start from it and not depend on spaces inside the parentheses! + r_paren_idx = stat_content.rfind(")") + + if r_paren_idx == -1: + pass + elif len(stat_content) <= r_paren_idx + 2: + pass + else: + # The status goes exactly one space after the closing bracket + assert (r_paren_idx + 2) < len(stat_content) + proc_status = stat_content[r_paren_idx + 2] + result = proc_status == "Z" + except Exception as e: + # If the file disappeared right during reading, it means the process is completely erased + if __class__._is_file_not_found_exception(e): + result = False + + return result + + @staticmethod + def _is_file_not_found_exception(e: Exception) -> bool: + if isinstance(e, FileNotFoundError): + return True + + if isinstance(e, ExecUtilException): + if e.exit_code == 2: + return True + + return False diff --git a/src/impl/platforms/win32/internal_platform_utils.py b/src/impl/platforms/win32/internal_platform_utils.py index 846950f0..c6df6378 100644 --- a/src/impl/platforms/win32/internal_platform_utils.py +++ b/src/impl/platforms/win32/internal_platform_utils.py @@ -2,6 +2,7 @@ from .. import internal_platform_utils as base from testgres.operations.os_ops import OsOperations +import typing class InternalPlatformUtils(base.InternalPlatformUtils): @@ -15,3 +16,12 @@ def FindPostmaster( assert type(bin_dir) is str assert type(data_dir) is str return __class__.FindPostmasterResult.create_not_implemented() + + def ProcessIsZombi_soft_check( + self, + os_ops: OsOperations, + pid: int, + ) -> typing.Optional[bool]: + assert isinstance(os_ops, OsOperations) + assert type(pid) is int + return None diff --git a/src/utils.py b/src/utils.py index 227c6525..036f8f6a 100644 --- a/src/utils.py +++ b/src/utils.py @@ -411,7 +411,21 @@ def get_pg_node_state( attempt = 0 sleep_time = C_SLEEP_TIME1 - platform_utils: typing.Optional[internal_platform_utils_factory.InternalPlatformUtils] = None + class tagPlaformUtilsProvider: + _platform_utils: typing.Optional[internal_platform_utils_factory.InternalPlatformUtils] = None + + def __init__(self): + self._platform_utils = None + + def get(self) -> internal_platform_utils_factory.InternalPlatformUtils: + if self._platform_utils is None: + self._platform_utils = internal_platform_utils_factory.create_internal_platform_utils(os_ops) + assert isinstance(self._platform_utils, internal_platform_utils_factory.InternalPlatformUtils) + + assert isinstance(self._platform_utils, internal_platform_utils_factory.InternalPlatformUtils) + return self._platform_utils + + platform_utils_provider = tagPlaformUtilsProvider() while True: assert type(attempt) is int @@ -513,6 +527,13 @@ def get_pg_node_state( assert pid != 0 + # ----------------- detect zombie + if platform_utils_provider.get().ProcessIsZombi_soft_check(os_ops, pid) is True: + internal_utils.send_log_debug("Postmaster process {} is a zombie.".format( + pid, + )) + return PostgresNodeState(NodeStatus.Zombie, pid) + # ----------------- return PostgresNodeState(NodeStatus.Running, pid) @@ -543,14 +564,8 @@ def get_pg_node_state( bin_dir, )) - if platform_utils is None: - platform_utils = internal_platform_utils_factory.create_internal_platform_utils(os_ops) - assert isinstance(platform_utils, internal_platform_utils_factory.InternalPlatformUtils) - - assert isinstance(platform_utils, internal_platform_utils_factory.InternalPlatformUtils) - try: - find_postmaster_r = platform_utils.FindPostmaster( + find_postmaster_r = platform_utils_provider.get().FindPostmaster( os_ops, bin_dir, data_dir, diff --git a/tests/test_testgres_common.py b/tests/test_testgres_common.py index 883fcf67..575f8fb4 100644 --- a/tests/test_testgres_common.py +++ b/tests/test_testgres_common.py @@ -687,7 +687,9 @@ def test_kill__ok( ): assert isinstance(node_svc, PostgresNodeService) - with __class__.helper__get_node(node_svc) as node: + node = __class__.helper__get_node(node_svc) + + try: assert isinstance(node, PostgresNode) assert (node.pid == 0) assert (node.status() == NodeStatus.Uninitialized) @@ -696,6 +698,9 @@ def test_kill__ok( assert not node.is_started node.slow_start() assert node.is_started + + assert node.status() == NodeStatus.Running + node.kill() assert not node.is_started @@ -717,8 +722,19 @@ def test_kill__ok( if s == NodeStatus.Running: continue - assert s == NodeStatus.Stopped + if s == NodeStatus.Stopped: + logging.info("Node stopped") + break + + if s == NodeStatus.Zombie: + logging.info("Node is zombie") + break + + logging.error("Node has unknown status: {}.".format(s.name)) break + finally: + if node.is_started: + node.stop() return def test_kill_backgroud_writer__ok( From f920a2587bc2bfbd78bc8fb026613b84d1ddcae8 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 15:21:59 +0300 Subject: [PATCH 18/41] CI2M: (fix ubuntu) user 'test' is added into 'postgres' group It allows to create files in '/var/run/postgresql' folder. --- Dockerfile--ubuntu_24_04.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile--ubuntu_24_04.tmpl b/Dockerfile--ubuntu_24_04.tmpl index 230bb478..9c2311d6 100644 --- a/Dockerfile--ubuntu_24_04.tmpl +++ b/Dockerfile--ubuntu_24_04.tmpl @@ -48,7 +48,7 @@ FROM base2_with_python-${PYTHON_VERSION} AS final EXPOSE 22 RUN ssh-keygen -A -RUN useradd -m test +RUN useradd -m test && usermod -aG postgres test # It enables execution of "sudo service ssh start" without password RUN echo "test ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers From d77a34f178aab306e50674f69883e9e99c874822 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 15:46:58 +0300 Subject: [PATCH 19/41] CI2M: all the tests are restored --- Dockerfile--alpine.tmpl | 2 +- Dockerfile--altlinux_10.tmpl | 2 +- Dockerfile--astralinux_1_7.tmpl | 2 +- Dockerfile--ubuntu_24_04.tmpl | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile--alpine.tmpl b/Dockerfile--alpine.tmpl index 9db8c046..f2e41dd0 100644 --- a/Dockerfile--alpine.tmpl +++ b/Dockerfile--alpine.tmpl @@ -140,5 +140,5 @@ if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ ls -la /home/test/.ssh/; \ fi; \ ls -la ./; \ -su test -c \"TEST_FILTER='kill' PATH=\"/home/test/.pyenv/bin:$PATH\" bash ./run_tests2.sh\"; \ +su test -c \"TEST_FILTER='' PATH=\"/home/test/.pyenv/bin:$PATH\" bash ./run_tests2.sh\"; \ "] diff --git a/Dockerfile--altlinux_10.tmpl b/Dockerfile--altlinux_10.tmpl index 46650b56..b834ee30 100644 --- a/Dockerfile--altlinux_10.tmpl +++ b/Dockerfile--altlinux_10.tmpl @@ -135,5 +135,5 @@ ls -la ./; \ chown test:test ./test_cfg_env.sh; \ cat ./test_cfg_env.sh; \ # Run su -, pull in the file with variables and start the tests \ -su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='kill' bash ./run_tests.sh\"; \ +su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='' bash ./run_tests.sh\"; \ "] diff --git a/Dockerfile--astralinux_1_7.tmpl b/Dockerfile--astralinux_1_7.tmpl index 2e7ece18..e6f71cf5 100644 --- a/Dockerfile--astralinux_1_7.tmpl +++ b/Dockerfile--astralinux_1_7.tmpl @@ -135,5 +135,5 @@ if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ ls -la /home/test/.ssh/; \ fi; \ ls -la ./; \ -su test -c \"TEST_FILTER='kill' bash ./run_tests.sh\"; \ +su test -c \"TEST_FILTER='' bash ./run_tests.sh\"; \ "] diff --git a/Dockerfile--ubuntu_24_04.tmpl b/Dockerfile--ubuntu_24_04.tmpl index 9c2311d6..d150ac4a 100644 --- a/Dockerfile--ubuntu_24_04.tmpl +++ b/Dockerfile--ubuntu_24_04.tmpl @@ -99,5 +99,5 @@ if [ -n \"${TEST_CFG__REMOTE_SSH_KEY:-}\" ]; then \ ls -la /home/test/.ssh/; \ fi; \ ls -la ./; \ -su test -c \"TEST_FILTER='kill' bash ./run_tests.sh\"; \ +su test -c \"TEST_FILTER='' bash ./run_tests.sh\"; \ "] From e5f23476d9782f7ff7c946caa113eb375d3eb901 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 18:42:32 +0300 Subject: [PATCH 20/41] get_pg_node_state is updated (T_PLATFORM_UTILS) Sync with #394. --- src/utils.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/utils.py b/src/utils.py index 036f8f6a..6efd9a49 100644 --- a/src/utils.py +++ b/src/utils.py @@ -412,17 +412,19 @@ def get_pg_node_state( sleep_time = C_SLEEP_TIME1 class tagPlaformUtilsProvider: - _platform_utils: typing.Optional[internal_platform_utils_factory.InternalPlatformUtils] = None + T_PLATFORM_UTILS = internal_platform_utils_factory.InternalPlatformUtils + + _platform_utils: typing.Optional[T_PLATFORM_UTILS] = None def __init__(self): self._platform_utils = None - def get(self) -> internal_platform_utils_factory.InternalPlatformUtils: + def get(self) -> T_PLATFORM_UTILS: if self._platform_utils is None: self._platform_utils = internal_platform_utils_factory.create_internal_platform_utils(os_ops) - assert isinstance(self._platform_utils, internal_platform_utils_factory.InternalPlatformUtils) + assert isinstance(self._platform_utils, __class__.T_PLATFORM_UTILS) - assert isinstance(self._platform_utils, internal_platform_utils_factory.InternalPlatformUtils) + assert isinstance(self._platform_utils, __class__.T_PLATFORM_UTILS) return self._platform_utils platform_utils_provider = tagPlaformUtilsProvider() From 3f0349e28026b7979a5abed8c810b81331f46bd0 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 18:44:51 +0300 Subject: [PATCH 21/41] impl__test_pg_ctl_wait_option is updated to process zombies We may catch a zombie during a node stop: "FAILED tests/test_testgres_common.py::TestTestgresCommon::test_pg_ctl_wait_option[local] - Exception: Unexpected node status: 3." It was on astralinux_1_7. --- tests/test_testgres_common.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_testgres_common.py b/tests/test_testgres_common.py index 575f8fb4..9be4a778 100644 --- a/tests/test_testgres_common.py +++ b/tests/test_testgres_common.py @@ -1630,9 +1630,15 @@ def impl__test_pg_ctl_wait_option( logging.info("Attempt #{0}.".format(nAttempt)) s1 = node.status() + logging.info("Node status is {}.".format(s1.name)) + if s1 == NodeStatus.Running: continue + if s1 == NodeStatus.Zombie: + # [2026-07-12] We will wait for final stop (stabilization). OK? + continue + if s1 == NodeStatus.Stopped: break From 37de5898d40835f0991cc05ac6eb30960bfd9b8d Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 20:26:00 +0300 Subject: [PATCH 22/41] CI2M: (alpine) SSH limits are increased NEW: MaxAuthTries 20 MaxSessions 500 MaxStartups 2000:30:2000 OLD: #MaxAuthTries 6 #MaxSessions 10 #MaxStartups 10:30:100 NOTE: We change they via find&replace. --- Dockerfile--alpine.tmpl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile--alpine.tmpl b/Dockerfile--alpine.tmpl index f2e41dd0..1cc1f6a2 100644 --- a/Dockerfile--alpine.tmpl +++ b/Dockerfile--alpine.tmpl @@ -14,6 +14,10 @@ RUN apk add --no-cache \ sudo \ git +RUN sed -i 's/#MaxStartups 10:30:100/MaxStartups 2000:30:2000/' /etc/ssh/sshd_config && \ + sed -i 's/#MaxSessions 10/MaxSessions 500/' /etc/ssh/sshd_config && \ + sed -i 's/#MaxAuthTries 6/MaxAuthTries 20/' /etc/ssh/sshd_config + # --------------------------------------------- base2_with_python-3 FROM base1 AS base2_with_python-3 From 1f10354940e613eef32f86a13e0cdda4b8507c77 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 20:57:19 +0300 Subject: [PATCH 23/41] CI2M: TestTestgresRemote::test_init__unk_LANG_and_LC_CTYPE is skipped on PG10. It does not work there. --- tests/test_testgres_remote.py | 40 +++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/tests/test_testgres_remote.py b/tests/test_testgres_remote.py index 33372cc4..2b64ff6e 100755 --- a/tests/test_testgres_remote.py +++ b/tests/test_testgres_remote.py @@ -21,6 +21,8 @@ # NOTE: those are ugly imports +from packaging.version import Version + def util_exists(util): def good_properties(f): @@ -72,6 +74,16 @@ def test_init__unk_LANG_and_LC_CTYPE(self): prev_LC_CTYPE = os.environ.get("LC_CTYPE") prev_LC_COLLATE = os.environ.get("LC_COLLATE") + node = __class__.helper__get_node() + + node_version = node.version + assert node_version is not None + assert type(node_version) is testgres.utils.PgVer + assert isinstance(node_version, Version) + + if node.version < Version("11"): + pytest.skip("This test does not work on old PG10-.") + try: # TODO: Pass unkData through test parameter. unkDatas = [ @@ -106,14 +118,15 @@ def test_init__unk_LANG_and_LC_CTYPE(self): assert os.getenv('LC_CTYPE') == unkData[1] assert os.getenv('LC_COLLATE') is None + node.cleanup() + exc: typing.Optional[BaseException] = None - with __class__.helper__get_node() as node: - try: - node.init() # IT RAISES! - except InitNodeException as e: - exc = e.__cause__ - assert exc is not None - assert isinstance(exc, ExecUtilException) + try: + node.init() # IT RAISES! + except InitNodeException as e: + exc = e.__cause__ + assert exc is not None + assert isinstance(exc, ExecUtilException) if exc is None: logging.warning("We expected an error!") @@ -126,8 +139,17 @@ def test_init__unk_LANG_and_LC_CTYPE(self): errMsg = str(exc) logging.info("Error message is {0}: {1}".format(type(exc).__name__, errMsg)) - assert "warning: setlocale: LC_CTYPE: cannot change locale (" + unkData[1] + ")" in errMsg - assert "initdb: error: invalid locale settings; check LANG and LC_* environment variables" in errMsg + assert type(exc.error) is str + + # Check an optional message from OS. + expectedMsg1 = "warning: setlocale: LC_CTYPE: cannot change locale (" + unkData[1] + ")" + + if expectedMsg1 not in exc.error: + logging.warning("Msg does not contain {!r}.".format(expectedMsg1)) + + # Check a mandatory message from initdb. + expectedMsg2 = "initdb: error: invalid locale settings; check LANG and LC_* environment variables" + assert expectedMsg2 in exc.error continue if not errorIsDetected: From cfd313dfc74c09d0b301ed2fd7e1c09a9840ed24 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 21:38:57 +0300 Subject: [PATCH 24/41] CI2M: (fix) altlinux-11 runs all the tests --- Dockerfile--altlinux_11.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile--altlinux_11.tmpl b/Dockerfile--altlinux_11.tmpl index c21eb2c8..21bd4ae6 100644 --- a/Dockerfile--altlinux_11.tmpl +++ b/Dockerfile--altlinux_11.tmpl @@ -136,5 +136,5 @@ ls -la ./; \ chown test:test ./test_cfg_env.sh; \ cat ./test_cfg_env.sh; \ # Run su -, pull in the file with variables and start the tests \ -su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='kill' bash ./run_tests.sh\"; \ +su - test -c \"cd /home/test/testgres && source ./test_cfg_env.sh && TEST_FILTER='' bash ./run_tests.sh\"; \ "] From 87a402279c3141a90a4c7517a48c97aadb5e8661 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 22:07:17 +0300 Subject: [PATCH 25/41] test_set_env__thread_safety is corrected (slow work) --- tests/test_os_ops_common.py | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/tests/test_os_ops_common.py b/tests/test_os_ops_common.py index c4c9cdde..170ea191 100644 --- a/tests/test_os_ops_common.py +++ b/tests/test_os_ops_common.py @@ -3727,18 +3727,38 @@ def test_set_env__thread_safety( C_NUM_THREADS = 2 - if OsOpsHelpers.is_localhost(os_ops): - C_NUM_ITERATIONS = 2000 - else: + if type(os_ops).__name__ == "RemoteOperations": C_NUM_ITERATIONS = 200 + else: + C_NUM_ITERATIONS = 2000 + + logging.info("NUM_ITERATIONS: {}".format(C_NUM_ITERATIONS)) # Queue for collecting exceptions from background threads exceptions_queue = queue.Queue() # The function that each thread will run - def thread_worker(var_name: str, var_value: str, iterations: int): + def thread_worker( + thread_num: int, + var_name: str, + var_value: str, + iterations: int, + ): + logging.info("Hello from thread [{}].".format( + thread_num, + )) + try: - for _ in range(iterations): + nPass = 0 + while nPass < iterations: + nPass += 1 + + if (nPass % 100) == 0: + logging.info("thread [{}]: {}".format( + thread_num, + nPass, + )) + # 1. The thread writes ITS own isolated variable os_ops.set_env(var_name, var_value) @@ -3755,7 +3775,12 @@ def thread_worker(var_name: str, var_value: str, iterations: int): # 3. Clean up after yourself os_ops.reset_env(var_name, None) assert os_ops.environ(var_name) is None + continue + logging.info("thread [{}] finished ({})".format( + thread_num, + nPass, + )) except Exception as e: # If something goes wrong, we pass the error to the main test thread exceptions_queue.put(e) @@ -3774,7 +3799,7 @@ def thread_worker(var_name: str, var_value: str, iterations: int): threads[i] = threading.Thread( target=thread_worker, - args=(thread_name, thread_val, C_NUM_ITERATIONS), + args=(i, thread_name, thread_val, C_NUM_ITERATIONS), ) continue From 01efb4ee9d70ee7c98ccebb297701e41c5d36633 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 23:07:51 +0300 Subject: [PATCH 26/41] test_set_env__thread_safety is updated --- tests/test_os_ops_common.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_os_ops_common.py b/tests/test_os_ops_common.py index 170ea191..8daa0b81 100644 --- a/tests/test_os_ops_common.py +++ b/tests/test_os_ops_common.py @@ -3751,14 +3751,14 @@ def thread_worker( try: nPass = 0 while nPass < iterations: - nPass += 1 - - if (nPass % 100) == 0: + if nPass > 0 and (nPass % 100) == 0: logging.info("thread [{}]: {}".format( thread_num, nPass, )) + nPass += 1 + # 1. The thread writes ITS own isolated variable os_ops.set_env(var_name, var_value) From b74e1cb4fbecd1ab4c427d0e5eea0fc82ce9c7c8 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 23:08:26 +0300 Subject: [PATCH 27/41] CI2M: ssh limits are inreased (alt, astra, ubuntu) --- Dockerfile--altlinux_10.tmpl | 4 ++++ Dockerfile--altlinux_11.tmpl | 4 ++++ Dockerfile--astralinux_1_7.tmpl | 4 ++++ Dockerfile--ubuntu_24_04.tmpl | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/Dockerfile--altlinux_10.tmpl b/Dockerfile--altlinux_10.tmpl index b834ee30..6eea5fba 100644 --- a/Dockerfile--altlinux_10.tmpl +++ b/Dockerfile--altlinux_10.tmpl @@ -17,6 +17,10 @@ RUN apt-get update && apt-get install -y \ libsqlite3-devel \ && apt-get clean +RUN sed -i 's/#MaxStartups 10:30:100/MaxStartups 2000:30:2000/' /etc/openssh/sshd_config && \ + sed -i 's/#MaxSessions 10/MaxSessions 500/' /etc/openssh/sshd_config && \ + sed -i 's/#MaxAuthTries 6/MaxAuthTries 20/' /etc/openssh/sshd_config + # --------------------------------------------- postgres FROM base1 AS base1_with_dev_tools diff --git a/Dockerfile--altlinux_11.tmpl b/Dockerfile--altlinux_11.tmpl index 21bd4ae6..171ffea1 100644 --- a/Dockerfile--altlinux_11.tmpl +++ b/Dockerfile--altlinux_11.tmpl @@ -18,6 +18,10 @@ RUN apt-get update && apt-get install -y \ libsqlite3-devel \ && apt-get clean +RUN sed -i 's/#MaxStartups 10:30:100/MaxStartups 2000:30:2000/' /etc/openssh/sshd_config && \ + sed -i 's/#MaxSessions 10/MaxSessions 500/' /etc/openssh/sshd_config && \ + sed -i 's/#MaxAuthTries 6/MaxAuthTries 20/' /etc/openssh/sshd_config + # --------------------------------------------- postgres FROM base1 AS base1_with_dev_tools diff --git a/Dockerfile--astralinux_1_7.tmpl b/Dockerfile--astralinux_1_7.tmpl index e6f71cf5..84e599ed 100644 --- a/Dockerfile--astralinux_1_7.tmpl +++ b/Dockerfile--astralinux_1_7.tmpl @@ -17,6 +17,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ time \ && rm -rf /var/lib/apt/lists/* +RUN sed -i 's/#MaxStartups 10:30:100/MaxStartups 2000:30:2000/' /etc/ssh/sshd_config && \ + sed -i 's/#MaxSessions 10/MaxSessions 500/' /etc/ssh/sshd_config && \ + sed -i 's/#MaxAuthTries 6/MaxAuthTries 20/' /etc/ssh/sshd_config + # --------------------------------------------- postgres FROM base1 AS base1_with_dev_tools diff --git a/Dockerfile--ubuntu_24_04.tmpl b/Dockerfile--ubuntu_24_04.tmpl index d150ac4a..eda0856c 100644 --- a/Dockerfile--ubuntu_24_04.tmpl +++ b/Dockerfile--ubuntu_24_04.tmpl @@ -23,6 +23,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ && rm -rf /var/lib/apt/lists/* +RUN sed -i 's/#MaxStartups 10:30:100/MaxStartups 2000:30:2000/' /etc/ssh/sshd_config && \ + sed -i 's/#MaxSessions 10/MaxSessions 500/' /etc/ssh/sshd_config && \ + sed -i 's/#MaxAuthTries 6/MaxAuthTries 20/' /etc/ssh/sshd_config + RUN bash /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y RUN install -d /usr/share/postgresql-common/pgdg From 1248faa80765fcbb5486c46cec81456d318dc1fe Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 23:35:00 +0300 Subject: [PATCH 28/41] CI2M: limits via docker run command Run containers with the following settings: --sysctl net.core.somaxconn=4096 --sysctl net.ipv4.tcp_max_syn_backlog=4096 --ulimit nofile=524288:524288 --ulimit nproc=65535:65535 --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39076c41..5a26d44e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -199,6 +199,10 @@ jobs: # "- sleep infinity" is a right command. Docker ignores "-" and runs "sleep infinity". # docker run -d -t \ + --sysctl net.core.somaxconn=4096 \ + --sysctl net.ipv4.tcp_max_syn_backlog=4096 \ + --ulimit nofile=524288:524288 \ + --ulimit nproc=65535:65535 \ --network ${{ env.RUN_CFG__NETWORK_NAME }} \ --name ${{ env.RUN_CFG__MACHINE2_NAME }} \ "${{ env.RUN_CFG__DOCKER_IMAGE_NAME }}" \ @@ -246,6 +250,10 @@ jobs: # Pass environment variables so Python tests know where to start. # Added the --rm flag so that the container is automatically deleted after the tests are completed docker run --rm -t \ + --sysctl net.core.somaxconn=4096 \ + --sysctl net.ipv4.tcp_max_syn_backlog=4096 \ + --ulimit nofile=524288:524288 \ + --ulimit nproc=65535:65535 \ --network ${{ env.RUN_CFG__NETWORK_NAME }} \ --name ${{ env.RUN_CFG__MACHINE1_NAME }} \ -v ${{ github.workspace }}/${{ env.RUN_CFG__LOGS_DIR }}:/home/test/testgres/logs \ From 2447df19ed4d6afde1e288252c06d859adf1e638 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Sun, 12 Jul 2026 23:59:11 +0300 Subject: [PATCH 29/41] CI2M: print nproc --- run_tests.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/run_tests.sh b/run_tests.sh index ebb02490..e92a5f85 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -6,6 +6,8 @@ if [ -z ${TEST_FILTER+x} ]; \ then export TEST_FILTER="TestTestgresLocal or (TestTestgresCommon and (not remote))"; \ fi +echo NPROC: $(nproc) + # fail early echo check that pg_config is in PATH command -v pg_config From f7bfbf64cdaa4d9963ac8fd1118bbe86c4829681 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Mon, 13 Jul 2026 14:45:10 +0300 Subject: [PATCH 30/41] CI2M: run_tests prints storage state of local and remote machines --- run_tests.sh | 54 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/run_tests.sh b/run_tests.sh index e92a5f85..981e781a 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -27,24 +27,68 @@ rm -f $COVERAGE_FILE pip install coverage +if [ -n "${TEST_CFG__REMOTE_HOST:-}" ] && [ -n "${TEST_CFG__REMOTE_USERNAME:-}" ]; then + cmd_str="ssh" + + if [ -n "${TEST_CFG__REMOTE_PASSWORD:-}" ]; then + cmd_str="sshpass -p \"$TEST_CFG__REMOTE_PASSWORD\" $cmd_str" + fi + + [ -n "${TEST_CFG__REMOTE_SSH_KEY:-}" ] && cmd_str="$cmd_str -i \"$TEST_CFG__REMOTE_SSH_KEY\"" + [ -n "${TEST_CFG__REMOTE_PORT:-}" ] && cmd_str="$cmd_str -p \"$TEST_CFG__REMOTE_PORT\"" + + cmd_str="$cmd_str \"$TEST_CFG__REMOTE_USERNAME@$TEST_CFG__REMOTE_HOST\" 'df -T'" + REMOTE_FS_STATE_QUERY="$cmd_str" +else + REMOTE_FS_STATE_QUERY="" +fi + +show_fs_state() { + df -T + if [ -n "$REMOTE_FS_STATE_QUERY" ]; then + eval "$REMOTE_FS_STATE_QUERY" + fi +} + +# ---------------------------------------- PATH + +show_fs_state + # run tests (PATH) -time coverage run -a -m pytest -l -vvv -n 4 -k "${TEST_FILTER}" +time coverage run -a -m pytest -l -vvv -n 8 -k "${TEST_FILTER}" + +# ---------------------------------------- PG_BIN + +show_fs_state # run tests (PG_BIN) PG_BIN=$(pg_config --bindir) \ -time coverage run -a -m pytest -l -vvv -n 4 -k "${TEST_FILTER}" +time coverage run -a -m pytest -l -vvv -n 8 -k "${TEST_FILTER}" + +# ---------------------------------------- PG_CONFIG + +show_fs_state # run tests (PG_CONFIG) PG_CONFIG=$(pg_config --bindir)/pg_config \ -time coverage run -a -m pytest -l -vvv -n 4 -k "${TEST_FILTER}" +time coverage run -a -m pytest -l -vvv -n 8 -k "${TEST_FILTER}" + +# ---------------------------------------- pg8000 + +show_fs_state # test pg8000 pip uninstall -y psycopg2 pip install pg8000 PG_CONFIG=$(pg_config --bindir)/pg_config \ -time coverage run -a -m pytest -l -vvv -n 4 -k "${TEST_FILTER}" +time coverage run -a -m pytest -l -vvv -n 8 -k "${TEST_FILTER}" + +# ---------------------------------------- finish + +show_fs_state + +# ---------------------------------------- coverage -# show coverage coverage report pip uninstall -y coverage From 68c85b1c753a87efa255a9ddf05a98477d6332a8 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Mon, 13 Jul 2026 14:47:38 +0300 Subject: [PATCH 31/41] FindPosmaster (linux) is rewritten --- .../linux/internal_platform_utils.py | 332 ++++++++++++++++-- 1 file changed, 299 insertions(+), 33 deletions(-) diff --git a/src/impl/platforms/linux/internal_platform_utils.py b/src/impl/platforms/linux/internal_platform_utils.py index b0ed64c3..3834f6e7 100644 --- a/src/impl/platforms/linux/internal_platform_utils.py +++ b/src/impl/platforms/linux/internal_platform_utils.py @@ -9,9 +9,11 @@ import re import shlex import typing +import time class InternalPlatformUtils(base.InternalPlatformUtils): + C_MAX_FIND_POSTMASTER_ATTEMPTS = 5 C_BASH_EXE = "/bin/bash" sm_exec_env = { @@ -35,39 +37,106 @@ def FindPostmaster( assert len(bin_dir) > 0 assert len(data_dir) > 0 + failures: typing.List[Exception] = [] + + postmaster_pid: typing.Optional[int] = None + + nAttempts = 0 + + while True: + nAttempts += 1 + + try: + postmaster_pid = __class__._FindPostmaster( + os_ops, + bin_dir, + data_dir, + ) + except Exception as e: + failures.append(e) + + log_msg = "FindPostmaster (bin_dir={!r}, data_dir={!r}) detects a problem. Exception {}:\n{}".format( + bin_dir, + data_dir, + type(e).__name__, + e, + ) + internal_utils.send_log_debug(log_msg) + + if nAttempts < __class__.C_MAX_FIND_POSTMASTER_ATTEMPTS: + time.sleep(0.05) + continue + + __class__._find_postmaster__throw_error__fail( + bin_dir=bin_dir, + data_dir=data_dir, + failures=failures, + ) + + break + + if postmaster_pid is None: + return InternalPlatformUtils.FindPostmasterResult.create_not_found() + + assert type(postmaster_pid) is int + + return InternalPlatformUtils.FindPostmasterResult.create_ok(postmaster_pid) + + # -------------------------------------------------------------------- + @staticmethod + def _FindPostmaster( + os_ops: OsOperations, + bin_dir: str, + data_dir: str + ) -> typing.Optional[int]: + assert isinstance(os_ops, OsOperations) + assert type(bin_dir) is str + assert type(data_dir) is str + assert type(__class__.C_BASH_EXE) is str + assert type(__class__.sm_exec_env) is dict + assert len(__class__.C_BASH_EXE) > 0 + assert len(bin_dir) > 0 + assert len(data_dir) > 0 + pg_path_e = re.escape(os_ops.build_path(bin_dir, "postgres")) data_dir_e = re.escape(data_dir) assert type(pg_path_e) is str assert type(data_dir_e) is str - regexp = r"^\s*[0-9]+\s+" + pg_path_e + r"(\s+.*)?\s+\-[D]\s+" + data_dir_e + r"(\s+.*)?" + regexp = r"^\s*[0-9]+\s+[0-9]+\s+" + pg_path_e + r"(\s+.*)?\s+\-[D]\s+" + data_dir_e + r"(\s+.*)?" cmd = [ __class__.C_BASH_EXE, "-c", - "ps -ewwo \"pid=,args=\" | grep -E " + shlex.quote(regexp), - ] + "ps -ewwo \"pid=,ppid=,args=\" | grep -E " + shlex.quote(regexp), + ] - exit_status, output_b, error_b = os_ops.exec_command( + exec_r = os_ops.exec_command( cmd=cmd, ignore_errors=True, verbose=True, exec_env=__class__.sm_exec_env, ) + assert type(exec_r) is tuple + assert len(exec_r) == 3 + + exit_status, output_b, error_b = exec_r + + assert type(exit_status) is int assert type(output_b) is bytes assert type(error_b) is bytes + if exit_status == 1: + return None + output = output_b.decode("utf-8") error = error_b.decode("utf-8") assert type(output) is str assert type(error) is str - if exit_status == 1: - return __class__.FindPostmasterResult.create_not_found() - if exit_status != 0: errMsg = f"test command returned an unexpected exit code: {exit_status}" raise ExecUtilException( @@ -82,43 +151,102 @@ def FindPostmaster( assert type(lines) is list if len(lines) == 0: - return __class__.FindPostmasterResult.create_not_found() + # ACHTUNG! + raise RuntimeError("Command returns 0 error code without output.") + + # parse result lines + pid_to_ppid: __class__.T_PID_TO_PPID = {} + + for i_line in range(len(lines)): + assert type(lines[i_line]) is str + + parts = lines[i_line].split() + assert type(parts) is list + + if len(parts) < 2: + __class__._find_postmaster__throw_error__bad_line_format( + lines, + i_line, + "no usefull data", + ) + + if not parts[0].isdigit(): + __class__._find_postmaster__throw_error__bad_line_format( + lines, + i_line, + "bad pid", + ) + + if not parts[1].isdigit(): + __class__._find_postmaster__throw_error__bad_line_format( + lines, + i_line, + "bad ppid", + ) + + pid = int(parts[0]) + ppid = int(parts[1]) + + if pid not in pid_to_ppid: + pid_to_ppid[pid] = ppid + continue - if len(lines) > 1: - msgs = [] - msgs.append("Many processes like a postmaster are found: {}.".format(len(lines))) + other_ppid = pid_to_ppid[pid] + assert type(other_ppid) is int - for i in range(len(lines)): - assert type(lines[i]) is str - lines.append("[{}] '{}'".format(i, lines[i])) + if ppid == other_ppid: + log_msg = "FindPostmaster (data_dir={!r}) get pid ({}) with ppid ({}) more than one time.".format( + data_dir, + pid, + ppid, + ) + internal_utils.send_log_debug(log_msg) continue - internal_utils.send_log_debug("\n".join(lines)) - return __class__.FindPostmasterResult.create_many_processes() + # ACTUNG ppid is changed --> restart + __class__._find_postmaster__throw_error__ppid_is_changed( + pid, + ppid, + other_ppid, + lines, + ) + + assert len(pid_to_ppid) <= len(lines) - def is_space_or_tab(ch) -> bool: - assert type(ch) is str - return ch == " " or ch == "\t" + true_postmasters = [ + pid for pid, ppid in pid_to_ppid.items() + if ppid not in pid_to_ppid + ] - line = lines[0] - start = 0 - while start < len(line) and is_space_or_tab(line[start]): - start += 1 + if len(true_postmasters) == 0: + __class__._find_postmaster__throw_error__cycle( + pid_to_ppid, + ) - pos = start - while pos < len(line) and line[pos].isnumeric(): - pos += 1 + if len(true_postmasters) == 1: + true_pid = true_postmasters[0] - if pos == start: - return __class__.FindPostmasterResult.create_has_problems() + if len(pid_to_ppid) > 1: + msg = "Many processes like a postmaster for data dir [{}] are found ({}).".format( + data_dir, + len(true_postmasters), + ) - if pos != len(line) and not line[pos].isspace(): - return __class__.FindPostmasterResult.create_has_problems() + msg += " List (ppid->pid): {}.".format( + __class__._make_text_from_pid_to_ppid(pid_to_ppid), + ) - pid = int(line[start:pos]) - assert type(pid) is int + msg += " True postmaster PID is {}.".format(true_pid) + internal_utils.send_log_debug(msg) - return __class__.FindPostmasterResult.create_ok(pid) + return true_pid + + assert len(true_postmasters) > 1 + + __class__._find_postmaster__throw_error__many_postmasters( + true_postmasters, + pid_to_ppid, + ) def ProcessIsZombi_soft_check( self, @@ -169,3 +297,141 @@ def _is_file_not_found_exception(e: Exception) -> bool: return True return False + + T_PID_TO_PPID = typing.Dict[int, int] + + @staticmethod + def _make_text_from_pid_to_ppid(pid_to_ppid: T_PID_TO_PPID) -> str: + result = "" + sep = "" + for pid, ppid in pid_to_ppid.items(): + result += sep + " {}->{}".format(ppid, pid) + sep = ", " + continue + return result + + @staticmethod + def _find_postmaster__throw_error__bad_line_format( + lines: typing.List[str], + i_line: int, + hint: str, + ) -> typing.NoReturn: + assert type(lines) is list + assert type(i_line) is int + assert type(hint) is int + + error_lines: typing.List[str] = [] + error_lines.append( + "Line {} has bad format. Hint: {}.".format( + i_line + 1, + hint, + ), + ) + error_lines.append( + "Problem line is:" + ) + error_lines.append( + " " + repr(lines[i_line]), + ) + error_lines.append( + "All the lines is:", + ) + for i in range(len(lines)): + error_lines.append( + " {}. {!r}".format(i + 1, lines[i]), + ) + continue + + raise RuntimeError("\n".join(error_lines)) + + @staticmethod + def _find_postmaster__throw_error__ppid_is_changed( + pid: int, + ppid: int, + other_ppid: int, + lines: typing.List[str], + ) -> typing.NoReturn: + assert type(pid) is int + assert type(ppid) is int + assert type(other_ppid) is int + assert type(lines) is list + + error_lines: typing.List[str] = [] + error_lines.append( + "Parent of process ({}) is changed from {} to {}.".format( + pid, + other_ppid, + ppid, + ), + ) + error_lines.append( + "All the lines is:", + ) + for i in range(len(lines)): + error_lines.append( + " {}. {!r}".format(i + 1, lines[i]), + ) + continue + + raise RuntimeError("\n".join(error_lines)) + + @staticmethod + def _find_postmaster__throw_error__cycle( + pid_to_ppid: T_PID_TO_PPID, + ) -> typing.NoReturn: + msg = "Cycle in processes postgres process tree. " + + msg += " List (ppid->pid): {},".format( + __class__._make_text_from_pid_to_ppid(pid_to_ppid), + ) + + msg += " List size is {}.".format( + len(pid_to_ppid), + ) + raise RuntimeError(msg) + + @staticmethod + def _find_postmaster__throw_error__many_postmasters( + postmaster_pids: typing.List[int], + pid_to_ppid: T_PID_TO_PPID, + ) -> typing.NoReturn: + msg = "Many processes like a postmaster are found ({}): {}.".format( + len(postmaster_pids), + ", ".join(map(str, postmaster_pids)), + ) + + msg += " Trees (ppid->pid): {}.".format( + __class__._make_text_from_pid_to_ppid(pid_to_ppid), + ) + + msg += " Total process count is {}.".format(len(pid_to_ppid)) + raise RuntimeError(msg) + + @staticmethod + def _find_postmaster__throw_error__fail( + bin_dir: str, + data_dir: str, + failures: typing.List[Exception], + ) -> typing.NoReturn: + assert type(bin_dir) is str + assert type(data_dir) is str + assert type(failures) is list + + err_msg = "FindPostmater did {} attempts and has not gotten a stable result. List of failures:\n" + + n = 0 + sep = "" + for e in failures: + assert isinstance(e, Exception) + + n += 1 + err_msg += sep + err_msg += "Failure #{}. Exception ({}):\n{}".format( + n, + type(e).__name__, + str(e), + ) + sep = "\n" + continue + + raise RuntimeError(err_msg) From e3b72a64e334bf55cfac4d8b586aee8fb82182dc Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Mon, 13 Jul 2026 17:01:16 +0300 Subject: [PATCH 32/41] [#247] PortManager__Generic2 - It is based of FS locks - It replaces PortManager__Generic --- src/consts.py | 4 + src/impl/port_manager__generic2.py | 166 ++++++++++++++++++++++++++++ src/impl/port_manager__this_host.py | 3 +- src/node.py | 6 +- src/utils.py | 4 +- tests/helpers/global_data.py | 6 +- 6 files changed, 180 insertions(+), 9 deletions(-) create mode 100755 src/impl/port_manager__generic2.py diff --git a/src/consts.py b/src/consts.py index 89c49ab7..d3589205 100644 --- a/src/consts.py +++ b/src/consts.py @@ -10,6 +10,10 @@ TMP_CACHE = 'tgsc_' TMP_BACKUP = 'tgsb_' +TMP_TESTGRES = "testgres" + +TMP_TESTGRES_PORTS = TMP_TESTGRES + "/ports" + # path to control file XLOG_CONTROL_FILE = "global/pg_control" diff --git a/src/impl/port_manager__generic2.py b/src/impl/port_manager__generic2.py new file mode 100755 index 00000000..2d224b98 --- /dev/null +++ b/src/impl/port_manager__generic2.py @@ -0,0 +1,166 @@ +from testgres.operations.os_ops import OsOperations + +from ..port_manager import PortManager +from ..exceptions import PortForException +from .. import consts + +import threading +import random +import typing +import logging + + +class OsLockFsObj: + _os_ops: typing.Optional[OsOperations] + _path: typing.Optional[str] + + def __init__(self, os_ops: OsOperations, path: str): + assert isinstance(os_ops, OsOperations) + assert type(path) == str # noqa: str + + os_ops.makedir(path) # throw + + self._os_ops = os_ops + self._path = path + return + + def release(self) -> None: + assert type(self._path) == str # noqa: str + assert isinstance(self._os_ops, OsOperations) + assert self._os_ops.path_exists(self._path) + + self._os_ops.rmdir(self._path) # throw + + self._path = None + self._os_ops = None + return + + +class PortManager__Generic2(PortManager): + _C_MIN_PORT_NUMBER = 1024 + _C_MAX_PORT_NUMBER = 65535 + + _os_ops: OsOperations + _guard: typing.Any + + # TODO: is there better to use bitmap fot _available_ports? + _available_ports: typing.Set[int] + _reserved_ports: typing.Dict[int, OsLockFsObj] + + _lock_dir: typing.Optional[str] + + def __init__(self, os_ops: OsOperations): + assert __class__._C_MIN_PORT_NUMBER <= __class__._C_MAX_PORT_NUMBER + + assert os_ops is not None + assert isinstance(os_ops, OsOperations) + self._os_ops = os_ops + self._guard = threading.Lock() + + self._available_ports = set( + range(__class__._C_MIN_PORT_NUMBER, __class__._C_MAX_PORT_NUMBER + 1) + ) + assert len(self._available_ports) == ( + (__class__._C_MAX_PORT_NUMBER - __class__._C_MIN_PORT_NUMBER) + 1 + ) + self._reserved_ports = dict() + self._lock_dir = None + return + + def reserve_port(self) -> int: + assert self._guard is not None + assert type(self._available_ports) == set # noqa: E721 + assert type(self._reserved_ports) == dict # noqa: E721 + assert isinstance(self._os_ops, OsOperations) + + with self._guard: + if self._lock_dir is None: + temp_dir = self._os_ops.get_tempdir() + assert type(temp_dir) == str # noqa: E721 + lock_dir = self._os_ops.build_path(temp_dir, consts.TMP_TESTGRES_PORTS) + assert type(lock_dir) == str # noqa: E721 + self._os_ops.makedirs(lock_dir) + self._lock_dir = lock_dir + + assert self._lock_dir is not None + assert type(self._lock_dir) == str # noqa: E721 + + t = tuple(self._available_ports) + assert len(t) == len(self._available_ports) + sampled_ports = random.sample(t, min(len(t), 100)) + t = None + + for port in sampled_ports: + assert type(port) == int # noqa: E721 + assert not (port in self._reserved_ports) + assert port in self._available_ports + + assert port >= __class__._C_MIN_PORT_NUMBER + assert port <= __class__._C_MAX_PORT_NUMBER + + if not self._os_ops.is_port_free(port): + continue + + try: + lock_path = self.helper__make_lock_path(port) + lock_obj = OsLockFsObj(self._os_ops, lock_path) # raise + except: # noqa: 722 + continue + + assert isinstance(lock_obj, OsLockFsObj) + assert self._os_ops.path_exists(lock_path) + + try: + self._reserved_ports[port] = lock_obj + except: # noqa: 722 + assert not (port in self._reserved_ports) + lock_obj.release() + raise + + self._available_ports.discard(port) + assert port in self._reserved_ports + assert not (port in self._available_ports) + __class__.helper__send_debug_msg("Port {} is reserved.", port) + return port + + raise PortForException("Can't select a port.") + + def release_port(self, number: int) -> None: + assert type(number) == int # noqa: E721 + assert number >= __class__._C_MIN_PORT_NUMBER + assert number <= __class__._C_MAX_PORT_NUMBER + + assert self._guard is not None + assert type(self._reserved_ports) == dict # noqa: E721 + + with self._guard: + assert number in self._reserved_ports + assert not (number in self._available_ports) + self._available_ports.add(number) + lock_obj = self._reserved_ports.pop(number) + assert not (number in self._reserved_ports) + assert number in self._available_ports + assert isinstance(lock_obj, OsLockFsObj) + lock_obj.release() + __class__.helper__send_debug_msg("Port {} is released.", number) + return + + @staticmethod + def helper__send_debug_msg(msg_template: str, *args) -> None: + assert msg_template is not None + assert args is not None + assert type(msg_template) == str # noqa: E721 + assert type(args) == tuple # noqa: E721 + assert msg_template != "" + s = "[port manager] " + s += msg_template.format(*args) + logging.debug(s) + + def helper__make_lock_path(self, port_number: int) -> str: + assert type(port_number) == int # noqa: E721 + # You have to call the reserve_port at first! + assert type(self._lock_dir) == str # noqa: E721 + + result = self._os_ops.build_path(self._lock_dir, str(port_number) + ".lock") + assert type(result) == str # noqa: E721 + return result diff --git a/src/impl/port_manager__this_host.py b/src/impl/port_manager__this_host.py index c7520d80..6c3ef41d 100755 --- a/src/impl/port_manager__this_host.py +++ b/src/impl/port_manager__this_host.py @@ -3,10 +3,11 @@ from .. import utils import threading +import typing class PortManager__ThisHost(PortManager): - sm_single_instance: PortManager = None + sm_single_instance: typing.Optional[PortManager] = None sm_single_instance_guard = threading.Lock() @staticmethod diff --git a/src/node.py b/src/node.py index c642bddb..be1c04f4 100644 --- a/src/node.py +++ b/src/node.py @@ -77,7 +77,7 @@ from .port_manager import PortManager from .impl.port_manager__this_host import PortManager__ThisHost -from .impl.port_manager__generic import PortManager__Generic +from .impl.port_manager__generic2 import PortManager__Generic2 from .logger import TestgresLogger @@ -309,12 +309,12 @@ def _get_port_manager(os_ops: OsOperations) -> PortManager: if os_ops is LocalOperations.get_single_instance(): assert utils._old_port_manager is not None - assert type(utils._old_port_manager) is PortManager__Generic + assert type(utils._old_port_manager) is PortManager__Generic2 assert utils._old_port_manager._os_ops is os_ops return PortManager__ThisHost.get_single_instance() # TODO: Throw the exception "Please define a port manager." ? - return PortManager__Generic(os_ops) + return PortManager__Generic2(os_ops) def clone_with_new_name_and_base_dir(self, name: str, base_dir: str): assert name is None or type(name) is str diff --git a/src/utils.py b/src/utils.py index 6efd9a49..b011f978 100644 --- a/src/utils.py +++ b/src/utils.py @@ -26,7 +26,7 @@ from testgres.operations.local_ops import LocalOperations from testgres.operations.helpers import Helpers as OsHelpers -from .impl.port_manager__generic import PortManager__Generic +from .impl.port_manager__generic2 import PortManager__Generic2 from .impl.platforms import internal_platform_utils_factory from .impl import internal_utils @@ -37,7 +37,7 @@ # # The old, global "port manager" always worked with LOCAL system # -_old_port_manager = PortManager__Generic(LocalOperations.get_single_instance()) +_old_port_manager = PortManager__Generic2(LocalOperations.get_single_instance()) # ports used by nodes bound_ports = _old_port_manager._reserved_ports diff --git a/tests/helpers/global_data.py b/tests/helpers/global_data.py index 111b6e17..c98ae9b4 100644 --- a/tests/helpers/global_data.py +++ b/tests/helpers/global_data.py @@ -5,7 +5,7 @@ from src.node import PortManager from src.node import PortManager__ThisHost -from src.node import PortManager__Generic +from src.node import PortManager__Generic2 import os import typing @@ -45,11 +45,11 @@ class OsOpsDescrs: class PortManagers: - sm_remote_port_manager = PortManager__Generic(OsOpsDescrs.sm_remote_os_ops) + sm_remote_port_manager = PortManager__Generic2(OsOpsDescrs.sm_remote_os_ops) sm_local_port_manager = PortManager__ThisHost.get_single_instance() - sm_local2_port_manager = PortManager__Generic(OsOpsDescrs.sm_local_os_ops) + sm_local2_port_manager = PortManager__Generic2(OsOpsDescrs.sm_local_os_ops) class PostgresNodeService: From 76cf6450d29a47674030390a9d032c7918af63d4 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Mon, 13 Jul 2026 17:02:52 +0300 Subject: [PATCH 33/41] test_kill_backgroud_writer__ok is improved (it fails under load) --- tests/test_testgres_common.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_testgres_common.py b/tests/test_testgres_common.py index 9be4a778..e8e6b587 100644 --- a/tests/test_testgres_common.py +++ b/tests/test_testgres_common.py @@ -754,9 +754,28 @@ def test_kill_backgroud_writer__ok( assert node.is_started node_pid = node.pid assert type(node_pid) is int + + # --- We expect BackgroundWriter to appear under load ------------------------ + bw_attempt = 0 + while True: + aux_pids = node.auxiliary_pids + assert type(aux_pids) is dict + + if ProcessType.BackgroundWriter in aux_pids: + break + + bw_attempt += 1 + if bw_attempt == 30: # We give the server up to 3 seconds to start all background workers. + raise RuntimeError("BackgroundWriter process did not start in time under heavy load.") + + time.sleep(0.1) + continue + + # ---------------------------------------------------------------------------- aux_pids = node.auxiliary_pids assert type(aux_pids) is dict assert ProcessType.BackgroundWriter in aux_pids + bw_pids = aux_pids[ProcessType.BackgroundWriter] assert type(bw_pids) is list assert len(bw_pids) == 1 From 31f702d83de0c5c375d1a5b34818f06cb40f6eaf Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Mon, 13 Jul 2026 17:25:30 +0300 Subject: [PATCH 34/41] bound_ports is deleted (it is critical change) Testgres does not provide this information any more. --- src/__init__.py | 3 +-- src/utils.py | 5 +---- tests/test_testgres_local.py | 35 ----------------------------------- 3 files changed, 2 insertions(+), 41 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index fe3565c3..95d3e296 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -41,7 +41,6 @@ from .utils import \ reserve_port, \ release_port, \ - bound_ports, \ get_bin_path, \ get_bin_dir, \ get_pg_config, \ @@ -73,7 +72,7 @@ "NodeApp", "PostgresNode", "PortManager", - "reserve_port", "release_port", "bound_ports", "get_bin_path", "get_bin_dir", "get_pg_config", "get_pg_version", "parse_pg_version", + "reserve_port", "release_port", "get_bin_path", "get_bin_dir", "get_pg_config", "get_pg_version", "parse_pg_version", "First", "Any", "OsOperations", "LocalOperations", "RemoteOperations", "ConnectionParams" ] diff --git a/src/utils.py b/src/utils.py index b011f978..8561c7cc 100644 --- a/src/utils.py +++ b/src/utils.py @@ -39,9 +39,6 @@ # _old_port_manager = PortManager__Generic2(LocalOperations.get_single_instance()) -# ports used by nodes -bound_ports = _old_port_manager._reserved_ports - # re-export version type class PgVer(Version): @@ -55,7 +52,7 @@ def __init__(self, version: str) -> None: def internal__reserve_port(): """ - Generate a new port and add it to 'bound_ports'. + Generate a new port. """ return _old_port_manager.reserve_port() diff --git a/tests/test_testgres_local.py b/tests/test_testgres_local.py index 437d5f6d..012ec12d 100644 --- a/tests/test_testgres_local.py +++ b/tests/test_testgres_local.py @@ -20,7 +20,6 @@ from src import get_pg_version # NOTE: those are ugly imports -from src.utils import bound_ports from src.utils import PgVer from src.node import ProcessProxy @@ -91,40 +90,6 @@ def test_pg_config(self): b = get_pg_config() assert (id(a) != id(b)) - def test_ports_management(self): - assert bound_ports is not None - assert type(bound_ports) is set - - if len(bound_ports) != 0: - logging.warning("bound_ports is not empty: {0}".format(bound_ports)) - - stage0__bound_ports = bound_ports.copy() - - with get_new_node() as node: - assert bound_ports is not None - assert type(bound_ports) is set - - assert node.port is not None - assert type(node.port) is int - - logging.info("node port is {0}".format(node.port)) - - assert node.port in bound_ports - assert node.port not in stage0__bound_ports - - assert stage0__bound_ports <= bound_ports - assert len(stage0__bound_ports) + 1 == len(bound_ports) - - stage1__bound_ports = stage0__bound_ports.copy() - stage1__bound_ports.add(node.port) - - assert stage1__bound_ports == bound_ports - - # check that port has been freed successfully - assert bound_ports is not None - assert type(bound_ports) is set - assert bound_ports == stage0__bound_ports - def test_child_process_dies(self): # test for FileNotFound exception during child_processes() function cmd = ["timeout", "60"] if os.name == 'nt' else ["sleep", "60"] From 0df53ac3a30c352725c4a55fd56272a988bacbb6 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Mon, 13 Jul 2026 17:26:54 +0300 Subject: [PATCH 35/41] port_manager__generic2 is updated (noqa E721 is deleted) --- src/impl/port_manager__generic2.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/impl/port_manager__generic2.py b/src/impl/port_manager__generic2.py index 2d224b98..a4b24c21 100755 --- a/src/impl/port_manager__generic2.py +++ b/src/impl/port_manager__generic2.py @@ -16,7 +16,7 @@ class OsLockFsObj: def __init__(self, os_ops: OsOperations, path: str): assert isinstance(os_ops, OsOperations) - assert type(path) == str # noqa: str + assert type(path) is str os_ops.makedir(path) # throw @@ -25,7 +25,7 @@ def __init__(self, os_ops: OsOperations, path: str): return def release(self) -> None: - assert type(self._path) == str # noqa: str + assert type(self._path) is str assert isinstance(self._os_ops, OsOperations) assert self._os_ops.path_exists(self._path) @@ -69,21 +69,21 @@ def __init__(self, os_ops: OsOperations): def reserve_port(self) -> int: assert self._guard is not None - assert type(self._available_ports) == set # noqa: E721 - assert type(self._reserved_ports) == dict # noqa: E721 + assert type(self._available_ports) is set + assert type(self._reserved_ports) is dict assert isinstance(self._os_ops, OsOperations) with self._guard: if self._lock_dir is None: temp_dir = self._os_ops.get_tempdir() - assert type(temp_dir) == str # noqa: E721 + assert type(temp_dir) is str lock_dir = self._os_ops.build_path(temp_dir, consts.TMP_TESTGRES_PORTS) - assert type(lock_dir) == str # noqa: E721 + assert type(lock_dir) is str self._os_ops.makedirs(lock_dir) self._lock_dir = lock_dir assert self._lock_dir is not None - assert type(self._lock_dir) == str # noqa: E721 + assert type(self._lock_dir) is str t = tuple(self._available_ports) assert len(t) == len(self._available_ports) @@ -91,7 +91,7 @@ def reserve_port(self) -> int: t = None for port in sampled_ports: - assert type(port) == int # noqa: E721 + assert type(port) is int assert not (port in self._reserved_ports) assert port in self._available_ports @@ -126,12 +126,12 @@ def reserve_port(self) -> int: raise PortForException("Can't select a port.") def release_port(self, number: int) -> None: - assert type(number) == int # noqa: E721 + assert type(number) is int assert number >= __class__._C_MIN_PORT_NUMBER assert number <= __class__._C_MAX_PORT_NUMBER assert self._guard is not None - assert type(self._reserved_ports) == dict # noqa: E721 + assert type(self._reserved_ports) is dict with self._guard: assert number in self._reserved_ports @@ -149,18 +149,18 @@ def release_port(self, number: int) -> None: def helper__send_debug_msg(msg_template: str, *args) -> None: assert msg_template is not None assert args is not None - assert type(msg_template) == str # noqa: E721 - assert type(args) == tuple # noqa: E721 + assert type(msg_template) is str + assert type(args) is tuple assert msg_template != "" s = "[port manager] " s += msg_template.format(*args) logging.debug(s) def helper__make_lock_path(self, port_number: int) -> str: - assert type(port_number) == int # noqa: E721 + assert type(port_number) is int # You have to call the reserve_port at first! - assert type(self._lock_dir) == str # noqa: E721 + assert type(self._lock_dir) is str result = self._os_ops.build_path(self._lock_dir, str(port_number) + ".lock") - assert type(result) == str # noqa: E721 + assert type(result) is str return result From 78dc38c442bd808d5134827e4a0638cb3ca254b6 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Mon, 13 Jul 2026 17:36:56 +0300 Subject: [PATCH 36/41] port_manager__generic2 is updated (ruff) --- src/impl/port_manager__generic2.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/impl/port_manager__generic2.py b/src/impl/port_manager__generic2.py index a4b24c21..b2b5f5c2 100755 --- a/src/impl/port_manager__generic2.py +++ b/src/impl/port_manager__generic2.py @@ -92,7 +92,7 @@ def reserve_port(self) -> int: for port in sampled_ports: assert type(port) is int - assert not (port in self._reserved_ports) + assert port not in self._reserved_ports assert port in self._available_ports assert port >= __class__._C_MIN_PORT_NUMBER @@ -104,7 +104,7 @@ def reserve_port(self) -> int: try: lock_path = self.helper__make_lock_path(port) lock_obj = OsLockFsObj(self._os_ops, lock_path) # raise - except: # noqa: 722 + except: # noqa: E722 continue assert isinstance(lock_obj, OsLockFsObj) @@ -112,14 +112,14 @@ def reserve_port(self) -> int: try: self._reserved_ports[port] = lock_obj - except: # noqa: 722 - assert not (port in self._reserved_ports) + except: # noqa: E722 + assert port not in self._reserved_ports lock_obj.release() raise self._available_ports.discard(port) assert port in self._reserved_ports - assert not (port in self._available_ports) + assert port not in self._available_ports __class__.helper__send_debug_msg("Port {} is reserved.", port) return port @@ -135,10 +135,10 @@ def release_port(self, number: int) -> None: with self._guard: assert number in self._reserved_ports - assert not (number in self._available_ports) + assert number not in self._available_ports self._available_ports.add(number) lock_obj = self._reserved_ports.pop(number) - assert not (number in self._reserved_ports) + assert number not in self._reserved_ports assert number in self._available_ports assert isinstance(lock_obj, OsLockFsObj) lock_obj.release() From 21a8097b51f449ca737f09a9a777582171b2cf17 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Mon, 13 Jul 2026 20:06:07 +0300 Subject: [PATCH 37/41] test_status__force_clean_postmaster_pid is rewritten --- tests/test_testgres_common.py | 72 ++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/tests/test_testgres_common.py b/tests/test_testgres_common.py index e8e6b587..80d22cb4 100644 --- a/tests/test_testgres_common.py +++ b/tests/test_testgres_common.py @@ -587,7 +587,27 @@ def test_status__empty_postmaster_pid(self, node_svc: PostgresNodeService): assert expected_msg == x.value.error return - def test_status__force_clean_postmaster_pid(self, node_svc: PostgresNodeService): + sm_false_true = [False, True] + + @pytest.fixture( + params=[ + pytest.param( + x, + id="sleep_after_clean={}".format(x), + ) + for x in sm_false_true + ] + ) + def sleep_after_clean(self, request: pytest.FixtureRequest) -> bool: + assert isinstance(request, pytest.FixtureRequest) + assert type(request.param) is bool + return request.param + + def test_status__force_clean_postmaster_pid( + self, + node_svc: PostgresNodeService, + sleep_after_clean: bool, + ): assert isinstance(node_svc, PostgresNodeService) assert (NodeStatus.Running) @@ -611,27 +631,42 @@ def test_status__force_clean_postmaster_pid(self, node_svc: PostgresNodeService) postmaster_pid_file )) + logging.info("Clean pid file...") node.os_ops.write( postmaster_pid_file, "", truncate=True, ) - x = node.os_ops.read( - postmaster_pid_file, - encoding="utf-8", - binary=False - ) - assert x == "" + if sleep_after_clean: + logging.info("SLEEP 65 sec!") + time.sleep(65) - with pytest.raises(expected_exception=ExecUtilException) as x: - node.status() + logging.info("Check node status...") + node_status: typing.Optional[NodeStatus] + try: + node_status = node.status() + except ExecUtilException as e: + logging.info("Catch exception ({}): {}".format( + type(e).__name__, + str(e), + )) - expected_msg = "pg_ctl: the PID file \"{}\" is empty\n".format( - postmaster_pid_file - ) + expected_msg = "pg_ctl: the PID file \"{}\" is empty\n".format( + postmaster_pid_file + ) + assert expected_msg == e.error + else: + assert node_status is not None - assert expected_msg == x.value.error + logging.info("Node Status is {}".format(node_status.name)) + + if node_status == NodeStatus.Stopped: + pass + elif node_status == NodeStatus.Zombie: + logging.warning("Zombie is detected!") + else: + raise RuntimeError("Unknown node status: {}.".format(node_status)) return def test_kill__is_not_initialized( @@ -3021,3 +3056,14 @@ def good_properties(f): for path in os_ops.environ("PATH").split(os.pathsep): if good_properties(os.path.join(path, util)): return True + + @staticmethod + def helper__is_file_not_found_exception(e: Exception) -> bool: + if isinstance(e, FileNotFoundError): + return True + + if isinstance(e, ExecUtilException): + if e.exit_code == 2: + return True + + return False From a320042d5accd23fff81dc88dd7e99360deeaa7a Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Mon, 13 Jul 2026 20:08:43 +0300 Subject: [PATCH 38/41] CI2M: DOCKER_HIGHLOAD_FLAGS --sysctl net.core.somaxconn=4096 --sysctl net.ipv4.tcp_max_syn_backlog=4096 --ulimit nofile=524288:524288 --ulimit nproc=65535:65535 --sysctl net.ipv4.tcp_tw_reuse=1 --sysctl net.ipv4.ip_local_port_range="1024 65535" --- .github/workflows/ci.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a26d44e..7585809c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -164,6 +164,14 @@ jobs: env: BASE_SIGN: "${{ matrix.platform }}-${{ matrix.case_suffix }}" + DOCKER_HIGHLOAD_FLAGS: >- + --sysctl net.core.somaxconn=4096 + --sysctl net.ipv4.tcp_max_syn_backlog=4096 + --ulimit nofile=524288:524288 + --ulimit nproc=65535:65535 + --sysctl net.ipv4.tcp_tw_reuse=1 + --sysctl net.ipv4.ip_local_port_range="1024 65535" + steps: - name: Prepare variables run: | @@ -199,10 +207,9 @@ jobs: # "- sleep infinity" is a right command. Docker ignores "-" and runs "sleep infinity". # docker run -d -t \ - --sysctl net.core.somaxconn=4096 \ - --sysctl net.ipv4.tcp_max_syn_backlog=4096 \ - --ulimit nofile=524288:524288 \ - --ulimit nproc=65535:65535 \ + ${{ env.DOCKER_HIGHLOAD_FLAGS }} \ + --sysctl net.ipv4.tcp_tw_reuse=1 \ + --sysctl net.ipv4.ip_local_port_range="1024 65535" \ --network ${{ env.RUN_CFG__NETWORK_NAME }} \ --name ${{ env.RUN_CFG__MACHINE2_NAME }} \ "${{ env.RUN_CFG__DOCKER_IMAGE_NAME }}" \ @@ -250,10 +257,7 @@ jobs: # Pass environment variables so Python tests know where to start. # Added the --rm flag so that the container is automatically deleted after the tests are completed docker run --rm -t \ - --sysctl net.core.somaxconn=4096 \ - --sysctl net.ipv4.tcp_max_syn_backlog=4096 \ - --ulimit nofile=524288:524288 \ - --ulimit nproc=65535:65535 \ + ${{ env.DOCKER_HIGHLOAD_FLAGS }} \ --network ${{ env.RUN_CFG__NETWORK_NAME }} \ --name ${{ env.RUN_CFG__MACHINE1_NAME }} \ -v ${{ github.workspace }}/${{ env.RUN_CFG__LOGS_DIR }}:/home/test/testgres/logs \ From c3ea664a27cd140f0cc7086665d7cbef4c17638f Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Mon, 13 Jul 2026 21:27:10 +0300 Subject: [PATCH 39/41] CI2M: run_tests -> 4 workers --- run_tests.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/run_tests.sh b/run_tests.sh index 981e781a..dafa085e 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -55,7 +55,7 @@ show_fs_state() { show_fs_state # run tests (PATH) -time coverage run -a -m pytest -l -vvv -n 8 -k "${TEST_FILTER}" +time coverage run -a -m pytest -l -vvv -n 4 -k "${TEST_FILTER}" # ---------------------------------------- PG_BIN @@ -63,7 +63,7 @@ show_fs_state # run tests (PG_BIN) PG_BIN=$(pg_config --bindir) \ -time coverage run -a -m pytest -l -vvv -n 8 -k "${TEST_FILTER}" +time coverage run -a -m pytest -l -vvv -n 4 -k "${TEST_FILTER}" # ---------------------------------------- PG_CONFIG @@ -71,7 +71,7 @@ show_fs_state # run tests (PG_CONFIG) PG_CONFIG=$(pg_config --bindir)/pg_config \ -time coverage run -a -m pytest -l -vvv -n 8 -k "${TEST_FILTER}" +time coverage run -a -m pytest -l -vvv -n 4 -k "${TEST_FILTER}" # ---------------------------------------- pg8000 @@ -81,7 +81,7 @@ show_fs_state pip uninstall -y psycopg2 pip install pg8000 PG_CONFIG=$(pg_config --bindir)/pg_config \ -time coverage run -a -m pytest -l -vvv -n 8 -k "${TEST_FILTER}" +time coverage run -a -m pytest -l -vvv -n 4 -k "${TEST_FILTER}" # ---------------------------------------- finish From 2685b019ccaa03e53ead53e57fb1caee507b1cf5 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Mon, 13 Jul 2026 21:29:47 +0300 Subject: [PATCH 40/41] CI2M: Run containers with "--init" option It must remove zomby processes in our container (alt11) --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7585809c..703c8722 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -165,6 +165,7 @@ jobs: BASE_SIGN: "${{ matrix.platform }}-${{ matrix.case_suffix }}" DOCKER_HIGHLOAD_FLAGS: >- + --init --sysctl net.core.somaxconn=4096 --sysctl net.ipv4.tcp_max_syn_backlog=4096 --ulimit nofile=524288:524288 From 3b4a27f3e81d44580943c3a171f01db8f01eb486 Mon Sep 17 00:00:00 2001 From: "d.kovalenko" Date: Mon, 13 Jul 2026 23:26:17 +0300 Subject: [PATCH 41/41] test_testgres_common.py is updated --- tests/test_testgres_common.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/tests/test_testgres_common.py b/tests/test_testgres_common.py index 80d22cb4..6089e821 100644 --- a/tests/test_testgres_common.py +++ b/tests/test_testgres_common.py @@ -639,6 +639,7 @@ def test_status__force_clean_postmaster_pid( ) if sleep_after_clean: + # server removes pid file and shutdown within 60 seconds. logging.info("SLEEP 65 sec!") time.sleep(65) @@ -800,7 +801,8 @@ def test_kill_backgroud_writer__ok( break bw_attempt += 1 - if bw_attempt == 30: # We give the server up to 3 seconds to start all background workers. + # We give the server up to 3 seconds to start all background workers. + if bw_attempt == 30: raise RuntimeError("BackgroundWriter process did not start in time under heavy load.") time.sleep(0.1) @@ -810,7 +812,6 @@ def test_kill_backgroud_writer__ok( aux_pids = node.auxiliary_pids assert type(aux_pids) is dict assert ProcessType.BackgroundWriter in aux_pids - bw_pids = aux_pids[ProcessType.BackgroundWriter] assert type(bw_pids) is list assert len(bw_pids) == 1 @@ -3056,14 +3057,3 @@ def good_properties(f): for path in os_ops.environ("PATH").split(os.pathsep): if good_properties(os.path.join(path, util)): return True - - @staticmethod - def helper__is_file_not_found_exception(e: Exception) -> bool: - if isinstance(e, FileNotFoundError): - return True - - if isinstance(e, ExecUtilException): - if e.exit_code == 2: - return True - - return False