diff mbox series

[ovs-dev,02/16] tests: Introduce new testing helpers.

Message ID 20201030002447.936548-2-blp@ovn.org
State Accepted
Headers show
Series [ovs-dev,01/16] tests: Drop support for glibc before version 2.11. | expand

Commit Message

Ben Pfaff Oct. 30, 2020, 12:24 a.m. UTC
These simplify a lot of otherwise harder to understand checks within
the tests.  This commit should show how valuable they are, although I'm
sure scope remains to use them in more places.

Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 tests/ovn-controller.at |   8 +-
 tests/ovn-ic.at         |  31 +--
 tests/ovn-macros.at     | 121 ++++++++++
 tests/ovn-nbctl.at      |  12 +-
 tests/ovn-northd.at     | 524 ++++++++++------------------------------
 tests/ovn.at            | 495 +++++++++++++------------------------
 tests/ovs-macros.at     |  41 +++-
 7 files changed, 468 insertions(+), 764 deletions(-)

Comments

0-day Robot Oct. 30, 2020, 1:03 a.m. UTC | #1
Bleep bloop.  Greetings Ben Pfaff, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


checkpatch:
WARNING: Line has trailing whitespace
#731 FILE: tests/ovn-northd.at:462:
wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis 

WARNING: Line has trailing whitespace
#1397 FILE: tests/ovn.at:8160:
AS_BOX([restart northd and make sure tag allocation is stable]) 

WARNING: Line has trailing whitespace
#1520 FILE: tests/ovn.at:10972:
wait_row_count Port_Binding 1 logical_port=cr-alice chassis=$hv4_chassis 

WARNING: Line has trailing whitespace
#1565 FILE: tests/ovn.at:11566:
check_column "$ha_ch" HA_Chassis _uuid 

WARNING: Line has trailing whitespace
#2223 FILE: tests/ovs-macros.at:274:
    

Lines checked: 2237, Warnings: 5, Errors: 0


Please check this out.  If you feel there has been an error, please email aconole@redhat.com

Thanks,
0-day Robot
diff mbox series

Patch

diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
index d8061345f8f3..014a97760bec 100644
--- a/tests/ovn-controller.at
+++ b/tests/ovn-controller.at
@@ -201,9 +201,7 @@  OVS_WAIT_UNTIL([
 ])
 
 # Only one Chassis_Private record should exist.
-OVS_WAIT_UNTIL([
-    test $(ovn-sbctl --columns _uuid list chassis_private | wc -l) -eq 1
-])
+wait_row_count Chassis_Private 1
 
 # Simulate system-id changing while ovn-controller is disconnected from the
 # SB.
@@ -227,9 +225,7 @@  OVS_WAIT_UNTIL([
 ])
 
 # Only one Chassis_Private record should exist.
-OVS_WAIT_UNTIL([
-    test $(ovn-sbctl --columns _uuid list chassis_private | wc -l) -eq 1
-])
+wait_row_count Chassis_Private 1
 
 # Gracefully terminate daemons
 OVN_CLEANUP_SBOX([hv])
diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at
index 6fb00319a4f5..1d40ce958d6c 100644
--- a/tests/ovn-ic.at
+++ b/tests/ovn-ic.at
@@ -5,7 +5,7 @@  ovn_init_ic_db
 ovn_start az1
 ovn_start az2
 
