veyron/services/mgmt/profile: Add command-line tool.

Add a command-line tool to interface with the profile manager. At this time,
there is only very limited support for setting the profile specification. Every
profile is the same.

Change-Id: Iedbbdf54068dddc138eafc981196bd2ce204e5e6
diff --git a/services/mgmt/profile/profile/impl/impl.go b/services/mgmt/profile/profile/impl/impl.go
new file mode 100644
index 0000000..ccb9038
--- /dev/null
+++ b/services/mgmt/profile/profile/impl/impl.go
@@ -0,0 +1,148 @@
+package impl
+
+import (
+	"fmt"
+
+	"veyron/lib/cmdline"
+	"veyron/services/mgmt/profile"
+)
+
+var cmdLabel = &cmdline.Command{
+	Run:      runLabel,
+	Name:     "label",
+	Short:    "Shows a human-readable profile key for the profile.",
+	Long:     "Shows a human-readable profile key for the profile.",
+	ArgsName: "<profile>",
+	ArgsLong: "<profile> is the full name of the profile.",
+}
+
+func runLabel(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.Errorf("label: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	p, err := profile.BindProfile(args[0])
+	if err != nil {
+		return fmt.Errorf("bind error: %v", err)
+	}
+	label, err := p.Label()
+	if err != nil {
+		return err
+	}
+	fmt.Fprintln(cmd.Stdout(), label)
+	return nil
+}
+
+var cmdDescription = &cmdline.Command{
+	Run:      runDescription,
+	Name:     "description",
+	Short:    "Shows a human-readable profile description for the profile.",
+	Long:     "Shows a human-readable profile description for the profile.",
+	ArgsName: "<profile>",
+	ArgsLong: "<profile> is the full name of the profile.",
+}
+
+func runDescription(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.Errorf("description: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	p, err := profile.BindProfile(args[0])
+	if err != nil {
+		return fmt.Errorf("bind error: %v", err)
+	}
+	desc, err := p.Description()
+	if err != nil {
+		return err
+	}
+	fmt.Fprintln(cmd.Stdout(), desc)
+	return nil
+}
+
+var cmdSpec = &cmdline.Command{
+	Run:      runSpec,
+	Name:     "spec",
+	Short:    "Shows the specification of the profile.",
+	Long:     "Shows the specification of the profile.",
+	ArgsName: "<profile>",
+	ArgsLong: "<profile> is the full name of the profile.",
+}
+
+func runSpec(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.Errorf("spec: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	p, err := profile.BindProfile(args[0])
+	if err != nil {
+		return fmt.Errorf("bind error: %v", err)
+	}
+	spec, err := p.Specification()
+	if err != nil {
+		return err
+	}
+	fmt.Fprintf(cmd.Stdout(), "%#v\n", spec)
+	return nil
+}
+
+var cmdPut = &cmdline.Command{
+	Run:      runPut,
+	Name:     "put",
+	Short:    "Sets a placeholder specification for the profile.",
+	Long:     "Sets a placeholder specification for the profile.",
+	ArgsName: "<profile>",
+	ArgsLong: "<profile> is the full name of the profile.",
+}
+
+func runPut(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.Errorf("put: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	p, err := profile.BindProfile(args[0])
+	if err != nil {
+		return fmt.Errorf("bind error: %v", err)
+	}
+
+	// TODO(rthellend): Read an actual specification from a file.
+	spec := profile.Specification{
+		Format:      profile.Format{Name: "elf", Attributes: map[string]string{"os": "linux", "arch": "amd64"}},
+		Libraries:   map[profile.Library]struct{}{profile.Library{Name: "foo", MajorVersion: "1", MinorVersion: "0"}: struct{}{}},
+		Label:       "example",
+		Description: "Example profile to test the profile manager implementation.",
+	}
+	if err := p.Put(spec); err != nil {
+		return err
+	}
+	fmt.Fprintln(cmd.Stdout(), "Specification updated successfully.")
+	return nil
+}
+
+var cmdRemove = &cmdline.Command{
+	Run:      runRemove,
+	Name:     "remove",
+	Short:    "removes the profile specification for the profile.",
+	Long:     "removes the profile specification for the profile.",
+	ArgsName: "<profile>",
+	ArgsLong: "<profile> is the full name of the profile.",
+}
+
+func runRemove(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.Errorf("remove: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	p, err := profile.BindProfile(args[0])
+	if err != nil {
+		return fmt.Errorf("bind error: %v", err)
+	}
+	if err = p.Remove(); err != nil {
+		return err
+	}
+	fmt.Fprintln(cmd.Stdout(), "Profile removed successfully.")
+	return nil
+}
+
+func Root() *cmdline.Command {
+	return &cmdline.Command{
+		Name:     "profile",
+		Short:    "Command-line tool for interacting with the Veyron profile manager",
+		Long:     "Command-line tool for interacting with the Veyron profile manager",
+		Children: []*cmdline.Command{cmdLabel, cmdDescription, cmdSpec, cmdPut, cmdRemove},
+	}
+}
diff --git a/services/mgmt/profile/profile/impl/impl_test.go b/services/mgmt/profile/profile/impl/impl_test.go
new file mode 100644
index 0000000..c8784b7
--- /dev/null
+++ b/services/mgmt/profile/profile/impl/impl_test.go
@@ -0,0 +1,165 @@
+package impl_test
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+	"testing"
+
+	"veyron/services/mgmt/profile"
+	"veyron/services/mgmt/profile/profile/impl"
+
+	"veyron2"
+	"veyron2/ipc"
+	"veyron2/naming"
+	"veyron2/rt"
+	"veyron2/security"
+	"veyron2/vlog"
+)
+
+var (
+	// spec is an example profile specification used throughout the test.
+	spec = profile.Specification{
+		Format:      profile.Format{Name: "elf", Attributes: map[string]string{"os": "linux"}},
+		Libraries:   map[profile.Library]struct{}{profile.Library{Name: "foo", MajorVersion: "1", MinorVersion: "0"}: struct{}{}},
+		Label:       "example",
+		Description: "Example profile to test the profile manager implementation.",
+	}
+)
+
+type server struct {
+	suffix string
+}
+
+func (s *server) Label(ipc.Context) (string, error) {
+	vlog.VI(2).Infof("%v.Label() was called", s.suffix)
+	if s.suffix != "exists" {
+		return "", fmt.Errorf("profile doesn't exist: %v", s.suffix)
+	}
+	return spec.Label, nil
+}
+
+func (s *server) Description(ipc.Context) (string, error) {
+	vlog.VI(2).Infof("%v.Description() was called", s.suffix)
+	if s.suffix != "exists" {
+		return "", fmt.Errorf("profile doesn't exist: %v", s.suffix)
+	}
+	return spec.Description, nil
+}
+
+func (s *server) Specification(ipc.Context) (profile.Specification, error) {
+	vlog.VI(2).Infof("%v.Specification() was called", s.suffix)
+	if s.suffix != "exists" {
+		return profile.Specification{}, fmt.Errorf("profile doesn't exist: %v", s.suffix)
+	}
+	return spec, nil
+}
+
+func (s *server) Put(_ ipc.Context, _ profile.Specification) error {
+	vlog.VI(2).Infof("%v.Put() was called", s.suffix)
+	return nil
+}
+
+func (s *server) Remove(ipc.Context) error {
+	vlog.VI(2).Infof("%v.Remove() was called", s.suffix)
+	if s.suffix != "exists" {
+		return fmt.Errorf("profile doesn't exist: %v", s.suffix)
+	}
+	return nil
+}
+
+type dispatcher struct {
+}
+
+func NewDispatcher() *dispatcher {
+	return &dispatcher{}
+}
+
+func (d *dispatcher) Lookup(suffix string) (ipc.Invoker, security.Authorizer, error) {
+	invoker := ipc.ReflectInvoker(profile.NewServerProfile(&server{suffix: suffix}))
+	return invoker, nil, nil
+}
+
+func startServer(t *testing.T, r veyron2.Runtime) (ipc.Server, naming.Endpoint, error) {
+	dispatcher := NewDispatcher()
+	server, err := r.NewServer()
+	if err != nil {
+		t.Errorf("NewServer failed: %v", err)
+		return nil, nil, err
+	}
+	if err := server.Register("", dispatcher); err != nil {
+		t.Errorf("Register failed: %v", err)
+		return nil, nil, err
+	}
+	endpoint, err := server.Listen("tcp", "localhost:0")
+	if err != nil {
+		t.Errorf("Listen failed: %v", err)
+		return nil, nil, err
+	}
+	return server, endpoint, nil
+}
+
+func stopServer(t *testing.T, server ipc.Server) {
+	if err := server.Stop(); err != nil {
+		t.Errorf("server.Stop failed: %v", err)
+	}
+}
+
+func TestProfileClient(t *testing.T) {
+	runtime := rt.Init()
+	server, endpoint, err := startServer(t, runtime)
+	if err != nil {
+		return
+	}
+	defer stopServer(t, server)
+	// Setup the command-line.
+	cmd := impl.Root()
+	var stdout, stderr bytes.Buffer
+	cmd.Init(nil, &stdout, &stderr)
+	exists := naming.JoinAddressName(endpoint.String(), "exists")
+
+	// Test the 'label' command.
+	if err := cmd.Execute([]string{"label", exists}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := spec.Label, strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+
+	// Test the 'description' command.
+	if err := cmd.Execute([]string{"description", exists}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := spec.Description, strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+
+	// Test the 'spec' command.
+	if err := cmd.Execute([]string{"spec", exists}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := fmt.Sprintf("%#v", spec), strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+
+	// Test the 'put' command.
+	if err := cmd.Execute([]string{"put", exists}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := "Specification updated successfully.", strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+
+	// Test the 'remove' command.
+	if err := cmd.Execute([]string{"remove", exists}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := "Profile removed successfully.", strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+}
diff --git a/services/mgmt/profile/profile/main.go b/services/mgmt/profile/profile/main.go
new file mode 100644
index 0000000..4b7f23f
--- /dev/null
+++ b/services/mgmt/profile/profile/main.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+	"veyron/services/mgmt/profile/profile/impl"
+
+	"veyron2/rt"
+)
+
+func main() {
+	r := rt.Init()
+	defer r.Shutdown()
+
+	impl.Root().Main()
+}