diff mbox series

[2/2,NOT-FOR-MERGE] Add qtest for migration over RDMA

Message ID 20250218074345.638203-2-lizhijian@fujitsu.com (mailing list archive)
State New
Headers show
Series [1/2] migration: Prioritize RDMA in ram_save_target_page() | expand

Commit Message

Li Zhijian Feb. 18, 2025, 7:43 a.m. UTC
This qtest requirs there is RXE link in the host.

Here is an example to show how to add this RXE link:
$ ./new-rdma-link.sh
192.168.22.93

Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
---
The RDMA migration was broken again...due to lack of sufficient test/qtest.

It's urgly to add and execute a script to establish an RDMA link in
the C program. If anyone has a better suggestion, please let me know.

$ cat ./new-rdma-link.sh
get_ipv4_addr() {
        ip -4 -o addr show dev "$1" |
                sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
}

has_soft_rdma() {
        rdma link | grep -q " netdev $1[[:blank:]]*\$"
}

start_soft_rdma() {
        local type

        modprobe rdma_rxe || return $?
        type=rxe
        (
                cd /sys/class/net &&
                        for i in *; do
                                [ -e "$i" ] || continue
                                [ "$i" = "lo" ] && continue
                                [ "$(<"$i/addr_len")" = 6 ] || continue
                                [ "$(<"$i/carrier")" = 1 ] || continue
                                has_soft_rdma "$i" && break
                                rdma link add "${i}_$type" type $type netdev "$i" && break
                        done
                has_soft_rdma "$i" && echo $i
        )

}

rxe_link=$(start_soft_rdma)
[[ "$rxe_link" ]] && get_ipv4_addr $rxe_link

Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
---
 tests/qtest/migration/new-rdma-link.sh |  34 ++++++++
 tests/qtest/migration/precopy-tests.c  | 103 +++++++++++++++++++++++++
 2 files changed, 137 insertions(+)
 create mode 100644 tests/qtest/migration/new-rdma-link.sh

Comments

Fabiano Rosas Feb. 18, 2025, 9:03 p.m. UTC | #1
Li Zhijian via <qemu-devel@nongnu.org> writes:

> This qtest requirs there is RXE link in the host.
>
> Here is an example to show how to add this RXE link:
> $ ./new-rdma-link.sh
> 192.168.22.93
>
> Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
> ---
> The RDMA migration was broken again...due to lack of sufficient test/qtest.
>
> It's urgly to add and execute a script to establish an RDMA link in
> the C program. If anyone has a better suggestion, please let me know.
>
> $ cat ./new-rdma-link.sh
> get_ipv4_addr() {
>         ip -4 -o addr show dev "$1" |
>                 sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
> }
>
> has_soft_rdma() {
>         rdma link | grep -q " netdev $1[[:blank:]]*\$"
> }
>
> start_soft_rdma() {
>         local type
>
>         modprobe rdma_rxe || return $?
>         type=rxe
>         (
>                 cd /sys/class/net &&
>                         for i in *; do
>                                 [ -e "$i" ] || continue
>                                 [ "$i" = "lo" ] && continue
>                                 [ "$(<"$i/addr_len")" = 6 ] || continue
>                                 [ "$(<"$i/carrier")" = 1 ] || continue
>                                 has_soft_rdma "$i" && break
>                                 rdma link add "${i}_$type" type $type netdev "$i" && break
>                         done
>                 has_soft_rdma "$i" && echo $i
>         )
>
> }
>
> rxe_link=$(start_soft_rdma)
> [[ "$rxe_link" ]] && get_ipv4_addr $rxe_link
>
> Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
> ---
>  tests/qtest/migration/new-rdma-link.sh |  34 ++++++++
>  tests/qtest/migration/precopy-tests.c  | 103 +++++++++++++++++++++++++
>  2 files changed, 137 insertions(+)
>  create mode 100644 tests/qtest/migration/new-rdma-link.sh
>
> diff --git a/tests/qtest/migration/new-rdma-link.sh b/tests/qtest/migration/new-rdma-link.sh
> new file mode 100644
> index 00000000000..ca20594eaae
> --- /dev/null
> +++ b/tests/qtest/migration/new-rdma-link.sh
> @@ -0,0 +1,34 @@
> +#!/bin/bash
> +
> +# Copied from blktests
> +get_ipv4_addr() {
> +	ip -4 -o addr show dev "$1" |
> +		sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
> +}
> +
> +has_soft_rdma() {
> +	rdma link | grep -q " netdev $1[[:blank:]]*\$"
> +}
> +
> +start_soft_rdma() {
> +	local type
> +
> +	modprobe rdma_rxe || return $?
> +	type=rxe
> +	(
> +		cd /sys/class/net &&
> +			for i in *; do
> +				[ -e "$i" ] || continue
> +				[ "$i" = "lo" ] && continue
> +				[ "$(<"$i/addr_len")" = 6 ] || continue
> +				[ "$(<"$i/carrier")" = 1 ] || continue
> +				has_soft_rdma "$i" && break
> +				rdma link add "${i}_$type" type $type netdev "$i" && break
> +			done
> +		has_soft_rdma "$i" && echo $i
> +	)
> +
> +}
> +
> +rxe_link=$(start_soft_rdma)
> +[[ "$rxe_link" ]] && get_ipv4_addr $rxe_link
> diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c
> index 162fa695318..d2a1c9c9438 100644
> --- a/tests/qtest/migration/precopy-tests.c
> +++ b/tests/qtest/migration/precopy-tests.c
> @@ -98,6 +98,105 @@ static void test_precopy_unix_dirty_ring(void)
>      test_precopy_common(&args);
>  }
>  
> +static int new_rdma_link(char *buffer) {
> +    // Copied from blktests
> +    const char *script =
> +        "#!/bin/bash\n"
> +        "\n"
> +        "get_ipv4_addr() {\n"
> +        "    ip -4 -o addr show dev \"$1\" |\n"
> +        "    sed -n 's/.*[[:blank:]]inet[[:blank:]]*\\([^[:blank:]/]*\\).*/\\1/p'\n"
> +        "}\n"
> +        "\n"
> +        "has_soft_rdma() {\n"
> +        "    rdma link | grep -q \" netdev $1[[:blank:]]*\\$\"\n"
> +        "}\n"
> +        "\n"
> +        "start_soft_rdma() {\n"
> +        "    local type\n"
> +        "\n"
> +        "    modprobe rdma_rxe || return $?\n"
> +        "    type=rxe\n"
> +        "    (\n"
> +        "        cd /sys/class/net &&\n"
> +        "        for i in *; do\n"
> +        "            [ -e \"$i\" ] || continue\n"
> +        "            [ \"$i\" = \"lo\" ] && continue\n"
> +        "            [ \"$(<$i/addr_len)\" = 6 ] || continue\n"
> +        "            [ \"$(<$i/carrier)\" = 1 ] || continue\n"
> +        "            has_soft_rdma \"$i\" && break\n"
> +        "            rdma link add \"${i}_$type\" type $type netdev \"$i\" && break\n"
> +        "        done\n"
> +        "        has_soft_rdma \"$i\" && echo $i\n"
> +        "    )\n"
> +        "}\n"
> +        "\n"
> +        "rxe_link=$(start_soft_rdma)\n"
> +        "[[ \"$rxe_link\" ]] && get_ipv4_addr $rxe_link\n";
> +
> +    char script_filename[] = "/tmp/temp_scriptXXXXXX";
> +    int fd = mkstemp(script_filename);
> +    if (fd == -1) {
> +        perror("Failed to create temporary file");
> +        return 1;
> +    }
> +
> +    FILE *fp = fdopen(fd, "w");
> +    if (fp == NULL) {
> +        perror("Failed to open file stream");
> +        close(fd);
> +        return 1;
> +    }
> +    fprintf(fp, "%s", script);
> +    fclose(fp);
> +
> +    if (chmod(script_filename, 0700) == -1) {
> +        perror("Failed to set execute permission");
> +        return 1;
> +    }
> +
> +    FILE *pipe = popen(script_filename, "r");
> +    if (pipe == NULL) {
> +        perror("Failed to run script");
> +        return 1;
> +    }
> +
> +    int idx = 0;
> +    while (fgets(buffer + idx, 128 - idx, pipe) != NULL) {
> +        idx += strlen(buffer);
> +    }
> +    if (buffer[idx - 1] == '\n')
> +        buffer[idx - 1] = 0;
> +
> +    int status = pclose(pipe);
> +    if (status == -1) {
> +        perror("Error reported by pclose()");
> +    } else if (!WIFEXITED(status)) {
> +        printf("Script did not terminate normally\n");
> +    }
> +
> +    remove(script_filename);
> +
> +    return 0;
> +}
> +
> +static void test_precopy_rdma_plain(void)
> +{
> +    char buffer[128] = {};
> +
> +    if (new_rdma_link(buffer))
> +        return;
> +
> +    g_autofree char *uri = g_strdup_printf("rdma:%s:7777", buffer);
> +
> +    MigrateCommon args = {
> +        .listen_uri = uri,
> +        .connect_uri = uri,
> +    };
> +
> +    test_precopy_common(&args);
> +}
> +
>  static void test_precopy_tcp_plain(void)
>  {
>      MigrateCommon args = {
> @@ -968,6 +1067,10 @@ static void migration_test_add_precopy_smoke(MigrationTestEnv *env)
>                         test_multifd_tcp_uri_none);
>      migration_test_add("/migration/multifd/tcp/plain/cancel",
>                         test_multifd_tcp_cancel);
> +#ifdef CONFIG_RDMA
> +    migration_test_add("/migration/precopy/rdma/plain",
> +                       test_precopy_rdma_plain);
> +#endif
>  }
>  
>  void migration_test_add_precopy(MigrationTestEnv *env)