-OVS_WAIT_UNTIL([test `ovn-ic-sbctl show | wc -l` -eq 2])
+wait_row_count ic-sb:Availability_Zone 2
 AT_CHECK([ovn-ic-sbctl show], [0], [dnl
 availability-zone az1
 availability-zone az2
@@ -39,32 +39,21 @@  AT_CHECK([ovn-ic-nbctl ts-add ts1])
 AT_CHECK([ovn-ic-nbctl ts-add ts2])
 
 # Check ISB
-OVS_WAIT_UNTIL([ovn-ic-sbctl list datapath | grep ts2])
-AT_CHECK([ovn-ic-sbctl -f csv -d bare --no-headings --columns transit_switch list datapath | sort], [0], [dnl
-ts1
-ts2
-])
+wait_row_count ic-sb:Datapath_Binding 1 transit_switch=ts1
+wait_row_count ic-sb:Datapath_Binding 1 transit_switch=ts2
+check_column "ts1 ts2" ic-sb:Datapath_Binding transit_switch
+check_column "ts1 ts2" nb:Logical_Switch name
 
-# Check NB
-AT_CHECK([ovn-nbctl -f csv -d bare --no-headings --columns name list logical_switch | sort], [0], [dnl
-ts1
-ts2
-])
 
 # Check SB DP key
-ts1_key=$(ovn-ic-sbctl -f csv -d bare --no-headings --columns tunnel_key find datapath transit_switch=ts1)
-sb_ts1_key=$(ovn-sbctl -f csv -d bare --no-headings --columns tunnel_key find datapath_binding external_ids:interconn-ts=ts1)
-AT_CHECK([test $ts1_key = $sb_ts1_key])
+ts1_key=$(fetch_column ic-sb:Datapath_Binding tunnel_key transit_switch=ts1)
+check_column "$ts1_key" Datapath_Binding tunnel_key external_ids:interconn-ts=ts1
 
 # Test delete
 AT_CHECK([ovn-ic-nbctl ts-del ts1])
-OVS_WAIT_WHILE([ovn-ic-sbctl list datapath | grep ts1])
-AT_CHECK([ovn-ic-sbctl -f csv -d bare --no-headings --columns transit_switch list datapath], [0], [dnl
-ts2
-])
-AT_CHECK([ovn-nbctl -f csv -d bare --no-headings --columns name list logical_switch | sort], [0], [dnl
-ts2
-])
+wait_row_count ic-sb:Datapath_Binding 0 transit_switch=ts1
+check_column ts2 ic-sb:Datapath_Binding transit_switch
+check_column ts2 nb:Logical_Switch name
 
 OVN_CLEANUP_IC([az1])
 
diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
index a6719be8303f..be596caf33d0 100644
--- a/tests/ovn-macros.at
+++ b/tests/ovn-macros.at
@@ -286,4 +286,125 @@  ovn_populate_arp__() {
 }
 m4_divert_pop([PREPARE_TESTS])
 
+OVS_START_SHELL_HELPERS
+# check COMMAND...
+#
+# Runs COMMAND and checks that it succeeds without any output.
+check() {
+    echo "$@"
+    AT_CHECK(["$@"])
+}
+
+parse_db() {
+    case $1 in
+        (*:*) echo ${1%%:*} ;;
+        (*) echo sb ;;
+    esac
+}
+
+parse_table() {
+    case $1 in
+        (*:*) echo ${1##*:} ;;
+        (*) echo $1 ;;
+    esac
+}
+
+# count_rows TABLE [CONDITION...]
+#
+# Prints the number of rows in TABLE (that satisfy CONDITION).
+# Uses the southbound db by default; set DB=nb for the northbound database.
+count_rows() {
+    local db=$(parse_db $1) table=$(parse_table $1); shift
+    ovn-${db}ctl --format=table --no-headings find $table "$@" | wc -l
+}
+
+# check_row_count [DATABASE:]TABLE COUNT [CONDITION...]
+#
+# Checks that TABLE contains COUNT rows (that satisfy CONDITION).
+# The default DATABASE is "sb".
+check_row_count() {
+    local db=$(parse_db $1) table=$(parse_table $1); shift
+    local count=$1; shift
+    local found=$(count_rows $db:$table "$@")
+    echo
+    echo "Checking for $count rows in $db $table${1+ with $*}... found $found"
+    if test "$count" != "$found"; then
+        ovn-${db}ctl list $table
+        AT_FAIL_IF([:])
+    fi
+}
+
+# wait_row_count [DATABASE:]TABLE COUNT [CONDITION...]
+#
+# Waits until TABLE contains COUNT rows (that satisfy CONDITION).
+# The default DATABASE is "sb".
+wait_row_count() {
+    local db=$(parse_db $1) table=$(parse_table $1); shift
+    local count=$1; shift
+    local a=$1 b=$2 c=$3
+    echo "Waiting until $count rows in $db $table${1+ with $*}..."
+    OVS_WAIT_UNTIL([test $count = $(count_rows $db:$table $a $b $c)],[
+      echo "$db table $table has the following rows. $(count_rows $db:$table $a $b $c) rows match instead of expected $count:"
+      ovn-${db}ctl list $table])
+}
+
+# fetch_column [DATABASE:]TABLE COLUMN [CONDITION...]
+#
+# Fetches and prints all the values of COLUMN in the rows of TABLE
+# (that satisfy CONDITION), sorting the results lexicographically.
+# The default DATABASE is "sb".
+fetch_column() {
+    local db=$(parse_db $1) table=$(parse_table $1) column=${2-_uuid}; shift; shift
+    # Using "echo" removes spaces and newlines.
+    echo $(ovn-${db}ctl --bare --columns $column find $table "$@" | sort)
+}
+
+# check_column EXPECTED [DATABASE:]TABLE COLUMN [CONDITION...]
+#
+# Fetches all of the values of COLUMN in the rows of TABLE (that
+# satisfy CONDITION), and compares them against EXPECTED (ignoring
+# order).
+#
+# The default DATABASE is "sb".
+check_column() {
+    local expected=$1 db=$(parse_db $2) table=$(parse_table $2) column=${3-_uuid}; shift; shift; shift
+    local found=$(ovn-${db}ctl --bare --columns $column find $table "$@")
+
+    # Sort the expected and found values.
+    local found=$(for d in $found; do echo $d; done | sort)
+    local expected=$(for d in $expected; do echo $d; done | sort)
+
+    echo
+    echo "Checking values in $db $table${1+ with $*} against $expected... found $found"
+    if test "$found" != "$expected"; then
+        ovn-${db}ctl list $table
+        AT_FAIL_IF([:])
+    fi
+}
+
+# wait_column EXPECTED [DATABASE:]TABLE [COLUMN [CONDITION...]]
+#
+# Wait until all of the values of COLUMN in the rows of TABLE (that
+# satisfy CONDITION) equal EXPECTED (ignoring order).
+#
+# The default DATABASE is "sb".
+#
+# COLUMN defaults to _uuid if unspecified.
+wait_column() {
+    local expected=$(for d in $1; do echo $d; done | sort)
+    local db=$(parse_db $2) table=$(parse_table $2) column=${3-_uuid}; shift; shift; shift
+    local a=$1 b=$2 c=$3
+
+    echo
+    echo "Waiting until $column in $db $table${1+ with $*} is $expected..."
+    OVS_WAIT_UNTIL([
+      found=$(ovn-${db}ctl --bare --columns $column find $table $a $b $c)
+      found=$(for d in $found; do echo $d; done | sort)
+      test "$expected" = "$found"
+    ], [
+      echo "$column in $db table $table has value $found, from the following rows:"
+      ovn-${db}ctl list $table])
+}
+OVS_END_SHELL_HELPERS
+
 m4_define([OVN_POPULATE_ARP], [AT_CHECK(ovn_populate_arp__, [0], [ignore])])
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index 3dbedc843b7c..79d580d3f454 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -536,18 +536,12 @@  snat             30.0.0.1                            192.168.1.0/24
 snat             fd01::1                             fd11::/64
 ])
 
-AT_CHECK([ovn-nbctl --bare --columns=options list nat | grep stateless=true| wc -l], [0],
-[0
-])
+check_row_count nb:NAT 0 options:stateless=true
 AT_CHECK([ovn-nbctl --stateless lr-nat-add lr0 dnat_and_snat 40.0.0.2 192.168.1.4])
-AT_CHECK([ovn-nbctl --bare --columns=options list nat | grep stateless=true| wc -l], [0],
-[1
-])
+check_row_count nb:NAT 1 options:stateless=true
 
 AT_CHECK([ovn-nbctl --stateless lr-nat-add lr0 dnat_and_snat fd21::1 fd11::2])
-AT_CHECK([ovn-nbctl --bare --columns=options list nat | grep stateless=true| wc -l], [0],
-[2
-])
+check_row_count nb:NAT 2 options:stateless=true
 
 AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat fd21::1])
 
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 946f20b6a176..869bcd6f6cba 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -22,130 +22,47 @@  nb_gwc1_uuid=`ovn-nbctl --bare --columns _uuid find Gateway_Chassis name="alice_
 
 # With the new ha_chassis_group table added, there should be no rows in
 # gateway_chassis table in SB DB.
-AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0
-])
-
-# There should be one ha_chassis_group with the name "alice"
-ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \
-ha_chassis_group name="alice"`
-
-AT_CHECK([test $ha_chassi_grp_name = alice])
-
-ha_chgrp_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group name=alice`
-
-AT_CHECK([ovn-sbctl --bare --columns ha_chassis_group find port_binding \
-logical_port="cr-alice" | grep $ha_chgrp_uuid | wc -l], [0], [1
-])
+check_row_count Gateway_Chassis 0
 
 # There should be one ha_chassis_group with the name "alice"
-ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \
-ha_chassis_group name="alice"`
-
-AT_CHECK([test $ha_chassi_grp_name = alice])
-
-ha_chgrp_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group name=alice`
-
-AT_CHECK([ovn-sbctl --bare --columns ha_chassis_group find port_binding \
-logical_port="cr-alice" | grep $ha_chgrp_uuid | wc -l], [0], [1
-])
-
-ha_ch=`ovn-sbctl --bare --columns ha_chassis  find ha_chassis_group`
-# Trim the spaces.
-ha_ch=`echo $ha_ch | sed 's/ //g'`
+check_row_count HA_Chassis_Group 1 name=alice
+ha_chgrp_uuid=$(fetch_column HA_Chassis_Group _uuid name=alice)
+check_row_count Port_Binding 1 logical_port=cr-alice ha_chassis_group=$ha_chgrp_uuid
 
-ha_ch_list=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list="$ha_ch_list $i"
-done
-
-# Trim the spaces.
-ha_ch_list=`echo $ha_ch_list | sed 's/ //g'`
-
-AT_CHECK([test "$ha_ch_list" = "$ha_ch"])
+ha_ch=$(fetch_column HA_Chassis_Group ha_chassis name=alice)
+check_column "$ha_ch" HA_Chassis _uuid
 
 # Delete chassis - gw2 in SB DB.
 # ovn-northd should not recreate ha_chassis rows
 # repeatedly when gw2 is deleted.
 ovn-sbctl chassis-del gw2
 
-ha_ch_list_1=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list_1="$ha_ch_list_1 $i"
-done
-
-# Trim the spaces.
-ha_ch_list_1=`echo $ha_ch_list_1 | sed 's/ //g'`
-
-ha_ch_list_2=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list_2="$ha_ch_list_2 $i"
-done
-
-# Trim the spaces.
-ha_ch_list_2=`echo $ha_ch_list_2 | sed 's/ //g'`
-
-AT_CHECK([test "$ha_ch_list_1" = "$ha_ch_list_2"])
+ha_ch_list=$(fetch_column HA_Chassis _uuid)
+check_column "$ha_ch_list" HA_Chassis _uuid
 
 # Add back the gw2 chassis
 ovn-sbctl chassis-add gw2 geneve 1.2.4.8
 
 # delete the 2nd Gateway_Chassis on NBDB for alice port
-gw_ch=`ovn-sbctl --bare --columns gateway_chassis find port_binding \
-logical_port="cr-alice"`
-AT_CHECK([test "$gw_ch" = ""])
+check_column '' Port_Binding gateway_chassis logical_port=cr-alice
 
-ha_ch=`ovn-sbctl --bare --columns ha_chassis  find ha_chassis_group`
-ha_ch=`echo $ha_ch | sed 's/ //g'`
-# Trim the spaces.
-echo "ha ch in grp = $ha_ch"
-
-ha_ch_list=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list="$ha_ch_list $i"
-done
-
-# Trim the spaces.
-ha_ch_list=`echo $ha_ch_list | sed 's/ //g'`
-
-AT_CHECK([test "$ha_ch_list" = "$ha_ch"])
+ha_ch=$(fetch_column HA_Chassis_Group ha_chassis)
+check_column "$ha_ch" HA_Chassis _uuid
 
 # delete the 2nd Gateway_Chassis on NBDB for alice port
 ovn-nbctl --wait=sb set Logical_Router_Port alice gateway_chassis=${nb_gwc1_uuid}
 
 # There should be only 1 row in ha_chassis SB DB table.
-AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis | wc -l], [0], [1
-])
-
-AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0
-])
-
-# There should be only 1 row in ha_chassis SB DB table.
-AT_CHECK([ovn-sbctl --bare --columns _uuid list ha_chassis | wc -l], [0], [1
-])
+check_row_count HA_Chassis 1
+check_row_count Gateway_Chassis 0
 
 # delete all the gateway_chassis on NBDB for alice port
-
 ovn-nbctl --wait=sb clear Logical_Router_Port alice gateway_chassis
 
 # expect that the ha_chassis doesn't exist anymore
-AT_CHECK([ovn-sbctl list gateway_chassis | wc -l], [0], [0
-])
-
-AT_CHECK([ovn-sbctl list ha_chassis | wc -l], [0], [0
-])
-
-AT_CHECK([ovn-sbctl list ha_chassis_group | wc -l], [0], [0
-])
-
-# expect that the ha_chassis doesn't exist anymore
-AT_CHECK([ovn-sbctl list ha_chassis | wc -l], [0], [0
-])
-AT_CHECK([ovn-sbctl list ha_chassis_group | wc -l], [0], [0
-])
+check_row_count HA_Chassis 0
+check_row_count Gateway_Chassis 0
+check_row_count Ha_Chassis_Group 0
 
 AT_CLEANUP
 
@@ -154,11 +71,11 @@  ovn_start
 
 ovn-nbctl ls-add S1
 ovn-nbctl --wait=sb lsp-add S1 S1-vm1
-AT_CHECK([test x`ovn-nbctl lsp-get-up S1-vm1` = xdown])
+wait_row_count nb:Logical_Switch_Port 1 name=S1-vm1 'up!=true'
 
 ovn-sbctl chassis-add hv1 geneve 127.0.0.1
 ovn-sbctl lsp-bind S1-vm1 hv1
-AT_CHECK([test x`ovn-nbctl lsp-get-up S1-vm1` = xup])
+wait_row_count nb:Logical_Switch_Port 1 name=S1-vm1 'up=true'
 
 AT_CLEANUP
 
@@ -334,8 +251,7 @@  as northd start_daemon ovn-northd --unixctl="$ovs_base"/northd/ovn-northd.ctl --
 ovn-nbctl ls-add sw
 ovn-nbctl --wait=sb lsp-add sw p1
 # northd created with unixctl option successfully created port_binding entry
-AT_CHECK([ovn-sbctl --bare --columns datapath find port_binding logical_port="p1" | wc -l], [0], [1
-])
+check_row_count Port_Binding 1 logical_port=p1
 AT_CHECK([ovn-nbctl --wait=sb lsp-del p1])
 
 # ovs-appctl exit with unixctl option
@@ -344,15 +260,13 @@  OVS_APP_EXIT_AND_WAIT_BY_TARGET(["$ovs_base"/northd/ovn-northd.ctl], ["$ovs_base
 # Check no port_binding entry for new port as ovn-northd is not running
 ovn-nbctl lsp-add sw p2
 ovn-nbctl --timeout=10 --wait=sb sync
-AT_CHECK([ovn-sbctl --bare --columns datapath find port_binding logical_port="p2" | wc -l], [0], [0
-])
+check_row_count Port_Binding 0 logical_port=p2
 
 # test default unixctl path
 as northd start_daemon ovn-northd --ovnnb-db=unix:"$ovs_base"/ovn-nb/ovn-nb.sock --ovnsb-db=unix:"$ovs_base"/ovn-sb/ovn-sb.sock
 ovn-nbctl --wait=sb lsp-add sw p3
 # northd created with default unixctl path successfully created port_binding entry
-AT_CHECK([ovn-sbctl --bare --columns datapath find port_binding logical_port="p3" | wc -l], [0], [1
-])
+check_row_count Port_Binding 1 logical_port=p3
 
 as ovn-sb
 OVS_APP_EXIT_AND_WAIT([ovsdb-server])
@@ -371,28 +285,22 @@  ovn-nbctl --wait=sb ha-chassis-group-add hagrp1
 # ovn-northd should not create HA chassis group and HA chassis rows
 # unless the HA chassis group in OVN NB DB is associated to
 # a logical router port or logical port of type external.
-AT_CHECK([ovn-sbctl --bare --columns name find ha_chassis_group name="hagrp1" \
-| wc -l], [0], [0
-])
+check_row_count HA_Chassis_Group 0 name=hagrp1
 
 ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 30
 ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch2 20
 ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch3 10
 
 # There should be no HA_Chassis rows in SB DB.
-AT_CHECK([ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \
-| grep -v '-' | wc -l ], [0], [0
-])
+check_row_count HA_Chassis 0
 
 # Add chassis ch1.
 ovn-sbctl chassis-add ch1 geneve 127.0.0.2
 
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl list chassis | grep ch1 | wc -l`])
+wait_row_count Chassis 1 name=ch1
 
 # There should be no HA_Chassis rows
-AT_CHECK([ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \
-| grep -v '-' | wc -l ], [0], [0
-])
+check_row_count HA_Chassis 0
 
 # Create a logical router port and attach ha chassis group.
 ovn-nbctl lr-add lr0
@@ -401,44 +309,21 @@  ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24
 hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp1`
 ovn-nbctl set logical_router_port lr0-public ha_chassis_group=$hagrp1_uuid
 
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
+wait_row_count HA_Chassis_Group 1 name=hagrp1
 
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
+check_row_count HA_Chassis 3
 
 # Make sure that ovn-northd doesn't recreate the ha_chassis
 # records if the chassis record is missing in SB DB.
-
-ha_ch_list_1=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list_1="$ha_ch_list_1 $i"
-done
-
-# Trim the spaces.
-ha_ch_list_1=`echo $ha_ch_list_1 | sed 's/ //g'`
-
-ha_ch_list_2=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list_2="$ha_ch_list_2 $i"
-done
-
-# Trim the spaces.
-ha_ch_list_2=`echo $ha_ch_list_2 | sed 's/ //g'`
-
-AT_CHECK([test "$ha_ch_list_1" = "$ha_ch_list_2"])
+ha_ch_list=$(fetch_column HA_Chassis _uuid)
+check_column "$ha_ch_list" HA_Chassis _uuid
 
 # 2 HA chassis should be created with 'chassis' column empty because
 # we have not added hv1 and hv2 chassis to the SB DB.
-AT_CHECK([test 2 = `ovn-sbctl list ha_chassis | grep chassis | awk '{print $3}' \
-| grep -v '-' | wc -l`])
+check_row_count HA_Chassis 2 'chassis=[[]]'
 
 # We should have 1 ha chassis with 'chassis' column set for hv1
-AT_CHECK([test 1 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | awk '{print $3}' \
-| grep '-' | wc -l`])
+check_row_count HA_Chassis 1 'chassis!=[[]]'
 
 # Create another logical router port and associate to the same ha_chasis_group
 ovn-nbctl lr-add lr1
