Merge "{veyron/veyron2}/services/mgmt/build: a simple Go build server"
diff --git a/services/mgmt/binary/binaryd/main.go b/services/mgmt/binary/binaryd/main.go
index 1ea4203..a676c94 100644
--- a/services/mgmt/binary/binaryd/main.go
+++ b/services/mgmt/binary/binaryd/main.go
@@ -80,7 +80,7 @@
return
}
if err := server.Serve(name, dispatcher); err != nil {
- vlog.Errorf("Server(%v) failed: %v", name, err)
+ vlog.Errorf("Serve(%v) failed: %v", name, err)
return
}
vlog.Infof("Binary repository published at %v/%v", endpoint, name)
diff --git a/services/mgmt/build/buildd/main.go b/services/mgmt/build/buildd/main.go
new file mode 100644
index 0000000..1904b2b
--- /dev/null
+++ b/services/mgmt/build/buildd/main.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+ "flag"
+
+ "veyron/lib/signals"
+ vflag "veyron/security/flag"
+ "veyron/services/mgmt/build/impl"
+
+ "veyron2/ipc"
+ "veyron2/rt"
+ "veyron2/services/mgmt/build"
+ "veyron2/vlog"
+)
+
+func main() {
+ var address, gobin, name, protocol string
+ // TODO(rthellend): Remove the address and protocol flags when the config manager is working.
+ flag.StringVar(&address, "address", "localhost:0", "network address to listen on")
+ flag.StringVar(&gobin, "gobin", "go", "path to the Go compiler")
+ flag.StringVar(&name, "name", "", "name to mount the build server as")
+ flag.StringVar(&protocol, "protocol", "tcp", "network type to listen on")
+ flag.Parse()
+ runtime := rt.Init()
+ defer runtime.Cleanup()
+ server, err := runtime.NewServer()
+ if err != nil {
+ vlog.Errorf("NewServer() failed: %v", err)
+ return
+ }
+ defer server.Stop()
+ endpoint, err := server.Listen(protocol, address)
+ if err != nil {
+ vlog.Errorf("Listen(%v, %v) failed: %v", protocol, address, err)
+ return
+ }
+ if err := server.Serve(name, ipc.SoloDispatcher(build.NewServerBuild(impl.NewInvoker(gobin)), vflag.NewAuthorizerOrDie())); err != nil {
+ vlog.Errorf("Serve(%v) failed: %v", name, err)
+ return
+ }
+ vlog.Infof("Build server endpoint=%q name=%q", endpoint, name)
+
+ // Wait until shutdown.
+ <-signals.ShutdownOnSignals()
+}
diff --git a/services/mgmt/build/impl/impl_test.go b/services/mgmt/build/impl/impl_test.go
new file mode 100644
index 0000000..31b2d71
--- /dev/null
+++ b/services/mgmt/build/impl/impl_test.go
@@ -0,0 +1,124 @@
+package impl
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ _ "veyron/lib/testutil"
+
+ "veyron2/ipc"
+ "veyron2/rt"
+ "veyron2/services/mgmt/build"
+)
+
+func init() {
+ rt.Init()
+}
+
+// startServer starts the build server.
+func startServer(t *testing.T) (build.Build, func()) {
+ root := os.Getenv("VEYRON_ROOT")
+ if root == "" {
+ t.Fatalf("VEYRON_ROOT is not set")
+ }
+ gobin := filepath.Join(root, "environment", "go", "bin", "go")
+ server, err := rt.R().NewServer()
+ if err != nil {
+ t.Fatalf("NewServer() failed: %v", 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)
+ }
+ unpublished := ""
+ if err := server.Serve(unpublished, ipc.SoloDispatcher(build.NewServerBuild(NewInvoker(gobin)), nil)); err != nil {
+ t.Fatalf("Serve(%q) failed: %v", unpublished, err)
+ }
+ name := "/" + endpoint.String()
+ client, err := build.BindBuild(name)
+ if err != nil {
+ t.Fatalf("BindBuild(%v) failed: %v", name, err)
+ }
+ return client, func() {
+ if err := server.Stop(); err != nil {
+ t.Fatalf("Stop() failed: %v", err)
+ }
+ }
+}
+
+func invokeBuild(t *testing.T, client build.Build, files []build.File) ([]byte, error) {
+ stream, err := client.Build(rt.R().NewContext())
+ if err != nil {
+ t.Errorf("Build() failed: %v", err)
+ return nil, err
+ }
+ for _, file := range files {
+ if err := stream.Send(file); err != nil {
+ t.Logf("Send() failed: %v", err)
+ stream.Cancel()
+ return nil, err
+ }
+ }
+ if err := stream.CloseSend(); err != nil {
+ t.Logf("CloseSend() failed: %v", err)
+ stream.Cancel()
+ return nil, err
+ }
+ output, err := stream.Finish()
+ if err != nil {
+ t.Logf("Finish() failed: %v", err)
+ stream.Cancel()
+ return nil, err
+ }
+ return output, nil
+}
+
+const mainSrc = `package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("Hello World!")
+}
+`
+
+// TestSuccess checks that the build server successfully builds a
+// package that depends on the standard Go library.
+func TestSuccess(t *testing.T) {
+ client, cleanup := startServer(t)
+ defer cleanup()
+
+ files := []build.File{
+ build.File{
+ Name: "test/main.go",
+ Contents: []byte(mainSrc),
+ },
+ }
+ output, err := invokeBuild(t, client, files)
+ if err != nil {
+ t.FailNow()
+ }
+ if got, expected := strings.TrimSpace(string(output)), "test"; got != expected {
+ t.Fatalf("Unexpected output: got %v, expected %v", got, expected)
+ }
+}
+
+// TestFailure checks that the build server fails to build a package
+// consisting of an empty file.
+func TestFailure(t *testing.T) {
+ client, cleanup := startServer(t)
+ defer cleanup()
+
+ files := []build.File{
+ build.File{
+ Name: "test/main.go",
+ Contents: []byte(""),
+ },
+ }
+ if _, err := invokeBuild(t, client, files); err == nil {
+ t.FailNow()
+ }
+}
diff --git a/services/mgmt/build/impl/invoker.go b/services/mgmt/build/impl/invoker.go
new file mode 100644
index 0000000..b2b9c52
--- /dev/null
+++ b/services/mgmt/build/impl/invoker.go
@@ -0,0 +1,83 @@
+package impl
+
+import (
+ "errors"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "veyron2/ipc"
+ "veyron2/services/mgmt/binary"
+ "veyron2/services/mgmt/build"
+ "veyron2/vlog"
+)
+
+var (
+ errOperationFailed = errors.New("operation failed")
+)
+
+// invoker holds the state of a build server invocation.
+type invoker struct {
+ // gobin is the path to the Go compiler binary.
+ gobin string
+}
+
+// NewInvoker is the invoker factory.
+func NewInvoker(gobin string) *invoker {
+ return &invoker{
+ gobin: gobin,
+ }
+}
+
+// BUILD INTERFACE IMPLEMENTATION
+
+func (i *invoker) Build(_ ipc.ServerContext, stream build.BuildServiceBuildStream) ([]byte, error) {
+ dir, prefix := "", ""
+ dirPerm, filePerm := os.FileMode(0700), os.FileMode(0600)
+ root, err := ioutil.TempDir(dir, prefix)
+ if err != nil {
+ vlog.Errorf("TempDir(%v, %v) failed: %v", dir, prefix, err)
+ return nil, errOperationFailed
+ }
+ defer os.RemoveAll(root)
+ srcDir := filepath.Join(root, "go", "src")
+ if err := os.MkdirAll(srcDir, dirPerm); err != nil {
+ vlog.Errorf("MkdirAll(%v, %v) failed: %v", srcDir, dirPerm, err)
+ return nil, errOperationFailed
+ }
+ for {
+ srcFile, err := stream.Recv()
+ if err != nil && err != io.EOF {
+ vlog.Errorf("Recv() failed: %v", err)
+ return nil, errOperationFailed
+ }
+ if err == io.EOF {
+ break
+ }
+ filePath := filepath.Join(srcDir, filepath.FromSlash(srcFile.Name))
+ dir := filepath.Dir(filePath)
+ if err := os.MkdirAll(dir, dirPerm); err != nil {
+ vlog.Errorf("MkdirAll(%v, %v) failed: %v", dir, dirPerm, err)
+ return nil, errOperationFailed
+ }
+ if err := ioutil.WriteFile(filePath, srcFile.Contents, filePerm); err != nil {
+ vlog.Errorf("WriteFile(%v, %v) failed: %v", filePath, filePerm, err)
+ return nil, errOperationFailed
+ }
+ }
+ cmd := exec.Command(i.gobin, "build", "-v", "...")
+ cmd.Env = append(cmd.Env, "GOPATH="+filepath.Dir(srcDir))
+ bytes, err := cmd.CombinedOutput()
+ if err != nil {
+ vlog.Errorf("CombinedOutput() failed: %v", err)
+ return nil, errOperationFailed
+ }
+ return bytes, nil
+}
+
+func (i *invoker) Describe(_ ipc.ServerContext, name string) (binary.Description, error) {
+ // TODO(jsimsa): Implement.
+ return binary.Description{}, nil
+}
diff --git a/services/mgmt/node/impl/invoker.go b/services/mgmt/node/impl/invoker.go
index a0badb6..565d6d8 100644
--- a/services/mgmt/node/impl/invoker.go
+++ b/services/mgmt/node/impl/invoker.go
@@ -41,8 +41,8 @@
"time"
"veyron/lib/config"
- ibuild "veyron/services/mgmt/build"
- "veyron/services/mgmt/lib/binary"
+ "veyron/services/mgmt/build"
+ cbinary "veyron/services/mgmt/lib/binary"
vexec "veyron/services/mgmt/lib/exec"
"veyron/services/mgmt/profile"
@@ -51,7 +51,7 @@
"veyron2/naming"
"veyron2/rt"
"veyron2/services/mgmt/application"
- "veyron2/services/mgmt/build"
+ "veyron2/services/mgmt/binary"
"veyron2/services/mgmt/node"
"veyron2/services/mgmt/repository"
"veyron2/verror"
@@ -125,24 +125,24 @@
// architecture is.
switch runtime.GOOS {
case "linux":
- result.Format.Name = ibuild.ELF.String()
- result.Format.Attributes["os"] = ibuild.LINUX.String()
+ result.Format.Name = build.ELF.String()
+ result.Format.Attributes["os"] = build.LINUX.String()
case "darwin":
- result.Format.Name = ibuild.MACH.String()
- result.Format.Attributes["os"] = ibuild.DARWIN.String()
+ result.Format.Name = build.MACH.String()
+ result.Format.Attributes["os"] = build.DARWIN.String()
case "windows":
- result.Format.Name = ibuild.PE.String()
- result.Format.Attributes["os"] = ibuild.WINDOWS.String()
+ result.Format.Name = build.PE.String()
+ result.Format.Attributes["os"] = build.WINDOWS.String()
default:
return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
}
switch runtime.GOARCH {
case "amd64":
- result.Format.Attributes["arch"] = ibuild.AMD64.String()
+ result.Format.Attributes["arch"] = build.AMD64.String()
case "arm":
- result.Format.Attributes["arch"] = ibuild.AMD64.String()
+ result.Format.Attributes["arch"] = build.AMD64.String()
case "x86":
- result.Format.Attributes["arch"] = ibuild.AMD64.String()
+ result.Format.Attributes["arch"] = build.AMD64.String()
default:
return nil, errors.New("Unsupported hardware architecture: " + runtime.GOARCH)
}
@@ -304,14 +304,14 @@
return result, nil
}
-func (i *invoker) IsRunnable(call ipc.ServerContext, binary build.BinaryDescription) (bool, error) {
- vlog.VI(0).Infof("%v.IsRunnable(%v)", i.suffix, binary)
+func (i *invoker) IsRunnable(call ipc.ServerContext, description binary.Description) (bool, error) {
+ vlog.VI(0).Infof("%v.IsRunnable(%v)", i.suffix, description)
nodeProfile, err := i.computeNodeProfile()
if err != nil {
return false, err
}
binaryProfiles := make([]profile.Specification, 0)
- for name, _ := range binary.Profiles {
+ for name, _ := range description.Profiles {
profile, err := i.getProfile(name)
if err != nil {
return false, err
@@ -331,7 +331,7 @@
// APPLICATION INTERFACE IMPLEMENTATION
func downloadBinary(workspace, name string) error {
- data, err := binary.Download(name)
+ data, err := cbinary.Download(name)
if err != nil {
vlog.Errorf("Download(%v) failed: %v", name, err)
return errOperationFailed
diff --git a/tools/vrpc/impl/impl.go b/tools/vrpc/impl/impl.go
index 27ce11e..aca51a3 100644
--- a/tools/vrpc/impl/impl.go
+++ b/tools/vrpc/impl/impl.go
@@ -16,7 +16,7 @@
"veyron2/vom"
"veyron2/wiretype"
- idl_build "veyron2/services/mgmt/build"
+ idl_binary "veyron2/services/mgmt/binary"
idl_node "veyron2/services/mgmt/node"
idl_mounttable "veyron2/services/mounttable"
)
@@ -126,7 +126,7 @@
vom.Register(x1)
var x2 idl_node.Description
vom.Register(x2)
- var x3 idl_build.BinaryDescription
+ var x3 idl_binary.Description
vom.Register(x3)
var x4 idl_mounttable.MountedServer
vom.Register(x4)