Thanks, that's definitely better than nothing. I'll experiment with this
locally, see if I can at least run it before sending a pull request.
Peter Xu Feb. 18, 2025, 10:40 p.m. UTC | #2
On Tue, Feb 18, 2025 at 06:03:48PM -0300, Fabiano Rosas wrote:
> Li Zhijian via <qemu-devel@nongnu.org> writes:
> 
> > This qtest requirs there is RXE link in the host.
> >
> > Here is an example to show how to add this RXE link:
> > $ ./new-rdma-link.sh
> > 192.168.22.93
> >
> > Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
> > ---
> > The RDMA migration was broken again...due to lack of sufficient test/qtest.
> >
> > It's urgly to add and execute a script to establish an RDMA link in
> > the C program. If anyone has a better suggestion, please let me know.
> >
> > $ cat ./new-rdma-link.sh
> > get_ipv4_addr() {
> >         ip -4 -o addr show dev "$1" |
> >                 sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
> > }
> >
> > has_soft_rdma() {
> >         rdma link | grep -q " netdev $1[[:blank:]]*\$"
> > }
> >
> > start_soft_rdma() {
> >         local type
> >
> >         modprobe rdma_rxe || return $?
> >         type=rxe
> >         (
> >                 cd /sys/class/net &&
> >                         for i in *; do
> >                                 [ -e "$i" ] || continue
> >                                 [ "$i" = "lo" ] && continue
> >                                 [ "$(<"$i/addr_len")" = 6 ] || continue
> >                                 [ "$(<"$i/carrier")" = 1 ] || continue
> >                                 has_soft_rdma "$i" && break
> >                                 rdma link add "${i}_$type" type $type netdev "$i" && break
> >                         done
> >                 has_soft_rdma "$i" && echo $i
> >         )
> >
> > }
> >
> > rxe_link=$(start_soft_rdma)
> > [[ "$rxe_link" ]] && get_ipv4_addr $rxe_link
> >
> > Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
> > ---
> >  tests/qtest/migration/new-rdma-link.sh |  34 ++++++++
> >  tests/qtest/migration/precopy-tests.c  | 103 +++++++++++++++++++++++++
> >  2 files changed, 137 insertions(+)
> >  create mode 100644 tests/qtest/migration/new-rdma-link.sh
> >
> > diff --git a/tests/qtest/migration/new-rdma-link.sh b/tests/qtest/migration/new-rdma-link.sh
> > new file mode 100644
> > index 00000000000..ca20594eaae
> > --- /dev/null
> > +++ b/tests/qtest/migration/new-rdma-link.sh
> > @@ -0,0 +1,34 @@
> > +#!/bin/bash
> > +
> > +# Copied from blktests
> > +get_ipv4_addr() {
> > +	ip -4 -o addr show dev "$1" |
> > +		sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
> > +}
> > +
> > +has_soft_rdma() {
> > +	rdma link | grep -q " netdev $1[[:blank:]]*\$"
> > +}
> > +
> > +start_soft_rdma() {
> > +	local type
> > +
> > +	modprobe rdma_rxe || return $?
> > +	type=rxe
> > +	(
> > +		cd /sys/class/net &&
> > +			for i in *; do
> > +				[ -e "$i" ] || continue
> > +				[ "$i" = "lo" ] && continue
> > +				[ "$(<"$i/addr_len")" = 6 ] || continue
> > +				[ "$(<"$i/carrier")" = 1 ] || continue
> > +				has_soft_rdma "$i" && break
> > +				rdma link add "${i}_$type" type $type netdev "$i" && break
> > +			done
> > +		has_soft_rdma "$i" && echo $i
> > +	)
> > +
> > +}
> > +
> > +rxe_link=$(start_soft_rdma)
> > +[[ "$rxe_link" ]] && get_ipv4_addr $rxe_link
> > diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c
> > index 162fa695318..d2a1c9c9438 100644
> > --- a/tests/qtest/migration/precopy-tests.c
> > +++ b/tests/qtest/migration/precopy-tests.c
> > @@ -98,6 +98,105 @@ static void test_precopy_unix_dirty_ring(void)
> >      test_precopy_common(&args);
> >  }
> >  
> > +static int new_rdma_link(char *buffer) {
> > +    // Copied from blktests
> > +    const char *script =
> > +        "#!/bin/bash\n"
> > +        "\n"
> > +        "get_ipv4_addr() {\n"
> > +        "    ip -4 -o addr show dev \"$1\" |\n"
> > +        "    sed -n 's/.*[[:blank:]]inet[[:blank:]]*\\([^[:blank:]/]*\\).*/\\1/p'\n"
> > +        "}\n"
> > +        "\n"
> > +        "has_soft_rdma() {\n"
> > +        "    rdma link | grep -q \" netdev $1[[:blank:]]*\\$\"\n"
> > +        "}\n"
> > +        "\n"
> > +        "start_soft_rdma() {\n"
> > +        "    local type\n"
> > +        "\n"
> > +        "    modprobe rdma_rxe || return $?\n"
> > +        "    type=rxe\n"
> > +        "    (\n"
> > +        "        cd /sys/class/net &&\n"
> > +        "        for i in *; do\n"
> > +        "            [ -e \"$i\" ] || continue\n"
> > +        "            [ \"$i\" = \"lo\" ] && continue\n"
> > +        "            [ \"$(<$i/addr_len)\" = 6 ] || continue\n"
> > +        "            [ \"$(<$i/carrier)\" = 1 ] || continue\n"
> > +        "            has_soft_rdma \"$i\" && break\n"
> > +        "            rdma link add \"${i}_$type\" type $type netdev \"$i\" && break\n"
> > +        "        done\n"
> > +        "        has_soft_rdma \"$i\" && echo $i\n"
> > +        "    )\n"
> > +        "}\n"
> > +        "\n"
> > +        "rxe_link=$(start_soft_rdma)\n"
> > +        "[[ \"$rxe_link\" ]] && get_ipv4_addr $rxe_link\n";
> > +
> > +    char script_filename[] = "/tmp/temp_scriptXXXXXX";
> > +    int fd = mkstemp(script_filename);
> > +    if (fd == -1) {
> > +        perror("Failed to create temporary file");
> > +        return 1;
> > +    }
> > +
> > +    FILE *fp = fdopen(fd, "w");
> > +    if (fp == NULL) {
> > +        perror("Failed to open file stream");
> > +        close(fd);
> > +        return 1;
> > +    }
> > +    fprintf(fp, "%s", script);
> > +    fclose(fp);
> > +
> > +    if (chmod(script_filename, 0700) == -1) {
> > +        perror("Failed to set execute permission");
> > +        return 1;
> > +    }
> > +
> > +    FILE *pipe = popen(script_filename, "r");
> > +    if (pipe == NULL) {
> > +        perror("Failed to run script");
> > +        return 1;
> > +    }
> > +
> > +    int idx = 0;
> > +    while (fgets(buffer + idx, 128 - idx, pipe) != NULL) {
> > +        idx += strlen(buffer);
> > +    }
> > +    if (buffer[idx - 1] == '\n')
> > +        buffer[idx - 1] = 0;
> > +
> > +    int status = pclose(pipe);
> > +    if (status == -1) {
> > +        perror("Error reported by pclose()");
> > +    } else if (!WIFEXITED(status)) {
> > +        printf("Script did not terminate normally\n");
> > +    }
> > +
> > +    remove(script_filename);

The script can be put separately instead if hard-coded here, right?

> > +
> > +    return 0;
> > +}
> > +
> > +static void test_precopy_rdma_plain(void)
> > +{
> > +    char buffer[128] = {};
> > +
> > +    if (new_rdma_link(buffer))
> > +        return;
> > +
> > +    g_autofree char *uri = g_strdup_printf("rdma:%s:7777", buffer);
> > +
> > +    MigrateCommon args = {
> > +        .listen_uri = uri,
> > +        .connect_uri = uri,
> > +    };
> > +
> > +    test_precopy_common(&args);
> > +}
> > +
> >  static void test_precopy_tcp_plain(void)
> >  {
> >      MigrateCommon args = {
> > @@ -968,6 +1067,10 @@ static void migration_test_add_precopy_smoke(MigrationTestEnv *env)
> >                         test_multifd_tcp_uri_none);
> >      migration_test_add("/migration/multifd/tcp/plain/cancel",
> >                         test_multifd_tcp_cancel);
> > +#ifdef CONFIG_RDMA
> > +    migration_test_add("/migration/precopy/rdma/plain",
> > +                       test_precopy_rdma_plain);
> > +#endif
> >  }
> >  
> >  void migration_test_add_precopy(MigrationTestEnv *env)
> 
> Thanks, that's definitely better than nothing. I'll experiment with this
> locally, see if I can at least run it before sending a pull request.

With your newly added --full, IIUC we can add whatever we want there.
E.g. we can add --rdma and iff specified, migration-test adds the rdma test.

Or.. skip the test when the rdma link isn't available.