@@ -447,94 +332,68 @@  ovn-nbctl lrp-add lr1 lr1-public 00:00:20:20:12:14 182.168.0.100/24
 ovn-nbctl set logical_router_port lr1-public ha_chassis_group=$hagrp1_uuid
 
 # We should still have 1 HA chassis group and 3 HA chassis in SB DB.
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
+wait_row_count HA_Chassis_Group 1 name=hagrp1
+check_row_count HA_Chassis 3
 
 # Change the priority of ch1 - ha chassis in NB DB. It should get
 # reflected in SB DB.
 ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 100
 
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns priority find \
-ha_chassis | grep 100 | wc -l`])
+wait_row_count HA_Chassis 1 priority=100
 
 # Delete ch1 HA chassis in NB DB.
 ovn-nbctl --wait=sb ha-chassis-group-remove-chassis hagrp1 ch1
 
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
+wait_row_count HA_Chassis 2
 
 # Add back the ha chassis
 ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch1 40
-OVS_WAIT_UNTIL([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
+wait_row_count HA_Chassis 3
 
 # Delete lr0-public. We should still have 1 HA chassis group and
 # 3 HA chassis in SB DB.
 ovn-nbctl --wait=sb lrp-del lr0-public
 
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
+wait_row_count HA_Chassis_Group 1 name=hagrp1
+wait_row_count HA_Chassis 3
 
 # Delete lr1-public. There should be no HA chassis group in SB DB.
 ovn-nbctl --wait=sb lrp-del lr1-public
 
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`])
+wait_row_count HA_Chassis_Group 0 name=hagrp1
+wait_row_count HA_Chassis 0
 
 # Add lr0-public again
 ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24
 ovn-nbctl set logical_router_port lr0-public ha_chassis_group=$hagrp1_uuid
 
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
+wait_row_count HA_Chassis_Group 1 name=hagrp1
+wait_row_count HA_Chassis 3
 
 # Create a Gateway chassis. ovn-northd should ignore this.
 ovn-nbctl lrp-set-gateway-chassis lr0-public ch-1 20
 
 # There should be only 1 HA chassis group in SB DB with the
 # name hagrp1.
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group | wc -l`])
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
+wait_row_count HA_Chassis_Group 1
+wait_row_count HA_Chassis_Group 1 name=hagrp1
+wait_row_count HA_Chassis 3
 
 # Now delete HA chassis group. ovn-northd should create HA chassis group
 # with the Gateway chassis name
 ovn-nbctl clear logical_router_port lr0-public ha_chassis_group
 ovn-nbctl ha-chassis-group-del hagrp1
 
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="lr0-public" | wc -l`])
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid \
-find ha_chassis | wc -l`])
+wait_row_count HA_Chassis_Group 0 name=hagrp1
+wait_row_count HA_Chassis_Group 1 name=lr0-public
+wait_row_count HA_Chassis 1
 
 ovn-nbctl lrp-set-gateway-chassis lr0-public ch2 10
 
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="lr0-public" | wc -l`])
+wait_row_count HA_Chassis_Group 1 name=lr0-public
 
 ovn-sbctl --bare --columns _uuid find ha_chassis
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
+wait_row_count HA_Chassis 2
 
 # Test if 'ref_chassis' column is properly set or not in
 # SB DB ha_chassis_group.
@@ -553,35 +412,23 @@  ovn-nbctl lsp-set-addresses sw0-lr0 router
 ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
 
 ovn-sbctl lsp-bind sw0-p1 comp1
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p1` = xup])
+wait_row_count nb:Logical_Switch_Port 1 name=sw0-p1 up=true
 
-comp1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="comp1"`
-comp2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="comp2"`
-ch2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="comp1"`
+comp1_ch_uuid=$(fetch_column Chassis _uuid name=comp1)
+comp2_ch_uuid=$(fetch_column Chassis _uuid name=comp2)
+ch2_ch_uuid=$comp1_ch_uuid
 
 echo "comp1_ch_uuid = $comp1_ch_uuid"
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$comp1_ch_uuid" = "$ref_ch_list"])
+wait_column "$comp1_ch_uuid" HA_Chassis_Group ref_chassis
 
 # unbind sw0-p1
 ovn-sbctl lsp-unbind sw0-p1
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p1` = xdown])
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "" = "$ref_ch_list"])
+wait_row_count nb:Logical_Switch_Port 1 name=sw0-p1 up=false
+wait_column "" HA_Chassis_Group ref_chassis
 
 # Bind sw0-p1 in comp2
 ovn-sbctl lsp-bind sw0-p1 comp2
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$comp2_ch_uuid" = "$ref_ch_list"])
+wait_column "$comp2_ch_uuid" HA_Chassis_Group ref_chassis
 
 ovn-nbctl ls-add sw1
 ovn-nbctl lsp-add sw1 sw1-p1
@@ -595,14 +442,10 @@  ovn-nbctl lsp-set-options sw1-lr1 router-port=lr1-sw1
 # Bind sw1-p1 in comp1.
 ovn-sbctl lsp-bind sw1-p1 comp1
 # Wait until sw1-p1 is up
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw1-p1` = xup])
+wait_row_count nb:Logical_Switch_Port 1 name=sw1-p1 up=true
 
 # sw1-p1 is not connected to lr0. So comp1 should not be in 'ref_chassis'
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$comp2_ch_uuid" = "$ref_ch_list"])
+wait_column "$comp2_ch_uuid" HA_Chassis_Group ref_chassis
 
 # Now attach sw0 to lr1
 ovn-nbctl lrp-add lr1 lr1-sw0 00:00:20:20:12:16 10.0.0.10/24
@@ -613,30 +456,14 @@  ovn-nbctl lsp-set-options sw0-lr1 router-port=lr1-sw0
 
 # Both comp1 and comp2 should be in 'ref_chassis' as sw1 is indirectly
 # connected to lr0
-exp_ref_ch_list=''
-for i in `ovn-sbctl --bare --columns _uuid list chassis | sort`
-do
-    if test $i = $comp1_ch_uuid; then
-        exp_ref_ch_list="${exp_ref_ch_list}$i"
-    elif test $i = $comp2_ch_uuid; then
-        exp_ref_ch_list="${exp_ref_ch_list}$i"
-    fi
-done
-
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
+exp_ref_ch_list="$comp1_ch_uuid $comp2_ch_uuid"
+
+wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis 
 
 # Unind sw1-p1. comp2 should not be in the ref_chassis.
 ovn-sbctl lsp-unbind sw1-p1
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw1-p1` = xdown])
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$comp2_ch_uuid" = "$ref_ch_list"])
+wait_row_count nb:Logical_Switch_Port 1 name=sw1-p1 up=false
+wait_column "$comp2_ch_uuid" HA_Chassis_Group ref_chassis
 
 # Create sw2 and attach it to lr2
 ovn-nbctl ls-add sw2
@@ -651,14 +478,10 @@  ovn-nbctl lsp-set-options sw2-lr2 router-port=lr2-sw2
 # Bind sw2-p1 to comp1
 ovn-sbctl lsp-bind sw2-p1 comp1
 # Wait until sw2-p1 is up
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw2-p1` = xup])
+wait_row_count nb:Logical_Switch_Port 1 name=sw2-p1 up=true
 
 # sw2-p1 is not connected to lr0. So comp1 should not be in 'ref_chassis'
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$comp2_ch_uuid" = "$ref_ch_list"])
+wait_column "$comp2_ch_uuid" HA_Chassis_Group ref_chassis
 
 # Now attach sw1 to lr2. With this sw2-p1 is indirectly connected to lr0.
 ovn-nbctl lrp-add lr2 lr2-sw1 00:00:20:20:12:18 20.0.0.10/24
@@ -669,60 +492,39 @@  ovn-nbctl lsp-set-options sw1-lr2 router-port=lr2-sw1
 
 # sw2-p1 is indirectly connected to lr0. So comp1 (and comp2) should be in
 # 'ref_chassis'
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
+wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis
 
 # Create sw0-p2 and bind it to comp1
 ovn-nbctl lsp-add sw0 sw0-p2
 ovn-sbctl lsp-bind sw0-p2 comp1
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p2` = xup])
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
+wait_row_count nb:Logical_Switch_Port 1 name=sw0-p2 up=true
+wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis
 
 # unbind sw0-p2
 ovn-sbctl lsp-unbind sw0-p2
-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up sw0-p2` = xdown])
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
+wait_row_count nb:Logical_Switch_Port 1 name=sw0-p2 up=false
+wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis
 
 # Delete lr1-sw0. comp1 should be deleted from ref_chassis as there is no link
 # from sw1 and sw2 to lr0.
 ovn-nbctl lrp-del lr1-sw0
 
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$comp2_ch_uuid" = "$ref_ch_list"])
+wait_column "$comp2_ch_uuid" HA_Chassis_Group ref_chassis
 
 # Set redirect-chassis option to lr0-public. It should be ignored
 # (because redirect-chassis is obsolete).
 ovn-nbctl set logical_router_port lr0-public options:redirect-chassis=ch1
 
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group | wc -l`])
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="lr0-public" | wc -l`])
+wait_row_count HA_Chassis_Group 1
+wait_row_count HA_Chassis_Group 1 name=lr0-public
 
-ovn-sbctl --bare --columns _uuid find ha_chassis
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
+wait_row_count HA_Chassis 2
 
 # Delete the gateway chassis.
 ovn-nbctl clear logical_router_port lr0-public gateway_chassis
 
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group |  wc -l`])
-AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
+wait_row_count HA_Chassis_Group 0
+check_row_count HA_Chassis 0
 
 # Delete old sw0.
 ovn-nbctl ls-del sw0
@@ -746,8 +548,8 @@  ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 ch3 10
 # ovn-northd should not create HA chassis group and HA chassis rows
 # unless the HA chassis group in OVN NB DB is associated to
 # a logical router port or logical port of type external.
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group |  wc -l`])
-AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
+wait_row_count HA_Chassis_Group 0
+check_row_count HA_Chassis 0
 
 hagrp1_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group \
 name=hagrp1`
@@ -756,69 +558,50 @@  name=hagrp1`
 # So ha_chassis_group should be ignored.
 ovn-nbctl set logical_switch_port sw0-pext1 ha_chassis_group=$hagrp1_uuid
 
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | grep chassis | wc -l`])
+wait_row_count HA_Chassis_Group 0 name=hagrp1
+check_row_count HA_Chassis 0
 
 # Set the type of sw0-pext1 to external
 ovn-nbctl lsp-set-type sw0-pext1 external
 
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
+wait_row_count HA_Chassis_Group 1 name=hagrp1
+check_row_count HA_Chassis 3
 
 sb_hagrp1_uuid=`ovn-sbctl --bare --columns _uuid find ha_chassis_group \
 name=hagrp1`
 
-AT_CHECK([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \
-ha_chassis_group find port_binding logical_port=sw0-pext1`])
+check_row_count Port_Binding 1 logical_port=sw0-pext1 ha_chassis_group=$sb_hagrp1_uuid
 
 # Set the type of sw0-pext2 to external and associate ha_chassis_group
 ovn-nbctl lsp-set-type sw0-pext2 external
 ovn-nbctl set logical_switch_port sw0-pext2 ha_chassis_group=$hagrp1_uuid
 
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
-
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis |
-grep -v chassis-name | wc -l`])
-AT_CHECK([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \
-ha_chassis_group find port_binding logical_port=sw0-pext1`])
-
-OVS_WAIT_UNTIL([test "$sb_hagrp1_uuid" = `ovn-sbctl --bare --columns \
-ha_chassis_group find port_binding logical_port=sw0-pext2`])
+wait_row_count HA_Chassis_Group 1 name=hagrp1
+check_row_count HA_Chassis 3
+check_row_count Port_Binding 1 logical_port=sw0-pext1 ha_chassis_group=$sb_hagrp1_uuid
+wait_row_count Port_Binding 1 logical_port=sw0-pext2 ha_chassis_group=$sb_hagrp1_uuid
 
 # sw0-p1 is a normal port. So ha_chassis_group should not be set
 # in port_binding.
 ovn-nbctl --wait=sb set logical_switch_port sw0-p1 \
 ha_chassis_group=$hagrp1_uuid
 
-OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
-logical_port=sw0-p1) = x], [0], [])
+wait_row_count Port_Binding 0 logical_port=sw0-p1 'chassis!=[[]]'
 
 # Clear ha_chassis_group for sw0-pext1
 ovn-nbctl --wait=sb clear logical_switch_port sw0-pext1 ha_chassis_group
 
-OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
-logical_port=sw0-pext1) = x], [0], [])
-
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns name find \
-ha_chassis_group name="hagrp1" | wc -l`])
+wait_row_count Port_Binding 0 logical_port=sw0-pext1 'chassis!=[[]]'
 
-AT_CHECK([test 3 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
+wait_row_count HA_Chassis_Group 1 name=hagrp1
+wait_row_count HA_Chassis 3
 
 # Clear ha_chassis_group for sw0-pext2
 ovn-nbctl --wait=sb clear logical_switch_port sw0-pext2 ha_chassis_group
 
-OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
-logical_port=sw0-pext2) = x], [0], [])
-
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group |  wc -l`])
-AT_CHECK([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
+wait_row_count Port_Binding 0 logical_port=sw0-pext2 'chassis!=[[]]'
+wait_row_count HA_Chassis_Group 0
+check_row_count HA_Chassis 0
 
 as ovn-sb
 OVS_APP_EXIT_AND_WAIT([ovsdb-server])
@@ -906,17 +689,11 @@  ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1
 
 ovn-nbctl lrp-set-gateway-chassis R1-S1 gw1
 
-uuid=`ovn-sbctl --columns=_uuid --bare find Port_Binding logical_port=cr-R1-S1`
-echo "CR-LRP UUID is: " $uuid
-
 ovn-nbctl lrp-set-redirect-type R1-S1 bridged
-OVS_WAIT_UNTIL([ovn-sbctl get Port_Binding ${uuid} options:redirect-type], [0], [bridged
-])
+wait_row_count Port_Binding 1 logical_port=cr-R1-S1 options:redirect-type=bridged
 
 ovn-nbctl lrp-set-redirect-type R1-S1 overlay
-OVS_WAIT_UNTIL([ovn-sbctl get Port_Binding ${uuid} options:redirect-type], [0], [overlay
-])
-
+wait_row_count Port_Binding 1 logical_port=cr-R1-S1 options:redirect-type=overlay
 AT_CLEANUP
 
 AT_SETUP([ovn -- check stateless dnat_and_snat rule])
@@ -935,9 +712,6 @@  ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1
 
 ovn-nbctl lrp-set-gateway-chassis R1-S1 gw1
 
-uuid=`ovn-sbctl --columns=_uuid --bare find Port_Binding logical_port=cr-R1-S1`
-echo "CR-LRP UUID is: " $uuid
-
 # IPV4
 ovn-nbctl lr-nat-add R1 dnat_and_snat  172.16.1.1 50.0.0.11
 
@@ -1296,37 +1070,32 @@  ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80
 ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1
 ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:20.0.0.3=sw1-p1
 
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list service_monitor |  wc -l`])
+wait_row_count Service_Monitor 0
 
 ovn-nbctl --wait=sb -- --id=@hc create \
 Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer . \
 health_check @hc
 
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list service_monitor |  wc -l`])
+wait_row_count Service_Monitor 0
 
 # create logical switches and ports
 ovn-nbctl ls-add sw0
 ovn-nbctl --wait=sb lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 \
 "00:00:00:00:00:03 10.0.0.3"
 
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns _uuid find \
-service_monitor | wc -l`])
+wait_row_count Service_Monitor 0
 
 ovn-nbctl ls-add sw1
 ovn-nbctl --wait=sb lsp-add sw1 sw1-p1 -- lsp-set-addresses sw1-p1 \
 "02:00:00:00:00:03 20.0.0.3"
 
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl --bare --columns _uuid find \
-service_monitor | sed '/^$/d' | wc -l`])
+wait_row_count Service_Monitor 0
 
 ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid find \
-service_monitor | wc -l`])
+wait_row_count Service_Monitor 1
 
 ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2
-
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns _uuid find \
-service_monitor | sed '/^$/d' | wc -l`])
+wait_row_count Service_Monitor 2
 
 ovn-nbctl --wait=sb ls-lb-add sw0 lb1
 
@@ -1337,7 +1106,7 @@  AT_CHECK([cat lflows.txt], [0], [dnl
 
 # Delete the Load_Balancer_Health_Check
 ovn-nbctl --wait=sb clear load_balancer . health_check
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list service_monitor |  wc -l`])
+wait_row_count Service_Monitor 0
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
@@ -1349,8 +1118,7 @@  ovn-nbctl --wait=sb -- --id=@hc create \
 Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer . \
 health_check @hc
 
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns _uuid find \
-service_monitor | sed '/^$/d' | wc -l`])
+wait_row_count Service_Monitor 2
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
@@ -1364,9 +1132,7 @@  sm_sw1_p1=`ovn-sbctl --bare --columns _uuid find service_monitor logical_port=sw
 # Set the service monitor for sw1-p1 to offline
 ovn-sbctl set service_monitor $sm_sw1_p1 status=offline
 
-OVS_WAIT_UNTIL([
-    status=`ovn-sbctl --bare --columns status find service_monitor logical_port=sw1-p1`
-    test "$status" = "offline"])
+wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=offline
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
@@ -1376,9 +1142,7 @@  AT_CHECK([cat lflows.txt], [0], [dnl
 # Set the service monitor for sw0-p1 to offline
 ovn-sbctl set service_monitor $sm_sw0_p1 status=offline
 
-OVS_WAIT_UNTIL([
-    status=`ovn-sbctl --bare --columns status find service_monitor logical_port=sw0-p1`
-    test "$status" = "offline"])
+wait_row_count Service_Monitor 1 logical_port=sw0-p1 status=offline
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
@@ -1394,9 +1158,7 @@  AT_CHECK([cat lflows.txt], [0], [dnl
 ovn-sbctl set service_monitor $sm_sw0_p1 status=online
 ovn-sbctl set service_monitor $sm_sw1_p1 status=online
 
-OVS_WAIT_UNTIL([
-    status=`ovn-sbctl --bare --columns status find service_monitor logical_port=sw1-p1`
-    test "$status" = "online"])
+wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=online
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
@@ -1405,9 +1167,7 @@  AT_CHECK([cat lflows.txt], [0], [dnl
 
 # Set the service monitor for sw1-p1 to error
 ovn-sbctl set service_monitor $sm_sw1_p1 status=error
-OVS_WAIT_UNTIL([
-    status=`ovn-sbctl --bare --columns status find service_monitor logical_port=sw1-p1`
-    test "$status" = "error"])
+wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=error
 
 ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" \
 | grep priority=120 > lflows.txt
@@ -1429,16 +1189,9 @@  health_check @hc
 #    * 10.0.0.3:1000
 #    * 20.0.0.3:80
 
-OVS_WAIT_UNTIL([test 3 = `ovn-sbctl --bare --columns _uuid find \
-service_monitor | sed '/^$/d' | wc -l`])
-
-# There should be 2 rows with logical_port=sw0-p1
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns _uuid find \
-service_monitor logical_port=sw0-p1 | sed '/^$/d' | wc -l`])
-
-# There should be 1 row1 with port=1000
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid find \
-service_monitor port=1000 | sed '/^$/d' | wc -l`])
+wait_row_count Service_Monitor 3
+wait_row_count Service_Monitor 2 logical_port=sw0-p1
+wait_row_count Service_Monitor 1 port=1000
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
@@ -1449,9 +1202,7 @@  AT_CHECK([cat lflows.txt], [0], [dnl
 # Set the service monitor for sw1-p1 to online
 ovn-sbctl set service_monitor $sm_sw1_p1 status=online
 
-OVS_WAIT_UNTIL([
-    status=`ovn-sbctl --bare --columns status find service_monitor logical_port=sw1-p1`
-    test "$status" = "online"])
+wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=online
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
@@ -1479,26 +1230,23 @@  ovn-nbctl ls-lb-add sw0 lb2
 ovn-nbctl ls-lb-add sw1 lb2
 ovn-nbctl lr-lb-add lr0 lb2
 
-OVS_WAIT_UNTIL([test 5 = `ovn-sbctl --bare --columns _uuid find \
-service_monitor | sed '/^$/d' | wc -l`])
+wait_row_count Service_Monitor 5
 
 # Change the svc_monitor_mac. This should get reflected in service_monitor table rows.
 ovn-nbctl set NB_Global . options:svc_monitor_mac="fe:a0:65:a2:01:03"
 
-OVS_WAIT_UNTIL([test 5 = `ovn-sbctl --bare --columns src_mac find \
-service_monitor | grep "fe:a0:65:a2:01:03" | wc -l`])
+wait_row_count Service_Monitor 5 src_mac='"fe:a0:65:a2:01:03"'
 
 # Change the source ip for 10.0.0.3 backend ip in lb2
 ovn-nbctl --wait=sb set load_balancer $lb2_uuid ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.100
 
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns src_ip find \
-service_monitor logical_port=sw0-p1 | grep "10.0.0.100" | wc -l`])
+wait_row_count Service_Monitor 1 logical_port=sw0-p1 src_ip=10.0.0.100
 
 ovn-nbctl --wait=sb lb-del lb1
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns _uuid find service_monitor | sed '/^$/d' | wc -l`])
+wait_row_count Service_Monitor 2
 
 ovn-nbctl --wait=sb lb-del lb2
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list service_monitor |  wc -l`])
+wait_row_count Service_Monitor 0
 
 AT_CLEANUP
 
@@ -1594,22 +1342,14 @@  AT_CHECK([ovn-nbctl --wait=sb sync], [0])
 
 # Ports are bound on different datapaths so it's expected that they both
 # get tunnel_key == 1.
-AT_CHECK([test 1 = $(ovn-sbctl --bare --columns tunnel_key find \
-port_binding logical_port=lsp1)])
-AT_CHECK([test 1 = $(ovn-sbctl --bare --columns tunnel_key find \
-port_binding logical_port=lsp2)])
+check_column 1 Port_Binding tunnel_key logical_port=lsp1
+check_column 1 Port_Binding tunnel_key logical_port=lsp2
 
 ovn-nbctl lsp-del lsp2 -- lsp-add ls1 lsp2
 AT_CHECK([ovn-nbctl --wait=sb sync], [0])
 
-AT_CHECK([test 1 = $(ovn-sbctl --bare --columns tunnel_key find \
-port_binding logical_port=lsp1)])
-AT_CHECK([test 2 = $(ovn-sbctl --bare --columns tunnel_key find \
-port_binding logical_port=lsp2)])
-
-# ovn-northd should allocate a new tunnel_key for lsp1 or lsp2 to maintain
-# unique DB indices.
-AT_CHECK([test ${pb1_key} != ${pb2_key}])
+check_column 1 Port_Binding tunnel_key logical_port=lsp1
+check_column 2 Port_Binding tunnel_key logical_port=lsp2
 
 AT_CLEANUP
 
@@ -1635,7 +1375,7 @@  AT_CHECK([ovn-nbctl --wait=sb sync], [0])
 ovn-nbctl lsp-del lsp2 -- lsp-add ls1 lsp2
 AT_CHECK([ovn-nbctl --wait=sb sync], [0])
 
-AT_CHECK([test 0 = $(ovn-sbctl list Ha_Chassis_Group | wc -l)])
+check_row_count HA_Chassis_Group 0
 
 AT_CLEANUP
 
@@ -1701,20 +1441,14 @@  ls2_key=$(ovn-sbctl --columns tunnel_key --bare list Datapath_Binding ls2)
 # Add lsp1 & lsp2 to a port group. This should generate two entries in the
 # SB (one per logical switch).
 ovn-nbctl --wait=sb pg-add pg_test lsp1 lsp2
-AT_CHECK([test 2 = $(ovn-sbctl --columns _uuid list Port_Group | grep uuid -c)])
-AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group name=${ls1_key}_pg_test], [0], [dnl
-lsp1
-])
-AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group name=${ls2_key}_pg_test], [0], [dnl
-lsp2
-])
+wait_row_count Port_Group 2
+check_row_count Port_Group 1 name=${ls1_key}_pg_test
+check_row_count Port_Group 1 name=${ls2_key}_pg_test
 
 # Delete logical switch ls1. This should remove the associated SB Port_Group.
 ovn-nbctl --wait=sb ls-del ls1
