From cb52dd83dfd8df2d67420acf0104726851fdac5b Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Fri, 11 Sep 2026 12:23:02 +0800 Subject: [PATCH 01/23] CI: add a GitHub Actions workflow for gpMgmt Behave tests Run the gpMgmt Behave suite on Rocky Linux 9, split by management command so that a failure in one command's feature file does not mask or pollute the others. The workflow builds an RPM once and fans out to a per-command matrix, each entry creating its own demo cluster. It is kept separate from the main build/installcheck workflow so that matrix expansion, environment setup and result parsing for Behave can evolve without disturbing the primary CI path. gpcheckperf is deliberately not in the matrix. Every scenario in gpcheckperf.feature is tagged @concourse_cluster -- it needs a multi-host cluster -- so a single-host entry filtering on "--tags ~@concourse_cluster" selects nothing and reports success without having run a single scenario. Greenplum's pipeline has the same single-host gpcheckperf job and it is empty there too; a green check that tests nothing is worse than no check at all. It can be added back when there is CI that can host a real multi-node cluster. demo_cluster.sh needs an absolute TRUSTED_SHELL path: the generated cluster config is later sourced by gpinitsystem, where $0 is no longer demo_cluster.sh and the relative path no longer resolves. --- .github/workflows/behave-cloudberry.yml | 874 ++++++++++++++++++++++++ 1 file changed, 874 insertions(+) create mode 100644 .github/workflows/behave-cloudberry.yml diff --git a/.github/workflows/behave-cloudberry.yml b/.github/workflows/behave-cloudberry.yml new file mode 100644 index 00000000000..255448b1ef8 --- /dev/null +++ b/.github/workflows/behave-cloudberry.yml @@ -0,0 +1,874 @@ +# +# -------------------------------------------------------------------- +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# -------------------------------------------------------------------- +# GitHub Actions Workflow: Apache Cloudberry Behave Pipeline +# -------------------------------------------------------------------- +# Description: +# +# This workflow runs Apache Cloudberry gpMgmt Behave tests on Rocky Linux 9. +# It is intentionally separated from the main build/installcheck workflow so +# that Behave-specific matrix expansion, environment setup, result parsing, +# and iterative test stabilization do not disturb the primary CI path. +# +# Workflow Overview: +# 1. **Prepare Behave Matrix**: +# - Expands the selected Behave command-level test matrix. +# - Supports manual filtering through `test_selection`. +# +# 2. **Build Job**: +# - Builds Apache Cloudberry and creates source/RPM artifacts for reuse +# within this workflow. +# +# 3. **Behave Job (Matrix)**: +# - Creates a demo cluster for each Behave matrix entry. +# - Runs the selected gpMgmt feature file(s) in isolation. +# - Parses Behave summaries and uploads logs/metadata artifacts. +# +# 4. **Report Job**: +# - Aggregates build and Behave job status into a final workflow summary. +# +# Execution Environment: +# - **Runs On**: ubuntu-22.04 with Rocky Linux 9 containers. +# - **Primary Test Scope**: `gpMgmt/test/behave/mgmt_utils` +# +# Notes: +# - Trigger mode: push, pull_request, and manual `workflow_dispatch`. +# - Behave tests are split by command to reduce cross-feature environment +# pollution. +# - This workflow currently focuses on single-host CI-compatible Behave tests. +# - Logs and parsed summaries are uploaded as artifacts for each matrix entry. +# -------------------------------------------------------------------- + +name: Apache Cloudberry Behave + +on: + push: + branches: [main, REL_2_STABLE] + pull_request: + branches: [main, REL_2_STABLE] + types: [opened, synchronize, reopened, edited] + workflow_dispatch: + inputs: + test_selection: + description: 'Select Behave tests to run (comma-separated). Examples: ic-behave-gpconfig,ic-behave-gpstart' + required: false + default: 'all' + type: string + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + packages: read + actions: write + checks: read + pull-requests: read + +env: + LOG_RETENTION_DAYS: 7 + ENABLE_DEBUG: false + +jobs: + ## ====================================================================== + ## Job: check-skip + ## Honour the [skip ci] markers documented in the PR template, the same + ## way build-cloudberry.yml does. Without this a doc-only PR still pays + ## for a full hour of Behave. + ## ====================================================================== + check-skip: + runs-on: ubuntu-22.04 + outputs: + should_skip: ${{ steps.skip-check.outputs.should_skip }} + steps: + - id: skip-check + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + PR_TITLE: ${{ github.event.pull_request.title || '' }} + PR_BODY: ${{ github.event.pull_request.body || '' }} + run: | + echo "should_skip=false" >> "$GITHUB_OUTPUT" + + if [[ "$EVENT_NAME" == "pull_request" ]]; then + MESSAGE="${PR_TITLE}\n${PR_BODY}" + ESCAPED_MESSAGE=$(printf "%s" "$MESSAGE") + if echo -e "$ESCAPED_MESSAGE" | grep -qEi '\[skip[ -]ci\]|\[ci[ -]skip\]|\[no[ -]ci\]'; then + echo "should_skip=true" >> "$GITHUB_OUTPUT" + fi + else + echo "Skip logic is not applied for $EVENT_NAME events." + fi + + prepare-behave-matrix: + runs-on: ubuntu-22.04 + needs: check-skip + if: needs.check-skip.outputs.should_skip != 'true' + outputs: + behave-matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - id: set-matrix + run: | + echo "=== Behave Matrix Preparation Diagnostics ===" + echo "Event type: ${{ github.event_name }}" + echo "Test selection input: '${{ github.event.inputs.test_selection || 'all' }}'" + + ALL_BEHAVE_TESTS='{ + "include": [ + {"test":"ic-behave-analyzedb","behave_features":["test/behave/mgmt_utils/analyzedb.feature"]}, + {"test":"ic-behave-gp-bash-functions","behave_features":["test/behave/mgmt_utils/gp_bash_functions.feature"]}, + {"test":"ic-behave-gpactivatestandby","behave_features":["test/behave/mgmt_utils/gpactivatestandby.feature"]}, + {"test":"ic-behave-gpaddmirrors", + "behave_features":["test/behave/mgmt_utils/gpaddmirrors.feature"], + "behave_args":"--tags ~@concourse_cluster" + }, + {"test":"ic-behave-gpcheckcat", + "behave_features":["test/behave/mgmt_utils/gpcheckcat.feature"], + "behave_args":"--tags ~@extended" + }, + {"test":"ic-behave-gpconfig","behave_features":["test/behave/mgmt_utils/gpconfig.feature"]}, + {"test":"ic-behave-gpinitstandby", + "behave_features":["test/behave/mgmt_utils/gpinitstandby.feature"], + "behave_args":"--tags ~@concourse_cluster" + }, + {"test":"ic-behave-gpinitsystem", + "behave_features":["test/behave/mgmt_utils/gpinitsystem.feature"], + "behave_args":"--tags ~@extended" + }, + {"test":"ic-behave-gpmovemirrors", + "behave_features":["test/behave/mgmt_utils/gpmovemirrors.feature"], + "behave_args":"--tags ~@concourse_cluster --tags ~@extended" + }, + {"test":"ic-behave-gprecoverseg", + "behave_features":["test/behave/mgmt_utils/gprecoverseg.feature"], + "behave_args":"--tags ~@concourse_cluster --tags ~@extended" + }, + {"test":"ic-behave-gpreload","behave_features":["test/behave/mgmt_utils/gpreload.feature"]}, + {"test":"ic-behave-gpstart", + "behave_features":["test/behave/mgmt_utils/gpstart.feature"], + "behave_args":"--tags ~@concourse_cluster" + }, + {"test":"ic-behave-gpstate", + "behave_features":["test/behave/mgmt_utils/gpstate.feature"], + "behave_args":"--tags ~@concourse_cluster" + }, + {"test":"ic-behave-gpstop","behave_features":["test/behave/mgmt_utils/gpstop.feature"], + "behave_args":"--tags ~@concourse_cluster" + }, + {"test":"ic-behave-gpssh", + "behave_features":["test/behave/mgmt_utils/gpssh.feature"], + "behave_args":"--tags ~@requires_netem" + }, + {"test":"ic-behave-minirepro", + "behave_features":["test/behave/mgmt_utils/minirepro.feature"], + "behave_args":"--tags ~@extended" + }, + {"test":"ic-behave-replication-slots", + "behave_features":["test/behave/mgmt_utils/replication_slots.feature"], + "behave_args":"--tags ~@extended" + } + ] + }' + + VALID_TESTS=$(echo "$ALL_BEHAVE_TESTS" | jq -r '.include[].test') + IFS=',' read -ra SELECTED_TESTS <<< "${{ github.event.inputs.test_selection || 'all' }}" + + if [[ "${SELECTED_TESTS[*]}" == "all" || -z "${SELECTED_TESTS[*]}" ]]; then + mapfile -t SELECTED_TESTS <<< "$VALID_TESTS" + fi + + INVALID_TESTS=() + FILTERED_TESTS=() + for TEST in "${SELECTED_TESTS[@]}"; do + TEST=$(echo "$TEST" | tr -d '[:space:]') + if echo "$VALID_TESTS" | grep -qw "$TEST"; then + FILTERED_TESTS+=("$TEST") + else + INVALID_TESTS+=("$TEST") + fi + done + + if [[ ${#INVALID_TESTS[@]} -gt 0 ]]; then + echo "::error::Invalid Behave test(s) selected: ${INVALID_TESTS[*]}" + echo "Valid tests are: $(echo "$VALID_TESTS" | tr '\n' ', ')" + exit 1 + fi + + RESULT='{"include":[' + FIRST=true + for TEST in "${FILTERED_TESTS[@]}"; do + CONFIG=$(jq -c --arg test "$TEST" '.include[] | select(.test == $test)' <<< "$ALL_BEHAVE_TESTS") + if [[ "$FIRST" == true ]]; then + FIRST=false + else + RESULT="${RESULT}," + fi + RESULT="${RESULT}${CONFIG}" + done + RESULT="${RESULT}]}" + + echo "Final behave matrix configuration:" + echo "$RESULT" | jq . + + { + echo "matrix<> "$GITHUB_OUTPUT" + + build: + name: Build Apache Cloudberry RPM + needs: check-skip + if: needs.check-skip.outputs.should_skip != 'true' + env: + JOB_TYPE: build + runs-on: ubuntu-22.04 + timeout-minutes: 120 + outputs: + build_timestamp: ${{ steps.set_timestamp.outputs.timestamp }} + container: + image: apache/incubator-cloudberry:cbdb-build-rocky9-latest + options: >- + --user root + -h cdw + -v /usr/share:/host_usr_share + -v /usr/local:/host_usr_local + -v /opt:/host_opt + steps: + - name: Free Disk Space + run: | + echo "=== Disk space before cleanup ===" + df -h / + + rm -rf /host_opt/hostedtoolcache || true + rm -rf /host_usr_local/lib/android || true + rm -rf /host_usr_share/dotnet || true + rm -rf /host_opt/ghc || true + rm -rf /host_usr_local/.ghcup || true + rm -rf /host_usr_share/swift || true + rm -rf /host_usr_local/share/powershell || true + rm -rf /host_usr_local/share/chromium || true + rm -rf /host_usr_share/miniconda || true + rm -rf /host_opt/az || true + rm -rf /host_usr_share/sbt || true + + echo "=== Disk space after cleanup ===" + df -h / + + - name: Set build timestamp + id: set_timestamp + run: | + timestamp=$(date +'%Y%m%d_%H%M%S') + echo "timestamp=$timestamp" | tee -a "$GITHUB_OUTPUT" + echo "BUILD_TIMESTAMP=$timestamp" | tee -a "$GITHUB_ENV" + + - name: Checkout Apache Cloudberry + uses: actions/checkout@v4 + with: + fetch-depth: 1 + submodules: true + + - name: Cloudberry Environment Initialization + env: + LOGS_DIR: build-logs + run: | + set -eo pipefail + if ! su - gpadmin -c "/tmp/init_system.sh"; then + echo "::error::Container initialization failed" + exit 1 + fi + + mkdir -p "${LOGS_DIR}/details" + chown -R gpadmin:gpadmin . + chmod -R 755 . + chmod 777 "${LOGS_DIR}" + + df -kh / + rm -rf /__t/* + df -kh / + + df -h | tee -a "${LOGS_DIR}/details/disk-usage.log" + free -h | tee -a "${LOGS_DIR}/details/memory-usage.log" + + { + echo "=== Environment Information ===" + uname -a + df -h + free -h + env + } | tee -a "${LOGS_DIR}/details/environment.log" + + echo "SRC_DIR=${GITHUB_WORKSPACE}" | tee -a "$GITHUB_ENV" + + - name: Run Apache Cloudberry configure script + env: + SRC_DIR: ${{ github.workspace }} + run: | + set -eo pipefail + chmod +x "${SRC_DIR}"/devops/build/automation/cloudberry/scripts/configure-cloudberry.sh + if ! time su - gpadmin -c "cd ${SRC_DIR} && SRC_DIR=${SRC_DIR} ENABLE_DEBUG=${{ env.ENABLE_DEBUG }} ${SRC_DIR}/devops/build/automation/cloudberry/scripts/configure-cloudberry.sh"; then + echo "::error::Configure script failed" + exit 1 + fi + + - name: Run Apache Cloudberry build script + env: + SRC_DIR: ${{ github.workspace }} + run: | + set -eo pipefail + chmod +x "${SRC_DIR}"/devops/build/automation/cloudberry/scripts/build-cloudberry.sh + if ! time su - gpadmin -c "cd ${SRC_DIR} && SRC_DIR=${SRC_DIR} ${SRC_DIR}/devops/build/automation/cloudberry/scripts/build-cloudberry.sh"; then + echo "::error::Build script failed" + exit 1 + fi + + - name: Create Source tarball, create RPM and verify artifacts + env: + CBDB_VERSION: 99.0.0 + BUILD_NUMBER: 1 + SRC_DIR: ${{ github.workspace }} + run: | + set -eo pipefail + + tar czf "${SRC_DIR}"/../apache-cloudberry-incubating-src.tgz -C "${SRC_DIR}"/.. ./cloudberry + mv "${SRC_DIR}"/../apache-cloudberry-incubating-src.tgz "${SRC_DIR}" + + # build-rpm.sh creates ~/rpmbuild/{SPECS,SOURCES}, copies the spec + # there itself, and stages LICENSE/NOTICE/DISCLAIMER into SOURCES, + # which is where the spec's %{_sourcedir} reads them from. Only the + # remaining rpmbuild directories need creating up front. + rpmdev-setuptree + + DEBUG_RPMBUILD_OPT="" + DEBUG_IDENTIFIER="" + if [ "${{ env.ENABLE_DEBUG }}" = "true" ]; then + DEBUG_RPMBUILD_OPT="--with-debug" + DEBUG_IDENTIFIER=".debug" + fi + + "${SRC_DIR}"/devops/build/packaging/rpm/build-rpm.sh --version "${CBDB_VERSION}" --release "${BUILD_NUMBER}" "${DEBUG_RPMBUILD_OPT}" + + os_version=$(grep -oP '(?<=^VERSION_ID=")[0-9]' /etc/os-release) + RPM_FILE="${HOME}"/rpmbuild/RPMS/x86_64/apache-cloudberry-db-incubating-"${CBDB_VERSION}"-"${BUILD_NUMBER}""${DEBUG_IDENTIFIER}".el"${os_version}".x86_64.rpm + cp "${RPM_FILE}" "${SRC_DIR}" + RPM_DEBUG="${HOME}"/rpmbuild/RPMS/x86_64/apache-cloudberry-db-incubating-debuginfo-"${CBDB_VERSION}"-"${BUILD_NUMBER}""${DEBUG_IDENTIFIER}".el"${os_version}".x86_64.rpm + cp "${RPM_DEBUG}" "${SRC_DIR}" + + - name: Upload build logs + uses: actions/upload-artifact@v4 + with: + name: behave-build-logs-${{ env.BUILD_TIMESTAMP }} + path: | + build-logs/ + retention-days: ${{ env.LOG_RETENTION_DAYS }} + + - name: Upload Cloudberry RPM build artifacts + uses: actions/upload-artifact@v4 + with: + name: apache-cloudberry-db-incubating-rpm-build-artifacts + retention-days: ${{ env.LOG_RETENTION_DAYS }} + if-no-files-found: error + path: | + *.rpm + + - name: Upload Cloudberry source build artifacts + uses: actions/upload-artifact@v4 + with: + name: apache-cloudberry-db-incubating-source-build-artifacts + retention-days: ${{ env.LOG_RETENTION_DAYS }} + if-no-files-found: error + path: | + apache-cloudberry-incubating-src.tgz + + behave: + name: ${{ matrix.test }} + needs: [check-skip, build, prepare-behave-matrix] + if: | + !cancelled() && + needs.check-skip.outputs.should_skip != 'true' && + needs.build.result == 'success' + runs-on: ubuntu-22.04 + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.prepare-behave-matrix.outputs.behave-matrix) }} + container: + image: apache/incubator-cloudberry:cbdb-build-rocky9-latest + options: >- + --privileged + --user root + --hostname cdw + --shm-size=2gb + --ulimit core=-1 + --cgroupns=host + -v /sys/fs/cgroup:/sys/fs/cgroup:rw + -v /usr/share:/host_usr_share + -v /usr/local:/host_usr_local + -v /opt:/host_opt + steps: + - name: Free Disk Space + run: | + echo "=== Disk space before cleanup ===" + df -h / + + rm -rf /host_opt/hostedtoolcache || true + rm -rf /host_usr_local/lib/android || true + rm -rf /host_usr_share/dotnet || true + rm -rf /host_opt/ghc || true + rm -rf /host_usr_local/.ghcup || true + rm -rf /host_usr_share/swift || true + rm -rf /host_usr_local/share/powershell || true + rm -rf /host_usr_local/share/chromium || true + rm -rf /host_usr_share/miniconda || true + rm -rf /host_opt/az || true + rm -rf /host_usr_share/sbt || true + + echo "=== Disk space after cleanup ===" + df -h / + + - name: Cloudberry Environment Initialization + env: + LOGS_DIR: build-logs + run: | + set -eo pipefail + if ! su - gpadmin -c "/tmp/init_system.sh"; then + echo "::error::Container initialization failed" + exit 1 + fi + + mkdir -p "${LOGS_DIR}/details" + chown -R gpadmin:gpadmin . + chmod -R 755 . + chmod 777 "${LOGS_DIR}" + + df -kh / + rm -rf /__t/* + df -kh / + + df -h | tee -a "${LOGS_DIR}/details/disk-usage.log" + free -h | tee -a "${LOGS_DIR}/details/memory-usage.log" + + { + echo "=== Environment Information ===" + uname -a + df -h + free -h + env + } | tee -a "${LOGS_DIR}/details/environment.log" + + echo "SRC_DIR=${GITHUB_WORKSPACE}" | tee -a "$GITHUB_ENV" + + # Ensure hostname resolves to IPv4, not ::1, so that gpinitsystem + # generates pg_hba.conf entries that gpstop/gpstart can use. + # Note: Do NOT remove ::1 cdw mapping. The demo cluster may be + # configured with IPv6 loopback addresses, and removing ::1 would + # cause SSH-based pg_ctl operations to fail. + echo '127.0.0.1 cdw' >> /etc/hosts + + # Pre-populate SSH known_hosts for the demo cluster hostname + # to avoid host-key prompts during SSH-based gp commands. + mkdir -p /home/gpadmin/.ssh + ssh-keyscan $(hostname) >> /home/gpadmin/.ssh/known_hosts 2>/dev/null + chown -R gpadmin:gpadmin /home/gpadmin/.ssh + + - name: Generate Behave Job Summary Start + if: always() + run: | + { + echo "# Behave Job Summary: ${{ matrix.test }}" + echo "## Environment" + echo "- Start Time: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" + echo "- OS Version: $(cat /etc/redhat-release)" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Download Cloudberry RPM build artifacts + uses: actions/download-artifact@v4 + with: + name: apache-cloudberry-db-incubating-rpm-build-artifacts + path: ${{ github.workspace }}/rpm_build_artifacts + merge-multiple: false + run-id: ${{ github.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Download Cloudberry Source build artifacts + uses: actions/download-artifact@v4 + with: + name: apache-cloudberry-db-incubating-source-build-artifacts + path: ${{ github.workspace }}/source_build_artifacts + merge-multiple: false + run-id: ${{ github.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify downloaded artifacts + id: verify-artifacts + run: | + set -eo pipefail + + SRC_TARBALL_FILE=$(ls "${GITHUB_WORKSPACE}"/source_build_artifacts/apache-cloudberry-incubating-src.tgz) + if [ ! -f "${SRC_TARBALL_FILE}" ]; then + echo "::error::SRC TARBALL file not found" + exit 1 + fi + echo "src_tarball_file=${SRC_TARBALL_FILE}" >> "$GITHUB_OUTPUT" + + RPM_FILE=$(ls "${GITHUB_WORKSPACE}"/rpm_build_artifacts/apache-cloudberry-db-incubating-[0-9]*.rpm | grep -v "debuginfo") + if [ ! -f "${RPM_FILE}" ]; then + echo "::error::RPM file not found" + exit 1 + fi + echo "rpm_file=${RPM_FILE}" >> "$GITHUB_OUTPUT" + + - name: Install Cloudberry RPM + if: success() + env: + RPM_FILE: ${{ steps.verify-artifacts.outputs.rpm_file }} + run: | + set -eo pipefail + + dnf clean all + dnf makecache --refresh || dnf makecache + rm -rf /usr/local/cloudberry-db + + if ! time dnf install -y --setopt=retries=10 --releasever=9 "${RPM_FILE}"; then + echo "::error::RPM installation failed" + exit 1 + fi + + # The RPM installs as root; Behave runs as gpadmin, and some + # scenarios change permissions under $GPHOME (for example + # gpinitstandby.feature's "$GPHOME/share directory is non-writable"). + # -H follows the command-line symlink. + chown -RH gpadmin:gpadmin /usr/local/cloudberry-db/ + + rm -rf "${GITHUB_WORKSPACE}"/rpm_build_artifacts + + - name: Extract source tarball + if: success() + env: + SRC_TARBALL_FILE: ${{ steps.verify-artifacts.outputs.src_tarball_file }} + SRC_DIR: ${{ github.workspace }} + run: | + set -eo pipefail + + if ! time tar zxf "${SRC_TARBALL_FILE}" -C "${SRC_DIR}"/.. ; then + echo "::error::Source extraction failed" + exit 1 + fi + + rm -rf "${GITHUB_WORKSPACE}"/source_build_artifacts + + - name: Create Apache Cloudberry demo cluster + if: success() + env: + SRC_DIR: ${{ github.workspace }} + run: | + set -eo pipefail + chmod +x "${SRC_DIR}"/devops/build/automation/cloudberry/scripts/create-cloudberry-demo-cluster.sh + + if ! time su - gpadmin -c "cd ${SRC_DIR} && NUM_PRIMARY_MIRROR_PAIRS='3' SRC_DIR=${SRC_DIR} ${SRC_DIR}/devops/build/automation/cloudberry/scripts/create-cloudberry-demo-cluster.sh"; then + echo "::error::Demo cluster creation failed" + exit 1 + fi + + - name: Run Behave Tests + if: success() + env: + SRC_DIR: ${{ github.workspace }} + shell: bash {0} + run: | + set -o pipefail + + mkdir -p build-logs/details + config_log="build-logs/details/make-${{ matrix.test }}-config0.log" + behave_targets="${{ join(matrix.behave_features, ' ') }}" + behave_args="${{ matrix.behave_args || '' }}" + + mkdir -p "/tmp/cloudberry-cores" + chmod 1777 "/tmp/cloudberry-cores" + sysctl -w kernel.core_pattern="/tmp/cloudberry-cores/core-%e-%s-%u-%g-%p-%t" + + dnf install -y libffi-devel || echo "Warning: failed to install libffi-devel" + su - gpadmin -c "pip3 install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt || pip install --user -r ${SRC_DIR}/gpMgmt/requirements-dev.txt" + + echo "Running features:" + for feature in $behave_targets; do + echo "- $feature" + done + if [[ -n "$behave_args" ]]; then + echo "Behave args: $behave_args" + fi + + behave_rc=0 + if ! time su - gpadmin -c "cd ${SRC_DIR}/gpMgmt && source /usr/local/cloudberry-db/cloudberry-env.sh && source ${SRC_DIR}/gpAux/gpdemo/gpdemo-env.sh && PYTHONPATH=${SRC_DIR}/gpMgmt:\$PYTHONPATH behave $behave_args $behave_targets" \ + 2>&1 | tee -a "$config_log"; then + echo "::warning::Behave execution reported failures" + behave_rc=1 + fi + + # A Behave run can segfault a backend without failing a scenario, so + # analyse whatever landed in the cores directory before giving up on + # this job. Mirrors the build workflow's handling. + echo "-----------------------------------------" + echo "Contents of cores directory:" + ls -Rl "/tmp/cloudberry-cores" || true + echo "-----------------------------------------" + "${SRC_DIR}"/devops/build/automation/cloudberry/scripts/analyze_core_dumps.sh "${{ matrix.test }}" + case "$?" in + 0) echo "No core dumps found for this configuration" ;; + 1) echo "Core dumps were found and analyzed successfully" ;; + 2) echo "::warning::Issues encountered during core dump analysis" ;; + *) echo "::error::Unexpected return code from core dump analysis" ;; + esac + + exit $behave_rc + + - name: Check for Core Dumps + if: always() + env: + SRC_DIR: ${{ github.workspace }} + shell: bash {0} + run: | + # The analysis script writes a log whether or not it found anything, + # so only look at the ones that actually analysed a core. + analysed=$(grep -l "Analyzing core file:" \ + "${SRC_DIR}"/build-logs/core_analysis_*.log 2>/dev/null) + + if [ -z "$analysed" ]; then + echo "No core dumps were found during test execution" + exit 0 + fi + + # Several scenarios make a segment's data directory unreadable and + # then start it, so the startup process reaches + # PANIC: could not open file "pg_wal/...": Permission denied + # ereport(PANIC) calls abort(), which leaves a core behind. Those are + # the test doing its job, not a crash -- report them, but only fail + # the job on a core that is not an ereport(PANIC): a segmentation + # fault, a bus error, or a failed Assert (ExceptionalCondition). + status=0 + for file in $analysed; do + echo "Core analysis file: $file" + echo "=== Content ===" + cat "$file" + echo "==============" + + if grep -q "ExceptionalCondition" "$file"; then + echo "::error::${file}: assertion failure" + status=1 + elif grep -qE "Program terminated with signal (SIGSEGV|SIGBUS|SIGILL|SIGFPE)" "$file"; then + echo "::error::${file}: crash signal" + status=1 + elif grep -q "errfinish" "$file"; then + echo "::warning::${file}: ereport(PANIC) core, expected for scenarios that break a segment on purpose" + else + echo "::error::${file}: unrecognised core, treating as a failure" + status=1 + fi + done + + exit $status + + - name: Parse Behave Results + if: always() + shell: bash {0} + run: | + set -o pipefail + + config_log="build-logs/details/make-${{ matrix.test }}-config0.log" + behave_cmd="behave ${{ matrix.behave_args || '' }} ${{ join(matrix.behave_features, ' ') }}" + if [ ! -f "$config_log" ]; then + { + echo "MAKE_COMMAND=\"${behave_cmd}\"" + echo "STATUS=missing_log" + echo "TOTAL_TESTS=0" + echo "FAILED_TESTS=0" + echo "PASSED_TESTS=0" + echo "IGNORED_TESTS=0" + } | tee "test_results.${{ matrix.test }}.0.txt" + exit 1 + fi + + features_line=$(grep -E '^[0-9]+ feature(s)? passed, [0-9]+ failed, [0-9]+ skipped$' "$config_log" | tail -n 1) + scenarios_line=$(grep -E '^[0-9]+ scenario(s)? passed, [0-9]+ failed, [0-9]+ skipped(, [0-9]+ untested)?$' "$config_log" | tail -n 1) + steps_line=$(grep -E '^[0-9]+ step(s)? passed, [0-9]+ failed, [0-9]+ skipped, [0-9]+ undefined(, [0-9]+ untested)?$' "$config_log" | tail -n 1) + + if [[ -z "$scenarios_line" ]]; then + { + echo "MAKE_COMMAND=\"${behave_cmd}\"" + echo "STATUS=parse_error" + echo "TOTAL_TESTS=0" + echo "FAILED_TESTS=0" + echo "PASSED_TESTS=0" + echo "IGNORED_TESTS=0" + } | tee "test_results.${{ matrix.test }}.0.txt" + exit 1 + fi + + scenario_counts=$(echo "$scenarios_line" | sed -E 's/^([0-9]+) scenario(s)? passed, ([0-9]+) failed, ([0-9]+) skipped(, ([0-9]+) untested)?$/\1 \3 \4 \6/') + read -r scenarios_passed scenarios_failed scenarios_skipped scenarios_untested <<< "$scenario_counts" + scenarios_untested=${scenarios_untested:-0} + total_scenarios=$((scenarios_passed + scenarios_failed + scenarios_skipped)) + + { + echo "MAKE_COMMAND=\"${behave_cmd}\"" + if [[ "$scenarios_failed" -eq 0 ]]; then + echo "STATUS=passed" + else + echo "STATUS=failed" + fi + echo "TOTAL_TESTS=${total_scenarios}" + echo "FAILED_TESTS=${scenarios_failed}" + echo "PASSED_TESTS=${scenarios_passed}" + echo "IGNORED_TESTS=${scenarios_skipped}" + echo "BEHAVE_UNTESTED_SCENARIOS=${scenarios_untested}" + echo "BEHAVE_FEATURES_SUMMARY=\"${features_line:-unavailable}\"" + echo "BEHAVE_SCENARIOS_SUMMARY=\"${scenarios_line}\"" + echo "BEHAVE_STEPS_SUMMARY=\"${steps_line:-unavailable}\"" + } | tee "test_results.${{ matrix.test }}.0.txt" + + if [[ "$scenarios_failed" -eq 0 ]]; then + exit 0 + fi + exit 1 + + - name: Generate Behave Job Summary End + if: always() + shell: bash {0} + run: | + { + echo "## Test Results" + echo "- End Time: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" + + if [[ ! -f "test_results.${{ matrix.test }}.0.txt" ]]; then + echo "### Result Status" + echo "⚠️ No results file found" + exit 0 + fi + + . "test_results.${{ matrix.test }}.0.txt" + + echo "### Command" + echo "\`$MAKE_COMMAND\`" + echo "" + + echo "### Status" + case "${STATUS:-unknown}" in + passed) + echo "✅ All scenarios passed" + ;; + failed) + echo "❌ Some scenarios failed" + ;; + parse_error) + echo "⚠️ Could not parse Behave results" + ;; + missing_log) + echo "⚠️ Behave log file missing" + ;; + *) + echo "⚠️ Unknown status: ${STATUS:-unknown}" + ;; + esac + + echo "" + echo "### Scenario Counts" + echo "| Metric | Count |" + echo "|--------|-------|" + echo "| Total Scenarios | ${TOTAL_TESTS:-0} |" + echo "| Passed Scenarios | ${PASSED_TESTS:-0} |" + echo "| Failed Scenarios | ${FAILED_TESTS:-0} |" + echo "| Skipped Scenarios | ${IGNORED_TESTS:-0} |" + echo "| Untested Scenarios | ${BEHAVE_UNTESTED_SCENARIOS:-0} |" + + echo "" + echo "### Behave Summary" + echo "| Metric | Summary |" + echo "|--------|---------|" + echo "| Features | ${BEHAVE_FEATURES_SUMMARY:-unavailable} |" + echo "| Scenarios | ${BEHAVE_SCENARIOS_SUMMARY:-unavailable} |" + echo "| Steps | ${BEHAVE_STEPS_SUMMARY:-unavailable} |" + } >> "$GITHUB_STEP_SUMMARY" || true + + - name: Collect gpAdminLogs + if: always() + shell: bash {0} + run: | + # gpsegsetuprecovery/gpsegrecovery and the management utilities log + # here, not to stdout, so without this a failed recovery leaves no + # evidence in the artifacts. Collect them as a tarball: some of the + # names contain a colon (gpgetstatususingtransition.py_cdw:gpadmin_*.log) + # and upload-artifact rejects those outright, failing the whole upload. + if [ -d /home/gpadmin/gpAdminLogs ]; then + tar czf build-logs/gpAdminLogs.tar.gz -C /home/gpadmin gpAdminLogs 2>/dev/null || true + ls -l build-logs/gpAdminLogs.tar.gz 2>/dev/null || echo "no gpAdminLogs collected" + else + echo "no gpAdminLogs directory" + fi + + - name: Upload behave logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: behave-logs-${{ matrix.test }}-${{ needs.build.outputs.build_timestamp || github.run_id }} + path: | + build-logs/ + retention-days: ${{ env.LOG_RETENTION_DAYS }} + + - name: Upload Behave Metadata + if: always() + uses: actions/upload-artifact@v4 + with: + name: behave-metadata-${{ matrix.test }} + path: | + test_results*.txt + retention-days: ${{ env.LOG_RETENTION_DAYS }} + + report: + name: Generate Apache Cloudberry Behave Report + needs: [check-skip, build, prepare-behave-matrix, behave] + if: always() && needs.check-skip.outputs.should_skip != 'true' + runs-on: ubuntu-22.04 + steps: + - name: Generate Final Report + run: | + { + echo "# Apache Cloudberry Behave Report" + echo "## Job Status" + echo "- Build Job: ${{ needs.build.result }}" + echo "- Behave Job: ${{ needs.behave.result }}" + echo "- Completion Time: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" + + if [[ "${{ needs.build.result }}" == "success" && + "${{ needs.behave.result }}" =~ ^(success|skipped)$ ]]; then + echo "✅ Pipeline completed successfully" + else + echo "⚠️ Pipeline completed with failures" + fi + } >> "$GITHUB_STEP_SUMMARY" + + - name: Notify on failure + if: | + needs.build.result != 'success' || + !contains(fromJson('["success","skipped"]'), needs.behave.result) + run: | + echo "::error::Behave pipeline failed! Check job summaries and logs for details" + echo "Timestamp: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" + echo "Build Result: ${{ needs.build.result }}" + echo "Behave Result: ${{ needs.behave.result }}" From c1fb70bd995851068128629583301eaa94f45a5d Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Sat, 12 Sep 2026 08:30:42 +0800 Subject: [PATCH 02/23] gpdemo: write an absolute TRUSTED_SHELL into the generated config demo_cluster.sh writes the cluster config with TRUSTED_SHELL="$(dirname "$0")/lalshell" left unexpanded, so the path is resolved when the file is sourced rather than when it is written. gpinitsystem sources it, and by then $0 is gpinitsystem, not demo_cluster.sh, so TRUSTED_SHELL points at a lalshell that is not there. Nothing noticed while the demo cluster was only ever created by demo_cluster.sh itself, but the Behave suite runs gpinitsystem -a -c ../gpAux/gpdemo/clusterConfigFile directly in a dozen scenarios. Expand the path at write time. --- gpAux/gpdemo/demo_cluster.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gpAux/gpdemo/demo_cluster.sh b/gpAux/gpdemo/demo_cluster.sh index 225bb76a5ee..7397894d359 100755 --- a/gpAux/gpdemo/demo_cluster.sh +++ b/gpAux/gpdemo/demo_cluster.sh @@ -314,8 +314,10 @@ cat >> $CLUSTER_CONFIG <<-EOF COORDINATOR_PORT=${COORDINATOR_DEMO_PORT} - # Shell to use to execute commands on all hosts - TRUSTED_SHELL="$(dirname "$0")/lalshell" + # Shell to use to execute commands on all hosts. Use an absolute path here + # because this file is later sourced by gpinitsystem, where \$0 is no longer + # demo_cluster.sh. + TRUSTED_SHELL=$(pwd)/lalshell ENCODING=UNICODE EOF From af546c55de726052963bb9652c8cf52f1d6371d7 Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Fri, 11 Sep 2026 12:23:52 +0800 Subject: [PATCH 03/23] gpcheckcat: add the mix_distribution_policy check Back-port from Greenplum a check Cloudberry was missing. mix_distribution_policy reports tables whose distribution policy mixes legacy and non-legacy hash opclasses, and cross-checks the result against the gp_use_legacy_hashops GUC so the operator is told which of the two states is inconsistent. Add the SQL fixtures the corresponding Behave scenarios load. Greenplum's companion ao_lastrownums check is deliberately not brought over: it reads pg_attribute_encoding.lastrownums, a column Cloudberry's catalog does not have, so the query errors out on every run and gpcheckcat reports failure on a freshly created database. --- gpMgmt/bin/gpcheckcat | 211 +++++++++++++++++- .../gppylib/test/unit/test_unit_gpcheckcat.py | 170 ++++++++++++++ .../create_legacy_hash_ops_tables.sql | 33 +++ .../create_non_legacy_hashops_tables.sql | 26 +++ 4 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 gpMgmt/test/behave/mgmt_utils/steps/data/gpcheckcat/create_legacy_hash_ops_tables.sql create mode 100644 gpMgmt/test/behave/mgmt_utils/steps/data/gpcheckcat/create_non_legacy_hashops_tables.sql diff --git a/gpMgmt/bin/gpcheckcat b/gpMgmt/bin/gpcheckcat index 00c2e4b21f3..68d93176032 100755 --- a/gpMgmt/bin/gpcheckcat +++ b/gpMgmt/bin/gpcheckcat @@ -1075,9 +1075,12 @@ def checkOwners(): a.rolname, m.rolname as coordinator_rolname from gp_dist_random('pg_class') r join pg_class c on (c.oid = r.oid) + left join pg_index i on (c.oid = i.indexrelid) left join pg_appendonly ao on (c.oid = ao.segrelid or c.oid = ao.blkdirrelid or - c.oid = ao.blkdiridxid) + c.oid = ao.visimaprelid or + i.indrelid = ao.blkdirrelid or + i.indrelid = ao.visimaprelid) left join pg_class o on (o.oid = ao.relid or o.reltoastrelid = c.oid) join pg_authid a on (a.oid = r.relowner) @@ -1948,6 +1951,203 @@ def checkOrphanedToastTables(): issue_type="orphaned_toast_tables", description='Repairing orphaned TOAST tables') +def fetch_guc_value(guc): + qry = ''' + show {} + '''.format(guc) + try: + conn = connect2(GV.cfg[GV.coordinator_dbid]) + curs = conn.query(qry) + rows = curs.getresult() + guc_value = rows[0][0] + return guc_value + + except Exception as e: + setError(ERROR_NOREPAIR) + GV.checkStatus = False + myprint('[ERROR] executing test: mix_distribution_policy') + myprint(' Execution error: ' + str(e)) + +def generateDistPolicyQueryFile(): + + query_sql = ''' + -- all tables that use legacy policy: + with legacy_opclass_oids(oid_array) as ( + select + array_agg(oid) + from + pg_opclass + where + opcfamily in ( + select + amprocfamily + from + pg_amproc + where + amproc :: oid in ( + 6140, 6141, 6142, 6143, 6144, 6145, 6146, + 6147, 6148, 6149, 6150, 6151, 6152, + 6153, 6154, 6155, 6156, 6157, 6158, + 6159, 6160, 6161, 6162, 6163, 6164, + 6165, 6166, 6167, 6168, 6170, 6169, + 6171 + ) + ) + ) + select + localoid :: regclass :: text as "Legacy Policy" + from + gp_distribution_policy, + legacy_opclass_oids + where + policytype = 'p' + and distclass :: oid[] && oid_array; + + -- all tables that don't use any legacy policy: + with legacy_opclass_oids(oid_array) as ( + select + array_agg(oid) + from + pg_opclass + where + opcfamily in ( + select + amprocfamily + from + pg_amproc + where + amproc :: oid in ( + 6140, 6141, 6142, 6143, 6144, 6145, 6146, + 6147, 6148, 6149, 6150, 6151, 6152, + 6153, 6154, 6155, 6156, 6157, 6158, + 6159, 6160, 6161, 6162, 6163, 6164, + 6165, 6166, 6167, 6168, 6170, 6169, + 6171 + ) + ) + ) +select + localoid :: regclass :: text as "Non Legacy Policy" +from + gp_distribution_policy, + legacy_opclass_oids +where + policytype = 'p' + and not (distclass :: oid[] && oid_array); + ''' + filename = 'gpcheckcat.distpolicy.sql' + + if not os.path.exists(filename) : + try: + with open(filename, 'w') as fp: + fp.write(query_sql + "\n") + except Exception as e: + logger.warning('Unable to generate verify file for {}'.format(filename)) + + +# Test to check if there are tables that use both legacy opclass/non legacy opclass +# in distribution policy +def checkMixDistPolicy() : + + qry = ''' + with legacy_opclass_oids(oid_array) as ( + select + array_agg(oid) + from + pg_opclass + where + opcfamily in ( + select + amprocfamily + from + pg_amproc + where + amproc :: oid in ( + 6140, 6141, 6142, 6143, 6144, 6145, 6146, + 6147, 6148, 6149, 6150, 6151, 6152, + 6153, 6154, 6155, 6156, 6157, 6158, + 6159, 6160, 6161, 6162, 6163, 6164, + 6165, 6166, 6167, 6168, 6170, 6169, + 6171 + ) + ) + ), + all_hash_ops(dc) as ( + select + distinct unnest(distclass :: oid[]) + from + gp_distribution_policy + ) + select + count(1) filter( + where + array[x.dc] && oid_array + ) as n_legacy_dist_class, + count(1) as n_total_dist_class + from + all_hash_ops x, + legacy_opclass_oids y; + ''' + + try: + conn = connect2(GV.cfg[GV.coordinator_dbid]) + curs = conn.query(qry) + rows = curs.getresult() + + if rows: + row = rows[0] + n_legacy_dist_class = row[0] + n_total_dist_class = row[1] + GV.checkStatus = False + + if n_legacy_dist_class > 0 and n_total_dist_class > n_legacy_dist_class : + generateDistPolicyQueryFile() + #if this condition is true then we have mix distribution Policy + myprint( + '[ERROR]: Found tables created using both legacy and non legacy hashops' + ' in distribution policy.' + 'Please run the gpcheckcat.distpolicy.sql file to list the tables.' + ) + else: + if (n_legacy_dist_class == 0 or n_legacy_dist_class == n_total_dist_class): + #if this condition is true then we dont have mix distribution policy + gp_use_legacy_hashops = fetch_guc_value("gp_use_legacy_hashops") + printDistPolicyMsg(gp_use_legacy_hashops, + n_legacy_dist_class, + n_total_dist_class + ) + + except Exception as e: + setError(ERROR_NOREPAIR) + GV.checkStatus = False + myprint('[ERROR] executing test: mix_distribution_policy') + myprint(' Execution error: ' + str(e)) + +def printDistPolicyMsg(gp_use_legacy_hashops,n_legacy_dist_class, n_total_dist_class): + + GV.checkStatus = True + + if n_total_dist_class - n_legacy_dist_class > 0 and gp_use_legacy_hashops == "on": + myprint( + '[ERROR]: GUC gp_use_legacy_hashops is on.' + ' all newly created tables will use legacy hash ops by default for hash distributed table, ' + 'but there are tables using non-legacy hash ops in the cluster. ' + 'Please run the gpcheckcat.distpolicy.sql file to list the tables.' + ) + GV.checkStatus = False + + elif n_legacy_dist_class == 0 and gp_use_legacy_hashops == "off": + GV.checkStatus = True + + elif n_legacy_dist_class > 0 and gp_use_legacy_hashops == "off" : + myprint( + '[ERROR]: GUC gp_use_legacy_hashops is off.' + ' all newly created tables will use non legacy hash ops by default for hash distributed table, ' + 'but there are tables using legacy hash ops in the cluster. ' + 'Please run the gpcheckcat.distpolicy.sql file to list the tables.' + ) + GV.checkStatus = False + ############################################################################ # Help populating repair part for all checked types @@ -2082,7 +2282,16 @@ all_checks = { "version": 'main', "order": 15, "online": False + }, + "mix_distribution_policy": + { + "description": "Check for tables that use legacy opclass in distribution policy", + "fn": lambda: checkMixDistPolicy(), + "version": 'main', + "order": 17, + "online": True } + } diff --git a/gpMgmt/bin/gppylib/test/unit/test_unit_gpcheckcat.py b/gpMgmt/bin/gppylib/test/unit/test_unit_gpcheckcat.py index ccdfb03a7ad..e4ecdf616a0 100755 --- a/gpMgmt/bin/gppylib/test/unit/test_unit_gpcheckcat.py +++ b/gpMgmt/bin/gppylib/test/unit/test_unit_gpcheckcat.py @@ -411,6 +411,176 @@ def count_joins(): self.num_batches += 1 self.num_joins = 0 self.num_starts = 0 + + @patch('gpcheckcat.connect2') + def test_checkMixDistPolicy_with_error_on_execution(self,mock_connect2): + # Mocking the database connection to raise an exception during execution + + mock_cursor = Mock() + mock_connect2.return_value.cursor.return_value.__enter__.return_value = mock_cursor + mock_cursor.execute.side_effect = Exception("Simulated error during execution") + + # Call the function to test + self.subject.checkMixDistPolicy() + + # Assertions + self.assertEqual(mock_cursor.execute.call_count, 1) + self.assertFalse(self.subject.GV.checkStatus) + + @patch('gpcheckcat.connect2') + def test_checkMixDistPolicy_exception_on_connect(self, mock_connect2): + # Mocking the database connection to raise an exception during connection + mock_connect2.side_effect = Exception("Simulated error during connection") + + # Call the function to test + self.subject.checkMixDistPolicy() + + # Assertions + self.assertEqual(mock_connect2.call_count, 1) + self.assertFalse(self.subject.GV.checkStatus) + + @patch('gpcheckcat.connect2') + def test_checkMixDistPolicy_exception_on_cursor_enter(self, mock_connect2): + # Mocking the database connection to raise an exception when entering the cursor context + mock_connect2.return_value.cursor.return_value.__enter__.side_effect = Exception("Simulated error entering cursor context") + + # Call the function to test + self.subject.checkMixDistPolicy() + + # Assertions + self.assertEqual(mock_connect2.call_count, 1) + self.assertFalse(self.subject.GV.checkStatus) + + @patch('gpcheckcat.connect2') + def test_fetch_guc_value_with_error_on_execution(self,mock_connect2): + # Mocking the database connection to raise an exception during execution + + mock_cursor = Mock() + mock_connect2.return_value.cursor.return_value.__enter__.return_value = mock_cursor + mock_cursor.execute.side_effect = Exception("Simulated error during execution") + + # Call the function to test + self.subject.fetch_guc_value("gp_use_legacy_hashops") + + # Assertions + self.assertEqual(mock_cursor.execute.call_count, 1) + self.assertEqual(mock_cursor.fetchone.call_count, 0) + self.subject.setError.assert_any_call(self.subject.ERROR_NOREPAIR) + self.assertFalse(self.subject.GV.checkStatus) + + @patch('gpcheckcat.connect2') + def test_fetch_guc_value_exception_on_connect(self, mock_connect2): + # Mocking the database connection to raise an exception during connection + mock_cursor = Mock() + mock_connect2.return_value.cursor.return_value.__enter__.return_value = mock_cursor + mock_connect2.side_effect = Exception("Simulated error during execution") + + # Call the function to test + self.subject.fetch_guc_value("gp_use_legacy_hashops") + + # Assertions + self.assertEqual(mock_cursor.execute.call_count, 0) + self.assertEqual(mock_connect2.call_count, 1) + self.assertEqual(mock_cursor.fetchone.call_count, 0) + self.subject.setError.assert_any_call(self.subject.ERROR_NOREPAIR) + self.assertFalse(self.subject.GV.checkStatus) + + @patch('gpcheckcat.connect2') + def test_fetch_guc_value_exception_on_cursor_enter(self, mock_connect2): + # Mocking the database connection to raise an exception on cursor context + mock_cursor = Mock() + #mock_connect2.return_value.cursor.return_value.__enter__.return_value = mock_cursor + mock_connect2.return_value.cursor.return_value = mock_cursor + mock_cursor.side_effect = Exception("Simulated error during execution") + + # Call the function to test + self.subject.fetch_guc_value("gp_use_legacy_hashops") + + # Assertions + self.assertEqual(mock_connect2.call_count, 1) + self.assertEqual(mock_cursor.execute.call_count, 0) + self.assertFalse(self.subject.GV.checkStatus) + self.assertEqual(mock_cursor.fetchone.call_count, 0) + self.subject.setError.assert_any_call(self.subject.ERROR_NOREPAIR) + + def test_generateDistPolicyQueryFile(self): + # Call the function + self.subject.generateDistPolicyQueryFile() + + # Check if the file is created + self.assertTrue(os.path.exists('gpcheckcat.distpolicy.sql')) + + # Read the file and check if it contains the expected query + with open('gpcheckcat.distpolicy.sql', 'r') as fp: + generated_query = fp.read() + + expected_query = ''' + -- all tables that use legacy policy: + with legacy_opclass_oids(oid_array) as ( + select + array_agg(oid) + from + pg_opclass + where + opcfamily in ( + select + amprocfamily + from + pg_amproc + where + amproc :: oid in ( + 6140, 6141, 6142, 6143, 6144, 6145, 6146, + 6147, 6148, 6149, 6150, 6151, 6152, + 6153, 6154, 6155, 6156, 6157, 6158, + 6159, 6160, 6161, 6162, 6163, 6164, + 6165, 6166, 6167, 6168, 6170, 6169, + 6171 + ) + ) + ) + select + localoid :: regclass :: text as "Legacy Policy" + from + gp_distribution_policy, + legacy_opclass_oids + where + policytype = 'p' + and distclass :: oid[] && oid_array; + + -- all tables that don't use any legacy policy: + with legacy_opclass_oids(oid_array) as ( + select + array_agg(oid) + from + pg_opclass + where + opcfamily in ( + select + amprocfamily + from + pg_amproc + where + amproc :: oid in ( + 6140, 6141, 6142, 6143, 6144, 6145, 6146, + 6147, 6148, 6149, 6150, 6151, 6152, + 6153, 6154, 6155, 6156, 6157, 6158, + 6159, 6160, 6161, 6162, 6163, 6164, + 6165, 6166, 6167, 6168, 6170, 6169, + 6171 + ) + ) + ) +select + localoid :: regclass :: text as "Non Legacy Policy" +from + gp_distribution_policy, + legacy_opclass_oids +where + policytype = 'p' + and not (distclass :: oid[] && oid_array); + ''' + self.assertEqual.__self__.maxDiff = None + self.assertTrue(generated_query.strip() == expected_query.strip()) class Global(): def __init__(self): self.opt = {} diff --git a/gpMgmt/test/behave/mgmt_utils/steps/data/gpcheckcat/create_legacy_hash_ops_tables.sql b/gpMgmt/test/behave/mgmt_utils/steps/data/gpcheckcat/create_legacy_hash_ops_tables.sql new file mode 100644 index 00000000000..d79ac75a184 --- /dev/null +++ b/gpMgmt/test/behave/mgmt_utils/steps/data/gpcheckcat/create_legacy_hash_ops_tables.sql @@ -0,0 +1,33 @@ +set gp_use_legacy_hashops = 1; + +create table t_old(a int, b int, c int) distributed by (a, b); +create table t1_old(a int, b int, c int) distributed by (a, b); +create table t_replicate_old(a int , b int) distributed replicated; +create table t_random_old(a int , b int) distributed randomly; + +CREATE TABLE rank_old (id int, rank int, year int, gender + char(1), count int) +DISTRIBUTED BY (id) +PARTITION BY RANGE (year) +( START (2006) END (2016) EVERY (1), + DEFAULT PARTITION extra ); + + +CREATE OR REPLACE FUNCTION random_between(low INT ,high INT) + RETURNS INT AS +$$ +BEGIN + RETURN floor(random()* (high-low + 1) + low); +END; +$$ language 'plpgsql' STRICT; + +insert into rank_old +select i, i, random_between(2005, 2017), 'g', i +from generate_series(1, 100000)i; + +-- some special characters in column names +create table t_space("a col" int); +create table t_dot("a.col" int); +create table t_dash("a-col" int); +create table t_multispecial("a col" int, "b.col" int, "c-col" int) distributed by ("a col", "b.col", "c-col"); + diff --git a/gpMgmt/test/behave/mgmt_utils/steps/data/gpcheckcat/create_non_legacy_hashops_tables.sql b/gpMgmt/test/behave/mgmt_utils/steps/data/gpcheckcat/create_non_legacy_hashops_tables.sql new file mode 100644 index 00000000000..cf6e89d7d9e --- /dev/null +++ b/gpMgmt/test/behave/mgmt_utils/steps/data/gpcheckcat/create_non_legacy_hashops_tables.sql @@ -0,0 +1,26 @@ +set gp_use_legacy_hashops = 0; + +create table t_new(a int, b int, c int) distributed by (a, b); +create table t1_new(a int, b int, c int) distributed by (a, b); +create table t_replicate_new(a int , b int) distributed replicated; +create table t_random_new(a int , b int) distributed randomly; + +CREATE TABLE rank_new (id int, rank int, year int, gender + char(1), count int) +DISTRIBUTED BY (id) +PARTITION BY RANGE (year) +( START (2006) END (2016) EVERY (1), + DEFAULT PARTITION extra ); + + +CREATE OR REPLACE FUNCTION random_between(low INT ,high INT) + RETURNS INT AS +$$ +BEGIN + RETURN floor(random()* (high-low + 1) + low); +END; +$$ language 'plpgsql' STRICT; + +insert into rank_new +select i, i, random_between(2005, 2017), 'g', i +from generate_series(1, 100000)i; From a73f819bc431e764d1b8d1f803f8a8f1fdeeebfe Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Fri, 11 Sep 2026 12:24:00 +0800 Subject: [PATCH 04/23] gpinitsystem: set the mirror's own port after pg_basebackup CREATE_QES_MIRROR seeds the mirror with pg_basebackup, which copies the primary's postgresql.conf verbatim -- including the primary's port. The mirror was then started with START_QE before that port was corrected, so on a single host it either bound the wrong port or collided with its own primary. Rewrite postgresql.conf before starting the segment. Greenplum has the same SED_PG_CONF call but places it after START_QE, which leaves the same window open there. Also emit explicit 127.0.0.1/32 and ::1/128 trust entries in the coordinator's pg_hba.conf. The hostname-based entry alone is not enough when the loopback address the client picks depends on the host's IPv4 and IPv6 resolution order. --- gpMgmt/bin/lib/gp_bash_functions.sh | 5 +++++ gpMgmt/bin/lib/gpcreateseg.sh | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/gpMgmt/bin/lib/gp_bash_functions.sh b/gpMgmt/bin/lib/gp_bash_functions.sh index de76620da2b..e42b993470a 100755 --- a/gpMgmt/bin/lib/gp_bash_functions.sh +++ b/gpMgmt/bin/lib/gp_bash_functions.sh @@ -783,6 +783,11 @@ BUILD_COORDINATOR_PG_HBA_FILE () { else $ECHO "host all $USER_NAME localhost trust" >> ${GP_DIR}/$PG_HBA $ECHO "host all $USER_NAME $COORDINATOR_HOSTNAME trust" >> ${GP_DIR}/$PG_HBA + # Also add explicit IPv4 and IPv6 loopback entries so that + # connections via 127.0.0.1 and ::1 are always accepted + # regardless of hostname resolution order. + $ECHO "host all $USER_NAME 127.0.0.1/32 trust" >> ${GP_DIR}/$PG_HBA + $ECHO "host all $USER_NAME ::1/128 trust" >> ${GP_DIR}/$PG_HBA fi diff --git a/gpMgmt/bin/lib/gpcreateseg.sh b/gpMgmt/bin/lib/gpcreateseg.sh index 5dd0f5b0006..78883f9463d 100755 --- a/gpMgmt/bin/lib/gpcreateseg.sh +++ b/gpMgmt/bin/lib/gpcreateseg.sh @@ -224,6 +224,20 @@ CREATE_QES_MIRROR () { fi RUN_COMMAND_REMOTE ${PRIMARY_HOSTADDRESS} "${EXPORT_GPHOME}; . ${GPHOME}/cloudberry-env.sh; cat - >> ${PRIMARY_DIR}/pg_hba.conf; pg_ctl -D ${PRIMARY_DIR} reload" <<< "${PG_HBA_ENTRIES}" RUN_COMMAND_REMOTE ${GP_HOSTADDRESS} "${EXPORT_GPHOME}; . ${GPHOME}/cloudberry-env.sh; rm -rf ${GP_DIR}; ${GPHOME}/bin/pg_basebackup --wal-method=stream --create-slot --slot='internal_wal_replication_slot' -R -c fast -E ./db_dumps -D ${GP_DIR} -h ${PRIMARY_HOSTADDRESS} -p ${PRIMARY_PORT} --target-gp-dbid ${GP_DBID};" + # pg_basebackup copies the primary's postgresql.conf verbatim, so the + # mirror would otherwise start on the primary's port. Append the right + # one before starting; the last setting in the file wins. + # + # $PORT_TXT ("#port") finds nothing in the copied file -- the port line is + # already active there -- so SED_PG_CONF takes its append path. That is + # what we want: its replace path runs an unanchored + # "s// #/", which with a search string of "port" + # would also rewrite every "support", "report" and "transport" in the + # file's comments. + LOG_MSG "[INFO][$INST_COUNT]:-Configuring segment $PG_CONF" + SED_PG_CONF ${GP_DIR}/$PG_CONF "$PORT_TXT" port=$GP_PORT 0 $GP_HOSTADDRESS + RETVAL=$? + PARA_EXIT $RETVAL "Update port number to $GP_PORT" START_QE "-w" RETVAL=$? PARA_EXIT $RETVAL "pg_basebackup of segment data directory from ${PRIMARY_HOSTADDRESS} to ${GP_HOSTADDRESS}" From 48e0d0ad03aac7b1bb8bae719c96c03190b0e22a Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Fri, 11 Sep 2026 12:24:08 +0800 Subject: [PATCH 05/23] Wait for promotion before reporting ready with promote_trigger_file Cloudberry keeps the promote_trigger_file GUC that PostgreSQL 16 removed, because gpactivatestandby's force path promotes a standby coordinator by creating the trigger file and then starting the server in utility mode. CheckForStandbyTrigger no longer honoured the GUC, so that path never promoted. Restore the check, and close the race it exposes in the postmaster: with hot_standby off, PM_STATUS_STANDBY is reported as soon as recovery starts, which "pg_ctl -w" treats as ready. gpstart would then connect before promotion finished and fail with "the database system is not accepting connections". Extend the existing promotion_requested guard to cover a configured trigger file that is already present, so pg_ctl waits for PM_STATUS_READY. --- src/backend/access/transam/xlogrecovery.c | 26 +++++++++++++++++++++++ src/backend/postmaster/postmaster.c | 20 +++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c index 63b4c323a61..28be3b44c72 100644 --- a/src/backend/access/transam/xlogrecovery.c +++ b/src/backend/access/transam/xlogrecovery.c @@ -4575,6 +4575,8 @@ SetPromoteIsTriggered(void) static bool CheckForStandbyTrigger(void) { + struct stat stat_buf; + if (LocalPromoteIsTriggered) return true; @@ -4587,6 +4589,30 @@ CheckForStandbyTrigger(void) return true; } + /* + * Cloudberry retains the promote_trigger_file GUC (removed upstream in + * PostgreSQL 16) because management utilities such as gpactivatestandby + * rely on it to promote a standby coordinator that is started in utility + * mode. Honor the GUC here: the presence of the configured file ends + * recovery, matching the documented behavior of the GUC. + */ + if (PromoteTriggerFile == NULL || strcmp(PromoteTriggerFile, "") == 0) + return false; + + if (stat(PromoteTriggerFile, &stat_buf) == 0) + { + ereport(LOG, + (errmsg("promote trigger file found: %s", PromoteTriggerFile))); + unlink(PromoteTriggerFile); + SetPromoteIsTriggered(); + return true; + } + else if (errno != ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat promote trigger file \"%s\": %m", + PromoteTriggerFile))); + return false; } diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 12c8649b159..b00199abea1 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -5755,6 +5755,26 @@ process_pm_pmsignal(void) if (recoveryTargetAction == RECOVERY_TARGET_ACTION_PROMOTE) promotion_requested = true; + /* + * GPDB: A configured promote_trigger_file whose file is already + * present means a promotion is imminent (for example + * gpactivatestandby's force path creates the trigger file before + * starting the standby coordinator in utility mode). Treat that the + * same as an explicit promotion request so that, with hot standby + * disabled, we do not prematurely report PM_STATUS_STANDBY. Otherwise + * "pg_ctl -w" would return as soon as recovery starts and the caller + * (gpstart) would try to read the catalog before the server has + * actually finished promoting and can accept connections. + */ + if (!promotion_requested && + PromoteTriggerFile != NULL && PromoteTriggerFile[0] != '\0') + { + struct stat stat_buf; + + if (stat(PromoteTriggerFile, &stat_buf) == 0) + promotion_requested = true; + } + /* * If we aren't planning to enter hot standby mode later, treat * RECOVERY_STARTED as meaning we're out of startup, and report status From 9de95bd5b64c679b86935e07a43a92848131c03c Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Fri, 11 Sep 2026 12:24:24 +0800 Subject: [PATCH 06/23] minirepro: dump the tables a view reads minirepro asks gp_dump_query_oids which objects a query touches, and for a query over a view the answer is the view alone. The resulting dump cannot reproduce the plan, because the base tables and -- more to the point -- their statistics are missing. Expand view dependencies through pg_rewrite so the tables behind a view are dumped alongside it, and build the SQL literal by doubling quotes instead of calling Escape(), which mangled non-ASCII query text. --- gpMgmt/bin/minirepro | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/gpMgmt/bin/minirepro b/gpMgmt/bin/minirepro index 79d4c9fbb31..9a93389b14c 100755 --- a/gpMgmt/bin/minirepro +++ b/gpMgmt/bin/minirepro @@ -74,6 +74,11 @@ sysnslist = "('pg_toast', 'pg_bitmapindex', 'pg_catalog', 'information_schema', # unset search path due to CVE-2018-1058 pgoptions = '-c optimizer=off -c gp_role=utility -c search_path=' + +def escape_sql_literal(value): + return value.replace("'", "''") + + class MRQuery(object): def __init__(self): self.schemas = [] @@ -136,7 +141,7 @@ def dump_query(connectionInfo, query_file): with open(query_file, 'r') as query_f: sql_text = query_f.read() - query = "select pg_catalog.gp_dump_query_oids('%s')" % Escape(sql_text) + query = "select pg_catalog.gp_dump_query_oids('%s')" % escape_sql_literal(sql_text) toolkit_sql = PATH_PREFIX + 'toolkit.sql' with open(toolkit_sql, 'w') as toolkit_f: @@ -165,6 +170,27 @@ def parse_oids(cursor, json_oids): if len(result.funcids) == 0: result.funcids = '0' + # In PG16, gp_dump_query_oids may return only view OIDs without their + # dependent table OIDs. Expand view dependencies so pg_dump includes + # the tables that views reference. + dep_query = "SELECT DISTINCT d.refobjid FROM pg_depend d " \ + "JOIN pg_class c ON d.classid = 'pg_rewrite'::regclass " \ + "JOIN pg_rewrite r ON d.objid = r.oid " \ + "WHERE r.ev_class IN (%s) AND d.refobjid != r.ev_class " \ + "AND d.refclassid = 'pg_class'::regclass " \ + "AND d.refobjid NOT IN (%s)" % (result.relids, result.relids) + try: + cursor.execute(dep_query) + dep_oids = [str(row[0]) for row in result_iter(cursor)] + if dep_oids: + result.relids = result.relids + ',' + ','.join(dep_oids) + except pgdb.DatabaseError as e: + # The dump is still usable without the view's base tables, so warn + # and carry on with the OIDs gp_dump_query_oids gave us. + sys.stderr.write('\nWarning: could not expand view dependencies; ' + 'the dump may be missing tables referenced by views.\n\n' + + str(e) + '\n\n') + cat_query = "SELECT distinct(nspname) FROM pg_class c, pg_namespace n WHERE " \ "c.relnamespace = n.oid AND c.oid IN (%s) " \ "AND n.nspname NOT IN %s" % (result.relids, sysnslist) From 26287c68679b4efa2e467c5b6dec53e0d973eecd Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Fri, 11 Sep 2026 14:45:09 +0800 Subject: [PATCH 07/23] analyzedb: do not escape identifiers with pg.escape_string PyGreSQL's escape_string is a C function that encodes its argument as ASCII, so it raises UnicodeEncodeError on any table or schema name containing non-ASCII characters, and analyzedb exits with status 2 on a database it can otherwise analyze perfectly well. With standard_conforming_strings on -- the default -- doubling single quotes is the whole of the escaping a string literal needs, so build the literal directly. Exercised by the "analyzedb can handle the table name with special utf-8 characters" Behave scenario. --- gpMgmt/bin/analyzedb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gpMgmt/bin/analyzedb b/gpMgmt/bin/analyzedb index 48d8e16872c..d91cba85aab 100755 --- a/gpMgmt/bin/analyzedb +++ b/gpMgmt/bin/analyzedb @@ -982,7 +982,11 @@ def get_oid_str(table_list): def regclass_schema_tbl(schema, tbl): schema_tbl = "%s.%s" % (escape_identifier(schema), escape_identifier(tbl)) - return "to_regclass('%s')" % (pg.escape_string(schema_tbl)) + # Not pg.escape_string(): PyGreSQL's C implementation encodes its argument + # as ASCII and raises UnicodeEncodeError on a table or schema name that + # contains non-ASCII characters. With standard_conforming_strings on -- the + # default -- doubling single quotes is the whole of the escaping needed. + return "to_regclass('%s')" % schema_tbl.replace("'", "''") # Escape double-quotes in a string, so that the resulting string is suitable for From 3fec0c9c84968281640cea447b51707156defc09 Mon Sep 17 00:00:00 2001 From: Dianjin Wang Date: Fri, 11 Sep 2026 12:24:39 +0800 Subject: [PATCH 08/23] test/behave: align the gpMgmt suite with the Greenplum test suite The Behave suite had drifted from the one it was forked from: scenarios added upstream for gprecoverseg, gpmovemirrors, gpaddmirrors, gpexpand, gpcheckcat, gpstop and minirepro were missing, several existing ones had diverged, and a number of step definitions carried only one of the @given/@when/@then decorators Greenplum registers, so features using "And" could not match them and Behave reported the steps as undefined even though the implementation was right there. Bring the feature files and their step definitions back in line with github.com/greenplum-db/gpdb-archive, keeping the Cloudberry-specific adjustments where the two products genuinely differ. Differential recovery is the one place they differ outright: Cloudberry has no "gprecoverseg --differential", so those scenarios cannot pass here. Tag them @differential and filter them out of the gprecoverseg matrix entry, splitting the Examples tables that mix differential with full and incremental so the other variants keep running. The scenarios stay in place, aligned with Greenplum, ready to be enabled with the feature. --- .github/workflows/behave-cloudberry.yml | 2 +- gpMgmt/test/behave/mgmt_utils/environment.py | 31 +- .../behave/mgmt_utils/gpaddmirrors.feature | 80 +- .../test/behave/mgmt_utils/gpcheckcat.feature | 228 ++++- .../test/behave/mgmt_utils/gpexpand.feature | 251 ++++- .../behave/mgmt_utils/gpinitsystem.feature | 4 +- .../behave/mgmt_utils/gpmovemirrors.feature | 215 ++++- .../behave/mgmt_utils/gprecoverseg.feature | 899 +++++++++++++++--- gpMgmt/test/behave/mgmt_utils/gpssh.feature | 3 +- gpMgmt/test/behave/mgmt_utils/gpstart.feature | 46 +- gpMgmt/test/behave/mgmt_utils/gpstate.feature | 4 +- gpMgmt/test/behave/mgmt_utils/gpstop.feature | 151 ++- .../test/behave/mgmt_utils/minirepro.feature | 78 +- .../mgmt_utils/replication_slots.feature | 20 +- .../behave/mgmt_utils/steps/mgmt_utils.py | 106 ++- .../mgmt_utils/steps/minirepro_mgmt_utils.py | 50 +- .../mgmt_utils/steps/mirrors_mgmt_utils.py | 51 +- .../mgmt_utils/steps/recoverseg_mgmt_utils.py | 78 +- .../steps/replication_slots_utils.py | 3 +- .../mgmt_utils/steps/tablespace_mgmt_utils.py | 14 +- gpMgmt/test/behave_utils/utils.py | 16 + 21 files changed, 1999 insertions(+), 331 deletions(-) diff --git a/.github/workflows/behave-cloudberry.yml b/.github/workflows/behave-cloudberry.yml index 255448b1ef8..637d70e167c 100644 --- a/.github/workflows/behave-cloudberry.yml +++ b/.github/workflows/behave-cloudberry.yml @@ -158,7 +158,7 @@ jobs: }, {"test":"ic-behave-gprecoverseg", "behave_features":["test/behave/mgmt_utils/gprecoverseg.feature"], - "behave_args":"--tags ~@concourse_cluster --tags ~@extended" + "behave_args":"--tags ~@concourse_cluster --tags ~@extended --tags ~@differential" }, {"test":"ic-behave-gpreload","behave_features":["test/behave/mgmt_utils/gpreload.feature"]}, {"test":"ic-behave-gpstart", diff --git a/gpMgmt/test/behave/mgmt_utils/environment.py b/gpMgmt/test/behave/mgmt_utils/environment.py index d79f9c18acc..a8a2f9be828 100644 --- a/gpMgmt/test/behave/mgmt_utils/environment.py +++ b/gpMgmt/test/behave/mgmt_utils/environment.py @@ -11,6 +11,7 @@ from steps.gpssh_exkeys_mgmt_utils import GpsshExkeysMgmtContext from steps.mgmt_utils import backup_bashrc, restore_bashrc from gppylib.db import dbconn +from gppylib.commands.base import Command, REMOTE def before_all(context): if list(map(int, behave.__version__.split('.'))) < [1,2,6]: @@ -62,19 +63,27 @@ def before_feature(context, feature): dbconn.execSQL(context.conn, 'create table t1(a integer, b integer)') dbconn.execSQL(context.conn, 'create table t2(c integer, d integer)') dbconn.execSQL(context.conn, 'create table t3(e integer, f integer)') + dbconn.execSQL(context.conn, 'create table spiegelungssätze(col_ä integer, 列2 integer)') dbconn.execSQL(context.conn, 'create view v1 as select a, b from t1, t3 where t1.a=t3.e') dbconn.execSQL(context.conn, 'create view v2 as select c, d from t2, t3 where t2.c=t3.f') dbconn.execSQL(context.conn, 'create view v3 as select a, d from v1, v2 where v1.a=v2.c') dbconn.execSQL(context.conn, 'insert into t1 values(1, 2)') dbconn.execSQL(context.conn, 'insert into t2 values(1, 3)') dbconn.execSQL(context.conn, 'insert into t3 values(1, 4)') + dbconn.execSQL(context.conn, 'insert into spiegelungssätze values(1, 5)') + # minirepro tests require statistical data about the contents of the database + # we should execute 'ANALYZE' to fill the pg_statistic catalog table. + dbconn.execSQL(context.conn, 'analyze t1') + dbconn.execSQL(context.conn, 'analyze t2') + dbconn.execSQL(context.conn, 'analyze t3') + dbconn.execSQL(context.conn, 'analyze spiegelungssätze') + dbconn.execSQL(context.conn, 'create or replace function select_one() returns integer as $$ select 1 $$ language sql') context.conn.commit() if 'gppkg' in feature.tags: run_command(context, 'bash demo/gppkg/generate_sample_gppkg.sh buildGppkg') run_command(context, 'cp -f /tmp/sample-gppkg/sample.gppkg test/behave/mgmt_utils/steps/data/') - def after_feature(context, feature): if 'analyzedb' in feature.tags: context.conn.close() @@ -102,6 +111,9 @@ def before_scenario(context, scenario): if 'gprecoverseg' in context.feature.tags: context.mirror_context = MirrorMgmtContext() + if 'gprecoverseg_newhost' in context.feature.tags: + context.mirror_context = MirrorMgmtContext() + if 'gpconfig' in context.feature.tags: context.gpconfig_context = GpConfigContext() @@ -146,11 +158,17 @@ def after_scenario(context, scenario): return tags_to_cleanup = ['gpmovemirrors', 'gpssh-exkeys'] - if set(context.feature.tags).intersection(tags_to_cleanup): + if set(context.feature.tags).intersection(tags_to_cleanup) and "skip_cleanup" not in scenario.effective_tags: if 'temp_base_dir' in context and os.path.exists(context.temp_base_dir): os.chmod(context.temp_base_dir, 0o700) shutil.rmtree(context.temp_base_dir) + if 'umount_required' in context and context.umount_required: + context.execute_steps(''' + # unmounting all mounter filesystem in concourse cluster + Then umount all mounted filesystem + ''') + tags_to_not_restart_db = ['analyzedb', 'gpssh-exkeys'] if not set(context.feature.tags).intersection(tags_to_not_restart_db): start_database_if_not_started(context) @@ -182,3 +200,12 @@ def after_scenario(context, scenario): execute_sql('postgres', create_fault_query) reset_fault_query = "SELECT gp_inject_fault_infinite('all', 'reset', dbid) FROM gp_segment_configuration WHERE status='u';" execute_sql('postgres', reset_fault_query) + + if os.getenv('SUSPEND_PG_REWIND') is not None: + del os.environ['SUSPEND_PG_REWIND'] + + if "remove_rsync_bash" in scenario.effective_tags: + for host in context.hosts_with_rsync_bash: + cmd = Command(name='remove /usr/local/bin/rsync', cmdStr="sudo rm /usr/local/bin/rsync", remoteHost=host, + ctxt=REMOTE) + cmd.run(validateAfter=True) diff --git a/gpMgmt/test/behave/mgmt_utils/gpaddmirrors.feature b/gpMgmt/test/behave/mgmt_utils/gpaddmirrors.feature index e65e782938d..9817183b579 100644 --- a/gpMgmt/test/behave/mgmt_utils/gpaddmirrors.feature +++ b/gpMgmt/test/behave/mgmt_utils/gpaddmirrors.feature @@ -5,6 +5,8 @@ Feature: Tests for gpaddmirrors And a tablespace is created with data When gpaddmirrors adds 3 mirrors And an FTS probe is triggered + #gpaddmirrors triggers full recovery where old replication slot is dropped and new one is created + And verify replication slot internal_wal_replication_slot is available on all the segments And the segments are synchronized Then verify the database has mirrors And the tablespace is valid @@ -24,6 +26,8 @@ Feature: Tests for gpaddmirrors And an FTS probe is triggered And the segments are synchronized And verify the database has mirrors + #gpaddmirrors triggers full recovery where old replication slot is dropped and new one is created + And verify replication slot internal_wal_replication_slot is available on all the segments And the tablespace is valid And user stops all primary processes And user can start transactions @@ -49,8 +53,10 @@ Feature: Tests for gpaddmirrors When gpaddmirrors adds 3 mirrors Then gpaddmirrors should return a return code of 0 + And gpaddmirrors should not print "Unable to kill walsender on primary" to stdout And verify the database has mirrors And the segments are synchronized + And check segment conf: postgresql.conf And user can start transactions Scenario: gpaddmirrors setup recovery part two @@ -162,7 +168,7 @@ Feature: Tests for gpaddmirrors And the user reset the walsender on the primary on content 0 And the user waits until saved async process is completed And recovery_progress.file should not exist in gpAdminLogs in gpAdminLogs - And the user waits until mirror on content 0,1,2 is up + And verify that mirror on content 0,1,2 is up And check if mirrors on content 0,1,2 are moved to new location on input file And verify there are no recovery backout files @@ -174,7 +180,19 @@ Feature: Tests for gpaddmirrors And all the segments are running And the segments are synchronized + And check segment conf: postgresql.conf + And all files in gpAdminLogs directory are deleted + Scenario: gpaddmirrors errors out if the directory for the mirror to be added is not empty + Given the cluster is generated with "3" primaries only + And all files in gpAdminLogs directory are deleted + And a gaddmirrors directory under '/tmp' with mode '0700' is created + And a gpaddmirrors input file is created + And edit the input file to add mirror with content 0,1,2 to a new non-empty directory with mode 0700 + When the user runs gpaddmirrors with input file and additional args "-a" + Then gpaddmirrors should print "Segment directory '/tmp/.*' exists but is not empty!" to stdout + And all the segments are running + And check segment conf: postgresql.conf And all files in gpAdminLogs directory are deleted @@ -191,7 +209,7 @@ Feature: Tests for gpaddmirrors # And the user waits until recovery_progress.file is created in gpAdminLogs and verifies its format # And the user waits until saved async process is completed # And recovery_progress.file should not exist in gpAdminLogs -# And the user waits until mirror on content 0,1,2 is up +# And verify that mirror on content 0,1,2 is up # # And check if mirrors on content 0,1,2 are moved to new location on input file # And verify there are no recovery backout files @@ -214,20 +232,21 @@ Feature: Tests for gpaddmirrors Scenario: spread mirroring configuration Given a working directory of the test as '/tmp/gpaddmirrors' And the database is not running - And a cluster is created with "spread" segment mirroring on "mdw" and "sdw1, sdw2, sdw3" + And a cluster is created with "spread" segment mirroring on "cdw" and "sdw1, sdw2, sdw3" Then verify that mirror segments are in "spread" configuration Given a preferred primary has failed When the user runs "gprecoverseg -a" Then gprecoverseg should return a return code of 0 And all the segments are running And the segments are synchronized + And check segment conf: postgresql.conf And the user runs "gpstop -aqM fast" @concourse_cluster Scenario Outline: gpaddmirrors can add mirrors even if mirrors failed during basebackup Given a working directory of the test as '/tmp/gpaddmirrors' And the database is not running - And a cluster is created with no mirrors on "mdw" and "sdw1, sdw2" + And a cluster is created with no mirrors on "cdw" and "sdw1, sdw2" And all files in gpAdminLogs directory are deleted on all hosts in the cluster And a gpaddmirrors directory under '/tmp' with mode '0700' is created And a gpaddmirrors input file is created @@ -269,7 +288,7 @@ Feature: Tests for gpaddmirrors Scenario Outline: gpaddmirrors can add mirrors even if start fails for mirrors Given a working directory of the test as '/tmp/gpaddmirrors' And the database is not running - And a cluster is created with no mirrors on "mdw" and "sdw1, sdw2" + And a cluster is created with no mirrors on "cdw" and "sdw1, sdw2" And all files in gpAdminLogs directory are deleted on all hosts in the cluster And a gpaddmirrors directory under '/tmp' with mode '0700' is created And a gpaddmirrors input file is created @@ -309,13 +328,14 @@ Feature: Tests for gpaddmirrors Scenario: gprecoverseg works correctly on a newly added mirror with HBA_HOSTNAMES=0 Given a working directory of the test as '/tmp/gpaddmirrors' And the database is not running - And with HBA_HOSTNAMES "0" a cluster is created with no mirrors on "mdw" and "sdw1, sdw2" + And with HBA_HOSTNAMES "0" a cluster is created with no mirrors on "cdw" and "sdw1, sdw2" And pg_hba file "/tmp/gpaddmirrors/data/primary/gpseg0/pg_hba.conf" on host "sdw1" contains only cidr addresses And gpaddmirrors adds mirrors And pg_hba file "/tmp/gpaddmirrors/data/primary/gpseg0/pg_hba.conf" on host "sdw1" contains only cidr addresses And pg_hba file "/tmp/gpaddmirrors/data/primary/gpseg0/pg_hba.conf" on host "sdw1" contains entries for "samehost" And verify that the file "pg_hba.conf" in each segment data directory has "no" line starting with "host.*replication.*\(127.0.0\|::1\).*trust" Then verify the database has mirrors + And gpaddmirrors should not print "Unable to kill walsender on primary" to stdout Then the mirror on content 0 is stopped with the immediate flag And an FTS probe is triggered @@ -348,11 +368,12 @@ Feature: Tests for gpaddmirrors Scenario: gprecoverseg works correctly on a newly added mirror with HBA_HOSTNAMES=1 Given a working directory of the test as '/tmp/gpaddmirrors' And the database is not running - And with HBA_HOSTNAMES "1" a cluster is created with no mirrors on "mdw" and "sdw1, sdw2" - And pg_hba file "/tmp/gpaddmirrors/data/primary/gpseg0/pg_hba.conf" on host "sdw1" contains entries for "mdw, sdw1" + And with HBA_HOSTNAMES "1" a cluster is created with no mirrors on "cdw" and "sdw1, sdw2" + And pg_hba file "/tmp/gpaddmirrors/data/primary/gpseg0/pg_hba.conf" on host "sdw1" contains entries for "cdw, sdw1" And gpaddmirrors adds mirrors with options "--hba-hostnames" - And pg_hba file "/tmp/gpaddmirrors/data/primary/gpseg0/pg_hba.conf" on host "sdw1" contains entries for "mdw, sdw1, sdw2, samehost" + And pg_hba file "/tmp/gpaddmirrors/data/primary/gpseg0/pg_hba.conf" on host "sdw1" contains entries for "cdw, sdw1, sdw2, samehost" Then verify the database has mirrors + And gpaddmirrors should not print "Unable to kill walsender on primary" to stdout When the mirror on content 0 is stopped with the immediate flag And an FTS probe is triggered @@ -385,50 +406,56 @@ Feature: Tests for gpaddmirrors Scenario: gpaddmirrors puts mirrors on the same hosts when there is a standby configured Given a working directory of the test as '/tmp/gpaddmirrors' And the database is not running - And a cluster is created with no mirrors on "mdw" and "sdw1, sdw2, sdw3" + And a cluster is created with no mirrors on "cdw" and "sdw1, sdw2, sdw3" And gpaddmirrors adds mirrors - Then verify the database has mirrors + Then gpaddmirrors should not print "Unable to kill walsender on primary" to stdout + And verify the database has mirrors And save the gparray to context And the database is not running - And a cluster is created with no mirrors on "mdw" and "sdw1, sdw2, sdw3" + And a cluster is created with no mirrors on "cdw" and "sdw1, sdw2, sdw3" And the user runs gpinitstandby with options " " Then gpinitstandby should return a return code of 0 And gpaddmirrors adds mirrors Then mirror hostlist matches the one saved in context + And check segment conf: postgresql.conf And the user runs "gpstop -aqM fast" @concourse_cluster Scenario: gpaddmirrors puts mirrors on different host Given a working directory of the test as '/tmp/gpaddmirrors' And the database is not running - And a cluster is created with no mirrors on "mdw" and "sdw1, sdw2, sdw3" + And a cluster is created with no mirrors on "cdw" and "sdw1, sdw2, sdw3" And gpaddmirrors adds mirrors in spread configuration Then verify that mirror segments are in "spread" configuration + And check segment conf: postgresql.conf And the user runs "gpstop -aqM fast" @concourse_cluster Scenario: gpaddmirrors with a default coordinator data directory Given a working directory of the test as '/tmp/gpaddmirrors' And the database is not running - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And gpaddmirrors adds mirrors - Then verify the database has mirrors + Then gpaddmirrors should not print "Unable to kill walsender on primary" to stdout + And verify the database has mirrors + And check segment conf: postgresql.conf And the user runs "gpstop -aqM fast" @concourse_cluster Scenario: gpaddmirrors with a given coordinator data directory [-d ] Given a working directory of the test as '/tmp/gpaddmirrors' And the database is not running - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And gpaddmirrors adds mirrors with temporary data dir Then verify the database has mirrors + And check segment conf: postgresql.conf And the user runs "gpstop -aqM fast" @concourse_cluster Scenario: gpaddmirrors mirrors are recognized after a cluster restart Given a working directory of the test as '/tmp/gpaddmirrors' And the database is not running - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" When gpaddmirrors adds mirrors Then verify the database has mirrors When an FTS probe is triggered @@ -438,13 +465,25 @@ Feature: Tests for gpaddmirrors And wait until the process "gpstart" goes down Then all the segments are running And the segments are synchronized + And check segment conf: postgresql.conf + And the user runs "gpstop -aqM fast" + + @concourse_cluster + Scenario: gpaddmirrors should create consistent port entry on mirrors postgresql.conf file + Given a working directory of the test as '/tmp/gpaddmirrors' + And the database is not running + And a cluster is created with no mirrors on "cdw" and "sdw1" + When gpaddmirrors adds mirrors + Then gpaddmirrors should not print "Unable to kill walsender on primary" to stdout + And verify the database has mirrors + And check segment conf: postgresql.conf And the user runs "gpstop -aqM fast" @concourse_cluster Scenario: gpaddmirrors when the primaries have data Given a working directory of the test as '/tmp/gpaddmirrors' And the database is not running - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And database "gptest" exists And there is a "heap" table "public.heap_table" in "gptest" with "100" rows And there is a "ao" table "public.ao_table" in "gptest" with "100" rows @@ -463,10 +502,11 @@ Feature: Tests for gpaddmirrors Scenario: tablespaces work on a multi-host environment Given a working directory of the test as '/tmp/gpaddmirrors' And the database is not running - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And a tablespace is created with data When gpaddmirrors adds mirrors - Then verify the database has mirrors + Then gpaddmirrors should not print "Unable to kill walsender on primary" to stdout + And verify the database has mirrors When an FTS probe is triggered And the segments are synchronized diff --git a/gpMgmt/test/behave/mgmt_utils/gpcheckcat.feature b/gpMgmt/test/behave/mgmt_utils/gpcheckcat.feature index d9b91838909..696f8a07008 100644 --- a/gpMgmt/test/behave/mgmt_utils/gpcheckcat.feature +++ b/gpMgmt/test/behave/mgmt_utils/gpcheckcat.feature @@ -9,6 +9,9 @@ Feature: gpcheckcat tests Given database "all_good" is dropped and recreated Then the user runs "gpcheckcat -A" Then gpcheckcat should return a return code of 0 + When the user runs "gpcheckcat -C pg_class" + Then gpcheckcat should return a return code of 0 + And gpcheckcat should not print "Execution error:" to stdout And the user runs "dropdb all_good" Scenario: gpcheckcat should drop leaked schemas @@ -125,6 +128,46 @@ Feature: gpcheckcat tests Then gpcheckcat should print "Extra" to stdout And gpcheckcat should print "Table miss_attr_db4.public.heap_table.1" to stdout + Scenario: gpcheckcat should report inconsistent pg_fastsequence.lastrownums values with gp_fastsequence for AO tables + Given database "errorneous_lastrownums" is dropped and recreated + And the user runs "psql errorneous_lastrownums -c "create table errlastrownum(a int) using ao_row; insert into errlastrownum select * from generate_series(1,100);"" + And the user runs "psql errorneous_lastrownums -c "alter table errlastrownum add column newcol int;"" + When the user runs "gpcheckcat -R ao_lastrownums errorneous_lastrownums" + Then gpcheckcat should return a return code of 0 + When the user runs sql "set allow_system_table_mods=on; update gp_fastsequence set last_sequence = 0 where last_sequence > 0;" in "errorneous_lastrownums" on first primary segment + When the user runs "gpcheckcat -R ao_lastrownums errorneous_lastrownums" + Then gpcheckcat should return a return code of 3 + And gpcheckcat should print "Failed test\(s\) that are not reported here: ao_lastrownums" to stdout + Given database "errorneous_lastrownums" is dropped and recreated + And the user runs "psql errorneous_lastrownums -c "create table errlastrownum(a int) using ao_row; insert into errlastrownum select * from generate_series(1,10);"" + And the user runs "psql errorneous_lastrownums -c "alter table errlastrownum add column newcol int;"" + When the user runs "gpcheckcat -R ao_lastrownums errorneous_lastrownums" + Then gpcheckcat should return a return code of 0 + Then the user runs sql "set allow_system_table_mods=on; delete from gp_fastsequence where last_sequence > 0;" in "errorneous_lastrownums" on first primary segment + When the user runs "gpcheckcat -R ao_lastrownums errorneous_lastrownums" + Then gpcheckcat should return a return code of 3 + And gpcheckcat should print "Failed test\(s\) that are not reported here: ao_lastrownums" to stdout + + Scenario: gpcheckcat should report inconsistent pg_fastsequence.lastrownums values with gp_fastsequence for AOCO tables + Given database "errorneous_lastrownums" is dropped and recreated + And the user runs "psql errorneous_lastrownums -c "create table errlastrownum(a int) using ao_column; insert into errlastrownum select * from generate_series(1,100);"" + And the user runs "psql errorneous_lastrownums -c "alter table errlastrownum add column newcol int;"" + When the user runs "gpcheckcat -R ao_lastrownums errorneous_lastrownums" + Then gpcheckcat should return a return code of 0 + When the user runs sql "set allow_system_table_mods=on; update gp_fastsequence set last_sequence = 0 where last_sequence > 0;" in "errorneous_lastrownums" on first primary segment + When the user runs "gpcheckcat -R ao_lastrownums errorneous_lastrownums" + Then gpcheckcat should return a return code of 3 + And gpcheckcat should print "Failed test\(s\) that are not reported here: ao_lastrownums" to stdout + Given database "errorneous_lastrownums" is dropped and recreated + And the user runs "psql errorneous_lastrownums -c "create table errlastrownum(a int) using ao_column; insert into errlastrownum select * from generate_series(1,10);"" + And the user runs "psql errorneous_lastrownums -c "alter table errlastrownum add column newcol int;"" + When the user runs "gpcheckcat -R ao_lastrownums errorneous_lastrownums" + Then gpcheckcat should return a return code of 0 + Then the user runs sql "set allow_system_table_mods=on; delete from gp_fastsequence where last_sequence > 0;" in "errorneous_lastrownums" on first primary segment + When the user runs "gpcheckcat -R ao_lastrownums errorneous_lastrownums" + Then gpcheckcat should return a return code of 3 + And gpcheckcat should print "Failed test\(s\) that are not reported here: ao_lastrownums" to stdout + Scenario: gpcheckcat should report and repair owner errors and produce timestamped repair scripts Given database "owner_db1" is dropped and recreated And database "owner_db2" is dropped and recreated @@ -155,6 +198,27 @@ Feature: gpcheckcat tests And the user runs "dropdb owner_db2" And the path "gpcheckcat.repair.*" is removed from current working directory + Scenario: gpcheckcat should report and repair owner errors on appendonly tables and its indexes + Given database "owner_db" is dropped and recreated + And the path "gpcheckcat.repair.*" is removed from current working directory + And there is a "ao" table "public.gpadmin_ao_tbl" in "owner_db" with data + And the user runs "psql owner_db -c "CREATE INDEX gpadmin_ao_tbl_idx on gpadmin_ao_tbl (column1);"" + And the user runs sql "alter table gpadmin_ao_tbl OWNER TO wolf" in "owner_db" on first primary segment + Then psql should return a return code of 0 + + When the user runs "gpcheckcat -R owner owner_db" + Then gpcheckcat should return a return code of 3 + Then the path "gpcheckcat.repair.*" is found in cwd "1" times + + When the user runs all the repair scripts in the dir "gpcheckcat.repair.*" + And the path "gpcheckcat.repair.*" is removed from current working directory + And the user runs "gpcheckcat -R owner owner_db" + Then Then gpcheckcat should return a return code of 0 + Then the path "gpcheckcat.repair.*" is found in cwd "0" times + + And the user runs "dropdb owner_db" + And the path "gpcheckcat.repair.*" is removed from current working directory + Scenario: gpcheckcat should report and repair invalid constraints Given database "constraint_db" is dropped and recreated And the path "gpcheckcat.repair.*" is removed from current working directory @@ -317,7 +381,7 @@ Feature: gpcheckcat tests And the user runs "psql extra_pk_db -c 'CREATE SCHEMA my_pk_schema' " And the user runs "psql extra_pk_db -f test/behave/mgmt_utils/steps/data/gpcheckcat/add_operator.sql " Then psql should return a return code of 0 - And the user runs "psql extra_pk_db -c "set allow_system_table_mods=true;DELETE FROM pg_catalog.pg_operator where oprname='!#'" " + And the user runs sql "set allow_system_table_mods=true;DELETE FROM pg_catalog.pg_operator where oprname='!#'" in "extra_pk_db" on first primary segment Then psql should return a return code of 0 When the user runs "gpcheckcat -R missing_extraneous extra_pk_db" Then gpcheckcat should return a return code of 3 @@ -435,7 +499,7 @@ Feature: gpcheckcat tests Then gpcheckcat should print "Table pg_type has a dependency issue on oid .* at content 0" to stdout And the user runs "dropdb gpcheckcat_dependency" - Scenario: gpcheckcat should report no inconsistency of pg_extension between Master and Segements + Scenario: gpcheckcat should report no inconsistency of pg_extension between Coordinator and Segements Given database "pgextension_db" is dropped and recreated And the user runs sql "set allow_system_table_mods=true;update pg_extension set extconfig='{2130}', extcondition='{2130}';" in "pgextension_db" on first primary segment Then the user runs "gpcheckcat -R inconsistent pgextension_db" @@ -651,11 +715,6 @@ Feature: gpcheckcat tests And the user runs "dropdb check_dependency_error" And the user runs "psql -d postgres -c "DROP ROLE foo"" - -########################### @concourse_cluster tests ########################### -# The @concourse_cluster tag denotes the scenario that requires a remote cluster - - @concourse_cluster Scenario Outline: gpcheckcat should discover missing attributes for external tables Given database "miss_attr_db3" is dropped and recreated And the user runs "echo > /tmp/backup_gpfdist_dummy" @@ -675,42 +734,42 @@ Feature: gpcheckcat tests | attrname | tablename | | ftrelid | pg_foreign_table | -# GPDB_12_MERGE_FIXME: -# 1, this case is removed because 12 partitioning implementation will not record pg_constraint, right? -# 2, gpcheckcat in the concourse only runs 1 or 2 tests, how about merging into another task? -# -# @concourse_cluster -# Scenario Outline: gpcheckcat should discover missing attributes for external tables -# Given database "miss_attr_db3" is dropped and recreated -# And the user runs "echo > /tmp/backup_gpfdist_dummy" -# And the user runs "gpfdist -p 8098 -d /tmp &" -# And there is a partition table "part_external" has external partitions of gpfdist with file "backup_gpfdist_dummy" on port "8098" in "miss_attr_db3" with data -# Then data for partition table "part_external" with leaf partition distributed across all segments on "miss_attr_db3" -# When the user runs "gpcheckcat miss_attr_db3" -# And gpcheckcat should return a return code of 0 -# Then gpcheckcat should not print "Missing" to stdout -# And the user runs "psql miss_attr_db3 -c "SET allow_system_table_mods=true; DELETE FROM where ='part_external_1_prt_p_2'::regclass::oid;"" -# Then psql should return a return code of 0 -# When the user runs "gpcheckcat miss_attr_db3" -# Then gpcheckcat should print "Missing" to stdout -# And gpcheckcat should print "part_external_1_prt_p_2_check" to stdout -# Examples: -# | attrname | tablename | -# | conrelid | pg_constraint | -# - - Scenario: gpcheckcat should discover missing attributes of pg_description catalogue table - Given there is a "heap" table "public.heap_table" in "miss_attr_db5" with data and description - When the user runs "gpcheckcat -v miss_attr_db5" + Scenario Outline: gpcheckcat should discover missing attributes for external tables + Given database "miss_attr_db3" is dropped and recreated + And the user runs "echo > /tmp/backup_gpfdist_dummy" + And the user runs "gpfdist -p 8098 -d /tmp &" + And there is a partition table "part_external" has external partitions of gpfdist with file "backup_gpfdist_dummy" on port "8098" in "miss_attr_db3" with data + Then data for partition table "part_external" with leaf partition distributed across all segments on "miss_attr_db3" + When the user runs "gpcheckcat miss_attr_db3" And gpcheckcat should return a return code of 0 Then gpcheckcat should not print "Missing" to stdout - And the user runs "psql miss_attr_db5 -c "SET allow_system_table_mods=true; DELETE FROM pg_description where objoid='heap_table'::regclass::oid;"" + And the user runs "psql miss_attr_db3 -c "SET allow_system_table_mods=true; DELETE FROM where ='part_external_1_prt_p_2';"" Then psql should return a return code of 0 - When the user runs "gpcheckcat -v miss_attr_db5" + When the user runs "gpcheckcat miss_attr_db3" + Then gpcheckcat should print "Missing" to stdout + And gpcheckcat should print "Name of test which found this issue: missing_extraneous_pg_class" to stdout + And gpcheckcat should print "Relation name: part_external_1_prt_p_2" to stdout + Examples: + | attrname | tablename | + | relname | pg_class | + + Scenario: gpcheckcat should discover missing attributes of pg_description and pg_shdescription catalogue table without errors + Given database "miss_attr_db5" is dropped and recreated + And there is a "heap" table "public.heap_table" in "miss_attr_db5" with data and description + And a tablespace is created with data and description + When the user runs "gpcheckcat miss_attr_db5" + Then gpcheckcat should return a return code of 0 + And gpcheckcat should not print "Missing" to stdout + When the user runs "psql miss_attr_db5 -c "SET allow_system_table_mods=true; DELETE FROM pg_description where objoid='heap_table'::regclass::oid;"" + Then psql should return a return code of 0 + When the user runs "psql miss_attr_db5 -c "SET allow_system_table_mods=true; DELETE FROM pg_shdescription where objoid=(SELECT oid from pg_tablespace where spcname='outerspace');"" + Then psql should return a return code of 0 + When the user runs "gpcheckcat miss_attr_db5" Then gpcheckcat should print "Missing description metadata of {.*} on content -1" to stdout And gpcheckcat should not print "Execution error:" to stdout And gpcheckcat should print "Name of test which found this issue: missing_extraneous_pg_description" to stdout - + Then gpcheckcat should print "Missing shdescription metadata of {.*} on content -1" to stdout + And gpcheckcat should print "Name of test which found this issue: missing_extraneous_pg_shdescription" to stdout Scenario: set multiple GUC at session level in gpcheckcat Given database "all_good" is dropped and recreated @@ -741,5 +800,96 @@ Feature: gpcheckcat tests And "gpstop -m" should return a return code of 0 And the user runs "gpstart -a" - - + Scenario: Validate if gpecheckcat throws error when there are tables created using mix distribution policy + Given database "hashops_db" is dropped and recreated + And the user runs "psql hashops_db -f test/behave/mgmt_utils/steps/data/gpcheckcat/create_legacy_hash_ops_tables.sql" + Then psql should return a return code of 0 + And the user runs "psql hashops_db -f test/behave/mgmt_utils/steps/data/gpcheckcat/create_non_legacy_hashops_tables.sql" + Then psql should return a return code of 0 + When the user runs "gpcheckcat -R mix_distribution_policy hashops_db " + And gpcheckcat should print "Found tables created using both legacy and non legacy hashops in distribution policy." to stdout + And gpcheckcat should print "Please run the gpcheckcat.distpolicy.sql file to list the tables." to stdout + And the user runs "dropdb hashops_db" + + Scenario: Validate if gpcheckcat succeeds and there are no tables + Given database "hashops_db" is dropped and recreated + When the user runs "gpcheckcat -R mix_distribution_policy hashops_db" + And gpcheckcat should print "PASSED" to stdout + And the user runs "dropdb hashops_db" + + Scenario: Validate if gpcheckcat throws error when GUC gp_use_legacy_hashops is on and there are non legacy tables + Given database "hashops_db" is dropped and recreated + And the user runs "psql hashops_db -f test/behave/mgmt_utils/steps/data/gpcheckcat/create_non_legacy_hashops_tables.sql" + Then psql should return a return code of 0 + And the user runs "gpconfig -c gp_use_legacy_hashops -v on --skipvalidation" + Then gpconfig should return a return code of 0 + And the user runs "gpstop -a" + Then gpstop should return a return code of 0 + And the user runs "gpstart -a" + When the user runs "gpcheckcat -R mix_distribution_policy hashops_db" + And gpcheckcat should print "GUC gp_use_legacy_hashops is on." to stdout + And gpcheckcat should print "all newly created tables will use legacy hash ops by default for hash distributed table," to stdout + And gpcheckcat should print "but there are tables using non-legacy hash ops in the cluster." to stdout + And gpcheckcat should print "Please run the gpcheckcat.distpolicy.sql file to list the tables." to stdout + And the user runs "dropdb hashops_db" + + Scenario: Validate if gpcheckcat succeeds when GUC gp_use_legacy_hashops is on and there are legacy tables + Given database "hashops_db" is dropped and recreated + And the user runs "psql hashops_db -f test/behave/mgmt_utils/steps/data/gpcheckcat/create_legacy_hash_ops_tables.sql" + Then psql should return a return code of 0 + And the user runs "gpconfig -c gp_use_legacy_hashops -v on --skipvalidation" + Then gpconfig should return a return code of 0 + And the user runs "gpstop -a" + Then gpstop should return a return code of 0 + And the user runs "gpstart -a" + When the user runs "gpcheckcat -R mix_distribution_policy hashops_db" + And gpcheckcat should print "PASSED" to stdout + And the user runs "dropdb hashops_db" + + Scenario: Validate if gpcheckcat throws error when GUC gp_use_legacy_hashops is off and there are legacy tables + Given database "hashops_db" is dropped and recreated + And the user runs "psql hashops_db -f test/behave/mgmt_utils/steps/data/gpcheckcat/create_legacy_hash_ops_tables.sql" + And the user runs "gpconfig -c gp_use_legacy_hashops -v off --skipvalidation" + Then gpconfig should return a return code of 0 + And the user runs "gpstop -a" + Then gpstop should return a return code of 0 + And the user runs "gpstart -a" + When the user runs "gpcheckcat -R mix_distribution_policy hashops_db" + And gpcheckcat should print "GUC gp_use_legacy_hashops is off." to stdout + And gpcheckcat should print "all newly created tables will use non legacy hash ops by default for hash distributed table," to stdout + And gpcheckcat should print "but there are tables using legacy hash ops in the cluster." to stdout + And gpcheckcat should print "Please run the gpcheckcat.distpolicy.sql file to list the tables." to stdout + And the user runs "dropdb hashops_db" + + Scenario: Validate if gpcheckcat succeeds when GUC gp_use_legacy_hashops is off and there are non legacy tables + Given database "hashops_db" is dropped and recreated + And the user runs "psql hashops_db -f test/behave/mgmt_utils/steps/data/gpcheckcat/create_non_legacy_hashops_tables.sql" + And the user runs "gpconfig -c gp_use_legacy_hashops -v off --skipvalidation" + Then gpconfig should return a return code of 0 + And the user runs "gpstop -a" + Then gpstop should return a return code of 0 + And the user runs "gpstart -a" + When the user runs "gpcheckcat -R mix_distribution_policy hashops_db" + And gpcheckcat should print "PASSED" to stdout + And the user runs "dropdb hashops_db" + + Scenario: gpcheckcat -l should report mix_distribution_policy to stdout + When the user runs "gpcheckcat -l " + And gpcheckcat should print "mix_distribution_policy" to stdout + + Scenario: gpcheckcat report all tables created using legacy opclass on multiple database + Given database "hashops_db" is dropped and recreated + And the user runs "psql hashops_db -f test/behave/mgmt_utils/steps/data/gpcheckcat/create_legacy_hash_ops_tables.sql" + And the user runs "psql hashops_db -f test/behave/mgmt_utils/steps/data/gpcheckcat/create_non_legacy_hashops_tables.sql" + Then psql should return a return code of 0 + Given database "hashops_db2" is dropped and recreated + And the user runs "psql hashops_db2 -f test/behave/mgmt_utils/steps/data/gpcheckcat/create_legacy_hash_ops_tables.sql" + And the user runs "psql hashops_db2 -f test/behave/mgmt_utils/steps/data/gpcheckcat/create_non_legacy_hashops_tables.sql" + Then psql should return a return code of 0 + When the user runs "gpcheckcat -A -R mix_distribution_policy" + And gpcheckcat should print "Found tables created using both legacy and non legacy hashops in distribution policy." to stdout + And gpcheckcat should print "Please run the gpcheckcat.distpolicy.sql file to list the tables." to stdout + Then gpcheckcat should print "Completed 1 test(s) on database 'hashops_db'" to logfile with latest timestamp + Then gpcheckcat should print "Completed 1 test(s) on database 'hashops_db2'" to logfile with latest timestamp + And the user runs "dropdb hashops_db" + And the user runs "dropdb hashops_db2" diff --git a/gpMgmt/test/behave/mgmt_utils/gpexpand.feature b/gpMgmt/test/behave/mgmt_utils/gpexpand.feature index 90bc88836d4..6bf58246def 100644 --- a/gpMgmt/test/behave/mgmt_utils/gpexpand.feature +++ b/gpMgmt/test/behave/mgmt_utils/gpexpand.feature @@ -7,11 +7,11 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And the coordinator pid has been saved And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1" + And the cluster is setup for an expansion on hosts "cdw,sdw1" When the user runs gpexpand interview to add 2 new segment and 0 new host "ignored.host" Then the number of segments have been saved And user has created expansiontest tables @@ -33,11 +33,11 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And the user runs gpinitstandby with options " " And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1" + And the cluster is setup for an expansion on hosts "cdw,sdw1" When the user runs gpexpand interview to add 2 new segment and 0 new host "ignored.host" Then user has created expansiontest tables And 4000000 rows are inserted into table "expansiontest0" in schema "public" with column type list "int" @@ -53,10 +53,10 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1" + And the cluster is setup for an expansion on hosts "cdw,sdw1" When the user runs gpexpand interview to add 2 new segment and 0 new host "ignored.host" Then the number of segments have been saved And user has created expansiontest tables @@ -76,10 +76,10 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1" + And the cluster is setup for an expansion on hosts "cdw,sdw1" When the user runs gpexpand interview to add 2 new segment and 0 new host "ignored.host" Then the number of segments have been saved When the user runs gpexpand with the latest gpexpand_inputfile with additional parameters "--silent" @@ -91,10 +91,10 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1,sdw2" + And the cluster is setup for an expansion on hosts "cdw,sdw1,sdw2" And the new host "sdw2" is ready to go When the user runs gpexpand interview to add 0 new segment and 1 new host "sdw2" Then the number of segments have been saved @@ -107,10 +107,10 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1,sdw2" + And the cluster is setup for an expansion on hosts "cdw,sdw1,sdw2" And the new host "sdw2" is ready to go When the user runs gpexpand interview to add 1 new segment and 1 new host "sdw2" Then the number of segments have been saved @@ -123,10 +123,10 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with mirrors on "mdw" and "sdw1" + And a cluster is created with mirrors on "cdw" and "sdw1" And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1" + And the cluster is setup for an expansion on hosts "cdw,sdw1" And the number of segments have been saved When the user runs gpexpand with a static inputfile for a single-node cluster with mirrors Then verify that the cluster has 4 new segments @@ -137,9 +137,9 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with mirrors on "mdw" and "sdw1" + And a cluster is created with mirrors on "cdw" and "sdw1" And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1" + And the cluster is setup for an expansion on hosts "cdw,sdw1" And the user runs gpexpand with a static inputfile for a two-node cluster with mirrors And expanded preferred primary on segment "3" has failed When the user runs "gprecoverseg -a" @@ -157,10 +157,10 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with mirrors on "mdw" and "sdw1" + And a cluster is created with mirrors on "cdw" and "sdw1" And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1,sdw2,sdw3" + And the cluster is setup for an expansion on hosts "cdw,sdw1,sdw2,sdw3" And the new host "sdw2,sdw3" is ready to go When the user runs gpexpand interview to add 0 new segment and 2 new host "sdw2,sdw3" Then the number of segments have been saved @@ -174,11 +174,11 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with mirrors on "mdw" and "sdw1" + And a cluster is created with mirrors on "cdw" and "sdw1" And the user runs gpinitstandby with options " " And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1,sdw2,sdw3" + And the cluster is setup for an expansion on hosts "cdw,sdw1,sdw2,sdw3" And the new host "sdw2,sdw3" is ready to go When the user runs gpexpand interview to add 1 new segment and 2 new host "sdw2,sdw3" Then the number of segments have been saved @@ -192,13 +192,13 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with mirrors on "mdw" and "sdw1" + And a cluster is created with mirrors on "cdw" and "sdw1" And the user runs gpinitstandby with options " " And database "gptest" exists And a tablespace is created with data And another tablespace is created with data And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1,sdw2,sdw3" + And the cluster is setup for an expansion on hosts "cdw,sdw1,sdw2,sdw3" And the new host "sdw2,sdw3" is ready to go When the user runs gpexpand interview to add 1 new segment and 2 new host "sdw2,sdw3" Then the number of segments have been saved @@ -234,6 +234,59 @@ Feature: expand the cluster by adding more segments When the user runs gpexpand to redistribute Then the tablespace is valid after gpexpand + @gpexpand_icproxy + Scenario: Cluster expansion failed (no new proxy address) with IC proxy mode enabled + Given the database is not running + And a working directory of the test as '/data/gpdata/gpexpand' + And the user runs command "rm -rf /data/gpdata/gpexpand/*" + And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into + And a cluster is created with no mirrors on "cdw" and "sdw1" + And the coordinator pid has been saved + And database "gptest" exists + And there are no gpexpand_inputfiles + And the cluster is running in IC proxy mode + And the cluster is setup for an expansion on hosts "cdw" + And the user runs gpexpand interview to add 1 new segment and 0 new host "ignore.host" + And the number of segments have been saved + When the user runs gpexpand with the latest gpexpand_inputfile without ret code check + Then gpexpand should return a return code of 3 + And gpexpand should print "Checking ICProxy addresses failed" to stdout + + @gpexpand_icproxy + Scenario: Cluster expansion failed (bind an wrong proxy address) with IC proxy mode enabled + Given the database is not running + And a working directory of the test as '/data/gpdata/gpexpand' + And the user runs command "rm -rf /data/gpdata/gpexpand/*" + And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into + And a cluster is created with no mirrors on "cdw" and "sdw1" + And the coordinator pid has been saved + And database "gptest" exists + And there are no gpexpand_inputfiles + And the cluster is running in IC proxy mode with new proxy address 4:2:cdw:16502 + And the cluster is setup for an expansion on hosts "cdw" + And the user runs gpexpand interview to add 1 new segment and 0 new host "ignore.host" + And the number of segments have been saved + When the user runs gpexpand with the latest gpexpand_inputfile without ret code check + Then gpexpand should return a return code of 3 + And gpexpand should print "The ic_proxy process failed to bind or listen" to stdout + + @gpexpand_icproxy + Scenario: Cluster expansion successful with IC proxy mode enabled + Given the database is not running + And a working directory of the test as '/data/gpdata/gpexpand' + And the user runs command "rm -rf /data/gpdata/gpexpand/*" + And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into + And a cluster is created with no mirrors on "cdw" and "sdw1" + And the coordinator pid has been saved + And database "gptest" exists + And there are no gpexpand_inputfiles + And the cluster is running in IC proxy mode with new proxy address 4:2:sdw1:16502 + And the cluster is setup for an expansion on hosts "cdw" + And the user runs gpexpand interview to add 1 new segment and 0 new host "ignore.host" + And the number of segments have been saved + When the user runs gpexpand with the latest gpexpand_inputfile without ret code check + Then gpexpand should return a return code of 0 + @gpexpand_verify_redistribution Scenario: Verify data is correctly redistributed after expansion Given the database is not running @@ -278,11 +331,11 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with mirrors on "mdw" and "sdw1" + And a cluster is created with mirrors on "cdw" and "sdw1" And database "gptest" exists And the user runs psql with "-f /home/gpadmin/sqldump/dump.sql" against database "gptest" And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1,sdw2,sdw3" + And the cluster is setup for an expansion on hosts "cdw,sdw1,sdw2,sdw3" And the new host "sdw2,sdw3" is ready to go And the user runs gpexpand interview to add 1 new segment and 2 new host "sdw2,sdw3" And the number of segments have been saved @@ -297,11 +350,11 @@ Feature: expand the cluster by adding more segments And a working directory of the test as '/data/gpdata/gpexpand' And the user runs command "rm -rf /data/gpdata/gpexpand/*" And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And the coordinator pid has been saved And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1" + And the cluster is setup for an expansion on hosts "cdw,sdw1" And the user runs gpexpand interview to add 2 new segment and 0 new host "ignored.host" And the number of segments have been saved And user has created test table @@ -321,11 +374,11 @@ Feature: expand the cluster by adding more segments And a working directory of the test as '/data/gpdata/gpexpand' And the user runs command "rm -rf /data/gpdata/gpexpand/*" And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And the coordinator pid has been saved And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw" + And the cluster is setup for an expansion on hosts "cdw" And the user runs gpexpand interview to add 1 new segment and 0 new host "ignore.host" And the number of segments have been saved When the user runs gpexpand with the latest gpexpand_inputfile with additional parameters "--silent" @@ -343,14 +396,14 @@ Feature: expand the cluster by adding more segments And a working directory of the test as '/data/gpdata/gpexpand' And the user runs command "rm -rf /data/gpdata/gpexpand/*" And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And the coordinator pid has been saved And database "gptest" exists And user has created test table And 20 rows are inserted into table "test" in schema "public" with column type list "int" And a long-run read-only transaction exists on "test" And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw" + And the cluster is setup for an expansion on hosts "cdw" And the user runs gpexpand interview to add 1 new segment and 0 new host "ignore.host" And the number of segments have been saved When the user runs gpexpand with the latest gpexpand_inputfile with additional parameters "--silent" @@ -368,12 +421,12 @@ Feature: expand the cluster by adding more segments And a working directory of the test as '/data/gpdata/gpexpand' And the user runs command "rm -rf /data/gpdata/gpexpand/*" And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And the coordinator pid has been saved And database "gptest" exists And a long-run transaction starts And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw" + And the cluster is setup for an expansion on hosts "cdw" And the user runs gpexpand interview to add 1 new segment and 0 new host "ignore.host" And the number of segments have been saved When the user runs gpexpand with the latest gpexpand_inputfile with additional parameters "--silent" @@ -391,11 +444,11 @@ Feature: expand the cluster by adding more segments And a working directory of the test as '/data/gpdata/gpexpand' And the user runs command "rm -rf /data/gpdata/gpexpand/*" And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And the coordinator pid has been saved And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw" + And the cluster is setup for an expansion on hosts "cdw" And the user runs gpexpand interview to add 1 new segment and 0 new host "ignore.host" And the number of segments have been saved And the transactions are started for dml @@ -418,11 +471,11 @@ Feature: expand the cluster by adding more segments And a working directory of the test as '/tmp/gpexpand_behave' And the user runs command "rm -rf /tmp/gpexpand_behave/*" And a temporary directory under "/tmp/gpexpand_behave/expandedData" to expand into - And a cluster is created with no mirrors on "mdw" and "sdw1" + And a cluster is created with no mirrors on "cdw" and "sdw1" And database "gptest" exists And create database schema table with special character And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1" + And the cluster is setup for an expansion on hosts "cdw,sdw1" And the number of segments have been saved And the user runs gpexpand interview to add 1 new segment and 0 new host "ignored.host" When the user runs gpexpand with the latest gpexpand_inputfile without ret code check @@ -492,10 +545,10 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with mirrors on "mdw" and "sdw1" + And a cluster is created with mirrors on "cdw" and "sdw1" And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1,sdw2,sdw3" + And the cluster is setup for an expansion on hosts "cdw,sdw1,sdw2,sdw3" And the new host "sdw2,sdw3" is ready to go When the user runs gpexpand interview to add 0 new segment and 2 new host "sdw2,sdw3" Then the number of segments have been saved @@ -507,11 +560,11 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with mirrors on "mdw" and "sdw1" + And a cluster is created with mirrors on "cdw" and "sdw1" And the user runs gpinitstandby with options " " And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1" + And the cluster is setup for an expansion on hosts "cdw,sdw1" And the primary on content 0 is stopped And user can start transactions And an FTS probe is triggered @@ -524,11 +577,11 @@ Feature: expand the cluster by adding more segments Given the database is not running And a working directory of the test as '/data/gpdata/gpexpand' And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into - And a cluster is created with mirrors on "mdw" and "sdw1" + And a cluster is created with mirrors on "cdw" and "sdw1" And the user runs gpinitstandby with options " " And database "gptest" exists And there are no gpexpand_inputfiles - And the cluster is setup for an expansion on hosts "mdw,sdw1" + And the cluster is setup for an expansion on hosts "cdw,sdw1" And the primary on content 0 is stopped And user can start transactions And an FTS probe is triggered @@ -540,3 +593,117 @@ Feature: expand the cluster by adding more segments When the user runs gpexpand with a static inputfile for a single-node cluster with mirrors without ret code check Then gpexpand should return a return code of 0 And gpexpand should print "One or more segments are either down or not in preferred role." to stdout + + @gpexpand_no_mirrors + + @gpexpand_segment + Scenario: Gpexpand should succeed when there has event trigger + Given the database is not running + And a working directory of the test as '/data/gpdata/gpexpand' + And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into + And the cluster is generated with "1" primaries only + And database "gptest" exists + And the user runs psql with "-c 'create table t(a int)'" against database "gptest" + And create event trigger function + And the user runs psql with "-c 'create event trigger log_alter on ddl_command_end execute function notcie_ddl()'" against database "gptest" + And there are no gpexpand_inputfiles + And the cluster is setup for an expansion on hosts "localhost" + When the user runs gpexpand interview to add 1 new segment and 0 new host "ignored.host" + Then the number of segments have been saved + When the user runs gpexpand with the latest gpexpand_inputfile with additional parameters "--silent" + Then verify that the cluster has 1 new segments + And the user runs psql with "-c 'alter table t add column b int'" against database "gptest" + + @gpexpand_segment + Scenario: expand a cluster and verify necessary catalog tables are copied to new segments + Given the database is not running + And a working directory of the test as '/data/gpdata/gpexpand' + And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into + And a cluster is created with no mirrors on "cdw" and "sdw1" + And database "gptest" exists + And the user runs psql with "-c 'CREATE ROLE abc; ALTER ROLE abc DENY DAY 0 DENY DAY 2 DENY BETWEEN DAY 4 AND DAY 5;'" against database "gptest" + And there are no gpexpand_inputfiles + And the cluster is setup for an expansion on hosts "cdw,sdw1" + When the user runs gpexpand interview to add 2 new segment and 0 new host "ignored.host" + Then the number of segments have been saved + When the user runs gpexpand with the latest gpexpand_inputfile with additional parameters "--silent" + Then verify that the cluster has 2 new segments + And verify that "pg_description" catalog table is present on new segments + And verify that "pg_shdescription" catalog table is present on new segments + And verify that "pg_auth_time_constraint" catalog table is present on new segments + When the user runs "gpcheckcat gptest" + Then gpcheckcat should return a return code of 0 + And the user runs psql with "-c 'DROP ROLE abc'" against database "gptest" + + @gpexpand_mirrors + @gpexpand_segment + @gpexpand_verify_catalogs + Scenario: expand a cluster that has mirrors and check that gpexpand does not copy extra data directories from master + Given the database is not running + # need to remove this log because otherwise SCAN_LOG may pick up a previous error/warning in the log + And the user runs command "rm -rf ~/gpAdminLogs/gpinitsystem*" + And a working directory of the test as '/data/gpdata/gpexpand' + And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into + And a cluster is created with mirrors on "cdw" and "sdw1" + And database "gptest" exists + And the user runs command "analyzedb -d gptest -a" + And there are no gpexpand_inputfiles + And the cluster is setup for an expansion on hosts "cdw,sdw1" + And the number of segments have been saved + When the user runs gpexpand with a static inputfile for a single-node cluster with mirrors + Then verify that the cluster has 4 new segments + And verify that the path "db_dumps" in each segment data directory does not exist + And verify that the path "gpperfmon/data" in each segment data directory does not exist + And verify that the path "gpperfmon/logs" in each segment data directory does not exist + And verify that the path "promote" in each segment data directory does not exist + And verify that the path "db_analyze" in each segment data directory does not exist + + @gpexpand_no_mirrors + @gpexpand_segment + Scenario: gpexpand should skip already expanded/broken tables when redistributing + Given the database is not running + And a working directory of the test as '/data/gpdata/gpexpand' + And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into + And a cluster is created with no mirrors on "cdw" and "sdw1" + And database "gptest" exists + And the user runs psql with "-c 'CREATE TABLE test_good_1(a int)'" against database "gptest" + And the user runs psql with "-c 'CREATE TABLE test_already_expanded(a int)'" against database "gptest" + And the user runs psql with "-c 'CREATE TABLE test_broken(a int)'" against database "gptest" + And the user runs psql with "-c 'CREATE TABLE test_good_2(a int)'" against database "gptest" + And the user runs sql "DROP TABLE test_broken" in "gptest" on primary segment with content 0 + And there are no gpexpand_inputfiles + And the cluster is setup for an expansion on hosts "cdw,sdw1" + When the user runs gpexpand interview to add 2 new segment and 0 new host "ignored.host" + Then the number of segments have been saved + When the user runs gpexpand with the latest gpexpand_inputfile with additional parameters "--silent" + Then verify that the cluster has 2 new segments + And the user runs psql with "-c 'ALTER TABLE test_already_expanded expand table'" against database "gptest" + When the user runs gpexpand to redistribute + Then gpexpand should print "[WARNING]:-Encountered unexpected issue when expanding table gptest.public.test_broken, skipping" escaped to stdout + And gpexpand should print "[INFO]:-Table gptest.public.test_already_expanded seems to be already expanded, marking as done" escaped to stdout + And table "test_good_1" should be marked as expanded + And table "test_good_2" should be marked as expanded + And table "test_already_expanded" should be marked as expanded + And table "test_broken" should not be marked as expanded + + @gpexpand_no_mirrors + @gpexpand_verify_dtx + Scenario: Gpexpand should succeed when xlog has DTX info + Given the database is not running + And a working directory of the test as '/data/gpdata/gpexpand' + And a temporary directory under "/data/gpdata/gpexpand/expandedData" to expand into + And the cluster is generated with "3" primaries only + And database "gptest" exists + And the user runs psql with "-c 'create extension IF NOT EXISTS gp_inject_fault;create table ttt(tc1 int);'" against database "gptest" + And the user runs psql with "-c "SELECT gp_inject_fault('before_notify_commited_dtx_transaction', 'suspend', dbid) FROM gp_segment_configuration WHERE content = -1 AND role = 'p';"" against database "gptest" + And the user runs the command "psql gptest -c 'insert into ttt select generate_series(1,100);'" in the background without sleep + And waiting "1" seconds + And there are no gpexpand_inputfiles + And the cluster is setup for an expansion on hosts "localhost" + When the user runs gpexpand interview to add 1 new segment and 0 new host "ignored.host" + Then the number of segments have been saved + When the user runs gpexpand with the latest gpexpand_inputfile with additional parameters "--silent" + And the user runs psql with "-c "SELECT gp_inject_fault('before_notify_commited_dtx_transaction', 'reset', dbid) FROM gp_segment_configuration WHERE content = -1 AND role = 'p';"" against database "gptest" + And waiting "1" seconds + And the user runs psql with "-c 'drop table ttt;'" against database "gptest" + Then verify that the cluster has 1 new segments diff --git a/gpMgmt/test/behave/mgmt_utils/gpinitsystem.feature b/gpMgmt/test/behave/mgmt_utils/gpinitsystem.feature index 1d69a5403ff..1c9051764bb 100644 --- a/gpMgmt/test/behave/mgmt_utils/gpinitsystem.feature +++ b/gpMgmt/test/behave/mgmt_utils/gpinitsystem.feature @@ -70,6 +70,7 @@ Feature: gpinitsystem tests Given the user runs "gpstate" Then gpstate should return a return code of 0 + @extended Scenario: gpinitsystem creates a backout file when gpinitsystem process terminated Given create demo cluster config And all files in gpAdminLogs directory are deleted @@ -84,6 +85,7 @@ Feature: gpinitsystem tests And gpinitsystem should return a return code of 0 And gpintsystem logs should not contain lines about running backout script + @extended Scenario: gpinitsystem creates a backout file when gpcreateseg process terminated Given create demo cluster config And all files in gpAdminLogs directory are deleted @@ -97,6 +99,7 @@ Feature: gpinitsystem tests And gpinitsystem should return a return code of 0 And gpintsystem logs should not contain lines about running backout script + @extended Scenario: gpinitsystem does not create or need backout file when user terminated very early Given create demo cluster config And all files in gpAdminLogs directory are deleted @@ -333,4 +336,3 @@ Feature: gpinitsystem tests When the user runs command "grep -q '.*gpcreateseg\.sh.*Completed ssh.*' ~/gpAdminLogs/gpinitsystem*log" Then grep should return a return code of 0 And the user runs command "mv ../gpAux/gpdemo/clusterConfigFile.bak ../gpAux/gpdemo/clusterConfigFile" - diff --git a/gpMgmt/test/behave/mgmt_utils/gpmovemirrors.feature b/gpMgmt/test/behave/mgmt_utils/gpmovemirrors.feature index ed6dda775f4..62565fa3245 100644 --- a/gpMgmt/test/behave/mgmt_utils/gpmovemirrors.feature +++ b/gpMgmt/test/behave/mgmt_utils/gpmovemirrors.feature @@ -26,23 +26,31 @@ Feature: Tests for gpmovemirrors Given a standard local demo cluster is created And a gpmovemirrors directory under '/tmp/gpmovemirrors' with mode '0700' is created And a 'good' gpmovemirrors file is created + And verify replication slot internal_wal_replication_slot is available on all the segments When the user runs gpmovemirrors Then gpmovemirrors should return a return code of 0 And verify the database has mirrors + #gpmovemirrors triggers full recovery where old replication slot is dropped and new one is created + And verify replication slot internal_wal_replication_slot is available on all the segments And all the segments are running And the segments are synchronized + And check segment conf: postgresql.conf And verify that mirrors are recognized after a restart Scenario: gpmovemirrors can change the port of mirrors within a single host Given a standard local demo cluster is created And a gpmovemirrors directory under '/tmp/gpmovemirrors' with mode '0700' is created And a 'samedir' gpmovemirrors file is created + And verify replication slot internal_wal_replication_slot is available on all the segments When the user runs gpmovemirrors Then gpmovemirrors should return a return code of 0 And verify the database has mirrors + #gpmovemirrors triggers full recovery where old replication slot is dropped and new one is created + And verify replication slot internal_wal_replication_slot is available on all the segments And all the segments are running And the segments are synchronized And verify that mirrors are recognized after a restart + And check segment conf: postgresql.conf Scenario: gpmovemirrors gives a warning when passed identical attributes for new and old mirrors Given a standard local demo cluster is created @@ -56,6 +64,7 @@ Feature: Tests for gpmovemirrors And the segments are synchronized And verify that mirrors are recognized after a restart + @skip_cleanup Scenario: tablespaces work Given a standard local demo cluster is created And a tablespace is created with data @@ -69,6 +78,7 @@ Feature: Tests for gpmovemirrors And verify that mirrors are recognized after a restart And the tablespace is valid + @skip_cleanup Scenario Outline: gpmovemirrors limits number of parallel processes correctly Given the database is running And all the segments are running @@ -102,7 +112,7 @@ Feature: Tests for gpmovemirrors add a validation error like both hosts recoverying to the same port - so that the triplet code fails assert that gp_seg_config wasn't updated """ - + @skip_cleanup Scenario Outline: user can if mirrors failed to move initially Given the database is running And all the segments are running @@ -178,6 +188,7 @@ Feature: Tests for gpmovemirrors And gprecoverseg should return a return code of 0 And all the segments are running And the segments are synchronized + And check segment conf: postgresql.conf And user can start transactions @@ -199,7 +210,7 @@ Feature: Tests for gpmovemirrors And the user reset the walsender on the primary on content 0 And the user waits until saved async process is completed And recovery_progress.file should not exist in gpAdminLogs - And the user waits until mirror on content 0,1 is up + And verify that mirror on content 0,1 is up And check if mirrors on content 0,1 are moved to new location on input file And user can start transactions And all files in gpAdminLogs directory are deleted on all hosts in the cluster @@ -223,10 +234,129 @@ Feature: Tests for gpmovemirrors And the user reset the walsender on the primary on content 0 And the user waits until saved async process is completed And recovery_progress.file should not exist in gpAdminLogs - And the user waits until mirror on content 0,1,2 is up + And verify that mirror on content 0,1,2 is up And check if mirrors on content 0,1,2 are moved to new location on input file And user can start transactions And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And the cluster is recovered in full and rebalanced + + @demo_cluster + @concourse_cluster + @skip_cleanup + Scenario: gpmovemirrors gives warning if pg_basebackup is already running for one of the mirrors to be moved + Given the database is running + And all the segments are running + And the segments are synchronized + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And the information of contents 0,1,2 is saved + And user immediately stops all mirror processes for content 0,1,2 + And user can start transactions + And the user suspend the walsender on the primary on content 0 + And the user asynchronously runs "gprecoverseg -aF" and the process is saved + And the user just waits until recovery_progress.file is created in gpAdminLogs + And user waits until gp_stat_replication table has no pg_basebackup entries for content 1,2 + And an FTS probe is triggered + And the user waits until mirror on content 1,2 is up + And verify that mirror on content 0 is down + And the gprecoverseg lock directory is removed + And user immediately stops all mirror processes for content 1,2 + And the user waits until mirror on content 1,2 is down + And a gpmovemirrors directory under '/tmp' with mode '0700' is created + And a gpmovemirrors input file is created + And edit the input file to recover mirror with content 0,1,2 to a new directory with mode 0700 + When the user runs gpmovemirrors with input file and additional args " " + Then gprecoverseg should print "Found pg_basebackup running for segments with contentIds [0], skipping recovery of these segments" to logfile + And gprecoverseg should return a return code of 0 + And gpmovemirrors should return a return code of 0 + And verify that mirror on content 1,2 is up + And verify that mirror on content 0 is down + And check if mirrors on content 1,2 are moved to new location on input file + And check if mirrors on content 0 are in their original configuration + And the user reset the walsender on the primary on content 0 + And the user waits until saved async process is completed + And recovery_progress.file should not exist in gpAdminLogs + And verify that mirror on content 0 is up + And the cluster is recovered in full and rebalanced + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + + @demo_cluster + @concourse_cluster + @skip_cleanup + Scenario: gpmovemirrors gives warning if pg_basebackup is already running for some of the mirrors to be moved + Given the database is running + And all the segments are running + And the segments are synchronized + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And the information of contents 0,1,2 is saved + And user immediately stops all mirror processes for content 0,1,2 + And user can start transactions + And the user suspend the walsender on the primary on content 0 + And the user suspend the walsender on the primary on content 1 + And the user asynchronously runs "gprecoverseg -aF" and the process is saved + And the user just waits until recovery_progress.file is created in gpAdminLogs + And user waits until gp_stat_replication table has no pg_basebackup entries for content 2 + And the user waits until mirror on content 2 is up + And verify that mirror on content 0,1 is down + And the gprecoverseg lock directory is removed + And user immediately stops all mirror processes for content 2 + And the user waits until mirror on content 2 is down + And a gpmovemirrors directory under '/tmp' with mode '0700' is created + And a gpmovemirrors input file is created + And edit the input file to recover mirror with content 0,1,2 to a new directory with mode 0700 + When the user runs gpmovemirrors with input file and additional args " " + Then gprecoverseg should print "Found pg_basebackup running for segments with contentIds [0, 1], skipping recovery of these segments" to logfile + And gprecoverseg should return a return code of 0 + And gpmovemirrors should return a return code of 0 + And verify that mirror on content 2 is up + And verify that mirror on content 0,1 is down + And check if mirrors on content 2 are moved to new location on input file + And check if mirrors on content 0,1 are in their original configuration + And the user reset the walsender on the primary on content 0 + And the user reset the walsender on the primary on content 1 + And the user waits until saved async process is completed + And recovery_progress.file should not exist in gpAdminLogs + And verify that mirror on content 0,1 is up + And the cluster is recovered in full and rebalanced + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + + @demo_cluster + @concourse_cluster + @skip_cleanup + Scenario: gpmovemirrors gives warning if pg_basebackup is already running for all mirrors to be moved + Given the database is running + And all the segments are running + And the segments are synchronized + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And the information of contents 0,1,2 is saved + And a gprecoverseg directory under '/tmp' with mode '0700' is created + And a gprecoverseg input file is created + And edit the input file to recover mirror with content 0 to a new directory on remote host with mode 0700 + And edit the input file to recover mirror with content 1 to a new directory on remote host with mode 0700 + And edit the input file to recover mirror with content 2 to a new directory on remote host with mode 0700 + And user immediately stops all mirror processes for content 0,1,2 + And user can start transactions + And the user suspend the walsender on the primary on content 0 + And the user suspend the walsender on the primary on content 1 + And the user suspend the walsender on the primary on content 2 + When the user asynchronously runs gprecoverseg with input file and additional args "-a" and the process is saved + And the user just waits until recovery_progress.file is created in gpAdminLogs + And verify that mirror on content 0,1,2 is down + And the gprecoverseg lock directory is removed + Given a gpmovemirrors directory under '/tmp' with mode '0700' is created + And a gpmovemirrors input file is created + And edit the input file to recover mirror with content 0,1,2 to a new directory with mode 0700 + When the user runs gpmovemirrors with input file and additional args "-v" + And gprecoverseg should return a return code of 0 + And gpmovemirrors should return a return code of 0 + Then gprecoverseg should print "Found pg_basebackup running for segments with contentIds [0, 1, 2], skipping recovery of these segments" to logfile + And the user reset the walsender on the primary on content 0 + And the user reset the walsender on the primary on content 1 + And the user reset the walsender on the primary on content 2 + And the user waits until saved async process is completed + And recovery_progress.file should not exist in gpAdminLogs + And verify that mirror on content 0,1,2 is up + And the cluster is recovered in full and rebalanced + And all files in gpAdminLogs directory are deleted on all hosts in the cluster ########################### @concourse_cluster tests ########################### @@ -236,7 +366,7 @@ Feature: Tests for gpmovemirrors Scenario: gpmovemirrors can change from group mirroring to spread mirroring Given verify that mirror segments are in "group" configuration And pg_hba file "/data/gpdata/primary/gpseg1/pg_hba.conf" on host "sdw1" contains only cidr addresses - And a sample gpmovemirrors input file is created in "spread" configuration + And a sample gpmovemirrors input file is created in "spread" configuration on "old" parent directory When the user runs "gpmovemirrors --input=/tmp/gpmovemirrors_input_spread" Then gpmovemirrors should return a return code of 0 # Verify that mirrors are functional in the new configuration @@ -268,19 +398,23 @@ Feature: Tests for gpmovemirrors Then gprecoverseg should return a return code of 0 And all the segments are running And the segments are synchronized + And check segment conf: postgresql.conf @concourse_cluster Scenario: gpmovemirrors can change from spread mirroring to group mirroring Given verify that mirror segments are in "spread" configuration - And a sample gpmovemirrors input file is created in "group" configuration + And a sample gpmovemirrors input file is created in "group" configuration on "old" parent directory When the user runs "gpmovemirrors --input=/tmp/gpmovemirrors_input_group --hba-hostnames" Then gpmovemirrors should return a return code of 0 # Verify that mirrors are functional in the new configuration Then verify the database has mirrors And all the segments are running And the segments are synchronized + And saving host IP address of "sdw3" # gpmovemirrors_input_group moves mirror on sdw3 to sdw2, corresponding primary should now have sdw2 entry And pg_hba file "/data/gpdata/primary/gpseg1/pg_hba.conf" on host "sdw1" contains entries for "sdw2" + And pg_hba file on primary of mirrors on "sdw2" with "1" contains no replication entries for "sdw3" + And verify that only replication connection primary has is to "sdw2" And verify that mirror segments are in "group" configuration And verify that mirrors are recognized after a restart And the information of a "mirror" segment on a remote host is saved @@ -305,12 +439,13 @@ Feature: Tests for gpmovemirrors Then gprecoverseg should return a return code of 0 And all the segments are running And the segments are synchronized + And check segment conf: postgresql.conf @concourse_cluster Scenario: tablespaces work on a multi-host environment Given verify that mirror segments are in "group" configuration And a tablespace is created with data - And a sample gpmovemirrors input file is created in "spread" configuration + And a sample gpmovemirrors input file is created in "spread" configuration on "old" parent directory When the user runs "gpmovemirrors --input=/tmp/gpmovemirrors_input_spread" Then gpmovemirrors should return a return code of 0 And verify the tablespace directories on host "sdw2" for content "1" are deleted @@ -353,13 +488,14 @@ Feature: Tests for gpmovemirrors And gprecoverseg should print "Initiating segment recovery." to stdout And check if mirrors on content 0,1,2 are moved to new location on input file - And gpAdminLogs directory has no "pg_basebackup*" files on all segment hosts + And gpAdminLogs directory has "pg_basebackup*" files on respective hosts only for content 0,1,2 And gpAdminLogs directory has no "pg_rewind*" files on all segment hosts And gpAdminLogs directory has "gpsegsetuprecovery*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files on all segment hosts And the mode of all the created data directories is changed to 0700 And the cluster is recovered in full and rebalanced + And check segment conf: postgresql.conf And the row count from table "test_movemirrors" in "postgres" is verified against the saved data @concourse_cluster @@ -370,6 +506,7 @@ Feature: Tests for gpmovemirrors And the segments are synchronized And all files in gpAdminLogs directory are deleted on all hosts in the cluster And the information of contents 0,1,2 is saved + And check segment conf: postgresql.conf And sql "DROP TABLE if exists test_movemirrors; CREATE TABLE test_movemirrors AS SELECT generate_series(1,10000) AS i" is executed in "postgres" db And the "test_movemirrors" table row count in "postgres" is saved @@ -387,13 +524,15 @@ Feature: Tests for gpmovemirrors And check if mirrors on content 0 are in their original configuration And check if mirrors on content 1,2 are moved to new location on input file And verify that mirror on content 1,2,3,4,5 is up - And gpAdminLogs directory has "pg_basebackup*" files on respective hosts only for content 0 + And gpAdminLogs directory has "pg_basebackup*" files on respective hosts only for content 0,1,2 And gpAdminLogs directory has no "pg_rewind*" files on all segment hosts And gpAdminLogs directory has "gpsegsetuprecovery*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files on all segment hosts + And check segment conf: postgresql.conf And the mode of all the created data directories is changed to 0700 And the cluster is recovered in full and rebalanced + And check segment conf: postgresql.conf And the row count from table "test_movemirrors" in "postgres" is verified against the saved data @concourse_cluster @@ -404,6 +543,7 @@ Feature: Tests for gpmovemirrors And the segments are synchronized And all files in gpAdminLogs directory are deleted on all hosts in the cluster And the information of contents 0,1,2,3,4,5 is saved + And check segment conf: postgresql.conf And sql "DROP TABLE if exists test_movemirrors; CREATE TABLE test_movemirrors AS SELECT generate_series(1,10000) AS i" is executed in "postgres" db And the "test_movemirrors" table row count in "postgres" is saved @@ -424,9 +564,11 @@ Feature: Tests for gpmovemirrors And gpAdminLogs directory has no "pg_rewind*" files on all segment hosts And gpAdminLogs directory has "gpsegsetuprecovery*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files on all segment hosts + And check segment conf: postgresql.conf And the mode of all the created data directories is changed to 0700 And the cluster is recovered in full and rebalanced + And check segment conf: postgresql.conf And the row count from table "test_movemirrors" in "postgres" is verified against the saved data @concourse_cluster @@ -437,6 +579,7 @@ Feature: Tests for gpmovemirrors And the segments are synchronized And all files in gpAdminLogs directory are deleted on all hosts in the cluster And the information of contents 0,1,2,3,4,5 is saved + And check segment conf: postgresql.conf And sql "DROP TABLE if exists test_movemirrors; CREATE TABLE test_movemirrors AS SELECT generate_series(1,10000) AS i" is executed in "postgres" db And the "test_movemirrors" table row count in "postgres" is saved @@ -459,7 +602,63 @@ Feature: Tests for gpmovemirrors And gpAdminLogs directory has no "pg_rewind*" files on all segment hosts And gpAdminLogs directory has "gpsegsetuprecovery*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files on all segment hosts + And check segment conf: postgresql.conf And the mode of all the created data directories is changed to 0700 And the cluster is recovered in full and rebalanced + And check segment conf: postgresql.conf And the row count from table "test_movemirrors" in "postgres" is verified against the saved data + + @concourse_cluster + Scenario: gpmovemirrors removes the stale replication entries from pg_hba when moving mirrors to another host + Given a working directory of the test as '/tmp/gpmovemirrors' + And the database is not running + And a cluster is created with "spread" segment mirroring on "cdw" and "sdw1, sdw2, sdw3" + And verify that mirror segments are in "spread" configuration + And a gpmovemirrors directory under '/tmp' with mode '0700' is created + And create an input file to move mirrors from "sdw1" to "sdw3" in "same" data directory + When the user runs "gpmovemirrors -a --input=/tmp/gpmovemirrors_input_sdw1_sdw3" + Then gpmovemirrors should return a return code of 0 + Then verify the database has mirrors + And all the segments are running + And the segments are synchronized + And saving host IP address of "sdw1" + And pg_hba file on primary of mirrors on "sdw3" with "3,4" contains no replication entries for "sdw1" + And verify that only replication connection primary has is to "sdw3" + + @concourse_cluster + Scenario: gpmovemirrors fails if the target host does not have enough free disk space to move mirror from source host + Given the database is running + And all the segments are running + And the segments are synchronized + And a tablespace is created with data + And mount a filesystem with min total capacity + And a gpmovemirrors input file is created + And edit the input file to move mirror with content 0 to a new directory on remote host with mode 0700 + And edit the input file to move mirror with content 1 to a new directory on remote host with mode 0700 + And edit the input file to move mirror with content 2 to a new directory on remote host with mode 0700 + And edit the input file to move mirror with content 3 to a new directory on remote host with mode 0700 + And edit the input file to move mirror with content 4 to a new directory on remote host with mode 0700 + And edit the input file to move mirror with content 5 to a new directory on remote host with mode 0700 + + When the user runs gpmovemirrors + Then gpmovemirrors should return a return code of 3 + And gpmovemirrors should print "Insufficient disk space on target mirror hosts." to stdout + And all the segments are running + And the segments are synchronized + + @concourse_cluster + Scenario: gpmovemirrors fails if the target host does not have enough free disk space to move mirror to new host + Given the database is running + And all the segments are running + And the segments are synchronized + And a tablespace is created with data + And mount a filesystem with min total capacity + And create an input file to move mirrors from "sdw2" to "sdw3" in "context" data directory + When the user runs "gpmovemirrors --input=/tmp/gpmovemirrors_input_sdw2_sdw3" + + Then gpmovemirrors should return a return code of 3 + And gpmovemirrors should print "Insufficient disk space on target mirror hosts." to stdout + And all the segments are running + And the segments are synchronized + diff --git a/gpMgmt/test/behave/mgmt_utils/gprecoverseg.feature b/gpMgmt/test/behave/mgmt_utils/gprecoverseg.feature index 3d28dfc11d5..6f70544d558 100644 --- a/gpMgmt/test/behave/mgmt_utils/gprecoverseg.feature +++ b/gpMgmt/test/behave/mgmt_utils/gprecoverseg.feature @@ -1,22 +1,74 @@ @gprecoverseg Feature: gprecoverseg tests - Scenario: incremental recovery works with tablespaces + @demo_cluster + @concourse_cluster + Scenario: gprecoverseg recovery with a recovery configuration file and differential flag + Given the database is running + And all the segments are running + And the segments are synchronized + And user immediately stops all mirror processes for content 0,1,2 + And the user waits until mirror on content 0,1,2 is down + And user can start transactions + And the gprecoverseg input file "recover_config_file" is cleaned up + When a gprecoverseg input file "recover_config_file" is created with all the failed segments and valid recovery type + And the user runs "gprecoverseg -i /tmp/recover_config_file -a --differential" + Then gprecoverseg should return a return code of 0 + And verify that mirror on content 0,1,2 is up + And gprecoverseg should print "Synchronization mode.* = Differential" to stdout 2 times + And gprecoverseg should print "Synchronization mode.* = Full" to stdout 1 times + And all the segments are running + And the segments are synchronized + + + @demo_cluster + @concourse_cluster + Scenario: gprecoverseg" with a recovery configuration file specifying the recovery type + Given the database is running + And all the segments are running + And the segments are synchronized + And user immediately stops all mirror processes for content 0,1,2 + And the user waits until mirror on content 0,1,2 is down + And user can start transactions + And the gprecoverseg input file "recover_config_file" is cleaned up + When a gprecoverseg input file "recover_config_file" is created with all the failed segments and invalid recovery type + And the user runs "gprecoverseg -i /tmp/recover_config_file -a" + Then gprecoverseg should return a return code of 2 + And gprecoverseg should print "Invalid recovery type provided, please provide any of I,D,F,i,d,f as recovery_type" to stdout + And verify that mirror on content 0,1,2 is down + When a gprecoverseg input file "recover_config_file" is created with all the failed segments and valid recovery type + And the user runs "gprecoverseg -i /tmp/recover_config_file -a" + Then gprecoverseg should return a return code of 0 + And verify that mirror on content 0,1,2 is up + And gprecoverseg should print "Synchronization mode.*= Incremental" to stdout 1 times + And gprecoverseg should print "Synchronization mode.* = Differential" to stdout 1 times + And gprecoverseg should print "Synchronization mode.* = Full" to stdout 1 times + And all the segments are running + And the segments are synchronized + + @demo_cluster + @concourse_cluster + Scenario Outline: recovery works with tablespaces Given the database is running - And a tablespace is created with data And user stops all primary processes And user can start transactions - When the user runs "gprecoverseg -a" + And a tablespace is created with data + When the user runs "gprecoverseg " Then gprecoverseg should return a return code of 0 + And gprecoverseg should print "Future gprecoverseg executions might remove the currently created pg_basebackup/pg_rewind/rsync progress files, please save these files if needed." to stdout And the segments are synchronized + And verify replication slot internal_wal_replication_slot is available on all the segments And the tablespace is valid + And the tablespace has valid symlink And the database segments are in execute mode Given another tablespace is created with data When the user runs "gprecoverseg -ra" Then gprecoverseg should return a return code of 0 And the segments are synchronized + And verify replication slot internal_wal_replication_slot is available on all the segments And the tablespace is valid + And the tablespace has valid symlink And the other tablespace is valid And the database segments are in execute mode Examples: @@ -25,28 +77,140 @@ Feature: gprecoverseg tests | differential | -a --differential | | full | -aF | - Scenario: full recovery works with tablespaces + + @demo_cluster + @concourse_cluster + Scenario: differential recovery runs successfully Given the database is running - And a tablespace is created with data + And the segments are synchronized + And verify replication slot internal_wal_replication_slot is available on all the segments And user stops all primary processes And user can start transactions - When the user runs "gprecoverseg -a -F" + When the user runs "gprecoverseg -av --differential" Then gprecoverseg should return a return code of 0 + And gprecoverseg should print "Successfully dropped replication slot internal_wal_replication_slot" to stdout + And gprecoverseg should print "Successfully created replication slot internal_wal_replication_slot" to stdout + And gprecoverseg should print "Segments successfully recovered" to stdout + And verify that mirror on content 0,1,2 is up + And verify replication slot internal_wal_replication_slot is available on all the segments And the segments are synchronized - And the tablespace is valid + And the cluster is rebalanced - Given another tablespace is created with data - When the user runs "gprecoverseg -ra" + + # Differential recovery is not implemented in Cloudberry yet. These stay + # aligned with Greenplum so they can be enabled along with the feature. + @differential + Scenario: differential recovery shows error message if run with the wrong argument + Given the database is running + And user stops all primary processes + And user can start transactions + And a gprecoverseg directory under '/tmp' with mode '0700' is created + And a gprecoverseg input file is created + When the user runs "gprecoverseg -a --differential -F" + Then gprecoverseg should return a return code of 2 + And gprecoverseg should print "Only one of -F and --differential may be specified" to stdout + When the user runs "gprecoverseg -a --differential -p localhost" + Then gprecoverseg should return a return code of 2 + And gprecoverseg should print "Only one of -p, -r and --differential may be specified" to stdout + When the user runs "gprecoverseg -a --differential -o outputConfigFile" + Then gprecoverseg should return a return code of 2 + And gprecoverseg should print "Invalid -o provided with --differential argument" to stdout + When the user runs "gprecoverseg -a --differential" + Then gprecoverseg should return a return code of 0 + And the segments are synchronized + And the cluster is rebalanced + + + @demo_cluster + @concourse_cluster + Scenario: Differential recovery succeeds if previous incremental recovery failed + Given the database is running + And user stops all primary processes + And user can start transactions + And all files in pg_wal directory are deleted from data directory of preferred primary of content 0,1,2 + When the user runs "gprecoverseg -a" + Then gprecoverseg should return a return code of 1 + And user can start transactions + And verify that mirror on content 0,1,2 is down + When the user runs "gprecoverseg -a --differential" Then gprecoverseg should return a return code of 0 - And the segments are synchronized - And the tablespace is valid - And the other tablespace is valid + And verify that mirror on content 0,1,2 is up + And verify replication slot internal_wal_replication_slot is available on all the segments + And the cluster is rebalanced + + @demo_cluster + @concourse_cluster + Scenario: Differential recovery succeeds if previous full recovery failed + Given the database is running + And user stops all primary processes + And user can start transactions + And a gprecoverseg directory under '/tmp' with mode '0700' is created + And a gprecoverseg input file is created + And edit the input file to recover mirror with content 0 incremental + And edit the input file to recover mirror with content 1 full inplace + And edit the input file to recover mirror with content 2 to a new directory on remote host with mode 0000 + When the user runs gprecoverseg with input file and additional args "-a" + Then gprecoverseg should return a return code of 1 + And user can start transactions + And verify that mirror on content 0,1 is up + And verify that mirror on content 2 is down + When the user runs "gprecoverseg -a --differential" + Then gprecoverseg should return a return code of 0 + And verify that mirror on content 0,1,2 is up + And verify replication slot internal_wal_replication_slot is available on all the segments + And the cluster is rebalanced + + + @concourse_cluster + Scenario: gpstate track of differential recovery for single host + Given the database is running + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And user immediately stops all mirror processes for content 0 + And the user waits until mirror on content 0 is down + And user can start transactions + And sql "DROP TABLE IF EXISTS test_recoverseg; CREATE TABLE test_recoverseg AS SELECT generate_series(1,100000000) AS a;" is executed in "postgres" db + And sql "DROP TABLE IF EXISTS test_recoverseg_1; CREATE TABLE test_recoverseg_1 AS SELECT generate_series(1,100000000) AS a;" is executed in "postgres" db + When the user asynchronously runs "gprecoverseg -a --differential" and the process is saved + Then the user waits until recovery_progress.file is created in gpAdminLogs and verifies that all dbids progress with pg_data are present + When the user runs "gpstate -e" + Then gpstate should print "Segments in recovery" to stdout + And gpstate output contains "differential" entries for mirrors of content 0 + And gpstate output looks like + | Segment | Port | Recovery type | Stage | Completed bytes \(kB\) | Percentage completed | + | \S+ | [0-9]+ | differential | Syncing pg_data of dbid 6 | ([\d,]+)[ \t] | \d+% | + And the user waits until saved async process is completed + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And sql "DROP TABLE IF EXISTS test_recoverseg;" is executed in "postgres" db + And sql "DROP TABLE IF EXISTS test_recoverseg_1;" is executed in "postgres" db + And the cluster is rebalanced + + + @concourse_cluster + Scenario: check Tablespace Recovery Progress with gpstate + Given the database is running + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And user immediately stops all mirror processes for content 0 + And user can start transactions + And a tablespace is created with data + And insert additional data into the tablespace + When the user asynchronously runs "gprecoverseg -a --differential" and the process is saved + Then the user waits until recovery_progress.file is created in gpAdminLogs and verifies that all dbids progress with tablespace are present + When the user runs "gpstate -e" + Then gpstate should print "Segments in recovery" to stdout + And gpstate output contains "differential" entries for mirrors of content 0 + And gpstate output looks like + | Segment | Port | Recovery type | Stage | Completed bytes \(kB\) | Percentage completed | + | \S+ | [0-9]+ | differential | Syncing tablespace of dbid 6 for oid \d+ | ([\d,]+)[ \t] | \d+% | + And the user waits until saved async process is completed + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And the cluster is rebalanced + Scenario Outline: full recovery limits number of parallel processes correctly - Given a standard local demo cluster is created + Given the database is running And 2 gprecoverseg directory under '/tmp/recoverseg' with mode '0700' is created And a good gprecoverseg input file is created for moving 2 mirrors - When the user runs gprecoverseg with input file and additional args "-a -F -v " + When the user runs gprecoverseg with input file and additional args "-a -v " Then gprecoverseg should return a return code of 0 And gprecoverseg should only spawn up to workers in WorkerPool And check if gprecoverseg ran "$GPHOME/sbin/gpsegsetuprecovery.py" 1 times with args "-b " @@ -55,6 +219,7 @@ Feature: gprecoverseg tests And gpsegrecovery should only spawn up to workers in WorkerPool And check if gprecoverseg ran "$GPHOME/sbin/gpsegstop.py" 1 times with args "-b " And the segments are synchronized + And check segment conf: postgresql.conf Examples: | args | coordinator_workers | segHost_workers | @@ -62,6 +227,24 @@ Feature: gprecoverseg tests | -B 2 -b 1 | 2 | 1 | | -B 1 -b 2 | 1 | 2 | + # Differential recovery is not implemented in Cloudberry yet. These stay + # aligned with Greenplum so they can be enabled along with the feature. + @differential + Scenario: Differential recovery limits number of parallel processes correctly + Given the database is running + And user immediately stops all primary processes for content 0,1,2 + And user can start transactions + When the user runs "gprecoverseg -av --differential -B 1 -b 2" + Then gprecoverseg should return a return code of 0 + And gprecoverseg should only spawn up to 1 workers in WorkerPool + And check if gprecoverseg ran "$GPHOME/sbin/gpsegsetuprecovery.py" 1 times with args "-b 2" + And check if gprecoverseg ran "$GPHOME/sbin/gpsegrecovery.py" 1 times with args "-b 2" + And gpsegsetuprecovery should only spawn up to 2 workers in WorkerPool + And gpsegrecovery should only spawn up to 2 workers in WorkerPool + And the segments are synchronized + And check segment conf: postgresql.conf + And the cluster is rebalanced + Scenario Outline: Rebalance correctly limits the number of concurrent processes Given the database is running And user stops all primary processes @@ -78,6 +261,7 @@ Feature: gprecoverseg tests And check if gprecoverseg ran "$GPHOME/sbin/gpsegrecovery.py" 1 times with args "-b " And check if gprecoverseg ran "$GPHOME/sbin/gpsegstop.py" 1 times with args "-b " And the segments are synchronized + And check segment conf: postgresql.conf Examples: | args | coordinator_workers | segHost_workers | @@ -98,72 +282,49 @@ Feature: gprecoverseg tests When the user runs "gprecoverseg -ra" Then gprecoverseg should return a return code of 0 And gprecoverseg should not print "Unhandled exception in thread started by recovery displays pg_controldata success info Given the database is running And all the segments are running And the segments are synchronized And user stops all mirror processes When user can start transactions - And the user runs "gprecoverseg -F -a" + And the user runs "gprecoverseg " Then gprecoverseg should return a return code of 0 And gprecoverseg should print "Successfully finished pg_controldata.* for dbid.*" to stdout And the segments are synchronized + And verify replication slot internal_wal_replication_slot is available on all the segments And check segment conf: postgresql.conf - Scenario: gprecoverseg incremental recovery displays pg_controldata success info - Given the database is running - And all the segments are running - And the segments are synchronized - And user stops all mirror processes - When user can start transactions - And the user runs "gprecoverseg -a" - Then gprecoverseg should return a return code of 0 - And gprecoverseg should print "Successfully finished pg_controldata.* for dbid.*" to stdout - And the segments are synchronized - And check segment conf: postgresql.conf + Examples: + | scenario | args | + | incremental | -a | + | full | -aF | + + @differential + Examples: + | scenario | args | + | differential | -a --differential | Scenario: gprecoverseg mixed recovery displays pg_basebackup and rewind progress to the user Given the database is running @@ -186,8 +347,8 @@ Feature: gprecoverseg tests And gprecoverseg should print "Segments successfully recovered" to stdout And check if gprecoverseg ran gpsegsetuprecovery.py 1 times with the expected args And check if gprecoverseg ran gpsegrecovery.py 1 times with the expected args - And gpAdminLogs directory has no "pg_basebackup*" files - And gpAdminLogs directory has no "pg_rewind*" files + And gpAdminLogs directory has "pg_basebackup*" files + And gpAdminLogs directory has "pg_rewind*" files And gpAdminLogs directory has "gpsegsetuprecovery*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files on all segment hosts And the old data directories are cleaned up for content 0 @@ -208,7 +369,7 @@ Feature: gprecoverseg tests When the user runs "gprecoverseg -a -s" Then gprecoverseg should return a return code of 0 And gprecoverseg should print "pg_rewind: Done!" to stdout for each mirror - And gpAdminLogs directory has no "pg_rewind*" files + And gpAdminLogs directory has "pg_rewind*" files And gpAdminLogs directory has "gpsegsetuprecovery*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files on all segment hosts @@ -226,7 +387,7 @@ Feature: gprecoverseg tests Then gprecoverseg should return a return code of 0 And gprecoverseg should print "Initiating segment recovery. Upon completion, will start the successfully recovered segments" to stdout And gprecoverseg should not print "pg_basebackup: base backup completed" to stdout - And gpAdminLogs directory has no "pg_basebackup*" files + And gpAdminLogs directory has "pg_basebackup*" files And all the segments are running And the segments are synchronized @@ -256,6 +417,9 @@ Feature: gprecoverseg tests And all the segments are running And the segments are synchronized + # Differential recovery is not implemented in Cloudberry yet. These stay + # aligned with Greenplum so they can be enabled along with the feature. + @differential Scenario: gprecoverseg differential recovery displays rsync progress to the user Given the database is running And all the segments are running @@ -268,9 +432,7 @@ Feature: gprecoverseg tests And gprecoverseg should print "Initiating segment recovery. Upon completion, will start the successfully recovered segments" to stdout And gprecoverseg should print "total size" to stdout for each mirror And gprecoverseg should print "Segments successfully recovered" to stdout - And gpAdminLogs directory has no "pg_basebackup*" files on all segment hosts - And gpAdminLogs directory has no "pg_rewind*" files on all segment hosts - And gpAdminLogs directory has no "rsync*" files on all segment hosts + And gpAdminLogs directory has "rsync*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files And gpAdminLogs directory has "gpsegsetuprecovery*" files And all the segments are running @@ -278,6 +440,9 @@ Feature: gprecoverseg tests And verify replication slot internal_wal_replication_slot is available on all the segments And check segment conf: postgresql.conf + # Differential recovery is not implemented in Cloudberry yet. These stay + # aligned with Greenplum so they can be enabled along with the feature. + @differential Scenario: gprecoverseg does not display rsync progress to the user when --no-progress option is specified Given the database is running And all the segments are running @@ -290,9 +455,7 @@ Feature: gprecoverseg tests And gprecoverseg should print "Initiating segment recovery. Upon completion, will start the successfully recovered segments" to stdout And gprecoverseg should not print "total size is .* speedup is .*" to stdout And gprecoverseg should print "Segments successfully recovered" to stdout - And gpAdminLogs directory has no "pg_basebackup*" files on all segment hosts - And gpAdminLogs directory has no "pg_rewind*" files on all segment hosts - And gpAdminLogs directory has no "rsync*" files on all segment hosts + And gpAdminLogs directory has "rsync*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files And gpAdminLogs directory has "gpsegsetuprecovery*" files And all the segments are running @@ -371,11 +534,61 @@ Feature: gprecoverseg tests When the user runs "gprecoverseg -a -s" And gprecoverseg should print "skipping pg_rewind on mirror as standby.signal is present" to stdout Then gprecoverseg should return a return code of 0 - And gpAdminLogs directory has no "pg_rewind*" files + And gpAdminLogs directory has "pg_rewind*" files + And all the segments are running + And the segments are synchronized + And the cluster is rebalanced + + Scenario: gprecoverseg should drop existing slot on full recovery + Given the database is running + And all the segments are running + And the segments are synchronized + And verify replication slot internal_wal_replication_slot is available on all the segments + And user stops all mirror processes + And user can start transactions + And the user waits until mirror on content 0,1,2 is down + When the user runs "gprecoverseg -a -F -v" + Then gprecoverseg should return a return code of 0 + And gprecoverseg should print "Checking if slot internal_wal_replication_slot exists" to stdout + And gprecoverseg should print "Successfully dropped replication slot internal_wal_replication_slot" to stdout + And gprecoverseg should print "pg_basebackup: base backup completed" to stdout + And gprecoverseg should print "Segments successfully recovered" to stdout + And verify that mirror on content 0,1,2 is up + And verify replication slot internal_wal_replication_slot is available on all the segments + And all the segments are running + And the segments are synchronized + And the cluster is rebalanced + + Scenario Outline: recovery should not try to drop slot if slot does not exist + Given the database is running + And all the segments are running + And the segments are synchronized + And verify replication slot internal_wal_replication_slot is available on all the segments + And the mirror on content 0 is stopped + And user can start transactions + And the status of the mirror on content 0 should be "d" + And the user runs sql "select pg_drop_replication_slot('internal_wal_replication_slot');" in "postgres" on first primary segment + When the user runs "gprecoverseg " + Then gprecoverseg should return a return code of 0 + And gprecoverseg should print "Checking if slot internal_wal_replication_slot exists" to stdout + And gprecoverseg should print "Slot internal_wal_replication_slot does not exist" to stdout + And gprecoverseg should not print "Successfully dropped replication slot internal_wal_replication_slot" to stdout + And gprecoverseg should print "Segments successfully recovered" to stdout + And verify that mirror on content 0 is up + And verify replication slot internal_wal_replication_slot is available on all the segments And all the segments are running And the segments are synchronized And the cluster is rebalanced + Examples: + | scenario | args | + | full | -avF | + + @differential + Examples: + | scenario | args | + | differential | -av --differential | + @backup_restore_bashrc Scenario: gprecoverseg should not return error when banner configured on host Given the database is running @@ -390,6 +603,49 @@ Feature: gprecoverseg tests And the segments are synchronized And the cluster is rebalanced + Scenario: gprecoverseg errors out with restricted options + Given the database is running + And user stops all primary processes + And user can start transactions + When the user runs "gprecoverseg -a -F -r" + Then gprecoverseg should return a return code of 2 + And gprecoverseg should print "-F option is not supported with -r option" to stdout + When the user runs "gprecoverseg -a -p localhost -F" + Then gprecoverseg should return a return code of 2 + And gprecoverseg should print "-F option is not supported with -p option" to stdout + When the user runs "gprecoverseg xyz" + Then gprecoverseg should return a return code of 2 + And gprecoverseg should print "Recovers a primary or mirror segment instance" to stdout + And gprecoverseg should print "too many arguments: only options may be specified" to stdout + When the user runs "gprecoverseg -a" + Then gprecoverseg should return a return code of 0 + And the segments are synchronized + And the cluster is rebalanced + + Scenario: gprecoverseg recovers segment for valid max-rate options and errors out for others + Given the database is running + And all the segments are running + And the segments are synchronized + When user stops all primary processes + And user can start transactions + And the user runs "gprecoverseg -aF --max-rate 30" + Then gprecoverseg should return a return code of 2 + And gprecoverseg should print "error: transfer rate 30 is out of range" to stdout + When the user runs "gprecoverseg -aF --max-rate k35" + Then gprecoverseg should return a return code of 2 + And gprecoverseg should print "error: transfer rate k35 is not a valid value" to stdout + When the user runs "gprecoverseg -aF --max-rate 0" + Then gprecoverseg should return a return code of 2 + And gprecoverseg should print "error: Transfer rate must be greater than zero" to stdout + When the user runs "gprecoverseg -aF --max-rate 32G" + Then gprecoverseg should return a return code of 2 + And gprecoverseg should print "error: Invalid --max-rate unit: G" to stdout + When the user runs "gprecoverseg -aF --max-rate 104857.6k" + Then gprecoverseg should return a return code of 0 + And gprecoverseg should print "Segments successfully recovered" to stdout + And gprecoverseg should print "Maximum Transfer Rate.*= 104857.6k" to stdout + And the segments are synchronized + And the cluster is rebalanced ########################### @concourse_cluster tests ########################### # The @concourse_cluster tag denotes the scenario that requires a remote cluster @@ -430,47 +686,71 @@ Feature: gprecoverseg tests And the cluster is returned to a good state Examples: - | scenario | args | - | incremental | -a | - | full | -aF | + | scenario | args | + | incremental | -a | + | differential | -a --differential | + | full | -aF | + + @demo_cluster + @concourse_cluster + Scenario: gprecoverseg creates output sample config file correctly when failed segment hosts are unreachable + Given the database is running + And all the segments are running + And the segments are synchronized + And the primary on content 1 is stopped + And the primary on content 2 is stopped + And user can start transactions + And the status of the primary on content 1 should be "d" + And the status of the primary on content 2 should be "d" + And the host for the primary on content 1 is made unreachable + When the user runs "gprecoverseg -o /tmp/output_config" + Then gprecoverseg should return a return code of 0 + And gprecoverseg should print "One or more hosts are not reachable via SSH." to stdout + And gprecoverseg should print "Host invalid_host is unreachable" to stdout + And the created config file /tmp/output_config contains the commented row for unreachable failed segment + And the cluster is returned to a good state + + @demo_cluster + @concourse_cluster + Scenario: gprecoverseg throws exception when -o flag used with invalid flags + Given the database is running + And all the segments are running + And the segments are synchronized + When the user runs "gprecoverseg -o output_config -i input_config" + Then gprecoverseg should return a return code of 2 + And gprecoverseg should print "Invalid -i provided with -o argument" to stdout + When the user runs "gprecoverseg -o /tmp/output_config -r" + Then gprecoverseg should return a return code of 2 + And gprecoverseg should print "Invalid -r provided with -o argument" to stdout @concourse_cluster - Scenario: incremental recovery works with tablespaces on a multi-host environment + Scenario Outline: recovery works with tablespaces on a multi-host environment Given the database is running - And a tablespace is created with data And user stops all primary processes And user can start transactions - When the user runs "gprecoverseg -a" + And a tablespace is created with data + When the user runs "gprecoverseg " Then gprecoverseg should return a return code of 0 And the segments are synchronized And the tablespace is valid + And the tablespace has valid symlink And the database segments are in execute mode Given another tablespace is created with data When the user runs "gprecoverseg -ra" Then gprecoverseg should return a return code of 0 And the segments are synchronized + And verify replication slot internal_wal_replication_slot is available on all the segments And the tablespace is valid + And the tablespace has valid symlink And the other tablespace is valid And the database segments are in execute mode - @concourse_cluster - Scenario: full recovery works with tablespaces on a multi-host environment - Given the database is running - And a tablespace is created with data - And user stops all primary processes - And user can start transactions - When the user runs "gprecoverseg -a -F" - Then gprecoverseg should return a return code of 0 - And the segments are synchronized - And the tablespace is valid - - Given another tablespace is created with data - When the user runs "gprecoverseg -ra" - Then gprecoverseg should return a return code of 0 - And the segments are synchronized - And the tablespace is valid - And the other tablespace is valid + Examples: + | scenario | args | + | incremental | -a | + | differential | -a --differential | + | full | -aF | @concourse_cluster Scenario: recovering a host with tablespaces succeeds @@ -511,6 +791,7 @@ Feature: gprecoverseg tests # verify the data And the tablespace is valid + And the tablespace has valid symlink And the row count from table "public.before_host_is_down" in "gptest" is verified against the saved data And the row count from table "public.after_host_is_down" in "gptest" is verified against the saved data @@ -525,14 +806,14 @@ Feature: gprecoverseg tests And sql "DROP TABLE IF EXISTS test_recoverseg; CREATE TABLE test_recoverseg AS SELECT generate_series(1,100000000) AS a;" is executed in "postgres" db When the user asynchronously runs "gprecoverseg -a" and the process is saved Then the user waits until recovery_progress.file is created in gpAdminLogs and verifies its format - And an FTS probe is triggered And the user waits until saved async process is completed And recovery_progress.file should not exist in gpAdminLogs And the user waits until mirror on content 0,1,2 is up And user can start transactions And all files in gpAdminLogs directory are deleted on all hosts in the cluster And a sample recovery_progress.file is created from saved lines - Then a sample gprecoverseg.lock directory is created in coordinator_data_directory + And we run a sample background script to generate a pid on "coordinator" segment + Then a sample gprecoverseg.lock directory is created using the background pid in coordinator_data_directory When the user runs "gpstate -e" Then gpstate should print "Segments in recovery" to stdout # And gpstate output contains "incremental,incremental,incremental" entries for mirrors of content 0,1,2 @@ -542,6 +823,7 @@ Feature: gprecoverseg tests # | \S+ | [0-9]+ | incremental | [0-9]+ | [0-9]+ | [0-9]+\% | # | \S+ | [0-9]+ | incremental | [0-9]+ | [0-9]+ | [0-9]+\% | And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And the background pid is killed on "coordinator" segment Then the gprecoverseg lock directory is removed And the cluster is rebalanced @@ -552,20 +834,14 @@ Feature: gprecoverseg tests And the user suspend the walsender on the primary on content 0 Then the user waits until recovery_progress.file is created in gpAdminLogs and verifies its format And verify that lines from recovery_progress.file are present in segment progress files in gpAdminLogs - + When the user runs "gpstate -e" + Then gpstate should print "Segments in recovery" to stdout And the user reset the walsender on the primary on content 0 And the user waits until saved async process is completed And recovery_progress.file should not exist in gpAdminLogs - And an FTS probe is triggered - And the user waits until mirror on content 0,1,2 is up + And verify that mirror on content 0,1,2 is up And user can start transactions - - And a sample recovery_progress.file is created from saved lines - Then a sample gprecoverseg.lock directory is created in coordinator_data_directory - When the user runs "gpstate -e" - Then gpstate should print "Segments in recovery" to stdout And all files in gpAdminLogs directory are deleted on all hosts in the cluster - Then the gprecoverseg lock directory is removed @demo_cluster @concourse_cluster @@ -583,7 +859,7 @@ Feature: gprecoverseg tests And the user reset the walsender on the primary on content 0 And the user waits until saved async process is completed And recovery_progress.file should not exist in gpAdminLogs - And the user waits until mirror on content 0,1,2 is up + And verify that mirror on content 0,1,2 is up And user can start transactions And all files in gpAdminLogs directory are deleted on all hosts in the cluster @@ -602,10 +878,27 @@ Feature: gprecoverseg tests And the user reset the walsender on the primary on content 0 And the user waits until saved async process is completed And recovery_progress.file should not exist in /tmp/custom_logdir - And the user waits until mirror on content 0,1,2 is up + And verify that mirror on content 0,1,2 is up And user can start transactions And all files in "/tmp/custom_logdir" directory are deleted on all hosts in the cluster + @demo_cluster + @concourse_cluster + Scenario: gprecoverseg creates recovery_progress.file in gpAdminLogs for differential recovery of mirrors + Given the database is running + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And user immediately stops all mirror processes for content 0,1,2 + And the user waits until mirror on content 0,1,2 is down + And user can start transactions + And sql "DROP TABLE IF EXISTS test_recoverseg; CREATE TABLE test_recoverseg AS SELECT generate_series(1,100000000) AS a;" is executed in "postgres" db + When the user asynchronously runs "gprecoverseg -a --differential" and the process is saved + Then the user waits until recovery_progress.file is created in gpAdminLogs and verifies its format + And verify that lines from recovery_progress.file are present in segment progress files in gpAdminLogs + And the user waits until saved async process is completed + And recovery_progress.file should not exist in gpAdminLogs + And verify that mirror on content 0,1,2 is up + And user can start transactions + And all files in gpAdminLogs directory are deleted on all hosts in the cluster @demo_cluster @concourse_cluster @@ -631,11 +924,12 @@ Feature: gprecoverseg tests And the user waits until mirror on content 0,1,2 is up And the old data directories are cleaned up for content 0 And user can start transactions + And check segment conf: postgresql.conf And all files in gpAdminLogs directory are deleted on all hosts in the cluster @demo_cluster @concourse_cluster - Scenario: SIGHUP on gprecoverseg should not display progress in gpstate -e + Scenario: SIGKILL on gprecoverseg should not display progress in gpstate -e Given the database is running And all the segments are running And the segments are synchronized @@ -649,13 +943,13 @@ Feature: gprecoverseg tests Then verify if the gprecoverseg.lock directory is present in coordinator_data_directory When the user runs "gpstate -e" Then gpstate should print "Segments in recovery" to stdout - When the user asynchronously sets up to end gprecoverseg process with SIGHUP + When the user asynchronously sets up to end gprecoverseg process with SIGKILL And the user waits until saved async process is completed - Then the gprecoverseg lock directory is removed When the user runs "gpstate -e" Then gpstate should not print "Segments in recovery" to stdout Then the user reset the walsender on the primary on content 0 And the user waits until mirror on content 0,1,2 is up + And the gprecoverseg lock directory is removed And the cluster is rebalanced @demo_cluster @@ -678,20 +972,185 @@ Feature: gprecoverseg tests When the user asynchronously runs gprecoverseg with input file and additional args "-a" and the process is saved Then the user waits until recovery_progress.file is created in gpAdminLogs and verifies its format And user waits until gp_stat_replication table has no pg_basebackup entries for content 1 - And an FTS probe is triggered And the user waits until mirror on content 1,2 is up And verify that mirror on content 0 is down And user can start transactions And verify that lines from recovery_progress.file are present in segment progress files in gpAdminLogs And the user reset the walsender on the primary on content 0 And the user waits until saved async process is completed - And gpAdminLogs directory has no "pg_basebackup*" files on all segment hosts - And gpAdminLogs directory has no "pg_rewind*" files on all segment hosts + And gpAdminLogs directory has "pg_basebackup*" files on respective hosts only for content 0,1 + And gpAdminLogs directory has "pg_rewind*" files on respective hosts only for content 2 And gpAdminLogs directory has "gpsegsetuprecovery*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files on all segment hosts And the cluster is recovered in full and rebalanced And the row count from table "test_recoverseg" in "postgres" is verified against the saved data + @demo_cluster + Scenario: gprecoverseg should not give warning if pg_basebackup is running for the up segments + Given the database is running + And all the segments are running + And the segments are synchronized + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And user immediately stops all primary processes for content 0,1 + # Wait for the failover to settle: while FTS is promoting the mirrors for + # content 0 and 1, a writer gang can still be built against the descriptors + # of the segments that just died, and the next statement fails with "FTS + # detected one or more segments are down". The scenario below this one does + # the same wait for the same reason. + And user can start transactions + And the user suspend the walsender on the primary on content 2 + And the user asynchronously runs pg_basebackup with primary of content 2 as source and the process is saved + And an FTS probe is triggered + And gp_stat_replication table has pg_basebackup entry for content 2 + When the user runs "gprecoverseg -avF" + Then gprecoverseg should not print "No basebackup running" to stdout + And gprecoverseg should return a return code of 0 + And verify that mirror on content 0,1 is up + And gp_stat_replication table has pg_basebackup entry for content 2 + And the user reset the walsender on the primary on content 2 + And the user waits until saved async process is completed + And verify that mirror on content 2 is up + And user can start transactions + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And the cluster is rebalanced + + @demo_cluster + @concourse_cluster + Scenario: gprecoverseg gives warning if pg_basebackup already running for one of the failed segments + Given the database is running + And all the segments are running + And the segments are synchronized + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And user immediately stops all primary processes for content 0,1,2 + And user can start transactions + And the user suspend the walsender on the primary on content 0 + And the user asynchronously runs "gprecoverseg -aF" and the process is saved + And the user just waits until recovery_progress.file is created in gpAdminLogs + And user waits until gp_stat_replication table has no pg_basebackup entries for content 1,2 + And the user waits until mirror on content 1,2 is up + And verify that mirror on content 0 is down + And the gprecoverseg lock directory is removed + And user immediately stops all primary processes for content 1,2 + And the user waits until mirror on content 1,2 is down + When the user runs "gprecoverseg -avF" + Then gprecoverseg should print "Found pg_basebackup running for segments with contentIds [0], skipping recovery of these segments" to logfile + And gprecoverseg should return a return code of 0 + And verify that mirror on content 1,2 is up + And verify that mirror on content 0 is down + And the user reset the walsender on the primary on content 0 + And the user waits until saved async process is completed + And recovery_progress.file should not exist in gpAdminLogs + And verify that mirror on content 0 is up + And the user runs "gprecoverseg -avF" + Then gprecoverseg should print "No basebackup running" to stdout + And gprecoverseg should return a return code of 0 + And the cluster is rebalanced + + @demo_cluster + @concourse_cluster + Scenario: gprecoverseg gives warning if pg_basebackup already running for some of the failed segments + Given the database is running + And all the segments are running + And the segments are synchronized + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And user immediately stops all primary processes for content 0,1,2 + And user can start transactions + And the user suspend the walsender on the primary on content 0 + And the user suspend the walsender on the primary on content 1 + And the user asynchronously runs "gprecoverseg -aF" and the process is saved + And the user just waits until recovery_progress.file is created in gpAdminLogs + And user waits until gp_stat_replication table has no pg_basebackup entries for content 2 + And the user waits until mirror on content 2 is up + And verify that mirror on content 0,1 is down + And the gprecoverseg lock directory is removed + And user immediately stops all primary processes for content 2 + And the user waits until mirror on content 2 is down + When the user runs "gprecoverseg -avF" + Then gprecoverseg should print "Found pg_basebackup running for segments with contentIds [0, 1], skipping recovery of these segments" to logfile + And gprecoverseg should return a return code of 0 + And verify that mirror on content 2 is up + And verify that mirror on content 0,1 is down + And the user reset the walsender on the primary on content 0 + And the user reset the walsender on the primary on content 1 + And the user waits until saved async process is completed + And recovery_progress.file should not exist in gpAdminLogs + And verify that mirror on content 0,1 is up + And the user runs "gprecoverseg -avF" + Then gprecoverseg should print "No basebackup running" to stdout + And gprecoverseg should return a return code of 0 + And the cluster is rebalanced + + @demo_cluster + @concourse_cluster + Scenario: gprecoverseg -aF gives warning if pg_basebackup already running for all of the failed segments + Given the database is running + And all the segments are running + And the segments are synchronized + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And user immediately stops all primary processes for content 0,1,2 + And user can start transactions + And the user suspend the walsender on the primary on content 0 + And the user suspend the walsender on the primary on content 1 + And the user suspend the walsender on the primary on content 2 + And the user asynchronously runs "gprecoverseg -aF" and the process is saved + And the user just waits until recovery_progress.file is created in gpAdminLogs + And verify that mirror on content 0,1,2 is down + And the gprecoverseg lock directory is removed + When the user runs "gprecoverseg -aF" + Then gprecoverseg should print "Found pg_basebackup running for segments with contentIds [0, 1, 2], skipping recovery of these segments" to logfile + And gprecoverseg should print "No segments to recover" to stdout + And gprecoverseg should return a return code of 0 + And verify that mirror on content 0,1,2 is down + And the user reset the walsender on the primary on content 0 + And the user reset the walsender on the primary on content 1 + And the user reset the walsender on the primary on content 2 + And the user waits until saved async process is completed + And recovery_progress.file should not exist in gpAdminLogs + And verify that mirror on content 0,1,2 is up + And the user runs "gprecoverseg -avF" + Then gprecoverseg should print "No basebackup running" to stdout + And gprecoverseg should return a return code of 0 + And verify that mirror on content 0,1,2 is up + And the cluster is rebalanced + + @demo_cluster + @concourse_cluster + Scenario: gprecoverseg -i gives warning if pg_basebackup already running for all failed segments + Given the database is running + And all the segments are running + And the segments are synchronized + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And user immediately stops all primary processes for content 0,1,2 + And user can start transactions + And the user suspend the walsender on the primary on content 0 + And the user suspend the walsender on the primary on content 1 + And the user suspend the walsender on the primary on content 2 + And a gprecoverseg directory under '/tmp' with mode '0700' is created + And a gprecoverseg input file is created + And edit the input file to recover mirror with content 0 full inplace + And edit the input file to recover mirror with content 1 full inplace + And edit the input file to recover mirror with content 2 full inplace + When the user asynchronously runs gprecoverseg with input file and additional args "-a" and the process is saved + Then the user just waits until recovery_progress.file is created in gpAdminLogs + And verify that mirror on content 0,1,2 is down + And the gprecoverseg lock directory is removed + When the user runs gprecoverseg with input file and additional args "-a" + Then gprecoverseg should print "Found pg_basebackup running for segments with contentIds [0, 1, 2], skipping recovery of these segments" to logfile + And gprecoverseg should print "No segments to recover" to stdout + And gprecoverseg should return a return code of 0 + And verify that mirror on content 0,1,2 is down + And the user reset the walsender on the primary on content 0 + And the user reset the walsender on the primary on content 1 + And the user reset the walsender on the primary on content 2 + And the user waits until saved async process is completed + And recovery_progress.file should not exist in gpAdminLogs + And verify that mirror on content 0,1,2 is up + And user can start transactions + When the user runs gprecoverseg with input file and additional args "-av" + Then gprecoverseg should print "No basebackup running" to stdout + And gprecoverseg should return a return code of 0 + Then the cluster is rebalanced + @demo_cluster @concourse_cluster Scenario: gprecoverseg incremental recovery segments come up even if one rewind fails @@ -709,16 +1168,16 @@ Feature: gprecoverseg tests And user can start transactions And check if incremental recovery failed for mirrors with content 0 for gprecoverseg - And gprecoverseg should print "Failed to recover the following segments. You must run gprecoverseg -F for all incremental failures" to stdout + And gprecoverseg should print "Failed to recover the following segments. You must run either gprecoverseg --differential or gprecoverseg -F for all incremental failures" to stdout And check if incremental recovery was successful for mirrors with content 1,2 - And gpAdminLogs directory has no "pg_basebackup*" files on all segment hosts + And gpAdminLogs directory has "pg_rewind*" files on all segment hosts And gpAdminLogs directory has "gpsegsetuprecovery*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files on all segment hosts And the cluster is recovered in full and rebalanced And the row count from table "test_recoverseg" in "postgres" is verified against the saved data - @demo_cluster + @demo_cluster @differential Scenario Outline: gprecoverseg differential recovery segments come up even if recovery for one segment fails Given the database is running And all the segments are running @@ -736,7 +1195,7 @@ Feature: gprecoverseg tests And gprecoverseg should print "Failed to recover the following segments. You must run either gprecoverseg --differential or gprecoverseg -F for all differential failures" to stdout And verify that mirror on content 1,2 is up And the segments are synchronized for content 1,2 - And gpAdminLogs directory has no "pg_basebackup*" files on all segment hosts + And gpAdminLogs directory has "rsync*" files on all segment hosts And gpAdminLogs directory has "gpsegsetuprecovery*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files on all segment hosts And the temporary directory is removed @@ -745,9 +1204,13 @@ Feature: gprecoverseg tests Examples: | scenario | args | - | differential | using differential | | full | in full | + @differential + Examples: + | scenario | args | + | differential | using differential | + @concourse_cluster Scenario: Propagating env var Given the database is running @@ -944,19 +1407,25 @@ Feature: gprecoverseg tests When the user runs gprecoverseg with input file and additional args "-a" Then gprecoverseg should return a return code of 1 And user can start transactions + And check segment conf: postgresql.conf + And check if incremental recovery failed for mirrors with content 0 for gprecoverseg And check if full recovery was successful for mirrors with content 1 And check if full recovery failed for mirrors with content 2 for gprecoverseg + And gprecoverseg should print "error:.*required WAL directory ""pg_wal"" does not exist" to stdout + And gprecoverseg should print "error: pg_basebackup: error: could not access directory.* Permission denied" to stdout And gprecoverseg should not print "Segments successfully recovered" to stdout And check if mirrors on content 0,1,2 are in their original configuration And the gp_configuration_history table should contain a backout entry for the primary segment for contents 2 And gpAdminLogs directory has "gpsegsetuprecovery*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files on all segment hosts + And check segment conf: postgresql.conf And the mode of all the created data directories is changed to 0700 And the cluster is recovered in full and rebalanced + And check segment conf: postgresql.conf And the row count from table "test_recoverseg" in "postgres" is verified against the saved data @demo_cluster @@ -990,8 +1459,8 @@ Feature: gprecoverseg tests And check if incremental recovery was successful for mirrors with content 2 And check if mirrors on content 0 are moved to new location on input file And check if mirrors on content 1,2 are in their original configuration - And gpAdminLogs directory has no "pg_basebackup*" files on all segment hosts - And gpAdminLogs directory has no "pg_rewind*" files on all segment hosts + And gpAdminLogs directory has "pg_basebackup*" files on respective hosts only for content 0,1 + And gpAdminLogs directory has "pg_rewind*" files on respective hosts only for content 2 And gpAdminLogs directory has "gpsegsetuprecovery*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files on all segment hosts And verify there are no recovery backout files @@ -1000,9 +1469,11 @@ Feature: gprecoverseg tests And the mode of all the created data directories is changed to 0700 Then the user runs "gprecoverseg -a" And gprecoverseg should return a return code of 0 + And all previous progress files are removed from gpAdminLogs directory on respective hosts only for content 0 And user can start transactions And the segments are synchronized And the cluster is rebalanced + And check segment conf: postgresql.conf And the row count from table "test_recoverseg" in "postgres" is verified against the saved data @demo_cluster @@ -1035,8 +1506,7 @@ Feature: gprecoverseg tests And verify that mirror on content 0,1,2 is down And check if mirrors on content 0,1,2 are moved to new location on input file - And gpAdminLogs directory has no "pg_basebackup*" files on all segment hosts - And gpAdminLogs directory has no "pg_rewind*" files on all segment hosts + And gpAdminLogs directory has "pg_basebackup*" files on all segment hosts And gpAdminLogs directory has "gpsegsetuprecovery*" files on all segment hosts And gpAdminLogs directory has "gpsegrecovery*" files on all segment hosts And verify there are no recovery backout files @@ -1047,8 +1517,71 @@ Feature: gprecoverseg tests And user can start transactions And the segments are synchronized And the cluster is rebalanced + And check segment conf: postgresql.conf And the row count from table "test_recoverseg" in "postgres" is verified against the saved data + @demo_cluster + @concourse_cluster + Scenario: gprecoverseg differential recovery gives warning if any of the failed segment's source is in backup already + Given the database is running + And all the segments are running + And the segments are synchronized + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And user immediately stops all primary processes for content 0,1,2 + And user can start transactions + And the user runs sql "select pg_start_backup('test')" in "postgres" on primary segment with content 0 + When the user runs "gprecoverseg -a --differential" + Then gprecoverseg should return a return code of 0 + And verify that mirror on content 1,2 is up + And verify that mirror on content 0 is down + Then gprecoverseg should print "Found differential recovery running for segments with contentIds [0], skipping recovery of these segments" to logfile + And the user runs sql "select pg_stop_backup()" in "postgres" on primary segment with content 0 + When the user runs "gprecoverseg -av --differential" + Then gprecoverseg should return a return code of 0 + And verify that mirror on content 0,1,2 is up + And the cluster is rebalanced + + @demo_cluster + @concourse_cluster + Scenario: gprecoverseg differential recovery gives warning if some of the failed segment's source is in backup already + Given the database is running + And all the segments are running + And the segments are synchronized + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And user immediately stops all primary processes for content 0,1,2 + And user can start transactions + And the user runs sql "select pg_start_backup('test')" in "postgres" on primary segment with content 0,1 + When the user runs "gprecoverseg -a --differential" + Then gprecoverseg should return a return code of 0 + And verify that mirror on content 2 is up + And verify that mirror on content 0,1 is down + Then gprecoverseg should print "Found differential recovery running for segments with contentIds [0, 1], skipping recovery of these segments" to logfile + And the user runs sql "select pg_stop_backup()" in "postgres" on primary segment with content 0,1 + When the user runs "gprecoverseg -av --differential" + Then gprecoverseg should return a return code of 0 + And verify that mirror on content 0,1,2 is up + And the cluster is rebalanced + + @demo_cluster + @concourse_cluster + Scenario: gprecoverseg differential recovery gives warning if all of the failed segment's source is in backup already + Given the database is running + And all the segments are running + And the segments are synchronized + And all files in gpAdminLogs directory are deleted on all hosts in the cluster + And user immediately stops all primary processes for content 0,1,2 + And user can start transactions + And the user runs sql "select pg_start_backup('test')" in "postgres" on primary segment with content 0,1,2 + When the user runs "gprecoverseg -a --differential" + Then gprecoverseg should return a return code of 0 + And verify that mirror on content 0,1,2 is down + Then gprecoverseg should print "Found differential recovery running for segments with contentIds [0, 1, 2], skipping recovery of these segments" to logfile + And the user runs sql "select pg_stop_backup()" in "postgres" on primary segment with content 0,1,2 + When the user runs "gprecoverseg -av --differential" + Then gprecoverseg should return a return code of 0 + And verify that mirror on content 0,1,2 is up + And the cluster is rebalanced + @concourse_cluster Scenario: gprecoverseg behave test requires a cluster with at least 2 hosts Given the database is running @@ -1056,7 +1589,7 @@ Feature: gprecoverseg tests And the information of a "mirror" segment on a remote host is saved @concourse_cluster - Scenario: When gprecoverseg full recovery is executed and an existing postmaster.pid on the killed primary segment corresponds to a non postgres process + Scenario Outline: When gprecoverseg recovery is executed and an existing postmaster.pid on the killed primary segment corresponds to a non postgres process Given the database is running And all the segments are running And the segments are synchronized @@ -1066,7 +1599,7 @@ Feature: gprecoverseg tests When user can start transactions And we run a sample background script to generate a pid on "primary" segment And we generate the postmaster.pid file with the background pid on "primary" segment - And the user runs "gprecoverseg -F -a" + And the user runs "gprecoverseg " Then gprecoverseg should return a return code of 0 And gprecoverseg should not print "Unhandled exception in thread started by when utility mode is set to @@ -125,8 +122,25 @@ Feature: gpstart behave tests | test_scenarios | utility_mode | psql_cmd | | super user connections | True | -c '\l' | | non-super user connections | True | -U foouser -c '\l' | - | super user connections | False | -c '\l' | - | non-super user connections | False | -U foouser -c '\l' | + + @concourse_cluster + @demo_cluster + Scenario Outline: "gpstart -m" accepts when utility mode is set to + Given the database is not running + And the user runs "gpstart -ma" + And "gpstart -ma" should return a return code of 0 + + When The user runs psql "" against database "postgres" when utility mode is set to "" + Then psql_cmd should return a return code of + And psql_cmd should print "" error message + + And the user runs "gpstop -mai" + And "gpstop -mai" should return a return code of 0 + + Examples: + | test_scenarios | utility_mode | psql_cmd | return_code | error_msg | + | super user connections | False | -c '\l' | 2 | psql: error: FATAL: System was started in single node mode - only utility mode connections are allowed | + | non-super user connections | False | -U foouser -c '\l' | 2 | psql: error: FATAL: System was started in single node mode - only utility mode connections are allowed | @concourse_cluster @demo_cluster @@ -143,11 +157,11 @@ Feature: gpstart behave tests And "gpstop -mai" should return a return code of 0 Examples: - | test_scenarios | utility_mode | psql_cmd | return_code | database | error_out_state | error_msg | - | super user connections | True | -c '\l' | 0 | accepts | should not | psql: error: FATAL: remaining connection slots are reserved for non-replication superuser connections | - | non-super user connections | True | -U foouser -c '\l' | 2 | rejects | should | psql: error: FATAL: remaining connection slots are reserved for non-replication superuser connections | - | super user connections | False | -c '\l' | 0 | accepts | should not | psql: error: FATAL: remaining connection slots are reserved for non-replication superuser connections | - | non-super user connections | False | -U foouser -c '\l' | 2 | rejects | should | psql: error: FATAL: remaining connection slots are reserved for non-replication superuser connections | + | test_scenarios | utility_mode | psql_cmd | return_code | database | error_out_state | error_msg | + | super user connections | True | -c '\l' | 0 | accepts | should not | psql: error: FATAL: remaining connection slots are reserved for non-replication superuser connections | + | non-super user connections | True | -U foouser -c '\l' | 2 | rejects | should | psql: error: FATAL: remaining connection slots are reserved for non-replication superuser connections | + | super user connections | False | -c '\l' | 2 | accepts | should | psql: error: FATAL: System was started in single node mode - only utility mode connections are allowed | + | non-super user connections | False | -U foouser -c '\l' | 2 | rejects | should | psql: error: FATAL: System was started in single node mode - only utility mode connections are allowed | @concourse_cluster @demo_cluster @@ -187,5 +201,3 @@ Feature: gpstart behave tests When the user runs "gpstart -a -B 1" Then "gpstart -a -B 1" should return a return code of 0 And gpcheckcat should not print "Number of segments which failed to start:.*" to stdout - - diff --git a/gpMgmt/test/behave/mgmt_utils/gpstate.feature b/gpMgmt/test/behave/mgmt_utils/gpstate.feature index e03c7f7bd8e..49fc475c0e2 100644 --- a/gpMgmt/test/behave/mgmt_utils/gpstate.feature +++ b/gpMgmt/test/behave/mgmt_utils/gpstate.feature @@ -251,7 +251,7 @@ Feature: gpstate tests Scenario: gpstate -m logs mirror details Given a standard local demo cluster is running When the user runs "gpstate -m" - Then gpstate should print "Current GPDB mirror list and status" to stdout + Then gpstate should print "Current CBDB mirror list and status" to stdout And gpstate output looks like | Mirror | Datadir | Port | Status | Data Status | | \S+ | .*/dbfast_mirror1/demoDataDir0 | [0-9]+ | Passive | Synchronized | @@ -263,7 +263,7 @@ Feature: gpstate tests And user stops all primary processes And user can start transactions When the user runs "gpstate -m" - Then gpstate should print "Current GPDB mirror list and status" to stdout + Then gpstate should print "Current CBDB mirror list and status" to stdout And gpstate output looks like | Mirror | Datadir | Port | Status | Data Status | | \S+ | .*/dbfast_mirror1/demoDataDir0 | [0-9]+ | Acting as Primary | Not In Sync | diff --git a/gpMgmt/test/behave/mgmt_utils/gpstop.feature b/gpMgmt/test/behave/mgmt_utils/gpstop.feature index aed47ea7103..1f350efb3e9 100644 --- a/gpMgmt/test/behave/mgmt_utils/gpstop.feature +++ b/gpMgmt/test/behave/mgmt_utils/gpstop.feature @@ -5,8 +5,10 @@ Feature: gpstop behave tests @demo_cluster Scenario: gpstop succeeds Given the database is running + And running postgres processes are saved in context When the user runs "gpstop -a" Then gpstop should return a return code of 0 + And verify no postgres process is running on all hosts @demo_cluster Scenario: gpstop runs with given coordinator data directory option @@ -33,20 +35,24 @@ Feature: gpstop behave tests Scenario: when there are user connections gpstop waits to shutdown until user switches to fast mode Given the database is running And the user asynchronously runs "psql postgres" and the process is saved + And running postgres processes are saved in context When the user runs gpstop -a -t 4 --skipvalidation and selects f And gpstop should print "'\(s\)mart_mode', '\(f\)ast_mode', '\(i\)mmediate_mode'" to stdout Then gpstop should return a return code of 0 + And verify no postgres process is running on all hosts @concourse_cluster @demo_cluster Scenario: when there are user connections gpstop waits to shutdown until user connections are disconnected Given the database is running And the user asynchronously runs "psql postgres" and the process is saved - And the user asynchronously sets up to end that process in 6 seconds + And the user asynchronously sets up to end that process in 15 seconds + And running postgres processes are saved in context When the user runs gpstop -a -t 2 --skipvalidation and selects s And gpstop should print "There were 1 user connections at the start of the shutdown" to stdout And gpstop should print "'\(s\)mart_mode', '\(f\)ast_mode', '\(i\)mmediate_mode'" to stdout Then gpstop should return a return code of 0 + And verify no postgres process is running on all hosts @demo_cluster Scenario: gpstop succeeds even if the standby host is unreachable @@ -58,6 +64,149 @@ Feature: gpstop behave tests And gpstop should return a return code of 0 And the standby host is made reachable + @demo_cluster + Scenario: gpstop succeeds when pg_ctl command fails + Given the database is running + And the user runs psql with "-c "CREATE EXTENSION IF NOT EXISTS gp_inject_fault;"" against database "postgres" + And the user runs psql with "-c "SELECT gp_inject_fault('checkpoint', 'sleep', '', '', '', 1, -1, 3600, dbid) FROM gp_segment_configuration WHERE content = -1 AND role = 'p'"" against database "postgres" + And running postgres processes are saved in context + When the user runs "gpstop -a -M fast" + And gpstop should print "Failed to shutdown coordinator with pg_ctl." to stdout + And gpstop should return a return code of 0 + And verify no postgres process is running on all hosts + + @demo_cluster + Scenario: gpstop succeeds with immediate option + Given the database is running + And the user asynchronously runs "psql postgres" and the process is saved + And the user asynchronously sets up to end that process in 15 seconds + And running postgres processes are saved in context + When the user runs "gpstop -a -M immediate" + And gpstop should print "Commencing Coordinator instance shutdown with mode='immediate'" to stdout + Then gpstop should return a return code of 0 + And verify no postgres process is running on all hosts + + @concourse_cluster + @demo_cluster + Scenario Outline: when the first gpstop interrupted and second gpstop handles the unfinished state with mode + Given the database is running + And the user asynchronously runs "psql postgres" and the process is saved + And running postgres processes are saved in context + When the user runs gpstop -a, selects s and interrupt the process + Then verify if the gpstop.lock directory is present in coordinator_data_directory + And the user runs gpstop -a and selects