If we could separate the script into a file, it'll be better.  We could
create scripts/migration dir and put all migration scripts over there, then
in the test it tries to detect rdma link and fetch the ip only (aka, we'd
better keep the test itself not rely on root permission..).
Zhijian Li (Fujitsu)" via Feb. 19, 2025, 5:33 a.m. UTC | #3
On 19/02/2025 06:40, Peter Xu wrote:
> On Tue, Feb 18, 2025 at 06:03:48PM -0300, Fabiano Rosas wrote:
>> Li Zhijian via <qemu-devel@nongnu.org> writes:
>>
>>> This qtest requirs there is RXE link in the host.
>>>
>>> Here is an example to show how to add this RXE link:
>>> $ ./new-rdma-link.sh
>>> 192.168.22.93
>>>
>>> Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
>>> ---
>>> The RDMA migration was broken again...due to lack of sufficient test/qtest.
>>>
>>> It's urgly to add and execute a script to establish an RDMA link in
>>> the C program. If anyone has a better suggestion, please let me know.
>>>
>>> $ cat ./new-rdma-link.sh
>>> get_ipv4_addr() {
>>>          ip -4 -o addr show dev "$1" |
>>>                  sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
>>> }
>>>
>>> has_soft_rdma() {
>>>          rdma link | grep -q " netdev $1[[:blank:]]*\$"
>>> }
>>>
>>> start_soft_rdma() {
>>>          local type
>>>
>>>          modprobe rdma_rxe || return $?
>>>          type=rxe
>>>          (
>>>                  cd /sys/class/net &&
>>>                          for i in *; do
>>>                                  [ -e "$i" ] || continue
>>>                                  [ "$i" = "lo" ] && continue
>>>                                  [ "$(<"$i/addr_len")" = 6 ] || continue
>>>                                  [ "$(<"$i/carrier")" = 1 ] || continue
>>>                                  has_soft_rdma "$i" && break
>>>                                  rdma link add "${i}_$type" type $type netdev "$i" && break
>>>                          done
>>>                  has_soft_rdma "$i" && echo $i
>>>          )
>>>
>>> }
>>>
>>> rxe_link=$(start_soft_rdma)
>>> [[ "$rxe_link" ]] && get_ipv4_addr $rxe_link
>>>
>>> Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
>>> ---
>>>   tests/qtest/migration/new-rdma-link.sh |  34 ++++++++
>>>   tests/qtest/migration/precopy-tests.c  | 103 +++++++++++++++++++++++++
>>>   2 files changed, 137 insertions(+)
>>>   create mode 100644 tests/qtest/migration/new-rdma-link.sh
>>>
>>> diff --git a/tests/qtest/migration/new-rdma-link.sh b/tests/qtest/migration/new-rdma-link.sh
>>> new file mode 100644
>>> index 00000000000..ca20594eaae
>>> --- /dev/null
>>> +++ b/tests/qtest/migration/new-rdma-link.sh
>>> @@ -0,0 +1,34 @@
>>> +#!/bin/bash
>>> +
>>> +# Copied from blktests
>>> +get_ipv4_addr() {
>>> +	ip -4 -o addr show dev "$1" |
>>> +		sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
>>> +}
>>> +
>>> +has_soft_rdma() {
>>> +	rdma link | grep -q " netdev $1[[:blank:]]*\$"
>>> +}
>>> +
>>> +start_soft_rdma() {
>>> +	local type
>>> +
>>> +	modprobe rdma_rxe || return $?
>>> +	type=rxe
>>> +	(
>>> +		cd /sys/class/net &&
>>> +			for i in *; do
>>> +				[ -e "$i" ] || continue
>>> +				[ "$i" = "lo" ] && continue
>>> +				[ "$(<"$i/addr_len")" = 6 ] || continue
>>> +				[ "$(<"$i/carrier")" = 1 ] || continue
>>> +				has_soft_rdma "$i" && break
>>> +				rdma link add "${i}_$type" type $type netdev "$i" && break
>>> +			done
>>> +		has_soft_rdma "$i" && echo $i
>>> +	)
>>> +
>>> +}
>>> +
>>> +rxe_link=$(start_soft_rdma)
>>> +[[ "$rxe_link" ]] && get_ipv4_addr $rxe_link
>>> diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c
>>> index 162fa695318..d2a1c9c9438 100644
>>> --- a/tests/qtest/migration/precopy-tests.c
>>> +++ b/tests/qtest/migration/precopy-tests.c
>>> @@ -98,6 +98,105 @@ static void test_precopy_unix_dirty_ring(void)
>>>       test_precopy_common(&args);
>>>   }
>>>   
>>> +static int new_rdma_link(char *buffer) {
>>> +    // Copied from blktests
>>> +    const char *script =
>>> +        "#!/bin/bash\n"
>>> +        "\n"
>>> +        "get_ipv4_addr() {\n"
>>> +        "    ip -4 -o addr show dev \"$1\" |\n"
>>> +        "    sed -n 's/.*[[:blank:]]inet[[:blank:]]*\\([^[:blank:]/]*\\).*/\\1/p'\n"
>>> +        "}\n"
>>> +        "\n"
>>> +        "has_soft_rdma() {\n"
>>> +        "    rdma link | grep -q \" netdev $1[[:blank:]]*\\$\"\n"
>>> +        "}\n"
>>> +        "\n"
>>> +        "start_soft_rdma() {\n"
>>> +        "    local type\n"
>>> +        "\n"
>>> +        "    modprobe rdma_rxe || return $?\n"
>>> +        "    type=rxe\n"
>>> +        "    (\n"
>>> +        "        cd /sys/class/net &&\n"
>>> +        "        for i in *; do\n"
>>> +        "            [ -e \"$i\" ] || continue\n"
>>> +        "            [ \"$i\" = \"lo\" ] && continue\n"
>>> +        "            [ \"$(<$i/addr_len)\" = 6 ] || continue\n"
>>> +        "            [ \"$(<$i/carrier)\" = 1 ] || continue\n"
>>> +        "            has_soft_rdma \"$i\" && break\n"
>>> +        "            rdma link add \"${i}_$type\" type $type netdev \"$i\" && break\n"
>>> +        "        done\n"
>>> +        "        has_soft_rdma \"$i\" && echo $i\n"
>>> +        "    )\n"
>>> +        "}\n"
>>> +        "\n"
>>> +        "rxe_link=$(start_soft_rdma)\n"
>>> +        "[[ \"$rxe_link\" ]] && get_ipv4_addr $rxe_link\n";
>>> +
>>> +    char script_filename[] = "/tmp/temp_scriptXXXXXX";
>>> +    int fd = mkstemp(script_filename);
>>> +    if (fd == -1) {
>>> +        perror("Failed to create temporary file");
>>> +        return 1;
>>> +    }
>>> +
>>> +    FILE *fp = fdopen(fd, "w");
>>> +    if (fp == NULL) {
>>> +        perror("Failed to open file stream");
>>> +        close(fd);
>>> +        return 1;
>>> +    }
>>> +    fprintf(fp, "%s", script);
>>> +    fclose(fp);
>>> +
>>> +    if (chmod(script_filename, 0700) == -1) {
>>> +        perror("Failed to set execute permission");
>>> +        return 1;
>>> +    }
>>> +
>>> +    FILE *pipe = popen(script_filename, "r");
>>> +    if (pipe == NULL) {
>>> +        perror("Failed to run script");
>>> +        return 1;
>>> +    }
>>> +
>>> +    int idx = 0;
>>> +    while (fgets(buffer + idx, 128 - idx, pipe) != NULL) {
>>> +        idx += strlen(buffer);
>>> +    }
>>> +    if (buffer[idx - 1] == '\n')
>>> +        buffer[idx - 1] = 0;
>>> +
>>> +    int status = pclose(pipe);
>>> +    if (status == -1) {
>>> +        perror("Error reported by pclose()");
>>> +    } else if (!WIFEXITED(status)) {
>>> +        printf("Script did not terminate normally\n");
>>> +    }
>>> +
>>> +    remove(script_filename);
> 
> The script can be put separately instead if hard-coded here, right?


Sure, If so, I wonder whether the migration-test program is able to know where is this script?


> 
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static void test_precopy_rdma_plain(void)
>>> +{
>>> +    char buffer[128] = {};
>>> +
>>> +    if (new_rdma_link(buffer))
>>> +        return;
>>> +
>>> +    g_autofree char *uri = g_strdup_printf("rdma:%s:7777", buffer);
>>> +
>>> +    MigrateCommon args = {
>>> +        .listen_uri = uri,
>>> +        .connect_uri = uri,
>>> +    };
>>> +
>>> +    test_precopy_common(&args);
>>> +}
>>> +
>>>   static void test_precopy_tcp_plain(void)
>>>   {
>>>       MigrateCommon args = {
>>> @@ -968,6 +1067,10 @@ static void migration_test_add_precopy_smoke(MigrationTestEnv *env)
>>>                          test_multifd_tcp_uri_none);
>>>       migration_test_add("/migration/multifd/tcp/plain/cancel",
>>>                          test_multifd_tcp_cancel);
>>> +#ifdef CONFIG_RDMA
>>> +    migration_test_add("/migration/precopy/rdma/plain",
>>> +                       test_precopy_rdma_plain);
>>> +#endif
>>>   }
>>>   
>>>   void migration_test_add_precopy(MigrationTestEnv *env)
>>
>> Thanks, that's definitely better than nothing. I'll experiment with this
>> locally, see if I can at least run it before sending a pull request.
> 
> With your newly added --full, IIUC we can add whatever we want there.
> E.g. we can add --rdma and iff specified, migration-test adds the rdma test.
> 
> Or.. skip the test when the rdma link isn't available.
> 
> If we could separate the script into a file, it'll be better.  We could
> create scripts/migration dir and put all migration scripts over there,

We have any other existing script? I didn't find it in current QEMU tree.


> then
> in the test it tries to detect rdma link and fetch the ip only

It should work without root permission if we just *detect* and *fetch ip*.

Do you also mean we can split new-rdma-link.sh to 2 separate scripts
- add-rdma-link.sh # optionally, execute by user before the test (require root permission)
- detect-fetch-rdma.sh # execute from the migration-test

Thanks
Zhijian


> (aka, we'd
> better keep the test itself not rely on root permission..).
>
Peter Xu Feb. 19, 2025, 12:47 p.m. UTC | #4
On Wed, Feb 19, 2025 at 05:33:26AM +0000, Zhijian Li (Fujitsu) wrote:
> 
> 
> On 19/02/2025 06:40, Peter Xu wrote:
> > On Tue, Feb 18, 2025 at 06:03:48PM -0300, Fabiano Rosas wrote:
> >> Li Zhijian via <qemu-devel@nongnu.org> writes:
> >>
> >>> This qtest requirs there is RXE link in the host.
> >>>
> >>> Here is an example to show how to add this RXE link:
> >>> $ ./new-rdma-link.sh
> >>> 192.168.22.93
> >>>
> >>> Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
> >>> ---
> >>> The RDMA migration was broken again...due to lack of sufficient test/qtest.
> >>>
> >>> It's urgly to add and execute a script to establish an RDMA link in
> >>> the C program. If anyone has a better suggestion, please let me know.
> >>>
> >>> $ cat ./new-rdma-link.sh
> >>> get_ipv4_addr() {
> >>>          ip -4 -o addr show dev "$1" |
> >>>                  sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
> >>> }
> >>>
> >>> has_soft_rdma() {
> >>>          rdma link | grep -q " netdev $1[[:blank:]]*\$"
> >>> }
> >>>
> >>> start_soft_rdma() {
> >>>          local type
> >>>
> >>>          modprobe rdma_rxe || return $?
> >>>          type=rxe
> >>>          (
> >>>                  cd /sys/class/net &&
> >>>                          for i in *; do
> >>>                                  [ -e "$i" ] || continue
> >>>                                  [ "$i" = "lo" ] && continue
> >>>                                  [ "$(<"$i/addr_len")" = 6 ] || continue
> >>>                                  [ "$(<"$i/carrier")" = 1 ] || continue
> >>>                                  has_soft_rdma "$i" && break
> >>>                                  rdma link add "${i}_$type" type $type netdev "$i" && break
> >>>                          done
> >>>                  has_soft_rdma "$i" && echo $i
> >>>          )
> >>>
> >>> }
> >>>
> >>> rxe_link=$(start_soft_rdma)
> >>> [[ "$rxe_link" ]] && get_ipv4_addr $rxe_link
> >>>
> >>> Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
> >>> ---
> >>>   tests/qtest/migration/new-rdma-link.sh |  34 ++++++++
> >>>   tests/qtest/migration/precopy-tests.c  | 103 +++++++++++++++++++++++++
> >>>   2 files changed, 137 insertions(+)
> >>>   create mode 100644 tests/qtest/migration/new-rdma-link.sh
> >>>
> >>> diff --git a/tests/qtest/migration/new-rdma-link.sh b/tests/qtest/migration/new-rdma-link.sh
> >>> new file mode 100644
> >>> index 00000000000..ca20594eaae
> >>> --- /dev/null
> >>> +++ b/tests/qtest/migration/new-rdma-link.sh
> >>> @@ -0,0 +1,34 @@
> >>> +#!/bin/bash
> >>> +
> >>> +# Copied from blktests
> >>> +get_ipv4_addr() {
> >>> +	ip -4 -o addr show dev "$1" |
> >>> +		sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
> >>> +}
> >>> +
> >>> +has_soft_rdma() {
> >>> +	rdma link | grep -q " netdev $1[[:blank:]]*\$"
> >>> +}
> >>> +
> >>> +start_soft_rdma() {
> >>> +	local type
> >>> +
> >>> +	modprobe rdma_rxe || return $?
> >>> +	type=rxe
> >>> +	(
> >>> +		cd /sys/class/net &&
> >>> +			for i in *; do
> >>> +				[ -e "$i" ] || continue
> >>> +				[ "$i" = "lo" ] && continue
> >>> +				[ "$(<"$i/addr_len")" = 6 ] || continue
> >>> +				[ "$(<"$i/carrier")" = 1 ] || continue
> >>> +				has_soft_rdma "$i" && break
> >>> +				rdma link add "${i}_$type" type $type netdev "$i" && break
> >>> +			done
> >>> +		has_soft_rdma "$i" && echo $i
> >>> +	)
> >>> +
> >>> +}
> >>> +
> >>> +rxe_link=$(start_soft_rdma)
> >>> +[[ "$rxe_link" ]] && get_ipv4_addr $rxe_link
> >>> diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c
> >>> index 162fa695318..d2a1c9c9438 100644
> >>> --- a/tests/qtest/migration/precopy-tests.c
> >>> +++ b/tests/qtest/migration/precopy-tests.c
> >>> @@ -98,6 +98,105 @@ static void test_precopy_unix_dirty_ring(void)
> >>>       test_precopy_common(&args);
> >>>   }
> >>>   
> >>> +static int new_rdma_link(char *buffer) {
> >>> +    // Copied from blktests
> >>> +    const char *script =
> >>> +        "#!/bin/bash\n"
> >>> +        "\n"
> >>> +        "get_ipv4_addr() {\n"
> >>> +        "    ip -4 -o addr show dev \"$1\" |\n"
> >>> +        "    sed -n 's/.*[[:blank:]]inet[[:blank:]]*\\([^[:blank:]/]*\\).*/\\1/p'\n"
> >>> +        "}\n"
> >>> +        "\n"
> >>> +        "has_soft_rdma() {\n"
> >>> +        "    rdma link | grep -q \" netdev $1[[:blank:]]*\\$\"\n"
> >>> +        "}\n"
> >>> +        "\n"
> >>> +        "start_soft_rdma() {\n"
> >>> +        "    local type\n"
> >>> +        "\n"
> >>> +        "    modprobe rdma_rxe || return $?\n"
> >>> +        "    type=rxe\n"
> >>> +        "    (\n"
> >>> +        "        cd /sys/class/net &&\n"
> >>> +        "        for i in *; do\n"
> >>> +        "            [ -e \"$i\" ] || continue\n"
> >>> +        "            [ \"$i\" = \"lo\" ] && continue\n"
> >>> +        "            [ \"$(<$i/addr_len)\" = 6 ] || continue\n"
> >>> +        "            [ \"$(<$i/carrier)\" = 1 ] || continue\n"
> >>> +        "            has_soft_rdma \"$i\" && break\n"
> >>> +        "            rdma link add \"${i}_$type\" type $type netdev \"$i\" && break\n"
> >>> +        "        done\n"
> >>> +        "        has_soft_rdma \"$i\" && echo $i\n"
> >>> +        "    )\n"
> >>> +        "}\n"
> >>> +        "\n"
> >>> +        "rxe_link=$(start_soft_rdma)\n"
> >>> +        "[[ \"$rxe_link\" ]] && get_ipv4_addr $rxe_link\n";
> >>> +
> >>> +    char script_filename[] = "/tmp/temp_scriptXXXXXX";
> >>> +    int fd = mkstemp(script_filename);
> >>> +    if (fd == -1) {
> >>> +        perror("Failed to create temporary file");
> >>> +        return 1;
> >>> +    }
> >>> +
> >>> +    FILE *fp = fdopen(fd, "w");
> >>> +    if (fp == NULL) {
> >>> +        perror("Failed to open file stream");
> >>> +        close(fd);
> >>> +        return 1;
> >>> +    }
> >>> +    fprintf(fp, "%s", script);
> >>> +    fclose(fp);
> >>> +
> >>> +    if (chmod(script_filename, 0700) == -1) {
> >>> +        perror("Failed to set execute permission");
> >>> +        return 1;
> >>> +    }
> >>> +
> >>> +    FILE *pipe = popen(script_filename, "r");
> >>> +    if (pipe == NULL) {
> >>> +        perror("Failed to run script");
> >>> +        return 1;
> >>> +    }
> >>> +
> >>> +    int idx = 0;
> >>> +    while (fgets(buffer + idx, 128 - idx, pipe) != NULL) {
> >>> +        idx += strlen(buffer);
> >>> +    }
> >>> +    if (buffer[idx - 1] == '\n')
> >>> +        buffer[idx - 1] = 0;
> >>> +
> >>> +    int status = pclose(pipe);
> >>> +    if (status == -1) {
> >>> +        perror("Error reported by pclose()");
> >>> +    } else if (!WIFEXITED(status)) {
> >>> +        printf("Script did not terminate normally\n");
> >>> +    }
> >>> +
> >>> +    remove(script_filename);
> > 
> > The script can be put separately instead if hard-coded here, right?
> 
> 
> Sure, If so, I wonder whether the migration-test program is able to know where is this script?
> 
> 
> > 
> >>> +
> >>> +    return 0;
> >>> +}
> >>> +
> >>> +static void test_precopy_rdma_plain(void)
> >>> +{
> >>> +    char buffer[128] = {};
> >>> +
> >>> +    if (new_rdma_link(buffer))
> >>> +        return;
> >>> +
> >>> +    g_autofree char *uri = g_strdup_printf("rdma:%s:7777", buffer);
> >>> +
> >>> +    MigrateCommon args = {
> >>> +        .listen_uri = uri,
> >>> +        .connect_uri = uri,
> >>> +    };
> >>> +
> >>> +    test_precopy_common(&args);
> >>> +}
> >>> +
> >>>   static void test_precopy_tcp_plain(void)
> >>>   {
> >>>       MigrateCommon args = {
> >>> @@ -968,6 +1067,10 @@ static void migration_test_add_precopy_smoke(MigrationTestEnv *env)
> >>>                          test_multifd_tcp_uri_none);
> >>>       migration_test_add("/migration/multifd/tcp/plain/cancel",
> >>>                          test_multifd_tcp_cancel);
> >>> +#ifdef CONFIG_RDMA
> >>> +    migration_test_add("/migration/precopy/rdma/plain",
> >>> +                       test_precopy_rdma_plain);
> >>> +#endif
> >>>   }
> >>>   
> >>>   void migration_test_add_precopy(MigrationTestEnv *env)
> >>
> >> Thanks, that's definitely better than nothing. I'll experiment with this
> >> locally, see if I can at least run it before sending a pull request.
> > 
> > With your newly added --full, IIUC we can add whatever we want there.
> > E.g. we can add --rdma and iff specified, migration-test adds the rdma test.
> > 
> > Or.. skip the test when the rdma link isn't available.
> > 
> > If we could separate the script into a file, it'll be better.  We could
> > create scripts/migration dir and put all migration scripts over there,
> 
> We have any other existing script? I didn't find it in current QEMU tree.