-AT_CHECK([test 1 = $(ovn-sbctl --columns _uuid list Port_Group | grep uuid -c)])
-AT_CHECK([ovn-sbctl --columns ports --bare find Port_Group name=${ls2_key}_pg_test], [0], [dnl
-lsp2
-])
+wait_row_count Port_Group 1
+check_row_count Port_Group 1 name=${ls2_key}_pg_test
 
 AT_CLEANUP
 
diff --git a/tests/ovn.at b/tests/ovn.at
index 04b7a3df736b..46f13f46617b 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -2063,20 +2063,12 @@  get_lsp_uuid () {
 # explictly
 
 # For Chassis hv1
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp11], [0], [dnl
-encap               : [[]]
-])
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp12], [0], [dnl
-encap               : [[]]
-])
+check_row_count Port_Binding 1 logical_port=lp11 'encap=[[]]'
+check_row_count Port_Binding 1 logical_port=lp12 'encap=[[]]'
 
 # For Chassis hv2
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp21], [0], [dnl
-encap               : [[]]
-])
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp22], [0], [dnl
-encap               : [[]]
-])
+check_row_count Port_Binding 1 logical_port=lp21 'encap=[[]]'
+check_row_count Port_Binding 1 logical_port=lp22 'encap=[[]]'
 
 # Bind the ports to the encap-ip
 for i in 1 2; do
@@ -2092,26 +2084,14 @@  sleep 1
 # ports to be bound to geneve tunnels.
 
 # For Chassis 1
-encap_rec=`ovn-sbctl --data=bare --no-heading --column _uuid find encap chassis_name=hv1 type=geneve ip=192.168.0.1`
-
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp11], [0], [dnl
-encap               : ${encap_rec}
-])
-
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp12], [0], [dnl
-encap               : ${encap_rec}
-])
+encap_rec=$(fetch_column Encap _uuid chassis_name=hv1 type=geneve ip=192.168.0.1)
+check_row_count Port_Binding 1 logical_port=lp11 encap=$encap_rec
+check_row_count Port_Binding 1 logical_port=lp12 encap=$encap_rec
 
 # For Chassis 2
-encap_rec=`ovn-sbctl --data=bare --no-heading --column _uuid find encap chassis_name=hv2 type=geneve ip=192.168.0.2`
-
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp21], [0], [dnl
-encap               : ${encap_rec}
-])
-
-AT_CHECK_UNQUOTED([ovn-sbctl  --column encap list port_binding lp22], [0], [dnl
-encap               : ${encap_rec}
-])
+encap_rec=$(fetch_column Encap _uuid chassis_name=hv2 type=geneve ip=192.168.0.2)
+check_row_count Port_Binding 1 logical_port=lp21 encap=$encap_rec
+check_row_count Port_Binding 1 logical_port=lp22 encap=$encap_rec
 
 # Pre-populate the hypervisors' ARP tables so that we don't lose any
 # packets for ARP resolution (native tunneling doesn't queue packets
@@ -4061,7 +4041,7 @@  ovn-nbctl --wait=hv set logical_router lr0 options:always_learn_from_arp_request
 
 test_arp 11 $sha $spa $tpa
 sleep 1
-AT_CHECK([ovn-sbctl find mac_binding ip="192.168.1.100"], [0], [])
+check_row_count MAC_Binding 0 ip="192.168.1.100"
 
 # When always_learn_from_arp_request=true, the new mac-binding will be learned.
 ovn-nbctl --wait=hv set logical_router lr0 options:always_learn_from_arp_request=true
@@ -4086,7 +4066,7 @@  ovn-nbctl --wait=hv set logical_router lr0 options:always_learn_from_arp_request
 
 sha=f00000000012
 test_arp 12 $sha $spa $tpa
-OVS_WAIT_UNTIL([ovn-sbctl find mac_binding ip="192.168.1.100" | grep f0:00:00:00:00:12])
+wait_row_count MAC_Binding 1 ip="192.168.1.100" mac='"f0:00:00:00:00:12"'
 ovn-nbctl --wait=hv sync
 # give to the hv the time to send queued ip packets
 sleep 1
@@ -8043,18 +8023,18 @@  ovn-nbctl lsp-add ls0 lp0
 ovn-nbctl lsp-add ls0 lp1
 ovn-nbctl lsp-set-addresses lp0 "f0:00:00:00:00:01 192.168.0.1"
 ovn-nbctl lsp-set-addresses lp1 "f0:00:00:00:00:02 192.168.0.2"
-dp_uuid=`ovn-sbctl find datapath | grep uuid | cut -f2 -d ":" | cut -f2 -d " "`
+dp_uuid=$(fetch_column Datapath_Binding _uuid)
 ovn-sbctl create MAC_Binding ip=10.0.0.1 datapath=$dp_uuid logical_port=lp0 mac="mac1"
 ovn-sbctl create MAC_Binding ip=10.0.0.1 datapath=$dp_uuid logical_port=lp1 mac="mac2"
 ovn-sbctl find MAC_Binding
 # Delete port lp0 and check that its MAC_Binding is deleted.
 ovn-nbctl lsp-del lp0
 ovn-sbctl find MAC_Binding
-OVS_WAIT_UNTIL([test `ovn-sbctl find MAC_Binding logical_port=lp0 | wc -l` = 0])
+wait_row_count MAC_Binding 0 logical_port=lp0
 # Delete logical switch ls0 and check that its MAC_Binding is deleted.
 ovn-nbctl ls-del ls0
 ovn-sbctl find MAC_Binding
-OVS_WAIT_UNTIL([test `ovn-sbctl find MAC_Binding | wc -l` = 0])
+wait_row_count MAC_Binding 0
 
 OVN_CLEANUP([hv1])
 
@@ -8121,61 +8101,62 @@  AT_CHECK([ovn-nbctl lsp-add ls0 parent1])
 AT_CHECK([ovn-nbctl lsp-add ls0 parent2])
 AT_CHECK([ovn-nbctl ls-add ls1])
 
-dnl When a tag is provided, no allocation is done
+AS_BOX([requested tag for parent1])
 AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c0 parent1 3])
-AT_CHECK([ovn-nbctl lsp-get-tag c0], [0], [3
-])
+c0_tag=$(ovn-nbctl lsp-get-tag c0)
+echo c0_tag=$c0_tag
+check test "$c0_tag" = 3
 dnl The same 'tag' gets created in southbound database.
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c0"], [0], [3
-])
+check_row_count Port_Binding 1 logical_port=c0 tag=$c0_tag
 
-dnl Allocate tags and see it getting created in both NB and SB
+AS_BOX([tag allocation 1 for parent1])
 AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c1 parent1 0])
-AT_CHECK([ovn-nbctl lsp-get-tag c1], [0], [1
-])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c1"], [0], [1
-])
+c1_tag=$(ovn-nbctl lsp-get-tag c1)
+echo c1_tag=$c1_tag
+check test "$c1_tag" != "$c0_tag"
+check_row_count Port_Binding 1 logical_port=c1 tag=$c1_tag
 
+AS_BOX([tag allocation 2 for parent1])
 AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c2 parent1 0])
-AT_CHECK([ovn-nbctl lsp-get-tag c2], [0], [2
-])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c2"], [0], [2
-])
-AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c3 parent1 0])
-AT_CHECK([ovn-nbctl lsp-get-tag c3], [0], [4
-])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c3"], [0], [4
-])
+c2_tag=$(ovn-nbctl lsp-get-tag c2)
+echo c2_tag=$c2_tag
+check test "$c2_tag" != "$c0_tag"
+check test "$c2_tag" != "$c1_tag"
+check_row_count Port_Binding 1 logical_port=c2 tag=$c2_tag
 
-dnl A different parent.
+AS_BOX([tag allocation 3 for parent1])
+AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c3 parent1 0])
+c3_tag=$(ovn-nbctl lsp-get-tag c3)
+echo c3_tag=$c3_tag
+check test "$c3_tag" != "$c0_tag"
+check test "$c3_tag" != "$c1_tag"
+check test "$c3_tag" != "$c2_tag"
+check_row_count Port_Binding 1 logical_port=c3 tag=$c3_tag
+
+AS_BOX([tag allocation 1 for parent2])
 AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c4 parent2 0])
-AT_CHECK([ovn-nbctl lsp-get-tag c4], [0], [1
-])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c4"], [0], [1
-])
+c4_tag=$(ovn-nbctl lsp-get-tag c4)
+echo c4_tag=$c4_tag
+check_row_count Port_Binding 1 logical_port=c4 tag=$c4_tag
 
+AS_BOX([tag allocation 2 for parent2])
 AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c5 parent2 0])
-AT_CHECK([ovn-nbctl lsp-get-tag c5], [0], [2
-])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c5"], [0], [2
-])
+c5_tag=$(ovn-nbctl lsp-get-tag c5)
+echo c5_tag=$c5_tag
+check test "$c5_tag" != "$c4_tag"
+check_row_count Port_Binding 1 logical_port=c5 tag=$c5_tag
 
-dnl Delete a logical port and create a new one.
+AS_BOX([delete and add tag allocation for parent1])
 AT_CHECK([ovn-nbctl --wait=sb lsp-del c1])
 AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c6 parent1 0])
-AT_CHECK([ovn-nbctl lsp-get-tag c6], [0], [1
-])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c6"], [0], [1
-])
-
-dnl Restart northd to see that the same allocation remains.
+c6_tag=$(ovn-nbctl lsp-get-tag c6)
+echo c6_tag=$c6_tag
+check_row_count Port_Binding 1 logical_port=c6 tag=$c6_tag
+check test "$c6_tag" != "$c0_tag"
+check test "$c6_tag" != "$c2_tag"
+check test "$c6_tag" != "$c3_tag"
+
+AS_BOX([restart northd and make sure tag allocation is stable]) 
 as northd
 OVS_APP_EXIT_AND_WAIT([ovn-northd])
 start_daemon ovn-northd \
@@ -8184,30 +8165,30 @@  start_daemon ovn-northd \
 
 dnl Create a switch to make sure that ovn-northd has run through the main loop.
 AT_CHECK([ovn-nbctl --wait=sb ls-add ls-dummy])
-AT_CHECK([ovn-nbctl lsp-get-tag c0], [0], [3
-])
-AT_CHECK([ovn-nbctl lsp-get-tag c6], [0], [1
-])
-AT_CHECK([ovn-nbctl lsp-get-tag c2], [0], [2
-])
-AT_CHECK([ovn-nbctl lsp-get-tag c3], [0], [4
-])
-AT_CHECK([ovn-nbctl lsp-get-tag c4], [0], [1
-])
-AT_CHECK([ovn-nbctl lsp-get-tag c5], [0], [2
+
+AT_CHECK_UNQUOTED([
+    for lsp in c0 c2 c3 c4 c5 c6; do
+        ovn-nbctl lsp-get-tag $lsp
+    done], [0],
+[$c0_tag
+$c2_tag
+$c3_tag
+$c4_tag
+$c5_tag
+$c6_tag
 ])
 
 dnl Create a switch port with a tag that has already been allocated.
 dnl It should go through fine with a duplicate tag.
-AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c7 parent2 2])
+AS_BOX([request duplicate tag])
+AT_CHECK([ovn-nbctl --wait=sb lsp-add ls1 c7 parent2 $c5_tag])
 AT_CHECK([ovn-nbctl lsp-get-tag c7], [0], [2
 ])
-AT_CHECK([ovn-sbctl --data=bare --no-heading --columns=tag find port_binding \
-logical_port="c7"], [0], [2
-])
-AT_CHECK([ovn-nbctl lsp-get-tag c5], [0], [2
-])
+check_row_count Port_Binding 1 logical_port=c7 tag=$c5_tag
+check_row_count Port_Binding 1 logical_port=c5 tag=$c5_tag
+check_row_count Port_Binding 2 parent_port=parent2 tag=$c5_tag
 
