veyron/lib/binary: binary repository client-side library

Change-Id: I7306eefbfe73fa1fbb49160190b818cfc509565d
diff --git a/lib/testutil/blackbox/subprocess.go b/lib/testutil/blackbox/subprocess.go
index 8c7b655..0cb8092 100644
--- a/lib/testutil/blackbox/subprocess.go
+++ b/lib/testutil/blackbox/subprocess.go
@@ -81,11 +81,11 @@
 	stdout, _ := cmd.StdoutPipe()
 	stdin, _ := cmd.StdinPipe()
 	return &Child{
-		Cmd:    cmd,
-		Name:   command,
-		Stdout: bufio.NewReader(stdout),
-		Stdin:  stdin,
-		stderr: stderr,
+		Cmd:       cmd,
+		Name:      command,
+		Stdout:    bufio.NewReader(stdout),
+		Stdin:     stdin,
+		stderr:    stderr,
 		t:         t,
 		hasFailed: false,
 	}
diff --git a/runtimes/google/ipc/full_test.go b/runtimes/google/ipc/full_test.go
index 66e1cc7..dcf068e 100644
--- a/runtimes/google/ipc/full_test.go
+++ b/runtimes/google/ipc/full_test.go
@@ -38,10 +38,10 @@
 
 var (
 	errAuthorizer = errors.New("ipc: application Authorizer denied access")
-	errMethod = verror.Abortedf("server returned an error")
-	clientID  security.PrivateID
-	serverID  security.PrivateID
-	clock     = new(fakeClock)
+	errMethod     = verror.Abortedf("server returned an error")
+	clientID      security.PrivateID
+	serverID      security.PrivateID
+	clock         = new(fakeClock)
 )
 
 type fakeClock struct {
diff --git a/services/mgmt/binary/binaryd/main.go b/services/mgmt/binary/binaryd/main.go
index bb59019..1ea4203 100644
--- a/services/mgmt/binary/binaryd/main.go
+++ b/services/mgmt/binary/binaryd/main.go
@@ -84,7 +84,6 @@
 		return
 	}
 	vlog.Infof("Binary repository published at %v/%v", endpoint, name)
-
 	// Wait until shutdown.
 	<-signals.ShutdownOnSignals()
 }
