services/device/interna/impl: tidy installations
This change continues adding a tidying a feature to the device manager
where it will delete Uninstalled installations not referenced by an
existing instance.
Change-Id: I4404d5758cbe814eda6b0ba4de3734fe120697c1
diff --git a/services/device/internal/impl/applife/app_life_test.go b/services/device/internal/impl/applife/app_life_test.go
index 60b1e78..cfd0002 100644
--- a/services/device/internal/impl/applife/app_life_test.go
+++ b/services/device/internal/impl/applife/app_life_test.go
@@ -358,12 +358,13 @@
t.Fatalf("Pid of hanging app (%d) has not exited after Stop() call", hangingPid)
}
- // Record all instances.
- shouldKeep := determineShouldKeep(t, root)
+ shouldKeepInstances := determineShouldKeep(t, root, filepath.Join(root, "app*", "installation*", "instances", "instance*"), "Deleted")
+ shouldKeepInstallations := addBackLinks(t, root, determineShouldKeep(t, root, filepath.Join(root, "app*", "installation*"), "Uninstalled"))
if err := utiltest.DeviceStub("dm").TidyNow(ctx); err != nil {
t.Fatalf("TidyNow failed: %v", err)
}
- validateTidying(t, root, shouldKeep)
+ validateTidying(t, root, filepath.Join(root, "app*", "installation*", "instances", "instance*"), shouldKeepInstances)
+ validateTidying(t, root, filepath.Join(root, "app*", "installation*"), shouldKeepInstallations)
// Cleanly shut down the device manager.
defer utiltest.VerifyNoRunningProcesses(t)
@@ -372,15 +373,15 @@
dmh.ExpectEOF()
}
-func determineShouldKeep(t *testing.T, root string) map[string]bool {
- paths, err := filepath.Glob(filepath.Join(root, "app*", "installation*", "instances", "instance*"))
+func determineShouldKeep(t *testing.T, root, globpath, state string) map[string]bool {
+ paths, err := filepath.Glob(globpath)
if err != nil {
t.Errorf("determineShouldKeep %v", err)
}
shouldKeep := make(map[string]bool)
for _, idir := range paths {
- p := filepath.Join(idir, "Deleted")
+ p := filepath.Join(idir, state)
_, err := os.Stat(p)
if os.IsNotExist(err) {
shouldKeep[idir] = true
@@ -391,10 +392,31 @@
}
}
return shouldKeep
+
}
-func validateTidying(t *testing.T, root string, shouldKeep map[string]bool) {
- paths, err := filepath.Glob(filepath.Join(root, "app*", "installation*", "instances", "instance*"))
+func addBackLinks(t *testing.T, root string, installationShouldKeep map[string]bool) map[string]bool {
+ paths, err := filepath.Glob(filepath.Join(root, "app*", "installation*", "instances", "instance*", "installation"))
+ if err != nil {
+ t.Errorf("addBackLinks %v", err)
+ }
+
+ for _, idir := range paths {
+ pth, err := os.Readlink(idir)
+ if err != nil {
+ t.Errorf("addBackLinks %v", err)
+ continue
+ }
+ if _, ok := installationShouldKeep[pth]; ok {
+ // An instance symlinks to this pth so must be kept.
+ installationShouldKeep[pth] = true
+ }
+ }
+ return installationShouldKeep
+}
+
+func validateTidying(t *testing.T, root, globpath string, shouldKeep map[string]bool) {
+ paths, err := filepath.Glob(globpath)
if err != nil {
t.Errorf("validateTidying %v", err)
}
@@ -402,7 +424,7 @@
// TidyUp adds nothing: pth should be a subset of shouldKeep.
for _, pth := range paths {
if _, ok := shouldKeep[pth]; !ok {
- t.Errorf("TidyUp wrongly added path: %s", pth)
+ t.Errorf("TidyUp (%s) wrongly added path: %s", globpath, pth)
return
}
}
@@ -410,7 +432,7 @@
// Tidy should not leave unkept instances: shouldKeep ^ pth should be entirely true.
for _, pth := range paths {
if !shouldKeep[pth] {
- t.Errorf("TidyUp failed to delete: %s", pth)
+ t.Errorf("TidyUp (%s) failed to delete: %s", globpath, pth)
return
}
}
@@ -419,7 +441,7 @@
for k, v := range shouldKeep {
if v {
if _, err := os.Stat(k); os.IsNotExist(err) {
- t.Errorf("TidyUp deleted an instance it shouldn't have: %s", k)
+ t.Errorf("TidyUp (%s) deleted an instance it shouldn't have: %s", globpath, k)
}
}
}
diff --git a/services/device/internal/impl/device_service.go b/services/device/internal/impl/device_service.go
index 9cfcc6c..97f4e51 100644
--- a/services/device/internal/impl/device_service.go
+++ b/services/device/internal/impl/device_service.go
@@ -679,7 +679,11 @@
// tidyHarness runs device manager cleanup operations
func (s *deviceService) tidyHarness(ctx *context.T) error {
- return pruneDeletedInstances(ctx, s.config.Root)
+ if err := pruneDeletedInstances(ctx, s.config.Root); err != nil {
+ return err
+ }
+
+ return pruneUninstalledInstallations(ctx, s.config.Root)
}
func (s *deviceService) TidyNow(ctx *context.T, call rpc.ServerCall) error {
diff --git a/services/device/internal/impl/tidyup.go b/services/device/internal/impl/tidyup.go
index e51509c..6b1ad1d 100644
--- a/services/device/internal/impl/tidyup.go
+++ b/services/device/internal/impl/tidyup.go
@@ -22,10 +22,8 @@
// TidyAge defaults to 1 day. Settable for tests.
var TidyOlderThan = time.Hour * 24
-// shouldDeleteInstance returns true if the tidying policy holds
-// that the instance should be deleted.
-func shouldDeleteInstance(idir string) (bool, error) {
- fi, err := os.Stat(filepath.Join(idir, device.InstanceStateDeleted.String()))
+func shouldDelete(idir, suffix string) (bool, error) {
+ fi, err := os.Stat(filepath.Join(idir, suffix))
if err != nil {
return false, err
}
@@ -37,16 +35,29 @@
return false, nil
}
+// shouldDeleteInstallation returns true if the tidying policy holds
+// for this installation.
+func shouldDeleteInstallation(idir string) (bool, error) {
+ return shouldDelete(idir, device.InstallationStateUninstalled.String())
+}
+
+// shouldDeleteInstance returns true if the tidying policy holds
+// that the instance should be deleted.
+func shouldDeleteInstance(idir string) (bool, error) {
+ return shouldDelete(idir, device.InstanceStateDeleted.String())
+}
+
+type pthError struct {
+ pth string
+ err error
+}
+
func pruneDeletedInstances(ctx *context.T, root string) error {
paths, err := filepath.Glob(filepath.Join(root, "app*", "installation*", "instances", "instance*"))
if err != nil {
return err
}
- type pthError struct {
- pth string
- err error
- }
allerrors := make([]pthError, 0)
for _, pth := range paths {
@@ -71,7 +82,10 @@
}
}
}
+ return processErrors(ctx, allerrors)
+}
+func processErrors(ctx *context.T, allerrors []pthError) error {
if len(allerrors) > 0 {
errormessages := make([]string, 0, len(allerrors))
for _, ep := range allerrors {
@@ -81,3 +95,64 @@
}
return nil
}
+
+func pruneUninstalledInstallations(ctx *context.T, root string) error {
+ // Read all the Uninstalled installations into a map.
+ installationPaths, err := filepath.Glob(filepath.Join(root, "app*", "installation*"))
+ if err != nil {
+ return err
+ }
+ pruneCandidates := make(map[string]struct{}, len(installationPaths))
+ for _, p := range installationPaths {
+ state, err := getInstallationState(p)
+ if err != nil {
+ return err
+ }
+
+ if state != device.InstallationStateUninstalled {
+ continue
+ }
+
+ pruneCandidates[p] = struct{}{}
+ }
+
+ instancePaths, err := filepath.Glob(filepath.Join(root, "app*", "installation*", "instances", "instance*", "installation"))
+ if err != nil {
+ return err
+ }
+
+ allerrors := make([]pthError, 0)
+
+ // Filter out installations that are still owned by an instance. Note
+ // that pruneUninstalledInstallations runs after
+ // pruneDeletedInstances so that freshly-pruned Instances will not
+ // retain the Installation.
+ for _, idir := range instancePaths {
+ installPath, err := os.Readlink(idir)
+ if err != nil {
+ allerrors = append(allerrors, pthError{idir, err})
+ continue
+ }
+
+ if _, ok := pruneCandidates[installPath]; ok {
+ delete(pruneCandidates, installPath)
+ }
+ }
+
+ // All remaining entries in pruneCandidates are not referenced by
+ // any instance.
+ for pth, _ := range pruneCandidates {
+ shouldDelete, err := shouldDeleteInstallation(pth)
+ if err != nil {
+ allerrors = append(allerrors, pthError{pth, err})
+ continue
+ }
+
+ if shouldDelete {
+ if err := suidHelper.deleteFileTree(pth, nil, nil); err != nil {
+ allerrors = append(allerrors, pthError{pth, err})
+ }
+ }
+ }
+ return processErrors(ctx, allerrors)
+}