+AS_BOX([tag_request without parent_name])
 AT_CHECK([ovn-nbctl ls-add ls2])
 dnl When there is no parent_name provided (for say, 'localnet'), 'tag_request'
 dnl gets copied to 'tag'
@@ -8614,8 +8595,7 @@  check_tos 0
 
 # Mark DSCP with a valid value
 qos_id=$(ovn-nbctl --wait=hv -- --id=@lp1-qos create QoS priority=100 action=dscp=48 match="inport\=\=\"lp1\"\ &&\ is_chassis_resident(\"lp1\")" direction="from-lport" -- set Logical_Switch lsw0 qos_rules=@lp1-qos)
-AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [1
-])
+as hv check_row_count nb:QoS 1
 check_tos 48
 
 # check at hv without qos meter
@@ -8649,8 +8629,7 @@  check_tos 63
 
 # Disable DSCP marking
 ovn-nbctl --wait=hv qos-del lsw0
-AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [0
-])
+as hv check_row_count nb:QoS 0
 check_tos 0
 
 # check at hv without qos meter
@@ -8659,8 +8638,7 @@  AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l],
 
 # check meter with chassis not resident
 ovn-nbctl qos-add lsw0 to-lport 1001 'inport=="lp3" && is_chassis_resident("lp3")' rate=11123 burst=111230
-AT_CHECK([as hv ovn-nbctl qos-list lsw0 | wc -l], [0], [1
-])
+as hv check_row_count nb:QoS 1
 
 # check no meter table
 AT_CHECK([as hv ovs-ofctl dump-flows br-int -O OpenFlow13 | grep meter | wc -l], [0], [0
@@ -10129,12 +10107,8 @@  AT_CHECK([ovn-nbctl --timeout=3 --wait=sb sync], [0], [ignore])
 # hv1 should be in 'ref_chassis' of the ha_chasssi_group as logical
 # switch 'foo' can reach the router 'R1' (which has gw router port)
 # via foo1 -> foo -> R0 -> join -> R1
-hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"`
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$hv1_ch_uuid" = "$ref_ch_list"])
+hv1_ch_uuid=$(fetch_column Chassis _uuid name=hv1)
+wait_column "$hv1_ch_uuid" HA_Chassis_Group ref_chassis
 
 # Allow some time for ovn-northd and ovn-controller to catch up.
 # XXX This should be more systematic.
@@ -10547,13 +10521,11 @@  expected=${dst_mac}${src_mac}08004500001c000000003f110100${src_ip}${dst_ip}00351
 echo $expected >> hv2-vif1.expected
 OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [hv2-vif1.expected])
 
-AT_CHECK([ovn-sbctl --bare --columns _uuid find Port_Binding logical_port=cr-alice | wc -l], [0], [1
-])
+check_row_count Port_Binding 1 logical_port=cr-alice
 
 ovn-nbctl --timeout=3 --wait=sb lrp-del-gateway-chassis alice hv2
 
-AT_CHECK([ovn-sbctl find Port_Binding logical_port=cr-alice | wc -l], [0], [0
-])
+check_row_count Port_Binding 0 logical_port=cr-alice
 
 OVN_CLEANUP([hv1],[hv2],[hv3])
 
@@ -10995,11 +10967,9 @@  as hv4 reset_pcap_file br-ex_n2 hv4/br-ex_n2
 ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 hv4 40
 
 # Wait till cr-alice is claimed by hv4
-hv4_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=hv4)
+hv4_chassis=$(fetch_column Chassis _uuid name=hv4)
 # check that the chassis redirect port has been claimed by the gw1 chassis
-OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
-logical_port=cr-alice | grep $hv4_chassis | wc -l], [0],[[1
-]])
+wait_row_count Port_Binding 1 logical_port=cr-alice chassis=$hv4_chassis 
 
 # Reset the pcap file for hv2/br-ex_n2. From now on ovn-controller in hv2
 # should not send GARPs for the router ports.
@@ -11352,7 +11322,7 @@  packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111
 # Send the first packet to trigger a ARP response and population of
 # mac_bindings table.
 as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
-OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding ip="10.0.0.2" | wc -l` -gt 0])
+wait_row_count MAC_Binding 1 ip="10.0.0.2"
 ovn-nbctl --wait=hv sync
 
 # Packet to Expect at 'alice1'
@@ -11583,31 +11553,13 @@  ovn-sbctl find Port_Binding type=chassisredirect
 echo "-------------------------------------------"
 
 # There should be one ha_chassis_group with the name "outside"
-ha_chassi_grp_name=`ovn-sbctl --bare --columns name find \
-ha_chassis_group name="outside"`
-
-AT_CHECK([test $ha_chassi_grp_name = outside])
+check_row_count HA_Chassis_Group 1 name=outside
 
 # There should be 2 ha_chassis rows in SB DB.
-AT_CHECK([ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | awk '{print $3}' \
-| grep '-' | wc -l ], [0], [2
-])
-
-ha_ch=`ovn-sbctl --bare --columns ha_chassis  find ha_chassis_group`
-# Trim the spaces.
-ha_ch=`echo $ha_ch | sed 's/ //g'`
+check_row_count HA_Chassis 2 'chassis!=[[]]'
 
-ha_ch_list=''
-for i in `ovn-sbctl --bare --columns _uuid list ha_chassis | sort`
-do
-    ha_ch_list="$ha_ch_list $i"
-done
-
-# Trim the spaces.
-ha_ch_list=`echo $ha_ch_list | sed 's/ //g'`
-
-AT_CHECK([test "$ha_ch_list" = "$ha_ch"])
+ha_ch=$(fetch_column HA_Chassis_Group ha_chassis)
+check_column "$ha_ch" HA_Chassis _uuid 
 
 for chassis in gw1 gw2 hv1 hv2; do
     as $chassis
@@ -11650,8 +11602,8 @@  as hv1 ovs-ofctl dump-flows br-int table=32
 echo "--- hv2 ---"
 as hv2 ovs-ofctl dump-flows br-int table=32
 
-gw1_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw1)
-gw2_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw2)
+gw1_chassis=$(fetch_column Chassis _uuid name=gw1)
+gw2_chassis=$(fetch_column Chassis _uuid name=gw2)
 
 OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
 grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
@@ -11679,29 +11631,12 @@  OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=9 | grep arp_tpa=192.16
 ]])
 
 # check that the chassis redirect port has been claimed by the gw1 chassis
-OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
-logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1
-]])
-
-hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"`
-hv2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv2"`
-
-exp_ref_ch_list=''
-for i in `ovn-sbctl --bare --columns _uuid list chassis | sort`
-do
-    if test $i = $hv1_ch_uuid; then
-        exp_ref_ch_list="${exp_ref_ch_list}$i"
-    elif test $i = $hv2_ch_uuid; then
-        exp_ref_ch_list="${exp_ref_ch_list}$i"
-    fi
-done
-
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
+wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis
 
+hv1_ch_uuid=$(fetch_column Chassis _uuid name=hv1)
+hv2_ch_uuid=$(fetch_column Chassis _uuid name=hv2)
+exp_ref_ch_list="$hv1_ch_uuid $hv2_ch_uuid"
+wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis
 
 # at this point, we invert the priority of the gw chassis between gw1 and gw2
 
@@ -11727,9 +11662,7 @@  grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
 ])
 
 # check that the chassis redirect port has been reclaimed by the gw2 chassis
-OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
-logical_port=cr-outside | grep $gw2_chassis | wc -l], [0],[[1
-]])
+wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw2_chassis
 
 # check BFD enablement on tunnel ports from gw1 #########
 as gw1
@@ -11801,9 +11734,7 @@  grep 00:00:02:01:02:04 | wc -l], [0], [[0
 ]])
 
 # check that the chassis redirect port has been reclaimed by the gw1 chassis
-OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
-logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1
-]])
+wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis
 
 ovn-nbctl --wait=hv set NB_Global . options:"bfd-min-rx"=2000
 as gw2
@@ -11836,11 +11767,7 @@  done
 # reference to hv1.
 as hv1 ovs-vsctl del-port hv1-vif1
 
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$hv2_ch_uuid" = "$ref_ch_list"])
+wait_column "$hv2_ch_uuid" HA_Chassis_Group ref_chassis
 
 # Delete the inside2 vif.
 ovn-sbctl show
@@ -11849,19 +11776,14 @@  echo "Deleting hv2-vif1"
 as hv2 ovs-vsctl del-port hv2-vif1
 
 # ref_chassis of ha_chassis_group should be empty
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     exp_ref_ch_list=""
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
+wait_column '' HA_Chassis_Group ref_chassis
 
 # Delete the Gateway_Chassis for lrp - outside
 ovn-nbctl clear Logical_Router_Port outside gateway_chassis
 
 # There shoud be no ha_chassis_group rows in SB DB.
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis_group | wc -l`])
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
+wait_row_count HA_Chassis_Group 0
+wait_row_count HA_Chassis 0
 
 ovn-nbctl remove NB_Global . options "bfd-min-rx"
 ovn-nbctl remove NB_Global . options "bfd-min-tx"
@@ -11879,16 +11801,13 @@  ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 20
 # ovn-northd should not create HA chassis group and HA chassis rows
 # unless the HA chassis group in OVN NB DB is associated to
 # a logical router port.
-OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list ha_chassis | wc -l`])
+wait_row_count HA_Chassis 0
 
 # Associate hagrp1 to outside logical router port
 ovn-nbctl set Logical_Router_Port outside ha_chassis_group=$hagrp1_uuid
 
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid \
-find ha_chassis_group | wc -l`])
-
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl list ha_chassis | grep chassis | \
-grep -v chassis-name | wc -l`])
+wait_row_count HA_Chassis_Group 1
+wait_row_count HA_Chassis 2
 
 OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
 grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
@@ -11930,24 +11849,10 @@  for i in 1 2; do
         ofport-request=1
 done
 
-hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"`
-hv2_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv2"`
-
-exp_ref_ch_list=''
-for i in `ovn-sbctl --bare --columns _uuid list chassis | sort`
-do
-    if test $i = $hv1_ch_uuid; then
-        exp_ref_ch_list="${exp_ref_ch_list}$i"
-    elif test $i = $hv2_ch_uuid; then
-        exp_ref_ch_list="${exp_ref_ch_list}$i"
-    fi
-done
-
-OVS_WAIT_UNTIL(
-    [ref_ch_list=`ovn-sbctl --bare --columns ref_chassis find ha_chassis_group | sort`
-     # Trim the spaces.
-     ref_ch_list=`echo $ref_ch_list | sed 's/ //g'`
-     test "$exp_ref_ch_list" = "$ref_ch_list"])
+hv1_ch_uuid=$(fetch_column Chassis _uuid name=hv1)
+hv2_ch_uuid=$(fetch_column Chassis _uuid name=hv2)
+exp_ref_ch_list="$hv1_ch_uuid $hv2_ch_uuid"
+wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis
 
 # Increase the priority of gw2
 ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 40
@@ -12035,9 +11940,7 @@  grep 00:00:02:01:02:04 | wc -l], [0], [[0
 ]])
 
 # check that the chassis redirect port has been reclaimed by the gw1 chassis
-OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
-logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1
-]])
+wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis
 
 OVN_CLEANUP([gw1],[gw2],[hv1],[hv2])
 
