wsprd: Pass state changes on the mountstate of a server to the JS
console

This also has a bug fix in the vdl generator where enums were generated
incorrectly.

MultiPart: 1/2
Change-Id: Iee5fe16ae89af312fa3862b2ec1dc521e17035f9
diff --git a/lib/vdl/codegen/javascript/gen.go b/lib/vdl/codegen/javascript/gen.go
index 6403d4d..454ac25 100644
--- a/lib/vdl/codegen/javascript/gen.go
+++ b/lib/vdl/codegen/javascript/gen.go
@@ -336,6 +336,17 @@
 	return false
 }
 
+func hasEnums(pkg *compile.Package) bool {
+	for _, file := range pkg.Files {
+		for _, def := range file.TypeDefs {
+			if def.Type.Kind() == vdl.Enum {
+				return true
+			}
+		}
+	}
+	return false
+}
+
 func generateSystemImports(data data) string {
 	res := "var vdl = require('"
 	packagePrefix := ""
@@ -358,7 +369,7 @@
 		}
 	}
 
-	if hasConsts(data.Pkg) {
+	if hasConsts(data.Pkg) || hasEnums(data.Pkg) {
 		if data.PathToCoreJS != "" {
 			res += "var canonicalize = require('" + packagePrefix + "/vdl/canonicalize');\n"
 		} else {
diff --git a/services/wsprd/app/app.go b/services/wsprd/app/app.go
index b71db40..435f7ad 100644
--- a/services/wsprd/app/app.go
+++ b/services/wsprd/app/app.go
@@ -750,3 +750,14 @@
 	blessings, _ := clientCall.RemoteBlessings()
 	return blessings, nil
 }
+
+func (c *Controller) SendLogMessage(level lib.LogLevel, msg string) error {
+	c.Lock()
+	defer c.Unlock()
+	id := c.lastGeneratedId
+	c.lastGeneratedId += 2
+	return c.writerCreator(id).Send(lib.ResponseLog, lib.LogMessage{
+		Level:   level,
+		Message: msg,
+	})
+}
diff --git a/services/wsprd/app/mock_jsServer_test.go b/services/wsprd/app/mock_jsServer_test.go
index c8ca3f7..55bbd30 100644
--- a/services/wsprd/app/mock_jsServer_test.go
+++ b/services/wsprd/app/mock_jsServer_test.go
@@ -65,6 +65,9 @@
 		}
 		m.receivedResponse = vdl.ValueOf(msg)
 		return nil
+	case lib.ResponseLog:
+		m.flowCount += 2
+		return nil
 	}
 	return fmt.Errorf("Unknown message type: %d", responseType)
 }
diff --git a/services/wsprd/lib/writer.go b/services/wsprd/lib/writer.go
index f6d60a1..a1ccecc 100644
--- a/services/wsprd/lib/writer.go
+++ b/services/wsprd/lib/writer.go
@@ -16,6 +16,7 @@
 	ResponseAuthRequest                   = 6
 	ResponseCancel                        = 7
 	ResponseValidate                      = 8 // Request to validate caveats.
+	ResponseLog                           = 9 // Sends a message to be logged.
 )
 
 type Response struct {
diff --git a/services/wsprd/lib/writer.vdl b/services/wsprd/lib/writer.vdl
index 7197042..ce94e21 100644
--- a/services/wsprd/lib/writer.vdl
+++ b/services/wsprd/lib/writer.vdl
@@ -12,3 +12,13 @@
 	Err           error
 	TraceResponse vtrace.Response
 }
+
+type LogLevel enum {
+   Info
+   Error
+}
+
+type LogMessage struct {
+  Level LogLevel
+  Message string
+}
diff --git a/services/wsprd/lib/writer.vdl.go b/services/wsprd/lib/writer.vdl.go
index 483ec60..e2845a9 100644
--- a/services/wsprd/lib/writer.vdl.go
+++ b/services/wsprd/lib/writer.vdl.go
@@ -9,6 +9,7 @@
 
 import (
 	// VDL system imports
+	"fmt"
 	"v.io/v23/vdl"
 
 	// VDL user imports
@@ -27,6 +28,65 @@
 }) {
 }
 
