Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package crmsh for openSUSE:Factory checked in at 2026-06-01 18:04:27 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/crmsh (Old) and /work/SRC/openSUSE:Factory/.crmsh.new.1937 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "crmsh" Mon Jun 1 18:04:27 2026 rev:409 rq:1356243 version:5.1.0+20260528.b0973362 Changes: -------- --- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes 2026-05-08 16:47:14.913858069 +0200 +++ /work/SRC/openSUSE:Factory/.crmsh.new.1937/crmsh.changes 2026-06-01 18:06:18.872049963 +0200 @@ -1,0 +2,20 @@ +Thu May 28 08:17:49 UTC 2026 - [email protected] + +- Update to version 5.1.0+20260528.b0973362: + * Chore: github-actions: update to node.js 24 actions + * Dev: behave: Add new test case for port conflict + * Dev: unittests: Adjust unit test for previous commit + * Dev: ui_cluster: Refactor qdevice options validation + * Dev: ui_cluster: Add --qnetd-port option and deprecate --qdevice-port option + * Dev: qdevice: Configure "-p <port>" for /etc/sysconfig/corosync-qnetd on qnetd server + +------------------------------------------------------------------- +Mon May 11 03:42:49 UTC 2026 - [email protected] + +- Update to version 5.1.0+20260511.e4dd3500: + * Dev: unittests: Adjust unit test for previous commit + * Dev: Separately check if corosync-qdevice.service is successfully started + * Dev: service_manager: Use 'systemctl is-active' to make sure the service is really active + * Dev: bootstrap: Find and show failed services + +------------------------------------------------------------------- Old: ---- crmsh-5.1.0+20260508.3e8e1915.tar.bz2 New: ---- crmsh-5.1.0+20260528.b0973362.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ crmsh.spec ++++++ --- /var/tmp/diff_new_pack.JtYj7w/_old 2026-06-01 18:06:20.156103215 +0200 +++ /var/tmp/diff_new_pack.JtYj7w/_new 2026-06-01 18:06:20.156103215 +0200 @@ -41,7 +41,7 @@ Summary: High Availability cluster command-line interface License: GPL-2.0-or-later Group: %{pkg_group} -Version: 5.1.0+20260508.3e8e1915 +Version: 5.1.0+20260528.b0973362 Release: 0 URL: http://crmsh.github.io Source0: %{name}-%{version}.tar.bz2 ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.JtYj7w/_old 2026-06-01 18:06:20.216105703 +0200 +++ /var/tmp/diff_new_pack.JtYj7w/_new 2026-06-01 18:06:20.220105869 +0200 @@ -9,7 +9,7 @@ </service> <service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/crmsh.git</param> - <param name="changesrevision">3e8e191562b01d457fd4b3a0656f6a1827172f7f</param> + <param name="changesrevision">b097336298cdaa1df6f28dd5b1b20ef75b5b54b9</param> </service> </servicedata> (No newline at EOF) ++++++ crmsh-5.1.0+20260508.3e8e1915.tar.bz2 -> crmsh-5.1.0+20260528.b0973362.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/.github/workflows/crmsh-cd.yml new/crmsh-5.1.0+20260528.b0973362/.github/workflows/crmsh-cd.yml --- old/crmsh-5.1.0+20260508.3e8e1915/.github/workflows/crmsh-cd.yml 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/.github/workflows/crmsh-cd.yml 2026-05-28 09:45:27.000000000 +0200 @@ -25,7 +25,7 @@ runs-on: ubuntu-24.04 timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: delivery process run: | docker pull "${CONTAINER_IMAGE}" @@ -42,7 +42,7 @@ runs-on: ubuntu-24.04 timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: submit process run: | docker pull "${CONTAINER_IMAGE}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/.github/workflows/crmsh-ci.yml new/crmsh-5.1.0+20260528.b0973362/.github/workflows/crmsh-ci.yml --- old/crmsh-5.1.0+20260508.3e8e1915/.github/workflows/crmsh-ci.yml 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/.github/workflows/crmsh-ci.yml 2026-05-28 09:45:27.000000000 +0200 @@ -17,7 +17,7 @@ general_check: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: check data-manifest run: | ./update-data-manifest.sh @@ -39,9 +39,9 @@ fail-fast: false timeout-minutes: 5 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -51,7 +51,7 @@ - name: Test with pytest in tox run: | tox -v -e${{ matrix.python-version }} - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: unit @@ -60,12 +60,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for crm_report bugs run: | index=`$GET_INDEX_OF crm_report_bugs` $CONTAINER_SCRIPT $index && $CONTAINER_SCRIPT -d && $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -74,12 +74,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for crm_report normal run: | index=`$GET_INDEX_OF crm_report_normal` $CONTAINER_SCRIPT $index && $CONTAINER_SCRIPT -d && $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -88,12 +88,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for bootstrap bugs run: | index=`$GET_INDEX_OF bootstrap_bugs` $CONTAINER_SCRIPT $index - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -102,12 +102,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for bootstrap bugs, under non root user run: | index=`$GET_INDEX_OF bootstrap_bugs` $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -116,12 +116,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for bootstrap common run: | index=`$GET_INDEX_OF bootstrap_init_join_remove` $CONTAINER_SCRIPT $index - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -130,12 +130,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for bootstrap common, under non root user run: | index=`$GET_INDEX_OF bootstrap_init_join_remove` $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -144,12 +144,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for bootstrap options run: | index=`$GET_INDEX_OF bootstrap_options` $CONTAINER_SCRIPT $index - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -158,12 +158,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for bootstrap firewalld run: | index=`$GET_INDEX_OF bootstrap_firewalld` $CONTAINER_SCRIPT $index - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -172,12 +172,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for bootstrap using password run: | index=`$GET_INDEX_OF bootstrap_password` $CONTAINER_SCRIPT $index - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -187,12 +187,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for crm corosync subcommand run: | index=`$GET_INDEX_OF corosync_ui` $CONTAINER_SCRIPT $index - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -201,12 +201,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for bootstrap options, under non root user run: | index=`$GET_INDEX_OF bootstrap_options` $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -215,12 +215,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for qdevice setup and remove run: | index=`$GET_INDEX_OF qdevice_setup_remove` $CONTAINER_SCRIPT $index - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -229,12 +229,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for qdevice setup and remove, under non root user run: | index=`$GET_INDEX_OF qdevice_setup_remove` $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -243,12 +243,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for qdevice options run: | index=`$GET_INDEX_OF qdevice_options` $CONTAINER_SCRIPT $index && $CONTAINER_SCRIPT -d && $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -257,12 +257,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for qdevice validate run: | index=`$GET_INDEX_OF qdevice_validate` $CONTAINER_SCRIPT $index - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -271,12 +271,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for qdevice validate, under non root user run: | index=`$GET_INDEX_OF qdevice_validate` $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -285,12 +285,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for qdevice user case run: | index=`$GET_INDEX_OF qdevice_usercase` $CONTAINER_SCRIPT $index && $CONTAINER_SCRIPT -d && $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -299,12 +299,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for resource failcount run: | index=`$GET_INDEX_OF resource_failcount` $CONTAINER_SCRIPT $index && $CONTAINER_SCRIPT -d && $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -313,12 +313,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for resource set run: | index=`$GET_INDEX_OF resource_set` $CONTAINER_SCRIPT $index - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -327,12 +327,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for resource set, under non root user run: | index=`$GET_INDEX_OF resource_set` $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -341,12 +341,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for configure sublevel bugs run: | index=`$GET_INDEX_OF configure_bugs` $CONTAINER_SCRIPT $index && $CONTAINER_SCRIPT -d && $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -355,12 +355,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for constraints bugs run: | index=`$GET_INDEX_OF constraints_bugs` $CONTAINER_SCRIPT $index && $CONTAINER_SCRIPT -d && $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -369,12 +369,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for geo cluster run: | index=`$GET_INDEX_OF geo_setup` $CONTAINER_SCRIPT $index && $CONTAINER_SCRIPT -d && $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -383,12 +383,12 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for healthcheck run: | index=`$GET_INDEX_OF healthcheck` $CONTAINER_SCRIPT $index && $CONTAINER_SCRIPT -d && $CONTAINER_SCRIPT $index -u - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -397,11 +397,11 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for cluster api run: | $CONTAINER_SCRIPT `$GET_INDEX_OF cluster_api` - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -410,11 +410,11 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for user access run: | $CONTAINER_SCRIPT `$GET_INDEX_OF user_access` - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -423,11 +423,11 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for ssh agent run: | $CONTAINER_SCRIPT `$GET_INDEX_OF ssh_agent` && $CONTAINER_SCRIPT -d && $CONTAINER_SCRIPT -u `$GET_INDEX_OF ssh_agent` - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -436,11 +436,11 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for blocking ssh run: | $CONTAINER_SCRIPT `$GET_INDEX_OF cluster_blocking_ssh` - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -449,13 +449,13 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for migration run: | echo '{ "exec-opts": ["native.cgroupdriver=systemd"] }' | sudo tee /etc/docker/daemon.json sudo systemctl restart docker.service $CONTAINER_SCRIPT `$GET_INDEX_OF migration` && $CONTAINER_SCRIPT -d && $CONTAINER_SCRIPT -u `$GET_INDEX_OF migration` - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -464,11 +464,11 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: functional test for pacemaker remote run: | $CONTAINER_SCRIPT `$GET_INDEX_OF pacemaker_remote` - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} flags: integration @@ -477,7 +477,7 @@ runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: original regression test run: | $CONTAINER_SCRIPT `$GET_INDEX_OF "regression test"` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/.github/workflows/test-container-image.yml new/crmsh-5.1.0+20260528.b0973362/.github/workflows/test-container-image.yml --- old/crmsh-5.1.0+20260508.3e8e1915/.github/workflows/test-container-image.yml 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/.github/workflows/test-container-image.yml 2026-05-28 09:45:27.000000000 +0200 @@ -16,7 +16,7 @@ run: working-directory: ./test_container steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: build container image run: podman image build -t haleap:ci . - name: push container image diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/crmsh/bootstrap.py new/crmsh-5.1.0+20260528.b0973362/crmsh/bootstrap.py --- old/crmsh-5.1.0+20260508.3e8e1915/crmsh/bootstrap.py 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/crmsh/bootstrap.py 2026-05-28 09:45:27.000000000 +0200 @@ -112,6 +112,7 @@ self.qdevice_inst = None self.qnetd_addr_input = None self.qdevice_port = None + self.qnetd_port = None self.qdevice_algo = None self.qdevice_tie_breaker = None self.qdevice_tls = None @@ -155,16 +156,38 @@ ctx.initialize_user() return ctx + def _any_qdevice_options_set(self): + return any([ + self.qdevice_port, + self.qnetd_port, + self.qdevice_algo, + self.qdevice_tie_breaker, + self.qdevice_tls, + self.qdevice_heuristics, + self.qdevice_heuristics_mode, + ]) + def _initialize_qdevice(self): """ Initialize qdevice instance """ if not self.qnetd_addr_input: + if self._any_qdevice_options_set() or self.stage == "qdevice": + utils.fatal("Option --qnetd-hostname is required if want to configure qdevice") return + + if self.qdevice_port is not None: + logger.warning("Option --qdevice-port is deprecated and will be removed in future release, please use --qnetd-port instead") + if self.qnetd_port is not None: + utils.fatal("Options --qdevice-port and --qnetd-port can't be used together") + if self.qdevice_heuristics_mode and not self.qdevice_heuristics: + utils.fatal("Option --qdevice-heuristics is required if want to configure heuristics mode") + + qnetd_port = self.qnetd_port or self.qdevice_port ssh_user, qnetd_host = utils.parse_user_at_host(self.qnetd_addr_input) self.qdevice_inst = qdevice.QDevice( qnetd_addr=qnetd_host, - port=self.qdevice_port, + port=qnetd_port, algo=self.qdevice_algo, tie_breaker=self.qdevice_tie_breaker, tls=self.qdevice_tls, @@ -726,12 +749,15 @@ logger.warning("You should change the hacluster password to something more secure!") if not start_pacemaker(enable_flag=True): - utils.fatal("Failed to start cluster services") + failed_services = get_failed_services() + failed_services_str = f" Please check failed services: {', '.join(failed_services)}" if failed_services else "" + utils.fatal(f"Failed to start cluster services.{failed_services_str}") if _context and _context.type == "init": if corosync.is_qdevice_configured(): - logger.info("Starting and enable corosync-qdevice.service on %s", utils.this_node()) - service_manager.start_service("corosync-qdevice.service", enable=True) + logger.info("Starting and enable %s on %s", constants.COROSYNC_QDEVICE_SERVICE, utils.this_node()) + if not service_manager.start_service(constants.COROSYNC_QDEVICE_SERVICE, enable=True): + logger.error("Failed to start %s on %s", constants.COROSYNC_QDEVICE_SERVICE, utils.this_node()) elif service_manager.service_is_enabled("corosync-qdevice.service"): service_manager.disable_service("corosync-qdevice.service") @@ -1556,8 +1582,8 @@ ssh_user, qnetd_host = utils.parse_user_at_host(qnetd_addr_input) qdevice.QDevice.check_qnetd_addr(qnetd_host) _context.qnetd_addr_input = qnetd_addr_input - qdevice_port = prompt_for_string("TCP PORT of QNetd server", default=5403, - valid_func=qdevice.QDevice.check_qdevice_port) + qnetd_port = prompt_for_string("TCP PORT of QNetd server", + valid_func=qdevice.QDevice.check_qnetd_port) qdevice_algo = prompt_for_string("QNetd decision ALGORITHM (ffsplit/lms)", default="ffsplit", valid_func=qdevice.QDevice.check_qdevice_algo) qdevice_tie_breaker = prompt_for_string("QNetd TIE_BREAKER (lowest/highest/valid node id)", default="lowest", @@ -1572,7 +1598,7 @@ _context.qdevice_inst = qdevice.QDevice( qnetd_host, - port=qdevice_port, + port=qnetd_port, algo=qdevice_algo, tie_breaker=qdevice_tie_breaker, tls=qdevice_tls, @@ -2902,4 +2928,19 @@ sbd.SBDManager.SBD_SYSTEMD_DELAY_START_DISABLE_DIR ) + STATIC_FILES_TO_SYNC ) + + +def get_failed_services(peer=None) -> list[str]: + failed_services = [] + shell = sh.cluster_shell() + for service in ( + constants.COROSYNC_SERVICE, + constants.SBD_SERVICE, + constants.PCMK_SERVICE + ): + cmd = f"systemctl is-failed {service}" + rc, _, _ = shell.get_rc_stdout_stderr_without_input(peer, cmd) + if rc == 0: + failed_services.append(service) + return failed_services # EOF diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/crmsh/constants.py new/crmsh-5.1.0+20260528.b0973362/crmsh/constants.py --- old/crmsh-5.1.0+20260508.3e8e1915/crmsh/constants.py 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/crmsh/constants.py 2026-05-28 09:45:27.000000000 +0200 @@ -440,6 +440,8 @@ NO_SSH_ERROR_MSG = "ssh-related operations are disabled. crmsh works in local mode." +COROSYNC_SERVICE = "corosync.service" +COROSYNC_QDEVICE_SERVICE = "corosync-qdevice.service" PCMK_SERVICE = "pacemaker.service" SBD_SERVICE = "sbd.service" # vim:ts=4:sw=4:et: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/crmsh/qdevice.py new/crmsh-5.1.0+20260528.b0973362/crmsh/qdevice.py --- old/crmsh-5.1.0+20260508.3e8e1915/crmsh/qdevice.py 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/crmsh/qdevice.py 2026-05-28 09:45:27.000000000 +0200 @@ -111,6 +111,8 @@ Call `certificate_process_on_init` to generate all of CA, server, and client certs. """ + SYSCONFIG_QNETD = "/etc/sysconfig/corosync-qnetd" + QNETD_DEFAULT_PORT = 5403 qnetd_service = "corosync-qnetd.service" qnetd_cacert_filename = "qnetd-cacert.crt" qdevice_crq_filename = "qdevice-net-node.crq" @@ -119,19 +121,19 @@ qdevice_path = "/etc/corosync/qdevice/net" qdevice_db_path = "/etc/corosync/qdevice/net/nssdb" - def __init__(self, qnetd_addr, port=5403, algo="ffsplit", tie_breaker="lowest", - tls="on", ssh_user=None, cmds=None, mode=None, cluster_name=None, is_stage=False): + def __init__(self, qnetd_addr, port=None, algo=None, tie_breaker=None, + tls=None, ssh_user=None, cmds=None, mode=None, cluster_name=None, is_stage=False): """ Init function """ self.qnetd_addr = qnetd_addr self.port = port - self.algo = algo - self.tie_breaker = tie_breaker - self.tls = tls + self.algo = algo or "ffsplit" + self.tie_breaker = tie_breaker or "lowest" + self.tls = tls or "on" self.ssh_user = ssh_user self.cmds = cmds - self.mode = mode + self.mode = mode or "sync" self.cluster_name = cluster_name self.qdevice_reload_policy = QdevicePolicy.QDEVICE_RESTART self.is_stage = is_stage @@ -208,9 +210,9 @@ @staticmethod - def check_qdevice_port(qdevice_port): - if not utils.valid_port(qdevice_port): - raise ValueError("invalid qdevice port range(1024 - 65535)") + def check_qnetd_port(qnetd_port): + if qnetd_port and not utils.valid_port(qnetd_port): + raise ValueError("invalid qnetd port range(1024 - 65535)") @staticmethod def check_qdevice_algo(qdevice_algo): @@ -258,7 +260,7 @@ utils.check_all_nodes_reachable("setup Qdevice") self.check_corosync_qdevice_available() self.check_qnetd_addr(self.qnetd_addr) - self.check_qdevice_port(self.port) + self.check_qnetd_port(self.port) self.check_qdevice_algo(self.algo) self.check_qdevice_tie_breaker(self.tie_breaker) self.check_qdevice_tls(self.tls) @@ -289,8 +291,11 @@ raise ValueError(f"{exception_msg}\n{suggestion_msg}") def start_qnetd(self): + service_manager = ServiceManager() + if service_manager.service_is_active(self.qnetd_service, self.qnetd_addr): + return logger.info("Starting and enable corosync-qnetd.service on %s" % self.qnetd_addr) - ServiceManager().start_service(self.qnetd_service, enable=True, remote_addr=self.qnetd_addr) + service_manager.start_service(self.qnetd_service, enable=True, remote_addr=self.qnetd_addr) def set_cluster_name(self): if not self.cluster_name: @@ -511,10 +516,80 @@ cmd = "test -f {crq_file} && rm -f {crq_file}".format(crq_file=cls_inst.qdevice_crq_on_qnetd) shell.get_stdout_or_raise_error(cmd, qnetd_host) + def _handle_port_when_qnetd_active(self): + cmd = "corosync-qnetd-tool -s" + out = sh.cluster_shell().get_stdout_or_raise_error(cmd, self.qnetd_addr) + res = re.search(r'QNetd address:\s+\S+:(\d+)', out) + if res: + port_in_qnetd = int(res.group(1)) + if self.port is not None and self.port != port_in_qnetd: + error_msg = f"The port {self.port} is different from the port {port_in_qnetd} that corosync-qnetd is using" + suggestion_msg = f"Please use '--qnetd-port {port_in_qnetd}' to keep consistent" + raise ValueError(f"{error_msg}\n{suggestion_msg}") + else: + self.port = port_in_qnetd + else: + # this should not happen, just in case + raise ValueError(f"Failed to get qnetd port from corosync-qnetd-tool output on {self.qnetd_addr}") + + def _handle_port_when_qnetd_inactive(self): + shell = sh.cluster_shell() + action_cmd = "" + + cmd = f"test -f {self.SYSCONFIG_QNETD}" + rc, _, _ = shell.get_rc_stdout_stderr_without_input(self.qnetd_addr, cmd) + if rc != 0: + port_option = "" if self.port is None else f"-p {self.port}" + options = f"COROSYNC_QNETD_OPTIONS=\"{port_option}\"\nCOROSYNC_QNETD_RUNAS=\"\"" + logger.info(f"Write qnetd options to {self.SYSCONFIG_QNETD} on {self.qnetd_addr}: {options.strip()}") + action_cmd = f"echo -e '{options}' > {self.SYSCONFIG_QNETD}" + + else: + cmd = f"grep '^[[:space:]]*COROSYNC_QNETD_OPTIONS=' {self.SYSCONFIG_QNETD}" + rc, out, _ = shell.get_rc_stdout_stderr_without_input(self.qnetd_addr, cmd) + if rc == 0 and out: + res = re.search(r'-p\s+(\d+)', out) + if res: + port_in_sysconfig = int(res.group(1)) + if self.port is not None and self.port != port_in_sysconfig: + error_msg = f"The port {self.port} is different from the port {port_in_sysconfig} in {self.SYSCONFIG_QNETD}" + suggestion_msg = f"Please use '--qnetd-port {port_in_sysconfig}' to keep consistent" + raise ValueError(f"{error_msg}\n{suggestion_msg}") + else: + self.port = port_in_sysconfig + + elif self.port is not None: + value_of_options = out.strip().split('=', 1)[1].strip('"') + if value_of_options: + options = f'COROSYNC_QNETD_OPTIONS="{value_of_options} -p {self.port}"' + else: + options = f'COROSYNC_QNETD_OPTIONS="-p {self.port}"' + logger.info(f"Update qnetd options in {self.SYSCONFIG_QNETD} on {self.qnetd_addr} to: {options}") + action_cmd = f"sed -i 's|COROSYNC_QNETD_OPTIONS=.*|{options}|' {self.SYSCONFIG_QNETD}" + else: + options = "COROSYNC_QNETD_OPTIONS=\"\"" if self.port is None else f"COROSYNC_QNETD_OPTIONS=\"-p {self.port}\"" + logger.info(f"Add qnetd options to {self.SYSCONFIG_QNETD} on {self.qnetd_addr}: {options}") + action_cmd = f"echo '{options}' >> {self.SYSCONFIG_QNETD}" + + if action_cmd: + shell.get_stdout_or_raise_error(action_cmd, self.qnetd_addr) + + def config_qnetd_port_in_sysconfig(self): + if ServiceManager().service_is_active("corosync-qnetd.service", self.qnetd_addr): + self._handle_port_when_qnetd_active() + else: + self._handle_port_when_qnetd_inactive() + + if self.port is None: + self.port = self.QNETD_DEFAULT_PORT + logger.info(f"Use port {self.port} for corosync-qnetd on {self.qnetd_addr}") + def config_qnetd_port(self): """ Enable qnetd port in firewalld """ + self.config_qnetd_port_in_sysconfig() + if not ServiceManager().service_is_active("firewalld.service", self.qnetd_addr): return if utils.check_port_open(self.qnetd_addr, self.port): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/crmsh/service_manager.py new/crmsh-5.1.0+20260528.b0973362/crmsh/service_manager.py --- old/crmsh-5.1.0+20260508.3e8e1915/crmsh/service_manager.py 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/crmsh/service_manager.py 2026-05-28 09:45:27.000000000 +0200 @@ -37,9 +37,13 @@ Return success node list """ if enable: - cmd = "systemctl enable --now '{}'".format(name) + cmd = ( + f"systemctl enable --now '{name}' && " + f"systemctl is-enabled '{name}' && " + f"systemctl is-active '{name}'" + ) else: - cmd = "systemctl start '{}'".format(name) + cmd = f"systemctl start '{name}' && systemctl is-active '{name}'" return self._call(remote_addr, node_list, cmd) def _call(self, remote_addr: str, node_list: typing.List[str], cmd: str) -> typing.List[str]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/crmsh/ui_cluster.py new/crmsh-5.1.0+20260528.b0973362/crmsh/ui_cluster.py --- old/crmsh-5.1.0+20260508.3e8e1915/crmsh/ui_cluster.py 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/crmsh/ui_cluster.py 2026-05-28 09:45:27.000000000 +0200 @@ -190,14 +190,19 @@ return if start_qdevice: - service_manager.start_service("corosync-qdevice", node_list=node_list) + success_list = service_manager.start_service("corosync-qdevice", node_list=node_list) + for node in node_list: + if node not in success_list: + logger.error("Failed to start %s on %s", constants.COROSYNC_QDEVICE_SERVICE, node) success_flag = True success_list = bootstrap.start_pacemaker(node_list=node_list) for node in node_list: if node not in success_list: + failed_services = bootstrap.get_failed_services(node) + failed_services_str = f" Please check failed services: {', '.join(failed_services)}" if failed_services else "" + logger.error("The cluster stack failed to start on %s.%s", node, failed_services_str) success_flag = False - logger.error("The cluster stack failed to start on %s", node) continue logger.info("The cluster stack started on %s", node) @@ -454,13 +459,15 @@ qdevice_group = parser.add_argument_group("QDevice configuration", re.sub(' ', '', constants.QDEVICE_HELP_INFO) + "\n\nOptions for configuring QDevice and QNetd.") qdevice_group.add_argument("--qnetd-hostname", dest="qnetd_addr_input", metavar="[USER@]HOST", help="User and host of the QNetd server. The host can be specified in either hostname or IP address.") - qdevice_group.add_argument("--qdevice-port", dest="qdevice_port", metavar="PORT", type=int, default=5403, - help="TCP PORT of QNetd server (default:5403)") - qdevice_group.add_argument("--qdevice-algo", dest="qdevice_algo", metavar="ALGORITHM", default="ffsplit", choices=['ffsplit', 'lms'], + qdevice_group.add_argument("--qdevice-port", dest="qdevice_port", metavar="PORT", type=int, + help=f"TCP PORT of QNetd server (default:{qdevice.QDevice.QNETD_DEFAULT_PORT}, deprecated, use --qnetd-port instead)") + qdevice_group.add_argument("--qnetd-port", dest="qnetd_port", metavar="PORT", type=int, + help=f"TCP PORT of QNetd server (default:{qdevice.QDevice.QNETD_DEFAULT_PORT})") + qdevice_group.add_argument("--qdevice-algo", dest="qdevice_algo", metavar="ALGORITHM", choices=['ffsplit', 'lms'], help="QNetd decision ALGORITHM (ffsplit/lms, default:ffsplit)") - qdevice_group.add_argument("--qdevice-tie-breaker", dest="qdevice_tie_breaker", metavar="TIE_BREAKER", default="lowest", + qdevice_group.add_argument("--qdevice-tie-breaker", dest="qdevice_tie_breaker", metavar="TIE_BREAKER", help="QNetd TIE_BREAKER (lowest/highest/valid_node_id, default:lowest)") - qdevice_group.add_argument("--qdevice-tls", dest="qdevice_tls", metavar="TLS", default="on", choices=['on', 'off', 'required'], + qdevice_group.add_argument("--qdevice-tls", dest="qdevice_tls", metavar="TLS", choices=['on', 'off', 'required'], help="Whether using TLS on QDevice (on/off/required, default:on)") qdevice_group.add_argument("--qdevice-heuristics", dest="qdevice_heuristics", metavar="COMMAND", help="COMMAND to run with absolute path. For multiple commands, use \";\" to separate (details about heuristics can see man 8 corosync-qdevice)") @@ -489,13 +496,6 @@ if len(args): stage = args[0] - if options.qnetd_addr_input: - if options.qdevice_heuristics_mode and not options.qdevice_heuristics: - parser.error("Option --qdevice-heuristics is required if want to configure heuristics mode") - options.qdevice_heuristics_mode = options.qdevice_heuristics_mode or "sync" - elif re.search("--qdevice-.*", ' '.join(sys.argv)) or (stage == "qdevice" and options.yes_to_all): - parser.error("Option --qnetd-hostname is required if want to configure qdevice") - # if options.geo and options.name == "hacluster": # parser.error("For a geo cluster, each cluster must have a unique name (use --name to set)") boot_context = bootstrap.Context.set_context(options) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/test/features/qdevice_validate.feature new/crmsh-5.1.0+20260528.b0973362/test/features/qdevice_validate.feature --- old/crmsh-5.1.0+20260508.3e8e1915/test/features/qdevice_validate.feature 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/test/features/qdevice_validate.feature 2026-05-28 09:45:27.000000000 +0200 @@ -26,9 +26,14 @@ Then Except "ERROR: cluster.init: host "node-without-ssh" is unreachable via SSH" @clean - Scenario: Option "--qdevice-port" set wrong port - When Try "crm cluster init --qnetd-hostname=qnetd-node --qdevice-port=1" - Then Except "ERROR: cluster.init: invalid qdevice port range(1024 - 65535)" + Scenario: Option "--qnetd-port" set wrong port + When Try "crm cluster init --qnetd-hostname=qnetd-node --qnetd-port=1" + Then Except "ERROR: cluster.init: invalid qnetd port range(1024 - 65535)" + + @clean + Scenario: Option "--qnetd-port" and "--qdevice-port" can't be used together + When Try "crm cluster init --qnetd-hostname=qnetd-node --qnetd-port=1234 --qdevice-port=1234" + Then Expected "Options --qdevice-port and --qnetd-port can't be used together" in stderr @clean Scenario: Option "--qdevice-tie-breaker" set wrong value @@ -44,21 +49,13 @@ @clean Scenario: Option "--qnetd-hostname" is required by other qdevice options - When Try "crm cluster init --qdevice-port=1234" - Then Expected multiple lines in stderr - """ - usage: init [options] [STAGE] - crm: error: Option --qnetd-hostname is required if want to configure qdevice - """ + When Try "crm cluster init --qnetd-port=1234" + Then Except "ERROR: cluster.init: Option --qnetd-hostname is required if want to configure qdevice" @clean Scenario: Option --qdevice-heuristics is required if want to configure heuristics mode When Try "crm cluster init --qnetd-hostname=qnetd-node --qdevice-heuristics-mode="on"" - Then Expected multiple lines in stderr - """ - usage: init [options] [STAGE] - crm: error: Option --qdevice-heuristics is required if want to configure heuristics mode - """ + Then Except "ERROR: cluster.init: Option --qdevice-heuristics is required if want to configure heuristics mode" @clean Scenario: Node for qnetd not installed corosync-qnetd @@ -101,11 +98,7 @@ When Run "crm cluster init -y" on "hanode1" Then Cluster service is "started" on "hanode1" When Try "crm cluster init qdevice -y" - Then Expected multiple lines in stderr - """ - usage: init [options] [STAGE] - crm: error: Option --qnetd-hostname is required if want to configure qdevice - """ + Then Except "ERROR: cluster.init: Option --qnetd-hostname is required if want to configure qdevice" @clean Scenario: Setup qdevice on a single node cluster with RA running(bsc#1181415) @@ -142,3 +135,36 @@ When Run "crm -F cluster remove --qdevice -y" on "hanode1" Then Cluster service is "started" on "hanode1" And Service "corosync-qdevice" is "stopped" on "hanode1" + + @clean + Scenario: Port conflict between qnetd and corosync + Given Service "corosync-qnetd" is "started" on "qnetd-node" + When Run "crm cluster init -y" on "hanode1" + Then Cluster service is "started" on "hanode1" + When Try "crm cluster init qdevice --qnetd-hostname=qnetd-node --qnetd-port 5402 -y" on "hanode1" + Then Expected multiple lines in stderr + """ + The port 5402 is different from the port 5403 that corosync-qnetd is using + Please use '--qnetd-port 5403' to keep consistent + """ + Then Service "corosync-qdevice" is "stopped" on "hanode1" + When Run "crm cluster init qdevice --qnetd-hostname=qnetd-node --qnetd-port 5403 -y" on "hanode1" + Then Service "corosync-qdevice" is "started" on "hanode1" + + When Run "crm cluster remove --qdevice -y" on "hanode1" + Then Service "corosync-qdevice" is "stopped" on "hanode1" + When Run "systemctl stop corosync-qnetd" on "qnetd-node" + Then Service "corosync-qnetd" is "stopped" on "qnetd-node" + When Run "crm cluster init qdevice --qnetd-hostname=qnetd-node --qnetd-port 5402 -y" on "hanode1" + Then Service "corosync-qdevice" is "started" on "hanode1" + And Service "corosync-qnetd" is "started" on "qnetd-node" + And Run "cat /etc/sysconfig/corosync-qnetd|grep "\-p 5402"" OK on "qnetd-node" + + When Run "crm cluster remove --qdevice -y" on "hanode1" + Then Service "corosync-qdevice" is "stopped" on "hanode1" + When Run "systemctl stop corosync-qnetd" on "qnetd-node" + Then Service "corosync-qnetd" is "stopped" on "qnetd-node" + When Run "crm cluster init qdevice --qnetd-hostname=qnetd-node -y" on "hanode1" + Then Service "corosync-qdevice" is "started" on "hanode1" + And Service "corosync-qnetd" is "started" on "qnetd-node" + And Run "cat /etc/corosync/corosync.conf|grep "port: 5402"" OK on "hanode1" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/test/features/steps/const.py new/crmsh-5.1.0+20260528.b0973362/test/features/steps/const.py --- old/crmsh-5.1.0+20260508.3e8e1915/test/features/steps/const.py 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/test/features/steps/const.py 2026-05-28 09:45:27.000000000 +0200 @@ -116,7 +116,9 @@ --qnetd-hostname [USER@]HOST User and host of the QNetd server. The host can be specified in either hostname or IP address. - --qdevice-port PORT TCP PORT of QNetd server (default:5403) + --qdevice-port PORT TCP PORT of QNetd server (default:5403, deprecated, + use --qnetd-port instead) + --qnetd-port PORT TCP PORT of QNetd server (default:5403) --qdevice-algo ALGORITHM QNetd decision ALGORITHM (ffsplit/lms, default:ffsplit) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/test/unittests/test_bootstrap.py new/crmsh-5.1.0+20260528.b0973362/test/unittests/test_bootstrap.py --- old/crmsh-5.1.0+20260508.3e8e1915/test/unittests/test_bootstrap.py 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/test/unittests/test_bootstrap.py 2026-05-28 09:45:27.000000000 +0200 @@ -113,7 +113,7 @@ def test_initialize_qdevice(self, mock_qdevice): ctx = crmsh.bootstrap.Context() ctx.qnetd_addr_input = "node3" - ctx.qdevice_port = 123 + ctx.qnetd_port = 123 ctx.stage = "" ctx._initialize_qdevice() mock_qdevice.assert_called_once_with(qnetd_addr='node3', port=123, ssh_user=None, algo=None, tie_breaker=None, tls=None, cmds=None, mode=None, is_stage=False) @@ -122,7 +122,7 @@ def test_initialize_qdevice_with_user(self, mock_qdevice): ctx = crmsh.bootstrap.Context() ctx.qnetd_addr_input = "alice@node3" - ctx.qdevice_port = 123 + ctx.qnetd_port = 123 ctx.stage = "" ctx._initialize_qdevice() mock_qdevice.assert_called_once_with(qnetd_addr='node3', port=123, ssh_user='alice', algo=None, tie_breaker=None, tls=None, cmds=None, mode=None, is_stage=False) @@ -1357,8 +1357,8 @@ mock_confirm.assert_called_once_with("Do you want to configure QDevice?") mock_prompt.assert_has_calls([ mock.call("HOST or IP of the QNetd server to be used"), - mock.call("TCP PORT of QNetd server", default=5403, - valid_func=qdevice.QDevice.check_qdevice_port), + mock.call("TCP PORT of QNetd server", + valid_func=qdevice.QDevice.check_qnetd_port), mock.call("QNetd decision ALGORITHM (ffsplit/lms)", default="ffsplit", valid_func=qdevice.QDevice.check_qdevice_algo), mock.call("QNetd TIE_BREAKER (lowest/highest/valid node id)", default="lowest", @@ -1560,6 +1560,25 @@ mock_parser_inst.get_node_list.assert_called_once_with(online=True, node_type="member") mock_cluster_copy.assert_called_once_with("/file1", nodes=["node1", "node2"]) + @mock.patch('crmsh.sh.cluster_shell') + def test_get_failed_services(self, mock_cluster_shell): + mock_cluster_shell_inst = mock.Mock() + mock_cluster_shell.return_value = mock_cluster_shell_inst + mock_cluster_shell_inst.get_rc_stdout_stderr_without_input.side_effect = [ + (1, None, None), + (0, None, None), + (1, None, None) + ] + + res = bootstrap.get_failed_services() + self.assertEqual(res, [constants.SBD_SERVICE]) + mock_cluster_shell.assert_called_once_with() + mock_cluster_shell_inst.get_rc_stdout_stderr_without_input.assert_has_calls([ + mock.call(None, f"systemctl is-failed {constants.COROSYNC_SERVICE}"), + mock.call(None, f"systemctl is-failed {constants.SBD_SERVICE}"), + mock.call(None, f"systemctl is-failed {constants.PCMK_SERVICE}") + ]) + class TestValidation(unittest.TestCase): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/test/unittests/test_qdevice.py new/crmsh-5.1.0+20260528.b0973362/test/unittests/test_qdevice.py --- old/crmsh-5.1.0+20260508.3e8e1915/test/unittests/test_qdevice.py 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/test/unittests/test_qdevice.py 2026-05-28 09:45:27.000000000 +0200 @@ -226,11 +226,11 @@ qdevice.QDevice.check_qnetd_addr("qnetd-node") @mock.patch('crmsh.utils.valid_port') - def test_check_qdevice_port(self, mock_port): + def test_check_qnetd_port(self, mock_port): mock_port.return_value = False with self.assertRaises(ValueError) as err: - qdevice.QDevice.check_qdevice_port("1") - excepted_err_string = "invalid qdevice port range(1024 - 65535)" + qdevice.QDevice.check_qnetd_port("1") + excepted_err_string = "invalid qnetd port range(1024 - 65535)" self.assertEqual(excepted_err_string, str(err.exception)) def test_check_qdevice_algo(self): @@ -278,7 +278,7 @@ @mock.patch('crmsh.qdevice.QDevice.check_qdevice_tls') @mock.patch('crmsh.qdevice.QDevice.check_qdevice_tie_breaker') @mock.patch('crmsh.qdevice.QDevice.check_qdevice_algo') - @mock.patch('crmsh.qdevice.QDevice.check_qdevice_port') + @mock.patch('crmsh.qdevice.QDevice.check_qnetd_port') @mock.patch('crmsh.qdevice.QDevice.check_qnetd_addr') @mock.patch('crmsh.qdevice.QDevice.check_corosync_qdevice_available') def test_valid_qdevice_options(self, mock_installed, mock_check_qnetd, mock_check_port, @@ -326,8 +326,10 @@ mock_installed.assert_called_once_with("corosync-qnetd", remote_addr="10.10.10.123") mock_init_tls_certs_on_qnetd.assert_called_once() + @mock.patch("crmsh.service_manager.ServiceManager.service_is_active") @mock.patch("crmsh.service_manager.ServiceManager.start_service") - def test_start_qnetd(self, mock_start): + def test_start_qnetd(self, mock_start, mock_active): + mock_active.return_value = False self.qdevice_with_ip.start_qnetd() mock_start.assert_called_once_with("corosync-qnetd.service", enable=True, remote_addr="10.10.10.123") @@ -685,6 +687,7 @@ mock_service_instance = mock.Mock() mock_service.return_value = mock_service_instance mock_service_instance.service_is_active.return_value = False + self.qdevice_with_ip.config_qnetd_port_in_sysconfig = mock.Mock() self.qdevice_with_ip.config_qnetd_port() @@ -704,6 +707,7 @@ mock_cluster_shell.return_value = mock_cluster_shell_instance mock_cluster_shell_instance.get_rc_stdout_stderr_without_input = mock.Mock(return_value=(0, None, None)) mock_cluster_shell_instance.get_stdout_or_raise_error = mock.Mock(return_value=None) + self.qdevice_with_ip.config_qnetd_port_in_sysconfig = mock.Mock() self.qdevice_with_ip.port = 5403 self.qdevice_with_ip.config_qnetd_port() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/test/unittests/test_service_manager.py new/crmsh-5.1.0+20260528.b0973362/test/unittests/test_service_manager.py --- old/crmsh-5.1.0+20260508.3e8e1915/test/unittests/test_service_manager.py 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/test/unittests/test_service_manager.py 2026-05-28 09:45:27.000000000 +0200 @@ -56,17 +56,17 @@ def test_start_service(self, mock_call_with_parallax: mock.MagicMock): self.service_manager._call.return_value = ['node1'] self.assertEqual(['node1'], self.service_manager.start_service('service1', remote_addr='node1')) - self.service_manager._call.assert_called_once_with('node1', [], "systemctl start 'service1'") + self.service_manager._call.assert_called_once_with('node1', [], "systemctl start 'service1' && systemctl is-active 'service1'") def test_start_service_on_multiple_host(self, mock_call_with_parallax: mock.MagicMock): self.service_manager._call.return_value = ['node1', 'node2'] self.assertEqual(['node1', 'node2'], self.service_manager.start_service('service1', node_list=['node1', 'node2'])) - self.service_manager._call.assert_called_once_with(None, ['node1', 'node2'], "systemctl start 'service1'") + self.service_manager._call.assert_called_once_with(None, ['node1', 'node2'], "systemctl start 'service1' && systemctl is-active 'service1'") def test_start_and_enable_service(self, mock_call_with_parallax: mock.MagicMock): self.service_manager._call.return_value = ['node1'] self.assertEqual(['node1'], self.service_manager.start_service('service1', enable=True, remote_addr='node1')) - self.service_manager._call.assert_called_once_with('node1', [], "systemctl enable --now 'service1'") + self.service_manager._call.assert_called_once_with('node1', [], "systemctl enable --now 'service1' && systemctl is-enabled 'service1' && systemctl is-active 'service1'") def test_stop_service(self, mock_call_with_parallax: mock.MagicMock): self.service_manager._call.return_value = ['node1'] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/crmsh-5.1.0+20260508.3e8e1915/test/unittests/test_ui_cluster.py new/crmsh-5.1.0+20260528.b0973362/test/unittests/test_ui_cluster.py --- old/crmsh-5.1.0+20260508.3e8e1915/test/unittests/test_ui_cluster.py 2026-05-08 08:10:10.000000000 +0200 +++ new/crmsh-5.1.0+20260528.b0973362/test/unittests/test_ui_cluster.py 2026-05-28 09:45:27.000000000 +0200 @@ -56,6 +56,7 @@ mock.call("The cluster stack already started on node2") ]) + @mock.patch('crmsh.bootstrap.get_failed_services') @mock.patch('crmsh.qdevice.QDevice.check_qdevice_vote') @mock.patch('crmsh.bootstrap.start_pacemaker') @mock.patch('logging.Logger.error') @@ -64,12 +65,14 @@ @mock.patch('crmsh.service_manager.ServiceManager.start_service') @mock.patch('crmsh.service_manager.ServiceManager.service_is_active') @mock.patch('crmsh.ui_utils.parse_and_validate_node_args') - def test_do_start(self, mock_parse_nodes, mock_active, mock_start, mock_qdevice_configured, mock_info, mock_error, mock_start_pacemaker, mock_check_qdevice): + def test_do_start(self, mock_parse_nodes, mock_active, mock_start, mock_qdevice_configured, mock_info, mock_error, mock_start_pacemaker, mock_check_qdevice, mock_get_failed_services): context_inst = mock.Mock() mock_start_pacemaker.return_value = ["node1"] mock_parse_nodes.return_value = ["node1", "node2"], False mock_active.side_effect = [False, False, False, False] mock_qdevice_configured.return_value = True + mock_get_failed_services.return_value = [] + mock_start.return_value = ["node1", "node2"] self.ui_cluster_inst.do_start(context_inst, "node1", "node2") @@ -82,7 +85,7 @@ mock_start.assert_called_once_with("corosync-qdevice", node_list=["node1", "node2"]) mock_qdevice_configured.assert_called_once_with() mock_info.assert_called_once_with("The cluster stack started on %s", "node1") - mock_error.assert_called_once_with("The cluster stack failed to start on %s", "node2") + mock_error.assert_called_once_with("The cluster stack failed to start on %s.%s", "node2", '') @mock.patch('crmsh.utils.wait_for_dc') @mock.patch('crmsh.ui_cluster.Cluster._node_ready_to_stop_cluster_service')