@@ -12304,22 +12207,14 @@  gw2_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw2)
 ovn-sbctl destroy Chassis $gw2_chassis
 
 # Wait for the gw2_chassis row is recreated.
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns=_uuid find Chassis name=gw2 | wc -l`])
-
-gw1_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw1)
-gw2_chassis=$(ovn-sbctl --bare --columns=_uuid find Chassis name=gw2)
+wait_row_count Chassis 1 name=gw2
 
 # When gw2 chassis row is destroyed, it gets recreated. There
 # is a small window in which gw2 may claim the cr-outside port if
 # it has not established bfd tunnel with gw1.
 # So make sure that, cr-outside is claimed by gw1 finally.
-OVS_WAIT_WHILE(
-    [cr_outside_ch=`ovn-sbctl --bare --columns=chassis find Port_binding logical_port=cr-outside`
-     test $cr_outside_ch = $gw2_chassis])
-
-OVS_WAIT_UNTIL(
-    [cr_outside_ch=`ovn-sbctl --bare --columns=chassis find Port_binding logical_port=cr-outside`
-     test $cr_outside_ch = $gw1_chassis])
+gw1_chassis=$(fetch_column Chassis _uuid name=gw1)
+wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis
 
 OVN_CLEANUP([gw1],[gw2],[hv1])
 
@@ -12432,11 +12327,9 @@  AT_CHECK([ovn-sbctl dump-flows lr0_ip6 | grep nd_na_router | \
 wc -l], [0], [4
 ])
 
-cr_uuid=`ovn-sbctl find port_binding logical_port=cr-ip6_public | grep _uuid | cut -f2 -d ":"`
-
 # Get the redirect chassis uuid.
-chassis_uuid=`ovn-sbctl list chassis hv1 | grep _uuid | cut -f2 -d ":"`
-OVS_WAIT_UNTIL([test $chassis_uuid = `ovn-sbctl get port_binding $cr_uuid chassis`])
+chassis_uuid=$(fetch_column Chassis _uuid name=hv1)
+wait_row_count Port_Binding 1 logical_port=cr-ip6_public chassis=$chassis_uuid
 
 trim_zeros() {
     sed 's/\(00\)\{1,\}$//'
@@ -12584,10 +12477,10 @@  ovn-nbctl lsp-set-options lsp0 requested-chassis=hv1
 ovn-nbctl --wait=hv --timeout=3 sync
 
 # Retrieve hv1 and hv2 chassis UUIDs from southbound database
-ovn-sbctl wait-until chassis hv1
-ovn-sbctl wait-until chassis hv2
-hv1_uuid=$(ovn-sbctl --bare --columns _uuid find chassis name=hv1)
-hv2_uuid=$(ovn-sbctl --bare --columns _uuid find chassis name=hv2)
+wait_row_count Chassis 1 name=hv1
+wait_row_count Chassis 1 name=hv2
+hv1_uuid=$(fetch_column Chassis _uuid name=hv1)
+hv2_uuid=$(fetch_column Chassis _uuid name=hv2)
 
 # (1) Chassis hv2 should not bind lsp0 when requested-chassis is hv1.
 echo "verifying that hv2 does not bind lsp0 when hv2 physical/logical mapping is added"
@@ -12595,7 +12488,7 @@  as hv2
 ovs-vsctl set interface hv2-vif0 external-ids:iface-id=lsp0
 
 OVS_WAIT_UNTIL([test 1 = $(grep -c "Not claiming lport lsp0" hv2/ovn-controller.log)])
-AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x], [0], [])
+wait_row_count Port_Binding 1 logical_port=lsp0 'chassis=[[]]'
 
 # (2) Chassis hv2 should not add flows in OFTABLE_PHY_TO_LOG and OFTABLE_LOG_TO_PHY tables.
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], [])
@@ -12607,7 +12500,7 @@  as hv1
 ovs-vsctl set interface hv1-vif0 external-ids:iface-id=lsp0
 
 OVS_WAIT_UNTIL([test 1 = $(grep -c "Claiming lport lsp0" hv1/ovn-controller.log)])
-AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x"$hv1_uuid"], [0], [])
+check_column "$hv1_uuid" Port_Binding chassis logical_port=lsp0
 
 # (4) Chassis hv1 should add flows in OFTABLE_PHY_TO_LOG and OFTABLE_LOG_TO_PHY tables.
 as hv1 ovs-ofctl dump-flows br-int
@@ -12620,7 +12513,7 @@  echo "verifying that lsp0 binding moves when requested-chassis is changed"
 
 ovn-nbctl lsp-set-options lsp0 requested-chassis=hv2
 OVS_WAIT_UNTIL([test 1 = $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)])
-OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x"$hv2_uuid"])
+wait_column "$hv2_uuid" Port_Binding chassis logical_port=lsp0
 
 # (6) Chassis hv2 should add flows and hv1 should not.
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ignore])
@@ -12647,23 +12540,23 @@  ovs-vsctl add-br br-phys
 ovn_attach n1 br-phys 192.168.0.11
 ovs-vsctl -- add-port br-int hv1-vif0 -- set Interface hv1-vif0 ofport-request=1
 
-ovn-sbctl wait-until chassis hv1
-hv1_hostname=$(ovn-sbctl --bare --columns hostname find Chassis name=hv1)
+wait_row_count Chassis 1 name=hv1
+hv1_hostname=$(fetch Chassis hostname name=hv1)
 echo "hv1_hostname=${hv1_hostname}"
 ovn-nbctl --wait=hv --timeout=3 lsp-set-options lsp0 requested-chassis=${hv1_hostname}
 as hv1 ovs-vsctl set interface hv1-vif0 external-ids:iface-id=lsp0
 
-hv1_uuid=$(ovn-sbctl --bare --columns _uuid find Chassis name=hv1)
+hv1_uuid=$(fetch_column Chassis _uuid name=hv1)
 echo "hv1_uuid=${hv1_uuid}"
 OVS_WAIT_UNTIL([test 1 = $(grep -c "Claiming lport lsp0" hv1/ovn-controller.log)])
-AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x"$hv1_uuid"], [0], [])
+wait_column "$hv1_uuid" Port_Binding chassis logical_port=lsp0
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ignore])
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep actions=output:1], [0], [ignore])
 
 ovn-nbctl --wait=hv --timeout=3 lsp-set-options lsp0 requested-chassis=non-existant-chassis
 OVS_WAIT_UNTIL([test 1 = $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)])
 ovn-nbctl --wait=hv --timeout=3 sync
-AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding logical_port=lsp0) = x], [0], [])
+wait_column '' Port_Binding chasssi logical_port=lsp0
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], [])
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep output], [1], [])
 
@@ -13501,29 +13394,17 @@  ovn-nbctl --id=@p get Logical_Switch_Port lp3 -- add Port_Group pg2 ports @p
 ovn-nbctl --wait=sb sync
 
 dnl Check if port group address sets were populated with ports' addresses
-AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses],
-         [0], [[["10.0.0.1", "10.0.0.2"]]
-])
-AT_CHECK([ovn-sbctl get Address_Set pg2_ip4 addresses],
-         [0], [[["10.0.0.2", "10.0.0.3"]]
-])
-AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses],
-         [0], [[["2001:db8::1", "2001:db8::2"]]
-])
-AT_CHECK([ovn-sbctl get Address_Set pg2_ip6 addresses],
-         [0], [[["2001:db8::2", "2001:db8::3"]]
-])
+check_column '10.0.0.1 10.0.0.2' Address_Set addresses name=pg1_ip4
+check_column '10.0.0.2 10.0.0.3' Address_Set addresses name=pg2_ip4
+check_column '2001:db8::1 2001:db8::2' Address_Set addresses name=pg1_ip6
+check_column '2001:db8::2 2001:db8::3' Address_Set addresses name=pg2_ip6
 
 ovn-nbctl --wait=sb lsp-set-addresses lp1 \
     "02:00:00:00:00:01 10.0.0.11 2001:db8::11"
 
 dnl Check if updated address got propagated to the port group address sets
-AT_CHECK([ovn-sbctl get Address_Set pg1_ip4 addresses],
-         [0], [[["10.0.0.11", "10.0.0.2"]]
-])
-AT_CHECK([ovn-sbctl get Address_Set pg1_ip6 addresses],
-         [0], [[["2001:db8::11", "2001:db8::2"]]
-])
+check_column '10.0.0.11 10.0.0.2' Address_Set addresses name=pg1_ip4
+check_column '2001:db8::11 2001:db8::2' Address_Set addresses name=pg1_ip6
 
 AT_CLEANUP
 
@@ -16010,8 +15891,8 @@  ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.24.4.100 10.0.0.10
 ovn-nbctl lr-nat-add lr1 dnat_and_snat 172.24.4.200 20.0.0.10
 
 # Check that the MAC_Binding entries have been properly created
-OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding logical_port="lr0-pub" ip="172.24.4.200" | wc -l` -gt 0])
-OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding logical_port="lr1-pub" ip="172.24.4.100" | wc -l` -gt 0])
+wait_row_count MAC_Binding 1 logical_port=lr0-pub ip=172.24.4.200
+wait_row_count MAC_Binding 1 logical_port=lr1-pub ip=172.24.4.100
 
 # Check that the GARPs went also to the external physical network
 # Wait until at least 4 packets have arrived and copy them to a separate file as
@@ -17219,10 +17100,7 @@  send_igmp_v3_report hv1-vif1 hv1 \
     /dev/null
 
 # Check IGMP_Group table on both HV.
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" -c`
-    test "${total_entries}" = "1"
-])
+wait_row_count IGMP_Group 1 address=239.0.1.68
 
 # Send traffic and make sure it gets forwarded only on the port that joined.
 as hv1 reset_pcap_file hv1-vif1 hv1/vif1
@@ -17247,10 +17125,7 @@  OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
 
 # Flush IGMP groups.
 ovn-sbctl ip-multicast-flush sw1
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" -c`
-    test "${total_entries}" = "0"
-])
+wait_row_count IGMP_Group 0 address=239.0.1.68
 
 # Check that traffic for 224.0.0.X is flooded even if some hosts register for
 # it.
@@ -17261,10 +17136,7 @@  send_igmp_v3_report hv1-vif1 hv1 \
     /dev/null
 
 # Check that the IGMP Group is learned.
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "224.0.0.42" -c`
-    test "${total_entries}" = "1"
-])
+wait_row_count IGMP_Group 1 address=224.0.0.42
 
 # Send traffic and make sure it gets flooded to all ports.
 as hv1 reset_pcap_file hv1-vif1 hv1/vif1
@@ -17355,10 +17227,7 @@  send_igmp_v3_report hv2-vif3 hv2 \
     /dev/null
 
 # Check that the IGMP Group is learned by all switches.
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" -c`
-    test "${total_entries}" = "2"
-])
+wait_row_count IGMP_Group 2 address=239.0.1.68
 
 # Send traffic from sw3 and make sure it is relayed by rtr.
 # to ports that joined.
@@ -17845,10 +17714,7 @@  send_mld_v2_report hv2-vif1 hv2 \
     /dev/null
 
 # Check that the IP multicast group is learned on both hv.
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c`
-    test "${total_entries}" = "2"
-])
+wait_row_count IGMP_Group 2 address='"ff0a:dead:beef::1"'
 
 # Send traffic and make sure it gets forwarded only on the two ports that
 # joined.
@@ -17881,10 +17747,7 @@  send_mld_v2_report hv1-vif1 hv1 \
     /dev/null
 
 # Check IGMP_Group table on both HV.
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c`
-    test "${total_entries}" = "1"
-])
+wait_row_count IGMP_Group 1 address='"ff0a:dead:beef::1"'
 
 # Send traffic and make sure it gets forwarded only on the port that joined.
 as hv1 reset_pcap_file hv1-vif1 hv1/vif1
@@ -17913,10 +17776,7 @@  OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
 
 # Flush IP multicast groups.
 ovn-sbctl ip-multicast-flush sw1
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep " ff0a:dead:beef::1" -c`
-    test "${total_entries}" = "0"
-])
+wait_row_count IGMP_Group 0 address='"ff0a:dead:beef::1"'
 
 # Check that traffic for "all-hosts" is flooded even if some hosts register
 # for it.
@@ -17927,10 +17787,7 @@  send_mld_v2_report hv1-vif1 hv1 \
     /dev/null
 
 # Check that the Multicast Group is learned.
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "ff02::1" -c`
-    test "${total_entries}" = "1"
-])
+wait_row_count IGMP_Group 1 address='"ff02::1"'
 
 # Send traffic and make sure it gets flooded to all ports.
 as hv1 reset_pcap_file hv1-vif1 hv1/vif1
