blob: 48e1e9b4ba7ce1048eec6dadbe81b9ddc8a4ecf1 [file] [log] [blame]
// Package starter provides a single function that starts up servers for a
// mounttable and a device manager that is mounted on it.
package starter
import (
"fmt"
"os"
"path/filepath"
"time"
"v.io/core/veyron/services/mgmt/device/config"
"v.io/core/veyron/services/mgmt/device/impl"
mounttable "v.io/core/veyron/services/mounttable/lib"
"v.io/core/veyron2"
"v.io/core/veyron2/context"
"v.io/core/veyron2/ipc"
"v.io/core/veyron2/naming"
"v.io/core/veyron2/vlog"
)
type NamespaceArgs struct {
Name string // Name to publish the mounttable service under.
ListenSpec ipc.ListenSpec // ListenSpec for the server.
ACLFile string // Path to the ACL file used by the mounttable.
// Name in the local neighborhood on which to make the mounttable
// visible. If empty, the mounttable will not be visible in the local
// neighborhood.
Neighborhood string
}
type DeviceArgs struct {
Name string // Name to publish the device service under.
ListenSpec ipc.ListenSpec // ListenSpec for the device server.
ConfigState *config.State // Configuration for the device.
TestMode bool // Whether the device is running in test mode or not.
RestartCallback func() // Callback invoked when the device service is restarted.
PairingToken string // PairingToken that a claimer needs to provide.
}
func (d *DeviceArgs) name(mt string) string {
if d.Name != "" {
return d.Name
}
return naming.Join(mt, "devmgr")
}
type Args struct {
Namespace NamespaceArgs
Device DeviceArgs
// If true, the global namespace will be made available on the
// mounttable server under "global/".
MountGlobalNamespaceInLocalNamespace bool
}
// Start creates servers for the mounttable and device services and links them together.
//
// Returns the callback to be invoked to shutdown the services on success, or
// an error on failure.
func Start(ctx *context.T, args Args) (func(), error) {
// TODO(caprita): use some mechanism (a file lock or presence of entry
// in mounttable) to ensure only one device manager is running in an
// installation?
mi := &impl.ManagerInfo{
Pid: os.Getpid(),
}
if err := impl.SaveManagerInfo(filepath.Join(args.Device.ConfigState.Root, "device-manager"), mi); err != nil {
return nil, fmt.Errorf("failed to save info: %v", err)
}
// If the device has not yet been claimed, start the mounttable and
// claimable service and wait for it to be claimed.
// Once a device is claimed, close any previously running servers and
// start a new mounttable and device service.
if claimable, claimed := impl.NewClaimableDispatcher(ctx, args.Device.ConfigState, args.Device.PairingToken); claimable != nil {
stopClaimable, err := startClaimableDevice(ctx, claimable, args)
if err != nil {
return nil, err
}
stop := make(chan struct{})
stopped := make(chan struct{})
go waitToBeClaimedAndStartClaimedDevice(ctx, stopClaimable, claimed, stop, stopped, args)
return func() {
close(stop)
<-stopped
}, nil
}
return startClaimedDevice(ctx, args)
}
func startClaimableDevice(ctx *context.T, dispatcher ipc.Dispatcher, args Args) (func(), error) {
mtName, stopMT, err := startMounttable(ctx, args.Namespace)
if err != nil {
return nil, err
}
server, err := veyron2.NewServer(ctx)
if err != nil {
stopMT()
return nil, err
}
shutdown := func() {
server.Stop()
stopMT()
}
endpoints, err := server.Listen(args.Device.ListenSpec)
if err != nil {
shutdown()
return nil, err
}
if err := server.ServeDispatcher(args.Device.name(mtName), dispatcher); err != nil {
shutdown()
return nil, err
}
vlog.Infof("Unclaimed device manager (%v) published as %v", endpoints[0].Name(), args.Device.name(mtName))
return shutdown, nil
}
func waitToBeClaimedAndStartClaimedDevice(ctx *context.T, stopClaimable func(), claimed, stop <-chan struct{}, stopped chan<- struct{}, args Args) {
// Wait for either the claimable service to complete, or be stopped
defer close(stopped)
select {
case <-claimed:
stopClaimable()
case <-stop:
stopClaimable()
return
}
shutdown, err := startClaimedDevice(ctx, args)
if err != nil {
vlog.Errorf("Failed to start device service after it was claimed: %v", err)
veyron2.GetAppCycle(ctx).Stop()
return
}
defer shutdown()
<-stop // Wait to be stopped
}
func startClaimedDevice(ctx *context.T, args Args) (func(), error) {
mtName, stopMT, err := startMounttable(ctx, args.Namespace)
stopDevice, err := startDeviceServer(ctx, args.Device, mtName)
if err != nil {
stopMT()
vlog.Errorf("Failed to start device service: %v", err)
return nil, err
}
if args.MountGlobalNamespaceInLocalNamespace {
mountGlobalNamespaceInLocalNamespace(ctx, mtName)
}
impl.InvokeCallback(ctx, args.Device.ConfigState.Name)
return func() {
stopDevice()
stopMT()
}, nil
}
func startMounttable(ctx *context.T, n NamespaceArgs) (string, func(), error) {
mtName, stopMT, err := mounttable.StartServers(ctx, n.ListenSpec, n.Name, n.Neighborhood, n.ACLFile)
if err != nil {
vlog.Errorf("mounttable.StartServers(%#v) failed: %v", n, err)
} else {
vlog.Infof("Local mounttable (%v) published as %q", mtName, n.Name)
}
return mtName, stopMT, err
}
// startDeviceServer creates an ipc.Server and sets it up to server the Device service.
//
// ls: ListenSpec for the server
// configState: configuration for the Device service dispatcher
// mt: Object address of the mounttable
// dm: Name to publish the device service under
// testMode: whether the service is to be run in test mode
// restarted: callback invoked when the device manager is restarted.
//
// Returns:
// (1) Function to be called to force the service to shutdown
// (2) Any errors in starting the service (in which case, (1) will be nil)
func startDeviceServer(ctx *context.T, args DeviceArgs, mt string) (shutdown func(), err error) {
server, err := veyron2.NewServer(ctx)
if err != nil {
return nil, err
}
shutdown = func() { server.Stop() }
endpoints, err := server.Listen(args.ListenSpec)
if err != nil {
shutdown()
return nil, err
}
args.ConfigState.Name = endpoints[0].Name()
vlog.Infof("Device manager (%v) published as %v", args.ConfigState.Name, args.name(mt))
dispatcher, err := impl.NewDispatcher(ctx, args.ConfigState, mt, args.TestMode, args.RestartCallback)
if err != nil {
shutdown()
return nil, err
}
shutdown = func() {
server.Stop()
impl.Shutdown(dispatcher)
}
if err := server.ServeDispatcher(args.name(mt), dispatcher); err != nil {
shutdown()
return nil, err
}
return shutdown, nil
}
func mountGlobalNamespaceInLocalNamespace(ctx *context.T, localMT string) {
ns := veyron2.GetNamespace(ctx)
for _, root := range ns.Roots() {
go func(r string) {
for {
err := ns.Mount(ctx, naming.Join(localMT, "global"), r, 0 /* forever */, naming.ServesMountTableOpt(true))
if err == nil {
break
}
vlog.Infof("Failed to Mount global namespace: %v", err)
time.Sleep(time.Second)
}
}(root)
}
}