We have a few that I'm aware of:

  - analyze-migration.py
  - vmstate-static-checker.py
  - userfaultfd-wrlat.py

> 
> 
> > then
> > in the test it tries to detect rdma link and fetch the ip only
> 
> It should work without root permission if we just *detect* and *fetch ip*.
> 
> Do you also mean we can split new-rdma-link.sh to 2 separate scripts
> - add-rdma-link.sh # optionally, execute by user before the test (require root permission)
> - detect-fetch-rdma.sh # execute from the migration-test

Hmm indeed we still need a script to scan over all the ports..

If having --rdma is a good idea, maybe we can further make it a parameter
to --rdma?

  $ migration-test --rdma $RDMA_IP

Or:

  $ migration-test --rdma-ip $RDMA_IP

Then maybe migration-test can directly take that IP and run the tests,
assuming the admin setup the rdma link.  Then we keep that one script.

Or I assume it's still ok that the test requires root only for --rdma, then
invoke the script directly in the test.  If so, we'd better also remove the
rdma link after test finished, so no side effect of the test (modprobe is
probably fine).

We can wait and see how far Fabiano went with this, and also his opinion.
Fabiano Rosas Feb. 19, 2025, 1:20 p.m. UTC | #5
Peter Xu <peterx@redhat.com> writes:

> On Wed, Feb 19, 2025 at 05:33:26AM +0000, Zhijian Li (Fujitsu) wrote:
>> 
>> 
>> On 19/02/2025 06:40, Peter Xu wrote:
>> > On Tue, Feb 18, 2025 at 06:03:48PM -0300, Fabiano Rosas wrote:
>> >> Li Zhijian via <qemu-devel@nongnu.org> writes:
>> >>
>> >>> This qtest requirs there is RXE link in the host.
>> >>>
>> >>> Here is an example to show how to add this RXE link:
>> >>> $ ./new-rdma-link.sh
>> >>> 192.168.22.93
>> >>>
>> >>> Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
>> >>> ---
>> >>> The RDMA migration was broken again...due to lack of sufficient test/qtest.
>> >>>
>> >>> It's urgly to add and execute a script to establish an RDMA link in
>> >>> the C program. If anyone has a better suggestion, please let me know.
>> >>>
>> >>> $ cat ./new-rdma-link.sh
>> >>> get_ipv4_addr() {
>> >>>          ip -4 -o addr show dev "$1" |
>> >>>                  sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
>> >>> }
>> >>>
>> >>> has_soft_rdma() {
>> >>>          rdma link | grep -q " netdev $1[[:blank:]]*\$"
>> >>> }
>> >>>
>> >>> start_soft_rdma() {
>> >>>          local type
>> >>>
>> >>>          modprobe rdma_rxe || return $?
>> >>>          type=rxe
>> >>>          (
>> >>>                  cd /sys/class/net &&
>> >>>                          for i in *; do
>> >>>                                  [ -e "$i" ] || continue
>> >>>                                  [ "$i" = "lo" ] && continue
>> >>>                                  [ "$(<"$i/addr_len")" = 6 ] || continue
>> >>>                                  [ "$(<"$i/carrier")" = 1 ] || continue
>> >>>                                  has_soft_rdma "$i" && break
>> >>>                                  rdma link add "${i}_$type" type $type netdev "$i" && break
>> >>>                          done
>> >>>                  has_soft_rdma "$i" && echo $i
>> >>>          )
>> >>>
>> >>> }
>> >>>
>> >>> rxe_link=$(start_soft_rdma)
>> >>> [[ "$rxe_link" ]] && get_ipv4_addr $rxe_link
>> >>>
>> >>> Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
>> >>> ---
>> >>>   tests/qtest/migration/new-rdma-link.sh |  34 ++++++++
>> >>>   tests/qtest/migration/precopy-tests.c  | 103 +++++++++++++++++++++++++
>> >>>   2 files changed, 137 insertions(+)
>> >>>   create mode 100644 tests/qtest/migration/new-rdma-link.sh
>> >>>
>> >>> diff --git a/tests/qtest/migration/new-rdma-link.sh b/tests/qtest/migration/new-rdma-link.sh
>> >>> new file mode 100644
>> >>> index 00000000000..ca20594eaae
>> >>> --- /dev/null
>> >>> +++ b/tests/qtest/migration/new-rdma-link.sh
>> >>> @@ -0,0 +1,34 @@
>> >>> +#!/bin/bash
>> >>> +
>> >>> +# Copied from blktests
>> >>> +get_ipv4_addr() {
>> >>> +	ip -4 -o addr show dev "$1" |
>> >>> +		sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
>> >>> +}
>> >>> +
>> >>> +has_soft_rdma() {
>> >>> +	rdma link | grep -q " netdev $1[[:blank:]]*\$"
>> >>> +}
>> >>> +
>> >>> +start_soft_rdma() {
>> >>> +	local type
>> >>> +
>> >>> +	modprobe rdma_rxe || return $?
>> >>> +	type=rxe
>> >>> +	(
>> >>> +		cd /sys/class/net &&
>> >>> +			for i in *; do
>> >>> +				[ -e "$i" ] || continue
>> >>> +				[ "$i" = "lo" ] && continue
>> >>> +				[ "$(<"$i/addr_len")" = 6 ] || continue
>> >>> +				[ "$(<"$i/carrier")" = 1 ] || continue
>> >>> +				has_soft_rdma "$i" && break
>> >>> +				rdma link add "${i}_$type" type $type netdev "$i" && break
>> >>> +			done
>> >>> +		has_soft_rdma "$i" && echo $i
>> >>> +	)
>> >>> +
>> >>> +}
>> >>> +
>> >>> +rxe_link=$(start_soft_rdma)
>> >>> +[[ "$rxe_link" ]] && get_ipv4_addr $rxe_link
>> >>> diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c
>> >>> index 162fa695318..d2a1c9c9438 100644
>> >>> --- a/tests/qtest/migration/precopy-tests.c
>> >>> +++ b/tests/qtest/migration/precopy-tests.c
>> >>> @@ -98,6 +98,105 @@ static void test_precopy_unix_dirty_ring(void)
>> >>>       test_precopy_common(&args);
>> >>>   }
>> >>>   
>> >>> +static int new_rdma_link(char *buffer) {
>> >>> +    // Copied from blktests
>> >>> +    const char *script =
>> >>> +        "#!/bin/bash\n"
>> >>> +        "\n"
>> >>> +        "get_ipv4_addr() {\n"
>> >>> +        "    ip -4 -o addr show dev \"$1\" |\n"
>> >>> +        "    sed -n 's/.*[[:blank:]]inet[[:blank:]]*\\([^[:blank:]/]*\\).*/\\1/p'\n"
>> >>> +        "}\n"
>> >>> +        "\n"
>> >>> +        "has_soft_rdma() {\n"
>> >>> +        "    rdma link | grep -q \" netdev $1[[:blank:]]*\\$\"\n"
>> >>> +        "}\n"
>> >>> +        "\n"
>> >>> +        "start_soft_rdma() {\n"
>> >>> +        "    local type\n"
>> >>> +        "\n"
>> >>> +        "    modprobe rdma_rxe || return $?\n"
>> >>> +        "    type=rxe\n"
>> >>> +        "    (\n"
>> >>> +        "        cd /sys/class/net &&\n"
>> >>> +        "        for i in *; do\n"
>> >>> +        "            [ -e \"$i\" ] || continue\n"
>> >>> +        "            [ \"$i\" = \"lo\" ] && continue\n"
>> >>> +        "            [ \"$(<$i/addr_len)\" = 6 ] || continue\n"
>> >>> +        "            [ \"$(<$i/carrier)\" = 1 ] || continue\n"
>> >>> +        "            has_soft_rdma \"$i\" && break\n"
>> >>> +        "            rdma link add \"${i}_$type\" type $type netdev \"$i\" && break\n"
>> >>> +        "        done\n"
>> >>> +        "        has_soft_rdma \"$i\" && echo $i\n"
>> >>> +        "    )\n"
>> >>> +        "}\n"
>> >>> +        "\n"
>> >>> +        "rxe_link=$(start_soft_rdma)\n"
>> >>> +        "[[ \"$rxe_link\" ]] && get_ipv4_addr $rxe_link\n";
>> >>> +
>> >>> +    char script_filename[] = "/tmp/temp_scriptXXXXXX";
>> >>> +    int fd = mkstemp(script_filename);
>> >>> +    if (fd == -1) {
>> >>> +        perror("Failed to create temporary file");
>> >>> +        return 1;
>> >>> +    }
>> >>> +
>> >>> +    FILE *fp = fdopen(fd, "w");
>> >>> +    if (fp == NULL) {
>> >>> +        perror("Failed to open file stream");
>> >>> +        close(fd);
>> >>> +        return 1;
>> >>> +    }
>> >>> +    fprintf(fp, "%s", script);
>> >>> +    fclose(fp);
>> >>> +
>> >>> +    if (chmod(script_filename, 0700) == -1) {
>> >>> +        perror("Failed to set execute permission");
>> >>> +        return 1;
>> >>> +    }
>> >>> +
>> >>> +    FILE *pipe = popen(script_filename, "r");
>> >>> +    if (pipe == NULL) {
>> >>> +        perror("Failed to run script");
>> >>> +        return 1;
>> >>> +    }
>> >>> +
>> >>> +    int idx = 0;
>> >>> +    while (fgets(buffer + idx, 128 - idx, pipe) != NULL) {
>> >>> +        idx += strlen(buffer);
>> >>> +    }
>> >>> +    if (buffer[idx - 1] == '\n')
>> >>> +        buffer[idx - 1] = 0;
>> >>> +
>> >>> +    int status = pclose(pipe);
>> >>> +    if (status == -1) {
>> >>> +        perror("Error reported by pclose()");
>> >>> +    } else if (!WIFEXITED(status)) {
>> >>> +        printf("Script did not terminate normally\n");
>> >>> +    }
>> >>> +
>> >>> +    remove(script_filename);
>> > 
>> > The script can be put separately instead if hard-coded here, right?
>> 
>> 
>> Sure, If so, I wonder whether the migration-test program is able to know where is this script?
>> 
>> 
>> > 
>> >>> +
>> >>> +    return 0;
>> >>> +}
>> >>> +
>> >>> +static void test_precopy_rdma_plain(void)
>> >>> +{
>> >>> +    char buffer[128] = {};
>> >>> +
>> >>> +    if (new_rdma_link(buffer))
>> >>> +        return;
>> >>> +
>> >>> +    g_autofree char *uri = g_strdup_printf("rdma:%s:7777", buffer);
>> >>> +
>> >>> +    MigrateCommon args = {
>> >>> +        .listen_uri = uri,
>> >>> +        .connect_uri = uri,
>> >>> +    };
>> >>> +
>> >>> +    test_precopy_common(&args);
>> >>> +}
>> >>> +
>> >>>   static void test_precopy_tcp_plain(void)
>> >>>   {
>> >>>       MigrateCommon args = {
>> >>> @@ -968,6 +1067,10 @@ static void migration_test_add_precopy_smoke(MigrationTestEnv *env)
>> >>>                          test_multifd_tcp_uri_none);
>> >>>       migration_test_add("/migration/multifd/tcp/plain/cancel",
>> >>>                          test_multifd_tcp_cancel);
>> >>> +#ifdef CONFIG_RDMA
>> >>> +    migration_test_add("/migration/precopy/rdma/plain",
>> >>> +                       test_precopy_rdma_plain);
>> >>> +#endif
>> >>>   }
>> >>>   
>> >>>   void migration_test_add_precopy(MigrationTestEnv *env)
>> >>
>> >> Thanks, that's definitely better than nothing. I'll experiment with this
>> >> locally, see if I can at least run it before sending a pull request.
>> > 
>> > With your newly added --full, IIUC we can add whatever we want there.
>> > E.g. we can add --rdma and iff specified, migration-test adds the rdma test.
>> > 
>> > Or.. skip the test when the rdma link isn't available.
>> > 
>> > If we could separate the script into a file, it'll be better.  We could
>> > create scripts/migration dir and put all migration scripts over there,
>> 
>> We have any other existing script? I didn't find it in current QEMU tree.
>
> We have a few that I'm aware of:
>
>   - analyze-migration.py
>   - vmstate-static-checker.py
>   - userfaultfd-wrlat.py
>