@@ -18027,10 +17884,7 @@  send_mld_v2_report hv2-vif3 hv2 \
     /dev/null
 
 # Check that the IGMP Group is learned by all switches.
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c`
-    test "${total_entries}" = "2"
-])
+wait_row_count IGMP_Group 2 address='"ff0a:dead:beef::1"'
 
 # Send traffic from sw3 and make sure it is relayed by rtr.
 # to ports that joined.
@@ -18081,10 +17935,7 @@  send_mld_v2_report hv1-vif4 hv1 \
     /dev/null
 
 # Check that the Multicast Group is learned by all switches.
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c`
-    test "${total_entries}" = "3"
-])
+wait_row_count IGMP_Group 3 address='"ff0a:dead:beef::1"'
 
 # Send traffic from sw3 and make sure it is relayed by rtr
 # to ports that joined.
@@ -18193,10 +18044,7 @@  send_mld_v2_report hv1-vif2 hv1 \
     expected_reports
 
 # Check that the IP multicast group is learned.
-OVS_WAIT_UNTIL([
-    total_entries=`ovn-sbctl find IGMP_Group | grep "ff0a:dead:beef::1" -c`
-    test "${total_entries}" = "1"
-])
+wait_row_count IGMP_Group 1 address='"ff0a:dead:beef::1"'
 
 # Send traffic from sw1-p21
 send_ip_multicast_pkt hv2-vif1 hv2 \
@@ -18917,14 +18765,14 @@  ip_to_hex() {
 #     ip - 10.0.0.30
 #     mac - 50:54:00:00:00:03
 
-AT_CHECK([test 0 = `ovn-sbctl list mac_binding | wc -l`])
+check_row_count MAC_Binding 0
 eth_src=505400000003
 eth_dst=ffffffffffff
 spa=$(ip_to_hex 10 0 0 30)
 tpa=$(ip_to_hex 10 0 0 30)
 send_garp 1 1 $eth_src $eth_dst $spa $tpa
 
-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid list mac_binding | wc -l`])
+wait_row_count MAC_Binding 1
 
 AT_CHECK([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
 list mac_binding], [0], [lr0-sw0
@@ -18964,7 +18812,7 @@  send_garp 1 1 $eth_src $eth_dst $spa $tpa
 # should be updated.
 OVS_WAIT_UNTIL([test 2 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
 
-AT_CHECK([test 1 = `ovn-sbctl --bare --columns _uuid list mac_binding | wc -l`])
+check_row_count MAC_Binding 1
 
 AT_CHECK([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
 list mac_binding], [0], [lr0-sw0
@@ -19544,8 +19392,7 @@  ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20
 OVN_POPULATE_ARP
 ovn-nbctl --wait=hv sync
 
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns _uuid find \
-service_monitor | sed '/^$/d' | wc -l`])
+wait_row_count Service_Monitor 2
 
 ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
 AT_CHECK([cat lflows.txt], [0], [dnl
@@ -19571,8 +19418,7 @@  OVS_WAIT_UNTIL(
 grep "405400000003${svc_mon_src_mac}" | wc -l`]
 )
 
-OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \
-service_monitor | grep offline | wc -l`])
+wait_row_count Service_Monitor 2 status=offline
 
 OVS_WAIT_UNTIL(
     [test 2 = `$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | \
@@ -19599,33 +19445,26 @@  AT_CHECK([cat lflows.txt], [0], [dnl
 # Delete sw0-p1
 ovn-nbctl lsp-del sw0-p1
 
-OVS_WAIT_UNTIL([test 1 = $(ovn-sbctl --bare --columns _uuid find \
-service_monitor | sed '/^$/d' | wc -l)])
+wait_row_count Service_Monitor 1
 
 # Add back sw0-p1 but without any IP address.
 ovn-nbctl lsp-add sw0 sw0-p1
 ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03" -- \
 lsp-set-port-security sw0-p1 "50:54:00:00:00:03"
 
-OVS_WAIT_UNTIL([test 2 = $(ovn-sbctl --bare --columns status find \
-service_monitor | grep offline | wc -l)])
+wait_row_count Service_Monitor 2 status=offline
 
 ovn-nbctl lsp-del sw0-p1
 ovn-nbctl lsp-del sw1-p1
-OVS_WAIT_UNTIL([test 0 = $(ovn-sbctl --bare --columns _uuid find \
-service_monitor | sed '/^$/d' | wc -l)])
+wait_row_count Service_Monitor 0
 
 # Add back sw0-p1 but without any address set.
 ovn-nbctl lsp-add sw0 sw0-p1
 
-OVS_WAIT_UNTIL([test 1 = $(ovn-sbctl --bare --columns _uuid find \
-service_monitor | sed '/^$/d' | wc -l)])
 
-OVS_WAIT_UNTIL([test 0 = $(ovn-sbctl --bare --columns status find \
-service_monitor | grep offline | wc -l)])
-
-OVS_WAIT_UNTIL([test 0 = $(ovn-sbctl --bare --columns status find \
-service_monitor | grep online | wc -l)])
+wait_row_count Service_Monitor 1
+wait_row_count Service_Monitor 0 status=offline
+wait_row_count Service_Monitor 0 status=online
 
 OVN_CLEANUP([hv1], [hv2])
 AT_CLEANUP
@@ -19736,9 +19575,7 @@  ovn-nbctl --wait=hv sync
 
 # And now for the anticlimax. We need to ensure that there is no
 # service monitor in the southbound db.
-
-AT_CHECK([test 0 = `ovn-sbctl --bare --columns _uuid find \
-service_monitor | sed '/^$/d' | wc -l`])
+check_row_count Service_Monitor 0
 
 # Let's also be sure the warning message about SCTP load balancers is
 # is in the ovn-northd log
@@ -22017,9 +21854,9 @@  as hv1 ovs-vsctl \
     -- set Interface vif1 external_ids:iface-id=lsp
 
 # Wait for port to be bound.
-OVS_WAIT_UNTIL([test $(ovn-sbctl --columns _uuid --bare list chassis hv1 | wc -l) -eq 1])
-ch=$(ovn-sbctl --columns _uuid --bare list chassis hv1)
-OVS_WAIT_UNTIL([test $(ovn-sbctl --columns chassis --bare list port_binding lsp | grep $ch -c) -eq 1])
+wait_row_count Chassis 1 name=hv1
+ch=$(fetch_column Chassis _uuid name=hv1)
+wait_row_count Port_Binding 1 logical_port=lsp chassis=$ch
 
 # Pause ovn-controller.
 as hv1 ovn-appctl -t ovn-controller debug/pause
@@ -22731,9 +22568,9 @@  as hv1 ovs-vsctl \
     -- set Interface vif1 external_ids:iface-id=sw0-p1
 
 # Wait for port to be bound.
-OVS_WAIT_UNTIL([test $(ovn-sbctl --columns _uuid --bare list chassis hv1 | wc -l) -eq 1])
-ch=$(ovn-sbctl --columns _uuid --bare list chassis hv1)
-OVS_WAIT_UNTIL([test $(ovn-sbctl --columns chassis --bare list port_binding sw0-p1 | grep $ch -c) -eq 1])
+wait_row_count Chassis 1 name=hv1
+ch=$(fetch_column Chassis _uuid name=hv1)
+wait_row_count Port_Binding 1 logical_port=sw0-p1 chassis=$ch
 
 as hv1 ovs-vsctl add-br br-temp
 as hv1 ovs-vsctl \
@@ -22743,13 +22580,13 @@  as hv1 ovs-vsctl \
 ovn-nbctl --wait=hv sync
 
 # hv1 ovn-controller should not bind sw0-p2.
-AT_CHECK([test $(ovn-sbctl --columns chassis --bare list port_binding sw0-p2 | grep $ch -c) -eq 0])
+check_row_count Port_Binding 0 logical_port=sw0-p2 chassis=$c
 
 # Trigger recompute and sw0-p2 should not be claimed.
 as hv1 ovn-appctl -t ovn-controller recompute
 ovn-nbctl --wait=hv sync
 
-AT_CHECK([test $(ovn-sbctl --columns chassis --bare list port_binding sw0-p2 | grep $ch -c) -eq 0])
+check_row_count Port_Binding 0 logical_port=sw0-p2 chassis=$ch
 
 ovn-sbctl --columns chassis --bare list port_binding sw0-p2
 
diff --git a/tests/ovs-macros.at b/tests/ovs-macros.at
index 3dcf8f96d8fe..856f5d2d7294 100644
--- a/tests/ovs-macros.at
+++ b/tests/ovs-macros.at
@@ -23,9 +23,11 @@  dnl that can ordinarily be run only within AT_SETUP...AT_CLEANUP.
 m4_define([OVS_START_SHELL_HELPERS],
   [m4_ifdef([AT_ingroup], [m4_fatal([$0: AT_SETUP and OVS_DEFINE_SHELL_HELPERS may not nest])])
    m4_define([AT_ingroup])
+   m4_define([AT_capture_files])
    m4_divert_push([PREPARE_TESTS])])
 m4_define([OVS_END_SHELL_HELPERS], [
    m4_divert_pop([PREPARE_TESTS])
+   m4_undefine([AT_capture_files])
    m4_undefine([AT_ingroup])])
 
 m4_divert_push([PREPARE_TESTS])
@@ -231,21 +233,52 @@  ovs_wait_failed () {
 ovs_wait "AS_ESCAPE([$3])" "AS_ESCAPE([$4])"
 ])
 
-dnl OVS_WAIT_UNTIL(COMMAND)
+dnl OVS_WAIT_UNTIL(COMMAND[, IF-FAILED])
 dnl
 dnl Executes shell COMMAND in a loop until it returns
 dnl zero return code.  If COMMAND did not return
 dnl zero code within reasonable time limit, then
-dnl the test fails.
+dnl the test fails.  In that case, runs IF-FAILED
+dnl before aborting.
 m4_define([OVS_WAIT_UNTIL],
   [OVS_WAIT([$1], [$2], [AT_LINE], [until $1])])
 
-dnl OVS_WAIT_WHILE(COMMAND)
+dnl OVS_WAIT_FOR_OUTPUT(COMMAND, EXIT-STATUS, STDOUT, STDERR)
+dnl
+dnl Executes shell COMMAND in a loop until it exits with status EXIT-STATUS,
+dnl prints STDOUT on stdout, and prints STDERR on stderr.  If this doesn't
+dnl happen within a reasonable time limit, then the test fails.
+m4_define([OVS_WAIT_FOR_OUTPUT], [dnl
+wait_expected_status=m4_if([$2], [], [0], [$2])
+AT_DATA([wait-expected-stdout], [$3])
+AT_DATA([wait-expected-stderr], [$4])
+ovs_wait_command() {
+    $1
+}
+ovs_wait_cond() {
+    ovs_wait_command >wait-stdout 2>wait-stderr
+    wait_status=$?
+    (test $wait_status = $wait_expected_status &&
+     $at_diff wait-expected-stdout wait-stdout &&
+     $at_diff wait-expected-stderr wait-stderr) >/dev/null 2>&1
+}
+ovs_wait_failed () {
+    if test $wait_status != $wait_expected_status; then
+        echo "exit status $wait_status != expected $wait_expected_status"
+    fi
+    $at_diff wait-expected-stdout wait-stdout
+    $at_diff wait-expected-stderr wait-stderr
+}
+ovs_wait "AS_ESCAPE([AT_LINE])" "for output from AS_ESCAPE([$1])"
+])
+    
+dnl OVS_WAIT_WHILE(COMMAND[, IF-FAILED])
 dnl
 dnl Executes shell COMMAND in a loop until it returns
 dnl non-zero return code.  If COMMAND did not return
 dnl non-zero code within reasonable time limit, then
-dnl the test fails.
+dnl the test fails.  In that case, runs IF-FAILED
+dnl before aborting.
 m4_define([OVS_WAIT_WHILE],
   [OVS_WAIT([if $1; then return 1; else return 0; fi], [$2],
             [AT_LINE], [while $1])])