veyron/services/mgmt: Add node mgr test for packages
This change adds a test for packages in the node manager, which requires
running the real binary repository implementation and creating a real
package.
At the same time:
- refactor the root directory initialization for binaryd
- add a library function to create a package from a directory
Change-Id: I055050f44404bdd13d8e69b03e36a7d0aa50c109
diff --git a/services/mgmt/binary/binaryd/main.go b/services/mgmt/binary/binaryd/main.go
index d65984f..677e9e7 100644
--- a/services/mgmt/binary/binaryd/main.go
+++ b/services/mgmt/binary/binaryd/main.go
@@ -2,11 +2,9 @@
import (
"flag"
- "io/ioutil"
"net"
"net/http"
"os"
- "path/filepath"
"veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/rt"
@@ -19,14 +17,11 @@
"veyron.io/veyron/veyron/services/mgmt/binary/impl"
)
-const (
- defaultDepth = 3
- defaultRootPrefix = "veyron_binary_repository"
-)
+const defaultDepth = 3
var (
name = flag.String("name", "", "name to mount the binary repository as")
- root = flag.String("root", "", "root directory for the binary repository")
+ rootFlag = flag.String("root", "", "root directory for the binary repository")
httpAddr = flag.String("http", ":0", "TCP address on which the HTTP server runs")
)
@@ -54,42 +49,16 @@
func main() {
runtime := rt.Init()
defer runtime.Cleanup()
- if *root == "" {
- var err error
- if *root, err = ioutil.TempDir("", defaultRootPrefix); err != nil {
- vlog.Errorf("TempDir() failed: %v\n", err)
- return
- }
- path, perm := filepath.Join(*root, impl.VersionFile), os.FileMode(0600)
- if err := ioutil.WriteFile(path, []byte(impl.Version), perm); err != nil {
- vlog.Errorf("WriteFile(%v, %v, %v) failed: %v", path, impl.Version, perm, err)
- return
- }
- } else {
- _, err := os.Stat(*root)
- switch {
- case err == nil:
- case os.IsNotExist(err):
- perm := os.FileMode(0700)
- if err := os.MkdirAll(*root, perm); err != nil {
- vlog.Errorf("MkdirAll(%v, %v) failed: %v", *root, perm, err)
- return
- }
- path, perm := filepath.Join(*root, impl.VersionFile), os.FileMode(0600)
- if err := ioutil.WriteFile(path, []byte(impl.Version), perm); err != nil {
- vlog.Errorf("WriteFile(%v, %v, %v) failed: %v", path, impl.Version, perm, err)
- return
- }
- default:
- vlog.Errorf("Stat(%v) failed: %v", *root, err)
- return
- }
- }
- vlog.Infof("Binary repository rooted at %v", *root)
-
- state, err := impl.NewState(*root, defaultDepth)
+ root, err := impl.SetupRoot(*rootFlag)
if err != nil {
- vlog.Errorf("NewState(%v, %v) failed: %v", *root, defaultDepth, err)
+ vlog.Errorf("SetupRoot(%q) failed: %v", *rootFlag, err)
+ return
+ }
+ vlog.Infof("Binary repository rooted at %v", root)
+
+ state, err := impl.NewState(root, defaultDepth)
+ if err != nil {
+ vlog.Errorf("NewState(%v, %v) failed: %v", root, defaultDepth, err)
return
}
diff --git a/services/mgmt/binary/impl/setup.go b/services/mgmt/binary/impl/setup.go
new file mode 100644
index 0000000..b0309dd
--- /dev/null
+++ b/services/mgmt/binary/impl/setup.go
@@ -0,0 +1,43 @@
+package impl
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "veyron.io/veyron/veyron2/vlog"
+)
+
+const defaultRootPrefix = "veyron_binary_repository"
+
+// SetupRoot sets up the root directory if it doesn't already exist. If an
+// empty string is used as root, create a new temporary directory.
+func SetupRoot(root string) (string, error) {
+ if root == "" {
+ var err error
+ if root, err = ioutil.TempDir("", defaultRootPrefix); err != nil {
+ vlog.Errorf("TempDir() failed: %v\n", err)
+ return "", err
+ }
+ }
+
+ _, err := os.Stat(root)
+ switch {
+ case err == nil:
+ case os.IsNotExist(err):
+ perm := os.FileMode(0700)
+ if err := os.MkdirAll(root, perm); err != nil {
+ vlog.Errorf("MkdirAll(%v, %v) failed: %v", root, perm, err)
+ return "", err
+ }
+ path, perm := filepath.Join(root, VersionFile), os.FileMode(0600)
+ if err := ioutil.WriteFile(path, []byte(Version), perm); err != nil {
+ vlog.Errorf("WriteFile(%v, %v, %v) failed: %v", path, Version, perm, err)
+ return "", err
+ }
+ default:
+ vlog.Errorf("Stat(%v) failed: %v", root, err)
+ return "", err
+ }
+ return root, nil
+}
diff --git a/services/mgmt/lib/binary/impl.go b/services/mgmt/lib/binary/impl.go
index 07bedd9..b68fe7f 100644
--- a/services/mgmt/lib/binary/impl.go
+++ b/services/mgmt/lib/binary/impl.go
@@ -11,6 +11,7 @@
"io"
"io/ioutil"
"os"
+ "path/filepath"
"time"
"veyron.io/veyron/veyron2/context"
@@ -285,3 +286,16 @@
mediaInfo := packages.MediaInfoForFileName(path)
return upload(ctx, file, mediaInfo, von)
}
+
+func UploadFromDir(ctx context.T, von, sourceDir string) error {
+ dir, err := ioutil.TempDir("", "create-package-")
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(dir)
+ zipfile := filepath.Join(dir, "file.zip")
+ if err := packages.CreateZip(zipfile, sourceDir); err != nil {
+ return err
+ }
+ return UploadFromFile(ctx, von, zipfile)
+}
diff --git a/services/mgmt/lib/packages/packages.go b/services/mgmt/lib/packages/packages.go
index 57f9dde..e55374b 100644
--- a/services/mgmt/lib/packages/packages.go
+++ b/services/mgmt/lib/packages/packages.go
@@ -85,6 +85,53 @@
return nil
}
+// CreateZip creates a package from the files in the source directory. The
+// created package is a Zip file.
+func CreateZip(zipFile, sourceDir string) error {
+ z, err := os.OpenFile(zipFile, os.O_CREATE|os.O_WRONLY, os.FileMode(0644))
+ if err != nil {
+ return err
+ }
+ defer z.Close()
+ w := zip.NewWriter(z)
+ if err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if sourceDir == path {
+ return nil
+ }
+ fh, err := zip.FileInfoHeader(info)
+ if err != nil {
+ return err
+ }
+ fh.Name, _ = filepath.Rel(sourceDir, path)
+ hdr, err := w.CreateHeader(fh)
+ if err != nil {
+ return err
+ }
+ if !info.IsDir() {
+ content, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ if _, err = hdr.Write(content); err != nil {
+ return err
+ }
+ }
+ return nil
+ }); err != nil {
+ return err
+ }
+ if err := w.Close(); err != nil {
+ return err
+ }
+ if err := SaveMediaInfo(zipFile, repository.MediaInfo{Type: "application/zip"}); err != nil {
+ return err
+ }
+ return nil
+}
+
func extractZip(zipFile, installDir string) error {
zr, err := zip.OpenReader(zipFile)
if err != nil {
diff --git a/services/mgmt/lib/packages/packages_test.go b/services/mgmt/lib/packages/packages_test.go
index 8182b44..f18e9bc 100644
--- a/services/mgmt/lib/packages/packages_test.go
+++ b/services/mgmt/lib/packages/packages_test.go
@@ -2,7 +2,6 @@
import (
"archive/tar"
- "archive/zip"
"compress/gzip"
"fmt"
"io"
@@ -107,44 +106,8 @@
}
func makeZip(t *testing.T, zipfile, dir string) {
- z, err := os.OpenFile(zipfile, os.O_CREATE|os.O_WRONLY, os.FileMode(0644))
- if err != nil {
- t.Fatalf("os.OpenFile(%q) failed: %v", zipfile, err)
- }
- defer z.Close()
- w := zip.NewWriter(z)
- filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- t.Fatalf("Walk(%q) error: %v", dir, err)
- }
- if dir == path {
- return nil
- }
- fh, err := zip.FileInfoHeader(info)
- if err != nil {
- t.Fatalf("FileInfoHeader failed: %v", err)
- }
- fh.Name, _ = filepath.Rel(dir, path)
- hdr, err := w.CreateHeader(fh)
- if err != nil {
- t.Fatalf("w.CreateHeader failed: %v", err)
- }
- if !info.IsDir() {
- content, err := ioutil.ReadFile(path)
- if err != nil {
- t.Fatalf("ioutil.ReadFile(%q) failed: %v", path, err)
- }
- if _, err = hdr.Write(content); err != nil {
- t.Fatalf("hdr.Write(%q) failed: %v", content, err)
- }
- }
- return nil
- })
- if err := w.Close(); err != nil {
- t.Fatalf("w.Close() failed: %v", err)
- }
- if err := ioutil.WriteFile(zipfile+".__info", []byte(`{"type":"application/zip"}`), os.FileMode(0644)); err != nil {
- t.Fatalf("ioutil.WriteFile() failed: %v", err)
+ if err := packages.CreateZip(zipfile, dir); err != nil {
+ t.Fatalf("packages.CreateZip failed: %v", err)
}
}
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index 103b2d4..334814b 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -47,6 +47,8 @@
"veyron.io/veyron/veyron/lib/signals"
"veyron.io/veyron/veyron/lib/testutil"
tsecurity "veyron.io/veyron/veyron/lib/testutil/security"
+ binaryimpl "veyron.io/veyron/veyron/services/mgmt/binary/impl"
+ libbinary "veyron.io/veyron/veyron/services/mgmt/lib/binary"
"veyron.io/veyron/veyron/services/mgmt/node/config"
"veyron.io/veyron/veyron/services/mgmt/node/impl"
suidhelper "veyron.io/veyron/veyron/services/mgmt/suidhelper/impl"
@@ -214,6 +216,17 @@
return message, nil
}
+func (appService) Cat(_ ipc.ServerContext, file string) (string, error) {
+ if file == "" || file[0] == filepath.Separator || file[0] == '.' {
+ return "", fmt.Errorf("illegal file name: %q", file)
+ }
+ bytes, err := ioutil.ReadFile(file)
+ if err != nil {
+ return "", err
+ }
+ return string(bytes), nil
+}
+
func ping() {
if call, err := rt.R().Client().StartCall(rt.R().NewContext(), "pingserver", "Ping", []interface{}{os.Getenv(suidhelper.SavedArgs)}); err != nil {
vlog.Fatalf("StartCall failed: %v", err)
@@ -222,6 +235,21 @@
}
}
+func cat(name, file string) (string, error) {
+ runtime := rt.R()
+ ctx, cancel := runtime.NewContext().WithTimeout(time.Minute)
+ defer cancel()
+ call, err := runtime.Client().StartCall(ctx, name, "Cat", []interface{}{file})
+ if err != nil {
+ return "", err
+ }
+ var content string
+ if ferr := call.Finish(&content, &err); ferr != nil {
+ err = ferr
+ }
+ return content, err
+}
+
func app(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
args = args[1:]
if expected, got := 1, len(args); expected != got {
@@ -711,6 +739,42 @@
return nil
}
+func startRealBinaryRepository(t *testing.T) func() {
+ root, err := binaryimpl.SetupRoot("")
+ if err != nil {
+ t.Fatalf("binaryimpl.SetupRoot failed: %v", err)
+ }
+ state, err := binaryimpl.NewState(root, 3)
+ if err != nil {
+ t.Fatalf("binaryimpl.NewState failed: %v", err)
+ }
+ server, _ := newServer()
+ name := "realbin"
+ if err := server.ServeDispatcher(name, binaryimpl.NewDispatcher(state, nil)); err != nil {
+ t.Fatalf("server.ServeDispatcher failed: %v", err)
+ }
+
+ tmpdir, err := ioutil.TempDir("", "test-package-")
+ if err != nil {
+ t.Fatalf("ioutil.TempDir failed: %v", err)
+ }
+ defer os.RemoveAll(tmpdir)
+ if err := ioutil.WriteFile(filepath.Join(tmpdir, "hello.txt"), []byte("Hello World!"), 0600); err != nil {
+ t.Fatalf("ioutil.WriteFile failed: %v", err)
+ }
+ if err := libbinary.UploadFromDir(rt.R().NewContext(), naming.Join(name, "testpkg"), tmpdir); err != nil {
+ t.Fatalf("libbinary.UploadFromDir failed: %v", err)
+ }
+ return func() {
+ if err := server.Stop(); err != nil {
+ t.Fatalf("server.Stop failed: %v", err)
+ }
+ if err := os.RemoveAll(root); err != nil {
+ t.Fatalf("os.RemoveAll(%q) failed: %v", root, err)
+ }
+ }
+}
+
// TestNodeManagerClaim claims a nodemanager and tests ACL permissions on its methods.
func TestNodeManagerClaim(t *testing.T) {
sh, deferFn := createShellAndMountTable(t)
@@ -1089,6 +1153,66 @@
}
}
+func TestNodeManagerPackages(t *testing.T) {
+ sh, deferFn := createShellAndMountTable(t)
+ defer deferFn()
+
+ // Set up mock application and binary repositories.
+ envelope, cleanup := startMockRepos(t)
+ defer cleanup()
+
+ defer startRealBinaryRepository(t)()
+
+ root, cleanup := setupRootDir(t)
+ defer cleanup()
+
+ crDir, crEnv := credentialsForChild("nodemanager")
+ defer os.RemoveAll(crDir)
+
+ // Create a script wrapping the test target that implements suidhelper.
+ helperPath := generateSuidHelperScript(t, root)
+
+ // Set up the node manager. Since we won't do node manager updates,
+ // don't worry about its application envelope and current link.
+ _, nms := runShellCommand(t, sh, crEnv, nodeManagerCmd, "nm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+ pid := readPID(t, nms)
+ defer syscall.Kill(pid, syscall.SIGINT)
+
+ // Create the local server that the app uses to let us know it's ready.
+ pingCh, cleanup := setupPingServer(t)
+ defer cleanup()
+
+ // Create the envelope for the first version of the app.
+ *envelope = envelopeFromShell(sh, nil, appCmd, "google naps", "appV1")
+ (*envelope).Packages = map[string]string{
+ "test": "realbin/testpkg",
+ }
+
+ // Install the app.
+ appID := installApp(t)
+
+ // Start an instance of the app.
+ startApp(t, appID)
+
+ // Wait until the app pings us that it's ready.
+ select {
+ case <-pingCh:
+ case <-time.After(pingTimeout):
+ t.Fatalf("failed to get ping")
+ }
+
+ // Ask the app to cat a file from the package.
+ file := filepath.Join("packages", "test", "hello.txt")
+ name := "appV1"
+ content, err := cat(name, file)
+ if err != nil {
+ t.Errorf("cat(%q, %q) failed: %v", name, file, err)
+ }
+ if expected := "Hello World!"; content != expected {
+ t.Errorf("unexpected content: expected %q, got %q", expected, content)
+ }
+}
+
func listAndVerifyAssociations(t *testing.T, stub node.NodeClientMethods, run veyron2.Runtime, expected []node.Association) {
assocs, err := stub.ListAssociations(run.NewContext())
if err != nil {