If it cannot be reached from there for some reason, we could copy it to
build/tests/qtest/migration during the build. As a last resort I'm fine
with just having it directly at tests/qtest/migration like this patch
does.

>> 
>> 
>> > then
>> > in the test it tries to detect rdma link and fetch the ip only
>> 
>> It should work without root permission if we just *detect* and *fetch ip*.
>> 
>> Do you also mean we can split new-rdma-link.sh to 2 separate scripts
>> - add-rdma-link.sh # optionally, execute by user before the test (require root permission)
>> - detect-fetch-rdma.sh # execute from the migration-test
>
> Hmm indeed we still need a script to scan over all the ports..
>
> If having --rdma is a good idea, maybe we can further make it a parameter
> to --rdma?
>
>   $ migration-test --rdma $RDMA_IP
>
> Or:
>
>   $ migration-test --rdma-ip $RDMA_IP

I think --rdma only makes sense if it's going to do something
special. The optmimal scenario is that it always runs the test when it
can and sets up/tears down anything it needs.

If it needs root, I'd prefer the test informs about this and does the
work itself.

It would also be good to have the add + detect separate so we have more
flexibility, maybe we manage to enable this in CI even.

So:

./add.sh
migration-test
(runs detect.sh + runs rdma test)
(leaves stuff behind)

migration-test
(skips rdma test with message that it needs root)

sudo migration-test
(runs add.sh + detect.sh + runs rdma test)
(cleans itself up)

Does that make sense to you? I hope it's not too much work.

If you'd like to limit the usage of sudo for running the tests, then we
could indeed add the --rdma option and this would be even more
strict. The good thing of not having --rdma is that I could call add.sh
and then run the full make check afterwards, but that's not a huge deal.

> Then maybe migration-test can directly take that IP and run the tests,
> assuming the admin setup the rdma link.  Then we keep that one script.
>
> Or I assume it's still ok that the test requires root only for --rdma, then
> invoke the script directly in the test.  If so, we'd better also remove the
> rdma link after test finished, so no side effect of the test (modprobe is
> probably fine).
>
> We can wait and see how far Fabiano went with this, and also his opinion.

I haven't got the chance to try the script yet. I still need to figure
out what packages I need from the distro.
Peter Xu Feb. 19, 2025, 2:11 p.m. UTC | #6
On Wed, Feb 19, 2025 at 10:20:21AM -0300, Fabiano Rosas wrote:
> Peter Xu <peterx@redhat.com> writes:
> 
> > On Wed, Feb 19, 2025 at 05:33:26AM +0000, Zhijian Li (Fujitsu) wrote:
> >> 
> >> 
> >> On 19/02/2025 06:40, Peter Xu wrote:
> >> > On Tue, Feb 18, 2025 at 06:03:48PM -0300, Fabiano Rosas wrote:
> >> >> Li Zhijian via <qemu-devel@nongnu.org> writes:
> >> >>
> >> >>> This qtest requirs there is RXE link in the host.
> >> >>>
> >> >>> Here is an example to show how to add this RXE link:
> >> >>> $ ./new-rdma-link.sh
> >> >>> 192.168.22.93
> >> >>>
> >> >>> Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
> >> >>> ---
> >> >>> The RDMA migration was broken again...due to lack of sufficient test/qtest.
> >> >>>
> >> >>> It's urgly to add and execute a script to establish an RDMA link in
> >> >>> the C program. If anyone has a better suggestion, please let me know.
> >> >>>
> >> >>> $ cat ./new-rdma-link.sh
> >> >>> get_ipv4_addr() {
> >> >>>          ip -4 -o addr show dev "$1" |
> >> >>>                  sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
> >> >>> }
> >> >>>
> >> >>> has_soft_rdma() {
> >> >>>          rdma link | grep -q " netdev $1[[:blank:]]*\$"
> >> >>> }
> >> >>>
> >> >>> start_soft_rdma() {
> >> >>>          local type
> >> >>>
> >> >>>          modprobe rdma_rxe || return $?
> >> >>>          type=rxe
> >> >>>          (
> >> >>>                  cd /sys/class/net &&
> >> >>>                          for i in *; do
> >> >>>                                  [ -e "$i" ] || continue
> >> >>>                                  [ "$i" = "lo" ] && continue
> >> >>>                                  [ "$(<"$i/addr_len")" = 6 ] || continue
> >> >>>                                  [ "$(<"$i/carrier")" = 1 ] || continue
> >> >>>                                  has_soft_rdma "$i" && break
> >> >>>                                  rdma link add "${i}_$type" type $type netdev "$i" && break
> >> >>>                          done
> >> >>>                  has_soft_rdma "$i" && echo $i
> >> >>>          )
> >> >>>
> >> >>> }
> >> >>>
> >> >>> rxe_link=$(start_soft_rdma)
> >> >>> [[ "$rxe_link" ]] && get_ipv4_addr $rxe_link
> >> >>>
> >> >>> Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
> >> >>> ---
> >> >>>   tests/qtest/migration/new-rdma-link.sh |  34 ++++++++
> >> >>>   tests/qtest/migration/precopy-tests.c  | 103 +++++++++++++++++++++++++
> >> >>>   2 files changed, 137 insertions(+)
> >> >>>   create mode 100644 tests/qtest/migration/new-rdma-link.sh
> >> >>>
> >> >>> diff --git a/tests/qtest/migration/new-rdma-link.sh b/tests/qtest/migration/new-rdma-link.sh
> >> >>> new file mode 100644
> >> >>> index 00000000000..ca20594eaae
> >> >>> --- /dev/null
> >> >>> +++ b/tests/qtest/migration/new-rdma-link.sh
> >> >>> @@ -0,0 +1,34 @@
> >> >>> +#!/bin/bash
> >> >>> +
> >> >>> +# Copied from blktests
> >> >>> +get_ipv4_addr() {
> >> >>> +	ip -4 -o addr show dev "$1" |
> >> >>> +		sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
> >> >>> +}
> >> >>> +
> >> >>> +has_soft_rdma() {
> >> >>> +	rdma link | grep -q " netdev $1[[:blank:]]*\$"
> >> >>> +}
> >> >>> +
> >> >>> +start_soft_rdma() {
> >> >>> +	local type
> >> >>> +
> >> >>> +	modprobe rdma_rxe || return $?
> >> >>> +	type=rxe
> >> >>> +	(
> >> >>> +		cd /sys/class/net &&
> >> >>> +			for i in *; do
> >> >>> +				[ -e "$i" ] || continue
> >> >>> +				[ "$i" = "lo" ] && continue
> >> >>> +				[ "$(<"$i/addr_len")" = 6 ] || continue
> >> >>> +				[ "$(<"$i/carrier")" = 1 ] || continue
> >> >>> +				has_soft_rdma "$i" && break
> >> >>> +				rdma link add "${i}_$type" type $type netdev "$i" && break
> >> >>> +			done
> >> >>> +		has_soft_rdma "$i" && echo $i
> >> >>> +	)
> >> >>> +
> >> >>> +}
> >> >>> +
> >> >>> +rxe_link=$(start_soft_rdma)
> >> >>> +[[ "$rxe_link" ]] && get_ipv4_addr $rxe_link
> >> >>> diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c
> >> >>> index 162fa695318..d2a1c9c9438 100644
> >> >>> --- a/tests/qtest/migration/precopy-tests.c
> >> >>> +++ b/tests/qtest/migration/precopy-tests.c
> >> >>> @@ -98,6 +98,105 @@ static void test_precopy_unix_dirty_ring(void)
> >> >>>       test_precopy_common(&args);
> >> >>>   }
> >> >>>   
> >> >>> +static int new_rdma_link(char *buffer) {
> >> >>> +    // Copied from blktests
> >> >>> +    const char *script =
> >> >>> +        "#!/bin/bash\n"
> >> >>> +        "\n"
> >> >>> +        "get_ipv4_addr() {\n"
> >> >>> +        "    ip -4 -o addr show dev \"$1\" |\n"
> >> >>> +        "    sed -n 's/.*[[:blank:]]inet[[:blank:]]*\\([^[:blank:]/]*\\).*/\\1/p'\n"
> >> >>> +        "}\n"
> >> >>> +        "\n"
> >> >>> +        "has_soft_rdma() {\n"
> >> >>> +        "    rdma link | grep -q \" netdev $1[[:blank:]]*\\$\"\n"
> >> >>> +        "}\n"
> >> >>> +        "\n"
> >> >>> +        "start_soft_rdma() {\n"
> >> >>> +        "    local type\n"
> >> >>> +        "\n"
> >> >>> +        "    modprobe rdma_rxe || return $?\n"
> >> >>> +        "    type=rxe\n"
> >> >>> +        "    (\n"
> >> >>> +        "        cd /sys/class/net &&\n"
> >> >>> +        "        for i in *; do\n"
> >> >>> +        "            [ -e \"$i\" ] || continue\n"
> >> >>> +        "            [ \"$i\" = \"lo\" ] && continue\n"
> >> >>> +        "            [ \"$(<$i/addr_len)\" = 6 ] || continue\n"
> >> >>> +        "            [ \"$(<$i/carrier)\" = 1 ] || continue\n"
> >> >>> +        "            has_soft_rdma \"$i\" && break\n"
> >> >>> +        "            rdma link add \"${i}_$type\" type $type netdev \"$i\" && break\n"
> >> >>> +        "        done\n"
> >> >>> +        "        has_soft_rdma \"$i\" && echo $i\n"
> >> >>> +        "    )\n"
> >> >>> +        "}\n"
> >> >>> +        "\n"
> >> >>> +        "rxe_link=$(start_soft_rdma)\n"
> >> >>> +        "[[ \"$rxe_link\" ]] && get_ipv4_addr $rxe_link\n";
> >> >>> +
> >> >>> +    char script_filename[] = "/tmp/temp_scriptXXXXXX";
> >> >>> +    int fd = mkstemp(script_filename);
> >> >>> +    if (fd == -1) {
> >> >>> +        perror("Failed to create temporary file");
> >> >>> +        return 1;
> >> >>> +    }
> >> >>> +
> >> >>> +    FILE *fp = fdopen(fd, "w");
> >> >>> +    if (fp == NULL) {
> >> >>> +        perror("Failed to open file stream");
> >> >>> +        close(fd);
> >> >>> +        return 1;
> >> >>> +    }
> >> >>> +    fprintf(fp, "%s", script);
> >> >>> +    fclose(fp);
> >> >>> +
> >> >>> +    if (chmod(script_filename, 0700) == -1) {
> >> >>> +        perror("Failed to set execute permission");
> >> >>> +        return 1;
> >> >>> +    }
> >> >>> +
> >> >>> +    FILE *pipe = popen(script_filename, "r");
> >> >>> +    if (pipe == NULL) {
> >> >>> +        perror("Failed to run script");
> >> >>> +        return 1;
> >> >>> +    }
> >> >>> +
> >> >>> +    int idx = 0;
> >> >>> +    while (fgets(buffer + idx, 128 - idx, pipe) != NULL) {
> >> >>> +        idx += strlen(buffer);
> >> >>> +    }
> >> >>> +    if (buffer[idx - 1] == '\n')
> >> >>> +        buffer[idx - 1] = 0;
> >> >>> +
> >> >>> +    int status = pclose(pipe);
> >> >>> +    if (status == -1) {
> >> >>> +        perror("Error reported by pclose()");
> >> >>> +    } else if (!WIFEXITED(status)) {
> >> >>> +        printf("Script did not terminate normally\n");
> >> >>> +    }
> >> >>> +
> >> >>> +    remove(script_filename);
> >> > 
> >> > The script can be put separately instead if hard-coded here, right?
> >> 
> >> 
> >> Sure, If so, I wonder whether the migration-test program is able to know where is this script?
> >> 
> >> 
> >> > 
> >> >>> +
> >> >>> +    return 0;
> >> >>> +}
> >> >>> +
> >> >>> +static void test_precopy_rdma_plain(void)
> >> >>> +{
> >> >>> +    char buffer[128] = {};
> >> >>> +
> >> >>> +    if (new_rdma_link(buffer))
> >> >>> +        return;
> >> >>> +
> >> >>> +    g_autofree char *uri = g_strdup_printf("rdma:%s:7777", buffer);
> >> >>> +
> >> >>> +    MigrateCommon args = {
> >> >>> +        .listen_uri = uri,
> >> >>> +        .connect_uri = uri,
> >> >>> +    };
> >> >>> +
> >> >>> +    test_precopy_common(&args);
> >> >>> +}
> >> >>> +
> >> >>>   static void test_precopy_tcp_plain(void)
> >> >>>   {
> >> >>>       MigrateCommon args = {
> >> >>> @@ -968,6 +1067,10 @@ static void migration_test_add_precopy_smoke(MigrationTestEnv *env)
> >> >>>                          test_multifd_tcp_uri_none);
> >> >>>       migration_test_add("/migration/multifd/tcp/plain/cancel",
> >> >>>                          test_multifd_tcp_cancel);
> >> >>> +#ifdef CONFIG_RDMA
> >> >>> +    migration_test_add("/migration/precopy/rdma/plain",
> >> >>> +                       test_precopy_rdma_plain);
> >> >>> +#endif
> >> >>>   }
> >> >>>   
> >> >>>   void migration_test_add_precopy(MigrationTestEnv *env)
> >> >>
> >> >> Thanks, that's definitely better than nothing. I'll experiment with this
> >> >> locally, see if I can at least run it before sending a pull request.
> >> > 
> >> > With your newly added --full, IIUC we can add whatever we want there.
> >> > E.g. we can add --rdma and iff specified, migration-test adds the rdma test.
> >> > 
> >> > Or.. skip the test when the rdma link isn't available.
> >> > 
> >> > If we could separate the script into a file, it'll be better.  We could
> >> > create scripts/migration dir and put all migration scripts over there,
> >> 
> >> We have any other existing script? I didn't find it in current QEMU tree.
> >
> > We have a few that I'm aware of:
> >
> >   - analyze-migration.py
> >   - vmstate-static-checker.py
> >   - userfaultfd-wrlat.py
> >
> 
> If it cannot be reached from there for some reason, we could copy it to
> build/tests/qtest/migration during the build. As a last resort I'm fine
> with just having it directly at tests/qtest/migration like this patch
> does.

