]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
qa/workunits/rbd: mirror group functional tests
authorMykola Golub <mgolub@suse.com>
Fri, 12 Mar 2021 11:57:57 +0000 (11:57 +0000)
committerPrasanna Kumar Kalever <prasanna.kalever@redhat.com>
Thu, 24 Apr 2025 15:56:24 +0000 (21:26 +0530)
Also sets the RBD_MIRROR_INSTANCES to 1 to avoid any deviations.

Signed-off-by: Mykola Golub <mgolub@suse.com>
Signed-off-by: Prasanna Kumar Kalever <prasanna.kalever@redhat.com>
qa/workunits/rbd/rbd_mirror_group.sh [new file with mode: 0755]
qa/workunits/rbd/rbd_mirror_helpers.sh

diff --git a/qa/workunits/rbd/rbd_mirror_group.sh b/qa/workunits/rbd/rbd_mirror_group.sh
new file mode 100755 (executable)
index 0000000..15f3a6d
--- /dev/null
@@ -0,0 +1,267 @@
+#!/usr/bin/env bash
+#
+# rbd_mirror_group.sh - test rbd-mirror daemon for snapshot-based group mirroring
+#
+# The script starts two ("local" and "remote") clusters using mstart.sh script,
+# creates a temporary directory, used for cluster configs, daemon logs, admin
+# socket, temporary files, and launches rbd-mirror daemon.
+#
+
+set -ex
+
+MIRROR_POOL_MODE=image
+MIRROR_IMAGE_MODE=snapshot
+
+. $(dirname $0)/rbd_mirror_helpers.sh
+
+setup
+testlog "TEST: create group and test replay"
+start_mirrors ${CLUSTER1}
+group=test-group
+create_group_and_enable_mirror ${CLUSTER2} ${POOL} ${group}
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group} 0
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group} 'up+replaying'
+wait_for_group_present ${CLUSTER1} ${POOL} ${group} 'present'
+if [ -z "${RBD_MIRROR_USE_RBD_MIRROR}" ]; then
+  wait_for_group_status_in_pool_dir ${CLUSTER2} ${POOL} ${group} 'down+unknown'
+fi
+
+testlog "TEST: add image to group and test replay"
+image=test-image
+create_image ${CLUSTER2} ${POOL} ${image}
+group_add_image ${CLUSTER2} ${POOL} ${group} ${POOL} ${image}
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group} 1
+wait_for_image_present ${CLUSTER1} ${POOL} ${image} 'present'
+write_image ${CLUSTER2} ${POOL} ${image} 100
+wait_for_group_replay_complete ${CLUSTER1} ${CLUSTER2} ${POOL} ${group}
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group} 'up+replaying'
+compare_images ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${image}
+
+testlog "TEST: test replay with remove image and later add the same"
+group_remove_image ${CLUSTER2} ${POOL} ${group} ${POOL} ${image}
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group} 0
+group_add_image ${CLUSTER2} ${POOL} ${group} ${POOL} ${image}
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group} 1
+
+testlog "TEST: stop mirror, create group, start mirror and test replay"
+stop_mirrors ${CLUSTER1}
+group1=test-group1
+create_group_and_enable_mirror ${CLUSTER2} ${POOL} ${group1}
+image1=test-image1
+create_image ${CLUSTER2} ${POOL} ${image1}
+group_add_image ${CLUSTER2} ${POOL} ${group1} ${POOL} ${image1}
+start_mirrors ${CLUSTER1}
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group1} 1
+wait_for_group_replay_complete ${CLUSTER1} ${CLUSTER2} ${POOL} ${group1}
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group1} 'up+replaying'
+if [ -z "${RBD_MIRROR_USE_RBD_MIRROR}" ]; then
+  wait_for_group_status_in_pool_dir ${CLUSTER2} ${POOL} ${group1} 'down+unknown'
+fi
+compare_images ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${image1}
+
+testlog "TEST: test the first group is replaying after restart"
+write_image ${CLUSTER2} ${POOL} ${image} 100
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group} 1
+wait_for_group_replay_complete ${CLUSTER1} ${CLUSTER2} ${POOL} ${group}
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group} 'up+replaying'
+compare_images ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${image}
+
+if [ -z "${RBD_MIRROR_USE_RBD_MIRROR}" ]; then
+  testlog "TEST: stop/start/restart group via admin socket"
+
+  admin_daemons ${CLUSTER1} rbd mirror group stop ${POOL}/${group1}
+  wait_for_group_replay_stopped ${CLUSTER1} ${POOL} ${group1} 1
+  wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group1} 'up+stopped'
+
+  admin_daemons ${CLUSTER1} rbd mirror group start ${POOL}/${group1}
+  wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group1} 1
+  wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group1} 'up+replaying'
+
+  admin_daemons ${CLUSTER1} rbd mirror group restart ${POOL}/${group1}
+  wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group1} 1
+  wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group1} 'up+replaying'
+
+  flush ${CLUSTER1}
+  admin_daemons ${CLUSTER1} rbd mirror group status ${POOL}/${group1}
+fi
+
+testlog "TEST: test group rename"
+new_name="${group}_RENAMED"
+rename_group ${CLUSTER2} ${POOL} ${group} ${new_name}
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${new_name} 1
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${new_name} 'up+replaying'
+rename_group ${CLUSTER2} ${POOL} ${new_name} ${group}
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group} 1
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group} 'up+replaying'
+
+testlog "TEST: failover and failback"
+start_mirrors ${CLUSTER2}
+
+testlog " - demote and promote same cluster"
+demote_group ${CLUSTER2} ${POOL} ${group1}
+wait_for_group_replay_stopped ${CLUSTER1} ${POOL} ${group1} 1
+
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group1} 'up+stopped'
+wait_for_group_status_in_pool_dir ${CLUSTER2} ${POOL} ${group1} 'up+stopped'
+promote_group ${CLUSTER2} ${POOL} ${group1}
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group1} 1
+
+write_image ${CLUSTER2} ${POOL} ${image1} 100
+wait_for_group_replay_complete ${CLUSTER1} ${CLUSTER2} ${POOL} ${group1}
+compare_images ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${image1}
+
+testlog " - failover (unmodified)"
+demote_group ${CLUSTER2} ${POOL} ${group}
+wait_for_group_replay_stopped ${CLUSTER1} ${POOL} ${group} 1
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group} 'up+stopped'
+wait_for_group_status_in_pool_dir ${CLUSTER2} ${POOL} ${group} 'up+stopped'
+promote_group ${CLUSTER1} ${POOL} ${group}
+wait_for_group_replay_started ${CLUSTER2} ${POOL} ${group} 1
+
+testlog " - failback (unmodified)"
+demote_group ${CLUSTER1} ${POOL} ${group}
+wait_for_group_replay_stopped ${CLUSTER2} ${POOL} ${group} 1
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group} 'up+stopped'
+wait_for_group_status_in_pool_dir ${CLUSTER2} ${POOL} ${group} 'up+stopped'
+promote_group ${CLUSTER2} ${POOL} ${group}
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group} 1
+wait_for_group_replay_complete ${CLUSTER1} ${CLUSTER2} ${POOL} ${group}
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group} 'up+replaying'
+wait_for_group_status_in_pool_dir ${CLUSTER2} ${POOL} ${group} 'up+stopped'
+compare_images ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${image}
+
+testlog " - failover"
+demote_group ${CLUSTER2} ${POOL} ${group1}
+wait_for_group_replay_stopped ${CLUSTER1} ${POOL} ${group1} 1
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group1} 'up+stopped'
+wait_for_group_status_in_pool_dir ${CLUSTER2} ${POOL} ${group1} 'up+stopped'
+promote_group ${CLUSTER1} ${POOL} ${group1}
+wait_for_group_replay_started ${CLUSTER2} ${POOL} ${group1} 1
+write_image ${CLUSTER1} ${POOL} ${image1} 100
+wait_for_group_replay_complete ${CLUSTER2} ${CLUSTER1} ${POOL} ${group1}
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group1} 'up+stopped'
+wait_for_group_status_in_pool_dir ${CLUSTER2} ${POOL} ${group1} 'up+replaying'
+compare_images ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${image1}
+
+testlog " - failback"
+demote_group ${CLUSTER1} ${POOL} ${group1}
+wait_for_group_replay_stopped ${CLUSTER2} ${POOL} ${group1} 1
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group1} 'up+stopped'
+wait_for_group_status_in_pool_dir ${CLUSTER2} ${POOL} ${group1} 'up+stopped'
+promote_group ${CLUSTER2} ${POOL} ${group1}
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group1} 1
+write_image ${CLUSTER2} ${POOL} ${image1} 100
+wait_for_group_replay_complete ${CLUSTER1} ${CLUSTER2} ${POOL} ${group1}
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group1} 'up+replaying'
+wait_for_group_status_in_pool_dir ${CLUSTER2} ${POOL} ${group1} 'up+stopped'
+compare_images ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${image1}
+
+testlog " - force promote"
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group} 1
+write_image ${CLUSTER2} ${POOL} ${image} 100
+wait_for_group_replay_complete ${CLUSTER1} ${CLUSTER2} ${POOL} ${group}
+promote_group ${CLUSTER1} ${POOL} ${group} '--force'
+
+wait_for_group_replay_stopped ${CLUSTER1} ${POOL} ${group} 1
+wait_for_group_replay_stopped ${CLUSTER2} ${POOL} ${group} 1
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group} 'up+stopped'
+wait_for_group_status_in_pool_dir ${CLUSTER2} ${POOL} ${group} 'up+stopped'
+write_image ${CLUSTER1} ${POOL} ${image} 100
+write_image ${CLUSTER2} ${POOL} ${image} 100
+group_remove_image ${CLUSTER1} ${POOL} ${group} ${POOL} ${image}
+remove_image_retry ${CLUSTER1} ${POOL} ${image}
+wait_for_image_present ${CLUSTER1} ${POOL} ${image} 'deleted'
+remove_group ${CLUSTER1} ${POOL} ${group}
+
+testlog "TEST: disable mirroring / delete non-primary group"
+disable_group_mirror ${CLUSTER2} ${POOL} ${group}
+wait_for_image_present ${CLUSTER1} ${POOL} ${image} 'deleted'
+wait_for_group_present ${CLUSTER1} ${POOL} ${group} 'deleted'
+enable_group_mirror ${CLUSTER2} ${POOL} ${group}
+wait_for_group_present ${CLUSTER1} ${POOL} ${group} 'present' 1
+wait_for_image_present ${CLUSTER1} ${POOL} ${image} 'present'
+
+testlog "TEST: disable mirror while daemon is stopped"
+stop_mirrors ${CLUSTER1}
+stop_mirrors ${CLUSTER2}
+disable_group_mirror ${CLUSTER2} ${POOL} ${group}
+if [ -z "${RBD_MIRROR_USE_RBD_MIRROR}" ]; then
+  wait_for_group_present ${CLUSTER1} ${POOL} ${group} 'present' 1
+  wait_for_image_present ${CLUSTER1} ${POOL} ${image} 'present'
+fi
+start_mirrors ${CLUSTER1}
+start_mirrors ${CLUSTER2}
+wait_for_image_present ${CLUSTER1} ${POOL} ${image} 'deleted'
+wait_for_group_present ${CLUSTER1} ${POOL} ${group} 'deleted'
+enable_group_mirror ${CLUSTER2} ${POOL} ${group}
+wait_for_group_present ${CLUSTER1} ${POOL} ${group} 'present' 1
+wait_for_image_present ${CLUSTER1} ${POOL} ${image} 'present'
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group} 1
+
+testlog "TEST: non-default namespace group mirroring"
+testlog " - replay"
+create_group_and_enable_mirror ${CLUSTER2} ${POOL}/${NS1} ${group}
+create_group_and_enable_mirror ${CLUSTER2} ${POOL}/${NS2} ${group}
+create_image ${CLUSTER2} ${POOL}/${NS1} ${image}
+create_image ${CLUSTER2} ${POOL}/${NS2} ${image}
+group_add_image ${CLUSTER2} ${POOL}/${NS1} ${group} ${POOL}/${NS1} ${image}
+group_add_image ${CLUSTER2} ${POOL}/${NS2} ${group} ${POOL}/${NS2} ${image}
+wait_for_group_replay_started ${CLUSTER1} ${POOL}/${NS1} ${group} 1
+wait_for_group_replay_started ${CLUSTER1} ${POOL}/${NS2} ${group} 1
+write_image ${CLUSTER2} ${POOL}/${NS1} ${image} 100
+write_image ${CLUSTER2} ${POOL}/${NS2} ${image} 100
+wait_for_group_replay_complete ${CLUSTER1} ${CLUSTER2} ${POOL}/${NS1} ${group}
+wait_for_group_replay_complete ${CLUSTER1} ${CLUSTER2} ${POOL}/${NS2} ${group}
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL}/${NS1} ${group} 'up+replaying'
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL}/${NS2} ${group} 'up+replaying'
+compare_images ${CLUSTER1} ${CLUSTER2} ${POOL}/${NS1} ${POOL}/${NS1} ${image}
+compare_images ${CLUSTER1} ${CLUSTER2} ${POOL}/${NS2} ${POOL}/${NS2} ${image}
+
+testlog " - disable mirroring / remove group"
+group_remove_image ${CLUSTER2} ${POOL}/${NS1} ${group} ${POOL}/${NS1} ${image}
+remove_image_retry ${CLUSTER2} ${POOL}/${NS1} ${image}
+wait_for_image_present ${CLUSTER1} ${POOL}/${NS1} ${image} 'deleted'
+remove_group ${CLUSTER1} ${POOL}/${NS1} ${group}
+wait_for_group_present ${CLUSTER1} ${POOL}/${NS1} ${group} 'deleted'
+disable_group_mirror ${CLUSTER2} ${POOL}/${NS2} ${group}
+wait_for_image_present ${CLUSTER1} ${POOL}/${NS2} ${image} 'deleted'
+wait_for_group_present ${CLUSTER1} ${POOL}/${NS2} ${group} 'deleted'
+
+testlog "TEST: simple group resync"
+image_id=$(get_image_id ${CLUSTER1} ${POOL} ${image})
+wait_for_image_present ${CLUSTER2} ${POOL} ${image} 'present'
+wait_for_image_present ${CLUSTER1} ${POOL} ${image} 'present'
+request_resync_group ${CLUSTER1} ${POOL} ${group}
+wait_for_image_present ${CLUSTER1} ${POOL} ${image} 'deleted' ${image_id}
+wait_for_image_present ${CLUSTER1} ${POOL} ${image} 'present'
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group} 1
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group} 'up+replaying'
+compare_images ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${image}
+
+testlog "TEST: request group resync while daemon is offline"
+image_id=$(get_image_id ${CLUSTER1} ${POOL} ${image})
+stop_mirrors ${CLUSTER1}
+request_resync_group ${CLUSTER1} ${POOL} ${group}
+start_mirrors ${CLUSTER1}
+wait_for_image_present ${CLUSTER1} ${POOL} ${image} 'deleted' ${image_id}
+wait_for_image_present ${CLUSTER1} ${POOL} ${image} 'present'
+wait_for_group_replay_started ${CLUSTER1} ${POOL} ${group} 1
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group} 'up+replaying'
+compare_images ${CLUSTER1} ${CLUSTER2} ${POOL} ${POOL} ${image}
+
+testlog "TEST: split-brain"
+promote_group ${CLUSTER1} ${POOL} ${group} --force
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group} 'up+stopped'
+write_image ${CLUSTER1} ${POOL} ${image} 10
+demote_group ${CLUSTER1} ${POOL} ${group}
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group} 'up+error' 'split-brain'
+request_resync_group ${CLUSTER1} ${POOL} ${group}
+wait_for_group_status_in_pool_dir ${CLUSTER1} ${POOL} ${group} 'up+replaying'
+
+#TODO: Fix blocklisting IP's which are consequence of "TEST: stop mirror, create group, start mirror and test replay"
+#if [ -z "${RBD_MIRROR_USE_RBD_MIRROR}" ]; then
+# teuthology will trash the daemon
+#  testlog "TEST: no blocklists"
+#  CEPH_ARGS='--id admin' ceph --cluster ${CLUSTER1} osd blocklist ls 2>&1 | grep -q "listed 0 entries"
+#  CEPH_ARGS='--id admin' ceph --cluster ${CLUSTER2} osd blocklist ls 2>&1 | grep -q "listed 0 entries"
+#fi
index b94317d75d3a538b1eb130ab8bff9664f741e6eb..dd0db96ef40c3a1df023902f270e21bef0b437fa 100755 (executable)
 #     ../qa/workunits/rbd/rbd_mirror_helpers.sh cleanup
 #
 
