diff mbox series

[ovs-dev,v2] test-stream: Add ssl tests for stream open block

Message ID 80d50f66d4311415dad5d9852cd3af2493028fad.camel@cloudandheat.com
State Changes Requested
Headers show
Series [ovs-dev,v2] test-stream: Add ssl tests for stream open block | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/intel-ovs-compilation success test: success

Commit Message

Stefan Hoffmann April 27, 2023, 4:27 p.m. UTC
This tests stream.c and stream.py with ssl connection at
CHECK_STREAM_OPEN_BLOCK.
For the tests, ovsdb needs to be build with libssl.

Signed-off-by: Stefan Hoffmann <stefan.hoffmann@cloudandheat.com>
---
 tests/ovsdb-idl.at   | 45 +++++++++++++++++++++++++++++++++++++++-----
 tests/test-stream.c  | 12 +++++++++++-
 tests/test-stream.py |  6 ++++++
 3 files changed, 57 insertions(+), 6 deletions(-)

Comments

Stefan Hoffmann April 28, 2023, 8:16 a.m. UTC | #1
On Thu, 2023-04-27 at 18:27 +0200, Stefan Hoffmann wrote:
> This tests stream.c and stream.py with ssl connection at
> CHECK_STREAM_OPEN_BLOCK.
> For the tests, ovsdb needs to be build with libssl.
> 
> Signed-off-by: Stefan Hoffmann <stefan.hoffmann@cloudandheat.com>

Some questions from myself:

Are there other places, that should also get ssl or python checks and
should be included here? Maybe something (python) client related.

Besides that I add some comments to discuss my change below.

> ---
>  tests/ovsdb-idl.at   | 45 +++++++++++++++++++++++++++++++++++++++-----
>  tests/test-stream.c  | 12 +++++++++++-
>  tests/test-stream.py |  6 ++++++
>  3 files changed, 57 insertions(+), 6 deletions(-)
> 
> diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at
> index 5a7e76eaa..b40a167eb 100644
> --- a/tests/ovsdb-idl.at
> +++ b/tests/ovsdb-idl.at
> @@ -8,7 +8,15 @@ m4_divert_text([PREPARE_TESTS], [
>  # specified).
>  ovsdb_start_idltest () {
>      ovsdb-tool create db ${2:-$abs_srcdir/idltest.ovsschema} || return $?
> -    ovsdb-server -vconsole:warn --log-file --detach --no-chdir --pidfile --remote=punix:socket ${1:+--remote=$1} db || return $?
> +    SSL_FLAGS=""
> +    if [[ "${1::4}" == "pssl" ]]; then
> +      SSL_FLAGS="--private-key=$PKIDIR/testpki-privkey2.pem \
> +                 --certificate=$PKIDIR/testpki-cert2.pem \
> +                 --ca-cert=$PKIDIR/testpki-cacert.pem"
> +    fi
> +    ovsdb-server -vconsole:warn --log-file --detach --no-chdir --pidfile \
> +                 $SSL_FLAGS --remote=punix:socket ${1:+--remote=$1} \
> +                 db || return $?
>      on_exit 'kill `cat ovsdb-server.pid`'
>  }

I like this approach more than OVSDB_CHECK_IDL_SSL_PY, where the server
is started at the m4_define section, still I could move the server
start also to CHECK_STREAM_OPEN_BLOCK directly.

>  
> @@ -2279,14 +2287,35 @@ m4_define([CHECK_STREAM_OPEN_BLOCK],
>    [AT_SETUP([Check stream open block - $1 - $3])
>     AT_SKIP_IF([test "$3" = "tcp6" && test "$IS_WIN32" = "yes"])
>     AT_SKIP_IF([test "$3" = "tcp6" && test "$HAVE_IPV6" = "no"])
> +   AT_SKIP_IF([test "$3" = "ssl6" && test "$IS_WIN32" = "yes"])
> +   AT_SKIP_IF([test "$3" = "ssl6" && test "$HAVE_IPV6" = "no"])
> +   AT_SKIP_IF([test "$3" = "ssl" && test "$HAVE_OPENSSL" = "no"])
> +   $PYTHON3 -c "import ssl"
> +   SSL_PRESENT=$?
> +   AT_SKIP_IF([test "$3" = "ssl" && test $SSL_PRESENT != 0])
> +   AT_SKIP_IF([test "$3" = "ssl6" && test "$HAVE_OPENSSL" = "no"])
> +   AT_SKIP_IF([test "$3" = "ssl6" && test $SSL_PRESENT != 0])
>     AT_KEYWORDS([ovsdb server stream open_block $3])
> -   AT_CHECK([ovsdb_start_idltest "ptcp:0:$4"])
> +   PROTOCOL=$3
> +   PROTOCOL=${PROTOCOL::3}
> +   LISTEN_PROTOCOL=p$PROTOCOL
> +   PKIDIR=$abs_top_builddir/tests
> +   AT_CHECK([ovsdb_start_idltest "$LISTEN_PROTOCOL:0:$4"])
>     PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
>     WRONG_PORT=$(($TCP_PORT + 101))
> -   AT_CHECK([$2 tcp:$4:$TCP_PORT], [0], [ignore])
> -   AT_CHECK([$2 tcp:$4:$WRONG_PORT], [1], [ignore], [ignore])
> +   AT_CHECK([$2 $PROTOCOL:$4:$TCP_PORT \
> +             $PKIDIR/testpki-privkey.pem $PKIDIR/testpki-cert.pem \
> +             $PKIDIR/testpki-cacert.pem],
> +             [0], [ignore])
> +   AT_CHECK([$2 $PROTOCOL:$4:$WRONG_PORT \
> +             $PKIDIR/testpki-privkey.pem $PKIDIR/testpki-cert.pem \
> +             $PKIDIR/testpki-cacert.pem],
> +             [1], [ignore], [ignore])
>     OVSDB_SERVER_SHUTDOWN
> -   AT_CHECK([$2 tcp:$4:$TCP_PORT], [1], [ignore], [ignore])
> +   AT_CHECK([$2 $PROTOCOL:$4:$TCP_PORT \
> +             $PKIDIR/testpki-privkey.pem $PKIDIR/testpki-cert.pem \
> +             $PKIDIR/testpki-cacert.pem],
> +             [1], [ignore], [ignore])
>     AT_CLEANUP])
>  
>  CHECK_STREAM_OPEN_BLOCK([C], [test-stream], [tcp], [127.0.0.1])
> @@ -2295,6 +2324,12 @@ CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
>                          [tcp], [127.0.0.1])
>  CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
>                          [tcp6], [[[::1]]])
> +CHECK_STREAM_OPEN_BLOCK([C], [test-stream], [ssl], [127.0.0.1])
> +CHECK_STREAM_OPEN_BLOCK([C], [test-stream], [ssl6], [[[::1]]])
> +CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
> +                        [ssl], [127.0.0.1])
> +CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
> +                        [ssl6], [[[::1]]])