Yes, if we want to have the test being able to trigger the script, we can
put it under tests/qtest/migration/.

> 
> >> 
> >> 
> >> > then
> >> > in the test it tries to detect rdma link and fetch the ip only
> >> 
> >> It should work without root permission if we just *detect* and *fetch ip*.
> >> 
> >> Do you also mean we can split new-rdma-link.sh to 2 separate scripts
> >> - add-rdma-link.sh # optionally, execute by user before the test (require root permission)
> >> - detect-fetch-rdma.sh # execute from the migration-test
> >
> > Hmm indeed we still need a script to scan over all the ports..
> >
> > If having --rdma is a good idea, maybe we can further make it a parameter
> > to --rdma?
> >
> >   $ migration-test --rdma $RDMA_IP
> >
> > Or:
> >
> >   $ migration-test --rdma-ip $RDMA_IP
> 
> I think --rdma only makes sense if it's going to do something
> special. The optmimal scenario is that it always runs the test when it
> can and sets up/tears down anything it needs.
> 
> If it needs root, I'd prefer the test informs about this and does the
> work itself.
> 
> It would also be good to have the add + detect separate so we have more
> flexibility, maybe we manage to enable this in CI even.
> 
> So:
> 
> ./add.sh
> migration-test
> (runs detect.sh + runs rdma test)
> (leaves stuff behind)
> 
> migration-test
> (skips rdma test with message that it needs root)
> 
> sudo migration-test
> (runs add.sh + detect.sh + runs rdma test)
> (cleans itself up)
> 
> Does that make sense to you? I hope it's not too much work.

Looks good here.  We can also keep all the rdma stuff into one file, taking
parameters.

./rdma-helper.sh setup
./rdma-helper.sh detect-ip

> 
> If you'd like to limit the usage of sudo for running the tests, then we
> could indeed add the --rdma option and this would be even more
> strict. The good thing of not having --rdma is that I could call add.sh
> and then run the full make check afterwards, but that's not a huge deal.
> 
> > Then maybe migration-test can directly take that IP and run the tests,
> > assuming the admin setup the rdma link.  Then we keep that one script.
> >
> > Or I assume it's still ok that the test requires root only for --rdma, then
> > invoke the script directly in the test.  If so, we'd better also remove the
> > rdma link after test finished, so no side effect of the test (modprobe is
> > probably fine).
> >
> > We can wait and see how far Fabiano went with this, and also his opinion.
> 
> I haven't got the chance to try the script yet. I still need to figure
> out what packages I need from the distro.

For misterious reasons I seem to have all the libs needed, probably I tried
to build RDMA at some point and do something with it.  So I gave it a shot
quickly, I can reproduce Zhijian's failure, and patch 1 fixes it.
Li Zhijian Feb. 20, 2025, 9:40 a.m. UTC | #7
On 19/02/2025 22:11, Peter Xu wrote:
>>>>> then
>>>>> in the test it tries to detect rdma link and fetch the ip only
>>>> It should work without root permission if we just*detect*  and*fetch ip*.
>>>>
>>>> Do you also mean we can split new-rdma-link.sh to 2 separate scripts
>>>> - add-rdma-link.sh # optionally, execute by user before the test (require root permission)
>>>> - detect-fetch-rdma.sh # execute from the migration-test
>>> Hmm indeed we still need a script to scan over all the ports..
>>>
>>> If having --rdma is a good idea, maybe we can further make it a parameter
>>> to --rdma?
>>>
>>>    $ migration-test --rdma $RDMA_IP
>>>
>>> Or:
>>>
>>>    $ migration-test --rdma-ip $RDMA_IP
>> I think --rdma only makes sense if it's going to do something
>> special. The optmimal scenario is that it always runs the test when it
>> can and sets up/tears down anything it needs.
>>
>> If it needs root, I'd prefer the test informs about this and does the
>> work itself.
>>
>> It would also be good to have the add + detect separate so we have more
>> flexibility, maybe we manage to enable this in CI even.
>>
>> So:
>>
>> ./add.sh
>> migration-test
>> (runs detect.sh + runs rdma test)
>> (leaves stuff behind)
>>
>> migration-test
>> (skips rdma test with message that it needs root)
>>
>> sudo migration-test
>> (runs add.sh + detect.sh + runs rdma test)
>> (cleans itself up)
>>
>> Does that make sense to you? I hope it's not too much work.
> Looks good here.  We can also keep all the rdma stuff into one file, taking
> parameters.
> 
> ./rdma-helper.sh setup
> ./rdma-helper.sh detect-ip

Hi Peter and Fabiano

Many thanks for your kindly idea and suggestion.
Please take another look at the changes below.
- I don't copy script to the build dir, just execute the script like misc-tests.c
- It will automatically create a new RXE if it doesn't exit when running in root

[PATCH] migration: Add qtest for migration over RDMA

This qtest requires there is RDMA(RoCE) link in the host.
Introduce a scripts/rdma-migration-helper.sh to
- setup a new RXE if it's root
- detect existing RoCE link
to make the qtest work smoothly.

Test will be skip if there is no available RoCE link.
 # Start of rdma tests
 # Running /x86_64/migration/precopy/rdma/plain
 ok 1 /x86_64/migration/precopy/rdma/plain # SKIP There is no available rdma link in the host.
 Maybe you are not running with the root permission
 # End of rdma tests

Admin is able to remove the RXE by passing 'cleanup' to this script.

Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
---
 scripts/rdma-migration-helper.sh      | 40 +++++++++++++++++++
 tests/qtest/migration/precopy-tests.c | 57 +++++++++++++++++++++++++++
 2 files changed, 97 insertions(+)
 create mode 100755 scripts/rdma-migration-helper.sh

diff --git a/scripts/rdma-migration-helper.sh b/scripts/rdma-migration-helper.sh
new file mode 100755
index 0000000000..4ef62baf0f
--- /dev/null
+++ b/scripts/rdma-migration-helper.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+# Copied from blktests
+get_ipv4_addr() {
+    ip -4 -o addr show dev "$1" |
+        sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p' |
+        tr -d '\n'
+}
+
+has_soft_rdma() {
+    rdma link | grep -q " netdev $1[[:blank:]]*\$"
+}
+
+rdma_rxe_setup_detect()
+{
+    (
+        cd /sys/class/net &&
+            for i in *; do
+                [ -e "$i" ] || continue
+                [ "$i" = "lo" ] && continue
+                [ "$(<"$i/addr_len")" = 6 ] || continue
+                [ "$(<"$i/carrier")" = 1 ] || continue
+
+                has_soft_rdma "$i" && break
+                [ "$operation" = "setup" ] && rdma link add "${i}_rxe" type rxe netdev "$i" && break
+            done
+        has_soft_rdma "$i" || return
+        get_ipv4_addr $i
+    )
+}
+
+operation=${1:-setup}
+
+if [ "$operation" == "setup" ] || [ "$operation" == "detect" ]; then
+    rdma_rxe_setup_detect
+elif [ "$operation" == "cleanup" ]; then
+    modprobe -r rdma_rxe
+else
+    echo "Usage: $0 [setup | cleanup | detect]"
+fi
diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c
index ba273d10b9..8c72eb699b 100644
--- a/tests/qtest/migration/precopy-tests.c
+++ b/tests/qtest/migration/precopy-tests.c
@@ -99,6 +99,59 @@ static void test_precopy_unix_dirty_ring(void)
     test_precopy_common(&args);
 }
 