-RBD_MIRROR_INSTANCES=${RBD_MIRROR_INSTANCES:-2}
+if type xmlstarlet > /dev/null 2>&1; then
+    XMLSTARLET=xmlstarlet
+elif type xml > /dev/null 2>&1; then
+    XMLSTARLET=xml
+else
+    echo "Missing xmlstarlet binary!"
+    exit 1
+fi
+
+RBD_MIRROR_INSTANCES=${RBD_MIRROR_INSTANCES:-1}
 
 CLUSTER1=cluster1
 CLUSTER2=cluster2
@@ -214,7 +223,6 @@ setup_cluster()
     local cluster=$1
 
     CEPH_ARGS='' ${CEPH_SRC}/mstart.sh ${cluster} -n ${RBD_MIRROR_VARGS}
-
     cd ${CEPH_ROOT}
     rm -f ${TEMPDIR}/${cluster}.conf
     ln -s $(readlink -f run/${cluster}/ceph.conf) \
@@ -444,8 +452,8 @@ stop_mirror()
     pid=$(cat $(daemon_pid_file "${cluster}") 2>/dev/null) || :
     if [ -n "${pid}" ]
     then
-        kill ${sig} ${pid}
         for s in 1 2 4 8 16 32; do
+            kill ${sig} ${pid}
             sleep $s
             ps auxww | awk -v pid=${pid} '$2 == pid {print; exit 1}' && break
         done
