services/binary/tidy: add an external GC command
This CL adds a command that can delete binaryd objects not referenced
by envelopes in a specified applicationd instance.
Change-Id: I9919c6a496d574b75fdf5ca23231af0e6e2cc692
diff --git a/services/binary/tidy/appd/mock.go b/services/binary/tidy/appd/mock.go
new file mode 100644
index 0000000..3043304
--- /dev/null
+++ b/services/binary/tidy/appd/mock.go
@@ -0,0 +1,51 @@
+// 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 appd
+
+import (
+ "testing"
+
+ "v.io/v23/context"
+ "v.io/v23/rpc"
+ "v.io/v23/security"
+ "v.io/v23/services/application"
+
+ "v.io/x/ref/services/binary/tidy/binaryd"
+ "v.io/x/ref/services/internal/servicetest"
+)
+
+type mockAppdInvoker struct {
+ binaryd.MockBinarydInvoker
+}
+
+type MatchStimulus struct {
+ Name string
+ Suffix string
+ Profiles []string
+}
+
+type MatchResult struct {
+ Env application.Envelope
+ Err error
+}
+
+func (mdi *mockAppdInvoker) Match(ctx *context.T, _ rpc.ServerCall, profiles []string) (application.Envelope, error) {
+ ir := mdi.Tape.Record(MatchStimulus{"Match", mdi.Suffix, profiles})
+ r := ir.(MatchResult)
+ return r.Env, r.Err
+}
+
+type dispatcher struct {
+ tape *servicetest.Tape
+ t *testing.T
+}
+
+func NewDispatcher(t *testing.T, tape *servicetest.Tape) rpc.Dispatcher {
+ return &dispatcher{tape: tape, t: t}
+}
+
+func (d *dispatcher) Lookup(p *context.T, suffix string) (interface{}, security.Authorizer, error) {
+ return &mockAppdInvoker{binaryd.NewMockBinarydInvoker(suffix, d.tape, d.t)}, nil, nil
+}
diff --git a/services/binary/tidy/binaryd/mock.go b/services/binary/tidy/binaryd/mock.go
new file mode 100644
index 0000000..5e9daf0
--- /dev/null
+++ b/services/binary/tidy/binaryd/mock.go
@@ -0,0 +1,99 @@
+// 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 binaryd
+
+import (
+ "log"
+ "testing"
+
+ "v.io/v23/context"
+ "v.io/v23/naming"
+ "v.io/v23/rpc"
+ "v.io/v23/security"
+ "v.io/v23/services/binary"
+ "v.io/v23/services/repository"
+
+ "v.io/x/ref/services/internal/servicetest"
+)
+
+type MockBinarydInvoker struct {
+ Suffix string
+ Tape *servicetest.Tape
+ t *testing.T
+}
+
+// simpleCore implements the core of all mock methods that take
+// arguments and return error.
+func (mdi *MockBinarydInvoker) SimpleCore(callRecord interface{}, name string) error {
+ ri := mdi.Tape.Record(callRecord)
+ switch r := ri.(type) {
+ case nil:
+ return nil
+ case error:
+ return r
+ }
+ log.Fatalf("%s (mock) response %v is of bad type", name, ri)
+ return nil
+}
+
+type DeleteStimulus struct {
+ Op string
+ Suffix string
+}
+
+func (mdi *MockBinarydInvoker) Delete(ctx *context.T, _ rpc.ServerCall) error {
+ return mdi.SimpleCore(DeleteStimulus{"Delete", mdi.Suffix}, "Delete")
+}
+
+type StatStimulus struct {
+ Op string
+ Suffix string
+}
+
+func (mdi *MockBinarydInvoker) Stat(ctx *context.T, _ rpc.ServerCall) ([]binary.PartInfo, repository.MediaInfo, error) {
+ // Only the presence or absence of the error is necessary.
+ if err := mdi.SimpleCore(StatStimulus{"Stat", mdi.Suffix}, "Stat"); err != nil {
+ return nil, repository.MediaInfo{}, err
+ }
+ return nil, repository.MediaInfo{}, nil
+}
+
+type GlobStimulus struct {
+ Pattern string
+}
+
+type GlobResponse struct {
+ Results []string
+ Err error
+}
+
+func (mdi *MockBinarydInvoker) Glob__(p *context.T, _ rpc.ServerCall, pattern string) (<-chan naming.GlobReply, error) {
+ gs := GlobStimulus{pattern}
+ gr := mdi.Tape.Record(gs).(GlobResponse)
+ ch := make(chan naming.GlobReply, len(gr.Results))
+ defer close(ch)
+ for _, r := range gr.Results {
+ ch <- naming.GlobReplyEntry{naming.MountEntry{Name: r}}
+ }
+ return ch, gr.Err
+}
+
+type dispatcher struct {
+ tape *servicetest.Tape
+ t *testing.T
+}
+
+func NewDispatcher(t *testing.T, tape *servicetest.Tape) rpc.Dispatcher {
+ return &dispatcher{tape: tape, t: t}
+}
+
+func NewMockBinarydInvoker(suffix string, tape *servicetest.Tape, t *testing.T) MockBinarydInvoker {
+ return MockBinarydInvoker{Suffix: suffix, Tape: tape, t: t}
+}
+
+func (d *dispatcher) Lookup(p *context.T, suffix string) (interface{}, security.Authorizer, error) {
+ v := NewMockBinarydInvoker(suffix, d.tape, d.t)
+ return &v, nil, nil
+}
diff --git a/services/binary/tidy/doc.go b/services/binary/tidy/doc.go
new file mode 100644
index 0000000..209fb4c
--- /dev/null
+++ b/services/binary/tidy/doc.go
@@ -0,0 +1,98 @@
+// 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.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+
+/*
+Tidy tidies the Vanadium binary repository by removing unused binaries.
+
+Usage:
+ tidy <command>
+
+The tidy commands are:
+ binary Binary sub-command tidies a specified binaryd
+ help Display help for commands or topics
+
+The global flags are:
+ -alsologtostderr=true
+ log to standard error as well as files
+ -log_backtrace_at=:0
+ when logging hits line file:N, emit a stack trace
+ -log_dir=
+ if non-empty, write log files to this directory
+ -logtostderr=false
+ log to standard error instead of files
+ -max_stack_buf_size=4292608
+ max size in bytes of the buffer to use for logging stack traces
+ -stderrthreshold=2
+ logs at or above this threshold go to stderr
+ -v=0
+ log level for V logs
+ -v23.credentials=
+ directory to use for storing security credentials
+ -v23.i18n-catalogue=
+ 18n catalogue files to load, comma separated
+ -v23.metadata=<just specify -v23.metadata to activate>
+ Displays metadata for the program and exits.
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+ local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+ object name of proxy service to use to export services across network
+ boundaries
+ -v23.tcp.address=
+ address to listen on
+ -v23.tcp.protocol=wsh
+ protocol to listen with
+ -v23.vtrace.cache-size=1024
+ The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+ Spans and annotations that match this regular expression will trigger trace
+ collection.
+ -v23.vtrace.dump-on-shutdown=true
+ If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+ Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+ comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+ comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Tidy binary - Binary sub-command tidies a specified binaryd
+
+Binary sub-command removes all binaries from a specified binaryd that are not
+referenced by an applicationd envelope stored in the specified applicationd.
+
+Usage:
+ tidy binary <applicationd> <binaryd>
+
+<applicationd> is the name or endpoint of the applicationd instance sourcing the
+envelopes. <binaryd> is the name or endpoint of a binaryd instance to clean up.
+
+Tidy help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+ tidy help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The tidy help flags are:
+ -style=compact
+ The formatting style for help output:
+ compact - Good for compact cmdline output.
+ full - Good for cmdline output, shows all global flags.
+ godoc - Good for godoc processing.
+ Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+ Format output to this target width in runes, or unlimited if width < 0.
+ Defaults to the terminal width if available. Override the default by setting
+ the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/services/binary/tidy/impl.go b/services/binary/tidy/impl.go
new file mode 100644
index 0000000..12bbe7d
--- /dev/null
+++ b/services/binary/tidy/impl.go
@@ -0,0 +1,200 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+ "fmt"
+ "sort"
+ "time"
+
+ "v.io/v23"
+ "v.io/v23/context"
+ "v.io/v23/naming"
+ "v.io/x/lib/cmdline"
+ "v.io/x/lib/set"
+ "v.io/x/lib/vlog"
+ "v.io/x/ref/lib/v23cmd"
+ _ "v.io/x/ref/runtime/factories/generic"
+ "v.io/x/ref/services/internal/binarylib"
+ "v.io/x/ref/services/internal/profiles"
+ "v.io/x/ref/services/repository"
+)
+
+var cmdTidy = &cmdline.Command{
+ Runner: v23cmd.RunnerFunc(runTidyUp),
+ Name: "binary",
+ Short: "Binary sub-command tidies a specified binaryd",
+ Long: `
+Binary sub-command removes all binaries from a specified binaryd that
+are not referenced by an applicationd envelope stored in the specified
+applicationd.
+`,
+ ArgsName: "<applicationd> <binaryd>",
+ ArgsLong: `
+<applicationd> is the name or endpoint of the applicationd instance
+sourcing the envelopes.
+<binaryd> is the name or endpoint of a binaryd instance to clean up.
+`,
+}
+
+// simpleGlob globs the provided endpoint as the namespace cmd does.
+func mapGlob(ctx *context.T, pattern string, mapFunc func(string)) (error, []error) {
+ ctx, cancel := context.WithTimeout(ctx, time.Minute)
+ defer cancel()
+
+ ns := v23.GetNamespace(ctx)
+ c, err := ns.Glob(ctx, pattern)
+ if err != nil {
+ vlog.Infof("ns.Glob(%q) failed: %v", pattern, err)
+ return err, nil
+ }
+
+ errors := []*naming.GlobError{}
+ for res := range c {
+ switch v := res.(type) {
+ case *naming.GlobReplyEntry:
+ if v.Value.Name != "" {
+ mapFunc(v.Value.Name)
+ }
+ case *naming.GlobReplyError:
+ errors = append(errors, &v.Value)
+ }
+ }
+
+ globErrors := make([]error, 0, len(errors))
+ for _, err := range errors {
+ globErrors = append(globErrors, fmt.Errorf("Glob error: %s: %v\n", err.Name, err.Error))
+ }
+ return nil, globErrors
+}
+
+func logGlobErrors(env *cmdline.Env, errors []error) {
+ for _, err := range errors {
+ vlog.Errorf("Glob error: %v", err)
+ }
+}
+
+// getProfileNames uses glob to extract the list of profile names
+// available from the binary server specified by endpoint.
+func getProfileNames(ctx *context.T, env *cmdline.Env, endpoint string) ([]string, error) {
+ profiles, err := profiles.GetKnownProfiles()
+ if err != nil {
+ return nil, err
+ }
+
+ pnames := make([]string, 0, len(profiles))
+ for _, p := range profiles {
+ pnames = append(pnames, p.Label)
+ }
+ return pnames, nil
+}
+
+func getNames(ctx *context.T, env *cmdline.Env, endpoint string) ([]string, error) {
+ resultSet := make(map[string]struct{})
+ err, errors := mapGlob(ctx, endpoint, func(s string) {
+ resultSet[s] = struct{}{}
+ })
+
+ if err != nil {
+ return nil, err
+ }
+ logGlobErrors(env, errors)
+ s := set.String.ToSlice(resultSet)
+ sort.Strings(s)
+ return s, nil
+}
+
+func runTidyUp(ctx *context.T, env *cmdline.Env, args []string) error {
+ if expected, got := 2, len(args); expected != got {
+ return env.UsageErrorf("match: incorrect number of arguments, expected %d, got %d", expected, got)
+ }
+
+ appEndpoint, binEndpoint := args[0], args[1]
+
+ profileNames, err := getProfileNames(ctx, env, binEndpoint)
+ if err != nil {
+ return err
+ }
+
+ envelopeNames, err := getNames(ctx, env, naming.Join(appEndpoint, "..."))
+ if err != nil {
+ return err
+ }
+
+ // Add every path in use to a set. Deletion scope is limited to
+ // only the binEndpoint.
+ bpaths := make(map[string]struct{})
+ for _, en := range envelopeNames {
+ // convert an envelope name into an envelope.
+ ac := repository.ApplicationClient(en)
+
+ for _, p := range profileNames {
+ e, err := ac.Match(ctx, []string{p})
+ if err != nil {
+ // This error case is very noisy.
+ vlog.VI(2).Infof("applicationd.Match(%s, %s) failed: %v\n", en, p, err)
+ continue
+ }
+
+ root, relative := naming.SplitAddressName(e.Binary.File)
+ if root == binEndpoint || root == "" {
+ bpaths[relative] = struct{}{}
+ }
+ for _, sf := range e.Packages {
+ root, relative := naming.SplitAddressName(sf.File)
+ if root == binEndpoint || root == "" {
+ bpaths[relative] = struct{}{}
+ }
+ }
+ }
+ }
+
+ binaryNames, err := getNames(ctx, env, naming.Join(binEndpoint, "..."))
+ if err != nil {
+ return err
+ }
+
+ deletionCandidates := make([]int, 0, len(binaryNames)-len(envelopeNames))
+ for i, bn := range binaryNames {
+ _, relative := naming.SplitAddressName(bn)
+ if _, ok := bpaths[relative]; ok {
+ // relative is mentioned in an envelope.
+ continue
+ }
+
+ if _, err := binarylib.Stat(ctx, bn); err != nil {
+ // This name is not a binary.
+ continue
+ }
+ deletionCandidates = append(deletionCandidates, i)
+ }
+
+ for _, i := range deletionCandidates {
+ b := binaryNames[i]
+ if err := binarylib.Delete(ctx, b); err != nil {
+ vlog.Errorf("Couldn't delete binary %s: %v", b, err)
+ }
+ }
+
+ return nil
+}
+
+var cmdRoot = &cmdline.Command{
+ Name: "tidy",
+ Short: "Tidy binary repositories",
+ Long: `
+Tidy tidies the Vanadium binary repository by removing unused
+binaries.
+`,
+ Children: []*cmdline.Command{cmdTidy},
+}
+
+func main() {
+ cmdline.HideGlobalFlagsExcept()
+ cmdline.Main(cmdRoot)
+}
diff --git a/services/binary/tidy/impl_test.go b/services/binary/tidy/impl_test.go
new file mode 100644
index 0000000..44e5d00
--- /dev/null
+++ b/services/binary/tidy/impl_test.go
@@ -0,0 +1,316 @@
+// 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
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+ "strings"
+ "testing"
+
+ "v.io/v23/services/application"
+ "v.io/x/lib/cmdline"
+ "v.io/x/ref/lib/v23cmd"
+ "v.io/x/ref/lib/xrpc"
+ _ "v.io/x/ref/runtime/factories/generic"
+ "v.io/x/ref/services/binary/tidy/appd"
+ "v.io/x/ref/services/binary/tidy/binaryd"
+ "v.io/x/ref/services/internal/servicetest"
+ "v.io/x/ref/test"
+)
+
+//go:generate v23 test generate
+
+func TestBinaryClient(t *testing.T) {
+ ctx, shutdown := test.V23Init()
+ defer shutdown()
+
+ binarytape := servicetest.NewTape()
+ binserver, err := xrpc.NewDispatchingServer(ctx, "", binaryd.NewDispatcher(t, binarytape))
+ if err != nil {
+ t.Fatalf("binaryd NewDispatchingServer failed: %v", err)
+ }
+
+ apptape := servicetest.NewTape()
+ appserver, err := xrpc.NewDispatchingServer(ctx, "", appd.NewDispatcher(t, apptape))
+ if err != nil {
+ t.Fatalf("applicationd NewDispatchingServer failed: %v", err)
+ }
+
+ // Setup the command-line.
+ var stdout, stderr bytes.Buffer
+ env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+ binaryName := binserver.Status().Endpoints[0].Name()
+ applicationName := appserver.Status().Endpoints[0].Name()
+
+ binarytape.SetResponses(
+ // Glob for all binary names
+ binaryd.GlobResponse{[]string{
+ "binaries",
+ "binaries/applicationd",
+ "binaries/applicationd/darwin-amd64",
+ "binaries/applicationd/darwin-amd64/app-darwin-amd-1",
+ "binaries/applicationd/darwin-amd64/app-darwin-amd-2",
+ "binaries/applicationd/linux-amd64",
+ "binaries/applicationd/linux-amd64/app-linux-amd-1",
+ "binaries/applicationd/linux-amd64/app-linux-amd-2",
+ "binaries/binaryd",
+ "binaries/binaryd/linux-amd64",
+ "binaries/binaryd/linux-amd64/bind-linux-amd-1",
+ "binaries/binaryd/linux-amd64/bind-linux-amd-2",
+ "binaries/binaryd/linux-amd64/bind-linux-amd-3",
+ "binaries/libraries",
+ "binaries/libraries/linux-amd64",
+ "binaries/libraries/linux-amd64/extra-goo-1",
+ },
+ nil,
+ },
+
+ // Stat calls
+ fmt.Errorf("binaries"),
+ fmt.Errorf("binaries/applicationd"),
+ fmt.Errorf("binaries/applicationd/darwin-amd64"),
+ nil, // binaries/applicationd/darwin-amd64/app-darwin-amd-1
+ fmt.Errorf("binaries/applicationd/linux-amd64"),
+ nil, // binaries/applicationd/linux-amd64/app-linux-amd-1
+ fmt.Errorf("binaries/binaryd"),
+ fmt.Errorf("binaries/binaryd/linux-amd64"),
+ nil, // binaries/binaryd/linux-amd64/bind-linux-amd-1
+ nil, // binaries/binaryd/linux-amd64/bind-linux-amd-2
+ fmt.Errorf("binaries/libraries"),
+ fmt.Errorf("binaries/libraries/linux-amd64"),
+
+ // Deletion of five binaries.
+ nil,
+ nil,
+ nil,
+ nil,
+ nil,
+ )
+
+ apptape.SetResponses(
+ // Glob for all versions of all apps
+ binaryd.GlobResponse{[]string{
+ "applications",
+ "applications/applicationd",
+ "applications/applicationd/0",
+ "applications/binaryd",
+ "applications/binaryd/1",
+ },
+ nil,
+ },
+
+ // applications.Match(linux-amd64)
+ appd.MatchResult{
+ application.Envelope{},
+ fmt.Errorf("no applications.Match(linux-amd64)"),
+ },
+ // applications.Match(linux-386)
+ appd.MatchResult{
+ application.Envelope{},
+ fmt.Errorf("no applications.Match(linux-386)"),
+ },
+ // applications.Match(linux-arm)
+ appd.MatchResult{
+ application.Envelope{},
+ fmt.Errorf("no applications.Match(linux-arm)"),
+ },
+ // applications.Match(darwin-amd64)
+ appd.MatchResult{
+ application.Envelope{},
+ fmt.Errorf("no applications.Match(darwin-amd64)"),
+ },
+ // applications/applicationd.Match(linux-amd64)
+ appd.MatchResult{
+ application.Envelope{
+ Binary: application.SignedFile{
+ File: "binaries/applicationd/linux-amd64/app-linux-amd-2",
+ },
+ },
+ nil,
+ },
+ // applications/applicationd.Match(linux-386)
+ appd.MatchResult{
+ application.Envelope{},
+ fmt.Errorf("no applications/applicationd.Match(linux-386)"),
+ },
+ // applications/applicationd.Match(linux-arm)
+ appd.MatchResult{
+ application.Envelope{},
+ fmt.Errorf("no applications/applicationd.Match(linux-arm)"),
+ },
+ // applications/applicationd.Match(darwin-amd64)
+ appd.MatchResult{
+ application.Envelope{
+ Binary: application.SignedFile{
+ File: "binaries/applicationd/darwin-amd64/app-darwin-amd-2",
+ },
+ },
+ nil,
+ },
+ // applications/applicationd/0.Match(linux-amd64)
+ appd.MatchResult{
+ application.Envelope{
+ Binary: application.SignedFile{
+ File: "binaries/applicationd/linux-amd64/app-linux-amd-2",
+ },
+ },
+ nil,
+ },
+ // applications/applicationd/0.Match(linux-386)
+ appd.MatchResult{
+ application.Envelope{},
+ fmt.Errorf("no applications/applicationd/0.Match(linux-386)"),
+ },
+ // applications/applicationd/0.Match(linux-arm)
+ appd.MatchResult{
+ application.Envelope{},
+ fmt.Errorf("no applications/applicationd/0.Match(linux-arm)"),
+ },
+ // applications/applicationd/0.Match(darwin-amd64)
+ appd.MatchResult{
+ application.Envelope{
+ Binary: application.SignedFile{
+ File: "binaries/applicationd/darwin-amd64/app-darwin-amd-2",
+ },
+ },
+ nil,
+ },
+ // applications/binaryd.Match(linux-amd64)
+ appd.MatchResult{
+ application.Envelope{
+ Binary: application.SignedFile{
+ File: "binaries/binaryd/linux-amd64/bind-linux-amd-3",
+ },
+ Packages: application.Packages{
+ "somewhere": {
+ File: "binaries/libraries/linux-amd64/extra-goo-1",
+ },
+ },
+ },
+ nil,
+ },
+ // applications/binaryd.Match(linux-386)
+ appd.MatchResult{
+ application.Envelope{},
+ fmt.Errorf("no applications/binaryd.Match(linux-386)"),
+ },
+ // applications/binaryd.Match(linux-arm)
+ appd.MatchResult{
+ application.Envelope{},
+ fmt.Errorf("no applications/binaryd.Match(linux-arm)"),
+ },
+ // applications/binaryd.Match(darwin-amd64)
+ appd.MatchResult{
+ application.Envelope{
+ Binary: application.SignedFile{
+ // Deliberately doesn't exist to show that this case is correctly handled.
+ File: "binaries/binaryd/darwin-amd64/bind-darwin-amd-2",
+ },
+ },
+ nil,
+ },
+ // applications/binaryd/1.Match(linux-amd64)
+ appd.MatchResult{
+ application.Envelope{
+ Binary: application.SignedFile{
+ File: "binaries/binaryd/linux-amd64/bind-linux-amd-3",
+ },
+ Packages: application.Packages{
+ "somewhere": {
+ File: "binaries/libraries/linux-amd64/extra-goo-1",
+ },
+ },
+ },
+ nil,
+ },
+ // applications/binaryd/1.Match(linux-386)
+ appd.MatchResult{
+ application.Envelope{},
+ fmt.Errorf("no applications/binaryd/1.Match(linux-386)"),
+ },
+ // applications/binaryd/1.Match(linux-arm)
+ appd.MatchResult{
+ application.Envelope{},
+ fmt.Errorf("no applications/binaryd/1.Match(linux-arm)"),
+ },
+ // applications/binaryd/1.Match(darwin-amd64)
+ appd.MatchResult{
+ application.Envelope{
+ Binary: application.SignedFile{
+ // Deliberately doesn't exist to show that this case is correctly handled.
+ File: "binaries/binaryd/darwin-amd64/bind-darwin-amd-2",
+ },
+ },
+ nil,
+ },
+ )
+
+ if err := v23cmd.ParseAndRunForTest(cmdTidy, ctx, env, []string{applicationName, binaryName}); err != nil {
+ t.Fatalf("error: %v", err)
+ }
+
+ // Verify no output.
+ if expected, got := "", strings.TrimSpace(stdout.String()); got != expected {
+ t.Errorf("Unexpected output from bintidy. Got %q, expected %q", got, expected)
+ }
+ if expected, got := "", strings.TrimSpace(stderr.String()); got != expected {
+ t.Errorf("Unexpected error from bintidy. Got %q, expected %q", got, expected)
+ }
+
+ // Verify binaryd tape.
+ if got, expected := binarytape.Play(), []interface{}{
+ binaryd.GlobStimulus{Pattern: "..."},
+
+ binaryd.StatStimulus{Op: "Stat", Suffix: "binaries"},
+ binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/applicationd"},
+ binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/applicationd/darwin-amd64"},
+ binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/applicationd/darwin-amd64/app-darwin-amd-1"},
+ binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/applicationd/linux-amd64"},
+ binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/applicationd/linux-amd64/app-linux-amd-1"},
+ binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/binaryd"},
+ binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/binaryd/linux-amd64"},
+ binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/binaryd/linux-amd64/bind-linux-amd-1"},
+ binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/binaryd/linux-amd64/bind-linux-amd-2"},
+ binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/libraries"},
+ binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/libraries/linux-amd64"},
+
+ binaryd.DeleteStimulus{Op: "Delete", Suffix: "binaries/applicationd/darwin-amd64/app-darwin-amd-1"},
+ binaryd.DeleteStimulus{Op: "Delete", Suffix: "binaries/applicationd/linux-amd64/app-linux-amd-1"},
+ binaryd.DeleteStimulus{Op: "Delete", Suffix: "binaries/binaryd/linux-amd64/bind-linux-amd-1"},
+ binaryd.DeleteStimulus{Op: "Delete", Suffix: "binaries/binaryd/linux-amd64/bind-linux-amd-2"},
+ }; !reflect.DeepEqual(expected, got) {
+ t.Errorf("binarytape invalid call sequence. Got %#v, want %#v", got, expected)
+ }
+
+ // Verify application tape.
+ if got, expected := apptape.Play(), []interface{}{
+ binaryd.GlobStimulus{"..."},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications", Profiles: []string{"linux-amd64"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications", Profiles: []string{"linux-386"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications", Profiles: []string{"linux-arm"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications", Profiles: []string{"darwin-amd64"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd", Profiles: []string{"linux-amd64"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd", Profiles: []string{"linux-386"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd", Profiles: []string{"linux-arm"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd", Profiles: []string{"darwin-amd64"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd/0", Profiles: []string{"linux-amd64"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd/0", Profiles: []string{"linux-386"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd/0", Profiles: []string{"linux-arm"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd/0", Profiles: []string{"darwin-amd64"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd", Profiles: []string{"linux-amd64"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd", Profiles: []string{"linux-386"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd", Profiles: []string{"linux-arm"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd", Profiles: []string{"darwin-amd64"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd/1", Profiles: []string{"linux-amd64"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd/1", Profiles: []string{"linux-386"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd/1", Profiles: []string{"linux-arm"}},
+ appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd/1", Profiles: []string{"darwin-amd64"}},
+ }; !reflect.DeepEqual(expected, got) {
+ t.Errorf("apptape invalid call sequence. Got %#v, want %#v", got, expected)
+ }
+
+}
diff --git a/services/binary/tidy/v23_internal_test.go b/services/binary/tidy/v23_internal_test.go
new file mode 100644
index 0000000..a80e0ec
--- /dev/null
+++ b/services/binary/tidy/v23_internal_test.go
@@ -0,0 +1,22 @@
+// 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.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+
+package main
+
+import (
+ "os"
+ "testing"
+
+ "v.io/x/ref/test"
+ "v.io/x/ref/test/modules"
+)
+
+func TestMain(m *testing.M) {
+ test.Init()
+ modules.DispatchAndExitIfChild()
+ os.Exit(m.Run())
+}
diff --git a/services/device/deviced/internal/impl/profile.go b/services/device/deviced/internal/impl/profile.go
index 74f9ad1..9908666 100644
--- a/services/device/deviced/internal/impl/profile.go
+++ b/services/device/deviced/internal/impl/profile.go
@@ -13,6 +13,7 @@
"v.io/v23/services/build"
"v.io/v23/services/device"
+ "v.io/x/ref/services/internal/profiles"
"v.io/x/ref/services/profile"
)
@@ -101,7 +102,7 @@
// TODO(jsimsa): Avoid retrieving the list of known profiles from a
// remote server if a recent cached copy exists.
func getProfile(name string) (*profile.Specification, error) {
- profiles, err := getKnownProfiles()
+ profiles, err := profiles.GetKnownProfiles()
if err != nil {
return nil, err
}
@@ -137,74 +138,6 @@
*/
}
-// getKnownProfiles gets a list of description for all publicly known
-// profiles.
-//
-// TODO(jsimsa): Avoid retrieving the list of known profiles from a
-// remote server if a recent cached copy exists.
-func getKnownProfiles() ([]*profile.Specification, error) {
- return []*profile.Specification{
- {
- Label: "linux-amd64",
- Description: "",
- Arch: build.ArchitectureAmd64,
- Os: build.OperatingSystemLinux,
- Format: build.FormatElf,
- },
- {
- // Note that linux-386 is used instead of linux-x86 for the
- // label to facilitate generation of a matching label string
- // using the runtime.GOARCH value. In VDL, the 386 architecture
- // is represented using the value X86 because the VDL grammar
- // does not allow identifiers starting with a number.
- Label: "linux-386",
- Description: "",
- Arch: build.ArchitectureX86,
- Os: build.OperatingSystemLinux,
- Format: build.FormatElf,
- },
- {
- Label: "linux-arm",
- Description: "",
- Arch: build.ArchitectureArm,
- Os: build.OperatingSystemLinux,
- Format: build.FormatElf,
- },
- {
- Label: "darwin-amd64",
- Description: "",
- Arch: build.ArchitectureAmd64,
- Os: build.OperatingSystemDarwin,
- Format: build.FormatMach,
- },
- }, nil
-
- // TODO(jsimsa): This function assumes the existence of a profile
- // server from which a list of known profiles can be retrieved. The
- // profile server is a work in progress. When it exists, the
- // commented out code below should work.
-
- /*
- knownProfiles := make([]profile.Specification, 0)
- client, err := r.NewClient()
- if err != nil {
- return nil, verror.New(ErrOperationFailed, nil, fmt.Sprintf("NewClient() failed: %v\n", err))
- }
- defer client.Close()
- server := // TODO
- method := "List"
- inputs := make([]interface{}, 0)
- call, err := client.StartCall(server, method, inputs)
- if err != nil {
- return nil, verror.New(ErrOperationFailed, nil, fmt.Sprintf("StartCall(%s, %q, %v) failed: %v\n", server, method, inputs, err))
- }
- if err := call.Finish(&knownProfiles); err != nil {
- return nil, verror.New(ErrOperationFailed, nil, fmt.Sprintf("Finish(&knownProfile) failed: %v\n", err))
- }
- return knownProfiles, nil
- */
-}
-
// matchProfiles inputs a profile that describes the host device and a
// set of publicly known profiles and outputs a device description that
// identifies the publicly known profiles supported by the host device.
@@ -241,7 +174,7 @@
if err != nil {
return empty, err
}
- knownProfiles, err := getKnownProfiles()
+ knownProfiles, err := profiles.GetKnownProfiles()
if err != nil {
return empty, err
}
diff --git a/services/internal/binarylib/client.go b/services/internal/binarylib/client.go
index 2219e34..2e0424e 100644
--- a/services/internal/binarylib/client.go
+++ b/services/internal/binarylib/client.go
@@ -105,6 +105,15 @@
return false
}
+func Stat(ctx *context.T, name string) (repository.MediaInfo, error) {
+ client := repository.BinaryClient(name)
+ _, mediaInfo, err := client.Stat(ctx)
+ if err != nil {
+ return repository.MediaInfo{}, err
+ }
+ return mediaInfo, nil
+}
+
func download(ctx *context.T, w io.WriteSeeker, von string) (repository.MediaInfo, error) {
client := repository.BinaryClient(von)
parts, mediaInfo, err := client.Stat(ctx)
diff --git a/services/internal/profiles/listprofiles.go b/services/internal/profiles/listprofiles.go
new file mode 100644
index 0000000..6542bbe
--- /dev/null
+++ b/services/internal/profiles/listprofiles.go
@@ -0,0 +1,85 @@
+// 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 profiles
+
+import (
+ // "bytes"
+ // "errors"
+ // "os/exec"
+ // "runtime"
+ // "strings"
+
+ "v.io/v23/services/build"
+ // "v.io/v23/services/device"
+ "v.io/x/ref/services/profile"
+)
+
+// GetKnownProfiles gets a list of description for all publicly known
+// profiles.
+//
+// TODO(jsimsa): Avoid retrieving the list of known profiles from a
+// remote server if a recent cached copy exists.
+func GetKnownProfiles() ([]*profile.Specification, error) {
+ return []*profile.Specification{
+ {
+ Label: "linux-amd64",
+ Description: "",
+ Arch: build.ArchitectureAmd64,
+ Os: build.OperatingSystemLinux,
+ Format: build.FormatElf,
+ },
+ {
+ // Note that linux-386 is used instead of linux-x86 for the
+ // label to facilitate generation of a matching label string
+ // using the runtime.GOARCH value. In VDL, the 386 architecture
+ // is represented using the value X86 because the VDL grammar
+ // does not allow identifiers starting with a number.
+ Label: "linux-386",
+ Description: "",
+ Arch: build.ArchitectureX86,
+ Os: build.OperatingSystemLinux,
+ Format: build.FormatElf,
+ },
+ {
+ Label: "linux-arm",
+ Description: "",
+ Arch: build.ArchitectureArm,
+ Os: build.OperatingSystemLinux,
+ Format: build.FormatElf,
+ },
+ {
+ Label: "darwin-amd64",
+ Description: "",
+ Arch: build.ArchitectureAmd64,
+ Os: build.OperatingSystemDarwin,
+ Format: build.FormatMach,
+ },
+ }, nil
+
+ // TODO(jsimsa): This function assumes the existence of a profile
+ // server from which a list of known profiles can be retrieved. The
+ // profile server is a work in progress. When it exists, the
+ // commented out code below should work.
+
+ /*
+ knownProfiles := make([]profile.Specification, 0)
+ client, err := r.NewClient()
+ if err != nil {
+ return nil, verror.New(ErrOperationFailed, nil, fmt.Sprintf("NewClient() failed: %v\n", err))
+ }
+ defer client.Close()
+ server := // TODO
+ method := "List"
+ inputs := make([]interface{}, 0)
+ call, err := client.StartCall(server, method, inputs)
+ if err != nil {
+ return nil, verror.New(ErrOperationFailed, nil, fmt.Sprintf("StartCall(%s, %q, %v) failed: %v\n", server, method, inputs, err))
+ }
+ if err := call.Finish(&knownProfiles); err != nil {
+ return nil, verror.New(ErrOperationFailed, nil, fmt.Sprintf("Finish(&knownProfile) failed: %v\n", err))
+ }
+ return knownProfiles, nil
+ */
+}
diff --git a/services/internal/servicetest/mock.go b/services/internal/servicetest/mock.go
index 5419382..8699eae 100644
--- a/services/internal/servicetest/mock.go
+++ b/services/internal/servicetest/mock.go
@@ -29,8 +29,9 @@
if len(t.responses) < 1 {
// Returning an error at this point will likely cause the mock
- // device manager to panic trying to cast the response to what
- // it expects. Panic'ing here at least makes the issue more
+ // using the tape to panic when it tries to cast the response
+ // to the desired type.
+ // Panic'ing here at least makes the issue more
// apparent.
// TODO(caprita): Don't panic.
panic(fmt.Errorf("Record(%#v) had no response", call))