package activator import ( "fmt" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/seeds" ) // wakeRouteChanged is doWake's check of the row it refreshed against the // row the wake began from (final review F4). The edge admits a wake on its // cached row, which can be minutes behind the registry: an import's // rollback restores the legacy route and lifts its fence before the edge // reloads, and a verify request admitted on the cached import row would // then take the host's lease and restore its replica while the rollback // deletes that same prefix. A refreshed row that the activator no longer // manages, or that is now a different route (account, app, seed, import // gate or address), is not woken: the answer is an ordinary failed wake, // before any lease or restore. nil means the wake goes on. // // The shape is judged by the entry's own role (re-review finding 9): an // instance entry's row must still be ActivatorManaged, and a worker // entry's (WakeWorker, a standalone sidecar, which binds no socket) must // still be a sidecar. The identity checks hold for both. // // The activator also wakes instance rows that are not ActivatorManaged: // the edge hands it every hibernating instance row, so a unit-backed one // (SplitBackend's Unit half, which systemd runs) and a row with no socket // column (the backend derives its paths) are woken too. A wake that began // for such a row goes on while the refreshed row has the same shape // (stillWoken); the identity checks hold for it as well. No seed or import // is one of these rows: the swap refuses a row with a backing and installs // an ActivatorManaged one, so a wake that began on an import's row is // always held to ActivatorManaged, and the row a rollback puts back fails // it. func wakeRouteChanged(began, now registry.Route, worker bool) error { switch { case worker && !now.IsSidecar(): return fmt.Errorf("%s: the route changed before the wake: it is no longer a sidecar", now.Host) case !worker && !stillWoken(began, now): return fmt.Errorf("%s: the route changed before the wake: the activator no longer manages it", now.Host) case began.Account != now.Account || began.App != now.App: return fmt.Errorf("%s: the route changed before the wake: it belongs to %s/%s now, not %s/%s", now.Host, now.Account, now.App, began.Account, began.App) case began.Addr != now.Addr: return fmt.Errorf("%s: the route changed before the wake: its address is %q now, not %q", now.Host, now.Addr, began.Addr) case began.ImportGate != now.ImportGate: return fmt.Errorf("%s: the route changed before the wake: its import gate changed", now.Host) case rowSeedID(began.SeedSource) != rowSeedID(now.SeedSource): return fmt.Errorf("%s: the route changed before the wake: its seed is %q now, not %q", now.Host, rowSeedID(now.SeedSource), rowSeedID(began.SeedSource)) } return nil } // stillWoken reports whether now is still a row an instance entry's wake, // begun for began, may go on with. A wake that began on an ActivatorManaged // row needs one still. Any other row the activator was handed must be an // instance row and keep the shape it began with, in the fields // ActivatorManaged reads: its hibernate flag, whether it has a socket, its // backing and its tunnel. A sidecar's row is never an instance entry's. func stillWoken(began, now registry.Route) bool { if ActivatorManaged(began) { return ActivatorManaged(now) } return now.Kind == "instance" && began.Kind == now.Kind && began.Hibernate == now.Hibernate && (began.Socket != "") == (now.Socket != "") && began.Backing == now.Backing && began.Tunnel == now.Tunnel } // rowSeedID is the seed a row's seed column names: its id, "" for none, and // the column itself when it does not parse. A seed's state moves on while // it is the same seed (ready, placed, committed), so only the id is the // row's identity. func rowSeedID(col string) string { rs, err := seeds.ParseRowSeed(col) switch { case err != nil: return col case rs == nil: return "" } return rs.ID }