+type LogLevel int
+
+const (
+	LogLevelInfo LogLevel = iota
+	LogLevelError
+)
+
+// LogLevelAll holds all labels for LogLevel.
+var LogLevelAll = [...]LogLevel{LogLevelInfo, LogLevelError}
+
+// LogLevelFromString creates a LogLevel from a string label.
+func LogLevelFromString(label string) (x LogLevel, err error) {
+	err = x.Set(label)
+	return
+}
+
+// Set assigns label to x.
+func (x *LogLevel) Set(label string) error {
+	switch label {
+	case "Info", "info":
+		*x = LogLevelInfo
+		return nil
+	case "Error", "error":
+		*x = LogLevelError
+		return nil
+	}
+	*x = -1
+	return fmt.Errorf("unknown label %q in lib.LogLevel", label)
+}
+
+// String returns the string label of x.
+func (x LogLevel) String() string {
+	switch x {
+	case LogLevelInfo:
+		return "Info"
+	case LogLevelError:
+		return "Error"
+	}
+	return ""
+}
+
+func (LogLevel) __VDLReflect(struct {
+	Name string "v.io/x/ref/services/wsprd/lib.LogLevel"
+	Enum struct{ Info, Error string }
+}) {
+}
+
+type LogMessage struct {
+	Level   LogLevel
+	Message string
+}
+
+func (LogMessage) __VDLReflect(struct {
+	Name string "v.io/x/ref/services/wsprd/lib.LogMessage"
+}) {
+}
+
 func init() {
 	vdl.Register((*ServerRpcReply)(nil))
+	vdl.Register((*LogLevel)(nil))
+	vdl.Register((*LogMessage)(nil))
 }
diff --git a/services/wsprd/rpc/server/server.go b/services/wsprd/rpc/server/server.go
index c7b8c80..8fe0131 100644
--- a/services/wsprd/rpc/server/server.go
+++ b/services/wsprd/rpc/server/server.go
@@ -63,6 +63,8 @@
 	FlowHandler
 	HandleStore
 
+	SendLogMessage(level lib.LogLevel, msg string) error
+
 	Context() *context.T
 }
 
@@ -110,6 +112,10 @@
 	outstandingAuthRequests map[int32]chan error
 
 	outstandingValidationRequests map[int32]chan []error
+
+	// statusClose will be closed when the server is shutting down, this will
+	// cause the status poller to exit.
+	statusClose chan struct{}
 }
 
 func NewServer(id uint32, listenSpec *rpc.ListenSpec, helper ServerHelper) (*Server, error) {
@@ -506,6 +512,37 @@
 	}
 }
 
+func (s *Server) readStatus() {
+	// A map of names to the last error message sent.
+	lastErrors := map[string]string{}
+	for {
+		status := s.server.Status()
+		for _, mountStatus := range status.Mounts {
+			var errMsg string
+			if mountStatus.LastMountErr != nil {
+				errMsg = mountStatus.LastMountErr.Error()
+			}
+			mountName := mountStatus.Name
+			if lastMessage, ok := lastErrors[mountName]; !ok || errMsg != lastMessage {
+				if errMsg == "" {
+					s.helper.SendLogMessage(
+						lib.LogLevelInfo, "serve: "+mountName+" successfully mounted ")
+				} else {
+					s.helper.SendLogMessage(
+						lib.LogLevelError, "serve: "+mountName+" failed with: "+errMsg)
+				}
+			}
+			lastErrors[mountName] = errMsg
+		}
+		select {
+		case <-time.After(10 * time.Second):
+			continue
+		case <-s.statusClose:
+			return
+		}
+	}
+}
+
 func (s *Server) Serve(name string) error {
 	s.serverStateLock.Lock()
 	defer s.serverStateLock.Unlock()
@@ -524,6 +561,8 @@
 	if err := s.server.ServeDispatcher(name, s.dispatcher); err != nil {
 		return err
 	}
+	s.statusClose = make(chan struct{}, 1)
+	go s.readStatus()
 	return nil
 }
 
@@ -644,6 +683,9 @@
 	}
 	s.serverStateLock.Lock()
 
+	if s.statusClose != nil {
+		close(s.statusClose)
+	}
 	if s.dispatcher != nil {
 		s.dispatcher.Cleanup()
 	}