package store import ( "os" "path/filepath" "strings" "testing" "time" ) func TestAFreshMachineHasAnEmptyStateNotAnError(t *testing.T) { // The first apply on a machine that has never been touched is the ordinary case, not a // failure. A host that errored here could never bootstrap anything. s, err := Load(filepath.Join(t.TempDir(), "nothing-here.json")) if err != nil { t.Fatalf("a fresh machine produced an error: %v", err) } if len(s.Resources) != 0 { t.Errorf("a fresh machine claims to own %d resources", len(s.Resources)) } } func TestAnUnreadableStateIsRefusedNotIgnored(t *testing.T) { // The dangerous one. Starting empty would make the host believe it owns nothing, so it // would remove nothing it should and re-apply everything it need not — silently. path := filepath.Join(t.TempDir(), "state.json") if err := os.WriteFile(path, []byte("{this is not json"), 0o600); err != nil { t.Fatal(err) } _, err := Load(path) if err == nil { t.Fatal("a corrupt state was read as an empty one") } if !strings.Contains(err.Error(), "believes it owns nothing") { t.Errorf("the error does not say why this matters: %v", err) } } func TestWhatIsSavedIsWhatIsLoaded(t *testing.T) { path := filepath.Join(t.TempDir(), "state.json") want := State{Resources: []Applied{ {ID: "etc", Type: "directory", Target: "/etc/mesh", AppliedAt: time.Now().UTC().Truncate(time.Second)}, {ID: "conf", Type: "file", Target: "/etc/mesh/host.conf", AppliedAt: time.Now().UTC().Truncate(time.Second)}, }} if err := Save(path, want); err != nil { t.Fatal(err) } got, err := Load(path) if err != nil { t.Fatal(err) } if len(got.Resources) != 2 || got.Resources[0].ID != "etc" || got.Resources[1].ID != "conf" { t.Fatalf("order or content was lost: %+v", got.Resources) } if got.UpdatedAt.IsZero() { t.Error("the state does not say when it was written") } } func TestTheStateIsNotWorldReadable(t *testing.T) { // It records what is on the machine and where. Not secret, and not everyone's business. path := filepath.Join(t.TempDir(), "state.json") if err := Save(path, State{Resources: []Applied{{ID: "a", Type: "file", Target: "/a"}}}); err != nil { t.Fatal(err) } info, err := os.Stat(path) if err != nil { t.Fatal(err) } if mode := info.Mode().Perm(); mode&0o077 != 0 { t.Errorf("the state is readable by others: %o", mode) } } func TestSavingLeavesNoDebrisBehind(t *testing.T) { // The write is atomic through a temporary file. A run that left those behind would fill a // directory with near-copies of the truth, and the next reader would have to guess. dir := t.TempDir() path := filepath.Join(dir, "state.json") for i := 0; i < 3; i++ { if err := Save(path, State{Resources: []Applied{{ID: "a", Type: "file", Target: "/a"}}}); err != nil { t.Fatal(err) } } entries, err := os.ReadDir(dir) if err != nil { t.Fatal(err) } if len(entries) != 1 { names := []string{} for _, e := range entries { names = append(names, e.Name()) } t.Errorf("expected only the state file, found: %v", names) } } func TestRecordReplacesRatherThanDuplicating(t *testing.T) { s := State{} s.Record(Applied{ID: "a", Type: "file", Target: "/old"}) s.Record(Applied{ID: "a", Type: "file", Target: "/new"}) if len(s.Resources) != 1 { t.Fatalf("one identity produced %d records", len(s.Resources)) } if s.Resources[0].Target != "/new" { t.Errorf("the record was not updated: %+v", s.Resources[0]) } } func TestOrphansAreWhatWasAppliedAndIsNoLongerDeclared(t *testing.T) { // The whole reason the store arrives at stage 2 rather than stage 3: removal is impossible // without knowing what was applied. s := State{Resources: []Applied{ {ID: "dir", Type: "directory", Target: "/etc/mesh"}, {ID: "file", Type: "file", Target: "/etc/mesh/a.conf"}, {ID: "kept", Type: "file", Target: "/etc/mesh/b.conf"}, }} orphans := s.Orphans(map[string]bool{"kept": true}, OriginCarried) if len(orphans) != 2 { t.Fatalf("expected two orphans, got %d: %+v", len(orphans), orphans) } // Reverse order: undoing in the order things were made would remove a directory before the // file inside it. if orphans[0].ID != "file" || orphans[1].ID != "dir" { t.Errorf("orphans are not in reverse order: %s then %s", orphans[0].ID, orphans[1].ID) } } func TestNothingIsAnOrphanWhenEverythingIsDeclared(t *testing.T) { s := State{Resources: []Applied{{ID: "a", Type: "file", Target: "/a"}}} if got := s.Orphans(map[string]bool{"a": true}, OriginCarried); len(got) != 0 { t.Errorf("a declared resource was treated as an orphan: %+v", got) } } func TestADeclarationDoesNotOrphanWhatTheBundleRaised(t *testing.T) { // 04-ISSUES/010. A first node raises its foundation from the bundle it carries, then enrols // and is sent a declaration naming two resources. Before origins, that removed the store, the // broker and the control plane that had sent it — the mesh deleting itself over the link the // message arrived on, in under a second, on the first end-to-end run. s := State{Resources: []Applied{ {ID: "store", Type: "container", Target: "mesh-store", Origin: OriginCarried}, {ID: "broker", Type: "container", Target: "mesh-broker", Origin: OriginCarried}, {ID: "greeting", Type: "directory", Target: "/var/lib/demo", Origin: OriginDeclared}, }} // The mesh declares nothing at all. Everything it previously declared is an orphan; nothing // the bundle raised is. orphans := s.Orphans(map[string]bool{}, OriginDeclared) if len(orphans) != 1 || orphans[0].ID != "greeting" { var got []string for _, o := range orphans { got = append(got, o.ID) } t.Fatalf("a declaration would remove %v; it may only remove what the mesh declared", got) } } func TestTheBundleDoesNotOrphanWhatTheMeshDeclared(t *testing.T) { // The same rule the other way. A host reconciling its carried bundle must not remove what the // mesh assigned to this node, or every restart would undo the node's actual work. s := State{Resources: []Applied{ {ID: "store", Type: "container", Target: "mesh-store", Origin: OriginCarried}, {ID: "workload", Type: "container", Target: "some-app", Origin: OriginDeclared}, }} orphans := s.Orphans(map[string]bool{"store": true}, OriginCarried) if len(orphans) != 0 { t.Errorf("reconciling the bundle would remove %s, which the mesh declared", orphans[0].ID) } } func TestStateWrittenBeforeOriginsExistedIsTreatedAsCarried(t *testing.T) { // Every resource a host had applied before this field existed came from its bundle, because // there was no other way to tell it anything. Guessing the other way would have the first // declaration remove the foundation — which is the fault this exists to prevent, arriving // through the upgrade that fixes it. s := State{Resources: []Applied{{ID: "store", Type: "container", Target: "mesh-store"}}} if got := s.Orphans(map[string]bool{}, OriginDeclared); len(got) != 0 { t.Errorf("a declaration would remove %s, recorded before origins existed", got[0].ID) } if got := s.Orphans(map[string]bool{}, OriginCarried); len(got) != 1 { t.Error("the bundle cannot remove its own resource, so nothing could ever remove it") } }