veyron/services/mgmt/device: Re-enable testDeviceManager
This change re-enables testDeviceManager so that self-updates will
verify that the new device manager binary will at least run.
When the device manager detects when it was started by another device
manager, it assumes that it is for testing purposes only and the only
allowed request is "device".Stop().
Change-Id: Idf762a6407f638f20d0c33c232a09df76adf0fe0
diff --git a/services/mgmt/device/deviced/server.go b/services/mgmt/device/deviced/server.go
index 33d7919..7362b43 100644
--- a/services/mgmt/device/deviced/server.go
+++ b/services/mgmt/device/deviced/server.go
@@ -6,11 +6,13 @@
"v.io/lib/cmdline"
+ vexec "v.io/core/veyron/lib/exec"
"v.io/core/veyron/lib/signals"
_ "v.io/core/veyron/profiles/roaming"
"v.io/core/veyron/services/mgmt/device/config"
"v.io/core/veyron/services/mgmt/device/impl"
"v.io/core/veyron2"
+ "v.io/core/veyron2/mgmt"
"v.io/core/veyron2/vlog"
)
@@ -25,6 +27,17 @@
ctx, shutdown := veyron2.Init()
defer shutdown()
+ var testMode bool
+ // If this device manager was started by another device manager, it must
+ // be part of a self update to test that this binary works. In that
+ // case, we need to disable a lot of functionality.
+ if handle, err := vexec.GetChildHandle(); err == nil {
+ if _, err := handle.Config.Get(mgmt.ParentNameConfigKey); err == nil {
+ testMode = true
+ vlog.Infof("TEST MODE")
+ }
+ }
+
server, err := veyron2.NewServer(ctx)
if err != nil {
vlog.Errorf("NewServer() failed: %v", err)
@@ -50,16 +63,20 @@
// implementation detail).
var exitErr error
- dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, func() { exitErr = cmdline.ErrExitCode(*restartExitCode) })
+ dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, testMode, func() { exitErr = cmdline.ErrExitCode(*restartExitCode) })
if err != nil {
vlog.Errorf("Failed to create dispatcher: %v", err)
return err
}
- if err := server.ServeDispatcher(*publishAs, dispatcher); err != nil {
- vlog.Errorf("Serve(%v) failed: %v", *publishAs, err)
+ var publishName string
+ if testMode == false {
+ publishName = *publishAs
+ }
+ if err := server.ServeDispatcher(publishName, dispatcher); err != nil {
+ vlog.Errorf("Serve(%v) failed: %v", publishName, err)
return err
}
- vlog.VI(0).Infof("Device manager published as: %v", *publishAs)
+ vlog.VI(0).Infof("Device manager published as: %v", publishName)
impl.InvokeCallback(ctx, name)
// Wait until shutdown. Ignore duplicate signals (sent by agent and
diff --git a/services/mgmt/device/impl/device_service.go b/services/mgmt/device/impl/device_service.go
index 629a283..0ff8ead 100644
--- a/services/mgmt/device/impl/device_service.go
+++ b/services/mgmt/device/impl/device_service.go
@@ -43,15 +43,16 @@
"os/exec"
"path/filepath"
"reflect"
+ "strconv"
"strings"
"sync"
- "time"
"v.io/core/veyron2"
"v.io/core/veyron2/context"
"v.io/core/veyron2/ipc"
"v.io/core/veyron2/mgmt"
"v.io/core/veyron2/naming"
+ "v.io/core/veyron2/security"
"v.io/core/veyron2/services/mgmt/application"
"v.io/core/veyron2/services/mgmt/binary"
"v.io/core/veyron2/services/mgmt/device"
@@ -60,7 +61,9 @@
"v.io/core/veyron2/vlog"
vexec "v.io/core/veyron/lib/exec"
+ "v.io/core/veyron/lib/flags/consts"
"v.io/core/veyron/lib/netstate"
+ vsecurity "v.io/core/veyron/security"
"v.io/core/veyron/services/mgmt/device/config"
"v.io/core/veyron/services/mgmt/profile"
)
@@ -102,6 +105,7 @@
config *config.State
disp *dispatcher
uat BlessingSystemAssociationStore
+ securityAgent *securityAgentState
}
// managerInfo holds state about a running device manager.
@@ -216,6 +220,7 @@
func (s *deviceService) revertDeviceManager(ctx *context.T) error {
if err := updateLink(s.config.Previous, s.config.CurrentLink); err != nil {
+ vlog.Errorf("updateLink failed: %v", err)
return err
}
if s.restartHandler != nil {
@@ -278,6 +283,61 @@
cfg.Set(mgmt.ParentNameConfigKey, listener.name())
cfg.Set(mgmt.ProtocolConfigKey, "tcp")
cfg.Set(mgmt.AddressConfigKey, "127.0.0.1:0")
+
+ var p security.Principal
+ var agentHandle []byte
+ if s.securityAgent != nil {
+ // TODO(rthellend): Cleanup principal
+ handle, conn, err := s.securityAgent.keyMgrAgent.NewPrincipal(ctx, false)
+ if err != nil {
+ vlog.Errorf("NewPrincipal() failed %v", err)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+ agentHandle = handle
+ var cancel func()
+ if p, cancel, err = agentPrincipal(ctx, conn); err != nil {
+ vlog.Errorf("agentPrincipal failed: %v", err)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+ defer cancel()
+
+ } else {
+ credentialsDir := filepath.Join(workspace, "credentials")
+ var err error
+ if p, err = vsecurity.CreatePersistentPrincipal(credentialsDir, nil); err != nil {
+ vlog.Errorf("CreatePersistentPrincipal(%v, nil) failed: %v", credentialsDir, err)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+ cmd.Env = append(cmd.Env, consts.VeyronCredentials+"="+credentialsDir)
+ }
+ dmPrincipal := veyron2.GetPrincipal(ctx)
+ dmBlessings, err := dmPrincipal.Bless(p.PublicKey(), dmPrincipal.BlessingStore().Default(), "testdm", security.UnconstrainedUse())
+ if err := p.BlessingStore().SetDefault(dmBlessings); err != nil {
+ vlog.Errorf("BlessingStore.SetDefault() failed: %v", err)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+ if _, err := p.BlessingStore().Set(dmBlessings, security.AllPrincipals); err != nil {
+ vlog.Errorf("BlessingStore.Set() failed: %v", err)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+ if err := p.AddToRoots(dmBlessings); err != nil {
+ vlog.Errorf("AddToRoots() failed: %v", err)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+
+ if s.securityAgent != nil {
+ file, err := s.securityAgent.keyMgrAgent.NewConnection(agentHandle)
+ if err != nil {
+ vlog.Errorf("NewConnection(%v) failed: %v", agentHandle, err)
+ return err
+ }
+ defer file.Close()
+
+ fd := len(cmd.ExtraFiles) + vexec.FileOffset
+ cmd.ExtraFiles = append(cmd.ExtraFiles, file)
+ cfg.Set(mgmt.SecurityAgentFDConfigKey, strconv.Itoa(fd))
+ }
+
handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg})
// Start the child process.
if err := handle.Start(); err != nil {
@@ -296,35 +356,14 @@
}
childName, err := listener.waitForValue(childReadyTimeout)
if err != nil {
+ vlog.Errorf("waitForValue(%v) failed: %v", childReadyTimeout, err)
return verror2.Make(ErrOperationFailed, ctx)
}
- // Check that invoking Revert() succeeds.
+ // Check that invoking Stop() succeeds.
childName = naming.Join(childName, "device")
dmClient := device.DeviceClient(childName)
- linkOld, pathOld, err := s.getCurrentFileInfo()
- if err != nil {
- return verror2.Make(ErrOperationFailed, ctx)
- }
- // Since the resolution of mtime for files is seconds, the test sleeps
- // for a second to make sure it can check whether the current symlink is
- // updated.
- time.Sleep(time.Second)
- if err := dmClient.Revert(ctx); err != nil {
- return verror2.Make(ErrOperationFailed, ctx)
- }
- linkNew, pathNew, err := s.getCurrentFileInfo()
- if err != nil {
- return verror2.Make(ErrOperationFailed, ctx)
- }
- // Check that the new device manager updated the current symbolic link.
- if !linkOld.ModTime().Before(linkNew.ModTime()) {
- vlog.Errorf("New device manager test failed")
- return verror2.Make(ErrOperationFailed, ctx)
- }
- // Ensure that the current symbolic link points to the same script.
- if pathNew != pathOld {
- updateLink(pathOld, s.config.CurrentLink)
- vlog.Errorf("New device manager test failed")
+ if err := dmClient.Stop(ctx, 0); err != nil {
+ vlog.Errorf("Stop() failed: %v", err)
return verror2.Make(ErrOperationFailed, ctx)
}
if err := handle.Wait(childWaitTimeout); err != nil {
@@ -432,11 +471,10 @@
return err
}
- // TODO(rthellend): testDeviceManager always fails due to https://github.com/veyron/release-issues/issues/714
- // Uncomment when the bug is fixed.
- //if err := s.testDeviceManager(ctx, workspace, envelope); err != nil {
- // return err
- //}
+ if err := s.testDeviceManager(ctx, workspace, envelope); err != nil {
+ vlog.Errorf("testDeviceManager failed: %v", err)
+ return err
+ }
if err := updateLink(filepath.Join(workspace, "deviced.sh"), s.config.CurrentLink); err != nil {
return err
@@ -470,10 +508,12 @@
func (s *deviceService) Revert(call ipc.ServerContext) error {
if s.config.Previous == "" {
+ vlog.Errorf("Revert failed: no previous version")
return verror2.Make(ErrUpdateNoOp, call.Context())
}
updatingState := s.updating
if updatingState.testAndSetUpdating() {
+ vlog.Errorf("Revert failed: already in progress")
return verror2.Make(ErrOperationInProgress, call.Context())
}
err := s.revertDeviceManager(call.Context())
diff --git a/services/mgmt/device/impl/dispatcher.go b/services/mgmt/device/impl/dispatcher.go
index 5632858..7dc317e 100644
--- a/services/mgmt/device/impl/dispatcher.go
+++ b/services/mgmt/device/impl/dispatcher.go
@@ -34,6 +34,7 @@
updating *updatingState
securityAgent *securityAgentState
restartHandler func()
+ testMode bool
}
// dispatcher holds the state of the device manager dispatcher.
@@ -72,7 +73,7 @@
)
// NewDispatcher is the device manager dispatcher factory.
-func NewDispatcher(principal security.Principal, config *config.State, restartHandler func()) (*dispatcher, error) {
+func NewDispatcher(principal security.Principal, config *config.State, testMode bool, restartHandler func()) (ipc.Dispatcher, error) {
if err := config.Validate(); err != nil {
return nil, fmt.Errorf("invalid config %v: %v", config, err)
}
@@ -95,6 +96,7 @@
callback: newCallbackState(config.Name),
updating: newUpdatingState(),
restartHandler: restartHandler,
+ testMode: testMode,
},
config: config,
uat: uat,
@@ -121,6 +123,9 @@
}
}
}
+ if testMode {
+ return &testModeDispatcher{d}, nil
+ }
return d, nil
}
@@ -166,8 +171,17 @@
// TODO(rjkroege): Consider refactoring authorizer implementations to
// be shareable with other components.
-func newAuthorizer(principal security.Principal, dir string, locks *acls.Locks) (security.Authorizer, error) {
- rootTam, _, err := locks.GetPathACL(principal, dir)
+func (d *dispatcher) newAuthorizer() (security.Authorizer, error) {
+ if d.internal.testMode {
+ // In test mode, the device manager will not be able to read
+ // the ACLs, because they were signed with the key of the real
+ // device manager. It's not a problem because the
+ // testModeDispatcher overrides the authorizer anyway.
+ return nil, nil
+ }
+
+ dir := d.getACLDir()
+ rootTam, _, err := d.locks.GetPathACL(d.principal, dir)
if err != nil && os.IsNotExist(err) {
vlog.VI(1).Infof("GetPathACL(%s) failed: %v", dir, err)
@@ -194,7 +208,8 @@
i--
}
}
- auth, err := newAuthorizer(d.principal, d.getACLDir(), d.locks)
+
+ auth, err := d.newAuthorizer()
if err != nil {
return nil, nil, err
}
@@ -214,6 +229,7 @@
config: d.config,
disp: d,
uat: d.uat,
+ securityAgent: d.internal.securityAgent,
})
return receiver, auth, nil
case appsSuffix:
@@ -252,9 +268,6 @@
return invoker, auth, nil
}
}
- if err != nil {
- return nil, nil, err
- }
receiver := device.ApplicationServer(&appService{
callback: d.internal.callback,
config: d.config,
@@ -289,6 +302,27 @@
}
}
+// testModeDispatcher is a wrapper around the real dispatcher. It returns the
+// exact same object as the real dispatcher, but the authorizer only allows
+// calls to "device".Stop().
+type testModeDispatcher struct {
+ realDispatcher ipc.Dispatcher
+}
+
+func (d *testModeDispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) {
+ obj, _, err := d.realDispatcher.Lookup(suffix)
+ return obj, d, err
+}
+
+func (testModeDispatcher) Authorize(ctx security.Context) error {
+ if ctx.Suffix() == deviceSuffix && ctx.Method() == "Stop" {
+ vlog.Infof("testModeDispatcher.Authorize: Allow %q.%s()", ctx.Suffix(), ctx.Method())
+ return nil
+ }
+ vlog.Infof("testModeDispatcher.Authorize: Reject %q.%s()", ctx.Suffix(), ctx.Method())
+ return verror.Make(ErrInvalidSuffix, nil)
+}
+
func newAppSpecificAuthorizer(sec security.Authorizer, config *config.State, suffix []string) (security.Authorizer, error) {
// TODO(rjkroege): This does not support <appname>.Start() to start all
// instances. Correct this.
diff --git a/services/mgmt/device/impl/impl_test.go b/services/mgmt/device/impl/impl_test.go
index ac9ec54..74e7291 100644
--- a/services/mgmt/device/impl/impl_test.go
+++ b/services/mgmt/device/impl/impl_test.go
@@ -162,11 +162,13 @@
// script prepared by a previous version of the device manager.
if len(args) > 0 {
if want, got := 4, len(args); want != got {
- vlog.Fatalf("expected %d additional arguments, got %d instead", want, got)
+ vlog.Fatalf("expected %d additional arguments, got %d instead: %q", want, got, args)
}
configState.Root, configState.Helper, configState.Origin, configState.CurrentLink = args[0], args[1], args[2], args[3]
}
- dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, func() { fmt.Println("restart handler") })
+ blessings := fmt.Sprint(veyron2.GetPrincipal(ctx).BlessingStore().Default())
+ testMode := strings.HasSuffix(blessings, "/testdm")
+ dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, testMode, func() { fmt.Println("restart handler") })
if err != nil {
vlog.Fatalf("Failed to create device manager dispatcher: %v", err)
}
@@ -182,7 +184,7 @@
if val, present := env["PAUSE_BEFORE_STOP"]; present && val == "1" {
modules.WaitForEOF(stdin)
}
- if dispatcher.Leaking() {
+ if impl.DispatcherLeaking(dispatcher) {
vlog.Fatalf("device manager leaking resources")
}
return nil
@@ -365,9 +367,7 @@
// Set up a second version of the device manager. The information in the
// envelope will be used by the device manager to stage the next
// version.
- crDir, crEnv := mgmttest.CredentialsForChild(ctx, "child")
- defer os.RemoveAll(crDir)
- *envelope = envelopeFromShell(sh, crEnv, deviceManagerCmd, application.DeviceManagerTitle, "v2DM")
+ *envelope = envelopeFromShell(sh, nil, deviceManagerCmd, application.DeviceManagerTitle, "v2DM")
updateDevice(t, ctx, "factoryDM")
// Current link should have been updated to point to v2.
@@ -409,9 +409,7 @@
}
// Create a third version of the device manager and issue an update.
- crDir, crEnv = mgmttest.CredentialsForChild(ctx, "child")
- defer os.RemoveAll(crDir)
- *envelope = envelopeFromShell(sh, crEnv, deviceManagerCmd, application.DeviceManagerTitle, "v3DM")
+ *envelope = envelopeFromShell(sh, nil, deviceManagerCmd, application.DeviceManagerTitle, "v3DM")
updateDevice(t, ctx, "v2DM")
scriptPathV3 := evalLink()
diff --git a/services/mgmt/device/impl/only_for_test.go b/services/mgmt/device/impl/only_for_test.go
index 082fc25..6f92afc 100644
--- a/services/mgmt/device/impl/only_for_test.go
+++ b/services/mgmt/device/impl/only_for_test.go
@@ -2,9 +2,11 @@
import (
"flag"
+ "fmt"
"os"
"path/filepath"
+ "v.io/core/veyron2/ipc"
"v.io/core/veyron2/services/mgmt/device"
"v.io/core/veyron2/vlog"
)
@@ -20,8 +22,15 @@
return len(c.channels) > 0
}
-func (d *dispatcher) Leaking() bool {
- return d.internal.callback.leaking()
+func DispatcherLeaking(d ipc.Dispatcher) bool {
+ switch obj := d.(type) {
+ case *dispatcher:
+ return obj.internal.callback.leaking()
+ case *testModeDispatcher:
+ return obj.realDispatcher.(*dispatcher).internal.callback.leaking()
+ default:
+ panic(fmt.Sprintf("unexpected type: %T", d))
+ }
}
func init() {
diff --git a/services/mgmt/device/impl/util.go b/services/mgmt/device/impl/util.go
index 596ef71..54d90d2 100644
--- a/services/mgmt/device/impl/util.go
+++ b/services/mgmt/device/impl/util.go
@@ -29,7 +29,7 @@
vlog.Errorf("Download(%v) failed: %v", name, err)
return verror2.Make(ErrOperationFailed, nil)
}
- path, perm := filepath.Join(workspace, fileName), os.FileMode(755)
+ path, perm := filepath.Join(workspace, fileName), os.FileMode(0755)
if err := ioutil.WriteFile(path, data, perm); err != nil {
vlog.Errorf("WriteFile(%v, %v) failed: %v", path, perm, err)
return verror2.Make(ErrOperationFailed, nil)
diff --git a/tools/mgmt/test.sh b/tools/mgmt/test.sh
index 7a28463..7cf1f9e 100755
--- a/tools/mgmt/test.sh
+++ b/tools/mgmt/test.sh
@@ -116,14 +116,15 @@
local -r APPLICATIOND_NAME="applicationd"
local -r DEVICED_APP_NAME="${APPLICATIOND_NAME}/deviced/test"
- BIN_STAGING_DIR=$(shell::tmp_dir)
+ BIN_STAGING_DIR="${WORKDIR}/bin"
+ mkdir -p "${BIN_STAGING_DIR}"
cp "${AGENTD_BIN}" "${SUIDHELPER_BIN}" "${INITHELPER_BIN}" "${DEVICEMANAGER_BIN}" "${BIN_STAGING_DIR}"
shell_test::setup_server_test
# Install and start device manager.
- DM_INSTALL_DIR=$(shell::tmp_dir)
+ DM_INSTALL_DIR="${WORKDIR}/dm"
- export VANADIUM_DEVICE_DIR="${DM_INSTALL_DIR}/dm"
+ export VANADIUM_DEVICE_DIR="${DM_INSTALL_DIR}"
if [[ "${WITH_SUID}" == "--with_suid" ]]; then
"${DEVICE_SCRIPT}" install "${BIN_STAGING_DIR}" --origin="${DEVICED_APP_NAME}" -- --veyron.tcp.address=127.0.0.1:0
@@ -170,7 +171,7 @@
# the device ("alice/myworkstation") can talk to it.
local -r BINARYD_NAME="binaryd"
shell_test::start_server "${VRUN}" --name=myworkstation/binaryd "${BINARYD_BIN}" --name="${BINARYD_NAME}" \
- --root_dir="$(shell::tmp_dir)/binstore" --veyron.tcp.address=127.0.0.1:0 --http=127.0.0.1:0 \
+ --root_dir="${WORKDIR}/binstore" --veyron.tcp.address=127.0.0.1:0 --http=127.0.0.1:0 \
|| shell_test::fail "line ${LINENO} failed to start binaryd"
# Upload a binary to the binary server. The binary we upload is binaryd
@@ -185,8 +186,9 @@
# Start an application server under the blessing "alice/myworkstation/applicationd" so that
# the device ("alice/myworkstation") can talk to it.
+ mkdir -p "${WORKDIR}/appstore"
shell_test::start_server "${VRUN}" --name=myworkstation/applicationd "${APPLICATIOND_BIN}" --name="${APPLICATIOND_NAME}" \
- --store="$(shell::tmp_dir)" --veyron.tcp.address=127.0.0.1:0 \
+ --store="${WORKDIR}/appstore" --veyron.tcp.address=127.0.0.1:0 \
|| shell_test::fail "line ${LINENO} failed to start applicationd"
# Upload an envelope for our test app.