carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package registry
2
3import (
4 "context"
5 "database/sql"
6 "encoding/json"
7 "errors"
8)
9
10// Recovery handovers remain pending until the successor's prelaunch is durable.
11// The existing isolated registry transport owns these writes, including at root.
12func (r *Registry) BeginRecoveryHandover(ctx context.Context, id, body string) error {
13 if r.remote != nil {
14 _, err := r.remote.call(ctx, remoteRequest{Method: "BeginRecoveryHandover", Id: id, Journal: body}, nil)
15 return err
16 }
17 if id == "" || body == "" {
18 return ErrClaimOwnership
19 }
20 if err := r.recoveryJournal(ctx); err != nil {
21 return err
22 }
23 tx, err := r.beginClaimTransaction(ctx, true)
24 if err != nil {
25 return err
26 }
27 defer tx.Rollback()
28 var saved string
29 err = tx.QueryRowContext(ctx, `SELECT body FROM recovery_handovers WHERE id=?`, id).Scan(&saved)
30 if err == nil {
31 if saved != body {
32 return ErrClaimOwnership
33 }
34 return nil
35 }
36 if !errors.Is(err, sql.ErrNoRows) {
37 return err
38 }
39 if _, err = tx.ExecContext(ctx, `INSERT INTO recovery_handovers(id,body,complete) VALUES(?,?,0)`, id, body); err != nil {
40 return err
41 }
42 return tx.Commit()
43}
44func (r *Registry) PendingRecoveryHandover(ctx context.Context, id string) (string, error) {
45 if r.remote != nil {
46 out, err := r.remote.call(ctx, remoteRequest{Method: "PendingRecoveryHandover", Id: id}, nil)
47 return out.Text, err
48 }
49 if err := r.recoveryJournal(ctx); err != nil {
50 return "", err
51 }
52 var body string
53 err := r.sql.QueryRowContext(ctx, `SELECT body FROM recovery_handovers WHERE id=? AND complete=0`, id).Scan(&body)
54 if errors.Is(err, sql.ErrNoRows) {
55 return "", nil
56 }
57 return body, err
58}
59func (r *Registry) CompleteRecoveryHandover(ctx context.Context, id, body string) error {
60 if r.remote != nil {
61 _, err := r.remote.call(ctx, remoteRequest{Method: "CompleteRecoveryHandover", Id: id, Journal: body}, nil)
62 return err
63 }
64 if err := r.recoveryJournal(ctx); err != nil {
65 return err
66 }
67 result, err := r.sql.ExecContext(ctx, `UPDATE recovery_handovers SET complete=1 WHERE id=? AND body=?`, id, body)
68 if err != nil {
69 return err
70 }
71 n, err := result.RowsAffected()
72 if err != nil {
73 return err
74 }
75 if n != 1 {
76 return ErrClaimOwnership
77 }
78 return nil
79}
80func (r *Registry) recoveryJournal(ctx context.Context) error {
81 _, err := r.sql.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS recovery_handovers(id TEXT PRIMARY KEY,body TEXT NOT NULL,complete INTEGER NOT NULL)`)
82 return err
83}
84
85func (r *Registry) PendingRecoveryHandovers(ctx context.Context) (map[string]string, error) {
86 if r.remote != nil {
87 out, err := r.remote.call(ctx, remoteRequest{Method: "PendingRecoveryHandovers"}, nil)
88 if err != nil {
89 return nil, err
90 }
91 var bodies map[string]string
92 err = json.Unmarshal([]byte(out.Text), &bodies)
93 return bodies, err
94 }
95 if err := r.recoveryJournal(ctx); err != nil {
96 return nil, err
97 }
98 rows, err := r.sql.QueryContext(ctx, `SELECT id,body FROM recovery_handovers WHERE complete=0`)
99 if err != nil {
100 return nil, err
101 }
102 defer rows.Close()
103 bodies := map[string]string{}
104 for rows.Next() {
105 var id, body string
106 if err := rows.Scan(&id, &body); err != nil {
107 return nil, err
108 }
109 bodies[id] = body
110 }
111 return bodies, rows.Err()
112}
113
114func (r *Registry) ReviseRecoveryHandover(ctx context.Context, id, prior, body string) error {
115 if r.remote != nil {
116 _, err := r.remote.call(ctx, remoteRequest{Method: "ReviseRecoveryHandover", Id: id, Expected: prior, Journal: body}, nil)
117 return err
118 }
119 if id == "" || prior == "" || body == "" {
120 return ErrClaimOwnership
121 }
122 if err := r.recoveryJournal(ctx); err != nil {
123 return err
124 }
125 tx, err := r.beginClaimTransaction(ctx, true)
126 if err != nil {
127 return err
128 }
129 defer tx.Rollback()
130 result, err := tx.ExecContext(ctx, `UPDATE recovery_handovers SET body=? WHERE id=? AND body=? AND complete=0`, body, id, prior)
131 if err != nil {
132 return err
133 }
134 n, err := result.RowsAffected()
135 if err != nil {
136 return err
137 }
138 if n != 1 {
139 return ErrClaimOwnership
140 }
141 return tx.Commit()
142}
143