Is it fine to add the tests this way or should I build a new
CHECK_STREAM_OPEN_BLOCK_SSL (and maybe CHECK_STREAM_OPEN_BLOCK_TCP)
section and merge it together, like OVSDB_CHECK_IDL_SSL_PY to
OVSDB_CHECK_IDL.
Upside would be, that we check if python ssl module is installed only
at ssl tests, but we would need to define all the checks and server
management twice.

>  
>  # same as OVSDB_CHECK_IDL but uses Python IDL implementation with tcp
>  # with multiple remotes to assert the idl connects to the leader of the Raft cluster
> diff --git a/tests/test-stream.c b/tests/test-stream.c
> index 68ce2c544..fb4af58b9 100644
> --- a/tests/test-stream.c
> +++ b/tests/test-stream.c
> @@ -19,6 +19,7 @@
>  #include "fatal-signal.h"
>  #include "openvswitch/vlog.h"
>  #include "stream.h"
> +#include "stream-ssl.h"
>  #include "util.h"
>  
>  VLOG_DEFINE_THIS_MODULE(test_stream);
> @@ -33,7 +34,16 @@ main(int argc, char *argv[])
>      set_program_name(argv[0]);
>  
>      if (argc < 2) {
> -        ovs_fatal(0, "usage: %s REMOTE", argv[0]);
> +        ovs_fatal(0, "usage: %s REMOTE [SSL_KEY] [SSL_CERT] [SSL_CA]",
> +                  argv[0]);
> +    }
> +    if (strncmp("ssl:", argv[1], 4) == 0) {
> +        if (argc < 5) {
> +            ovs_fatal(0, "usage: %s REMOTE [SSL_KEY] [SSL_CERT] [SSL_CA]",
> +                      argv[0]);
> +        }
> +        stream_ssl_set_ca_cert_file(argv[4], false);
> +        stream_ssl_set_key_and_cert(argv[2], argv[3]);
>      }
>  
>      error = stream_open_block(stream_open(argv[1], &stream, DSCP_DEFAULT),
> diff --git a/tests/test-stream.py b/tests/test-stream.py
> index 93d63c019..73f26d0b9 100644
> --- a/tests/test-stream.py
> +++ b/tests/test-stream.py
> @@ -19,6 +19,12 @@ import ovs.stream
>  
>  def main(argv):
>      remote = argv[1]
> +
> +    if remote.startswith("ssl"):
> +        ovs.stream.SSLStream.ssl_set_ca_cert_file(argv[4])
> +        ovs.stream.SSLStream.ssl_set_certificate_file(argv[3])
> +        ovs.stream.SSLStream.ssl_set_private_key_file(argv[2])
> +
>      err, stream = ovs.stream.Stream.open_block(
>              ovs.stream.Stream.open(remote), 10000)
>
Ilya Maximets April 28, 2023, 1:12 p.m. UTC | #2
On 4/28/23 10:16, Stefan Hoffmann wrote:
> On Thu, 2023-04-27 at 18:27 +0200, Stefan Hoffmann wrote:
>> This tests stream.c and stream.py with ssl connection at
>> CHECK_STREAM_OPEN_BLOCK.
>> For the tests, ovsdb needs to be build with libssl.
>>
>> Signed-off-by: Stefan Hoffmann <stefan.hoffmann@cloudandheat.com>
> 
> Some questions from myself:
> 
> Are there other places, that should also get ssl or python checks and
> should be included here? Maybe something (python) client related.

Most of the tests are executed for both C and Python.  Python IDL doesn't
support some features that C implementation does, so some tests are not
running for Python.  There might be a few that are running for C only, but
can technically be checked with Python as well, but it needs some careful
read of the testsuite to find them.

We also have some tests for json and jsonrpc that are implemented for both
C and  Python in the similar way, but they are mostly abstracted from the
underlying stream implementation.

We don't have a lot of negative tests, i.e. failure simulations, but
these are hard to do.  Mocking is a way, but it's only for Python, so
should live in the pytest realm, I think.

> 
> Besides that I add some comments to discuss my change below.
> 
>> ---
>>  tests/ovsdb-idl.at   | 45 +++++++++++++++++++++++++++++++++++++++-----
>>  tests/test-stream.c  | 12 +++++++++++-
>>  tests/test-stream.py |  6 ++++++
>>  3 files changed, 57 insertions(+), 6 deletions(-)
>>
>> diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at
>> index 5a7e76eaa..b40a167eb 100644
>> --- a/tests/ovsdb-idl.at
>> +++ b/tests/ovsdb-idl.at
>> @@ -8,7 +8,15 @@ m4_divert_text([PREPARE_TESTS], [
>>  # specified).
>>  ovsdb_start_idltest () {
>>      ovsdb-tool create db ${2:-$abs_srcdir/idltest.ovsschema} || return $?
>> -    ovsdb-server -vconsole:warn --log-file --detach --no-chdir --pidfile --remote=punix:socket ${1:+--remote=$1} db || return $?
>> +    SSL_FLAGS=""
>> +    if [[ "${1::4}" == "pssl" ]]; then
>> +      SSL_FLAGS="--private-key=$PKIDIR/testpki-privkey2.pem \
>> +                 --certificate=$PKIDIR/testpki-cert2.pem \
>> +                 --ca-cert=$PKIDIR/testpki-cacert.pem"

We need to define the PKIDIR in this function for completeness,
I think.  Even though it is already defined in the CHECK_STREAM_OPEN_BLOCK,
it's not defined in other users of ovsdb_start_idltest.

>> +    fi
>> +    ovsdb-server -vconsole:warn --log-file --detach --no-chdir --pidfile \
>> +                 $SSL_FLAGS --remote=punix:socket ${1:+--remote=$1} \
>> +                 db || return $?
>>      on_exit 'kill `cat ovsdb-server.pid`'
>>  }
> 
> I like this approach more than OVSDB_CHECK_IDL_SSL_PY, where the server
> is started at the m4_define section, still I could move the server
> start also to CHECK_STREAM_OPEN_BLOCK directly.

It's actually better to avoid shell scripting if possible.  m4 macros
are playing much more nicely with the autotest.  Shell commands will
not be visible in the testsuite log and it's hard to figure out what
exactly failed in the shell script for that reason.

In this particular case, the code is split into a function to avoid
huge code duplication.  Ideally, though, this whole function should
be replaced with a macro definition and all the calls inside should
be wrapped into AT_CHECK, the 'if' should be an m4_if and the
${1:+--remote=$1} should be an m4_if as well.  And all the '\' should
be 'dnl' instead ('\' breaks command logging in the autotest).
Some things are just easier to write with shell scripts though.
Some things are just legacy code that doesn't worth being re-written
just for the looks or minor debugability improvements.

> 
>>  
>> @@ -2279,14 +2287,35 @@ m4_define([CHECK_STREAM_OPEN_BLOCK],
>>    [AT_SETUP([Check stream open block - $1 - $3])
>>     AT_SKIP_IF([test "$3" = "tcp6" && test "$IS_WIN32" = "yes"])
>>     AT_SKIP_IF([test "$3" = "tcp6" && test "$HAVE_IPV6" = "no"])
>> +   AT_SKIP_IF([test "$3" = "ssl6" && test "$IS_WIN32" = "yes"])
>> +   AT_SKIP_IF([test "$3" = "ssl6" && test "$HAVE_IPV6" = "no"])
>> +   AT_SKIP_IF([test "$3" = "ssl" && test "$HAVE_OPENSSL" = "no"])
>> +   $PYTHON3 -c "import ssl"
>> +   SSL_PRESENT=$?
>> +   AT_SKIP_IF([test "$3" = "ssl" && test $SSL_PRESENT != 0])
>> +   AT_SKIP_IF([test "$3" = "ssl6" && test "$HAVE_OPENSSL" = "no"])
>> +   AT_SKIP_IF([test "$3" = "ssl6" && test $SSL_PRESENT != 0])

We also need to check for a case where python is built without SSL
support.  OVSDB_CHECK_IDL_SSL_PY does that by trying to import ssl.

>>     AT_KEYWORDS([ovsdb server stream open_block $3])
>> -   AT_CHECK([ovsdb_start_idltest "ptcp:0:$4"])
>> +   PROTOCOL=$3
>> +   PROTOCOL=${PROTOCOL::3}
>> +   LISTEN_PROTOCOL=p$PROTOCOL
>> +   PKIDIR=$abs_top_builddir/tests
>> +   AT_CHECK([ovsdb_start_idltest "$LISTEN_PROTOCOL:0:$4"])
>>     PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
>>     WRONG_PORT=$(($TCP_PORT + 101))
>> -   AT_CHECK([$2 tcp:$4:$TCP_PORT], [0], [ignore])
>> -   AT_CHECK([$2 tcp:$4:$WRONG_PORT], [1], [ignore], [ignore])
>> +   AT_CHECK([$2 $PROTOCOL:$4:$TCP_PORT \
>> +             $PKIDIR/testpki-privkey.pem $PKIDIR/testpki-cert.pem \
>> +             $PKIDIR/testpki-cacert.pem],

These ssl args can be stored in a variable and re-used instead of
repeating them 3 times below.

>> +             [0], [ignore])
>> +   AT_CHECK([$2 $PROTOCOL:$4:$WRONG_PORT \
>> +             $PKIDIR/testpki-privkey.pem $PKIDIR/testpki-cert.pem \
>> +             $PKIDIR/testpki-cacert.pem],
>> +             [1], [ignore], [ignore])
>>     OVSDB_SERVER_SHUTDOWN
>> -   AT_CHECK([$2 tcp:$4:$TCP_PORT], [1], [ignore], [ignore])
>> +   AT_CHECK([$2 $PROTOCOL:$4:$TCP_PORT \
>> +             $PKIDIR/testpki-privkey.pem $PKIDIR/testpki-cert.pem \
>> +             $PKIDIR/testpki-cacert.pem],
>> +             [1], [ignore], [ignore])
>>     AT_CLEANUP])
>>  
>>  CHECK_STREAM_OPEN_BLOCK([C], [test-stream], [tcp], [127.0.0.1])
>> @@ -2295,6 +2324,12 @@ CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
>>                          [tcp], [127.0.0.1])
>>  CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
>>                          [tcp6], [[[::1]]])
>> +CHECK_STREAM_OPEN_BLOCK([C], [test-stream], [ssl], [127.0.0.1])
>> +CHECK_STREAM_OPEN_BLOCK([C], [test-stream], [ssl6], [[[::1]]])
>> +CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
>> +                        [ssl], [127.0.0.1])
>> +CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
>> +                        [ssl6], [[[::1]]])
> 
> Is it fine to add the tests this way or should I build a new
> CHECK_STREAM_OPEN_BLOCK_SSL (and maybe CHECK_STREAM_OPEN_BLOCK_TCP)
> section and merge it together, like OVSDB_CHECK_IDL_SSL_PY to
> OVSDB_CHECK_IDL.
> Upside would be, that we check if python ssl module is installed only
> at ssl tests, but we would need to define all the checks and server
> management twice.

