veyron/services/mgmt/device: enable profiles statically
Start using device profiles in device manager, in the absence of a remote
Profile service: the set of 'known' profiles will be hardcoded for now.
Specifically:
- implement getKnownProfiles as a hardcoded list of profiles (for now, just
linux-amd64)
- make use of the device's profile when fetching app envelopes (and verify in
test that profiles are requested correctly during Match)
- export computeDeviceProfile and add command to deviced to dump the (internal
representation of the) profile detected by the device for debugging/inspection
- add command to device tool to invoke remote Describe command on device manager
(which returns the profile labels supported by the device.
Change-Id: I3e24021354edaeb90fb6efb11cee4330f63e8f86
diff --git a/services/mgmt/device/deviced/commands.go b/services/mgmt/device/deviced/commands.go
index 62d3108..1689b77 100644
--- a/services/mgmt/device/deviced/commands.go
+++ b/services/mgmt/device/deviced/commands.go
@@ -130,3 +130,20 @@
}
return nil
}
+
+var cmdProfile = &cmdline.Command{
+ Run: runProfile,
+ Name: "profile",
+ Short: "Dumps profile for the device manager.",
+ Long: "Prints the internal profile description for the device manager.",
+}
+
+func runProfile(cmd *cmdline.Command, _ []string) error {
+ spec, err := impl.ComputeDeviceProfile()
+ if err != nil {
+ vlog.Errorf("ComputeDeviceProfile failed: %v", err)
+ return err
+ }
+ fmt.Fprintf(cmd.Stdout(), "Profile: %#v\n", spec)
+ return nil
+}
diff --git a/services/mgmt/device/deviced/main.go b/services/mgmt/device/deviced/main.go
index 9b21878..710b33f 100644
--- a/services/mgmt/device/deviced/main.go
+++ b/services/mgmt/device/deviced/main.go
@@ -9,7 +9,7 @@
Long: `
deviced can be used to launch, configure, or manage the device manager.
`,
- Children: []*cmdline.Command{cmdInstall, cmdUninstall, cmdStart, cmdStop},
+ Children: []*cmdline.Command{cmdInstall, cmdUninstall, cmdStart, cmdStop, cmdProfile},
Run: runServer,
}
rootCmd.Main()
diff --git a/services/mgmt/device/impl/device_service.go b/services/mgmt/device/impl/device_service.go
index b67a496..e8c9617 100644
--- a/services/mgmt/device/impl/device_service.go
+++ b/services/mgmt/device/impl/device_service.go
@@ -144,31 +144,21 @@
}
func (*deviceService) Describe(ipc.ServerContext) (device.Description, error) {
- empty := device.Description{}
- deviceProfile, err := computeDeviceProfile()
- if err != nil {
- return empty, err
- }
- knownProfiles, err := getKnownProfiles()
- if err != nil {
- return empty, err
- }
- result := matchProfiles(deviceProfile, knownProfiles)
- return result, nil
+ return describe()
}
func (*deviceService) IsRunnable(_ ipc.ServerContext, description binary.Description) (bool, error) {
- deviceProfile, err := computeDeviceProfile()
+ deviceProfile, err := ComputeDeviceProfile()
if err != nil {
return false, err
}
- binaryProfiles := make([]profile.Specification, 0)
+ binaryProfiles := make([]*profile.Specification, 0)
for name, _ := range description.Profiles {
profile, err := getProfile(name)
if err != nil {
return false, err
}
- binaryProfiles = append(binaryProfiles, *profile)
+ binaryProfiles = append(binaryProfiles, profile)
}
result := matchProfiles(deviceProfile, binaryProfiles)
return len(result.Profiles) > 0, nil
diff --git a/services/mgmt/device/impl/mock_repo_test.go b/services/mgmt/device/impl/mock_repo_test.go
index 16fed19..4c2dd10 100644
--- a/services/mgmt/device/impl/mock_repo_test.go
+++ b/services/mgmt/device/impl/mock_repo_test.go
@@ -3,9 +3,11 @@
import (
"crypto/md5"
"encoding/hex"
+ "fmt"
"io"
"io/ioutil"
"os"
+ "reflect"
"testing"
"v.io/core/veyron2/ipc"
@@ -62,8 +64,11 @@
}
// APPLICATION REPOSITORY INTERFACE IMPLEMENTATION
-func (i *arInvoker) Match(ipc.ServerContext, []string) (application.Envelope, error) {
+func (i *arInvoker) Match(_ ipc.ServerContext, profiles []string) (application.Envelope, error) {
vlog.VI(1).Infof("Match()")
+ if want := []string{"test-profile"}; !reflect.DeepEqual(profiles, want) {
+ return application.Envelope{}, fmt.Errorf("Expected profiles %v, got %v", want, profiles)
+ }
return i.envelope, nil
}
diff --git a/services/mgmt/device/impl/only_for_test.go b/services/mgmt/device/impl/only_for_test.go
index 62a69cb..a0fc4a2 100644
--- a/services/mgmt/device/impl/only_for_test.go
+++ b/services/mgmt/device/impl/only_for_test.go
@@ -5,6 +5,7 @@
"os"
"path/filepath"
+ "v.io/core/veyron2/services/mgmt/device"
"v.io/core/veyron2/vlog"
)
@@ -40,6 +41,10 @@
}
}
isSetuid = possiblyMockIsSetuid
+
+ describe = func() (descr device.Description, err error) {
+ return device.Description{Profiles: map[string]struct{}{"test-profile": struct{}{}}}, nil
+ }
}
func possiblyMockIsSetuid(fileStat os.FileInfo) bool {
diff --git a/services/mgmt/device/impl/profile.go b/services/mgmt/device/impl/profile.go
index 275fc3d..4e784af 100644
--- a/services/mgmt/device/impl/profile.go
+++ b/services/mgmt/device/impl/profile.go
@@ -13,13 +13,13 @@
"v.io/core/veyron2/services/mgmt/device"
)
-// computeDeviceProfile generates a description of the runtime
+// ComputeDeviceProfile generates a description of the runtime
// environment (supported file format, OS, architecture, libraries) of
// the host device.
//
// TODO(jsimsa): Avoid computing the host device description from
// scratch if a recent cached copy exists.
-func computeDeviceProfile() (*profile.Specification, error) {
+func ComputeDeviceProfile() (*profile.Specification, error) {
result := profile.Specification{}
// Find out what the supported file format, operating system, and
@@ -52,8 +52,8 @@
switch runtime.GOOS {
case "linux":
// For Linux, we identify what dynamically linked libraries are
- // install by parsing the output of "ldconfig -p".
- command := exec.Command("ldconfig", "-p")
+ // installed by parsing the output of "ldconfig -p".
+ command := exec.Command("/sbin/ldconfig", "-p")
output, err := command.CombinedOutput()
if err != nil {
return nil, err
@@ -101,32 +101,43 @@
// TODO(jsimsa): Avoid retrieving the list of known profiles from a
// remote server if a recent cached copy exists.
func getProfile(name string) (*profile.Specification, error) {
+ profiles, err := getKnownProfiles()
+ if err != nil {
+ return nil, err
+ }
+ for _, p := range profiles {
+ if p.Label == name {
+ return p, nil
+ }
+ }
+ return nil, nil
+
// TODO(jsimsa): This function assumes the existence of a profile
// server from which the profiles can be retrieved. The profile
// server is a work in progress. When it exists, the commented out
// code below should work.
- var profile profile.Specification
/*
- client, err := r.NewClient()
- if err != nil {
- vlog.Errorf("NewClient() failed: %v", err)
- return nil, err
- }
- defer client.Close()
- server := // TODO
- method := "Specification"
- inputs := make([]interface{}, 0)
- call, err := client.StartCall(server + "/" + name, method, inputs)
- if err != nil {
- vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server + "/" + name, method, inputs, err)
- return nil, err
- }
- if err := call.Finish(&profiles); err != nil {
- vlog.Errorf("Finish(%v) failed: %v\n", &profiles, err)
- return nil, err
- }
+ var profile profile.Specification
+ client, err := r.NewClient()
+ if err != nil {
+ vlog.Errorf("NewClient() failed: %v", err)
+ return nil, err
+ }
+ defer client.Close()
+ server := // TODO
+ method := "Specification"
+ inputs := make([]interface{}, 0)
+ call, err := client.StartCall(server + "/" + name, method, inputs)
+ if err != nil {
+ vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server + "/" + name, method, inputs, err)
+ return nil, err
+ }
+ if err := call.Finish(&profiles); err != nil {
+ vlog.Errorf("Finish(%v) failed: %v\n", &profiles, err)
+ return nil, err
+ }
+ return &profile, nil
*/
- return &profile, nil
}
// getKnownProfiles gets a list of description for all publicly known
@@ -134,39 +145,51 @@
//
// TODO(jsimsa): Avoid retrieving the list of known profiles from a
// remote server if a recent cached copy exists.
-func getKnownProfiles() ([]profile.Specification, error) {
+func getKnownProfiles() ([]*profile.Specification, error) {
+ return []*profile.Specification{
+ {
+ Label: "linux-amd64",
+ Description: "",
+ Arch: build.AMD64,
+ OS: build.Linux,
+ Format: build.ELF,
+ },
+ // TODO(caprita): Add profiles for Mac, Pi, etc.
+ }, nil
+
// TODO(jsimsa): This function assumes the existence of a profile
// server from which a list of known profiles can be retrieved. The
// profile server is a work in progress. When it exists, the
// commented out code below should work.
- knownProfiles := make([]profile.Specification, 0)
+
/*
- client, err := r.NewClient()
- if err != nil {
- vlog.Errorf("NewClient() failed: %v\n", err)
- return nil, err
- }
- defer client.Close()
- server := // TODO
- method := "List"
- inputs := make([]interface{}, 0)
- call, err := client.StartCall(server, method, inputs)
- if err != nil {
- vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server, method, inputs, err)
- return nil, err
- }
- if err := call.Finish(&knownProfiles); err != nil {
- vlog.Errorf("Finish(&knownProfile) failed: %v\n", err)
- return nil, err
- }
+ knownProfiles := make([]profile.Specification, 0)
+ client, err := r.NewClient()
+ if err != nil {
+ vlog.Errorf("NewClient() failed: %v\n", err)
+ return nil, err
+ }
+ defer client.Close()
+ server := // TODO
+ method := "List"
+ inputs := make([]interface{}, 0)
+ call, err := client.StartCall(server, method, inputs)
+ if err != nil {
+ vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server, method, inputs, err)
+ return nil, err
+ }
+ if err := call.Finish(&knownProfiles); err != nil {
+ vlog.Errorf("Finish(&knownProfile) failed: %v\n", err)
+ return nil, err
+ }
+ return knownProfiles, nil
*/
- return knownProfiles, nil
}
// matchProfiles inputs a profile that describes the host device and a
// set of publicly known profiles and outputs a device description that
// identifies the publicly known profiles supported by the host device.
-func matchProfiles(p *profile.Specification, known []profile.Specification) device.Description {
+func matchProfiles(p *profile.Specification, known []*profile.Specification) device.Description {
result := device.Description{Profiles: make(map[string]struct{})}
loop:
for _, profile := range known {
@@ -189,3 +212,29 @@
}
return result
}
+
+// describe returns a Description containing the profile that matches the
+// current device. It's declared as a variable so we can override it for
+// testing.
+var describe = func() (device.Description, error) {
+ empty := device.Description{}
+ deviceProfile, err := ComputeDeviceProfile()
+ if err != nil {
+ return empty, err
+ }
+ knownProfiles, err := getKnownProfiles()
+ if err != nil {
+ return empty, err
+ }
+ result := matchProfiles(deviceProfile, knownProfiles)
+ if len(result.Profiles) == 0 {
+ // For now, return "unknown" as the profile, if no known profile
+ // matches the device's profile.
+ //
+ // TODO(caprita): Get rid of this crutch once we have profiles
+ // defined for our supported systems; for now it helps us make
+ // the integration test work on e.g. Mac.
+ result.Profiles["unknown"] = struct{}{}
+ }
+ return result, nil
+}
diff --git a/services/mgmt/device/impl/util.go b/services/mgmt/device/impl/util.go
index 50d892e..09fa70a 100644
--- a/services/mgmt/device/impl/util.go
+++ b/services/mgmt/device/impl/util.go
@@ -41,7 +41,15 @@
stub := repository.ApplicationClient(origin)
// TODO(jsimsa): Include logic that computes the set of supported
// profiles.
- profiles := []string{"test"}
+ profilesSet, err := describe()
+ if err != nil {
+ vlog.Errorf("Failed to obtain profile labels: %v", err)
+ return nil, verror2.Make(ErrOperationFailed, ctx)
+ }
+ var profiles []string
+ for label := range profilesSet.Profiles {
+ profiles = append(profiles, label)
+ }
envelope, err := stub.Match(ctx, profiles)
if err != nil {
vlog.Errorf("Match(%v) failed: %v", profiles, err)
diff --git a/services/mgmt/profile/profile.vdl b/services/mgmt/profile/profile.vdl
index 1e12ec8..63240ec 100644
--- a/services/mgmt/profile/profile.vdl
+++ b/services/mgmt/profile/profile.vdl
@@ -17,17 +17,17 @@
// Specification is how we represent a profile internally. It should
// provide enough information to allow matching of binaries to devices.
type Specification struct {
- // Arch is the target hardware architecture of the profile.
- Arch build.Architecture
+ // Label is a human-friendly concise label for the profile,
+ // e.g. "linux-media".
+ Label string
// Description is a human-friendly description of the profile.
Description string
+ // Arch is the target hardware architecture of the profile.
+ Arch build.Architecture
+ // OS is the target operating system of the profile.
+ OS build.OperatingSystem
// Format is the file format supported by the profile.
Format build.Format
// Libraries is a set of libraries the profile requires.
Libraries set[Library]
- // Label is a human-friendly concise label for the profile,
- // e.g. "linux-media".
- Label string
- // OS is the target operating system of the profile.
- OS build.OperatingSystem
}
diff --git a/services/mgmt/profile/profile.vdl.go b/services/mgmt/profile/profile.vdl.go
index 8b50064..6db310d 100644
--- a/services/mgmt/profile/profile.vdl.go
+++ b/services/mgmt/profile/profile.vdl.go
@@ -30,19 +30,19 @@
// Specification is how we represent a profile internally. It should
// provide enough information to allow matching of binaries to devices.
type Specification struct {
- // Arch is the target hardware architecture of the profile.
- Arch build.Architecture
+ // Label is a human-friendly concise label for the profile,
+ // e.g. "linux-media".
+ Label string
// Description is a human-friendly description of the profile.
Description string
+ // Arch is the target hardware architecture of the profile.
+ Arch build.Architecture
+ // OS is the target operating system of the profile.
+ OS build.OperatingSystem
// Format is the file format supported by the profile.
Format build.Format
// Libraries is a set of libraries the profile requires.
Libraries map[Library]struct{}
- // Label is a human-friendly concise label for the profile,
- // e.g. "linux-media".
- Label string
- // OS is the target operating system of the profile.
- OS build.OperatingSystem
}
func (Specification) __VDLReflect(struct {
diff --git a/services/mgmt/profile/profiled/test.sh b/services/mgmt/profile/profiled/test.sh
index 756916d..685dfee 100755
--- a/services/mgmt/profile/profiled/test.sh
+++ b/services/mgmt/profile/profiled/test.sh
@@ -55,7 +55,7 @@
OUTPUT=$(shell::tmp_file)
"${PROFILE_BIN}" specification "${PROFILE}" | tee "${OUTPUT}" || shell_test::fail "line ${LINENO}: 'spec' failed"
GOT=$(cat "${OUTPUT}")
- WANT='profile.Specification{Arch:"amd64", Description:"Example profile to test the profile manager implementation.", Format:"ELF", Libraries:map[profile.Library]struct {}{profile.Library{Name:"foo", MajorVersion:"1", MinorVersion:"0"}:struct {}{}}, Label:"example", OS:"linux"}'
+ WANT='profile.Specification{Label:"example", Description:"Example profile to test the profile manager implementation.", Arch:"amd64", OS:"linux", Format:"ELF", Libraries:map[profile.Library]struct {}{profile.Library{Name:"foo", MajorVersion:"1", MinorVersion:"0"}:struct {}{}}}'
shell_test::assert_eq "${GOT}" "${WANT}" "${LINENO}"
# Remove the profile.
diff --git a/services/mgmt/repository/repository.vdl.go b/services/mgmt/repository/repository.vdl.go
index 75842f2..36401ea 100644
--- a/services/mgmt/repository/repository.vdl.go
+++ b/services/mgmt/repository/repository.vdl.go
@@ -586,21 +586,21 @@
}
result.TypeDefs = []__vdlutil.Any{
- __wiretype.NamedPrimitiveType{Type: 0x3, Name: "v.io/core/veyron2/services/mgmt/build.Architecture", Tags: []string(nil)}, __wiretype.NamedPrimitiveType{Type: 0x3, Name: "v.io/core/veyron2/services/mgmt/build.Format", Tags: []string(nil)}, __wiretype.StructType{
+ __wiretype.NamedPrimitiveType{Type: 0x3, Name: "v.io/core/veyron2/services/mgmt/build.Architecture", Tags: []string(nil)}, __wiretype.NamedPrimitiveType{Type: 0x3, Name: "v.io/core/veyron2/services/mgmt/build.OperatingSystem", Tags: []string(nil)}, __wiretype.NamedPrimitiveType{Type: 0x3, Name: "v.io/core/veyron2/services/mgmt/build.Format", Tags: []string(nil)}, __wiretype.StructType{
[]__wiretype.FieldType{
__wiretype.FieldType{Type: 0x3, Name: "Name"},
__wiretype.FieldType{Type: 0x3, Name: "MajorVersion"},
__wiretype.FieldType{Type: 0x3, Name: "MinorVersion"},
},
"v.io/core/veyron/services/mgmt/profile.Library", []string(nil)},
- __wiretype.MapType{Key: 0x43, Elem: 0x2, Name: "", Tags: []string(nil)}, __wiretype.NamedPrimitiveType{Type: 0x3, Name: "v.io/core/veyron2/services/mgmt/build.OperatingSystem", Tags: []string(nil)}, __wiretype.StructType{
+ __wiretype.MapType{Key: 0x44, Elem: 0x2, Name: "", Tags: []string(nil)}, __wiretype.StructType{
[]__wiretype.FieldType{
- __wiretype.FieldType{Type: 0x41, Name: "Arch"},
- __wiretype.FieldType{Type: 0x3, Name: "Description"},
- __wiretype.FieldType{Type: 0x42, Name: "Format"},
- __wiretype.FieldType{Type: 0x44, Name: "Libraries"},
__wiretype.FieldType{Type: 0x3, Name: "Label"},
- __wiretype.FieldType{Type: 0x45, Name: "OS"},
+ __wiretype.FieldType{Type: 0x3, Name: "Description"},
+ __wiretype.FieldType{Type: 0x41, Name: "Arch"},
+ __wiretype.FieldType{Type: 0x42, Name: "OS"},
+ __wiretype.FieldType{Type: 0x43, Name: "Format"},
+ __wiretype.FieldType{Type: 0x45, Name: "Libraries"},
},
"v.io/core/veyron/services/mgmt/profile.Specification", []string(nil)},
__wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}}
diff --git a/tools/mgmt/device/impl.go b/tools/mgmt/device/impl.go
index fced994..9a6aaf9 100644
--- a/tools/mgmt/device/impl.go
+++ b/tools/mgmt/device/impl.go
@@ -102,6 +102,29 @@
return nil
}
+var cmdDescribe = &cmdline.Command{
+ Run: runDescribe,
+ Name: "describe",
+ Short: "Describe the device.",
+ Long: "Describe the device.",
+ ArgsName: "<device>",
+ ArgsLong: `
+<device> is the veyron object name of the device manager's app service.`,
+}
+
+func runDescribe(cmd *cmdline.Command, args []string) error {
+ if expected, got := 1, len(args); expected != got {
+ return cmd.UsageErrorf("describe: incorrect number of arguments, expected %d, got %d", expected, got)
+ }
+ deviceName := args[0]
+ if description, err := device.DeviceClient(deviceName).Describe(gctx); err != nil {
+ return fmt.Errorf("Describe failed: %v", err)
+ } else {
+ fmt.Fprintf(cmd.Stdout(), "%+v\n", description)
+ }
+ return nil
+}
+
func root() *cmdline.Command {
return &cmdline.Command{
Name: "device",
@@ -109,6 +132,6 @@
Long: `
The device tool facilitates interaction with the veyron device manager.
`,
- Children: []*cmdline.Command{cmdInstall, cmdStart, associateRoot(), cmdClaim, cmdStop, cmdSuspend, cmdResume, aclRoot()},
+ Children: []*cmdline.Command{cmdInstall, cmdStart, associateRoot(), cmdDescribe, cmdClaim, cmdStop, cmdSuspend, cmdResume, aclRoot()},
}
}
diff --git a/tools/mgmt/test.sh b/tools/mgmt/test.sh
index 432bedf..a0fb910 100755
--- a/tools/mgmt/test.sh
+++ b/tools/mgmt/test.sh
@@ -123,6 +123,9 @@
shell_test::assert_eq "$("${DEBUG_BIN}" stats read "${DM_NAME}/__debug/stats/security/principal/blessingstore" | head -1 | sed -e 's/^.*Default blessings: '//)" \
"alice/myworkstation" "${LINENO}"
+ # Get the device's profile.
+ local -r DEVICE_PROFILE=$("${DEVICE_BIN}" describe "${DM_NAME}/device" | sed -r 's/\{Profiles:map\[(.+):.*/\1/g')
+
# Start a binary server under the blessing "alice/myworkstation/binaryd" so that
# the device ("alice/myworkstation") can talk to it.
local -r BINARYD_NAME="binaryd"
@@ -150,10 +153,10 @@
local -r SAMPLE_APP_NAME="${APPLICATIOND_NAME}/testapp/v0"
local -r APP_PUBLISH_NAME="testbinaryd"
echo "{\"Title\":\"BINARYD\", \"Args\":[\"--name=${APP_PUBLISH_NAME}\", \"--root_dir=./binstore\", \"--veyron.tcp.address=127.0.0.1:0\"], \"Binary\":\"${SAMPLE_APP_BIN_NAME}\", \"Env\":[]}" > ./app.envelope && \
- "${APPLICATION_BIN}" put "${SAMPLE_APP_NAME}" test ./app.envelope && rm ./app.envelope
+ "${APPLICATION_BIN}" put "${SAMPLE_APP_NAME}" "${DEVICE_PROFILE}" ./app.envelope && rm ./app.envelope
# Verify that the envelope we uploaded shows up with glob.
- shell_test::assert_eq "$("${APPLICATION_BIN}" match "${SAMPLE_APP_NAME}" test | grep Title | sed -e 's/^.*"Title": "'// | sed -e 's/",//')" \
+ shell_test::assert_eq "$("${APPLICATION_BIN}" match "${SAMPLE_APP_NAME}" "${DEVICE_PROFILE}" | grep Title | sed -e 's/^.*"Title": "'// | sed -e 's/",//')" \
"BINARYD" "${LINENO}"
# Install the app on the device.