blob: fb32e5a6e29cbad2aea133df91404c0a2d6ff0f7 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package discovery_test
import (
"bytes"
"fmt"
"reflect"
"testing"
"time"
"v.io/v23/discovery"
"v.io/v23/security"
"v.io/x/lib/ibe"
idiscovery "v.io/x/ref/lib/discovery"
"v.io/x/ref/lib/discovery/plugins/mock"
"v.io/x/ref/lib/discovery/testutil"
"v.io/x/ref/lib/security/bcrypter"
_ "v.io/x/ref/runtime/factories/generic"
"v.io/x/ref/test"
)
func TestBasic(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
df, err := idiscovery.NewFactory(ctx, mock.New())
if err != nil {
t.Fatal(err)
}
defer df.Shutdown()
ads := []discovery.Advertisement{
{
Id: discovery.AdId{1, 2, 3},
InterfaceName: "v.io/a",
Addresses: []string{"/h1:123/x", "/h2:123/y"},
Attributes: discovery.Attributes{"a1": "v1"},
},
{
InterfaceName: "v.io/b",
Addresses: []string{"/h1:123/x", "/h2:123/z"},
Attributes: discovery.Attributes{"b1": "v1"},
},
}
d1, err := df.New(ctx)
if err != nil {
t.Fatal(err)
}
var stops []func()
for i, _ := range ads {
stop, err := testutil.Advertise(ctx, d1, nil, &ads[i])
if err != nil {
t.Fatal(err)
}
stops = append(stops, stop)
}
// Make sure none of advertisements are discoverable by the same discovery instance.
if err := testutil.ScanAndMatch(ctx, d1, ``); err != nil {
t.Error(err)
}
// Create a new discovery instance. All advertisements should be discovered with that.
d2, err := df.New(ctx)
if err != nil {
t.Fatal(err)
}
if err := testutil.ScanAndMatch(ctx, d2, `v.InterfaceName="v.io/a"`, ads[0]); err != nil {
t.Error(err)
}
if err := testutil.ScanAndMatch(ctx, d2, `v.InterfaceName="v.io/b"`, ads[1]); err != nil {
t.Error(err)
}
if err := testutil.ScanAndMatch(ctx, d2, ``, ads...); err != nil {
t.Error(err)
}
if err := testutil.ScanAndMatch(ctx, d2, `v.InterfaceName="v.io/c"`); err != nil {
t.Error(err)
}
// Open a new scan channel and consume expected advertisements first.
scanCh, scanStop, err := testutil.Scan(ctx, d2, `v.InterfaceName="v.io/a"`)
if err != nil {
t.Fatal(err)
}
defer scanStop()
update := <-scanCh
if !testutil.MatchFound(ctx, []discovery.Update{update}, ads[0]) {
t.Errorf("unexpected scan: %v", update)
}
// Make sure scan returns the lost advertisement when advertising is stopped.
stops[0]()
update = <-scanCh
if !testutil.MatchLost(ctx, []discovery.Update{update}, ads[0]) {
t.Errorf("unexpected scan: %v", update)
}
// Also it shouldn't affect the other.
if err := testutil.ScanAndMatch(ctx, d2, `v.InterfaceName="v.io/b"`, ads[1]); err != nil {
t.Error(err)
}
// Stop advertising the remaining one; Shouldn't discover any service.
stops[1]()
if err := testutil.ScanAndMatch(ctx, d2, ``); err != nil {
t.Error(err)
}
}
// TODO(jhahn): Add a low level test that ensures the advertisement is unusable
// by the listener, if encrypted rather than replying on a higher level API.
func TestVisibility(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
df, _ := idiscovery.NewFactory(ctx, mock.New())
defer df.Shutdown()
master, err := ibe.SetupBB2()
if err != nil {
ctx.Fatalf("ibe.SetupBB2 failed: %v", err)
}
root := bcrypter.NewRoot("v.io", master)
crypter := bcrypter.NewCrypter()
if err := crypter.AddParams(ctx, root.Params()); err != nil {
ctx.Fatalf("bcrypter.AddParams failed: %v", err)
}
ad := discovery.Advertisement{
InterfaceName: "v.io/v23/a",
Addresses: []string{"/h1:123/x", "/h2:123/y"},
Attributes: map[string]string{"a1": "v1", "a2": "v2"},
}
visibility := []security.BlessingPattern{
security.BlessingPattern("v.io:bob"),
security.BlessingPattern("v.io:alice").MakeNonExtendable(),
}
d1, _ := df.New(ctx)
sctx := bcrypter.WithCrypter(ctx, crypter)
stop, err := testutil.Advertise(sctx, d1, visibility, &ad)
if err != nil {
t.Fatal(err)
}
defer stop()
d2, _ := df.New(ctx)
// Bob and his friend should discover the advertisement.
bobctx, _ := testutil.WithPrivateKey(ctx, root, "v.io:bob")
if err := testutil.ScanAndMatch(bobctx, d2, ``, ad); err != nil {
t.Error(err)
}
bobfriendctx, _ := testutil.WithPrivateKey(ctx, root, "v.io:bob:friend")
if err := testutil.ScanAndMatch(bobfriendctx, d2, ``, ad); err != nil {
t.Error(err)
}
// Alice should discover the advertisement, but her friend shouldn't.
alicectx, _ := testutil.WithPrivateKey(ctx, root, "v.io:alice")
if err := testutil.ScanAndMatch(alicectx, d2, ``, ad); err != nil {
t.Error(err)
}
alicefriendctx, _ := testutil.WithPrivateKey(ctx, root, "v.io:alice:friend")
if err := testutil.ScanAndMatch(alicefriendctx, d2, ``); err != nil {
t.Error(err)
}
// Other people shouldn't discover the advertisement.
carolctx, _ := testutil.WithPrivateKey(ctx, root, "v.io:carol")
if err := testutil.ScanAndMatch(carolctx, d2, ``); err != nil {
t.Error(err)
}
}
func TestDuplicates(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
df, _ := idiscovery.NewFactory(ctx, mock.New())
defer df.Shutdown()
ad := discovery.Advertisement{
InterfaceName: "v.io/v23/a",
Addresses: []string{"/h1:123/x"},
}
d, _ := df.New(ctx)
if _, err := testutil.Advertise(ctx, d, nil, &ad); err != nil {
t.Fatal(err)
}
if _, err := testutil.Advertise(ctx, d, nil, &ad); err == nil {
t.Error("expect an error; but got none")
}
}
func TestMerge(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
p1, p2 := mock.New(), mock.New()
df, _ := idiscovery.NewFactory(ctx, p1, p2)
defer df.Shutdown()
adinfo := idiscovery.AdInfo{
Ad: discovery.Advertisement{
Id: discovery.AdId{1, 2, 3},
InterfaceName: "v.io/v23/a",
Addresses: []string{"/h1:123/x"},
},
Hash: idiscovery.AdHash{1, 2, 3},
}
d, _ := df.New(ctx)
scanCh, scanStop, err := testutil.Scan(ctx, d, ``)
if err != nil {
t.Fatal(err)
}
defer scanStop()
// A plugin returns an advertisement and we should see it.
p1.RegisterAd(&adinfo)
update := <-scanCh
if !testutil.MatchFound(ctx, []discovery.Update{update}, adinfo.Ad) {
t.Errorf("unexpected scan: %v", update)
}
// The other plugin returns the same advertisement, but we should not see it.
p2.RegisterAd(&adinfo)
select {
case update = <-scanCh:
t.Errorf("unexpected scan: %v", update)
case <-time.After(5 * time.Millisecond):
}
// Two plugins update the service, but we should see the update only once.
newAdinfo := adinfo
newAdinfo.Ad.Addresses = []string{"/h1:456/x"}
newAdinfo.Hash = idiscovery.AdHash{4, 5, 6}
go func() { p1.RegisterAd(&newAdinfo) }()
go func() { p2.RegisterAd(&newAdinfo) }()
// Should see 'Lost' first.
update = <-scanCh
if !testutil.MatchLost(ctx, []discovery.Update{update}, adinfo.Ad) {
t.Errorf("unexpected scan: %v", update)
}
update = <-scanCh
if !testutil.MatchFound(ctx, []discovery.Update{update}, newAdinfo.Ad) {
t.Errorf("unexpected scan: %v", update)
}
select {
case update = <-scanCh:
t.Errorf("unexpected scan: %v", update)
case <-time.After(5 * time.Millisecond):
}
// Lost in both, should see a lost event once.
p1.UnregisterAd(&newAdinfo)
p2.UnregisterAd(&newAdinfo)
update = <-scanCh
if !testutil.MatchLost(ctx, []discovery.Update{update}, newAdinfo.Ad) {
t.Errorf("unexpected scan: %v", update)
}
select {
case update = <-scanCh:
t.Errorf("unexpected scan: %v", update)
case <-time.After(5 * time.Millisecond):
}
}
func TestLostInOneButNotAllPlugins(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
p1, p2 := mock.New(), mock.New()
df, _ := idiscovery.NewFactory(ctx, p1, p2)
defer df.Shutdown()
ad := idiscovery.AdInfo{
Ad: discovery.Advertisement{
Id: discovery.AdId{1, 2, 3},
InterfaceName: "v.io/v23/a",
Addresses: []string{"/h1:123/x"},
},
Hash: idiscovery.AdHash{1, 2, 3},
TimestampNs: time.Now().UnixNano(),
}
olderAd := ad
olderAd.TimestampNs -= 1000000000
olderAd.Hash = idiscovery.AdHash{4, 5, 6}
newerAd := ad
newerAd.TimestampNs += 1000000000
newerAd.Hash = idiscovery.AdHash{7, 8, 9}
d, _ := df.New(ctx)
scanCh, scanStop, err := testutil.Scan(ctx, d, "")
if err != nil {
t.Fatal(err)
}
defer scanStop()
noevent := func() error {
select {
case update := <-scanCh:
return fmt.Errorf("unexpected scan: %v", update)
case <-time.After(5 * time.Millisecond):
return nil
}
}
p1.RegisterAd(&ad)
if update := <-scanCh; !testutil.MatchFound(ctx, []discovery.Update{update}, ad.Ad) {
t.Errorf("unexpected scan: %v", update)
}
// p2 sees the same ad, but no event should be delivered.
p2.RegisterAd(&ad)
if err := noevent(); err != nil {
t.Error(err)
}
// And if p1 loses ad, but p2 doesn't, then nothing should be delivered.
p1.UnregisterAd(&ad)
if err := noevent(); err != nil {
t.Error(err)
}
// An older ad should be ignored
p1.RegisterAd(&olderAd)
if err := noevent(); err != nil {
t.Error(err)
}
// But a newer one should be seen as a LOST + FOUND
p2.RegisterAd(&newerAd)
if update := <-scanCh; !testutil.MatchLost(ctx, []discovery.Update{update}, ad.Ad) {
t.Errorf("unexpected: %v", update)
}
if update := <-scanCh; !testutil.MatchFound(ctx, []discovery.Update{update}, newerAd.Ad) {
t.Errorf("unexpected: %v", update)
}
// Newer ad lost by p2 and never seen by p1, should be lost
p2.UnregisterAd(&newerAd)
if update := <-scanCh; !testutil.MatchLost(ctx, []discovery.Update{update}, ad.Ad) {
t.Errorf("unexpected: %v", update)
}
}
func TestTimestamp(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
p1, p2 := mock.New(), mock.New()
df, _ := idiscovery.NewFactory(ctx, p1, p2)
defer df.Shutdown()
adinfo := idiscovery.AdInfo{
Ad: discovery.Advertisement{
Id: discovery.AdId{1, 2, 3},
InterfaceName: "v.io/v23/a",
Addresses: []string{"/h1:123/x"},
},
Hash: idiscovery.AdHash{1, 2, 3},
TimestampNs: 1001,
}
d, _ := df.New(ctx)
scanCh, scanStop, err := testutil.Scan(ctx, d, ``)
if err != nil {
t.Fatal(err)
}
defer scanStop()
// A plugin returns an advertisement and we should see it.
p1.RegisterAd(&adinfo)
update := <-scanCh
if !testutil.MatchFound(ctx, []discovery.Update{update}, adinfo.Ad) {
t.Errorf("unexpected scan: %v", update)
}
// The other plugin returns an old advertisement, but we should not see it.
oldAdinfo := adinfo
oldAdinfo.Ad.Addresses = []string{"/h0:123/x"}
oldAdinfo.Hash = idiscovery.AdHash{0, 1, 2}
oldAdinfo.TimestampNs = 1000
p2.RegisterAd(&oldAdinfo)
select {
case update = <-scanCh:
t.Errorf("unexpected scan: %v", update)
case <-time.After(5 * time.Millisecond):
}
}
func TestLargeAdvertisement(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
df, err := idiscovery.NewFactory(ctx, mock.NewWithAdStatus(idiscovery.AdNotReady))
if err != nil {
t.Fatal(err)
}
defer df.Shutdown()
ads := []discovery.Advertisement{
{
InterfaceName: "v.io/a",
Addresses: []string{"/h1:123/x"},
Attributes: discovery.Attributes{"a": "v"},
},
{
InterfaceName: "v.io/b",
Addresses: []string{"/h1:123/y"},
Attachments: discovery.Attachments{
"a1": bytes.Repeat([]byte{1}, 2048),
"a2": bytes.Repeat([]byte{2}, 4096),
"a3": bytes.Repeat([]byte{3}, 1024),
"a4": bytes.Repeat([]byte{4}, 2048),
},
},
}
d1, err := df.New(ctx)
if err != nil {
t.Fatal(err)
}
for i, _ := range ads {
stop, err := testutil.Advertise(ctx, d1, nil, &ads[i])
if err != nil {
t.Fatal(err)
}
defer stop()
}
d2, err := df.New(ctx)
if err != nil {
t.Fatal(err)
}
if err := testutil.ScanAndMatch(ctx, d2, `v.InterfaceName="v.io/a"`, ads[0]); err != nil {
t.Error(err)
}
if err := testutil.ScanAndMatch(ctx, d2, `v.Attributes["a"]="v"`, ads[0]); err != nil {
t.Error(err)
}
scanCh, scanStop, err := testutil.Scan(ctx, d2, `v.InterfaceName="v.io/b"`)
if err != nil {
t.Fatal(err)
}
defer scanStop()
update := <-scanCh
// Make sure that the directory server does not return all of the attachments if they are too large.
if reflect.DeepEqual(update.Advertisement().Attachments, ads[1].Attachments) {
t.Errorf("did not expect all of attachments, but got all: %v", update.Advertisement())
}
// But we should be able to fetch them lazily.
if !testutil.UpdateEqual(ctx, update, ads[1]) {
t.Errorf("Match failed; got %v, but wanted %v", update, ads[1])
}
}
func TestLargeAttachments(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
df, err := idiscovery.NewFactory(ctx, mock.NewWithAdStatus(idiscovery.AdPartiallyReady))
if err != nil {
t.Fatal(err)
}
defer df.Shutdown()
ad := discovery.Advertisement{
InterfaceName: "v.io/a",
Addresses: []string{"/h1:123/x", "/h2:123/y"},
Attachments: discovery.Attachments{
"a1": []byte{1, 2, 3},
"a2": []byte{4, 5, 6},
},
}
d1, err := df.New(ctx)
if err != nil {
t.Fatal(err)
}
stop, err := testutil.Advertise(ctx, d1, nil, &ad)
if err != nil {
t.Fatal(err)
}
defer stop()
d2, err := df.New(ctx)
if err != nil {
t.Fatal(err)
}
if err := testutil.ScanAndMatch(ctx, d2, ``, ad); err != nil {
t.Error(err)
}
}
func TestShutdown(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
df, _ := idiscovery.NewFactory(ctx, mock.New())
ad := discovery.Advertisement{
InterfaceName: "v.io/v23/a",
Addresses: []string{"/h1:123/x"},
}
d1, _ := df.New(ctx)
if _, err := testutil.Advertise(ctx, d1, nil, &ad); err != nil {
t.Error(err)
}
d2, _ := df.New(ctx)
if err := testutil.ScanAndMatch(ctx, d2, ``, ad); err != nil {
t.Error(err)
}
// Verify Close can be called multiple times.
df.Shutdown()
df.Shutdown()
// Make sure advertise and scan do not work after closed.
ad.Id = discovery.AdId{} // To avoid dup error.
if _, err := testutil.Advertise(ctx, d1, nil, &ad); err == nil {
t.Error("expect an error; but got none")
}
if err := testutil.ScanAndMatch(ctx, d2, ``, ad); err == nil {
t.Error("expect an error; but got none")
}
}