The reason why the OVSDB_CHECK_IDL consists of many separate implementations
is that most of them are sightly different, i.e. use different flags, or
different programs with different format of arguments.  In the stream test
we're designing test programs from scratch and it's easier to have formats
exactly the same.

We could have combined implementations of OVSDB_CHECK_IDL by adding more
checks and arguments, but, in general, it's just a trade-off between code
duplication and code complexity.  As long as we do not copy-paste huge chunks
of code, it's fine to duplicate a little for the sake of readability.

> 
>>  
>>  # same as OVSDB_CHECK_IDL but uses Python IDL implementation with tcp
>>  # with multiple remotes to assert the idl connects to the leader of the Raft cluster
>> diff --git a/tests/test-stream.c b/tests/test-stream.c
>> index 68ce2c544..fb4af58b9 100644
>> --- a/tests/test-stream.c
>> +++ b/tests/test-stream.c
>> @@ -19,6 +19,7 @@
>>  #include "fatal-signal.h"
>>  #include "openvswitch/vlog.h"
>>  #include "stream.h"
>> +#include "stream-ssl.h"
>>  #include "util.h"
>>  
>>  VLOG_DEFINE_THIS_MODULE(test_stream);
>> @@ -33,7 +34,16 @@ main(int argc, char *argv[])
>>      set_program_name(argv[0]);
>>  
>>      if (argc < 2) {
>> -        ovs_fatal(0, "usage: %s REMOTE", argv[0]);
>> +        ovs_fatal(0, "usage: %s REMOTE [SSL_KEY] [SSL_CERT] [SSL_CA]",
>> +                  argv[0]);
>> +    }
>> +    if (strncmp("ssl:", argv[1], 4) == 0) {
>> +        if (argc < 5) {
>> +            ovs_fatal(0, "usage: %s REMOTE [SSL_KEY] [SSL_CERT] [SSL_CA]",
>> +                      argv[0]);
>> +        }
>> +        stream_ssl_set_ca_cert_file(argv[4], false);
>> +        stream_ssl_set_key_and_cert(argv[2], argv[3]);
>>      }
>>  
>>      error = stream_open_block(stream_open(argv[1], &stream, DSCP_DEFAULT),
>> diff --git a/tests/test-stream.py b/tests/test-stream.py
>> index 93d63c019..73f26d0b9 100644
>> --- a/tests/test-stream.py
>> +++ b/tests/test-stream.py
>> @@ -19,6 +19,12 @@ import ovs.stream
>>  
>>  def main(argv):
>>      remote = argv[1]
>> +
>> +    if remote.startswith("ssl"):
>> +        ovs.stream.SSLStream.ssl_set_ca_cert_file(argv[4])
>> +        ovs.stream.SSLStream.ssl_set_certificate_file(argv[3])
>> +        ovs.stream.SSLStream.ssl_set_private_key_file(argv[2])

We should, probably, add a semicolon to the match and have similar
argument count checks here as we do in the C version.

Best regards, Ilya Maximets.
Stefan Hoffmann April 28, 2023, 1:27 p.m. UTC | #3
On Fri, 2023-04-28 at 15:12 +0200, Ilya Maximets wrote:
> On 4/28/23 10:16, Stefan Hoffmann wrote:
> > On Thu, 2023-04-27 at 18:27 +0200, Stefan Hoffmann wrote:
> > > This tests stream.c and stream.py with ssl connection at
> > > CHECK_STREAM_OPEN_BLOCK.
> > > For the tests, ovsdb needs to be build with libssl.
> > > 
> > > Signed-off-by: Stefan Hoffmann <stefan.hoffmann@cloudandheat.com>
> > 
> > Some questions from myself:
> > 
> > Are there other places, that should also get ssl or python checks and
> > should be included here? Maybe something (python) client related.
> 
> Most of the tests are executed for both C and Python.  Python IDL doesn't
> support some features that C implementation does, so some tests are not
> running for Python.  There might be a few that are running for C only, but
> can technically be checked with Python as well, but it needs some careful
> read of the testsuite to find them.
> 
> We also have some tests for json and jsonrpc that are implemented for both
> C and  Python in the similar way, but they are mostly abstracted from the
> underlying stream implementation.
> 
> We don't have a lot of negative tests, i.e. failure simulations, but
> these are hard to do.  Mocking is a way, but it's only for Python, so
> should live in the pytest realm, I think.
> 
> > 
> > Besides that I add some comments to discuss my change below.
> > 
> > > ---
> > >  tests/ovsdb-idl.at   | 45 +++++++++++++++++++++++++++++++++++++++-----
> > >  tests/test-stream.c  | 12 +++++++++++-
> > >  tests/test-stream.py |  6 ++++++
> > >  3 files changed, 57 insertions(+), 6 deletions(-)
> > > 
> > > diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at
> > > index 5a7e76eaa..b40a167eb 100644
> > > --- a/tests/ovsdb-idl.at
> > > +++ b/tests/ovsdb-idl.at
> > > @@ -8,7 +8,15 @@ m4_divert_text([PREPARE_TESTS], [
> > >  # specified).
> > >  ovsdb_start_idltest () {
> > >      ovsdb-tool create db ${2:-$abs_srcdir/idltest.ovsschema} || return $?
> > > -    ovsdb-server -vconsole:warn --log-file --detach --no-chdir --pidfile --remote=punix:socket ${1:+--remote=$1} db || return $?
> > > +    SSL_FLAGS=""
> > > +    if [[ "${1::4}" == "pssl" ]]; then
> > > +      SSL_FLAGS="--private-key=$PKIDIR/testpki-privkey2.pem \
> > > +                 --certificate=$PKIDIR/testpki-cert2.pem \
> > > +                 --ca-cert=$PKIDIR/testpki-cacert.pem"
> 
> We need to define the PKIDIR in this function for completeness,
> I think.  Even though it is already defined in the CHECK_STREAM_OPEN_BLOCK,
> it's not defined in other users of ovsdb_start_idltest.
> 
> > > +    fi
> > > +    ovsdb-server -vconsole:warn --log-file --detach --no-chdir --pidfile \
> > > +                 $SSL_FLAGS --remote=punix:socket ${1:+--remote=$1} \
> > > +                 db || return $?
> > >      on_exit 'kill `cat ovsdb-server.pid`'
> > >  }
> > 
> > I like this approach more than OVSDB_CHECK_IDL_SSL_PY, where the server
> > is started at the m4_define section, still I could move the server
> > start also to CHECK_STREAM_OPEN_BLOCK directly.
> 
> It's actually better to avoid shell scripting if possible.  m4 macros
> are playing much more nicely with the autotest.  Shell commands will
> not be visible in the testsuite log and it's hard to figure out what
> exactly failed in the shell script for that reason.
> 
> In this particular case, the code is split into a function to avoid
> huge code duplication.  Ideally, though, this whole function should
> be replaced with a macro definition and all the calls inside should
> be wrapped into AT_CHECK, the 'if' should be an m4_if and the
> ${1:+--remote=$1} should be an m4_if as well.  And all the '\' should
> be 'dnl' instead ('\' breaks command logging in the autotest).
> Some things are just easier to write with shell scripts though.
> Some things are just legacy code that doesn't worth being re-written
> just for the looks or minor debugability improvements.

Thanks for explaining.
Should I introduce a m4 macro than, that we can use later to replace
ovsdb_start_idltest or keep it, as it is for now?

> 
> > 
> > >  
> > > @@ -2279,14 +2287,35 @@ m4_define([CHECK_STREAM_OPEN_BLOCK],
> > >    [AT_SETUP([Check stream open block - $1 - $3])
> > >     AT_SKIP_IF([test "$3" = "tcp6" && test "$IS_WIN32" = "yes"])
> > >     AT_SKIP_IF([test "$3" = "tcp6" && test "$HAVE_IPV6" = "no"])
> > > +   AT_SKIP_IF([test "$3" = "ssl6" && test "$IS_WIN32" = "yes"])
> > > +   AT_SKIP_IF([test "$3" = "ssl6" && test "$HAVE_IPV6" = "no"])
> > > +   AT_SKIP_IF([test "$3" = "ssl" && test "$HAVE_OPENSSL" = "no"])
> > > +   $PYTHON3 -c "import ssl"
> > > +   SSL_PRESENT=$?
> > > +   AT_SKIP_IF([test "$3" = "ssl" && test $SSL_PRESENT != 0])
> > > +   AT_SKIP_IF([test "$3" = "ssl6" && test "$HAVE_OPENSSL" = "no"])
> > > +   AT_SKIP_IF([test "$3" = "ssl6" && test $SSL_PRESENT != 0])
> 
> We also need to check for a case where python is built without SSL
> support.  OVSDB_CHECK_IDL_SSL_PY does that by trying to import ssl.

This is done 5 lines above, I copied that part from
OVSDB_CHECK_IDL_SSL_PY.

> 
> > >     AT_KEYWORDS([ovsdb server stream open_block $3])
> > > -   AT_CHECK([ovsdb_start_idltest "ptcp:0:$4"])
> > > +   PROTOCOL=$3
> > > +   PROTOCOL=${PROTOCOL::3}
> > > +   LISTEN_PROTOCOL=p$PROTOCOL
> > > +   PKIDIR=$abs_top_builddir/tests
> > > +   AT_CHECK([ovsdb_start_idltest "$LISTEN_PROTOCOL:0:$4"])
> > >     PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
> > >     WRONG_PORT=$(($TCP_PORT + 101))
> > > -   AT_CHECK([$2 tcp:$4:$TCP_PORT], [0], [ignore])
> > > -   AT_CHECK([$2 tcp:$4:$WRONG_PORT], [1], [ignore], [ignore])
> > > +   AT_CHECK([$2 $PROTOCOL:$4:$TCP_PORT \
> > > +             $PKIDIR/testpki-privkey.pem $PKIDIR/testpki-cert.pem \
> > > +             $PKIDIR/testpki-cacert.pem],
> 
> These ssl args can be stored in a variable and re-used instead of
> repeating them 3 times below.
> 
> > > +             [0], [ignore])
> > > +   AT_CHECK([$2 $PROTOCOL:$4:$WRONG_PORT \
> > > +             $PKIDIR/testpki-privkey.pem $PKIDIR/testpki-cert.pem \
> > > +             $PKIDIR/testpki-cacert.pem],
> > > +             [1], [ignore], [ignore])
> > >     OVSDB_SERVER_SHUTDOWN
> > > -   AT_CHECK([$2 tcp:$4:$TCP_PORT], [1], [ignore], [ignore])
> > > +   AT_CHECK([$2 $PROTOCOL:$4:$TCP_PORT \
> > > +             $PKIDIR/testpki-privkey.pem $PKIDIR/testpki-cert.pem \
> > > +             $PKIDIR/testpki-cacert.pem],
> > > +             [1], [ignore], [ignore])
> > >     AT_CLEANUP])
> > >  
> > >  CHECK_STREAM_OPEN_BLOCK([C], [test-stream], [tcp], [127.0.0.1])
> > > @@ -2295,6 +2324,12 @@ CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
> > >                          [tcp], [127.0.0.1])
> > >  CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
> > >                          [tcp6], [[[::1]]])
> > > +CHECK_STREAM_OPEN_BLOCK([C], [test-stream], [ssl], [127.0.0.1])
> > > +CHECK_STREAM_OPEN_BLOCK([C], [test-stream], [ssl6], [[[::1]]])
> > > +CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
> > > +                        [ssl], [127.0.0.1])
> > > +CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
> > > +                        [ssl6], [[[::1]]])
> > 
> > Is it fine to add the tests this way or should I build a new
> > CHECK_STREAM_OPEN_BLOCK_SSL (and maybe CHECK_STREAM_OPEN_BLOCK_TCP)
> > section and merge it together, like OVSDB_CHECK_IDL_SSL_PY to
> > OVSDB_CHECK_IDL.
> > Upside would be, that we check if python ssl module is installed only
> > at ssl tests, but we would need to define all the checks and server
> > management twice.
> 
> The reason why the OVSDB_CHECK_IDL consists of many separate implementations
> is that most of them are sightly different, i.e. use different flags, or
> different programs with different format of arguments.  In the stream test
> we're designing test programs from scratch and it's easier to have formats
> exactly the same.
> 
> We could have combined implementations of OVSDB_CHECK_IDL by adding more
> checks and arguments, but, in general, it's just a trade-off between code
> duplication and code complexity.  As long as we do not copy-paste huge chunks
> of code, it's fine to duplicate a little for the sake of readability.
> 
> > 
> > >  
> > >  # same as OVSDB_CHECK_IDL but uses Python IDL implementation with tcp
> > >  # with multiple remotes to assert the idl connects to the leader of the Raft cluster
> > > diff --git a/tests/test-stream.c b/tests/test-stream.c
> > > index 68ce2c544..fb4af58b9 100644
> > > --- a/tests/test-stream.c
> > > +++ b/tests/test-stream.c
> > > @@ -19,6 +19,7 @@
> > >  #include "fatal-signal.h"
> > >  #include "openvswitch/vlog.h"
> > >  #include "stream.h"
> > > +#include "stream-ssl.h"
> > >  #include "util.h"
> > >  
> > >  VLOG_DEFINE_THIS_MODULE(test_stream);
> > > @@ -33,7 +34,16 @@ main(int argc, char *argv[])
> > >      set_program_name(argv[0]);
> > >  
> > >      if (argc < 2) {
> > > -        ovs_fatal(0, "usage: %s REMOTE", argv[0]);
> > > +        ovs_fatal(0, "usage: %s REMOTE [SSL_KEY] [SSL_CERT] [SSL_CA]",
> > > +                  argv[0]);
> > > +    }
> > > +    if (strncmp("ssl:", argv[1], 4) == 0) {
> > > +        if (argc < 5) {
> > > +            ovs_fatal(0, "usage: %s REMOTE [SSL_KEY] [SSL_CERT] [SSL_CA]",
> > > +                      argv[0]);
> > > +        }
> > > +        stream_ssl_set_ca_cert_file(argv[4], false);
> > > +        stream_ssl_set_key_and_cert(argv[2], argv[3]);
> > >      }
> > >  
> > >      error = stream_open_block(stream_open(argv[1], &stream, DSCP_DEFAULT),
> > > diff --git a/tests/test-stream.py b/tests/test-stream.py
> > > index 93d63c019..73f26d0b9 100644
> > > --- a/tests/test-stream.py
> > > +++ b/tests/test-stream.py
> > > @@ -19,6 +19,12 @@ import ovs.stream
> > >  
> > >  def main(argv):
> > >      remote = argv[1]
> > > +
> > > +    if remote.startswith("ssl"):
> > > +        ovs.stream.SSLStream.ssl_set_ca_cert_file(argv[4])
> > > +        ovs.stream.SSLStream.ssl_set_certificate_file(argv[3])
> > > +        ovs.stream.SSLStream.ssl_set_private_key_file(argv[2])
> 
> We should, probably, add a semicolon to the match and have similar
> argument count checks here as we do in the C version.
> 
> Best regards, Ilya Maximets.

Thanks,
Stefan
Ilya Maximets April 28, 2023, 4:09 p.m. UTC | #4
On 4/28/23 15:27, Stefan Hoffmann wrote:
> On Fri, 2023-04-28 at 15:12 +0200, Ilya Maximets wrote:
>> On 4/28/23 10:16, Stefan Hoffmann wrote:
>>> On Thu, 2023-04-27 at 18:27 +0200, Stefan Hoffmann wrote:
>>>> This tests stream.c and stream.py with ssl connection at
>>>> CHECK_STREAM_OPEN_BLOCK.
>>>> For the tests, ovsdb needs to be build with libssl.
>>>>
>>>> Signed-off-by: Stefan Hoffmann <stefan.hoffmann@cloudandheat.com>
>>>
>>> Some questions from myself:
>>>
>>> Are there other places, that should also get ssl or python checks and
>>> should be included here? Maybe something (python) client related.
>>
>> Most of the tests are executed for both C and Python.  Python IDL doesn't
>> support some features that C implementation does, so some tests are not
>> running for Python.  There might be a few that are running for C only, but
>> can technically be checked with Python as well, but it needs some careful
>> read of the testsuite to find them.
>>
>> We also have some tests for json and jsonrpc that are implemented for both
>> C and  Python in the similar way, but they are mostly abstracted from the
>> underlying stream implementation.
>>
>> We don't have a lot of negative tests, i.e. failure simulations, but
>> these are hard to do.  Mocking is a way, but it's only for Python, so
>> should live in the pytest realm, I think.
>>
>>>
>>> Besides that I add some comments to discuss my change below.
>>>
>>>> ---
>>>>  tests/ovsdb-idl.at   | 45 +++++++++++++++++++++++++++++++++++++++-----
>>>>  tests/test-stream.c  | 12 +++++++++++-
>>>>  tests/test-stream.py |  6 ++++++
>>>>  3 files changed, 57 insertions(+), 6 deletions(-)
>>>>
>>>> diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at
>>>> index 5a7e76eaa..b40a167eb 100644
>>>> --- a/tests/ovsdb-idl.at
>>>> +++ b/tests/ovsdb-idl.at
>>>> @@ -8,7 +8,15 @@ m4_divert_text([PREPARE_TESTS], [
>>>>  # specified).
>>>>  ovsdb_start_idltest () {
>>>>      ovsdb-tool create db ${2:-$abs_srcdir/idltest.ovsschema} || return $?
>>>> -    ovsdb-server -vconsole:warn --log-file --detach --no-chdir --pidfile --remote=punix:socket ${1:+--remote=$1} db || return $?
>>>> +    SSL_FLAGS=""
>>>> +    if [[ "${1::4}" == "pssl" ]]; then
>>>> +      SSL_FLAGS="--private-key=$PKIDIR/testpki-privkey2.pem \
>>>> +                 --certificate=$PKIDIR/testpki-cert2.pem \
>>>> +                 --ca-cert=$PKIDIR/testpki-cacert.pem"
>>
>> We need to define the PKIDIR in this function for completeness,
>> I think.  Even though it is already defined in the CHECK_STREAM_OPEN_BLOCK,
>> it's not defined in other users of ovsdb_start_idltest.
>>
>>>> +    fi
>>>> +    ovsdb-server -vconsole:warn --log-file --detach --no-chdir --pidfile \
>>>> +                 $SSL_FLAGS --remote=punix:socket ${1:+--remote=$1} \
>>>> +                 db || return $?
>>>>      on_exit 'kill `cat ovsdb-server.pid`'
>>>>  }
>>>
>>> I like this approach more than OVSDB_CHECK_IDL_SSL_PY, where the server
>>> is started at the m4_define section, still I could move the server
>>> start also to CHECK_STREAM_OPEN_BLOCK directly.
>>
>> It's actually better to avoid shell scripting if possible.  m4 macros
>> are playing much more nicely with the autotest.  Shell commands will
>> not be visible in the testsuite log and it's hard to figure out what
>> exactly failed in the shell script for that reason.
>>
>> In this particular case, the code is split into a function to avoid
>> huge code duplication.  Ideally, though, this whole function should
>> be replaced with a macro definition and all the calls inside should
>> be wrapped into AT_CHECK, the 'if' should be an m4_if and the
>> ${1:+--remote=$1} should be an m4_if as well.  And all the '\' should
>> be 'dnl' instead ('\' breaks command logging in the autotest).
>> Some things are just easier to write with shell scripts though.
>> Some things are just legacy code that doesn't worth being re-written
>> just for the looks or minor debugability improvements.
> 
> Thanks for explaining.
> Should I introduce a m4 macro than, that we can use later to replace
> ovsdb_start_idltest or keep it, as it is for now?

I'd say either keep the shell function as is or replace it entirely
with a macro.  See the OVSDB_CLUSTER_START_IDLTEST for example.
Replacing will require changes in all the callers.  And the macro
should be defined outside of m4_divert_text.

Both options are fine by me.  In case of replacing, you may turn the
change into a patch set with the first patch preforming some ground
work on converting the function into a macro and the second patch
adding SSL stream tests.

Half-measure of introducing the macro but keeping the shell function
doesn't sound great to me.

> 
>>
>>>
>>>>  
>>>> @@ -2279,14 +2287,35 @@ m4_define([CHECK_STREAM_OPEN_BLOCK],
>>>>    [AT_SETUP([Check stream open block - $1 - $3])
>>>>     AT_SKIP_IF([test "$3" = "tcp6" && test "$IS_WIN32" = "yes"])
>>>>     AT_SKIP_IF([test "$3" = "tcp6" && test "$HAVE_IPV6" = "no"])
>>>> +   AT_SKIP_IF([test "$3" = "ssl6" && test "$IS_WIN32" = "yes"])
>>>> +   AT_SKIP_IF([test "$3" = "ssl6" && test "$HAVE_IPV6" = "no"])
>>>> +   AT_SKIP_IF([test "$3" = "ssl" && test "$HAVE_OPENSSL" = "no"])
>>>> +   $PYTHON3 -c "import ssl"
>>>> +   SSL_PRESENT=$?
>>>> +   AT_SKIP_IF([test "$3" = "ssl" && test $SSL_PRESENT != 0])
>>>> +   AT_SKIP_IF([test "$3" = "ssl6" && test "$HAVE_OPENSSL" = "no"])
>>>> +   AT_SKIP_IF([test "$3" = "ssl6" && test $SSL_PRESENT != 0])
>>
>> We also need to check for a case where python is built without SSL
>> support.  OVSDB_CHECK_IDL_SSL_PY does that by trying to import ssl.
> 
> This is done 5 lines above, I copied that part from
> OVSDB_CHECK_IDL_SSL_PY.


Oh, sorry.  Missed that part.

Best regards, Ilya Maximets.
diff mbox series

Patch

diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at
index 5a7e76eaa..b40a167eb 100644
--- a/tests/ovsdb-idl.at
+++ b/tests/ovsdb-idl.at
@@ -8,7 +8,15 @@  m4_divert_text([PREPARE_TESTS], [
 # specified).
 ovsdb_start_idltest () {
     ovsdb-tool create db ${2:-$abs_srcdir/idltest.ovsschema} || return $?
-    ovsdb-server -vconsole:warn --log-file --detach --no-chdir --pidfile --remote=punix:socket ${1:+--remote=$1} db || return $?
+    SSL_FLAGS=""
+    if [[ "${1::4}" == "pssl" ]]; then
+      SSL_FLAGS="--private-key=$PKIDIR/testpki-privkey2.pem \
+                 --certificate=$PKIDIR/testpki-cert2.pem \
+                 --ca-cert=$PKIDIR/testpki-cacert.pem"
+    fi
+    ovsdb-server -vconsole:warn --log-file --detach --no-chdir --pidfile \
+                 $SSL_FLAGS --remote=punix:socket ${1:+--remote=$1} \
+                 db || return $?
     on_exit 'kill `cat ovsdb-server.pid`'
 }
 
@@ -2279,14 +2287,35 @@  m4_define([CHECK_STREAM_OPEN_BLOCK],
   [AT_SETUP([Check stream open block - $1 - $3])
    AT_SKIP_IF([test "$3" = "tcp6" && test "$IS_WIN32" = "yes"])
    AT_SKIP_IF([test "$3" = "tcp6" && test "$HAVE_IPV6" = "no"])
+   AT_SKIP_IF([test "$3" = "ssl6" && test "$IS_WIN32" = "yes"])
+   AT_SKIP_IF([test "$3" = "ssl6" && test "$HAVE_IPV6" = "no"])
+   AT_SKIP_IF([test "$3" = "ssl" && test "$HAVE_OPENSSL" = "no"])
+   $PYTHON3 -c "import ssl"
+   SSL_PRESENT=$?
+   AT_SKIP_IF([test "$3" = "ssl" && test $SSL_PRESENT != 0])
+   AT_SKIP_IF([test "$3" = "ssl6" && test "$HAVE_OPENSSL" = "no"])
+   AT_SKIP_IF([test "$3" = "ssl6" && test $SSL_PRESENT != 0])
    AT_KEYWORDS([ovsdb server stream open_block $3])
-   AT_CHECK([ovsdb_start_idltest "ptcp:0:$4"])
+   PROTOCOL=$3
+   PROTOCOL=${PROTOCOL::3}
+   LISTEN_PROTOCOL=p$PROTOCOL
+   PKIDIR=$abs_top_builddir/tests
+   AT_CHECK([ovsdb_start_idltest "$LISTEN_PROTOCOL:0:$4"])
    PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
    WRONG_PORT=$(($TCP_PORT + 101))
-   AT_CHECK([$2 tcp:$4:$TCP_PORT], [0], [ignore])
-   AT_CHECK([$2 tcp:$4:$WRONG_PORT], [1], [ignore], [ignore])
+   AT_CHECK([$2 $PROTOCOL:$4:$TCP_PORT \
+             $PKIDIR/testpki-privkey.pem $PKIDIR/testpki-cert.pem \
+             $PKIDIR/testpki-cacert.pem],
+             [0], [ignore])
+   AT_CHECK([$2 $PROTOCOL:$4:$WRONG_PORT \
+             $PKIDIR/testpki-privkey.pem $PKIDIR/testpki-cert.pem \
+             $PKIDIR/testpki-cacert.pem],
+             [1], [ignore], [ignore])
    OVSDB_SERVER_SHUTDOWN
-   AT_CHECK([$2 tcp:$4:$TCP_PORT], [1], [ignore], [ignore])
+   AT_CHECK([$2 $PROTOCOL:$4:$TCP_PORT \
+             $PKIDIR/testpki-privkey.pem $PKIDIR/testpki-cert.pem \
+             $PKIDIR/testpki-cacert.pem],
+             [1], [ignore], [ignore])
    AT_CLEANUP])
 
 CHECK_STREAM_OPEN_BLOCK([C], [test-stream], [tcp], [127.0.0.1])
@@ -2295,6 +2324,12 @@  CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
                         [tcp], [127.0.0.1])
 CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
                         [tcp6], [[[::1]]])
+CHECK_STREAM_OPEN_BLOCK([C], [test-stream], [ssl], [127.0.0.1])
+CHECK_STREAM_OPEN_BLOCK([C], [test-stream], [ssl6], [[[::1]]])
+CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
+                        [ssl], [127.0.0.1])
+CHECK_STREAM_OPEN_BLOCK([Python3], [$PYTHON3 $srcdir/test-stream.py],
+                        [ssl6], [[[::1]]])
 
 # same as OVSDB_CHECK_IDL but uses Python IDL implementation with tcp
 # with multiple remotes to assert the idl connects to the leader of the Raft cluster
diff --git a/tests/test-stream.c b/tests/test-stream.c
index 68ce2c544..fb4af58b9 100644
--- a/tests/test-stream.c
+++ b/tests/test-stream.c
@@ -19,6 +19,7 @@ 
 #include "fatal-signal.h"
 #include "openvswitch/vlog.h"
 #include "stream.h"
+#include "stream-ssl.h"
 #include "util.h"
 
 VLOG_DEFINE_THIS_MODULE(test_stream);
@@ -33,7 +34,16 @@  main(int argc, char *argv[])
     set_program_name(argv[0]);
 
     if (argc < 2) {
-        ovs_fatal(0, "usage: %s REMOTE", argv[0]);
+        ovs_fatal(0, "usage: %s REMOTE [SSL_KEY] [SSL_CERT] [SSL_CA]",
+                  argv[0]);
+    }
+    if (strncmp("ssl:", argv[1], 4) == 0) {
+        if (argc < 5) {
+            ovs_fatal(0, "usage: %s REMOTE [SSL_KEY] [SSL_CERT] [SSL_CA]",
+                      argv[0]);
+        }
+        stream_ssl_set_ca_cert_file(argv[4], false);
+        stream_ssl_set_key_and_cert(argv[2], argv[3]);
     }
 
     error = stream_open_block(stream_open(argv[1], &stream, DSCP_DEFAULT),
diff --git a/tests/test-stream.py b/tests/test-stream.py
index 93d63c019..73f26d0b9 100644
--- a/tests/test-stream.py
+++ b/tests/test-stream.py
@@ -19,6 +19,12 @@  import ovs.stream
 
 def main(argv):
     remote = argv[1]
+
+    if remote.startswith("ssl"):
+        ovs.stream.SSLStream.ssl_set_ca_cert_file(argv[4])
+        ovs.stream.SSLStream.ssl_set_certificate_file(argv[3])
+        ovs.stream.SSLStream.ssl_set_private_key_file(argv[2])
+
     err, stream = ovs.stream.Stream.open_block(
             ovs.stream.Stream.open(remote), 10000)