+#ifdef CONFIG_RDMA
+
+#define RDMA_MIGRATION_HELPER "scripts/rdma-migration-helper.sh"
+static int new_rdma_link(char *buffer) {
+    const char *argument = (geteuid() == 0) ? "setup" : "detect";
+    char command[1024];
+
+    snprintf(command, sizeof(command), "%s %s", RDMA_MIGRATION_HELPER, argument);
+
+    FILE *pipe = popen(command, "r");
+    if (pipe == NULL) {
+        perror("Failed to run script");
+        return -1;
+    }
+
+    int idx = 0;
+    while (fgets(buffer + idx, 128 - idx, pipe) != NULL) {
+        idx += strlen(buffer);
+    }
+
+    int status = pclose(pipe);
+    if (status == -1) {
+        perror("Error reported by pclose()");
+        return -1;
+    } else if (WIFEXITED(status)) {
+        return WEXITSTATUS(status);
+    }
+
+    return -1;
+}
+
+static void test_precopy_rdma_plain(void)
+{
+    char buffer[128] = {};
+
+    if (new_rdma_link(buffer)) {
+        g_test_skip("There is no available rdma link in the host.\n"
+                    "Maybe you are not running with the root permission");
+        return;
+    }
+
+    /* FIXME: query a free port instead of hard code. */
+    g_autofree char *uri = g_strdup_printf("rdma:%s:7777", buffer);
+
+    MigrateCommon args = {
+        .listen_uri = uri,
+        .connect_uri = uri,
+    };
+
+    test_precopy_common(&args);
+}
+#endif
+
 static void test_precopy_tcp_plain(void)
 {
     MigrateCommon args = {
@@ -1124,6 +1177,10 @@ static void migration_test_add_precopy_smoke(MigrationTestEnv *env)
                        test_multifd_tcp_uri_none);
     migration_test_add("/migration/multifd/tcp/plain/cancel",
                        test_multifd_tcp_cancel);
+#ifdef CONFIG_RDMA
+    migration_test_add("/migration/precopy/rdma/plain",
+                       test_precopy_rdma_plain);
+#endif
 }
 
 void migration_test_add_precopy(MigrationTestEnv *env)
Peter Xu Feb. 20, 2025, 3:55 p.m. UTC | #8
On Thu, Feb 20, 2025 at 05:40:38PM +0800, Li Zhijian wrote:
> On 19/02/2025 22:11, Peter Xu wrote:
> >>>>> then
> >>>>> in the test it tries to detect rdma link and fetch the ip only
> >>>> It should work without root permission if we just*detect*  and*fetch ip*.
> >>>>
> >>>> Do you also mean we can split new-rdma-link.sh to 2 separate scripts
> >>>> - add-rdma-link.sh # optionally, execute by user before the test (require root permission)
> >>>> - detect-fetch-rdma.sh # execute from the migration-test
> >>> Hmm indeed we still need a script to scan over all the ports..
> >>>
> >>> If having --rdma is a good idea, maybe we can further make it a parameter
> >>> to --rdma?
> >>>
> >>>    $ migration-test --rdma $RDMA_IP
> >>>
> >>> Or:
> >>>
> >>>    $ migration-test --rdma-ip $RDMA_IP
> >> I think --rdma only makes sense if it's going to do something
> >> special. The optmimal scenario is that it always runs the test when it
> >> can and sets up/tears down anything it needs.
> >>
> >> If it needs root, I'd prefer the test informs about this and does the
> >> work itself.
> >>
> >> It would also be good to have the add + detect separate so we have more
> >> flexibility, maybe we manage to enable this in CI even.
> >>
> >> So:
> >>
> >> ./add.sh
> >> migration-test
> >> (runs detect.sh + runs rdma test)
> >> (leaves stuff behind)
> >>
> >> migration-test
> >> (skips rdma test with message that it needs root)
> >>
> >> sudo migration-test
> >> (runs add.sh + detect.sh + runs rdma test)
> >> (cleans itself up)
> >>
> >> Does that make sense to you? I hope it's not too much work.
> > Looks good here.  We can also keep all the rdma stuff into one file, taking
> > parameters.
> > 
> > ./rdma-helper.sh setup
> > ./rdma-helper.sh detect-ip
> 
> Hi Peter and Fabiano
> 
> Many thanks for your kindly idea and suggestion.
> Please take another look at the changes below.
> - I don't copy script to the build dir, just execute the script like misc-tests.c
> - It will automatically create a new RXE if it doesn't exit when running in root

Thanks!  This is much better.  Comments below.

> 
> [PATCH] migration: Add qtest for migration over RDMA
> 
> This qtest requires there is RDMA(RoCE) link in the host.
> Introduce a scripts/rdma-migration-helper.sh to
> - setup a new RXE if it's root
> - detect existing RoCE link
> to make the qtest work smoothly.
> 
> Test will be skip if there is no available RoCE link.
>  # Start of rdma tests
>  # Running /x86_64/migration/precopy/rdma/plain
>  ok 1 /x86_64/migration/precopy/rdma/plain # SKIP There is no available rdma link in the host.
>  Maybe you are not running with the root permission
>  # End of rdma tests
> 
> Admin is able to remove the RXE by passing 'cleanup' to this script.
> 
> Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
> ---
>  scripts/rdma-migration-helper.sh      | 40 +++++++++++++++++++
>  tests/qtest/migration/precopy-tests.c | 57 +++++++++++++++++++++++++++
>  2 files changed, 97 insertions(+)
>  create mode 100755 scripts/rdma-migration-helper.sh
> 
> diff --git a/scripts/rdma-migration-helper.sh b/scripts/rdma-migration-helper.sh
> new file mode 100755
> index 0000000000..4ef62baf0f
> --- /dev/null
> +++ b/scripts/rdma-migration-helper.sh
> @@ -0,0 +1,40 @@
> +#!/bin/bash
> +
> +# Copied from blktests
> +get_ipv4_addr() {
> +    ip -4 -o addr show dev "$1" |
> +        sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p' |
> +        tr -d '\n'
> +}
> +
> +has_soft_rdma() {
> +    rdma link | grep -q " netdev $1[[:blank:]]*\$"
> +}
> +
> +rdma_rxe_setup_detect()
> +{
> +    (
> +        cd /sys/class/net &&
> +            for i in *; do
> +                [ -e "$i" ] || continue
> +                [ "$i" = "lo" ] && continue
> +                [ "$(<"$i/addr_len")" = 6 ] || continue
> +                [ "$(<"$i/carrier")" = 1 ] || continue
> +
> +                has_soft_rdma "$i" && break
> +                [ "$operation" = "setup" ] && rdma link add "${i}_rxe" type rxe netdev "$i" && break
> +            done
> +        has_soft_rdma "$i" || return
> +        get_ipv4_addr $i
> +    )
> +}
> +
> +operation=${1:-setup}
> +
> +if [ "$operation" == "setup" ] || [ "$operation" == "detect" ]; then
> +    rdma_rxe_setup_detect
> +elif [ "$operation" == "cleanup" ]; then
> +    modprobe -r rdma_rxe

Would this affects host when there's existing user of rdma_rxe (or fail
with -EBUSY)?  If there's no major side effect of leftover rdma link, we
could also drop the "cleanup" for now and start from simple.

> +else
> +    echo "Usage: $0 [setup | cleanup | detect]"
> +fi
> diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c
> index ba273d10b9..8c72eb699b 100644
> --- a/tests/qtest/migration/precopy-tests.c
> +++ b/tests/qtest/migration/precopy-tests.c
> @@ -99,6 +99,59 @@ static void test_precopy_unix_dirty_ring(void)
>      test_precopy_common(&args);
>  }
>  
> +#ifdef CONFIG_RDMA
> +
> +#define RDMA_MIGRATION_HELPER "scripts/rdma-migration-helper.sh"
> +static int new_rdma_link(char *buffer) {

Nit: newline before "{".

> +    const char *argument = (geteuid() == 0) ? "setup" : "detect";
> +    char command[1024];
> +
> +    snprintf(command, sizeof(command), "%s %s", RDMA_MIGRATION_HELPER, argument);
> +
> +    FILE *pipe = popen(command, "r");
> +    if (pipe == NULL) {
> +        perror("Failed to run script");
> +        return -1;
> +    }
> +
> +    int idx = 0;
> +    while (fgets(buffer + idx, 128 - idx, pipe) != NULL) {
> +        idx += strlen(buffer);
> +    }
> +
> +    int status = pclose(pipe);
> +    if (status == -1) {
> +        perror("Error reported by pclose()");
> +        return -1;
> +    } else if (WIFEXITED(status)) {
> +        return WEXITSTATUS(status);
> +    }
> +
> +    return -1;
> +}
> +
> +static void test_precopy_rdma_plain(void)
> +{
> +    char buffer[128] = {};
> +
> +    if (new_rdma_link(buffer)) {
> +        g_test_skip("There is no available rdma link in the host.\n"
> +                    "Maybe you are not running with the root permission");

Nit: can be slightly more verbose?

           g_test_skip("\nThere is no available rdma link to run RDMA
                       migration test.  To enable the test:\n"
                       "(1) Run \'%s setup\' with root and rerun the test\n"
                       "(2) Run the test with root privilege\n",
                       RDMA_MIGRATION_HELPER);


> +        return;
> +    }
> +
> +    /* FIXME: query a free port instead of hard code. */
> +    g_autofree char *uri = g_strdup_printf("rdma:%s:7777", buffer);
> +
> +    MigrateCommon args = {
> +        .listen_uri = uri,
> +        .connect_uri = uri,
> +    };
> +
> +    test_precopy_common(&args);
> +}
> +#endif
> +
>  static void test_precopy_tcp_plain(void)
>  {
>      MigrateCommon args = {
> @@ -1124,6 +1177,10 @@ static void migration_test_add_precopy_smoke(MigrationTestEnv *env)
>                         test_multifd_tcp_uri_none);
>      migration_test_add("/migration/multifd/tcp/plain/cancel",
>                         test_multifd_tcp_cancel);
> +#ifdef CONFIG_RDMA
> +    migration_test_add("/migration/precopy/rdma/plain",
> +                       test_precopy_rdma_plain);
> +#endif
>  }
>  
>  void migration_test_add_precopy(MigrationTestEnv *env)
> -- 
> 2.41.0
>
Zhijian Li (Fujitsu)" via Feb. 21, 2025, 1:32 a.m. UTC | #9
On 20/02/2025 23:55, Peter Xu wrote:
> On Thu, Feb 20, 2025 at 05:40:38PM +0800, Li Zhijian wrote:
>> On 19/02/2025 22:11, Peter Xu wrote:
>>>>>>> then
>>>>>>> in the test it tries to detect rdma link and fetch the ip only
>>>>>> It should work without root permission if we just*detect*  and*fetch ip*.
>>>>>>
>>>>>> Do you also mean we can split new-rdma-link.sh to 2 separate scripts
>>>>>> - add-rdma-link.sh # optionally, execute by user before the test (require root permission)
>>>>>> - detect-fetch-rdma.sh # execute from the migration-test
>>>>> Hmm indeed we still need a script to scan over all the ports..
>>>>>
>>>>> If having --rdma is a good idea, maybe we can further make it a parameter
>>>>> to --rdma?
>>>>>
>>>>>     $ migration-test --rdma $RDMA_IP
>>>>>
>>>>> Or:
>>>>>
>>>>>     $ migration-test --rdma-ip $RDMA_IP
>>>> I think --rdma only makes sense if it's going to do something
>>>> special. The optmimal scenario is that it always runs the test when it
>>>> can and sets up/tears down anything it needs.
>>>>
>>>> If it needs root, I'd prefer the test informs about this and does the
>>>> work itself.
>>>>
>>>> It would also be good to have the add + detect separate so we have more
>>>> flexibility, maybe we manage to enable this in CI even.
>>>>
>>>> So:
>>>>
>>>> ./add.sh
>>>> migration-test
>>>> (runs detect.sh + runs rdma test)
>>>> (leaves stuff behind)
>>>>
>>>> migration-test
>>>> (skips rdma test with message that it needs root)
>>>>
>>>> sudo migration-test
>>>> (runs add.sh + detect.sh + runs rdma test)
>>>> (cleans itself up)
>>>>
>>>> Does that make sense to you? I hope it's not too much work.
>>> Looks good here.  We can also keep all the rdma stuff into one file, taking
>>> parameters.
>>>
>>> ./rdma-helper.sh setup
>>> ./rdma-helper.sh detect-ip
>>
>> Hi Peter and Fabiano
>>
>> Many thanks for your kindly idea and suggestion.
>> Please take another look at the changes below.
>> - I don't copy script to the build dir, just execute the script like misc-tests.c
>> - It will automatically create a new RXE if it doesn't exit when running in root
> 
> Thanks!  This is much better.  Comments below.
> 
>>
>> [PATCH] migration: Add qtest for migration over RDMA
>>
>> This qtest requires there is RDMA(RoCE) link in the host.
>> Introduce a scripts/rdma-migration-helper.sh to
>> - setup a new RXE if it's root
>> - detect existing RoCE link
>> to make the qtest work smoothly.
>>
>> Test will be skip if there is no available RoCE link.
>>   # Start of rdma tests
>>   # Running /x86_64/migration/precopy/rdma/plain
>>   ok 1 /x86_64/migration/precopy/rdma/plain # SKIP There is no available rdma link in the host.
>>   Maybe you are not running with the root permission
>>   # End of rdma tests
>>
>> Admin is able to remove the RXE by passing 'cleanup' to this script.
>>
>> Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
>> ---
>>   scripts/rdma-migration-helper.sh      | 40 +++++++++++++++++++
>>   tests/qtest/migration/precopy-tests.c | 57 +++++++++++++++++++++++++++
>>   2 files changed, 97 insertions(+)
>>   create mode 100755 scripts/rdma-migration-helper.sh
>>
>> diff --git a/scripts/rdma-migration-helper.sh b/scripts/rdma-migration-helper.sh
>> new file mode 100755
>> index 0000000000..4ef62baf0f
>> --- /dev/null
>> +++ b/scripts/rdma-migration-helper.sh
>> @@ -0,0 +1,40 @@
>> +#!/bin/bash
>> +
>> +# Copied from blktests
>> +get_ipv4_addr() {
>> +    ip -4 -o addr show dev "$1" |
>> +        sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p' |
>> +        tr -d '\n'
>> +}
>> +
>> +has_soft_rdma() {
>> +    rdma link | grep -q " netdev $1[[:blank:]]*\$"
>> +}
>> +
>> +rdma_rxe_setup_detect()
>> +{
>> +    (
>> +        cd /sys/class/net &&
>> +            for i in *; do
>> +                [ -e "$i" ] || continue
>> +                [ "$i" = "lo" ] && continue
>> +                [ "$(<"$i/addr_len")" = 6 ] || continue
>> +                [ "$(<"$i/carrier")" = 1 ] || continue
>> +
>> +                has_soft_rdma "$i" && break
>> +                [ "$operation" = "setup" ] && rdma link add "${i}_rxe" type rxe netdev "$i" && break
>> +            done
>> +        has_soft_rdma "$i" || return
>> +        get_ipv4_addr $i
>> +    )
>> +}
>> +
>> +operation=${1:-setup}
>> +
>> +if [ "$operation" == "setup" ] || [ "$operation" == "detect" ]; then
>> +    rdma_rxe_setup_detect
>> +elif [ "$operation" == "cleanup" ]; then
>> +    modprobe -r rdma_rxe
> 
> Would this affects host when there's existing user of rdma_rxe (or fail
> with -EBUSY)?  