diff --git a/services/mgmt/lib/binary/impl.go b/services/mgmt/lib/binary/impl.go
new file mode 100644
index 0000000..357c5d0
--- /dev/null
+++ b/services/mgmt/lib/binary/impl.go
@@ -0,0 +1,276 @@
+// Package binary provides a client-side library for the binary
+// repository.
+//
+// TODO(jsimsa): Implement parallel download and upload.
+package binary
+
+import (
+	"bytes"
+	"crypto/md5"
+	"encoding/hex"
+	"io"
+	"io/ioutil"
+	"os"
+
+	"veyron2/rt"
+	"veyron2/services/mgmt/binary"
+	"veyron2/services/mgmt/repository"
+	"veyron2/verror"
+	"veyron2/vlog"
+)
+
+var (
+	errOperationFailed = verror.Internalf("operation failed")
+	errNotExist        = verror.NotFoundf("binary does not exist")
+)
+
+const (
+	nAttempts   = 2
+	partSize    = 1 << 22
+	subpartSize = 1 << 12
+)
+
+func Delete(name string) error {
+	client, err := repository.BindBinary(name)
+	if err != nil {
+		vlog.Errorf("BindBinary(%v) failed: %v", name, err)
+		return err
+	}
+	if err := client.Delete(rt.R().NewContext()); err != nil {
+		vlog.Errorf("Delete() failed: %v", err)
+		return err
+	}
+	return nil
+}
+
+func download(w io.WriteSeeker, von string) error {
+	client, err := repository.BindBinary(von)
+	if err != nil {
+		vlog.Errorf("BindBinary(%v) failed: %v", von, err)
+		return err
+	}
+	parts, err := client.Stat(rt.R().NewContext())
+	if err != nil {
+		vlog.Errorf("Stat() failed: %v", err)
+		return err
+	}
+	for _, part := range parts {
+		if part.Checksum == binary.MissingChecksum {
+			return errNotExist
+		}
+	}
+	offset, whence := int64(0), 0
+	for i, part := range parts {
+		success := false
+	download:
+		for j := 0; !success && j < nAttempts; j++ {
+			if _, err := w.Seek(offset, whence); err != nil {
+				vlog.Errorf("Seek(%v, %v) failed: %v", offset, whence, err)
+				continue
+			}
+			stream, err := client.Download(rt.R().NewContext(), int32(i))
+			if err != nil {
+				vlog.Errorf("Download(%v) failed: %v", i, err)
+				continue
+			}
+			h, nreceived := md5.New(), 0
+			for {
+				bytes, err := stream.Recv()
+				if err != nil {
+					if err != io.EOF {
+						vlog.Errorf("Recv() failed: %v", err)
+						stream.Cancel()
+						continue download
+					}
+					break
+				}
+				if _, err := w.Write(bytes); err != nil {
+					vlog.Errorf("Write() failed: %v", err)
+					stream.Cancel()
+					continue download
+				}
+				h.Write(bytes)
+				nreceived += len(bytes)
+			}
+			if err := stream.Finish(); err != nil {
+				vlog.Errorf("Finish() failed: %v", err)
+				continue
+			}
+			if expected, got := part.Checksum, hex.EncodeToString(h.Sum(nil)); expected != got {
+				vlog.Errorf("Unexpected checksum: expected %v, got %v", expected, got)
+				continue
+			}
+			if expected, got := part.Size, int64(nreceived); expected != got {
+				vlog.Errorf("Unexpected size: expected %v, got %v", expected, got)
+				continue
+			}
+			success = true
+		}
+		if !success {
+			return errOperationFailed
+		}
+		offset += part.Size
+	}
+	return nil
+}
+
+func Download(von string) ([]byte, error) {
+	dir, prefix := "", ""
+	file, err := ioutil.TempFile(dir, prefix)
+	if err != nil {
+		vlog.Errorf("TempFile(%v, %v) failed: %v", dir, prefix, err)
+		return nil, errOperationFailed
+	}
+	defer os.Remove(file.Name())
+	defer file.Close()
+	if err := download(file, von); err != nil {
+		return nil, errOperationFailed
+	}
+	bytes, err := ioutil.ReadFile(file.Name())
+	if err != nil {
+		vlog.Errorf("ReadFile(%v) failed: %v", file.Name(), err)
+		return nil, errOperationFailed
+	}
+	return bytes, nil
+}
+
+func DownloadToFile(von, path string) error {
+	dir, prefix := "", ""
+	file, err := ioutil.TempFile(dir, prefix)
+	if err != nil {
+		vlog.Errorf("TempFile(%v, %v) failed: %v", dir, prefix, err)
+		return errOperationFailed
+	}
+	defer file.Close()
+	if err := download(file, von); err != nil {
+		if err := os.Remove(file.Name()); err != nil {
+			vlog.Errorf("Remove(%v) failed: %v", file.Name(), err)
+		}
+		return errOperationFailed
+	}
+	perm := os.FileMode(0700)
+	if err := file.Chmod(perm); err != nil {
+		vlog.Errorf("Chmod(%v) failed: %v", perm, err)
+		if err := os.Remove(file.Name()); err != nil {
+			vlog.Errorf("Remove(%v) failed: %v", file.Name(), err)
+		}
+		return errOperationFailed
+	}
+	if err := os.Rename(file.Name(), path); err != nil {
+		vlog.Errorf("Rename(%v, %v) failed: %v", file.Name(), path, err)
+		if err := os.Remove(file.Name()); err != nil {
+			vlog.Errorf("Remove(%v) failed: %v", file.Name(), err)
+		}
+		return errOperationFailed
+	}
+	return nil
+}
+
+func upload(r io.ReadSeeker, von string) error {
+	client, err := repository.BindBinary(von)
+	if err != nil {
+		vlog.Errorf("BindBinary(%v) failed: %v", von, err)
+		return err
+	}
+	offset, whence := int64(0), 2
+	size, err := r.Seek(offset, whence)
+	if err != nil {
+		vlog.Errorf("Seek(%v, %v) failed: %v", offset, whence, err)
+		return errOperationFailed
+	}
+	nparts := (size-1)/partSize + 1
+	if err := client.Create(rt.R().NewContext(), int32(nparts)); err != nil {
+		vlog.Errorf("Create() failed: %v", err)
+		return err
+	}
+	for i := 0; int64(i) < nparts; i++ {
+		success := false
+	upload:
+		for j := 0; !success && j < nAttempts; j++ {
+			offset, whence := int64(i*partSize), 0
+			if _, err := r.Seek(offset, whence); err != nil {
+				vlog.Errorf("Seek(%v, %v) failed: %v", offset, whence, err)
+				continue
+			}
+			stream, err := client.Upload(rt.R().NewContext(), int32(i))
+			if err != nil {
+				vlog.Errorf("Upload(%v) failed: %v", i, err)
+				continue
+			}
+			buffer := make([]byte, partSize)
+			if int64(i+1) == nparts {
+				buffer = buffer[:(size % partSize)]
+			}
+			nread := 0
+			for nread < len(buffer) {
+				n, err := r.Read(buffer[nread:])
+				nread += n
+				if err != nil && (err != io.EOF || nread < len(buffer)) {
+					vlog.Errorf("Read() failed: %v", err)
+					stream.Cancel()
+					continue upload
+				}
+			}
+			for from := 0; from < len(buffer); from += subpartSize {
+				to := from + subpartSize
+				if to > len(buffer) {
+					to = len(buffer)
+				}
+				if err := stream.Send(buffer[from:to]); err != nil {
+					vlog.Errorf("Send() failed: %v", err)
+					stream.Cancel()
+					continue upload
+				}
+			}
+			if err := stream.CloseSend(); err != nil {
+				vlog.Errorf("CloseSend() failed: %v", err)
+				parts, statErr := client.Stat(rt.R().NewContext())
+				if statErr != nil {
+					vlog.Errorf("Stat() failed: %v", statErr)
+					if deleteErr := client.Delete(rt.R().NewContext()); err != nil {
+						vlog.Errorf("Delete() failed: %v", deleteErr)
+					}
+					return err
+				}
+				if parts[i].Checksum == binary.MissingChecksum {
+					stream.Cancel()
+					continue
+				}
+			}
+			if err := stream.Finish(); err != nil {
+				vlog.Errorf("Finish() failed: %v", err)
+				parts, statErr := client.Stat(rt.R().NewContext())
+				if statErr != nil {
+					vlog.Errorf("Stat() failed: %v", statErr)
+					if deleteErr := client.Delete(rt.R().NewContext()); err != nil {
+						vlog.Errorf("Delete() failed: %v", deleteErr)
+					}
+					return err
+				}
+				if parts[i].Checksum == binary.MissingChecksum {
+					continue
+				}
+			}
+			success = true
+		}
+		if !success {
+			return errOperationFailed
+		}
+	}
+	return nil
+}
+
+func Upload(von string, data []byte) error {
+	buffer := bytes.NewReader(data)
+	return upload(buffer, von)
+}
+
+func UploadFromFile(von, path string) error {
+	file, err := os.Open(path)
+	defer file.Close()
+	if err != nil {
+		vlog.Errorf("Open(%v) failed: %v", err)
+		return errOperationFailed
+	}
+	return upload(file, von)
+}
diff --git a/services/mgmt/lib/binary/impl_test.go b/services/mgmt/lib/binary/impl_test.go
new file mode 100644
index 0000000..e99c693
--- /dev/null
+++ b/services/mgmt/lib/binary/impl_test.go
@@ -0,0 +1,135 @@
+package binary
+
+import (
+	"bytes"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"veyron/lib/testutil"
+	"veyron/services/mgmt/binary/impl"
+
+	"veyron2/naming"
+	"veyron2/rt"
+	"veyron2/vlog"
+)
+
+const (
+	veyronPrefix = "veyron_binary_repository"
+)
+
+func init() {
+	rt.Init()
+}
+
+func setupRepository(t *testing.T) (string, func()) {
+	// Setup the root of the binary repository.
+	root, err := ioutil.TempDir("", veyronPrefix)
+	if err != nil {
+		t.Fatalf("TempDir() failed: %v", err)
+	}
+	path, perm := filepath.Join(root, impl.VersionFile), os.FileMode(0600)
+	if err := ioutil.WriteFile(path, []byte(impl.Version), perm); err != nil {
+		vlog.Fatalf("WriteFile(%v, %v, %v) failed: %v", path, impl.Version, perm, err)
+	}
+	// Setup and start the binary repository server.
+	server, err := rt.R().NewServer()
+	if err != nil {
+		t.Fatalf("NewServer() failed: %v", err)
+	}
+	depth := 2
+	dispatcher, err := impl.NewDispatcher(root, depth, nil)
+	if err != nil {
+		t.Fatalf("NewDispatcher(%v, %v, %v) failed: %v", root, depth, nil, err)
+	}
+	protocol, hostname := "tcp", "localhost:0"
+	endpoint, err := server.Listen(protocol, hostname)
+	if err != nil {
+		t.Fatalf("Listen(%v, %v) failed: %v", protocol, hostname, err)
+	}
+	suffix := ""
+	if err := server.Serve(suffix, dispatcher); err != nil {
+		t.Fatalf("Serve(%v, %v) failed: %v", suffix, dispatcher, err)
+	}
+	von := naming.JoinAddressName(endpoint.String(), "//test")
+	return von, func() {
+		if err := os.Remove(path); err != nil {
+			t.Fatalf("Remove(%v) failed: %v", path, err)
+		}
+		// Check that any directories and files that were created to
+		// represent the binary objects have been garbage collected.
+		if err := os.Remove(root); err != nil {
+			t.Fatalf("Remove(%v) failed: %v", root, err)
+		}
+		// Shutdown the binary repository server.
+		if err := server.Stop(); err != nil {
+			t.Fatalf("Stop() failed: %v", err)
+		}
+	}
+}
+
+// TestBufferAPI tests the binary repository client-side library
+// interface using buffers.
+func TestBufferAPI(t *testing.T) {
+	von, cleanup := setupRepository(t)
+	defer cleanup()
+	data := testutil.RandomBytes(testutil.Rand.Intn(10 << 20))
+	if err := Upload(von, data); err != nil {
+		t.Fatalf("Upload(%v) failed: %v", von, err)
+	}
+	output, err := Download(von)
+	if err != nil {
+		t.Fatalf("Download(%v) failed: %v", von, err)
+	}
+	if bytes.Compare(data, output) != 0 {
+		t.Fatalf("Data mismatch:\nexpected %v %v\ngot %v %v", len(data), data[:100], len(output), output[:100])
+	}
+	if err := Delete(von); err != nil {
+		t.Fatalf("Delete(%v) failed: %v", von, err)
+	}
+	if _, err := Download(von); err == nil {
+		t.Fatalf("Download(%v) did not fail", von)
+	}
+}
+
+// TestFileAPI tests the binary repository client-side library
+// interface using files.
+func TestFileAPI(t *testing.T) {
+	von, cleanup := setupRepository(t)
+	defer cleanup()
+	// Create up to 10MB of random bytes.
+	data := testutil.RandomBytes(testutil.Rand.Intn(10 << 20))
+	dir, prefix := "", ""
+	src, err := ioutil.TempFile(dir, prefix)
+	if err != nil {
+		t.Fatalf("TempFile(%v, %v) failed: %v", dir, prefix, err)
+	}
+	defer os.Remove(src.Name())
+	defer src.Close()
+	dst, err := ioutil.TempFile(dir, prefix)
+	if err != nil {
+		t.Fatalf("TempFile(%v, %v) failed: %v", dir, prefix, err)
+	}
+	defer os.Remove(dst.Name())
+	defer dst.Close()
+	if _, err := src.Write(data); err != nil {
+		t.Fatalf("Write() failed: %v", err)
+	}
+	if err := UploadFromFile(von, src.Name()); err != nil {
+		t.Fatalf("UploadFromFile(%v, %v) failed: %v", von, src.Name(), err)
+	}
+	if err := DownloadToFile(von, dst.Name()); err != nil {
+		t.Fatalf("DownloadToFile(%v, %v) failed: %v", von, dst.Name(), err)
+	}
+	output, err := ioutil.ReadFile(dst.Name())
+	if err != nil {
+		t.Fatalf("ReadFile(%v) failed: %v", dst.Name(), err)
+	}
+	if bytes.Compare(data, output) != 0 {
+		t.Fatalf("Data mismatch:\nexpected %v %v\ngot %v %v", len(data), data[:100], len(output), output[:100])
+	}
+	if err := Delete(von); err != nil {
+		t.Fatalf("Delete(%v) failed: %v", von, err)
+	}
+}
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index f944877..a903c63 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -1,6 +1,8 @@
 package impl_test
 
 import (
+	"crypto/md5"
+	"encoding/hex"
 	"errors"
 	"fmt"
 	"io"
@@ -119,7 +121,14 @@
 
 func (*crInvoker) Stat(ipc.ServerContext) ([]binary.PartInfo, error) {
 	vlog.VI(0).Infof("Stat()")
-	return make([]binary.PartInfo, 1), nil
+	h := md5.New()
+	bytes, err := ioutil.ReadFile(os.Args[0])
+	if err != nil {
+		return []binary.PartInfo{}, errOperationFailed
+	}
+	h.Write(bytes)
+	part := binary.PartInfo{Checksum: hex.EncodeToString(h.Sum(nil)), Size: int64(len(bytes))}
+	return []binary.PartInfo{part}, nil
 }
 
 func (i *crInvoker) Upload(ipc.ServerContext, int32, repository.BinaryServiceUploadStream) error {
diff --git a/services/mgmt/node/impl/invoker.go b/services/mgmt/node/impl/invoker.go
index 72c3186..c6ca55d 100644
--- a/services/mgmt/node/impl/invoker.go
+++ b/services/mgmt/node/impl/invoker.go
@@ -28,7 +28,6 @@
 	"bytes"
 	"errors"
 	"fmt"
-	"io"
 	"io/ioutil"
 	"math/rand"
 	"os"
@@ -44,6 +43,7 @@
 	"veyron/lib/config"
 	vexec "veyron/lib/exec"
 	ibuild "veyron/services/mgmt/build"
+	"veyron/services/mgmt/lib/binary"
 	"veyron/services/mgmt/profile"
 
 	"veyron2/ipc"
@@ -330,55 +330,15 @@
 
 // APPLICATION INTERFACE IMPLEMENTATION
 
-func downloadBinary(workspace, binary string) error {
-	stub, err := repository.BindBinary(binary)
+func downloadBinary(workspace, name string) error {
+	data, err := binary.Download(name)
 	if err != nil {
-		vlog.Errorf("BindBinary(%q) failed: %v", binary, err)
+		vlog.Errorf("Download(%v) failed: %v", name, err)
 		return errOperationFailed
 	}
-	path := filepath.Join(workspace, "noded")
-	file, err := os.Create(path)
-	if err != nil {
-		vlog.Errorf("Create(%q) failed: %v", path, err)
-		return errOperationFailed
-	}
-	defer file.Close()
-	parts, err := stub.Stat(rt.R().NewContext())
-	if err != nil {
-		vlog.Errorf("Stat() failed: %v", err)
-		return errOperationFailed
-	}
-	// TODO(jsimsa): Replace the code below with a call to a client-side
-	// binary library once this library exists. In particular, we should
-	// take care of resumption and consistency checking.
-	for i := 0; i < len(parts); i++ {
-		stream, err := stub.Download(rt.R().NewContext(), int32(i))
-		if err != nil {
-			vlog.Errorf("Download() failed: %v", err)
-			return errOperationFailed
-		}
-		for {
-			bytes, err := stream.Recv()
-			if err == io.EOF {
-				break
-			}
-			if err != nil {
-				vlog.Errorf("Recv() failed: %v", err)
-				return errOperationFailed
-			}
-			if _, err := file.Write(bytes); err != nil {
-				vlog.Errorf("Write() failed: %v", err)
-				return errOperationFailed
-			}
-		}
-		if err := stream.Finish(); err != nil {
-			vlog.Errorf("Finish() failed: %v", err)
-			return errOperationFailed
-		}
-	}
-	mode := os.FileMode(0755)
-	if err := file.Chmod(mode); err != nil {
-		vlog.Errorf("Chmod(%v) failed: %v", mode, err)
+	path, perm := filepath.Join(workspace, "noded"), os.FileMode(755)
+	if err := ioutil.WriteFile(path, data, perm); err != nil {
+		vlog.Errorf("WriteFile(%v, %v) failed: %v", path, perm, err)
 		return errOperationFailed
 	}
 	return nil
diff --git a/tools/binary/impl/impl.go b/tools/binary/impl/impl.go
index d4d205f..29b26b1 100644
--- a/tools/binary/impl/impl.go
+++ b/tools/binary/impl/impl.go
@@ -2,13 +2,9 @@
 
 import (
 	"fmt"
-	"io"
-	"os"
 
 	"veyron/lib/cmdline"
-
-	"veyron2/rt"
-	"veyron2/services/mgmt/repository"
+	"veyron/services/mgmt/lib/binary"
 )
 
 var cmdDelete = &cmdline.Command{
@@ -16,21 +12,16 @@
 	Name:     "delete",
 	Short:    "Delete binary",
 	Long:     "Delete connects to the binary repository and deletes the specified binary",
-	ArgsName: "<binary>",
-	ArgsLong: "<binary> is the object name of the binary to delete",
+	ArgsName: "<von>",
+	ArgsLong: "<von> is the veyron object name of the binary to delete",
 }
 
 func runDelete(cmd *cmdline.Command, args []string) error {
 	if expected, got := 1, len(args); expected != got {
 		return cmd.Errorf("delete: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
-	binary := args[0]
-
-	c, err := repository.BindBinary(binary)
-	if err != nil {
-		return fmt.Errorf("bind error: %v", err)
-	}
-	if err = c.Delete(rt.R().NewContext()); err != nil {
+	von := args[0]
+	if err := binary.Delete(von); err != nil {
 		return err
 	}
 	fmt.Fprintf(cmd.Stdout(), "Binary deleted successfully\n")
@@ -45,9 +36,9 @@
 Download connects to the binary repository, downloads the specified binary, and
 writes it to a file.
 `,
-	ArgsName: "<binary> <filename>",
+	ArgsName: "<von> <filename>",
 	ArgsLong: `
-<binary> is the object name of the binary to download
+<von> is the veyron object name of the binary to download
 <filename> is the name of the file where the binary will be written
 `,
 }
@@ -56,44 +47,10 @@
 	if expected, got := 2, len(args); expected != got {
 		return cmd.Errorf("download: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
-	binary, filename := args[0], args[1]
-	f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700)
-	if err != nil {
-		return fmt.Errorf("failed to open %q: %v", filename, err)
-	}
-	defer f.Close()
-
-	c, err := repository.BindBinary(binary)
-	if err != nil {
-		return fmt.Errorf("bind error: %v", err)
-	}
-
-	// TODO(jsimsa): Replace the code below with a call to a client-side
-	// binary library once this library exists. In particular, we should
-	// take care of resumption and consistency checking.
-	stream, err := c.Download(rt.R().NewContext(), 0)
-	if err != nil {
+	von, filename := args[0], args[1]
+	if err := binary.DownloadToFile(von, filename); err != nil {
 		return err
 	}
-
-	for {
-		buf, err := stream.Recv()
-		if err != nil {
-			if err == io.EOF {
-				break
-			}
-			return fmt.Errorf("recv error: %v", err)
-		}
-		if _, err = f.Write(buf); err != nil {
-			return fmt.Errorf("write error: %v", err)
-		}
-	}
-
-	err = stream.Finish()
-	if err != nil {
-		return fmt.Errorf("finish error: %v", err)
-	}
-
 	fmt.Fprintf(cmd.Stdout(), "Binary downloaded to file %s\n", filename)
 	return nil
 }
@@ -106,9 +63,9 @@
 Upload connects to the binary repository and uploads the binary of the specified
 file. When successful, it writes the name of the new binary to stdout.
 `,
-	ArgsName: "<binary> <filename>",
+	ArgsName: "<von> <filename>",
 	ArgsLong: `
-<binary> is the object name of the binary to upload
+<von> is the veyron object name of the binary to upload
 <filename> is the name of the file to upload
 `,
 }
@@ -117,49 +74,11 @@
 	if expected, got := 2, len(args); expected != got {
 		return cmd.Errorf("upload: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
-	binary, filename := args[0], args[1]
-	f, err := os.Open(filename)
-	if err != nil {
-		return fmt.Errorf("failed to open %q: %v", filename, err)
-	}
-	defer f.Close()
-
-	c, err := repository.BindBinary(binary)
-	if err != nil {
-		return fmt.Errorf("bind error: %v", err)
-	}
-
-	// TODO(jsimsa): Add support for uploading multi-part binaries.
-	if err := c.Create(rt.R().NewContext(), 1); err != nil {
+	von, filename := args[0], args[1]
+	if err := binary.UploadFromFile(von, filename); err != nil {
 		return err
 	}
-
-	stream, err := c.Upload(rt.R().NewContext(), 0)
-	if err != nil {
-		return err
-	}
-
-	var buf [4096]byte
-	for {
-		n, err := f.Read(buf[:])
-		if err != nil {
-			if err == io.EOF {
-				break
-			}
-			return fmt.Errorf("read error: %v", err)
-		}
-		if err := stream.Send(buf[:n]); err != nil {
-			return fmt.Errorf("send error: %v", err)
-		}
-	}
-	if err := stream.CloseSend(); err != nil {
-		return fmt.Errorf("closesend error: %v", err)
-	}
-
-	if err := stream.Finish(); err != nil {
-		return fmt.Errorf("finish error: %v", err)
-	}
-
+	fmt.Fprintf(cmd.Stdout(), "Binary uploaded from file %s\n", filename)
 	return nil
 }
 
diff --git a/tools/binary/impl/impl_test.go b/tools/binary/impl/impl_test.go
index 70c0122..5589c4a 100644
--- a/tools/binary/impl/impl_test.go
+++ b/tools/binary/impl/impl_test.go
@@ -2,6 +2,8 @@
 
 import (
 	"bytes"
+	"crypto/md5"
+	"encoding/hex"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -26,12 +28,12 @@
 }
 
 func (s *server) Create(ipc.ServerContext, int32) error {
-	vlog.VI(2).Infof("Create() was called. suffix=%v", s.suffix)
+	vlog.Infof("Create() was called. suffix=%v", s.suffix)
 	return nil
 }
 
 func (s *server) Delete(ipc.ServerContext) error {
-	vlog.VI(2).Infof("Delete() was called. suffix=%v", s.suffix)
+	vlog.Infof("Delete() was called. suffix=%v", s.suffix)
 	if s.suffix != "exists" {
 		return fmt.Errorf("binary doesn't exist: %v", s.suffix)
 	}
@@ -39,24 +41,28 @@
 }
 
 func (s *server) Download(_ ipc.ServerContext, _ int32, stream repository.BinaryServiceDownloadStream) error {
-	vlog.VI(2).Infof("Download() was called. suffix=%v", s.suffix)
+	vlog.Infof("Download() was called. suffix=%v", s.suffix)
 	stream.Send([]byte("Hello"))
 	stream.Send([]byte("World"))
 	return nil
 }
 
 func (s *server) DownloadURL(ipc.ServerContext) (string, int64, error) {
-	vlog.VI(2).Infof("DownloadURL() was called. suffix=%v", s.suffix)
+	vlog.Infof("DownloadURL() was called. suffix=%v", s.suffix)
 	return "", 0, nil
 }
 
 func (s *server) Stat(ipc.ServerContext) ([]binary.PartInfo, error) {
-	vlog.VI(2).Infof("Stat() was called. suffix=%v", s.suffix)
-	return []binary.PartInfo{}, nil
+	vlog.Infof("Stat() was called. suffix=%v", s.suffix)
+	h := md5.New()
+	text := "HelloWorld"
+	h.Write([]byte(text))
+	part := binary.PartInfo{Checksum: hex.EncodeToString(h.Sum(nil)), Size: int64(len(text))}
+	return []binary.PartInfo{part}, nil
 }
 
 func (s *server) Upload(_ ipc.ServerContext, _ int32, stream repository.BinaryServiceUploadStream) error {
-	vlog.VI(2).Infof("Upload() was called. suffix=%v", s.suffix)
+	vlog.Infof("Upload() was called. suffix=%v", s.suffix)
 	for {
 		if _, err := stream.Recv(); err != nil {
 			break
diff --git a/tools/playground/compilerd/main.go b/tools/playground/compilerd/main.go
index b565ae3..83f778e 100644
--- a/tools/playground/compilerd/main.go
+++ b/tools/playground/compilerd/main.go
@@ -29,7 +29,7 @@
 
 func healthz(w http.ResponseWriter, r *http.Request) {
 	select {
-	case <- lameduck:
+	case <-lameduck:
 		w.WriteHeader(http.StatusInternalServerError)
 	default:
 		w.Write([]byte("OK"))