blob: b045bbff086d16f74994dc3bea53131ef5ef4b19 [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 main_test
import (
"fmt"
"io/ioutil"
"os"
"reflect"
"testing"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/naming"
"v.io/v23/security"
"v.io/v23/services/application"
"v.io/v23/verror"
"v.io/x/ref/lib/xrpc"
appd "v.io/x/ref/services/application/applicationd"
"v.io/x/ref/services/repository"
"v.io/x/ref/test"
"v.io/x/ref/test/testutil"
)
func newPublisherSignature(t *testing.T, ctx *context.T, msg []byte) (security.Blessings, security.Signature) {
// Generate publisher blessings
p := v23.GetPrincipal(ctx)
b, err := p.BlessSelf("publisher")
if err != nil {
t.Fatal(err)
}
sig, err := p.Sign(msg)
if err != nil {
t.Fatal(err)
}
return b, sig
}
// TestInterface tests that the implementation correctly implements
// the Application interface.
func TestInterface(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
dir, prefix := "", ""
store, err := ioutil.TempDir(dir, prefix)
if err != nil {
t.Fatalf("TempDir(%q, %q) failed: %v", dir, prefix, err)
}
defer os.RemoveAll(store)
dispatcher, err := appd.NewDispatcher(store)
if err != nil {
t.Fatalf("NewDispatcher() failed: %v", err)
}
server, err := xrpc.NewDispatchingServer(ctx, "", dispatcher)
if err != nil {
t.Fatalf("NewServer(%v) failed: %v", dispatcher, err)
}
endpoint := server.Status().Endpoints[0].String()
// Create client stubs for talking to the server.
stub := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search"))
stubV0 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v0"))
stubV1 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v1"))
stubV2 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v2"))
stubV3 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v3"))
blessings, sig := newPublisherSignature(t, ctx, []byte("binarycontents"))
// Create example envelopes.
envelopeV1 := application.Envelope{
Args: []string{"--help"},
Env: []string{"DEBUG=1"},
Binary: application.SignedFile{
File: "/v23/name/of/binary",
Signature: sig,
},
Publisher: blessings,
}
envelopeV2 := application.Envelope{
Args: []string{"--verbose"},
Env: []string{"DEBUG=0"},
Binary: application.SignedFile{
File: "/v23/name/of/binary",
Signature: sig,
},
Publisher: blessings,
}
envelopeV3 := application.Envelope{
Args: []string{"--verbose", "--spiffynewflag"},
Env: []string{"DEBUG=0"},
Binary: application.SignedFile{
File: "/v23/name/of/binary",
Signature: sig,
},
Publisher: blessings,
}
// Test Put(), adding a number of application envelopes.
if err := stubV1.Put(ctx, []string{"base", "media"}, envelopeV1); err != nil {
t.Fatalf("Put() failed: %v", err)
}
if err := stubV2.Put(ctx, []string{"base"}, envelopeV2); err != nil {
t.Fatalf("Put() failed: %v", err)
}
if err := stub.Put(ctx, []string{"base", "media"}, envelopeV1); err == nil || verror.ErrorID(err) != appd.ErrInvalidSuffix.ID {
t.Fatalf("Unexpected error: expected %v, got %v", appd.ErrInvalidSuffix, err)
}
// Test Match(), trying to retrieve both existing and non-existing
// application envelopes.
var output application.Envelope
if output, err = stubV2.Match(ctx, []string{"base", "media"}); err != nil {
t.Fatalf("Match() failed: %v", err)
}
if !reflect.DeepEqual(envelopeV2, output) {
t.Fatalf("Incorrect output: expected %v, got %v", envelopeV2, output)
}
if output, err = stubV1.Match(ctx, []string{"media"}); err != nil {
t.Fatalf("Match() failed: %v", err)
}
if !reflect.DeepEqual(envelopeV1, output) {
t.Fatalf("Unexpected output: expected %v, got %v", envelopeV1, output)
}
if _, err := stubV2.Match(ctx, []string{"media"}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
}
if _, err := stubV2.Match(ctx, []string{}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
}
// Test that Match() against a name without a version suffix returns the latest.
if output, err = stub.Match(ctx, []string{"base", "media"}); err != nil {
t.Fatalf("Match() failed: %v", err)
}
if !reflect.DeepEqual(envelopeV2, output) {
t.Fatalf("Incorrect output: expected %v, got %v", envelopeV2, output)
}
// Test that we can add another envelope in sort order and we still get the
// correct (i.e. newest) version.
if err := stubV3.Put(ctx, []string{"base"}, envelopeV3); err != nil {
t.Fatalf("Put() failed: %v", err)
}
if output, err = stub.Match(ctx, []string{"base", "media"}); err != nil {
t.Fatalf("Match() failed: %v", err)
}
if !reflect.DeepEqual(envelopeV3, output) {
t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output)
}
// Test that this is not based on time but on sort order.
envelopeV0 := application.Envelope{
Args: []string{"--help", "--zeroth"},
Env: []string{"DEBUG=1"},
Binary: application.SignedFile{
File: "/v23/name/of/binary",
Signature: sig,
},
Publisher: blessings,
}
if err := stubV0.Put(ctx, []string{"base"}, envelopeV0); err != nil {
t.Fatalf("Put() failed: %v", err)
}
if output, err = stub.Match(ctx, []string{"base", "media"}); err != nil {
t.Fatalf("Match() failed: %v", err)
}
if !reflect.DeepEqual(envelopeV3, output) {
t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output)
}
// Test Glob
matches, _, err := testutil.GlobName(ctx, naming.JoinAddressName(endpoint, ""), "...")
if err != nil {
t.Errorf("Unexpected Glob error: %v", err)
}
expected := []string{
"",
"search",
"search/v0",
"search/v1",
"search/v2",
"search/v3",
}
if !reflect.DeepEqual(matches, expected) {
t.Errorf("unexpected Glob results. Got %q, want %q", matches, expected)
}
// Test Remove(), trying to remove both existing and non-existing
// application envelopes.
if err := stubV1.Remove(ctx, "base"); err != nil {
t.Fatalf("Remove() failed: %v", err)
}
if output, err = stubV1.Match(ctx, []string{"media"}); err != nil {
t.Fatalf("Match() failed: %v", err)
}
if err := stubV1.Remove(ctx, "base"); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
}
if err := stub.Remove(ctx, "base"); err != nil {
t.Fatalf("Remove() failed: %v", err)
}
if err := stubV2.Remove(ctx, "media"); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
}
if err := stubV1.Remove(ctx, "media"); err != nil {
t.Fatalf("Remove() failed: %v", err)
}
// Finally, use Match() to test that Remove really removed the
// application envelopes.
if _, err := stubV1.Match(ctx, []string{"base"}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
}
if _, err := stubV1.Match(ctx, []string{"media"}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
}
if _, err := stubV2.Match(ctx, []string{"base"}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
}
// Shutdown the application repository server.
if err := server.Stop(); err != nil {
t.Fatalf("Stop() failed: %v", err)
}
}
func TestPreserveAcrossRestarts(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
dir, prefix := "", ""
storedir, err := ioutil.TempDir(dir, prefix)
if err != nil {
t.Fatalf("TempDir(%q, %q) failed: %v", dir, prefix, err)
}
defer os.RemoveAll(storedir)
dispatcher, err := appd.NewDispatcher(storedir)
if err != nil {
t.Fatalf("NewDispatcher() failed: %v", err)
}
server, err := xrpc.NewDispatchingServer(ctx, "", dispatcher)
if err != nil {
t.Fatalf("Serve(%v) failed: %v", dispatcher, err)
}
endpoint := server.Status().Endpoints[0].String()
// Create client stubs for talking to the server.
stubV1 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v1"))
blessings, sig := newPublisherSignature(t, ctx, []byte("binarycontents"))
// Create example envelopes.
envelopeV1 := application.Envelope{
Args: []string{"--help"},
Env: []string{"DEBUG=1"},
Binary: application.SignedFile{
File: "/v23/name/of/binary",
Signature: sig,
},
Publisher: blessings,
}
if err := stubV1.Put(ctx, []string{"media"}, envelopeV1); err != nil {
t.Fatalf("Put() failed: %v", err)
}
// There is content here now.
output, err := stubV1.Match(ctx, []string{"media"})
if err != nil {
t.Fatalf("Match(%v) failed: %v", "media", err)
}
if !reflect.DeepEqual(envelopeV1, output) {
t.Fatalf("Incorrect output: expected %v, got %v", envelopeV1, output)
}
server.Stop()
// Setup and start a second application server.
dispatcher, err = appd.NewDispatcher(storedir)
if err != nil {
t.Fatalf("NewDispatcher() failed: %v", err)
}
server, err = xrpc.NewDispatchingServer(ctx, "", dispatcher)
if err != nil {
t.Fatalf("NewServer(%v) failed: %v", dispatcher, err)
}
endpoint = server.Status().Endpoints[0].String()
stubV1 = repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v1"))
output, err = stubV1.Match(ctx, []string{"media"})
if err != nil {
t.Fatalf("Match(%v) failed: %v", "media", err)
}
if !reflect.DeepEqual(envelopeV1, output) {
t.Fatalf("Incorrect output: expected %v, got %v", envelopeV1, output)
}
}
// TestTidyNow tests that TidyNow operates correctly.
func TestTidyNow(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
dir, prefix := "", ""
store, err := ioutil.TempDir(dir, prefix)
if err != nil {
t.Fatalf("TempDir(%q, %q) failed: %v", dir, prefix, err)
}
defer os.RemoveAll(store)
dispatcher, err := appd.NewDispatcher(store)
if err != nil {
t.Fatalf("NewDispatcher() failed: %v", err)
}
server, err := xrpc.NewDispatchingServer(ctx, "", dispatcher)
if err != nil {
t.Fatalf("NewServer(%v) failed: %v", dispatcher, err)
}
defer func() {
if err := server.Stop(); err != nil {
t.Fatalf("Stop() failed: %v", err)
}
}()
endpoint := server.Status().Endpoints[0].String()
// Create client stubs for talking to the server.
stub := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search"))
stubs := make([]repository.ApplicationClientStub, 0)
for _, vn := range []string{"v0", "v1", "v2", "v3"} {
s := repository.ApplicationClient(naming.JoinAddressName(endpoint, fmt.Sprintf("search/%s", vn)))
stubs = append(stubs, s)
}
blessings, sig := newPublisherSignature(t, ctx, []byte("binarycontents"))
// Create example envelopes.
envelopeV1 := application.Envelope{
Args: []string{"--help"},
Env: []string{"DEBUG=1"},
Binary: application.SignedFile{
File: "/v23/name/of/binary",
Signature: sig,
},
Publisher: blessings,
}
envelopeV2 := application.Envelope{
Args: []string{"--verbose"},
Env: []string{"DEBUG=0"},
Binary: application.SignedFile{
File: "/v23/name/of/binary",
Signature: sig,
},
Publisher: blessings,
}
envelopeV3 := application.Envelope{
Args: []string{"--verbose", "--spiffynewflag"},
Env: []string{"DEBUG=0"},
Binary: application.SignedFile{
File: "/v23/name/of/binary",
Signature: sig,
},
Publisher: blessings,
}
stuffEnvelopes(t, ctx, stubs, []profEnvTuple{
{
&envelopeV1,
[]string{"base", "media"},
},
})
// Verify that we have one
testGlob(t, ctx, endpoint, []string{
"",
"search",
"search/v0",
})
// Tidy when already tidy does not alter.
if err := stubs[0].TidyNow(ctx); err != nil {
t.Errorf("TidyNow failed: %v", err)
}
testGlob(t, ctx, endpoint, []string{
"",
"search",
"search/v0",
})
stuffEnvelopes(t, ctx, stubs, []profEnvTuple{
{
&envelopeV1,
[]string{"base", "media"},
},
{
&envelopeV2,
[]string{"media"},
},
{
&envelopeV3,
[]string{"base"},
},
})
// Now there are three envelopes which is one more than the
// numberOfVersionsToKeep set for the test. However
// we need both envelopes v0 and v2 to keep two versions for
// profile media and envelopes v0 and v3 to keep two versions
// for profile base so we continue to have three versions.
if err := stubs[0].TidyNow(ctx); err != nil {
t.Errorf("TidyNow failed: %v", err)
}
testGlob(t, ctx, endpoint, []string{
"",
"search",
"search/v0",
"search/v1",
"search/v2",
})
// And the newest version for each profile differs because
// not every version supports all profiles.
output1, err := stub.Match(ctx, []string{"media"})
if err != nil {
t.Fatalf("Match(%v) failed: %v", "media", err)
}
if !reflect.DeepEqual(envelopeV2, output1) {
t.Fatalf("Incorrect output: expected %v, got %v", envelopeV2, output1)
}
output2, err := stub.Match(ctx, []string{"base"})
if err != nil {
t.Fatalf("Match(%v) failed: %v", "base", err)
}
if !reflect.DeepEqual(envelopeV3, output2) {
t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output2)
}
// Test that we can add an envelope for v3 with profile media and after calling
// TidyNow(), there will be all versions still in glob but v0 will only match profile
// base and not have an envelope for profile media.
if err := stubs[3].Put(ctx, []string{"media"}, envelopeV3); err != nil {
t.Fatalf("Put() failed: %v", err)
}
if err := stubs[0].TidyNow(ctx); err != nil {
t.Errorf("TidyNow failed: %v", err)
}
testGlob(t, ctx, endpoint, []string{
"",
"search",
"search/v0",
"search/v1",
"search/v2",
"search/v3",
})
output3, err := stubs[0].Match(ctx, []string{"base"})
if err != nil {
t.Fatalf("Match(%v) failed: %v", "base", err)
}
if !reflect.DeepEqual(envelopeV3, output2) {
t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output3)
}
output4, err := stubs[0].Match(ctx, []string{"base"})
if err != nil {
t.Fatalf("Match(%v) failed: %v", "base", err)
}
if !reflect.DeepEqual(envelopeV3, output2) {
t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output4)
}
_, err = stubs[0].Match(ctx, []string{"media"})
if verror.ErrorID(err) != verror.ErrNoExist.ID {
t.Fatalf("got error %v, expected %v", err, verror.ErrNoExist)
}
stuffEnvelopes(t, ctx, stubs, []profEnvTuple{
{
&envelopeV1,
[]string{"base", "media"},
},
{
&envelopeV2,
[]string{"base", "media"},
},
{
&envelopeV3,
[]string{"base", "media"},
},
{
&envelopeV3,
[]string{"base", "media"},
},
})
// Now there are four versions for all profiles so tidying
// will remove the older versions.
if err := stubs[0].TidyNow(ctx); err != nil {
t.Errorf("TidyNow failed: %v", err)
}
testGlob(t, ctx, endpoint, []string{
"",
"search",
"search/v2",
"search/v3",
})
}
type profEnvTuple struct {
e *application.Envelope
p []string
}
func testGlob(t *testing.T, ctx *context.T, endpoint string, expected []string) {
matches, _, err := testutil.GlobName(ctx, naming.JoinAddressName(endpoint, ""), "...")
if err != nil {
t.Errorf("Unexpected Glob error: %v", err)
}
if !reflect.DeepEqual(matches, expected) {
t.Errorf("unexpected Glob results. Got %q, want %q", matches, expected)
}
}
func stuffEnvelopes(t *testing.T, ctx *context.T, stubs []repository.ApplicationClientStub, pets []profEnvTuple) {
for i, pet := range pets {
if err := stubs[i].Put(ctx, pet.p, *pet.e); err != nil {
t.Fatalf("%d: Put(%v) failed: %v", i, pet, err)
}
}
}