@@ -1556,6 +1564,320 @@ wait_for_image_in_omap()
     wait_for_omap_keys ${cluster} ${pool} rbd_mirror_leader image_map
 }
 
+create_group()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+
+    rbd --cluster ${cluster} group create ${pool}/${group}
+}
+
+rename_group()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local new_name=$4
+
+    rbd --cluster ${cluster} group rename ${pool}/${group} ${pool}/${new_name}
+}
+
+remove_group()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+
+    rbd --cluster ${cluster} group remove ${pool}/${group}
+}
+
+group_add_image()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local image_pool=$4
+    local image=$5
+
+    rbd --cluster ${cluster} \
+        group image add ${pool}/${group} ${image_pool}/${image}
+}
+
+group_remove_image()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local image_pool=$4
+    local image=$5
+
+    rbd --cluster ${cluster} \
+        group image remove ${pool}/${group} ${image_pool}/${image}
+}
+
+enable_group_mirror()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local mode=${4:-${MIRROR_IMAGE_MODE}}
+
+    rbd --cluster=${cluster} mirror group enable ${pool}/${group} ${mode}
+}
+
+disable_group_mirror()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+
+    rbd --cluster=${cluster} mirror group disable ${pool}/${group}
+}
+
+create_group_and_enable_mirror()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local mode=${4:-${MIRROR_IMAGE_MODE}}
+
+    create_group ${cluster} ${pool} ${group}
+    enable_group_mirror ${cluster} ${pool} ${group} ${mode}
+}
+
+demote_group()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+
+    rbd --cluster=${cluster} mirror group demote ${pool}/${group}
+}
+
+promote_group()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local force=$4
+
+    rbd --cluster=${cluster} mirror group promote ${pool}/${group} ${force}
+}
+
+mirror_group_snapshot()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+
+    rbd --cluster=${cluster} mirror group snapshot ${pool}/${group}
+}
+
+request_resync_group()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+
+    rbd --cluster=${cluster} mirror group resync ${pool}/${group}
+}
+
+test_group_present()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local test_state=$4
+    local test_image_count=$5
+    local current_state=deleted
+    local current_image_count
+
+    rbd --cluster ${cluster} group list ${pool} | grep "^${group}$" &&
+        current_state=present
+    test "${test_state}" = "${current_state}" || return 1
+
+    test "${current_state}" = present -a -n "${image_count}" || return 0
+
+    current_image_count=$(rbd --cluster ${cluster} group image list ${pool}/${group} | wc -l)
+    test "${test_image_count}" = "${current_image_count}"
+}
+
+wait_for_group_present()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local state=$4
+    local image_count=$5
+    local s
+
+    for s in 0.1 1 2 4 8 8 8 8 8 8 8 8 16 16 32 32; do
+        sleep ${s}
+        test_group_present \
+            "${cluster}" "${pool}" "${group}" "${state}" "${image_count}" &&
+        return 0
+    done
+    return 1
+}
+
+test_group_replay_state()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local test_state=$4
+    local image_count=$5
+    local status_result
+    local current_state=stopped
+
+    status_result=$(admin_daemons "${cluster}" rbd mirror group status ${pool}/${group} | grep -i 'state') || return 1
+    echo "${status_result}" | grep -i 'Replaying' && current_state=started
+    test "${test_state}" = "${current_state}" || return 1
+
+    test -n "${image_count}" || return 0
+
+    # TODO: test images when group image status is returned by the rbd-mirror
+}
+
+wait_for_group_replay_state()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local state=$4
+    local image_count=$5
+    local s
+
+    # TODO: add a way to force rbd-mirror to update replayers
+    for s in 0.1 1 2 4 8 8 8 8 8 8 8 8 16 16; do
+        sleep ${s}
+        test_group_replay_state "${cluster}" "${pool}" "${group}" "${state}" \
+                                "${image_count}" &&
+            return 0
+    done
+    return 1
+}
+
+wait_for_group_replay_started()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local image_count=$4
+
+    wait_for_group_replay_state "${cluster}" "${pool}" "${group}" started "${image_count}"
+}
+
+wait_for_group_replay_stopped()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local image_count=$4
+
+    wait_for_group_replay_state "${cluster}" "${pool}" "${group}" stopped "${image_count}"
+}
+
+get_newest_group_mirror_snapshot()
+{
+    local cluster=$1
+    local pool=$2
+    local image=$3
+    local log=$4
+
+    rbd --cluster "${cluster}" group snap list "${pool}/${group}" --format xml | \
+        xmlstarlet sel -t -c "//group_snaps/group_snap[state='complete' and position()=last()]" > \
+        ${log} || true
+}
+
+wait_for_group_snapshot_sync_complete()
+{
+    local local_cluster=$1
+    local cluster=$2
+    local pool=$3
+    local group=$4
+
+    local status_log=${TEMPDIR}/$(mkfname ${cluster}-${pool}-${group}.status)
+    local local_status_log=${TEMPDIR}/$(mkfname ${local_cluster}-${pool}-${group}.status)
+
+    mirror_group_snapshot "${cluster}" "${pool}" "${group}"
+    get_newest_group_mirror_snapshot "${cluster}" "${pool}" "${group}" "${status_log}"
+    local group_snap_id=$(xmlstarlet sel -t -v "//group_snap/snapshot" < ${status_log} | awk -F. '{print $NF}')
+
+    while true; do
+        for s in 0.2 0.4 0.8 1.6 2 2 4 4 8 8 16 16 32 32; do
+            sleep ${s}
+
+            get_newest_group_mirror_snapshot "${local_cluster}" "${pool}" "${image}" "${local_status_log}"
+            local local_group_snap_id=$(xmlstarlet sel -t -v "//group_snap/snapshot" < ${local_status_log} | awk -F. '{print $NF}')
+
+            test "${local_group_snap_id}" = "${group_snap_id}" && return 0
+        done
+
+        return 1
+    done
+    return 1
+}
+
+wait_for_group_replay_complete()
+{
+    local local_cluster=$1
+    local cluster=$2
+    local pool=$3
+    local group=$4
+
+    if [ "${MIRROR_IMAGE_MODE}" = "snapshot" ]; then
+        wait_for_group_snapshot_sync_complete ${local_cluster} ${cluster} ${pool} ${group}
+    else
+        return 1
+    fi
+}
+
+test_group_status_in_pool_dir()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local state_pattern="$4"
+    local description_pattern="$5"
+    local service_pattern="$6"
+
+    local status_log=${TEMPDIR}/$(mkfname ${cluster}-${pool}-${group}.mirror_status)
+    CEPH_ARGS='' rbd --cluster ${cluster} mirror group status ${pool}/${group} |
+        tee ${status_log} >&2
+    grep "^  state: .*${state_pattern}" ${status_log} || return 1
+    grep "^  description: .*${description_pattern}" ${status_log} || return 1
+
+    if [ -n "${service_pattern}" ]; then
+        grep "service: *${service_pattern}" ${status_log} || return 1
+    elif echo ${state_pattern} | grep '^up+'; then
+        grep "service: *${MIRROR_USER_ID_PREFIX}.* on " ${status_log} || return 1
+    else
+        grep "service: " ${status_log} && return 1
+    fi
+
+    return 0
+}
+
+wait_for_group_status_in_pool_dir()
+{
+    local cluster=$1
+    local pool=$2
+    local group=$3
+    local state_pattern="$4"
+    local description_pattern="$5"
+    local service_pattern="$6"
+
+    for s in 1 2 4 8 8 8 8 8 8 8 8 16 16; do
+        sleep ${s}
+        test_group_status_in_pool_dir ${cluster} ${pool} ${group} \
+            "${state_pattern}" "${description_pattern}" "${service_pattern}" &&
+            return 0
+    done
+    return 1
+}
+
 #
 # Main
 #