| index 91323d807..abee1cfb3 100644 |
| --- a/internal/hostbox/plan9fix2_inventory_test.go |
| +++ b/internal/hostbox/plan9fix2_inventory_test.go |
| @@ -1,6 +1,7 @@ |
| package hostbox |
| |
| import ( |
| + "errors" |
| "path/filepath" |
| "testing" |
| |
| @@ -15,8 +16,24 @@ func TestPlan9Fix2InventoryNamespace(t *testing.T) { |
| name, kind, root, path, id string |
| want bool |
| }{ |
| - {"state", "path", "data", "instances/app.example", "", true}, |
| + {"state parent itself", "path", "data", "instances/app.example", "", true}, |
| {"child", "path", "data", "instances/app.example/app.example.db", "", true}, |
| + {"nested child", "path", "data", "instances/app.example/sub/file", "", true}, |
| + {"sibling host dir", "path", "data", "instances/other.example", "", false}, |
| + {"sibling host file", "path", "data", "instances/other.example/other.example.db", "", false}, |
| + {"prefix sibling child", "path", "data", "instances/app.example-other/app.example.db", "", false}, |
| + {"not clean dot", "path", "data", "instances/app.example/./x", "", false}, |
| + {"not clean double slash", "path", "data", "instances/app.example//x", "", false}, |
| + {"not clean trailing slash", "path", "data", "instances/app.example/x/", "", false}, |
| + {"traversal back in", "path", "data", "instances/app.example/../app.example/x", "", false}, |
| + {"traversal out", "path", "data", "../x", "", false}, |
| + {"absolute", "path", "data", "/instances/app.example/x", "", false}, |
| + {"absolute data path", "path", "data", filepath.ToSlash(filepath.Join(data, "instances", "app.example", "x")), "", false}, |
| + {"root registry db", "path", "data", "registry.db", "", false}, |
| + {"root registry db-wal", "path", "data", "registry.db-wal", "", false}, |
| + {"root registry db-shm", "path", "data", "registry.db-shm", "", false}, |
| + {"root registry db-journal", "path", "data", "registry.db-journal", "", false}, |
| + {"flat db name under nested", "path", "data", "app.example.db", "", false}, |
| {"route", "route", "registry", "app.example", "row", true}, |
| {"timer", "timer", "registry", "app.example/daily", "timer", true}, |
| {"foreign state", "path", "data", "instances/other.example", "", false}, |
| @@ -33,9 +50,13 @@ func TestPlan9Fix2InventoryNamespace(t *testing.T) { |
| t.Run(tc.name, func(t *testing.T) { |
| in := base |
| in.Entries = []StopInventoryEntry{{Kind: tc.kind, ManagedRoot: tc.root, RelativePath: filepath.FromSlash(tc.path), RowIdentity: tc.id, Ownership: "owner", Materialization: "materialized"}} |
| - if err := ValidateAgentInventory(in, &row, data); (err == nil) != tc.want { |
| + err := ValidateAgentInventory(in, &row, data) |
| + if (err == nil) != tc.want { |
| t.Fatalf("allowed=%v: %v", tc.want, err) |
| } |
| + if !tc.want && !errors.Is(err, ErrTypedInventory) { |
| + t.Fatalf("rejection is not ErrTypedInventory: %v", err) |
| + } |
| }) |
| } |
| // A missing row can record the initial route intent, but grants no path authority. |
| @@ -48,16 +69,47 @@ func TestPlan9Fix2InventoryNamespace(t *testing.T) { |
| t.Fatal("missing row granted path authority") |
| } |
| row.DBPath = filepath.Join(data, row.Host+".db") |
| - for _, name := range []string{row.Host, row.Host + ".db", row.Host + ".db-wal", row.Host + ".db-shm"} { |
| + for _, name := range []string{row.Host + ".db", row.Host + ".db-wal", row.Host + ".db-shm", row.Host + ".db-journal", row.Host, row.Host + "/" + row.Host + ".db", row.Host + "/child"} { |
| base.Entries[0].RelativePath = name |
| if err := ValidateAgentInventory(base, &row, data); err != nil { |
| t.Fatalf("legacy %s: %v", name, err) |
| } |
| } |
| - for _, name := range []string{".", ".hostclaims", row.Host + "-other", row.Host + ".db-other"} { |
| - base.Entries[0].RelativePath = name |
| - if err := ValidateAgentInventory(base, &row, data); err == nil { |
| - t.Fatalf("legacy accepted foreign %s", name) |
| + for _, name := range []string{ |
| + ".", ".hostclaims", |
| + row.Host + "-other", row.Host + ".db-other", row.Host + ".db/", "./" + row.Host + ".db", |
| + "../" + row.Host + ".db", "/" + row.Host + ".db", filepath.ToSlash(filepath.Join(data, row.Host+".db")), |
| + "other.example.db", "other.example", "instances/other.example", "instances/other.example/other.example.db", |
| + "registry.db", "registry.db-wal", "registry.db-shm", "registry.db-journal", |
| + } { |
| + base.Entries[0].RelativePath = filepath.FromSlash(name) |
| + if err := ValidateAgentInventory(base, &row, data); !errors.Is(err, ErrTypedInventory) { |
| + t.Fatalf("legacy accepted foreign %s: %v", name, err) |
| + } |
| + } |
| +} |
| + |
| +func TestPlan9Fix2InventoryRegistryDBAtRootRefused(t *testing.T) { |
| + data := t.TempDir() |
| + row := registry.Route{Host: "app.example", ClaimOwnership: registry.ClaimOwnership{ClaimIncarnation: "inc", ClaimStamp: 1, ClaimOwner: "owner", ClaimRowID: "row"}} |
| + for _, dbPath := range []string{ |
| + filepath.Join(data, "app.example.db"), |
| + filepath.Join(data, "instances", "app.example", "app.example.db"), |
| + } { |
| + row.DBPath = dbPath |
| + for _, name := range []string{"registry.db", "registry.db-wal", "registry.db-shm", "registry.db-journal"} { |
| + in := StopInventory{Host: row.Host, Incarnation: "inc", InstallationStamp: 1, Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: name, Ownership: "owner", Materialization: "materialized"}}} |
| + if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { |
| + t.Fatalf("db %s accepted %s: %v", dbPath, name, err) |
| + } |
| + } |
| + } |
| + row.Host = "registry" |
| + row.DBPath = filepath.Join(data, "registry.db") |
| + for _, name := range []string{"registry.db", "registry.db-wal", "registry.db-shm", "registry.db-journal"} { |
| + in := StopInventory{Host: row.Host, Incarnation: "inc", InstallationStamp: 1, Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: name, Ownership: "owner", Materialization: "materialized"}}} |
| + if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { |
| + t.Fatalf("colliding host accepted %s: %v", name, err) |
| } |
| } |
| } |
| @@ -69,22 +121,39 @@ func TestPlan9Fix2AgentCannotChooseNamespaceThroughRegistryDBPath(t *testing.T) |
| t.Run(parent, func(t *testing.T) { |
| row.DBPath = filepath.Join(data, filepath.FromSlash(parent), "app.example.db") |
| in := StopInventory{Host: row.Host, Incarnation: "inc", InstallationStamp: 1, Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: filepath.FromSlash(parent), Ownership: "owner", Materialization: "materialized"}}} |
| - if err := ValidateAgentInventory(in, &row, data); err == nil { |
| - t.Fatalf("agent chose root namespace %s through DBPath", parent) |
| + if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { |
| + t.Fatalf("agent chose root namespace %s through DBPath: %v", parent, err) |
| } |
| }) |
| } |
| row.Host = "hostguard" |
| row.DBPath = filepath.Join(data, "hostguard.db") |
| in := StopInventory{Host: row.Host, Incarnation: "inc", InstallationStamp: 1, Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: "hostguard", Ownership: "owner", Materialization: "materialized"}}} |
| - if err := ValidateAgentInventory(in, &row, data); err == nil { |
| - t.Fatal("flat hostname selected root hostguard namespace") |
| + if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { |
| + t.Fatalf("flat hostname selected root hostguard namespace: %v", err) |
| } |
| row.Host = "instances" |
| row.DBPath = filepath.Join(data, "instances.db") |
| in.Host = row.Host |
| in.Entries[0].RelativePath = "instances/other.example" |
| - if err := ValidateAgentInventory(in, &row, data); err == nil { |
| - t.Fatal("flat hostname selected shared instances namespace") |
| + if err := ValidateAgentInventory(in, &row, data); !errors.Is(err, ErrTypedInventory) { |
| + t.Fatalf("flat hostname selected shared instances namespace: %v", err) |
| + } |
| +} |
| + |
| +// A flat host-named state directory is nameable only for a host whose name |
| +// cannot collide with a root namespace or another host's database file. |
| +func TestPlan9Fix3FlatStateNameMustNotCollide(t *testing.T) { |
| + data := t.TempDir() |
| + for host, want := range map[string]bool{ |
| + "app.example": true, "hostguard": false, "instances": false, "registry.db": false, |
| + "registry.db-wal.example": false, "other.db": false, "other.db-wal": false, "shop.example.db-journal": false, |
| + } { |
| + row := registry.Route{Host: host, DBPath: filepath.Join(data, host+".db"), ClaimOwnership: registry.ClaimOwnership{ClaimIncarnation: "inc", ClaimStamp: 1, ClaimOwner: "owner", ClaimRowID: "row"}} |
| + in := StopInventory{Host: host, Incarnation: "inc", InstallationStamp: 1, Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: host, Ownership: "owner", Materialization: "intended"}}} |
| + err := ValidateAgentInventory(in, &row, data) |
| + if (err == nil) != want { |
| + t.Errorf("host %q: allowed=%v, want %v (%v)", host, err == nil, want, err) |
| + } |
| } |
| } |