carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package stopevidence
2
3import (
4 "context"
5 "errors"
6 "path/filepath"
7 "sort"
8 "time"
9
10 "github.com/carlosframework/platform/internal/activator"
11 "github.com/carlosframework/platform/internal/cgroup"
12 "github.com/carlosframework/platform/internal/controlname"
13)
14
15var (
16 ErrFence = errors.New("stopevidence: matching stop fence required")
17 ErrNoCgroupEvidence = errors.New("stopevidence: cgroup observation required")
18 ErrPopulated = errors.New("stopevidence: cgroup populated")
19 ErrMembers = errors.New("stopevidence: cgroup has members")
20 ErrReplicatorsOpen = errors.New("stopevidence: replicators not closed")
21 ErrFDHolders = errors.New("stopevidence: database descriptors still held")
22)
23
24type Inputs struct {
25 Host string
26 Generation uint64
27 Fence *activator.StopEvidence
28 // Cgroup is the host's PARENT handle (cgroup.Manager.OpenCleanup), never
29 // one generation's child: under the generation layout the parent is
30 // empty only when every generation is (zero-downtime spec §7.3), and its
31 // Members walks every cgroup below it.
32 Cgroup interface {
33 Populated() (bool, error)
34 Members() ([]int, error)
35 }
36 DBPaths []string
37 ProcRoot string
38 ReplicatorsClosed bool
39}
40
41type Evidence struct {
42 Host string
43 Generation uint64
44 CheckedAt time.Time
45 FenceClosed bool
46 SpawnAttemptsDrained bool
47 CgroupUnpopulated bool
48 CgroupMembersEmpty bool
49 ReplicatorsClosed bool
50 FDScanComplete bool
51 CheckedPaths []string
52 ProcRoot string
53}
54
55func Collect(ctx context.Context, in Inputs) (Evidence, error) {
56 if ctx == nil {
57 return Evidence{}, ErrInvalidInput
58 }
59 if err := ctx.Err(); err != nil {
60 return Evidence{}, err
61 }
62 if !controlname.ValidHostShape(in.Host) || in.Generation == 0 {
63 return Evidence{}, ErrInvalidInput
64 }
65 if in.Fence == nil || !in.Fence.Matches(in.Host, in.Generation) {
66 return Evidence{}, ErrFence
67 }
68 if in.Cgroup == nil {
69 return Evidence{}, ErrNoCgroupEvidence
70 }
71 if !in.ReplicatorsClosed {
72 return Evidence{}, ErrReplicatorsOpen
73 }
74 if len(in.DBPaths) == 0 {
75 return Evidence{}, ErrInvalidInput
76 }
77 seen := map[string]bool{}
78 for _, db := range in.DBPaths {
79 if !filepath.IsAbs(db) {
80 return Evidence{}, ErrInvalidInput
81 }
82 db = filepath.Clean(db)
83 seen[db], seen[db+"-wal"], seen[db+"-shm"] = true, true, true
84 }
85 paths := make([]string, 0, len(seen))
86 for path := range seen {
87 paths = append(paths, path)
88 }
89 sort.Strings(paths)
90 populated, err := in.Cgroup.Populated()
91 if err != nil {
92 return Evidence{}, err
93 }
94 if populated {
95 return Evidence{}, ErrPopulated
96 }
97 members, err := in.Cgroup.Members()
98 if err != nil {
99 return Evidence{}, err
100 }
101 if len(members) != 0 {
102 return Evidence{}, ErrMembers
103 }
104 root := in.ProcRoot
105 if root == "" {
106 root = "/proc"
107 }
108 holders, err := scanFDs(ctx, root, paths)
109 if err != nil {
110 return Evidence{}, err
111 }
112 if len(holders) != 0 {
113 return Evidence{}, ErrFDHolders
114 }
115 if err := ctx.Err(); err != nil {
116 return Evidence{}, err
117 }
118 return Evidence{
119 Host: in.Host, Generation: in.Generation, CheckedAt: time.Now().UTC(),
120 FenceClosed: true, SpawnAttemptsDrained: true,
121 CgroupUnpopulated: true, CgroupMembersEmpty: true,
122 ReplicatorsClosed: true, FDScanComplete: true,
123 CheckedPaths: paths, ProcRoot: root,
124 }, nil
125}
126
127func Cleanup(ctx context.Context, c *cgroup.Cleanup) error {
128 if c == nil {
129 return ErrInvalidInput
130 }
131 return c.KillAndEmpty(ctx)
132}
133