lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/xnet/virtnet/virtnet_test.go (about)

     1  // Copyright (C) 2018-2020  Nexedi SA and Contributors.
     2  //                          Kirill Smelkov <kirr@nexedi.com>
     3  //
     4  // This program is free software: you can Use, Study, Modify and Redistribute
     5  // it under the terms of the GNU General Public License version 3, or (at your
     6  // option) any later version, as published by the Free Software Foundation.
     7  //
     8  // You can also Link and Combine this program with other software covered by
     9  // the terms of any of the Free Software licenses or any of the Open Source
    10  // Initiative approved licenses and Convey the resulting work. Corresponding
    11  // source of such a combination shall include the source code for all other
    12  // software used.
    13  //
    14  // This program is distributed WITHOUT ANY WARRANTY; without even the implied
    15  // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    16  //
    17  // See COPYING file for full licensing terms.
    18  // See https://www.nexedi.com/licensing for rationale and options.
    19  
    20  package virtnet_test
    21  
    22  import (
    23  	"context"
    24  	"io"
    25  	"net"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	"golang.org/x/sync/errgroup"
    31  
    32  	"lab.nexedi.com/kirr/go123/exc"
    33  	"lab.nexedi.com/kirr/go123/internal/xtesting"
    34  	"lab.nexedi.com/kirr/go123/xnet"
    35  	"lab.nexedi.com/kirr/go123/xnet/pipenet"
    36  	. "lab.nexedi.com/kirr/go123/xnet/virtnet"
    37  
    38  	"github.com/pkg/errors"
    39  )
    40  
    41  // testNet is testing network environment.
    42  //
    43  // It consists of a subnetwork backed by pipenet with 2 hosts: hα and hβ. On
    44  // both hosts a listener is started at "" (i.e. it will have ":1" address).
    45  // There is a connection established in between α:2-β:2.
    46  type testNet struct {
    47  	testing.TB
    48  
    49  	net      *SubNetwork
    50  	hα, hβ   *Host
    51  	lα, lβ   xnet.Listener
    52  	cαβ, cβα net.Conn
    53  }
    54  
    55  // newTestNet creates new testing network environment.
    56  func newTestNet(t0 testing.TB) *testNet {
    57  	t := &testNet{TB: t0}
    58  	t.Helper()
    59  
    60  	var err error
    61  	t.net = pipenet.AsVirtNet(pipenet.New("t"))
    62  	t.hα, err = t.net.NewHost(context.Background(), "α")
    63  	if err != nil {
    64  		t.Fatal(err)
    65  	}
    66  	t.hβ, err = t.net.NewHost(context.Background(), "β")
    67  	if err != nil {
    68  		t.Fatal(err)
    69  	}
    70  	t.lα, err = t.hα.Listen(context.Background(), "")
    71  	if err != nil {
    72  		t.Fatal(err)
    73  	}
    74  	t.lβ, err = t.hβ.Listen(context.Background(), "")
    75  	if err != nil {
    76  		t.Fatal(err)
    77  	}
    78  
    79  	// preestablish α:2-β:2 connection
    80  	wg := &errgroup.Group{}
    81  	defer func() {
    82  		err := wg.Wait()
    83  		if err != nil {
    84  			t.Fatal(err)
    85  		}
    86  	}()
    87  
    88  	wg.Go(func() error {
    89  		c, err := t.lβ.Accept(context.Background())
    90  		if err != nil {
    91  			return err
    92  		}
    93  		t.cβα = c
    94  		return nil
    95  	})
    96  
    97  	c, err := t.hα.Dial(context.Background(), "β:1")
    98  	if err != nil {
    99  		t.Fatal(err)
   100  	}
   101  	t.cαβ = c
   102  
   103  	return t
   104  }
   105  
   106  // xneterr constructs net.OpError for testNet network.
   107  //
   108  // if addr is of form "α:1" - only .Addr is set.
   109  // if addr is of form "α:1->β:1" - both .Source and .Addr are set.
   110  func xneterr(op, addr string, err error) *net.OpError {
   111  	addrv := strings.Split(addr, "->")
   112  	if len(addrv) > 2 {
   113  		exc.Raisef("xneterr: invalid addr %q", addr)
   114  	}
   115  
   116  	operr := &net.OpError{
   117  		Op:  op,
   118  		Net: "pipet", // matches newTestNet
   119  		Err: err,
   120  	}
   121  
   122  	for i, addr := range addrv {
   123  		a, e := ParseAddr("pipet", addr)
   124  		exc.Raiseif(e)
   125  
   126  		if i == len(addrv)-1 {
   127  			operr.Addr = a
   128  		} else {
   129  			operr.Source = a
   130  		}
   131  	}
   132  
   133  	return operr
   134  }
   135  
   136  // xobject lookups testNet object by name.
   137  func (t *testNet) xobject(name string) io.Closer {
   138  	switch name {
   139  	case "subnet":	return t.net
   140  	case "hα":	return t.hα
   141  	case "hβ":	return t.hβ
   142  	case "lα":	return t.lα
   143  	case "lβ":	return t.lβ
   144  	case "cαβ":	return t.cαβ
   145  	case "cβα":	return t.cβα
   146  	}
   147  
   148  	exc.Raisef("invalid object: %q", name)
   149  	panic(0)
   150  }
   151  
   152  type testFlag int
   153  const serialOnly testFlag = 1
   154  
   155  // testClose verifies object.Close vs test func.
   156  //
   157  // object to close is specified by name, e.g. "hβ". test func should try to do
   158  // an action and verify it gets expected error given object is closed.
   159  //
   160  // two scenarios are verified:
   161  //
   162  //   - serial case: first close, then test, and
   163  //   - concurrent case: close is run in parallel to test.
   164  //
   165  // if concurrent case is not applicable for test (e.g. it tries to run a
   166  // function that does not block, like e.g. NewHost in pipenet case), it can be
   167  // disabled via passing optional serialOnly flag.
   168  func testClose(t0 testing.TB, object string, test func(*testNet), flagv ...testFlag) {
   169  	t0.Helper()
   170  
   171  	// serial case
   172  	t := newTestNet(t0)
   173  	obj := t.xobject(object)
   174  
   175  	err := obj.Close()
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  	test(t)
   180  
   181  	if len(flagv) > 0 && flagv[0] == serialOnly {
   182  		return
   183  	}
   184  
   185  	// concurrent case
   186  	t = newTestNet(t0)
   187  	obj = t.xobject(object)
   188  
   189  	wg := &errgroup.Group{}
   190  	wg.Go(func() error {
   191  		tdelay()
   192  		return obj.Close()
   193  	})
   194  
   195  	test(t)
   196  	err = wg.Wait()
   197  	if err != nil {
   198  		t.Fatal(err)
   199  	}
   200  }
   201  
   202  // tdelay delays a bit.
   203  //
   204  // needed e.g. to test Close interaction with waiting read or write
   205  // (we cannot easily sync and make sure e.g. read is started and became asleep)
   206  func tdelay() {
   207  	time.Sleep(1 * time.Millisecond)
   208  }
   209  
   210  // TestClose verifies that for all virtnet objects Close properly interrupt /
   211  // errors all corresponding operations.
   212  func TestClose(t *testing.T) {
   213  	bg := context.Background()
   214  	assert := xtesting.Assert(t)
   215  
   216  	//          Subnet Host listener conn
   217  	// NewHost     x
   218  	// Dial        x     x      x
   219  	// Listen      x     x
   220  	// Accept      x     x      x
   221  	// Read/Write  x     x             x
   222  
   223  	// ---- NewHost ----
   224  
   225  	// subnet.NewHost vs subnet.Close
   226  	testClose(t, "subnet", func(t *testNet) {
   227  		h, err := t.net.NewHost(bg, "γ")
   228  		assert.Eq(h, (*Host)(nil))
   229  		assert.Eq(errors.Cause(err), ErrNetDown)
   230  		assert.Eq(err.Error(), "virtnet \"pipet\": new host \"γ\": network is down")
   231  	}, serialOnly)
   232  
   233  	// ---- Dial ----
   234  
   235  	// host.Dial vs subnet.Close
   236  	testClose(t, "subnet", func(t *testNet) {
   237  		c, err := t.hα.Dial(bg, "β:1")
   238  		assert.Eq(c, nil)
   239  		assert.Eq(err, xneterr("dial", "α:3->β:1", ErrNetDown))
   240  	})
   241  
   242  	// host1.Dial vs host1.Close
   243  	testClose(t, "hα", func(t *testNet) {
   244  		c, err := t.hα.Dial(bg, "β:1")
   245  		assert.Eq(c, nil)
   246  		assert.Eq(err, xneterr("dial", "α:3->β:1", ErrHostDown))
   247  	})
   248  
   249  	// host1.Dial vs host2.Close
   250  	testClose(t, "hβ", func(t *testNet) {
   251  		c, err := t.hα.Dial(bg, "β:1")
   252  		assert.Eq(c, nil)
   253  		assert.Eq(err, xneterr("dial", "α:3->β:1", ErrConnRefused))
   254  	})
   255  
   256  	// host1.Dial vs host2.listener.Close
   257  	testClose(t, "lβ", func(t *testNet) {
   258  		c, err := t.hα.Dial(bg, "β:1")
   259  		assert.Eq(c, nil)
   260  		assert.Eq(err, xneterr("dial", "α:3->β:1", ErrConnRefused))
   261  	})
   262  
   263  	// ---- Listen ----
   264  
   265  	// host.Listen vs subnet.Close
   266  	testClose(t, "subnet", func(t *testNet) {
   267  		l, err := t.hα.Listen(bg, "")
   268  		assert.Eq(l, nil)
   269  		assert.Eq(err, xneterr("listen", "α:0", ErrNetDown))
   270  	}, serialOnly)
   271  
   272  	// host.Listen vs host.Close
   273  	testClose(t, "hα", func(t *testNet) {
   274  		l, err := t.hα.Listen(bg, "")
   275  		assert.Eq(l, nil)
   276  		assert.Eq(err, xneterr("listen", "α:0", ErrHostDown))
   277  	}, serialOnly)
   278  
   279  	// ---- Accept ----
   280  
   281  	// listener.Accept vs subnet.Close
   282  	testClose(t, "subnet", func(t *testNet) {
   283  		c, err := t.lα.Accept(bg)
   284  		assert.Eq(c, nil)
   285  		assert.Eq(err, xneterr("accept", "α:1", ErrNetDown))
   286  	})
   287  
   288  	// listener.Accept vs host.Close
   289  	testClose(t, "hα", func(t *testNet) {
   290  		c, err := t.lα.Accept(bg)
   291  		assert.Eq(c, nil)
   292  		assert.Eq(err, xneterr("accept", "α:1", ErrHostDown))
   293  	})
   294  
   295  	// listener.Accept vs listener.Close
   296  	testClose(t, "lα", func(t *testNet) {
   297  		c, err := t.lα.Accept(bg)
   298  		assert.Eq(c, nil)
   299  		assert.Eq(err, xneterr("accept", "α:1", ErrSockDown))
   300  	})
   301  
   302  	// ---- Read/Write ----
   303  
   304  	buf := []byte("hello world!")
   305  
   306  	// conn.{Read,Write} vs subnet.Close
   307  	testClose(t, "subnet", func(t *testNet) {
   308  		n, err := t.cαβ.Read(buf)
   309  		assert.Eq(n, 0)
   310  		// err can be also EOF because subnet.Close closes cβα too and
   311  		// depending on scheduling we might first get EOF on our end.
   312  		if err != io.EOF {
   313  			assert.Eq(err, xneterr("read", "β:2->α:2", ErrNetDown))
   314  		}
   315  	})
   316  	testClose(t, "subnet", func(t *testNet) {
   317  		n, err := t.cαβ.Write(buf)
   318  		assert.Eq(n, 0)
   319  		assert.Eq(err, xneterr("write", "α:2->β:2", ErrNetDown))
   320  	})
   321  
   322  	// conn1.{Read,Write} vs host1.Close
   323  	testClose(t, "hα", func(t *testNet) {
   324  		n, err := t.cαβ.Read(buf)
   325  		assert.Eq(n, 0)
   326  		assert.Eq(err, xneterr("read", "β:2->α:2", ErrHostDown))
   327  	})
   328  	testClose(t, "hα", func(t *testNet) {
   329  		n, err := t.cαβ.Write(buf)
   330  		assert.Eq(n, 0)
   331  		assert.Eq(err, xneterr("write", "α:2->β:2", ErrHostDown))
   332  	})
   333  
   334  	// conn1.{Read,Write} vs host2.Close
   335  	testClose(t, "hβ", func(t *testNet) {
   336  		n, err := t.cαβ.Read(buf)
   337  		assert.Eq(n, 0)
   338  		assert.Eq(err, io.EOF)
   339  	})
   340  	testClose(t, "hβ", func(t *testNet) {
   341  		n, err := t.cαβ.Write(buf)
   342  		assert.Eq(n, 0)
   343  		assert.Eq(err, xneterr("write", "α:2->β:2", io.ErrClosedPipe))
   344  	})
   345  
   346  	// conn1.{Read,Write} vs conn1.Close
   347  	testClose(t, "cαβ", func(t *testNet) {
   348  		n, err := t.cαβ.Read(buf)
   349  		assert.Eq(n, 0)
   350  		assert.Eq(err, xneterr("read", "β:2->α:2", ErrSockDown))
   351  	})
   352  	testClose(t, "cαβ", func(t *testNet) {
   353  		n, err := t.cαβ.Write(buf)
   354  		assert.Eq(n, 0)
   355  		assert.Eq(err, xneterr("write", "α:2->β:2", ErrSockDown))
   356  	})
   357  
   358  	// conn1.{Read,Write} vs conn2.Close
   359  	testClose(t, "cβα", func(t *testNet) {
   360  		n, err := t.cαβ.Read(buf)
   361  		assert.Eq(n, 0)
   362  		assert.Eq(err, io.EOF)
   363  	})
   364  	testClose(t, "cβα", func(t *testNet) {
   365  		n, err := t.cαβ.Write(buf)
   366  		assert.Eq(n, 0)
   367  		assert.Eq(err, xneterr("write", "α:2->β:2", io.ErrClosedPipe))
   368  	})
   369  }
   370  
   371  // TestVNetDown verifies that engine shutdown error signal is properly handled.
   372  func TestVNetDown(t0 *testing.T) {
   373  	assert := xtesting.Assert(t0)
   374  
   375  	t := newTestNet(t0)
   376  	errSomeProblem := errors.New("some problem")
   377  	SubnetShutdown(t.net, errSomeProblem) // notifier.VNetDown does this
   378  
   379  	// SubNetwork.Close = shutdown(nil) and all that interactions were
   380  	// verified in TestClose. Here lets check only that we get proper Close error.
   381  	err := t.net.Close()
   382  	assert.Eq(errors.Cause(err), errSomeProblem)
   383  	assert.Eq(err.Error(), "virtnet \"pipet\": close: some problem")
   384  }
   385  
   386  // TestAutoClose verifies that subnet.AutoClose() leads to subnet.Close() after
   387  // its last host is closed.
   388  func TestAutoClose(t0 *testing.T) {
   389  	t := newTestNet(t0)
   390  	X := exc.Raiseif
   391  	assert := xtesting.Assert(t0)
   392  
   393  	t.net.AutoClose()
   394  
   395  	hγ, err := t.net.NewHost(context.Background(), "γ");  X(err)
   396  	err = t.hα.Close();  X(err)
   397  	err = t.hβ.Close();  X(err)
   398  	err = hγ.Close();    X(err)
   399  
   400  	hδ, err := t.net.NewHost(context.Background(), "δ")
   401  	assert.Eq(hδ, (*Host)(nil))
   402  	assert.Eq(errors.Cause(err), ErrNetDown)
   403  	assert.Eq(err.Error(), "virtnet \"pipet\": new host \"δ\": network is down")
   404  }