'modprobe -r ' will fail with EBUSY in this case.


> If there's no major side effect of leftover rdma link, we
> could also drop the "cleanup" for now and start from simple.

In my understanding, there is no any side effect, I'm fine to drop it in this time.


> 
>> +else
>> +    echo "Usage: $0 [setup | cleanup | detect]"
>> +fi
>> diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c
>> index ba273d10b9..8c72eb699b 100644
>> --- a/tests/qtest/migration/precopy-tests.c
>> +++ b/tests/qtest/migration/precopy-tests.c
>> @@ -99,6 +99,59 @@ static void test_precopy_unix_dirty_ring(void)
>>       test_precopy_common(&args);
>>   }
>>   
>> +#ifdef CONFIG_RDMA
>> +
>> +#define RDMA_MIGRATION_HELPER "scripts/rdma-migration-helper.sh"
>> +static int new_rdma_link(char *buffer) {
> 
> Nit: newline before "{".

Good catch!


> 
>> +    const char *argument = (geteuid() == 0) ? "setup" : "detect";
>> +    char command[1024];
>> +
>> +    snprintf(command, sizeof(command), "%s %s", RDMA_MIGRATION_HELPER, argument);
>> +
>> +    FILE *pipe = popen(command, "r");
>> +    if (pipe == NULL) {
>> +        perror("Failed to run script");
>> +        return -1;
>> +    }
>> +
>> +    int idx = 0;
>> +    while (fgets(buffer + idx, 128 - idx, pipe) != NULL) {
>> +        idx += strlen(buffer);
>> +    }
>> +
>> +    int status = pclose(pipe);
>> +    if (status == -1) {
>> +        perror("Error reported by pclose()");
>> +        return -1;
>> +    } else if (WIFEXITED(status)) {
>> +        return WEXITSTATUS(status);
>> +    }
>> +
>> +    return -1;
>> +}
>> +
>> +static void test_precopy_rdma_plain(void)
>> +{
>> +    char buffer[128] = {};
>> +
>> +    if (new_rdma_link(buffer)) {
>> +        g_test_skip("There is no available rdma link in the host.\n"
>> +                    "Maybe you are not running with the root permission");
> 
> Nit: can be slightly more verbose?
> 
>             g_test_skip("\nThere is no available rdma link to run RDMA
>                         migration test.  To enable the test:\n"
>                         "(1) Run \'%s setup\' with root and rerun the test\n"
>                         "(2) Run the test with root privilege\n",
>                         RDMA_MIGRATION_HELPER);

Yes, it's much better.


Thanks
Zhijian


> 
> 
>> +        return;
>> +    }
>> +
>> +    /* FIXME: query a free port instead of hard code. */
>> +    g_autofree char *uri = g_strdup_printf("rdma:%s:7777", buffer);
>> +
>> +    MigrateCommon args = {
>> +        .listen_uri = uri,
>> +        .connect_uri = uri,
>> +    };
>> +
>> +    test_precopy_common(&args);
>> +}
>> +#endif
>> +
>>   static void test_precopy_tcp_plain(void)
>>   {
>>       MigrateCommon args = {
>> @@ -1124,6 +1177,10 @@ static void migration_test_add_precopy_smoke(MigrationTestEnv *env)
>>                          test_multifd_tcp_uri_none);
>>       migration_test_add("/migration/multifd/tcp/plain/cancel",
>>                          test_multifd_tcp_cancel);
>> +#ifdef CONFIG_RDMA
>> +    migration_test_add("/migration/precopy/rdma/plain",
>> +                       test_precopy_rdma_plain);
>> +#endif
>>   }
>>   
>>   void migration_test_add_precopy(MigrationTestEnv *env)
>> -- 
>> 2.41.0
>>
>
diff mbox series

Patch

diff --git a/tests/qtest/migration/new-rdma-link.sh b/tests/qtest/migration/new-rdma-link.sh
new file mode 100644
index 00000000000..ca20594eaae
--- /dev/null
+++ b/tests/qtest/migration/new-rdma-link.sh
@@ -0,0 +1,34 @@ 
+#!/bin/bash
+
+# Copied from blktests
+get_ipv4_addr() {
+	ip -4 -o addr show dev "$1" |
+		sed -n 's/.*[[:blank:]]inet[[:blank:]]*\([^[:blank:]/]*\).*/\1/p'
+}
+
+has_soft_rdma() {
+	rdma link | grep -q " netdev $1[[:blank:]]*\$"
+}
+
+start_soft_rdma() {
+	local type
+
+	modprobe rdma_rxe || return $?
+	type=rxe
+	(
+		cd /sys/class/net &&
+			for i in *; do
+				[ -e "$i" ] || continue
+				[ "$i" = "lo" ] && continue
+				[ "$(<"$i/addr_len")" = 6 ] || continue
+				[ "$(<"$i/carrier")" = 1 ] || continue
+				has_soft_rdma "$i" && break
+				rdma link add "${i}_$type" type $type netdev "$i" && break
+			done
+		has_soft_rdma "$i" && echo $i
+	)
+
+}
+
+rxe_link=$(start_soft_rdma)
+[[ "$rxe_link" ]] && get_ipv4_addr $rxe_link
diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c
index 162fa695318..d2a1c9c9438 100644
--- a/tests/qtest/migration/precopy-tests.c
+++ b/tests/qtest/migration/precopy-tests.c
@@ -98,6 +98,105 @@  static void test_precopy_unix_dirty_ring(void)
     test_precopy_common(&args);
 }
 
+static int new_rdma_link(char *buffer) {
+    // Copied from blktests
+    const char *script =
+        "#!/bin/bash\n"
+        "\n"
+        "get_ipv4_addr() {\n"
+        "    ip -4 -o addr show dev \"$1\" |\n"
+        "    sed -n 's/.*[[:blank:]]inet[[:blank:]]*\\([^[:blank:]/]*\\).*/\\1/p'\n"
+        "}\n"
+        "\n"
+        "has_soft_rdma() {\n"
+        "    rdma link | grep -q \" netdev $1[[:blank:]]*\\$\"\n"
+        "}\n"
+        "\n"
+        "start_soft_rdma() {\n"
+        "    local type\n"
+        "\n"
+        "    modprobe rdma_rxe || return $?\n"
+        "    type=rxe\n"
+        "    (\n"
+        "        cd /sys/class/net &&\n"
+        "        for i in *; do\n"
+        "            [ -e \"$i\" ] || continue\n"
+        "            [ \"$i\" = \"lo\" ] && continue\n"
+        "            [ \"$(<$i/addr_len)\" = 6 ] || continue\n"
+        "            [ \"$(<$i/carrier)\" = 1 ] || continue\n"
+        "            has_soft_rdma \"$i\" && break\n"
+        "            rdma link add \"${i}_$type\" type $type netdev \"$i\" && break\n"
+        "        done\n"
+        "        has_soft_rdma \"$i\" && echo $i\n"
+        "    )\n"
+        "}\n"
+        "\n"
+        "rxe_link=$(start_soft_rdma)\n"
+        "[[ \"$rxe_link\" ]] && get_ipv4_addr $rxe_link\n";
+
+    char script_filename[] = "/tmp/temp_scriptXXXXXX";
+    int fd = mkstemp(script_filename);
+    if (fd == -1) {
+        perror("Failed to create temporary file");
+        return 1;
+    }
+
+    FILE *fp = fdopen(fd, "w");
+    if (fp == NULL) {
+        perror("Failed to open file stream");
+        close(fd);
+        return 1;
+    }
+    fprintf(fp, "%s", script);
+    fclose(fp);
+
+    if (chmod(script_filename, 0700) == -1) {
+        perror("Failed to set execute permission");
+        return 1;
+    }
+
+    FILE *pipe = popen(script_filename, "r");
+    if (pipe == NULL) {
+        perror("Failed to run script");
+        return 1;
+    }
+
+    int idx = 0;
+    while (fgets(buffer + idx, 128 - idx, pipe) != NULL) {
+        idx += strlen(buffer);
+    }
+    if (buffer[idx - 1] == '\n')
+        buffer[idx - 1] = 0;
+
+    int status = pclose(pipe);
+    if (status == -1) {
+        perror("Error reported by pclose()");
+    } else if (!WIFEXITED(status)) {
+        printf("Script did not terminate normally\n");
+    }
+
+    remove(script_filename);
+
+    return 0;
+}
+
+static void test_precopy_rdma_plain(void)
+{
+    char buffer[128] = {};
+
+    if (new_rdma_link(buffer))
+        return;
+
+    g_autofree char *uri = g_strdup_printf("rdma:%s:7777", buffer);
+
+    MigrateCommon args = {
+        .listen_uri = uri,
+        .connect_uri = uri,
+    };
+
+    test_precopy_common(&args);
+}
+
 static void test_precopy_tcp_plain(void)
 {
     MigrateCommon args = {
@@ -968,6 +1067,10 @@  static void migration_test_add_precopy_smoke(MigrationTestEnv *env)
                        test_multifd_tcp_uri_none);
     migration_test_add("/migration/multifd/tcp/plain/cancel",
                        test_multifd_tcp_cancel);
+#ifdef CONFIG_RDMA
+    migration_test_add("/migration/precopy/rdma/plain",
+                       test_precopy_rdma_plain);
+#endif
 }
 
 void migration_test_add_precopy(MigrationTestEnv *env)