a73x

5e59e2b0

wire: the spec/status vocabulary the loop always meant

a73x   2026-08-22 17:14

Commit message
wire: the spec/status vocabulary the loop always meant

The wire named its halves after the decision that created them — desired
state down, actual state back — and the adjective drifted: VMDesired but
ActualVM, ExposureDesired and ExposureActual, one field spelled power on
the way up and power_state on the way down. Every message in a snapshot is
desired by construction and every message in a report is actual, so the
suffix named the envelope, not the thing.

One VM, two halves: VMSpec is what the control plane asked for, VMStatus
is what its host observed, and the envelopes are Snapshot and Report.
Exposures follow. The one property both halves share is spelled
power_state on both. QuarantinedVM.vmspec_json had the right word since
birth; the agent's state.VMSpec had it too.

Nothing moves on the wire. Protobuf serialises field numbers, not message
or field names, so a v0.0.7 agent talks to this server unchanged — the
field-number lock in internal/transport is the proof, and it runs with
every number where it was. The JSON API's actual_power is untouched; that
rename is a deprecation and belongs with the rest of them.

RETRO.md
Old New
@@ -13,3 +13,7 @@ One line per push to `main`: what slowed the work down. Enforced by
13 means. Pushing main answered it: origin/main became HEAD, gremlins read a 13 means. Pushing main answered it: origin/main became HEAD, gremlins read a
14 diff with no entries as "everything changed", and the whole-tree run it 14 diff with no entries as "everything changed", and the whole-tree run it
15 started was killed by the one mutant that signals its own process group. 15 started was killed by the one mutant that signals its own process group.
16 - Wire rename: `make ci` failed twice on nothing wrong. A fresh worktree has
17 an empty web/dist, so the web package misses its coverage floor until
18 `make web` runs; and proto-check diffs generated code against the COMMIT,
19 so it always fails on an uncommitted .proto change. Neither gate says so.
docs/architecture.md
Old New
@@ -16,8 +16,8 @@ hosts and share only a wire contract:
16 | **Data plane** (agent) | `internal/agent/*`, `cmd/eitri-agent` | Owns the entire VM lifecycle: disks, networking, VMM processes. | 16 | **Data plane** (agent) | `internal/agent/*`, `cmd/eitri-agent` | Owns the entire VM lifecycle: disks, networking, VMM processes. |
17 | **Wire contract** | `internal/pb`, `internal/transport` | The only code shared across the boundary: protobuf messages + QUIC framing/TLS. | 17 | **Wire contract** | `internal/pb`, `internal/transport` | The only code shared across the boundary: protobuf messages + QUIC framing/TLS. |
18 18
19 The server expresses intent as a `pb.DesiredStateSnapshot` (server → agent); the 19 The server expresses intent as a `pb.Snapshot` (server → agent); the
20 agent reports back a `pb.ActualStateReport` (agent → server). The server never 20 agent reports back a `pb.Report` (agent → server). The server never
21 touches a VM and never executes a process: besides writing desired state to its 21 touches a VM and never executes a process: besides writing desired state to its
22 store and poking the SSE hub, its only real-world side effects are control-plane 22 store and poking the SSE hub, its only real-world side effects are control-plane
23 services (the SSH jump gate and cert minting). All side effects on VMs and hosts 23 services (the SSH jump gate and cert minting). All side effects on VMs and hosts
docs/assumptions.md
Old New
@@ -240,7 +240,7 @@ guests sit inside their own host's `bridge_cidr` and satisfy the old guard too.
240 240
241 **Inverted** 2026-08-04, which is what the entry above was asking for: the 241 **Inverted** 2026-08-04, which is what the entry above was asking for: the
242 subnet a host's guests are on is now reported BY the host, in every 242 subnet a host's guests are on is now reported BY the host, in every
243 `ActualStateReport`, and the fleet records what it is told. Proven on the same 243 `Report`, and the fleet records what it is told. Proven on the same
244 M1 — its row read 10.102.1.0/24, an allocation from a pool it never used, and 244 M1 — its row read 10.102.1.0/24, an allocation from a pool it never used, and
245 now reads 192.168.64.0/24, which is where its guests actually are. 245 now reads 192.168.64.0/24, which is where its guests actually are.
246 246
internal/agent/exposeproxy/exposeproxy.go
Old New
@@ -166,7 +166,7 @@ func (e *exposure) close() {
166 // depends on. A change to any of it is a close and a rebind rather than an 166 // depends on. A change to any of it is a close and a rebind rather than an
167 // edit — the socket's own address, or the protocol it speaks, is part of what 167 // edit — the socket's own address, or the protocol it speaks, is part of what
168 // changed. 168 // changed.
169 func specKey(d *pb.ExposureDesired) string { 169 func specKey(d *pb.ExposureSpec) string {
170 return fmt.Sprintf("%s/%d/%d/%s", d.GetVmId(), d.GetGuestPort(), d.GetHostPort(), d.GetProtocol()) 170 return fmt.Sprintf("%s/%d/%d/%s", d.GetVmId(), d.GetGuestPort(), d.GetHostPort(), d.GetProtocol())
171 } 171 }
172 172
@@ -182,11 +182,11 @@ func specKey(d *pb.ExposureDesired) string {
182 // about serving it is going wrong — a descriptor shortage the accept loop is 182 // about serving it is going wrong — a descriptor shortage the accept loop is
183 // riding out — because a port that is bound and struggling is not the same 183 // riding out — because a port that is bound and struggling is not the same
184 // thing as a port that is bound and fine. 184 // thing as a port that is bound and fine.
185 func (m *Manager) Converge(desired []*pb.ExposureDesired) []*pb.ExposureActual { 185 func (m *Manager) Converge(desired []*pb.ExposureSpec) []*pb.ExposureStatus {
186 m.mu.Lock() 186 m.mu.Lock()
187 defer m.mu.Unlock() 187 defer m.mu.Unlock()
188 188
189 want := make(map[string]*pb.ExposureDesired, len(desired)) 189 want := make(map[string]*pb.ExposureSpec, len(desired))
190 for _, d := range desired { 190 for _, d := range desired {
191 want[d.GetId()] = d 191 want[d.GetId()] = d
192 } 192 }
@@ -198,7 +198,7 @@ func (m *Manager) Converge(desired []*pb.ExposureDesired) []*pb.ExposureActual {
198 delete(m.live, id) 198 delete(m.live, id)
199 } 199 }
200 200
201 out := make([]*pb.ExposureActual, 0, len(desired)) 201 out := make([]*pb.ExposureStatus, 0, len(desired))
202 for _, d := range desired { 202 for _, d := range desired {
203 ex, ok := m.live[d.GetId()] 203 ex, ok := m.live[d.GetId()]
204 switch { 204 switch {
@@ -214,7 +214,7 @@ func (m *Manager) Converge(desired []*pb.ExposureDesired) []*pb.ExposureActual {
214 if !ex.bound() { 214 if !ex.bound() {
215 if err := m.bind(d, ex); err != nil { 215 if err := m.bind(d, ex); err != nil {
216 ex.reason = err.Error() 216 ex.reason = err.Error()
217 out = append(out, &pb.ExposureActual{Id: d.GetId(), State: "failed", Reason: ex.reason, Sessions: ex.counters()}) 217 out = append(out, &pb.ExposureStatus{Id: d.GetId(), State: "failed", Reason: ex.reason, Sessions: ex.counters()})
218 continue 218 continue
219 } 219 }
220 } 220 }
@@ -227,7 +227,7 @@ func (m *Manager) Converge(desired []*pb.ExposureDesired) []*pb.ExposureActual {
227 if ex.pc != nil { 227 if ex.pc != nil {
228 ex.evictMovedSessions(m.addr(d.GetVmId())) 228 ex.evictMovedSessions(m.addr(d.GetVmId()))
229 } 229 }
230 out = append(out, &pb.ExposureActual{Id: d.GetId(), State: "active", Reason: ex.reason, Sessions: ex.counters()}) 230 out = append(out, &pb.ExposureStatus{Id: d.GetId(), State: "active", Reason: ex.reason, Sessions: ex.counters()})
231 } 231 }
232 return out 232 return out
233 } 233 }
@@ -235,7 +235,7 @@ func (m *Manager) Converge(desired []*pb.ExposureDesired) []*pb.ExposureActual {
235 // bind opens the socket one desired exposure calls for and starts the loop that 235 // bind opens the socket one desired exposure calls for and starts the loop that
236 // serves it. UDP is a bound packet socket; everything else is a TCP listener — 236 // serves it. UDP is a bound packet socket; everything else is a TCP listener —
237 // a server that names no protocol at all means the one this proxy started with. 237 // a server that names no protocol at all means the one this proxy started with.
238 func (m *Manager) bind(d *pb.ExposureDesired, ex *exposure) error { 238 func (m *Manager) bind(d *pb.ExposureSpec, ex *exposure) error {
239 hostAddr := net.JoinHostPort("0.0.0.0", strconv.Itoa(int(d.GetHostPort()))) 239 hostAddr := net.JoinHostPort("0.0.0.0", strconv.Itoa(int(d.GetHostPort())))
240 if d.GetProtocol() == "udp" { 240 if d.GetProtocol() == "udp" {
241 ua, err := net.ResolveUDPAddr("udp4", hostAddr) 241 ua, err := net.ResolveUDPAddr("udp4", hostAddr)
internal/agent/exposeproxy/exposeproxy_test.go
Old New
@@ -79,12 +79,12 @@ func boundPort(t *testing.T, m *Manager, id string) string {
79 return ex.ln.Addr().String() 79 return ex.ln.Addr().String()
80 } 80 }
81 81
82 func desired(id, vmID string, guestPort, hostPort uint32) *pb.ExposureDesired { 82 func desired(id, vmID string, guestPort, hostPort uint32) *pb.ExposureSpec {
83 return &pb.ExposureDesired{Id: id, VmId: vmID, GuestPort: guestPort, HostPort: hostPort, Protocol: "tcp"} 83 return &pb.ExposureSpec{Id: id, VmId: vmID, GuestPort: guestPort, HostPort: hostPort, Protocol: "tcp"}
84 } 84 }
85 85
86 func desiredUDP(id, vmID string, guestPort, hostPort uint32) *pb.ExposureDesired { 86 func desiredUDP(id, vmID string, guestPort, hostPort uint32) *pb.ExposureSpec {
87 return &pb.ExposureDesired{Id: id, VmId: vmID, GuestPort: guestPort, HostPort: hostPort, Protocol: "udp"} 87 return &pb.ExposureSpec{Id: id, VmId: vmID, GuestPort: guestPort, HostPort: hostPort, Protocol: "udp"}
88 } 88 }
89 89
90 // speak dials addr, sends msg, and returns what came back. 90 // speak dials addr, sends msg, and returns what came back.
@@ -105,7 +105,7 @@ func TestConvergeBindsAndPipesToTheGuest(t *testing.T) {
105 g := newFakeGuest(t) 105 g := newFakeGuest(t)
106 m := newTestManager(t, map[string]string{"vm1": g.addr}) 106 m := newTestManager(t, map[string]string{"vm1": g.addr})
107 107
108 got := m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", g.port, 0)}) 108 got := m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", g.port, 0)})
109 require.Len(t, got, 1) 109 require.Len(t, got, 1)
110 assert.Equal(t, "e1", got[0].GetId()) 110 assert.Equal(t, "e1", got[0].GetId())
111 assert.Equal(t, "active", got[0].GetState()) 111 assert.Equal(t, "active", got[0].GetState())
@@ -117,9 +117,9 @@ func TestConvergeIsIdempotent(t *testing.T) {
117 g := newFakeGuest(t) 117 g := newFakeGuest(t)
118 m := newTestManager(t, map[string]string{"vm1": g.addr}) 118 m := newTestManager(t, map[string]string{"vm1": g.addr})
119 119
120 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", g.port, 0)}) 120 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", g.port, 0)})
121 addr := boundPort(t, m, "e1") 121 addr := boundPort(t, m, "e1")
122 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", g.port, 0)}) 122 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", g.port, 0)})
123 123
124 assert.Equal(t, addr, boundPort(t, m, "e1"), "an unchanged spec keeps its listener") 124 assert.Equal(t, addr, boundPort(t, m, "e1"), "an unchanged spec keeps its listener")
125 assert.Equal(t, "echo:hi", speak(t, addr, "hi")) 125 assert.Equal(t, "echo:hi", speak(t, addr, "hi"))
@@ -128,7 +128,7 @@ func TestConvergeIsIdempotent(t *testing.T) {
128 func TestConvergeClosesAVanishedExposure(t *testing.T) { 128 func TestConvergeClosesAVanishedExposure(t *testing.T) {
129 g := newFakeGuest(t) 129 g := newFakeGuest(t)
130 m := newTestManager(t, map[string]string{"vm1": g.addr}) 130 m := newTestManager(t, map[string]string{"vm1": g.addr})
131 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", g.port, 0)}) 131 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", g.port, 0)})
132 addr := boundPort(t, m, "e1") 132 addr := boundPort(t, m, "e1")
133 133
134 got := m.Converge(nil) 134 got := m.Converge(nil)
@@ -143,10 +143,10 @@ func TestConvergeRebindsAChangedSpec(t *testing.T) {
143 second := newFakeGuest(t) 143 second := newFakeGuest(t)
144 m := newTestManager(t, map[string]string{"vm1": first.addr}) 144 m := newTestManager(t, map[string]string{"vm1": first.addr})
145 145
146 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", first.port, 0)}) 146 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", first.port, 0)})
147 oldAddr := boundPort(t, m, "e1") 147 oldAddr := boundPort(t, m, "e1")
148 148
149 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", second.port, 0)}) 149 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", second.port, 0)})
150 newAddr := boundPort(t, m, "e1") 150 newAddr := boundPort(t, m, "e1")
151 assert.NotEqual(t, oldAddr, newAddr, "a changed spec is a close and a rebind") 151 assert.NotEqual(t, oldAddr, newAddr, "a changed spec is a close and a rebind")
152 152
@@ -163,7 +163,7 @@ func TestConvergeReportsABindFailureAndHealsWhenThePortFrees(t *testing.T) {
163 require.NoError(t, err) 163 require.NoError(t, err)
164 held := portOf(t, squatter.Addr()) 164 held := portOf(t, squatter.Addr())
165 165
166 got := m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", g.port, held)}) 166 got := m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", g.port, held)})
167 require.Len(t, got, 1) 167 require.Len(t, got, 1)
168 assert.Equal(t, "failed", got[0].GetState()) 168 assert.Equal(t, "failed", got[0].GetState())
169 assert.NotEmpty(t, got[0].GetReason(), "the report carries what the OS said") 169 assert.NotEmpty(t, got[0].GetReason(), "the report carries what the OS said")
@@ -173,7 +173,7 @@ func TestConvergeReportsABindFailureAndHealsWhenThePortFrees(t *testing.T) {
173 require.NoError(t, squatter.Close()) 173 require.NoError(t, squatter.Close())
174 var state string 174 var state string
175 for i := 0; i < 20; i++ { 175 for i := 0; i < 20; i++ {
176 got = m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", g.port, held)}) 176 got = m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", g.port, held)})
177 state = got[0].GetState() 177 state = got[0].GetState()
178 if state == "active" { 178 if state == "active" {
179 break 179 break
@@ -187,7 +187,7 @@ func TestConvergeReportsABindFailureAndHealsWhenThePortFrees(t *testing.T) {
187 func TestConvergeRebindsAListenerWhoseAcceptLoopDied(t *testing.T) { 187 func TestConvergeRebindsAListenerWhoseAcceptLoopDied(t *testing.T) {
188 g := newFakeGuest(t) 188 g := newFakeGuest(t)
189 m := newTestManager(t, map[string]string{"vm1": g.addr}) 189 m := newTestManager(t, map[string]string{"vm1": g.addr})
190 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", g.port, 0)}) 190 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", g.port, 0)})
191 191
192 // Break the listener behind the manager's back, the way fd exhaustion would 192 // Break the listener behind the manager's back, the way fd exhaustion would
193 // end an accept loop: the manager never asked for this and still believes 193 // end an accept loop: the manager never asked for this and still believes
@@ -220,7 +220,7 @@ func TestConvergeRebindsAListenerWhoseAcceptLoopDied(t *testing.T) {
220 // 0, so that rebind cannot fail and no converge ever reports "failed" — the 220 // 0, so that rebind cannot fail and no converge ever reports "failed" — the
221 // interim report is only observable when the fresh bind is also refused, 221 // interim report is only observable when the fresh bind is also refused,
222 // which is the case TestConvergeReportsABindFailure... already covers. 222 // which is the case TestConvergeReportsABindFailure... already covers.
223 got := m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", g.port, 0)}) 223 got := m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", g.port, 0)})
224 require.Len(t, got, 1) 224 require.Len(t, got, 1)
225 assert.Equal(t, "active", got[0].GetState()) 225 assert.Equal(t, "active", got[0].GetState())
226 226
@@ -235,7 +235,7 @@ func TestConvergeRebindsAListenerWhoseAcceptLoopDied(t *testing.T) {
235 235
236 func TestConnectionClosesWhenTheGuestHasNoAddress(t *testing.T) { 236 func TestConnectionClosesWhenTheGuestHasNoAddress(t *testing.T) {
237 m := newTestManager(t, map[string]string{}) // the guest is still leasing 237 m := newTestManager(t, map[string]string{}) // the guest is still leasing
238 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", 8080, 0)}) 238 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", 8080, 0)})
239 239
240 c, err := net.Dial("tcp", boundPort(t, m, "e1")) 240 c, err := net.Dial("tcp", boundPort(t, m, "e1"))
241 require.NoError(t, err) 241 require.NoError(t, err)
@@ -263,7 +263,7 @@ func TestSpliceIsHalfCloseAware(t *testing.T) {
263 }() 263 }()
264 264
265 m := newTestManager(t, map[string]string{"vm1": "127.0.0.1"}) 265 m := newTestManager(t, map[string]string{"vm1": "127.0.0.1"})
266 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", portOf(t, ln.Addr()), 0)}) 266 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", portOf(t, ln.Addr()), 0)})
267 267
268 c, err := net.Dial("tcp", boundPort(t, m, "e1")) 268 c, err := net.Dial("tcp", boundPort(t, m, "e1"))
269 require.NoError(t, err) 269 require.NoError(t, err)
@@ -298,7 +298,7 @@ func TestRevokeDrainsRatherThanCuts(t *testing.T) {
298 }() 298 }()
299 299
300 m := newTestManager(t, map[string]string{"vm1": "127.0.0.1"}) 300 m := newTestManager(t, map[string]string{"vm1": "127.0.0.1"})
301 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", portOf(t, ln.Addr()), 0)}) 301 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", portOf(t, ln.Addr()), 0)})
302 addr := boundPort(t, m, "e1") 302 addr := boundPort(t, m, "e1")
303 303
304 c, err := net.Dial("tcp", addr) 304 c, err := net.Dial("tcp", addr)
@@ -392,7 +392,7 @@ func TestExposureRefusesConnectionsPastItsCap(t *testing.T) {
392 g := newHoldingGuest(t) 392 g := newHoldingGuest(t)
393 m := newTestManager(t, map[string]string{"vm1": g.addr}) 393 m := newTestManager(t, map[string]string{"vm1": g.addr})
394 m.maxConns = 2 394 m.maxConns = 2
395 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", g.port, 0)}) 395 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", g.port, 0)})
396 addr := boundPort(t, m, "e1") 396 addr := boundPort(t, m, "e1")
397 397
398 first, second := hold(t, addr), hold(t, addr) 398 first, second := hold(t, addr), hold(t, addr)
@@ -415,7 +415,7 @@ func TestTheCapReleasesWhenAConnectionEnds(t *testing.T) {
415 g := newHoldingGuest(t) 415 g := newHoldingGuest(t)
416 m := newTestManager(t, map[string]string{"vm1": g.addr}) 416 m := newTestManager(t, map[string]string{"vm1": g.addr})
417 m.maxConns = 1 417 m.maxConns = 1
418 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", g.port, 0)}) 418 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", g.port, 0)})
419 addr := boundPort(t, m, "e1") 419 addr := boundPort(t, m, "e1")
420 420
421 first := hold(t, addr) 421 first := hold(t, addr)
@@ -431,7 +431,7 @@ func TestTheCapCountsConnectionsDrainingThroughARebind(t *testing.T) {
431 first, second := newHoldingGuest(t), newHoldingGuest(t) 431 first, second := newHoldingGuest(t), newHoldingGuest(t)
432 m := newTestManager(t, map[string]string{"vm1": first.addr}) 432 m := newTestManager(t, map[string]string{"vm1": first.addr})
433 m.maxConns = 1 433 m.maxConns = 1
434 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", first.port, 0)}) 434 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", first.port, 0)})
435 435
436 held := hold(t, boundPort(t, m, "e1")) 436 held := hold(t, boundPort(t, m, "e1"))
437 437
@@ -439,7 +439,7 @@ func TestTheCapCountsConnectionsDrainingThroughARebind(t *testing.T) {
439 // listener is still open and still spending its two descriptors, so it is 439 // listener is still open and still spending its two descriptors, so it is
440 // still what the cap is counting — an exposure that forgot it on the rebind 440 // still what the cap is counting — an exposure that forgot it on the rebind
441 // would let this port hold twice what it is allowed. 441 // would let this port hold twice what it is allowed.
442 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", second.port, 0)}) 442 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", second.port, 0)})
443 waitConns(t, m, "e1", 1) 443 waitConns(t, m, "e1", 1)
444 444
445 over, err := net.Dial("tcp", boundPort(t, m, "e1")) 445 over, err := net.Dial("tcp", boundPort(t, m, "e1"))
@@ -497,7 +497,7 @@ func TestADescriptorShortageIsReportedWhileThePortStaysBound(t *testing.T) {
497 497
498 // The port is still bound, so it is still active — with the streak beside 498 // The port is still bound, so it is still active — with the streak beside
499 // it, which is the whole point: "active" alone would read as healthy. 499 // it, which is the whole point: "active" alone would read as healthy.
500 got := m.Converge([]*pb.ExposureDesired{d}) 500 got := m.Converge([]*pb.ExposureSpec{d})
501 require.Len(t, got, 1) 501 require.Len(t, got, 1)
502 assert.Equal(t, "active", got[0].GetState()) 502 assert.Equal(t, "active", got[0].GetState())
503 assert.Contains(t, got[0].GetReason(), "out of descriptors") 503 assert.Contains(t, got[0].GetReason(), "out of descriptors")
@@ -508,7 +508,7 @@ func TestADescriptorShortageIsReportedWhileThePortStaysBound(t *testing.T) {
508 defer client.Close() 508 defer client.Close()
509 ln.next <- acceptResult{conn: accepted} 509 ln.next <- acceptResult{conn: accepted}
510 waitReason(t, m, "e1", func(r string) bool { return r == "" }, "a recovered port must stop reporting a shortage") 510 waitReason(t, m, "e1", func(r string) bool { return r == "" }, "a recovered port must stop reporting a shortage")
511 got = m.Converge([]*pb.ExposureDesired{d}) 511 got = m.Converge([]*pb.ExposureSpec{d})
512 assert.Equal(t, "active", got[0].GetState()) 512 assert.Equal(t, "active", got[0].GetState())
513 assert.Empty(t, got[0].GetReason()) 513 assert.Empty(t, got[0].GetReason())
514 } 514 }
@@ -580,7 +580,7 @@ func TestAcceptKeepsThePortThroughADescriptorShortage(t *testing.T) {
580 func TestStopAllClosesEveryListener(t *testing.T) { 580 func TestStopAllClosesEveryListener(t *testing.T) {
581 g := newFakeGuest(t) 581 g := newFakeGuest(t)
582 m := NewManager(func(string) string { return g.addr }) 582 m := NewManager(func(string) string { return g.addr })
583 m.Converge([]*pb.ExposureDesired{ 583 m.Converge([]*pb.ExposureSpec{
584 desired("e1", "vm1", g.port, 0), 584 desired("e1", "vm1", g.port, 0),
585 desired("e2", "vm1", g.port, 0), 585 desired("e2", "vm1", g.port, 0),
586 }) 586 })
@@ -597,7 +597,7 @@ func TestStopAllClosesEveryListener(t *testing.T) {
597 // counters converges the same desired set again and returns what the named 597 // counters converges the same desired set again and returns what the named
598 // exposure reports about what it has carried. Converge is level-triggered, so 598 // exposure reports about what it has carried. Converge is level-triggered, so
599 // asking twice is how the report is taken in production too. 599 // asking twice is how the report is taken in production too.
600 func counters(t *testing.T, m *Manager, d []*pb.ExposureDesired, id string) *pb.ExposureSessions { 600 func counters(t *testing.T, m *Manager, d []*pb.ExposureSpec, id string) *pb.ExposureSessions {
601 t.Helper() 601 t.Helper()
602 for _, a := range m.Converge(d) { 602 for _, a := range m.Converge(d) {
603 if a.GetId() == id { 603 if a.GetId() == id {
@@ -616,7 +616,7 @@ func TestExposureCountsWhatItHoldsAndWhatItTurnsAway(t *testing.T) {
616 g := newHoldingGuest(t) 616 g := newHoldingGuest(t)
617 m := newTestManager(t, map[string]string{"vm1": g.addr}) 617 m := newTestManager(t, map[string]string{"vm1": g.addr})
618 m.maxConns = 1 618 m.maxConns = 1
619 spec := []*pb.ExposureDesired{desired("e1", "vm1", g.port, 0)} 619 spec := []*pb.ExposureSpec{desired("e1", "vm1", g.port, 0)}
620 m.Converge(spec) 620 m.Converge(spec)
621 addr := boundPort(t, m, "e1") 621 addr := boundPort(t, m, "e1")
622 622
@@ -653,7 +653,7 @@ func TestExposureCountsWhatItHoldsAndWhatItTurnsAway(t *testing.T) {
653 // one reading "dropped" should look at the guest. 653 // one reading "dropped" should look at the guest.
654 func TestExposureCountsACallerItCannotCarry(t *testing.T) { 654 func TestExposureCountsACallerItCannotCarry(t *testing.T) {
655 m := newTestManager(t, map[string]string{}) // no address for vm1: still leasing 655 m := newTestManager(t, map[string]string{}) // no address for vm1: still leasing
656 spec := []*pb.ExposureDesired{desired("e1", "vm1", 8080, 0)} 656 spec := []*pb.ExposureSpec{desired("e1", "vm1", 8080, 0)}
657 m.Converge(spec) 657 m.Converge(spec)
658 addr := boundPort(t, m, "e1") 658 addr := boundPort(t, m, "e1")
659 659
@@ -680,7 +680,7 @@ func TestAFailedExposureStillReportsItsCounters(t *testing.T) {
680 680
681 m := newTestManager(t, map[string]string{"vm1": "127.0.0.1"}) 681 m := newTestManager(t, map[string]string{"vm1": "127.0.0.1"})
682 port := portOf(t, held.Addr()) 682 port := portOf(t, held.Addr())
683 out := m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", 8080, port)}) 683 out := m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", 8080, port)})
684 require.Len(t, out, 1) 684 require.Len(t, out, 1)
685 require.Equal(t, "failed", out[0].GetState()) 685 require.Equal(t, "failed", out[0].GetState())
686 require.NotNil(t, out[0].GetSessions()) 686 require.NotNil(t, out[0].GetSessions())
internal/agent/exposeproxy/udp_test.go
Old New
@@ -123,7 +123,7 @@ func TestUDPConvergeBindsAndForwardsBothWays(t *testing.T) {
123 g := newFakeUDPGuest(t) 123 g := newFakeUDPGuest(t)
124 m := newTestManager(t, map[string]string{"vm1": g.addr}) 124 m := newTestManager(t, map[string]string{"vm1": g.addr})
125 125
126 got := m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", g.port, 0)}) 126 got := m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", g.port, 0)})
127 require.Len(t, got, 1) 127 require.Len(t, got, 1)
128 assert.Equal(t, "active", got[0].GetState(), "active means the packet socket is bound") 128 assert.Equal(t, "active", got[0].GetState(), "active means the packet socket is bound")
129 129
@@ -135,7 +135,7 @@ func TestUDPConvergeBindsAndForwardsBothWays(t *testing.T) {
135 func TestUDPKeepsDatagramBoundaries(t *testing.T) { 135 func TestUDPKeepsDatagramBoundaries(t *testing.T) {
136 g := newFakeUDPGuest(t) 136 g := newFakeUDPGuest(t)
137 m := newTestManager(t, map[string]string{"vm1": g.addr}) 137 m := newTestManager(t, map[string]string{"vm1": g.addr})
138 m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", g.port, 0)}) 138 m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", g.port, 0)})
139 c := udpClient(t, boundUDPPort(t, m, "e1")) 139 c := udpClient(t, boundUDPPort(t, m, "e1"))
140 140
141 // Two datagrams in, two out, in order and whole — nothing here coalesces a 141 // Two datagrams in, two out, in order and whole — nothing here coalesces a
@@ -152,7 +152,7 @@ func TestUDPKeepsDatagramBoundaries(t *testing.T) {
152 func TestUDPSessionIsPerClientAddress(t *testing.T) { 152 func TestUDPSessionIsPerClientAddress(t *testing.T) {
153 g := newFakeUDPGuest(t) 153 g := newFakeUDPGuest(t)
154 m := newTestManager(t, map[string]string{"vm1": g.addr}) 154 m := newTestManager(t, map[string]string{"vm1": g.addr})
155 m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", g.port, 0)}) 155 m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", g.port, 0)})
156 addr := boundUDPPort(t, m, "e1") 156 addr := boundUDPPort(t, m, "e1")
157 157
158 first, second := udpClient(t, addr), udpClient(t, addr) 158 first, second := udpClient(t, addr), udpClient(t, addr)
@@ -174,7 +174,7 @@ func TestUDPSessionIsPromotedOnlyByAGuestReply(t *testing.T) {
174 quietPort := uint32(silent.LocalAddr().(*net.UDPAddr).Port) 174 quietPort := uint32(silent.LocalAddr().(*net.UDPAddr).Port)
175 175
176 m := newTestManager(t, map[string]string{"vm1": "127.0.0.1"}) 176 m := newTestManager(t, map[string]string{"vm1": "127.0.0.1"})
177 m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", quietPort, 0)}) 177 m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", quietPort, 0)})
178 c := udpClient(t, boundUDPPort(t, m, "e1")) 178 c := udpClient(t, boundUDPPort(t, m, "e1"))
179 _, err = c.Write([]byte("anyone there")) 179 _, err = c.Write([]byte("anyone there"))
180 require.NoError(t, err) 180 require.NoError(t, err)
@@ -186,7 +186,7 @@ func TestUDPSessionIsPromotedOnlyByAGuestReply(t *testing.T) {
186 186
187 // Now a guest that answers. The reply is the promotion. 187 // Now a guest that answers. The reply is the promotion.
188 g := newFakeUDPGuest(t) 188 g := newFakeUDPGuest(t)
189 m.Converge([]*pb.ExposureDesired{desiredUDP("e2", "vm1", g.port, 0)}) 189 m.Converge([]*pb.ExposureSpec{desiredUDP("e2", "vm1", g.port, 0)})
190 talking := udpClient(t, boundUDPPort(t, m, "e2")) 190 talking := udpClient(t, boundUDPPort(t, m, "e2"))
191 require.Equal(t, "echo:hi", say(t, talking, "hi")) 191 require.Equal(t, "echo:hi", say(t, talking, "hi"))
192 192
@@ -206,7 +206,7 @@ func TestUDPSessionExpiresOnBothWindows(t *testing.T) {
206 silent, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)}) 206 silent, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
207 require.NoError(t, err) 207 require.NoError(t, err)
208 t.Cleanup(func() { silent.Close() }) 208 t.Cleanup(func() { silent.Close() })
209 m.Converge([]*pb.ExposureDesired{ 209 m.Converge([]*pb.ExposureSpec{
210 desiredUDP("quiet", "vm1", uint32(silent.LocalAddr().(*net.UDPAddr).Port), 0), 210 desiredUDP("quiet", "vm1", uint32(silent.LocalAddr().(*net.UDPAddr).Port), 0),
211 desiredUDP("live", "vm1", g.port, 0), 211 desiredUDP("live", "vm1", g.port, 0),
212 }) 212 })
@@ -231,7 +231,7 @@ func TestUDPRefusesNewSessionsAtItsCapWithoutEvicting(t *testing.T) {
231 g := newFakeUDPGuest(t) 231 g := newFakeUDPGuest(t)
232 m := newTestManager(t, map[string]string{"vm1": g.addr}) 232 m := newTestManager(t, map[string]string{"vm1": g.addr})
233 m.maxSessions = 1 233 m.maxSessions = 1
234 m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", g.port, 0)}) 234 m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", g.port, 0)})
235 addr := boundUDPPort(t, m, "e1") 235 addr := boundUDPPort(t, m, "e1")
236 236
237 held := udpClient(t, addr) 237 held := udpClient(t, addr)
@@ -262,13 +262,13 @@ func TestUDPConvergeEvictsASessionWhoseGuestMoved(t *testing.T) {
262 // Both fakes answer on 127.0.0.1, so the guest PORT is what tells them 262 // Both fakes answer on 127.0.0.1, so the guest PORT is what tells them
263 // apart; the pin under test is the address, so move the VM to an address 263 // apart; the pin under test is the address, so move the VM to an address
264 // nothing is at and prove the session goes. 264 // nothing is at and prove the session goes.
265 m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", first.port, 0)}) 265 m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", first.port, 0)})
266 c := udpClient(t, boundUDPPort(t, m, "e1")) 266 c := udpClient(t, boundUDPPort(t, m, "e1"))
267 require.Equal(t, "echo:before", say(t, c, "before")) 267 require.Equal(t, "echo:before", say(t, c, "before"))
268 s := oneSession(t, m, "e1") 268 s := oneSession(t, m, "e1")
269 269
270 where["vm1"] = "127.0.0.2" 270 where["vm1"] = "127.0.0.2"
271 m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", first.port, 0)}) 271 m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", first.port, 0)})
272 assert.Equal(t, 0, sessions(t, m, "e1"), "a pin that no longer matches is not a session to keep") 272 assert.Equal(t, 0, sessions(t, m, "e1"), "a pin that no longer matches is not a session to keep")
273 _, err := s.guest.Write([]byte("orphan")) 273 _, err := s.guest.Write([]byte("orphan"))
274 assert.Error(t, err, "the evicted session's socket is closed, not leaked") 274 assert.Error(t, err, "the evicted session's socket is closed, not leaked")
@@ -276,17 +276,17 @@ func TestUDPConvergeEvictsASessionWhoseGuestMoved(t *testing.T) {
276 // A guest the host has lost track of entirely is no different: there is 276 // A guest the host has lost track of entirely is no different: there is
277 // nowhere to send, so there is no session. 277 // nowhere to send, so there is no session.
278 where["vm1"] = second.addr 278 where["vm1"] = second.addr
279 m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", second.port, 0)}) 279 m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", second.port, 0)})
280 require.Equal(t, "echo:after", say(t, udpClient(t, boundUDPPort(t, m, "e1")), "after")) 280 require.Equal(t, "echo:after", say(t, udpClient(t, boundUDPPort(t, m, "e1")), "after"))
281 where["vm1"] = "" 281 where["vm1"] = ""
282 m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", second.port, 0)}) 282 m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", second.port, 0)})
283 assert.Equal(t, 0, sessions(t, m, "e1"), "no address is not an address to keep sending to") 283 assert.Equal(t, 0, sessions(t, m, "e1"), "no address is not an address to keep sending to")
284 } 284 }
285 285
286 func TestUDPSessionEndsWhenItsGuestSocketFails(t *testing.T) { 286 func TestUDPSessionEndsWhenItsGuestSocketFails(t *testing.T) {
287 g := newFakeUDPGuest(t) 287 g := newFakeUDPGuest(t)
288 m := newTestManager(t, map[string]string{"vm1": g.addr}) 288 m := newTestManager(t, map[string]string{"vm1": g.addr})
289 m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", g.port, 0)}) 289 m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", g.port, 0)})
290 c := udpClient(t, boundUDPPort(t, m, "e1")) 290 c := udpClient(t, boundUDPPort(t, m, "e1"))
291 require.Equal(t, "echo:up", say(t, c, "up")) 291 require.Equal(t, "echo:up", say(t, c, "up"))
292 292
@@ -304,7 +304,7 @@ func TestUDPSessionEndsWhenItsGuestSocketFails(t *testing.T) {
304 304
305 func TestUDPDropsADatagramForAGuestWithNoAddress(t *testing.T) { 305 func TestUDPDropsADatagramForAGuestWithNoAddress(t *testing.T) {
306 m := newTestManager(t, map[string]string{}) // the guest is still leasing 306 m := newTestManager(t, map[string]string{}) // the guest is still leasing
307 m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", 8080, 0)}) 307 m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", 8080, 0)})
308 308
309 c := udpClient(t, boundUDPPort(t, m, "e1")) 309 c := udpClient(t, boundUDPPort(t, m, "e1"))
310 _, err := c.Write([]byte("anyone")) 310 _, err := c.Write([]byte("anyone"))
@@ -324,7 +324,7 @@ func TestUDPConvergeReportsABindFailure(t *testing.T) {
324 require.NoError(t, err) 324 require.NoError(t, err)
325 held := uint32(squatter.LocalAddr().(*net.UDPAddr).Port) 325 held := uint32(squatter.LocalAddr().(*net.UDPAddr).Port)
326 326
327 got := m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", g.port, held)}) 327 got := m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", g.port, held)})
328 require.Len(t, got, 1) 328 require.Len(t, got, 1)
329 assert.Equal(t, "failed", got[0].GetState()) 329 assert.Equal(t, "failed", got[0].GetState())
330 assert.NotEmpty(t, got[0].GetReason(), "the report carries what the OS said") 330 assert.NotEmpty(t, got[0].GetReason(), "the report carries what the OS said")
@@ -332,7 +332,7 @@ func TestUDPConvergeReportsABindFailure(t *testing.T) {
332 require.NoError(t, squatter.Close()) 332 require.NoError(t, squatter.Close())
333 var state string 333 var state string
334 for i := 0; i < 20; i++ { 334 for i := 0; i < 20; i++ {
335 got = m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", g.port, held)}) 335 got = m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", g.port, held)})
336 state = got[0].GetState() 336 state = got[0].GetState()
337 if state == "active" { 337 if state == "active" {
338 break 338 break
@@ -347,10 +347,10 @@ func TestChangingTheProtocolRebinds(t *testing.T) {
347 udpGuest := newFakeUDPGuest(t) 347 udpGuest := newFakeUDPGuest(t)
348 m := newTestManager(t, map[string]string{"vm1": tcpGuest.addr}) 348 m := newTestManager(t, map[string]string{"vm1": tcpGuest.addr})
349 349
350 m.Converge([]*pb.ExposureDesired{desired("e1", "vm1", tcpGuest.port, 0)}) 350 m.Converge([]*pb.ExposureSpec{desired("e1", "vm1", tcpGuest.port, 0)})
351 assert.Equal(t, "echo:tcp", speak(t, boundPort(t, m, "e1"), "tcp")) 351 assert.Equal(t, "echo:tcp", speak(t, boundPort(t, m, "e1"), "tcp"))
352 352
353 m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", udpGuest.port, 0)}) 353 m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", udpGuest.port, 0)})
354 m.mu.Lock() 354 m.mu.Lock()
355 ex := m.live["e1"] 355 ex := m.live["e1"]
356 m.mu.Unlock() 356 m.mu.Unlock()
@@ -361,7 +361,7 @@ func TestChangingTheProtocolRebinds(t *testing.T) {
361 func TestStopAllClosesPacketSocketsToo(t *testing.T) { 361 func TestStopAllClosesPacketSocketsToo(t *testing.T) {
362 g := newFakeUDPGuest(t) 362 g := newFakeUDPGuest(t)
363 m := NewManager(func(string) string { return g.addr }) 363 m := NewManager(func(string) string { return g.addr })
364 m.Converge([]*pb.ExposureDesired{desiredUDP("e1", "vm1", g.port, 0)}) 364 m.Converge([]*pb.ExposureSpec{desiredUDP("e1", "vm1", g.port, 0)})
365 addr := boundUDPPort(t, m, "e1") 365 addr := boundUDPPort(t, m, "e1")
366 c := udpClient(t, addr) 366 c := udpClient(t, addr)
367 require.Equal(t, "echo:live", say(t, c, "live")) 367 require.Equal(t, "echo:live", say(t, c, "live"))
@@ -393,7 +393,7 @@ func TestUDPCountsItsSessionsAndRefusals(t *testing.T) {
393 g := newFakeUDPGuest(t) 393 g := newFakeUDPGuest(t)
394 m := newTestManager(t, map[string]string{"vm1": g.addr}) 394 m := newTestManager(t, map[string]string{"vm1": g.addr})
395 m.maxSessions = 1 395 m.maxSessions = 1
396 spec := []*pb.ExposureDesired{desiredUDP("e1", "vm1", g.port, 0)} 396 spec := []*pb.ExposureSpec{desiredUDP("e1", "vm1", g.port, 0)}
397 m.Converge(spec) 397 m.Converge(spec)
398 addr := boundUDPPort(t, m, "e1") 398 addr := boundUDPPort(t, m, "e1")
399 399
@@ -421,7 +421,7 @@ func TestUDPCountsItsSessionsAndRefusals(t *testing.T) {
421 // port at its cap, and the two must not read the same in a report. 421 // port at its cap, and the two must not read the same in a report.
422 func TestUDPCountsADatagramForAGuestWithNoAddress(t *testing.T) { 422 func TestUDPCountsADatagramForAGuestWithNoAddress(t *testing.T) {
423 m := newTestManager(t, map[string]string{}) // vm1 has no address yet 423 m := newTestManager(t, map[string]string{}) // vm1 has no address yet
424 spec := []*pb.ExposureDesired{desiredUDP("e1", "vm1", 9999, 0)} 424 spec := []*pb.ExposureSpec{desiredUDP("e1", "vm1", 9999, 0)}
425 m.Converge(spec) 425 m.Converge(spec)
426 addr := boundUDPPort(t, m, "e1") 426 addr := boundUDPPort(t, m, "e1")
427 427
internal/agent/reconcile/hostkey_test.go
Old New
@@ -14,11 +14,11 @@ import (
14 ) 14 )
15 15
16 // needsHostCert marks a desired VM the way a fleet with an SSH CA does. 16 // needsHostCert marks a desired VM the way a fleet with an SSH CA does.
17 func needsHostCert(v *pb.VMDesired) { v.HostCertRequired = true } 17 func needsHostCert(v *pb.VMSpec) { v.HostCertRequired = true }
18 18
19 // withHostCert supplies the certificate the control plane signed. 19 // withHostCert supplies the certificate the control plane signed.
20 func withHostCert(cert string) func(*pb.VMDesired) { 20 func withHostCert(cert string) func(*pb.VMSpec) {
21 return func(v *pb.VMDesired) { v.HostCertRequired = true; v.SshHostCert = cert } 21 return func(v *pb.VMSpec) { v.HostCertRequired = true; v.SshHostCert = cert }
22 } 22 }
23 23
24 // TestAwaitingHostCertDoesNotSpendRetryBudget is the regression that matters 24 // TestAwaitingHostCertDoesNotSpendRetryBudget is the regression that matters
internal/agent/reconcile/quota_test.go
Old New
@@ -9,8 +9,8 @@ import (
9 ) 9 )
10 10
11 // withRes overrides a desired VM's resource request. 11 // withRes overrides a desired VM's resource request.
12 func withRes(vcpus, memMB, diskGB int64) func(*pb.VMDesired) { 12 func withRes(vcpus, memMB, diskGB int64) func(*pb.VMSpec) {
13 return func(v *pb.VMDesired) { v.Vcpus = vcpus; v.MemMb = memMB; v.DiskGb = diskGB } 13 return func(v *pb.VMSpec) { v.Vcpus = vcpus; v.MemMb = memMB; v.DiskGb = diskGB }
14 } 14 }
15 15
16 func TestQuotaUnderCapBoots(t *testing.T) { 16 func TestQuotaUnderCapBoots(t *testing.T) {
@@ -93,7 +93,7 @@ func TestQuotaFreedByQuarantineEventuallyBootsTheWaitingVM(t *testing.T) {
93 // Two ticks suffice deterministically: whatever the map order, tick 1 reaps 93 // Two ticks suffice deterministically: whatever the map order, tick 1 reaps
94 // vm1 and releases its compute, so tick 2 always admits vm2. The third is 94 // vm1 and releases its compute, so tick 2 always admits vm2. The third is
95 // slack, not a flake bound. 95 // slack, not a flake bound.
96 var rep *pb.ActualStateReport 96 var rep *pb.Report
97 for range 3 { 97 for range 3 {
98 rep = f.step(snap(2, 98 rep = f.step(snap(2,
99 tombstoned(vm("vm1", withRes(2, 512, 5))), 99 tombstoned(vm("vm1", withRes(2, 512, 5))),
internal/agent/reconcile/reconcile.go
Old New
@@ -219,12 +219,12 @@ type Engine struct {
219 // explicit because the two carry different grace periods (VanishGrace vs the 219 // explicit because the two carry different grace periods (VanishGrace vs the
220 // shorter TombstoneGrace). 220 // shorter TombstoneGrace).
221 // 221 //
222 // LIFETIME: desired points into the caller's DesiredStateSnapshot, and a worker 222 // LIFETIME: desired points into the caller's Snapshot, and a worker
223 // holds that pointer well past the Step that delivered it — for as long as its 223 // holds that pointer well past the Step that delivered it — for as long as its
224 // pass runs, up to VMTimeout. A snapshot handed to Step must therefore be 224 // pass runs, up to VMTimeout. A snapshot handed to Step must therefore be
225 // treated as immutable while any pass may still be running. 225 // treated as immutable while any pass may still be running.
226 type assignment struct { 226 type assignment struct {
227 desired *pb.VMDesired 227 desired *pb.VMSpec
228 tombstoned bool 228 tombstoned bool
229 } 229 }
230 230
@@ -232,7 +232,7 @@ type assignment struct {
232 // desired state and local records: a desired-only id is a create, a record-only 232 // desired state and local records: a desired-only id is a create, a record-only
233 // id is a vanished VM to reap, and an id in both converges. The union is also 233 // id is a vanished VM to reap, and an id in both converges. The union is also
234 // exactly the set of VMs that need a reconcile pass this tick. 234 // exactly the set of VMs that need a reconcile pass this tick.
235 func assignments(snap *pb.DesiredStateSnapshot, recs map[string]state.Record) map[string]assignment { 235 func assignments(snap *pb.Snapshot, recs map[string]state.Record) map[string]assignment {
236 out := make(map[string]assignment, len(snap.Vms)+len(recs)) 236 out := make(map[string]assignment, len(snap.Vms)+len(recs))
237 for _, d := range snap.Vms { 237 for _, d := range snap.Vms {
238 out[d.VmId] = assignment{desired: d, tombstoned: d.Tombstoned} 238 out[d.VmId] = assignment{desired: d, tombstoned: d.Tombstoned}
@@ -247,7 +247,7 @@ func assignments(snap *pb.DesiredStateSnapshot, recs map[string]state.Record) ma
247 247
248 // tombstonedSet returns the ids the control plane has flagged for deletion. 248 // tombstonedSet returns the ids the control plane has flagged for deletion.
249 // The destroy ack is level-triggered from it (see ackDestroyed). 249 // The destroy ack is level-triggered from it (see ackDestroyed).
250 func tombstonedSet(snap *pb.DesiredStateSnapshot) map[string]bool { 250 func tombstonedSet(snap *pb.Snapshot) map[string]bool {
251 out := make(map[string]bool, len(snap.Vms)) 251 out := make(map[string]bool, len(snap.Vms))
252 for _, d := range snap.Vms { 252 for _, d := range snap.Vms {
253 if d.Tombstoned { 253 if d.Tombstoned {
@@ -258,7 +258,7 @@ func tombstonedSet(snap *pb.DesiredStateSnapshot) map[string]bool {
258 } 258 }
259 259
260 // Step routes one desired-state snapshot to the per-VM workers and returns the 260 // Step routes one desired-state snapshot to the per-VM workers and returns the
261 // host's ActualStateReport. It never waits for a worker: the report carries each 261 // host's Report. It never waits for a worker: the report carries each
262 // VM's LAST-PUBLISHED state, so a VM busy in a multi-second operation cannot 262 // VM's LAST-PUBLISHED state, so a VM busy in a multi-second operation cannot
263 // delay the heartbeat. A VM that has not published yet simply has no row. 263 // delay the heartbeat. A VM that has not published yet simply has no row.
264 // 264 //
@@ -267,7 +267,7 @@ func tombstonedSet(snap *pb.DesiredStateSnapshot) map[string]bool {
267 // 267 //
268 // ctx is accepted for signature stability and bounds nothing here — the work 268 // ctx is accepted for signature stability and bounds nothing here — the work
269 // happens in workers, where VMTimeout bounds each VM's pass. 269 // happens in workers, where VMTimeout bounds each VM's pass.
270 func (e *Engine) Step(ctx context.Context, snap *pb.DesiredStateSnapshot) *pb.ActualStateReport { 270 func (e *Engine) Step(ctx context.Context, snap *pb.Snapshot) *pb.Report {
271 // ── 1. Epoch fence ─────────────────────────────────────────────────────── 271 // ── 1. Epoch fence ───────────────────────────────────────────────────────
272 // CRITICAL: the fence path must touch NOTHING: no SaveEpoch, no provisioner 272 // CRITICAL: the fence path must touch NOTHING: no SaveEpoch, no provisioner
273 // calls, no dispatch, no state mutations. It returns the current actual state 273 // calls, no dispatch, no state mutations. It returns the current actual state
@@ -304,7 +304,7 @@ func (e *Engine) Step(ctx context.Context, snap *pb.DesiredStateSnapshot) *pb.Ac
304 // reap frees compute may run after a sibling's admission, so the sibling is 304 // reap frees compute may run after a sibling's admission, so the sibling is
305 // quota-refused and boots on a later tick. The refusal is non-terminal and the 305 // quota-refused and boots on a later tick. The refusal is non-terminal and the
306 // loop is level-triggered, so the guarantee is eventual rather than same-tick. 306 // loop is level-triggered, so the guarantee is eventual rather than same-tick.
307 func (e *Engine) dispatch(snap *pb.DesiredStateSnapshot, recs map[string]state.Record) { 307 func (e *Engine) dispatch(snap *pb.Snapshot, recs map[string]state.Record) {
308 live := assignments(snap, recs) 308 live := assignments(snap, recs)
309 309
310 m := e.manager() 310 m := e.manager()
@@ -327,8 +327,8 @@ func (e *Engine) dispatch(snap *pb.DesiredStateSnapshot, recs map[string]state.R
327 // 327 //
328 // recs is the record view the destroy ack is computed against, with the caller 328 // recs is the record view the destroy ack is computed against, with the caller
329 // choosing how fresh it is (see ackDestroyed). 329 // choosing how fresh it is (see ackDestroyed).
330 func (e *Engine) aggregate(epoch uint64, recs map[string]state.Record) *pb.ActualStateReport { 330 func (e *Engine) aggregate(epoch uint64, recs map[string]state.Record) *pb.Report {
331 rep := &pb.ActualStateReport{LastSeenEpoch: epoch} 331 rep := &pb.Report{LastSeenEpoch: epoch}
332 m := e.manager() 332 m := e.manager()
333 m.collect(rep) 333 m.collect(rep)
334 e.ackDestroyed(rep, m.tombstones(), recs) 334 e.ackDestroyed(rep, m.tombstones(), recs)
@@ -338,8 +338,8 @@ func (e *Engine) aggregate(epoch uint64, recs map[string]state.Record) *pb.Actua
338 // fenceReport is the read-only report returned for a stale snapshot: current 338 // fenceReport is the read-only report returned for a stale snapshot: current
339 // actual state, derived entirely from persisted records, with no mutation and 339 // actual state, derived entirely from persisted records, with no mutation and
340 // no dispatch. 340 // no dispatch.
341 func (e *Engine) fenceReport(currentEpoch uint64) *pb.ActualStateReport { 341 func (e *Engine) fenceReport(currentEpoch uint64) *pb.Report {
342 rep := &pb.ActualStateReport{FenceViolation: true, LastSeenEpoch: currentEpoch} 342 rep := &pb.Report{FenceViolation: true, LastSeenEpoch: currentEpoch}
343 recs, err := e.St.LoadVMs() 343 recs, err := e.St.LoadVMs()
344 if err != nil { 344 if err != nil {
345 return rep 345 return rep
@@ -447,7 +447,7 @@ func (e *Engine) reconcileOne(ctx context.Context, id string, a assignment, pub
447 // Step passes the view it dispatched with, read before this tick's destroying 447 // Step passes the view it dispatched with, read before this tick's destroying
448 // passes ran, so the ack lands one or more ticks AFTER the pass that removed the 448 // passes ran, so the ack lands one or more ticks AFTER the pass that removed the
449 // record, not in the same tick. Being level-triggered is what makes that fine. 449 // record, not in the same tick. Being level-triggered is what makes that fine.
450 func (e *Engine) ackDestroyed(rep *pb.ActualStateReport, tombstoned map[string]bool, recs map[string]state.Record) { 450 func (e *Engine) ackDestroyed(rep *pb.Report, tombstoned map[string]bool, recs map[string]state.Record) {
451 for id := range tombstoned { 451 for id := range tombstoned {
452 if _, hasRecord := recs[id]; !hasRecord { 452 if _, hasRecord := recs[id]; !hasRecord {
453 rep.Destroyed = append(rep.Destroyed, id) 453 rep.Destroyed = append(rep.Destroyed, id)
@@ -522,7 +522,7 @@ func (e *Engine) reapVM(ctx context.Context, id string, rec state.Record, isTomb
522 // PrepareRootDisk but before boot leaves a disk on a still-empty BootID. Treating 522 // PrepareRootDisk but before boot leaves a disk on a still-empty BootID. Treating
523 // that disk as "exists" would divert the retry to converge(), which never 523 // that disk as "exists" would divert the retry to converge(), which never
524 // rebuilds the disk or seed, and the VM would never recover. 524 // rebuilds the disk or seed, and the VM would never recover.
525 func (e *Engine) reconcileVM(ctx context.Context, d *pb.VMDesired, rec state.Record, ok bool, res *vmResult, pub publisher) { 525 func (e *Engine) reconcileVM(ctx context.Context, d *pb.VMSpec, rec state.Record, ok bool, res *vmResult, pub publisher) {
526 if !ok || rec.BootID == "" { 526 if !ok || rec.BootID == "" {
527 e.create(ctx, d, rec, ok, res, pub) 527 e.create(ctx, d, rec, ok, res, pub)
528 return 528 return
@@ -673,7 +673,7 @@ func (e *Engine) quotaCheckLocked(vmID string, spec state.VMSpec) string {
673 // prior record (retry budget, last known address) and ok reports whether one 673 // prior record (retry budget, last known address) and ok reports whether one
674 // exists. Quota comes from the serialized admission ledger (see admit); the 674 // exists. Quota comes from the serialized admission ledger (see admit); the
675 // address comes from the backend, at boot. 675 // address comes from the backend, at boot.
676 func (e *Engine) create(ctx context.Context, d *pb.VMDesired, rec state.Record, ok bool, res *vmResult, pub publisher) { 676 func (e *Engine) create(ctx context.Context, d *pb.VMSpec, rec state.Record, ok bool, res *vmResult, pub publisher) {
677 // Defensive: never start an attempt (which would burn retry budget) on a 677 // Defensive: never start an attempt (which would burn retry budget) on a
678 // context that is already dead. UNREACHABLE today — a pass context is a 678 // context that is already dead. UNREACHABLE today — a pass context is a
679 // fresh context.Background plus VMTimeout (see worker.run), so it cannot 679 // fresh context.Background plus VMTimeout (see worker.run), so it cannot
@@ -685,7 +685,7 @@ func (e *Engine) create(ctx context.Context, d *pb.VMDesired, rec state.Record,
685 return 685 return
686 } 686 }
687 687
688 spec := specFromDesired(d) 688 spec := specFromWire(d)
689 689
690 // Fix 2: if the desired spec differs from the stored spec, the user edited the 690 // Fix 2: if the desired spec differs from the stored spec, the user edited the
691 // VM definition. Reset CreateAttempts so the new spec gets a fresh retry budget 691 // VM definition. Reset CreateAttempts so the new spec gets a fresh retry budget
@@ -934,7 +934,7 @@ func (e *Engine) failConverge(rec state.Record, err error, res *vmResult) {
934 934
935 // converge drives an existing VM toward its desired power state, 935 // converge drives an existing VM toward its desired power state,
936 // handling lost detection and restart logic. 936 // handling lost detection and restart logic.
937 func (e *Engine) converge(ctx context.Context, d *pb.VMDesired, rec state.Record, res *vmResult) { 937 func (e *Engine) converge(ctx context.Context, d *pb.VMSpec, rec state.Record, res *vmResult) {
938 // Keep the ledger reflecting this live VM (covers the un-delete case, where a 938 // Keep the ledger reflecting this live VM (covers the un-delete case, where a
939 // quarantined VM returns to desired after its compute was released). 939 // quarantined VM returns to desired after its compute was released).
940 e.note(d.VmId, rec.Spec) 940 e.note(d.VmId, rec.Spec)
@@ -1040,7 +1040,7 @@ func quarantinedEntry(rec state.Record, grace time.Duration) *pb.QuarantinedVM {
1040 // appending straight into the shared report, so a single VM's output stands on 1040 // appending straight into the shared report, so a single VM's output stands on
1041 // its own — which is what lets a per-VM worker publish it independently. 1041 // its own — which is what lets a per-VM worker publish it independently.
1042 type vmResult struct { 1042 type vmResult struct {
1043 vm *pb.ActualVM 1043 vm *pb.VMStatus
1044 quarantined *pb.QuarantinedVM 1044 quarantined *pb.QuarantinedVM
1045 // hostPubKey rides every row this VM contributes rather than being passed 1045 // hostPubKey rides every row this VM contributes rather than being passed
1046 // to report at each of its call sites. The public key is level-triggered — 1046 // to report at each of its call sites. The public key is level-triggered —
@@ -1066,7 +1066,7 @@ func recAddrs(rec state.Record) addrs { return addrs{ip: rec.IP, networkIP: rec.
1066 // report records this VM's actual row. A VM contributes at most one row, so a 1066 // report records this VM's actual row. A VM contributes at most one row, so a
1067 // later call in the same reconcile replaces an earlier one. 1067 // later call in the same reconcile replaces an earlier one.
1068 func (r *vmResult) report(vmID string, at addrs, power, phase, lastError string) { 1068 func (r *vmResult) report(vmID string, at addrs, power, phase, lastError string) {
1069 r.vm = newActualVM(vmID, at, power, phase, lastError) 1069 r.vm = newVMStatus(vmID, at, power, phase, lastError)
1070 } 1070 }
1071 1071
1072 // clone returns a deep copy, so the caller's report owns its rows outright. 1072 // clone returns a deep copy, so the caller's report owns its rows outright.
@@ -1076,7 +1076,7 @@ func (r *vmResult) report(vmID string, at addrs, power, phase, lastError string)
1076 func (r vmResult) clone() vmResult { 1076 func (r vmResult) clone() vmResult {
1077 out := r 1077 out := r
1078 if r.vm != nil { 1078 if r.vm != nil {
1079 out.vm = proto.Clone(r.vm).(*pb.ActualVM) 1079 out.vm = proto.Clone(r.vm).(*pb.VMStatus)
1080 } 1080 }
1081 if r.quarantined != nil { 1081 if r.quarantined != nil {
1082 out.quarantined = proto.Clone(r.quarantined).(*pb.QuarantinedVM) 1082 out.quarantined = proto.Clone(r.quarantined).(*pb.QuarantinedVM)
@@ -1085,7 +1085,7 @@ func (r vmResult) clone() vmResult {
1085 } 1085 }
1086 1086
1087 // merge folds this VM's result into the host report. 1087 // merge folds this VM's result into the host report.
1088 func (r *vmResult) merge(rep *pb.ActualStateReport) { 1088 func (r *vmResult) merge(rep *pb.Report) {
1089 if r.vm != nil { 1089 if r.vm != nil {
1090 r.vm.SshHostPubkey = r.hostPubKey 1090 r.vm.SshHostPubkey = r.hostPubKey
1091 rep.Vms = append(rep.Vms, r.vm) 1091 rep.Vms = append(rep.Vms, r.vm)
@@ -1120,17 +1120,17 @@ func byteScale(n int64) (float64, string) {
1120 return 1 << 20, "MiB" 1120 return 1 << 20, "MiB"
1121 } 1121 }
1122 1122
1123 // newActualVM builds one ActualVM row from what a reconcile pass observed. 1123 // newVMStatus builds one VMStatus row from what a reconcile pass observed.
1124 // The row's remaining field, ssh_host_pubkey, is stamped by merge — see 1124 // The row's remaining field, ssh_host_pubkey, is stamped by merge — see
1125 // vmResult.hostPubKey. Unset values are the proto zero-value "". 1125 // vmResult.hostPubKey. Unset values are the proto zero-value "".
1126 func newActualVM(vmID string, at addrs, power, phase, lastError string) *pb.ActualVM { 1126 func newVMStatus(vmID string, at addrs, power, phase, lastError string) *pb.VMStatus {
1127 return &pb.ActualVM{ 1127 return &pb.VMStatus{
1128 VmId: vmID, 1128 VmId: vmID,
1129 Ip: at.ip, 1129 Ip: at.ip,
1130 NetworkIp: at.networkIP, 1130 NetworkIp: at.networkIP,
1131 Power: power, 1131 PowerState: power,
1132 Phase: phase, 1132 Phase: phase,
1133 LastError: lastError, 1133 LastError: lastError,
1134 } 1134 }
1135 } 1135 }
1136 1136
@@ -1155,8 +1155,8 @@ func netMACIfNetworked(spec state.VMSpec) string {
1155 return state.NetMAC(spec.VMID) 1155 return state.NetMAC(spec.VMID)
1156 } 1156 }
1157 1157
1158 // specFromDesired maps a pb.VMDesired to state.VMSpec. 1158 // specFromWire maps a pb.VMSpec to state.VMSpec.
1159 func specFromDesired(d *pb.VMDesired) state.VMSpec { 1159 func specFromWire(d *pb.VMSpec) state.VMSpec {
1160 return state.VMSpec{ 1160 return state.VMSpec{
1161 VMID: d.VmId, 1161 VMID: d.VmId,
1162 Name: d.Name, 1162 Name: d.Name,
internal/agent/reconcile/reconcile_test.go
Old New
@@ -247,7 +247,7 @@ func (f *fixture) restart(t *testing.T) {
247 // blocking on workers is the point — but a test has to observe a tick before it 247 // blocking on workers is the point — but a test has to observe a tick before it
248 // can assert on it. A fenced snapshot dispatches nothing, so its report is 248 // can assert on it. A fenced snapshot dispatches nothing, so its report is
249 // returned as-is. 249 // returned as-is.
250 func (f *fixture) step(s *pb.DesiredStateSnapshot) *pb.ActualStateReport { 250 func (f *fixture) step(s *pb.Snapshot) *pb.Report {
251 rep := f.eng.Step(context.Background(), s) 251 rep := f.eng.Step(context.Background(), s)
252 if rep.FenceViolation { 252 if rep.FenceViolation {
253 return rep 253 return rep
@@ -259,17 +259,17 @@ func (f *fixture) step(s *pb.DesiredStateSnapshot) *pb.ActualStateReport {
259 // aggregateNow re-reads records and builds the report for epoch, the way Step 259 // aggregateNow re-reads records and builds the report for epoch, the way Step
260 // does after its dispatch. Tests call it after waitIdle so the ack sees records 260 // does after its dispatch. Tests call it after waitIdle so the ack sees records
261 // the just-finished passes have already changed. 261 // the just-finished passes have already changed.
262 func (f *fixture) aggregateNow(epoch uint64) *pb.ActualStateReport { 262 func (f *fixture) aggregateNow(epoch uint64) *pb.Report {
263 recs, _ := f.st.LoadVMs() 263 recs, _ := f.st.LoadVMs()
264 return f.eng.aggregate(epoch, recs) 264 return f.eng.aggregate(epoch, recs)
265 } 265 }
266 266
267 func snap(epoch uint64, vms ...*pb.VMDesired) *pb.DesiredStateSnapshot { 267 func snap(epoch uint64, vms ...*pb.VMSpec) *pb.Snapshot {
268 return &pb.DesiredStateSnapshot{Epoch: epoch, Vms: vms} 268 return &pb.Snapshot{Epoch: epoch, Vms: vms}
269 } 269 }
270 270
271 func vm(id string, opts ...func(*pb.VMDesired)) *pb.VMDesired { 271 func vm(id string, opts ...func(*pb.VMSpec)) *pb.VMSpec {
272 v := &pb.VMDesired{VmId: id, Name: "vm-" + id, ImageUrl: "http://x/i.img", 272 v := &pb.VMSpec{VmId: id, Name: "vm-" + id, ImageUrl: "http://x/i.img",
273 ImageSha256: "abc", Vcpus: 1, MemMb: 512, DiskGb: 5, PowerState: "running"} 273 ImageSha256: "abc", Vcpus: 1, MemMb: 512, DiskGb: 5, PowerState: "running"}
274 for _, o := range opts { 274 for _, o := range opts {
275 o(v) 275 o(v)
@@ -277,10 +277,10 @@ func vm(id string, opts ...func(*pb.VMDesired)) *pb.VMDesired {
277 return v 277 return v
278 } 278 }
279 279
280 func tombstoned(v *pb.VMDesired) *pb.VMDesired { v.Tombstoned = true; return v } 280 func tombstoned(v *pb.VMSpec) *pb.VMSpec { v.Tombstoned = true; return v }
281 func stopped(v *pb.VMDesired) { v.PowerState = "stopped" } 281 func stopped(v *pb.VMSpec) { v.PowerState = "stopped" }
282 282
283 func findVM(rep *pb.ActualStateReport, id string) *pb.ActualVM { 283 func findVM(rep *pb.Report, id string) *pb.VMStatus {
284 for _, v := range rep.Vms { 284 for _, v := range rep.Vms {
285 if v.VmId == id { 285 if v.VmId == id {
286 return v 286 return v
@@ -299,7 +299,7 @@ func TestCreateAllocatesIPPreparesAndBoots(t *testing.T) {
299 av := findVM(rep, "vm1") 299 av := findVM(rep, "vm1")
300 require.NotNil(t, av) 300 require.NotNil(t, av)
301 assert.Equal(t, "10.77.1.2", av.Ip, ".1 is the gateway") 301 assert.Equal(t, "10.77.1.2", av.Ip, ".1 is the gateway")
302 assert.Equal(t, "running", av.Power) 302 assert.Equal(t, "running", av.PowerState)
303 assert.Equal(t, "ready", av.Phase) 303 assert.Equal(t, "ready", av.Phase)
304 assert.Equal(t, uint64(1), rep.LastSeenEpoch) 304 assert.Equal(t, uint64(1), rep.LastSeenEpoch)
305 } 305 }
@@ -387,7 +387,7 @@ func TestSameTickSiblingCountsFailedCreateAgainstQuota(t *testing.T) {
387 f := setup(t) 387 f := setup(t)
388 f.eng.MaxVCPUs = 3 388 f.eng.MaxVCPUs = 3
389 f.prov.bootErr = assert.AnError 389 f.prov.bootErr = assert.AnError
390 twoVCPU := func(v *pb.VMDesired) { v.Vcpus = 2 } 390 twoVCPU := func(v *pb.VMSpec) { v.Vcpus = 2 }
391 391
392 rep := f.step(snap(1, vm("vm1", twoVCPU), vm("vm2", twoVCPU))) 392 rep := f.step(snap(1, vm("vm1", twoVCPU), vm("vm2", twoVCPU)))
393 393
@@ -433,7 +433,7 @@ func TestUserStopIsStoppedNotLost(t *testing.T) {
433 433
434 rep := f.step(snap(2, vm("vm1", stopped))) 434 rep := f.step(snap(2, vm("vm1", stopped)))
435 av := findVM(rep, "vm1") 435 av := findVM(rep, "vm1")
436 assert.Equal(t, "stopped", av.Power) 436 assert.Equal(t, "stopped", av.PowerState)
437 assert.NotEqual(t, "failed", av.Phase, "recorded stop request: stopped != lost") 437 assert.NotEqual(t, "failed", av.Phase, "recorded stop request: stopped != lost")
438 } 438 }
439 439
@@ -640,7 +640,7 @@ func TestVMTimeoutBoundsSlowOperations(t *testing.T) {
640 return "", ctx.Err() 640 return "", ctx.Err()
641 } 641 }
642 642
643 done := make(chan *pb.ActualStateReport, 1) 643 done := make(chan *pb.Report, 1)
644 go func() { done <- f.step(snap(1, vm("vm1"))) }() 644 go func() { done <- f.step(snap(1, vm("vm1"))) }()
645 645
646 select { 646 select {
@@ -830,7 +830,7 @@ func TestAddressDiscoveredAfterBootIsPersisted(t *testing.T) {
830 require.NotNil(t, av) 830 require.NotNil(t, av)
831 require.Equal(t, []string{"vm1"}, f.prov.booted) 831 require.Equal(t, []string{"vm1"}, f.prov.booted)
832 assert.Empty(t, av.Ip, "the guest has not asked for an address yet") 832 assert.Empty(t, av.Ip, "the guest has not asked for an address yet")
833 assert.Equal(t, "running", av.Power, "no address is not a failure") 833 assert.Equal(t, "running", av.PowerState, "no address is not a failure")
834 assert.Equal(t, "ready", av.Phase) 834 assert.Equal(t, "ready", av.Phase)
835 recs, _ := f.st.LoadVMs() 835 recs, _ := f.st.LoadVMs()
836 require.Contains(t, recs, "vm1") 836 require.Contains(t, recs, "vm1")
@@ -853,7 +853,7 @@ func TestAddressDiscoveredAfterBootIsPersisted(t *testing.T) {
853 // all; the LAN address arrives when the site's DHCP server says so. 853 // all; the LAN address arrives when the site's DHCP server says so.
854 func TestNetworkedVMReportsBothAddresses(t *testing.T) { 854 func TestNetworkedVMReportsBothAddresses(t *testing.T) {
855 f := setup(t) 855 f := setup(t)
856 lan := func(v *pb.VMDesired) { v.Network = "lan" } 856 lan := func(v *pb.VMSpec) { v.Network = "lan" }
857 857
858 rep := f.step(snap(1, vm("vm1", lan))) 858 rep := f.step(snap(1, vm("vm1", lan)))
859 av := findVM(rep, "vm1") 859 av := findVM(rep, "vm1")
@@ -911,7 +911,7 @@ func TestSeedCarriesBothNICsOnlyForANetworkedGuest(t *testing.T) {
911 var got seed.Params 911 var got seed.Params
912 f.eng.Seed = func(_ string, p seed.Params) error { got = p; return nil } 912 f.eng.Seed = func(_ string, p seed.Params) error { got = p; return nil }
913 913
914 f.step(snap(1, vm("vm1", func(v *pb.VMDesired) { v.Network = "lan" }))) 914 f.step(snap(1, vm("vm1", func(v *pb.VMSpec) { v.Network = "lan" })))
915 assert.Equal(t, state.MAC("vm1"), got.MAC) 915 assert.Equal(t, state.MAC("vm1"), got.MAC)
916 assert.Equal(t, state.NetMAC("vm1"), got.NetworkMAC) 916 assert.Equal(t, state.NetMAC("vm1"), got.NetworkMAC)
917 917
internal/agent/reconcile/worker.go
Old New
@@ -127,7 +127,7 @@ func (m *manager) tombstones() map[string]bool {
127 // it marshals — two reports marshalled concurrently would race on one message. 127 // it marshals — two reports marshalled concurrently would race on one message.
128 // Cloning makes the report self-contained instead of resting on an assumption 128 // Cloning makes the report self-contained instead of resting on an assumption
129 // about how many goroutines might serialize it. 129 // about how many goroutines might serialize it.
130 func (m *manager) collect(rep *pb.ActualStateReport) { 130 func (m *manager) collect(rep *pb.Report) {
131 for _, w := range m.snapshot() { 131 for _, w := range m.snapshot() {
132 w.mu.Lock() 132 w.mu.Lock()
133 res := w.result 133 res := w.result
internal/agent/reconcile/worker_test.go
Old New
@@ -35,9 +35,9 @@ func wedge(arrived chan<- struct{}, release <-chan struct{}) func(context.Contex
35 // does not come back promptly. Every test below wedges a VM, so a Step that 35 // does not come back promptly. Every test below wedges a VM, so a Step that
36 // waited on its workers would hang the whole package instead of naming the 36 // waited on its workers would hang the whole package instead of naming the
37 // property that broke. 37 // property that broke.
38 func stepNoWait(t *testing.T, f *fixture, s *pb.DesiredStateSnapshot) *pb.ActualStateReport { 38 func stepNoWait(t *testing.T, f *fixture, s *pb.Snapshot) *pb.Report {
39 t.Helper() 39 t.Helper()
40 done := make(chan *pb.ActualStateReport, 1) 40 done := make(chan *pb.Report, 1)
41 go func() { done <- f.eng.Step(context.Background(), s) }() 41 go func() { done <- f.eng.Step(context.Background(), s) }()
42 select { 42 select {
43 case rep := <-done: 43 case rep := <-done:
@@ -104,11 +104,11 @@ func TestVMsReconcileConcurrently(t *testing.T) {
104 f.eng.manager().waitIdle() 104 f.eng.manager().waitIdle()
105 } 105 }
106 106
107 // TestLatestDesiredWinsForABusyVM pins coalescing: desired-state updates that 107 // TestLatestSpecWinsForABusyVM pins coalescing: desired-state updates that
108 // arrive while a VM is busy overwrite each other, so the worker reconciles 108 // arrive while a VM is busy overwrite each other, so the worker reconciles
109 // against the NEWEST desired when it comes free — a superseded intermediate 109 // against the NEWEST desired when it comes free — a superseded intermediate
110 // state is never acted on. 110 // state is never acted on.
111 func TestLatestDesiredWinsForABusyVM(t *testing.T) { 111 func TestLatestSpecWinsForABusyVM(t *testing.T) {
112 f := setup(t) 112 f := setup(t)
113 arrived := make(chan struct{}, 1) 113 arrived := make(chan struct{}, 1)
114 release := make(chan struct{}) 114 release := make(chan struct{})
@@ -129,7 +129,7 @@ func TestLatestDesiredWinsForABusyVM(t *testing.T) {
129 f.eng.manager().waitIdle() 129 f.eng.manager().waitIdle()
130 130
131 rep := f.aggregateNow(3) // the newest snapshot the worker reconciled against 131 rep := f.aggregateNow(3) // the newest snapshot the worker reconciled against
132 assert.Equal(t, "running", findVM(rep, "vm1").GetPower()) 132 assert.Equal(t, "running", findVM(rep, "vm1").GetPowerState())
133 assert.Empty(t, f.prov.shutdown, "the superseded 'stopped' desired must never be reconciled") 133 assert.Empty(t, f.prov.shutdown, "the superseded 'stopped' desired must never be reconciled")
134 } 134 }
135 135
internal/agent/seed/seed.go
Old New
@@ -21,7 +21,7 @@ type Params struct {
21 InstanceID string // used as cloud-init instance-id; falls back to Hostname when empty 21 InstanceID string // used as cloud-init instance-id; falls back to Hostname when empty
22 // SSHUserCAAuthorizedKey, when non-empty, is the tenant's user-CA set in 22 // SSHUserCAAuthorizedKey, when non-empty, is the tenant's user-CA set in
23 // authorized_keys form — one canonical CA per line (from the agent joining the 23 // authorized_keys form — one canonical CA per line (from the agent joining the
24 // VMDesired.ssh_user_ca_authorized_keys set). Its presence 24 // VMSpec.ssh_user_ca_authorized_keys set). Its presence
25 // makes the seed inject an sshd drop-in (TrustedUserCAKeys) so the guest 25 // makes the seed inject an sshd drop-in (TrustedUserCAKeys) so the guest
26 // trusts CA-signed user certs minted by the jump gate. Empty = no injection. 26 // trusts CA-signed user certs minted by the jump gate. Empty = no injection.
27 // Exempt from the newline check: it is embedded as a YAML block scalar (like 27 // Exempt from the newline check: it is embedded as a YAML block scalar (like
internal/agent/syncclient/client.go
Old New
@@ -47,7 +47,7 @@ type Console interface {
47 // *exposeproxy.Manager). A nil Exposures converges nothing and reports nothing 47 // *exposeproxy.Manager). A nil Exposures converges nothing and reports nothing
48 // — an agent with no proxy publishes no ports, and says so by saying nothing. 48 // — an agent with no proxy publishes no ports, and says so by saying nothing.
49 type Exposures interface { 49 type Exposures interface {
50 Converge(desired []*pb.ExposureDesired) []*pb.ExposureActual 50 Converge(desired []*pb.ExposureSpec) []*pb.ExposureStatus
51 } 51 }
52 52
53 // DefaultTickInterval is the fallback report/reconcile cadence when 53 // DefaultTickInterval is the fallback report/reconcile cadence when
@@ -220,7 +220,7 @@ func (c *Client) maybeUpgrade(ctx context.Context, up *pb.AgentUpgrade) {
220 // returns the rows the report carries. Level-triggered like everything else: 220 // returns the rows the report carries. Level-triggered like everything else:
221 // every snapshot re-converges, so a bind that lost its port to a squatting 221 // every snapshot re-converges, so a bind that lost its port to a squatting
222 // process is retried on the next tick. 222 // process is retried on the next tick.
223 func (c *Client) convergeExposures(snap *pb.DesiredStateSnapshot) []*pb.ExposureActual { 223 func (c *Client) convergeExposures(snap *pb.Snapshot) []*pb.ExposureStatus {
224 if c.Exposures == nil { 224 if c.Exposures == nil {
225 return nil 225 return nil
226 } 226 }
@@ -231,7 +231,7 @@ func (c *Client) convergeExposures(snap *pb.DesiredStateSnapshot) []*pb.Exposure
231 // fenced snapshot is one this host has already moved past, and driving 231 // fenced snapshot is one this host has already moved past, and driving
232 // listeners from it would re-open a port the fleet has since revoked — the 232 // listeners from it would re-open a port the fleet has since revoked — the
233 // same reason the fence path touches nothing else. 233 // same reason the fence path touches nothing else.
234 func (c *Client) reportExposures(snap *pb.DesiredStateSnapshot, rep *pb.ActualStateReport) []*pb.ExposureActual { 234 func (c *Client) reportExposures(snap *pb.Snapshot, rep *pb.Report) []*pb.ExposureStatus {
235 if rep.GetFenceViolation() { 235 if rep.GetFenceViolation() {
236 return nil 236 return nil
237 } 237 }
@@ -372,7 +372,7 @@ func (c *Client) session(ctx context.Context) error {
372 }() 372 }()
373 373
374 var mu sync.Mutex 374 var mu sync.Mutex
375 var latest *pb.DesiredStateSnapshot 375 var latest *pb.Snapshot
376 // connectedOnce is set true (under mu) once the recv goroutine reads its 376 // connectedOnce is set true (under mu) once the recv goroutine reads its
377 // first snapshot. session wraps its terminal error with errSessionConnected 377 // first snapshot. session wraps its terminal error with errSessionConnected
378 // when set, so Run() resets its consecutive-failure counter. 378 // when set, so Run() resets its consecutive-failure counter.
internal/agent/syncclient/exposures_test.go
Old New
@@ -13,10 +13,10 @@ import (
13 type fakeExposures struct { 13 type fakeExposures struct {
14 sawIDs []string 14 sawIDs []string
15 calls int 15 calls int
16 out []*pb.ExposureActual 16 out []*pb.ExposureStatus
17 } 17 }
18 18
19 func (f *fakeExposures) Converge(desired []*pb.ExposureDesired) []*pb.ExposureActual { 19 func (f *fakeExposures) Converge(desired []*pb.ExposureSpec) []*pb.ExposureStatus {
20 f.calls++ 20 f.calls++
21 f.sawIDs = nil 21 f.sawIDs = nil
22 for _, d := range desired { 22 for _, d := range desired {
@@ -26,11 +26,11 @@ func (f *fakeExposures) Converge(desired []*pb.ExposureDesired) []*pb.ExposureAc
26 } 26 }
27 27
28 func TestConvergeExposuresPassesTheSnapshotThrough(t *testing.T) { 28 func TestConvergeExposuresPassesTheSnapshotThrough(t *testing.T) {
29 fe := &fakeExposures{out: []*pb.ExposureActual{{Id: "e1", State: "active"}}} 29 fe := &fakeExposures{out: []*pb.ExposureStatus{{Id: "e1", State: "active"}}}
30 c := &Client{Exposures: fe} 30 c := &Client{Exposures: fe}
31 31
32 got := c.convergeExposures(&pb.DesiredStateSnapshot{ 32 got := c.convergeExposures(&pb.Snapshot{
33 Exposures: []*pb.ExposureDesired{{Id: "e1", VmId: "vm1", GuestPort: 8080, HostPort: 30080}}, 33 Exposures: []*pb.ExposureSpec{{Id: "e1", VmId: "vm1", GuestPort: 8080, HostPort: 30080}},
34 }) 34 })
35 35
36 assert.Equal(t, []string{"e1"}, fe.sawIDs) 36 assert.Equal(t, []string{"e1"}, fe.sawIDs)
@@ -40,8 +40,8 @@ func TestConvergeExposuresPassesTheSnapshotThrough(t *testing.T) {
40 40
41 func TestConvergeExposuresWithoutAProxyConvergesNothing(t *testing.T) { 41 func TestConvergeExposuresWithoutAProxyConvergesNothing(t *testing.T) {
42 c := &Client{} 42 c := &Client{}
43 assert.Nil(t, c.convergeExposures(&pb.DesiredStateSnapshot{ 43 assert.Nil(t, c.convergeExposures(&pb.Snapshot{
44 Exposures: []*pb.ExposureDesired{{Id: "e1"}}, 44 Exposures: []*pb.ExposureSpec{{Id: "e1"}},
45 }), "an agent with no proxy publishes nothing, and says so by saying nothing") 45 }), "an agent with no proxy publishes nothing, and says so by saying nothing")
46 } 46 }
47 47
@@ -49,9 +49,9 @@ func TestConvergeExposuresRefusesAFencedSnapshot(t *testing.T) {
49 fe := &fakeExposures{} 49 fe := &fakeExposures{}
50 c := &Client{Exposures: fe} 50 c := &Client{Exposures: fe}
51 51
52 got := c.reportExposures(&pb.DesiredStateSnapshot{ 52 got := c.reportExposures(&pb.Snapshot{
53 Exposures: []*pb.ExposureDesired{{Id: "e1"}}, 53 Exposures: []*pb.ExposureSpec{{Id: "e1"}},
54 }, &pb.ActualStateReport{FenceViolation: true}) 54 }, &pb.Report{FenceViolation: true})
55 55
56 assert.Zero(t, fe.calls, "a snapshot the engine refused must not drive the listeners") 56 assert.Zero(t, fe.calls, "a snapshot the engine refused must not drive the listeners")
57 assert.Nil(t, got) 57 assert.Nil(t, got)
internal/agent/syncclient/leak_test.go
Old New
@@ -64,7 +64,7 @@ func newDropServer(t *testing.T) (addr, fp string, stop func()) {
64 // step; then drop so the client's recv goroutine errors and the 64 // step; then drop so the client's recv goroutine errors and the
65 // session ends the way a real server drop / idle timeout ends it. 65 // session ends the way a real server drop / idle timeout ends it.
66 _ = transport.WriteMsg(down, &pb.ServerMessage{ 66 _ = transport.WriteMsg(down, &pb.ServerMessage{
67 Msg: &pb.ServerMessage_Snapshot{Snapshot: &pb.DesiredStateSnapshot{}}}) 67 Msg: &pb.ServerMessage_Snapshot{Snapshot: &pb.Snapshot{}}})
68 time.Sleep(30 * time.Millisecond) 68 time.Sleep(30 * time.Millisecond)
69 _ = conn.CloseWithError(0, "drop") 69 _ = conn.CloseWithError(0, "drop")
70 }() 70 }()
internal/pb/sync.pb.go
Old New
@@ -80,7 +80,7 @@ func (x *AgentMessage) GetHello() *Hello {
80 return nil 80 return nil
81 } 81 }
82 82
83 func (x *AgentMessage) GetReport() *ActualStateReport { 83 func (x *AgentMessage) GetReport() *Report {
84 if x != nil { 84 if x != nil {
85 if x, ok := x.Msg.(*AgentMessage_Report); ok { 85 if x, ok := x.Msg.(*AgentMessage_Report); ok {
86 return x.Report 86 return x.Report
@@ -116,7 +116,7 @@ type AgentMessage_Hello struct {
116 } 116 }
117 117
118 type AgentMessage_Report struct { 118 type AgentMessage_Report struct {
119 Report *ActualStateReport `protobuf:"bytes,2,opt,name=report,proto3,oneof"` 119 Report *Report `protobuf:"bytes,2,opt,name=report,proto3,oneof"`
120 } 120 }
121 121
122 type AgentMessage_ConsoleOpened struct { 122 type AgentMessage_ConsoleOpened struct {
@@ -184,7 +184,7 @@ func (x *ServerMessage) GetMsg() isServerMessage_Msg {
184 return nil 184 return nil
185 } 185 }
186 186
187 func (x *ServerMessage) GetSnapshot() *DesiredStateSnapshot { 187 func (x *ServerMessage) GetSnapshot() *Snapshot {
188 if x != nil { 188 if x != nil {
189 if x, ok := x.Msg.(*ServerMessage_Snapshot); ok { 189 if x, ok := x.Msg.(*ServerMessage_Snapshot); ok {
190 return x.Snapshot 190 return x.Snapshot
@@ -216,7 +216,7 @@ type isServerMessage_Msg interface {
216 } 216 }
217 217
218 type ServerMessage_Snapshot struct { 218 type ServerMessage_Snapshot struct {
219 Snapshot *DesiredStateSnapshot `protobuf:"bytes,1,opt,name=snapshot,proto3,oneof"` 219 Snapshot *Snapshot `protobuf:"bytes,1,opt,name=snapshot,proto3,oneof"`
220 } 220 }
221 221
222 type ServerMessage_ConsoleOpen struct { 222 type ServerMessage_ConsoleOpen struct {
@@ -240,7 +240,7 @@ type Hello struct {
240 Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` 240 Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"`
241 Arch string `protobuf:"bytes,4,opt,name=arch,proto3" json:"arch,omitempty"` 241 Arch string `protobuf:"bytes,4,opt,name=arch,proto3" json:"arch,omitempty"`
242 Provisioner string `protobuf:"bytes,5,opt,name=provisioner,proto3" json:"provisioner,omitempty"` // "cloudhv" 242 Provisioner string `protobuf:"bytes,5,opt,name=provisioner,proto3" json:"provisioner,omitempty"` // "cloudhv"
243 // ActualStateReport.guest_cidr. 243 // Report.guest_cidr.
244 LastSeenEpoch uint64 `protobuf:"varint,7,opt,name=last_seen_epoch,json=lastSeenEpoch,proto3" json:"last_seen_epoch,omitempty"` // for the restore runbook 244 LastSeenEpoch uint64 `protobuf:"varint,7,opt,name=last_seen_epoch,json=lastSeenEpoch,proto3" json:"last_seen_epoch,omitempty"` // for the restore runbook
245 Capacity *Capacity `protobuf:"bytes,8,opt,name=capacity,proto3" json:"capacity,omitempty"` 245 Capacity *Capacity `protobuf:"bytes,8,opt,name=capacity,proto3" json:"capacity,omitempty"`
246 Credential string `protobuf:"bytes,9,opt,name=credential,proto3" json:"credential,omitempty"` // Bearer host credential, verified in first frame 246 Credential string `protobuf:"bytes,9,opt,name=credential,proto3" json:"credential,omitempty"` // Bearer host credential, verified in first frame
@@ -610,13 +610,16 @@ func (x *HostMetrics) GetDiskFreeGb() int64 {
610 return 0 610 return 0
611 } 611 }
612 612
613 type ActualVM struct { 613 // VMStatus is the half of a VM its host owns: what it observed, never what it
614 state protoimpl.MessageState `protogen:"open.v1"` 614 // was told. One VM, two halves — VMSpec travels down in a Snapshot, VMStatus
615 VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"` 615 // travels back in a Report, and the control plane holds both on one row.
616 Power string `protobuf:"bytes,2,opt,name=power,proto3" json:"power,omitempty"` // "running"|"stopped" 616 type VMStatus struct {
617 Phase string `protobuf:"bytes,3,opt,name=phase,proto3" json:"phase,omitempty"` // "creating"|"ready"|"failed"|"quarantined" 617 state protoimpl.MessageState `protogen:"open.v1"`
618 Ip string `protobuf:"bytes,4,opt,name=ip,proto3" json:"ip,omitempty"` // the guest's address on its host's NAT underlay — every guest has one, from boot 618 VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
619 LastError string `protobuf:"bytes,5,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"` 619 PowerState string `protobuf:"bytes,2,opt,name=power_state,json=powerState,proto3" json:"power_state,omitempty"` // "running"|"stopped" as observed; VMSpec.power_state is what was asked for
620 Phase string `protobuf:"bytes,3,opt,name=phase,proto3" json:"phase,omitempty"` // "creating"|"ready"|"failed"|"quarantined"
621 Ip string `protobuf:"bytes,4,opt,name=ip,proto3" json:"ip,omitempty"` // the guest's address on its host's NAT underlay — every guest has one, from boot
622 LastError string `protobuf:"bytes,5,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"`
620 // The guest's ed25519 HOST public key. It is generated on the host, and the 623 // The guest's ed25519 HOST public key. It is generated on the host, and the
621 // private half never leaves it — this is the only half that travels. Sent on 624 // private half never leaves it — this is the only half that travels. Sent on
622 // every report for as long as the VM exists (level-triggered), so a lost 625 // every report for as long as the VM exists (level-triggered), so a lost
@@ -633,7 +636,7 @@ type ActualVM struct {
633 StatusDetail string `protobuf:"bytes,7,opt,name=status_detail,json=statusDetail,proto3" json:"status_detail,omitempty"` 636 StatusDetail string `protobuf:"bytes,7,opt,name=status_detail,json=statusDetail,proto3" json:"status_detail,omitempty"`
634 // The address the site's DHCP server granted this guest on its SECOND NIC, 637 // The address the site's DHCP server granted this guest on its SECOND NIC,
635 // the one attached to the named host network its spec asked for (see 638 // the one attached to the named host network its spec asked for (see
636 // VMDesired.network). Empty for the guests that have no such NIC — the 639 // VMSpec.network). Empty for the guests that have no such NIC — the
637 // majority — and for one DHCP round-trip after a networked guest boots, 640 // majority — and for one DHCP round-trip after a networked guest boots,
638 // because the host learns it by watching the exchange rather than granting 641 // because the host learns it by watching the exchange rather than granting
639 // it. Never a substitute for ip: that one is known before the guest is even 642 // it. Never a substitute for ip: that one is known before the guest is even
@@ -644,20 +647,20 @@ type ActualVM struct {
644 sizeCache protoimpl.SizeCache 647 sizeCache protoimpl.SizeCache
645 } 648 }
646 649
647 func (x *ActualVM) Reset() { 650 func (x *VMStatus) Reset() {
648 *x = ActualVM{} 651 *x = VMStatus{}
649 mi := &file_proto_eitri_v1_sync_proto_msgTypes[6] 652 mi := &file_proto_eitri_v1_sync_proto_msgTypes[6]
650 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 653 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
651 ms.StoreMessageInfo(mi) 654 ms.StoreMessageInfo(mi)
652 } 655 }
653 656
654 func (x *ActualVM) String() string { 657 func (x *VMStatus) String() string {
655 return protoimpl.X.MessageStringOf(x) 658 return protoimpl.X.MessageStringOf(x)
656 } 659 }
657 660
658 func (*ActualVM) ProtoMessage() {} 661 func (*VMStatus) ProtoMessage() {}
659 662
660 func (x *ActualVM) ProtoReflect() protoreflect.Message { 663 func (x *VMStatus) ProtoReflect() protoreflect.Message {
661 mi := &file_proto_eitri_v1_sync_proto_msgTypes[6] 664 mi := &file_proto_eitri_v1_sync_proto_msgTypes[6]
662 if x != nil { 665 if x != nil {
663 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 666 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -669,61 +672,61 @@ func (x *ActualVM) ProtoReflect() protoreflect.Message {
669 return mi.MessageOf(x) 672 return mi.MessageOf(x)
670 } 673 }
671 674
672 // Deprecated: Use ActualVM.ProtoReflect.Descriptor instead. 675 // Deprecated: Use VMStatus.ProtoReflect.Descriptor instead.
673 func (*ActualVM) Descriptor() ([]byte, []int) { 676 func (*VMStatus) Descriptor() ([]byte, []int) {
674 return file_proto_eitri_v1_sync_proto_rawDescGZIP(), []int{6} 677 return file_proto_eitri_v1_sync_proto_rawDescGZIP(), []int{6}
675 } 678 }
676 679
677 func (x *ActualVM) GetVmId() string { 680 func (x *VMStatus) GetVmId() string {
678 if x != nil { 681 if x != nil {
679 return x.VmId 682 return x.VmId
680 } 683 }
681 return "" 684 return ""
682 } 685 }
683 686
684 func (x *ActualVM) GetPower() string { 687 func (x *VMStatus) GetPowerState() string {
685 if x != nil { 688 if x != nil {
686 return x.Power 689 return x.PowerState
687 } 690 }
688 return "" 691 return ""
689 } 692 }
690 693
691 func (x *ActualVM) GetPhase() string { 694 func (x *VMStatus) GetPhase() string {
692 if x != nil { 695 if x != nil {
693 return x.Phase 696 return x.Phase
694 } 697 }
695 return "" 698 return ""
696 } 699 }
697 700
698 func (x *ActualVM) GetIp() string { 701 func (x *VMStatus) GetIp() string {
699 if x != nil { 702 if x != nil {
700 return x.Ip 703 return x.Ip
701 } 704 }
702 return "" 705 return ""
703 } 706 }
704 707
705 func (x *ActualVM) GetLastError() string { 708 func (x *VMStatus) GetLastError() string {
706 if x != nil { 709 if x != nil {
707 return x.LastError 710 return x.LastError
708 } 711 }
709 return "" 712 return ""
710 } 713 }
711 714
712 func (x *ActualVM) GetSshHostPubkey() string { 715 func (x *VMStatus) GetSshHostPubkey() string {
713 if x != nil { 716 if x != nil {
714 return x.SshHostPubkey 717 return x.SshHostPubkey
715 } 718 }
716 return "" 719 return ""
717 } 720 }
718 721
719 func (x *ActualVM) GetStatusDetail() string { 722 func (x *VMStatus) GetStatusDetail() string {
720 if x != nil { 723 if x != nil {
721 return x.StatusDetail 724 return x.StatusDetail
722 } 725 }
723 return "" 726 return ""
724 } 727 }
725 728
726 func (x *ActualVM) GetNetworkIp() string { 729 func (x *VMStatus) GetNetworkIp() string {
727 if x != nil { 730 if x != nil {
728 return x.NetworkIp 731 return x.NetworkIp
729 } 732 }
@@ -798,9 +801,11 @@ func (x *QuarantinedVM) GetDestroyAtUnix() int64 {
798 return 0 801 return 0
799 } 802 }
800 803
801 type ActualStateReport struct { 804 // Report is what one host observes, level-triggered: the status of every VM
805 // and exposure it holds, and the host's own numbers.
806 type Report struct {
802 state protoimpl.MessageState `protogen:"open.v1"` 807 state protoimpl.MessageState `protogen:"open.v1"`
803 Vms []*ActualVM `protobuf:"bytes,1,rep,name=vms,proto3" json:"vms,omitempty"` 808 Vms []*VMStatus `protobuf:"bytes,1,rep,name=vms,proto3" json:"vms,omitempty"`
804 // LEVEL-TRIGGERED destroy ack: ALL tombstoned vm_ids with no local 809 // LEVEL-TRIGGERED destroy ack: ALL tombstoned vm_ids with no local
805 // record/disk/process, repeated every report until hard-deleted server-side. 810 // record/disk/process, repeated every report until hard-deleted server-side.
806 Destroyed []string `protobuf:"bytes,2,rep,name=destroyed,proto3" json:"destroyed,omitempty"` 811 Destroyed []string `protobuf:"bytes,2,rep,name=destroyed,proto3" json:"destroyed,omitempty"`
@@ -818,7 +823,7 @@ type ActualStateReport struct {
818 // knowable at connect time at all. Capacity is in both messages for the same 823 // knowable at connect time at all. Capacity is in both messages for the same
819 // reason: an opening value, then the ongoing truth. 824 // reason: an opening value, then the ongoing truth.
820 GuestCidr string `protobuf:"bytes,8,opt,name=guest_cidr,json=guestCidr,proto3" json:"guest_cidr,omitempty"` 825 GuestCidr string `protobuf:"bytes,8,opt,name=guest_cidr,json=guestCidr,proto3" json:"guest_cidr,omitempty"`
821 Exposures []*ExposureActual `protobuf:"bytes,9,rep,name=exposures,proto3" json:"exposures,omitempty"` 826 Exposures []*ExposureStatus `protobuf:"bytes,9,rep,name=exposures,proto3" json:"exposures,omitempty"`
822 // The address this host presents on the network it reaches the control 827 // The address this host presents on the network it reaches the control
823 // plane over — the address an operator dials to reach a published guest 828 // plane over — the address an operator dials to reach a published guest
824 // port. Empty means "not yet known", never "no address": a host that cannot 829 // port. Empty means "not yet known", never "no address": a host that cannot
@@ -830,20 +835,20 @@ type ActualStateReport struct {
830 sizeCache protoimpl.SizeCache 835 sizeCache protoimpl.SizeCache
831 } 836 }
832 837
833 func (x *ActualStateReport) Reset() { 838 func (x *Report) Reset() {
834 *x = ActualStateReport{} 839 *x = Report{}
835 mi := &file_proto_eitri_v1_sync_proto_msgTypes[8] 840 mi := &file_proto_eitri_v1_sync_proto_msgTypes[8]
836 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 841 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
837 ms.StoreMessageInfo(mi) 842 ms.StoreMessageInfo(mi)
838 } 843 }
839 844
840 func (x *ActualStateReport) String() string { 845 func (x *Report) String() string {
841 return protoimpl.X.MessageStringOf(x) 846 return protoimpl.X.MessageStringOf(x)
842 } 847 }
843 848
844 func (*ActualStateReport) ProtoMessage() {} 849 func (*Report) ProtoMessage() {}
845 850
846 func (x *ActualStateReport) ProtoReflect() protoreflect.Message { 851 func (x *Report) ProtoReflect() protoreflect.Message {
847 mi := &file_proto_eitri_v1_sync_proto_msgTypes[8] 852 mi := &file_proto_eitri_v1_sync_proto_msgTypes[8]
848 if x != nil { 853 if x != nil {
849 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 854 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -855,82 +860,83 @@ func (x *ActualStateReport) ProtoReflect() protoreflect.Message {
855 return mi.MessageOf(x) 860 return mi.MessageOf(x)
856 } 861 }
857 862
858 // Deprecated: Use ActualStateReport.ProtoReflect.Descriptor instead. 863 // Deprecated: Use Report.ProtoReflect.Descriptor instead.
859 func (*ActualStateReport) Descriptor() ([]byte, []int) { 864 func (*Report) Descriptor() ([]byte, []int) {
860 return file_proto_eitri_v1_sync_proto_rawDescGZIP(), []int{8} 865 return file_proto_eitri_v1_sync_proto_rawDescGZIP(), []int{8}
861 } 866 }
862 867
863 func (x *ActualStateReport) GetVms() []*ActualVM { 868 func (x *Report) GetVms() []*VMStatus {
864 if x != nil { 869 if x != nil {
865 return x.Vms 870 return x.Vms
866 } 871 }
867 return nil 872 return nil
868 } 873 }
869 874
870 func (x *ActualStateReport) GetDestroyed() []string { 875 func (x *Report) GetDestroyed() []string {
871 if x != nil { 876 if x != nil {
872 return x.Destroyed 877 return x.Destroyed
873 } 878 }
874 return nil 879 return nil
875 } 880 }
876 881
877 func (x *ActualStateReport) GetQuarantined() []*QuarantinedVM { 882 func (x *Report) GetQuarantined() []*QuarantinedVM {
878 if x != nil { 883 if x != nil {
879 return x.Quarantined 884 return x.Quarantined
880 } 885 }
881 return nil 886 return nil
882 } 887 }
883 888
884 func (x *ActualStateReport) GetCapacity() *Capacity { 889 func (x *Report) GetCapacity() *Capacity {
885 if x != nil { 890 if x != nil {
886 return x.Capacity 891 return x.Capacity
887 } 892 }
888 return nil 893 return nil
889 } 894 }
890 895
891 func (x *ActualStateReport) GetFenceViolation() bool { 896 func (x *Report) GetFenceViolation() bool {
892 if x != nil { 897 if x != nil {
893 return x.FenceViolation 898 return x.FenceViolation
894 } 899 }
895 return false 900 return false
896 } 901 }
897 902
898 func (x *ActualStateReport) GetLastSeenEpoch() uint64 { 903 func (x *Report) GetLastSeenEpoch() uint64 {
899 if x != nil { 904 if x != nil {
900 return x.LastSeenEpoch 905 return x.LastSeenEpoch
901 } 906 }
902 return 0 907 return 0
903 } 908 }
904 909
905 func (x *ActualStateReport) GetMetrics() *HostMetrics { 910 func (x *Report) GetMetrics() *HostMetrics {
906 if x != nil { 911 if x != nil {
907 return x.Metrics 912 return x.Metrics
908 } 913 }
909 return nil 914 return nil
910 } 915 }
911 916
912 func (x *ActualStateReport) GetGuestCidr() string { 917 func (x *Report) GetGuestCidr() string {
913 if x != nil { 918 if x != nil {
914 return x.GuestCidr 919 return x.GuestCidr
915 } 920 }
916 return "" 921 return ""
917 } 922 }
918 923
919 func (x *ActualStateReport) GetExposures() []*ExposureActual { 924 func (x *Report) GetExposures() []*ExposureStatus {
920 if x != nil { 925 if x != nil {
921 return x.Exposures 926 return x.Exposures
922 } 927 }
923 return nil 928 return nil
924 } 929 }
925 930
926 func (x *ActualStateReport) GetHostUplinkAddr() string { 931 func (x *Report) GetHostUplinkAddr() string {
927 if x != nil { 932 if x != nil {
928 return x.HostUplinkAddr 933 return x.HostUplinkAddr
929 } 934 }
930 return "" 935 return ""
931 } 936 }
932 937
933 type VMDesired struct { 938 // VMSpec is the half of a VM the control plane owns. See VMStatus.
939 type VMSpec struct {
934 state protoimpl.MessageState `protogen:"open.v1"` 940 state protoimpl.MessageState `protogen:"open.v1"`
935 VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"` 941 VmId string `protobuf:"bytes,1,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
936 Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` 942 Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
@@ -953,7 +959,7 @@ type VMDesired struct {
953 Tombstoned bool `protobuf:"varint,11,opt,name=tombstoned,proto3" json:"tombstoned,omitempty"` // present-but-tombstoned (drives quarantine + destroyed[]) 959 Tombstoned bool `protobuf:"varint,11,opt,name=tombstoned,proto3" json:"tombstoned,omitempty"` // present-but-tombstoned (drives quarantine + destroyed[])
954 SshAuthorizedKey string `protobuf:"bytes,12,opt,name=ssh_authorized_key,json=sshAuthorizedKey,proto3" json:"ssh_authorized_key,omitempty"` 960 SshAuthorizedKey string `protobuf:"bytes,12,opt,name=ssh_authorized_key,json=sshAuthorizedKey,proto3" json:"ssh_authorized_key,omitempty"`
955 // The certificate the control plane signed for the public key the host 961 // The certificate the control plane signed for the public key the host
956 // reported in ActualVM.ssh_host_pubkey (authorized_keys form); seed installs 962 // reported in VMStatus.ssh_host_pubkey (authorized_keys form); seed installs
957 // it as /etc/ssh/ssh_host_ed25519_key-cert.pub. Empty until the round trip 963 // it as /etc/ssh/ssh_host_ed25519_key-cert.pub. Empty until the round trip
958 // completes, and forever when the jump gate is off. 964 // completes, and forever when the jump gate is off.
959 SshHostCert string `protobuf:"bytes,17,opt,name=ssh_host_cert,json=sshHostCert,proto3" json:"ssh_host_cert,omitempty"` 965 SshHostCert string `protobuf:"bytes,17,opt,name=ssh_host_cert,json=sshHostCert,proto3" json:"ssh_host_cert,omitempty"`
@@ -974,20 +980,20 @@ type VMDesired struct {
974 sizeCache protoimpl.SizeCache 980 sizeCache protoimpl.SizeCache
975 } 981 }
976 982
977 func (x *VMDesired) Reset() { 983 func (x *VMSpec) Reset() {
978 *x = VMDesired{} 984 *x = VMSpec{}
979 mi := &file_proto_eitri_v1_sync_proto_msgTypes[9] 985 mi := &file_proto_eitri_v1_sync_proto_msgTypes[9]
980 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 986 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
981 ms.StoreMessageInfo(mi) 987 ms.StoreMessageInfo(mi)
982 } 988 }
983 989
984 func (x *VMDesired) String() string { 990 func (x *VMSpec) String() string {
985 return protoimpl.X.MessageStringOf(x) 991 return protoimpl.X.MessageStringOf(x)
986 } 992 }
987 993
988 func (*VMDesired) ProtoMessage() {} 994 func (*VMSpec) ProtoMessage() {}
989 995
990 func (x *VMDesired) ProtoReflect() protoreflect.Message { 996 func (x *VMSpec) ProtoReflect() protoreflect.Message {
991 mi := &file_proto_eitri_v1_sync_proto_msgTypes[9] 997 mi := &file_proto_eitri_v1_sync_proto_msgTypes[9]
992 if x != nil { 998 if x != nil {
993 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 999 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -999,147 +1005,148 @@ func (x *VMDesired) ProtoReflect() protoreflect.Message {
999 return mi.MessageOf(x) 1005 return mi.MessageOf(x)
1000 } 1006 }
1001 1007
1002 // Deprecated: Use VMDesired.ProtoReflect.Descriptor instead. 1008 // Deprecated: Use VMSpec.ProtoReflect.Descriptor instead.
1003 func (*VMDesired) Descriptor() ([]byte, []int) { 1009 func (*VMSpec) Descriptor() ([]byte, []int) {
1004 return file_proto_eitri_v1_sync_proto_rawDescGZIP(), []int{9} 1010 return file_proto_eitri_v1_sync_proto_rawDescGZIP(), []int{9}
1005 } 1011 }
1006 1012
1007 func (x *VMDesired) GetVmId() string { 1013 func (x *VMSpec) GetVmId() string {
1008 if x != nil { 1014 if x != nil {
1009 return x.VmId 1015 return x.VmId
1010 } 1016 }
1011 return "" 1017 return ""
1012 } 1018 }
1013 1019
1014 func (x *VMDesired) GetName() string { 1020 func (x *VMSpec) GetName() string {
1015 if x != nil { 1021 if x != nil {
1016 return x.Name 1022 return x.Name
1017 } 1023 }
1018 return "" 1024 return ""
1019 } 1025 }
1020 1026
1021 func (x *VMDesired) GetImageUrl() string { 1027 func (x *VMSpec) GetImageUrl() string {
1022 if x != nil { 1028 if x != nil {
1023 return x.ImageUrl 1029 return x.ImageUrl
1024 } 1030 }
1025 return "" 1031 return ""
1026 } 1032 }
1027 1033
1028 func (x *VMDesired) GetImageSha256() string { 1034 func (x *VMSpec) GetImageSha256() string {
1029 if x != nil { 1035 if x != nil {
1030 return x.ImageSha256 1036 return x.ImageSha256
1031 } 1037 }
1032 return "" 1038 return ""
1033 } 1039 }
1034 1040
1035 func (x *VMDesired) GetCloudInit() string { 1041 func (x *VMSpec) GetCloudInit() string {
1036 if x != nil { 1042 if x != nil {
1037 return x.CloudInit 1043 return x.CloudInit
1038 } 1044 }
1039 return "" 1045 return ""
1040 } 1046 }
1041 1047
1042 func (x *VMDesired) GetVcpus() int64 { 1048 func (x *VMSpec) GetVcpus() int64 {
1043 if x != nil { 1049 if x != nil {
1044 return x.Vcpus 1050 return x.Vcpus
1045 } 1051 }
1046 return 0 1052 return 0
1047 } 1053 }
1048 1054
1049 func (x *VMDesired) GetMemMb() int64 { 1055 func (x *VMSpec) GetMemMb() int64 {
1050 if x != nil { 1056 if x != nil {
1051 return x.MemMb 1057 return x.MemMb
1052 } 1058 }
1053 return 0 1059 return 0
1054 } 1060 }
1055 1061
1056 func (x *VMDesired) GetDiskGb() int64 { 1062 func (x *VMSpec) GetDiskGb() int64 {
1057 if x != nil { 1063 if x != nil {
1058 return x.DiskGb 1064 return x.DiskGb
1059 } 1065 }
1060 return 0 1066 return 0
1061 } 1067 }
1062 1068
1063 func (x *VMDesired) GetPersistent() bool { 1069 func (x *VMSpec) GetPersistent() bool {
1064 if x != nil { 1070 if x != nil {
1065 return x.Persistent 1071 return x.Persistent
1066 } 1072 }
1067 return false 1073 return false
1068 } 1074 }
1069 1075
1070 func (x *VMDesired) GetPowerState() string { 1076 func (x *VMSpec) GetPowerState() string {
1071 if x != nil { 1077 if x != nil {
1072 return x.PowerState 1078 return x.PowerState
1073 } 1079 }
1074 return "" 1080 return ""
1075 } 1081 }
1076 1082
1077 func (x *VMDesired) GetTombstoned() bool { 1083 func (x *VMSpec) GetTombstoned() bool {
1078 if x != nil { 1084 if x != nil {
1079 return x.Tombstoned 1085 return x.Tombstoned
1080 } 1086 }
1081 return false 1087 return false
1082 } 1088 }
1083 1089
1084 func (x *VMDesired) GetSshAuthorizedKey() string { 1090 func (x *VMSpec) GetSshAuthorizedKey() string {
1085 if x != nil { 1091 if x != nil {
1086 return x.SshAuthorizedKey 1092 return x.SshAuthorizedKey
1087 } 1093 }
1088 return "" 1094 return ""
1089 } 1095 }
1090 1096
1091 func (x *VMDesired) GetSshHostCert() string { 1097 func (x *VMSpec) GetSshHostCert() string {
1092 if x != nil { 1098 if x != nil {
1093 return x.SshHostCert 1099 return x.SshHostCert
1094 } 1100 }
1095 return "" 1101 return ""
1096 } 1102 }
1097 1103
1098 func (x *VMDesired) GetSshUserCaAuthorizedKeys() []string { 1104 func (x *VMSpec) GetSshUserCaAuthorizedKeys() []string {
1099 if x != nil { 1105 if x != nil {
1100 return x.SshUserCaAuthorizedKeys 1106 return x.SshUserCaAuthorizedKeys
1101 } 1107 }
1102 return nil 1108 return nil
1103 } 1109 }
1104 1110
1105 func (x *VMDesired) GetHostCertRequired() bool { 1111 func (x *VMSpec) GetHostCertRequired() bool {
1106 if x != nil { 1112 if x != nil {
1107 return x.HostCertRequired 1113 return x.HostCertRequired
1108 } 1114 }
1109 return false 1115 return false
1110 } 1116 }
1111 1117
1112 func (x *VMDesired) GetNetwork() string { 1118 func (x *VMSpec) GetNetwork() string {
1113 if x != nil { 1119 if x != nil {
1114 return x.Network 1120 return x.Network
1115 } 1121 }
1116 return "" 1122 return ""
1117 } 1123 }
1118 1124
1119 type DesiredStateSnapshot struct { 1125 // Snapshot is the FULL spec for one host; the agent converges toward it.
1126 type Snapshot struct {
1120 state protoimpl.MessageState `protogen:"open.v1"` 1127 state protoimpl.MessageState `protogen:"open.v1"`
1121 Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` // agents refuse epoch < highest seen 1128 Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` // agents refuse epoch < highest seen
1122 Vms []*VMDesired `protobuf:"bytes,2,rep,name=vms,proto3" json:"vms,omitempty"` // FULL set for this host, including tombstoned 1129 Vms []*VMSpec `protobuf:"bytes,2,rep,name=vms,proto3" json:"vms,omitempty"` // FULL set for this host, including tombstoned
1123 AgentUpgrade *AgentUpgrade `protobuf:"bytes,3,opt,name=agent_upgrade,json=agentUpgrade,proto3" json:"agent_upgrade,omitempty"` // optional operator-initiated agent self-upgrade 1130 AgentUpgrade *AgentUpgrade `protobuf:"bytes,3,opt,name=agent_upgrade,json=agentUpgrade,proto3" json:"agent_upgrade,omitempty"` // optional operator-initiated agent self-upgrade
1124 Exposures []*ExposureDesired `protobuf:"bytes,4,rep,name=exposures,proto3" json:"exposures,omitempty"` // FULL set for this host 1131 Exposures []*ExposureSpec `protobuf:"bytes,4,rep,name=exposures,proto3" json:"exposures,omitempty"` // FULL set for this host
1125 unknownFields protoimpl.UnknownFields 1132 unknownFields protoimpl.UnknownFields
1126 sizeCache protoimpl.SizeCache 1133 sizeCache protoimpl.SizeCache
1127 } 1134 }
1128 1135
1129 func (x *DesiredStateSnapshot) Reset() { 1136 func (x *Snapshot) Reset() {
1130 *x = DesiredStateSnapshot{} 1137 *x = Snapshot{}
1131 mi := &file_proto_eitri_v1_sync_proto_msgTypes[10] 1138 mi := &file_proto_eitri_v1_sync_proto_msgTypes[10]
1132 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 1139 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1133 ms.StoreMessageInfo(mi) 1140 ms.StoreMessageInfo(mi)
1134 } 1141 }
1135 1142
1136 func (x *DesiredStateSnapshot) String() string { 1143 func (x *Snapshot) String() string {
1137 return protoimpl.X.MessageStringOf(x) 1144 return protoimpl.X.MessageStringOf(x)
1138 } 1145 }
1139 1146
1140 func (*DesiredStateSnapshot) ProtoMessage() {} 1147 func (*Snapshot) ProtoMessage() {}
1141 1148
1142 func (x *DesiredStateSnapshot) ProtoReflect() protoreflect.Message { 1149 func (x *Snapshot) ProtoReflect() protoreflect.Message {
1143 mi := &file_proto_eitri_v1_sync_proto_msgTypes[10] 1150 mi := &file_proto_eitri_v1_sync_proto_msgTypes[10]
1144 if x != nil { 1151 if x != nil {
1145 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 1152 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -1151,33 +1158,33 @@ func (x *DesiredStateSnapshot) ProtoReflect() protoreflect.Message {
1151 return mi.MessageOf(x) 1158 return mi.MessageOf(x)
1152 } 1159 }
1153 1160
1154 // Deprecated: Use DesiredStateSnapshot.ProtoReflect.Descriptor instead. 1161 // Deprecated: Use Snapshot.ProtoReflect.Descriptor instead.
1155 func (*DesiredStateSnapshot) Descriptor() ([]byte, []int) { 1162 func (*Snapshot) Descriptor() ([]byte, []int) {
1156 return file_proto_eitri_v1_sync_proto_rawDescGZIP(), []int{10} 1163 return file_proto_eitri_v1_sync_proto_rawDescGZIP(), []int{10}
1157 } 1164 }
1158 1165
1159 func (x *DesiredStateSnapshot) GetEpoch() uint64 { 1166 func (x *Snapshot) GetEpoch() uint64 {
1160 if x != nil { 1167 if x != nil {
1161 return x.Epoch 1168 return x.Epoch
1162 } 1169 }
1163 return 0 1170 return 0
1164 } 1171 }
1165 1172
1166 func (x *DesiredStateSnapshot) GetVms() []*VMDesired { 1173 func (x *Snapshot) GetVms() []*VMSpec {
1167 if x != nil { 1174 if x != nil {
1168 return x.Vms 1175 return x.Vms
1169 } 1176 }
1170 return nil 1177 return nil
1171 } 1178 }
1172 1179
1173 func (x *DesiredStateSnapshot) GetAgentUpgrade() *AgentUpgrade { 1180 func (x *Snapshot) GetAgentUpgrade() *AgentUpgrade {
1174 if x != nil { 1181 if x != nil {
1175 return x.AgentUpgrade 1182 return x.AgentUpgrade
1176 } 1183 }
1177 return nil 1184 return nil
1178 } 1185 }
1179 1186
1180 func (x *DesiredStateSnapshot) GetExposures() []*ExposureDesired { 1187 func (x *Snapshot) GetExposures() []*ExposureSpec {
1181 if x != nil { 1188 if x != nil {
1182 return x.Exposures 1189 return x.Exposures
1183 } 1190 }
@@ -1460,13 +1467,13 @@ func (x *TCPOpened) GetError() string {
1460 return "" 1467 return ""
1461 } 1468 }
1462 1469
1463 // ExposureDesired is one published guest port a host should be serving: bind 1470 // ExposureSpec is one published guest port a host should be serving: bind
1464 // host_port on the host, pipe every accepted connection (or every datagram) 1471 // host_port on the host, pipe every accepted connection (or every datagram)
1465 // to guest_port inside the guest. It rides the snapshot at TOP LEVEL rather 1472 // to guest_port inside the guest. It rides the snapshot at TOP LEVEL rather
1466 // than nested in VMDesired, because exposures are their own objects converging 1473 // than nested in VMSpec, because exposures are their own objects converging
1467 // on their own cadence — an exposure can be created while its VM is still 1474 // on their own cadence — an exposure can be created while its VM is still
1468 // imaging, and it binds immediately. 1475 // imaging, and it binds immediately.
1469 type ExposureDesired struct { 1476 type ExposureSpec struct {
1470 state protoimpl.MessageState `protogen:"open.v1"` 1477 state protoimpl.MessageState `protogen:"open.v1"`
1471 Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 1478 Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
1472 VmId string `protobuf:"bytes,2,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"` 1479 VmId string `protobuf:"bytes,2,opt,name=vm_id,json=vmId,proto3" json:"vm_id,omitempty"`
@@ -1477,20 +1484,20 @@ type ExposureDesired struct {
1477 sizeCache protoimpl.SizeCache 1484 sizeCache protoimpl.SizeCache
1478 } 1485 }
1479 1486
1480 func (x *ExposureDesired) Reset() { 1487 func (x *ExposureSpec) Reset() {
1481 *x = ExposureDesired{} 1488 *x = ExposureSpec{}
1482 mi := &file_proto_eitri_v1_sync_proto_msgTypes[16] 1489 mi := &file_proto_eitri_v1_sync_proto_msgTypes[16]
1483 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 1490 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1484 ms.StoreMessageInfo(mi) 1491 ms.StoreMessageInfo(mi)
1485 } 1492 }
1486 1493
1487 func (x *ExposureDesired) String() string { 1494 func (x *ExposureSpec) String() string {
1488 return protoimpl.X.MessageStringOf(x) 1495 return protoimpl.X.MessageStringOf(x)
1489 } 1496 }
1490 1497
1491 func (*ExposureDesired) ProtoMessage() {} 1498 func (*ExposureSpec) ProtoMessage() {}
1492 1499
1493 func (x *ExposureDesired) ProtoReflect() protoreflect.Message { 1500 func (x *ExposureSpec) ProtoReflect() protoreflect.Message {
1494 mi := &file_proto_eitri_v1_sync_proto_msgTypes[16] 1501 mi := &file_proto_eitri_v1_sync_proto_msgTypes[16]
1495 if x != nil { 1502 if x != nil {
1496 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 1503 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -1502,51 +1509,51 @@ func (x *ExposureDesired) ProtoReflect() protoreflect.Message {
1502 return mi.MessageOf(x) 1509 return mi.MessageOf(x)
1503 } 1510 }
1504 1511
1505 // Deprecated: Use ExposureDesired.ProtoReflect.Descriptor instead. 1512 // Deprecated: Use ExposureSpec.ProtoReflect.Descriptor instead.
1506 func (*ExposureDesired) Descriptor() ([]byte, []int) { 1513 func (*ExposureSpec) Descriptor() ([]byte, []int) {
1507 return file_proto_eitri_v1_sync_proto_rawDescGZIP(), []int{16} 1514 return file_proto_eitri_v1_sync_proto_rawDescGZIP(), []int{16}
1508 } 1515 }
1509 1516
1510 func (x *ExposureDesired) GetId() string { 1517 func (x *ExposureSpec) GetId() string {
1511 if x != nil { 1518 if x != nil {
1512 return x.Id 1519 return x.Id
1513 } 1520 }
1514 return "" 1521 return ""
1515 } 1522 }
1516 1523
1517 func (x *ExposureDesired) GetVmId() string { 1524 func (x *ExposureSpec) GetVmId() string {
1518 if x != nil { 1525 if x != nil {
1519 return x.VmId 1526 return x.VmId
1520 } 1527 }
1521 return "" 1528 return ""
1522 } 1529 }
1523 1530
1524 func (x *ExposureDesired) GetGuestPort() uint32 { 1531 func (x *ExposureSpec) GetGuestPort() uint32 {
1525 if x != nil { 1532 if x != nil {
1526 return x.GuestPort 1533 return x.GuestPort
1527 } 1534 }
1528 return 0 1535 return 0
1529 } 1536 }
1530 1537
1531 func (x *ExposureDesired) GetHostPort() uint32 { 1538 func (x *ExposureSpec) GetHostPort() uint32 {
1532 if x != nil { 1539 if x != nil {
1533 return x.HostPort 1540 return x.HostPort
1534 } 1541 }
1535 return 0 1542 return 0
1536 } 1543 }
1537 1544
1538 func (x *ExposureDesired) GetProtocol() string { 1545 func (x *ExposureSpec) GetProtocol() string {
1539 if x != nil { 1546 if x != nil {
1540 return x.Protocol 1547 return x.Protocol
1541 } 1548 }
1542 return "" 1549 return ""
1543 } 1550 }
1544 1551
1545 // ExposureActual is one exposure's state as its host observes it: "active" 1552 // ExposureStatus is one exposure's state as its host observes it: "active"
1546 // once the host socket is bound, "failed" with the OS error otherwise. 1553 // once the host socket is bound, "failed" with the OS error otherwise.
1547 // "active" means the HOST half of the pipe exists — whether anything answers 1554 // "active" means the HOST half of the pipe exists — whether anything answers
1548 // inside the guest is the guest's half, and this does not pretend otherwise. 1555 // inside the guest is the guest's half, and this does not pretend otherwise.
1549 type ExposureActual struct { 1556 type ExposureStatus struct {
1550 state protoimpl.MessageState `protogen:"open.v1"` 1557 state protoimpl.MessageState `protogen:"open.v1"`
1551 Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 1558 Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
1552 State string `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` // "active"|"failed" 1559 State string `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` // "active"|"failed"
@@ -1556,20 +1563,20 @@ type ExposureActual struct {
1556 sizeCache protoimpl.SizeCache 1563 sizeCache protoimpl.SizeCache
1557 } 1564 }
1558 1565
1559 func (x *ExposureActual) Reset() { 1566 func (x *ExposureStatus) Reset() {
1560 *x = ExposureActual{} 1567 *x = ExposureStatus{}
1561 mi := &file_proto_eitri_v1_sync_proto_msgTypes[17] 1568 mi := &file_proto_eitri_v1_sync_proto_msgTypes[17]
1562 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 1569 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1563 ms.StoreMessageInfo(mi) 1570 ms.StoreMessageInfo(mi)
1564 } 1571 }
1565 1572
1566 func (x *ExposureActual) String() string { 1573 func (x *ExposureStatus) String() string {
1567 return protoimpl.X.MessageStringOf(x) 1574 return protoimpl.X.MessageStringOf(x)
1568 } 1575 }
1569 1576
1570 func (*ExposureActual) ProtoMessage() {} 1577 func (*ExposureStatus) ProtoMessage() {}
1571 1578
1572 func (x *ExposureActual) ProtoReflect() protoreflect.Message { 1579 func (x *ExposureStatus) ProtoReflect() protoreflect.Message {
1573 mi := &file_proto_eitri_v1_sync_proto_msgTypes[17] 1580 mi := &file_proto_eitri_v1_sync_proto_msgTypes[17]
1574 if x != nil { 1581 if x != nil {
1575 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 1582 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -1581,33 +1588,33 @@ func (x *ExposureActual) ProtoReflect() protoreflect.Message {
1581 return mi.MessageOf(x) 1588 return mi.MessageOf(x)
1582 } 1589 }
1583 1590
1584 // Deprecated: Use ExposureActual.ProtoReflect.Descriptor instead. 1591 // Deprecated: Use ExposureStatus.ProtoReflect.Descriptor instead.
1585 func (*ExposureActual) Descriptor() ([]byte, []int) { 1592 func (*ExposureStatus) Descriptor() ([]byte, []int) {
1586 return file_proto_eitri_v1_sync_proto_rawDescGZIP(), []int{17} 1593 return file_proto_eitri_v1_sync_proto_rawDescGZIP(), []int{17}
1587 } 1594 }
1588 1595
1589 func (x *ExposureActual) GetId() string { 1596 func (x *ExposureStatus) GetId() string {
1590 if x != nil { 1597 if x != nil {
1591 return x.Id 1598 return x.Id
1592 } 1599 }
1593 return "" 1600 return ""
1594 } 1601 }
1595 1602
1596 func (x *ExposureActual) GetState() string { 1603 func (x *ExposureStatus) GetState() string {
1597 if x != nil { 1604 if x != nil {
1598 return x.State 1605 return x.State
1599 } 1606 }
1600 return "" 1607 return ""
1601 } 1608 }
1602 1609
1603 func (x *ExposureActual) GetReason() string { 1610 func (x *ExposureStatus) GetReason() string {
1604 if x != nil { 1611 if x != nil {
1605 return x.Reason 1612 return x.Reason
1606 } 1613 }
1607 return "" 1614 return ""
1608 } 1615 }
1609 1616
1610 func (x *ExposureActual) GetSessions() *ExposureSessions { 1617 func (x *ExposureStatus) GetSessions() *ExposureSessions {
1611 if x != nil { 1618 if x != nil {
1612 return x.Sessions 1619 return x.Sessions
1613 } 1620 }
@@ -1700,16 +1707,16 @@ var File_proto_eitri_v1_sync_proto protoreflect.FileDescriptor
1700 1707
1701 const file_proto_eitri_v1_sync_proto_rawDesc = "" + 1708 const file_proto_eitri_v1_sync_proto_rawDesc = "" +
1702 "\n" + 1709 "\n" +
1703 "\x19proto/eitri/v1/sync.proto\x12\beitri.v1\"\xed\x01\n" + 1710 "\x19proto/eitri/v1/sync.proto\x12\beitri.v1\"\xe2\x01\n" +
1704 "\fAgentMessage\x12'\n" + 1711 "\fAgentMessage\x12'\n" +
1705 "\x05hello\x18\x01 \x01(\v2\x0f.eitri.v1.HelloH\x00R\x05hello\x125\n" + 1712 "\x05hello\x18\x01 \x01(\v2\x0f.eitri.v1.HelloH\x00R\x05hello\x12*\n" +
1706 "\x06report\x18\x02 \x01(\v2\x1b.eitri.v1.ActualStateReportH\x00R\x06report\x12@\n" + 1713 "\x06report\x18\x02 \x01(\v2\x10.eitri.v1.ReportH\x00R\x06report\x12@\n" +
1707 "\x0econsole_opened\x18\x03 \x01(\v2\x17.eitri.v1.ConsoleOpenedH\x00R\rconsoleOpened\x124\n" + 1714 "\x0econsole_opened\x18\x03 \x01(\v2\x17.eitri.v1.ConsoleOpenedH\x00R\rconsoleOpened\x124\n" +
1708 "\n" + 1715 "\n" +
1709 "tcp_opened\x18\x04 \x01(\v2\x13.eitri.v1.TCPOpenedH\x00R\ttcpOpenedB\x05\n" + 1716 "tcp_opened\x18\x04 \x01(\v2\x13.eitri.v1.TCPOpenedH\x00R\ttcpOpenedB\x05\n" +
1710 "\x03msg\"\xc0\x01\n" + 1717 "\x03msg\"\xb4\x01\n" +
1711 "\rServerMessage\x12<\n" + 1718 "\rServerMessage\x120\n" +
1712 "\bsnapshot\x18\x01 \x01(\v2\x1e.eitri.v1.DesiredStateSnapshotH\x00R\bsnapshot\x12:\n" + 1719 "\bsnapshot\x18\x01 \x01(\v2\x12.eitri.v1.SnapshotH\x00R\bsnapshot\x12:\n" +
1713 "\fconsole_open\x18\x02 \x01(\v2\x15.eitri.v1.ConsoleOpenH\x00R\vconsoleOpen\x12.\n" + 1720 "\fconsole_open\x18\x02 \x01(\v2\x15.eitri.v1.ConsoleOpenH\x00R\vconsoleOpen\x12.\n" +
1714 "\btcp_open\x18\x03 \x01(\v2\x11.eitri.v1.TCPOpenH\x00R\atcpOpenB\x05\n" + 1721 "\btcp_open\x18\x03 \x01(\v2\x11.eitri.v1.TCPOpenH\x00R\atcpOpenB\x05\n" +
1715 "\x03msg\"\xdd\x02\n" + 1722 "\x03msg\"\xdd\x02\n" +
@@ -1750,10 +1757,11 @@ const file_proto_eitri_v1_sync_proto_rawDesc = "" +
1750 "\fdisk_used_gb\x18\a \x01(\x03R\n" + 1757 "\fdisk_used_gb\x18\a \x01(\x03R\n" +
1751 "diskUsedGb\x12 \n" + 1758 "diskUsedGb\x12 \n" +
1752 "\fdisk_free_gb\x18\b \x01(\x03R\n" + 1759 "\fdisk_free_gb\x18\b \x01(\x03R\n" +
1753 "diskFreeGb\"\xe6\x01\n" + 1760 "diskFreeGb\"\xf1\x01\n" +
1754 "\bActualVM\x12\x13\n" + 1761 "\bVMStatus\x12\x13\n" +
1755 "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12\x14\n" + 1762 "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12\x1f\n" +
1756 "\x05power\x18\x02 \x01(\tR\x05power\x12\x14\n" + 1763 "\vpower_state\x18\x02 \x01(\tR\n" +
1764 "powerState\x12\x14\n" +
1757 "\x05phase\x18\x03 \x01(\tR\x05phase\x12\x0e\n" + 1765 "\x05phase\x18\x03 \x01(\tR\x05phase\x12\x0e\n" +
1758 "\x02ip\x18\x04 \x01(\tR\x02ip\x12\x1d\n" + 1766 "\x02ip\x18\x04 \x01(\tR\x02ip\x12\x1d\n" +
1759 "\n" + 1767 "\n" +
@@ -1767,9 +1775,9 @@ const file_proto_eitri_v1_sync_proto_rawDesc = "" +
1767 "\x04name\x18\x02 \x01(\tR\x04name\x12\x1f\n" + 1775 "\x04name\x18\x02 \x01(\tR\x04name\x12\x1f\n" +
1768 "\vvmspec_json\x18\x03 \x01(\fR\n" + 1776 "\vvmspec_json\x18\x03 \x01(\fR\n" +
1769 "vmspecJson\x12&\n" + 1777 "vmspecJson\x12&\n" +
1770 "\x0fdestroy_at_unix\x18\x04 \x01(\x03R\rdestroyAtUnix\"\xc5\x03\n" + 1778 "\x0fdestroy_at_unix\x18\x04 \x01(\x03R\rdestroyAtUnix\"\xba\x03\n" +
1771 "\x11ActualStateReport\x12$\n" + 1779 "\x06Report\x12$\n" +
1772 "\x03vms\x18\x01 \x03(\v2\x12.eitri.v1.ActualVMR\x03vms\x12\x1c\n" + 1780 "\x03vms\x18\x01 \x03(\v2\x12.eitri.v1.VMStatusR\x03vms\x12\x1c\n" +
1773 "\tdestroyed\x18\x02 \x03(\tR\tdestroyed\x129\n" + 1781 "\tdestroyed\x18\x02 \x03(\tR\tdestroyed\x129\n" +
1774 "\vquarantined\x18\x03 \x03(\v2\x17.eitri.v1.QuarantinedVMR\vquarantined\x12.\n" + 1782 "\vquarantined\x18\x03 \x03(\v2\x17.eitri.v1.QuarantinedVMR\vquarantined\x12.\n" +
1775 "\bcapacity\x18\x04 \x01(\v2\x12.eitri.v1.CapacityR\bcapacity\x12'\n" + 1783 "\bcapacity\x18\x04 \x01(\v2\x12.eitri.v1.CapacityR\bcapacity\x12'\n" +
@@ -1778,10 +1786,10 @@ const file_proto_eitri_v1_sync_proto_rawDesc = "" +
1778 "\ametrics\x18\a \x01(\v2\x15.eitri.v1.HostMetricsR\ametrics\x12\x1d\n" + 1786 "\ametrics\x18\a \x01(\v2\x15.eitri.v1.HostMetricsR\ametrics\x12\x1d\n" +
1779 "\n" + 1787 "\n" +
1780 "guest_cidr\x18\b \x01(\tR\tguestCidr\x126\n" + 1788 "guest_cidr\x18\b \x01(\tR\tguestCidr\x126\n" +
1781 "\texposures\x18\t \x03(\v2\x18.eitri.v1.ExposureActualR\texposures\x12(\n" + 1789 "\texposures\x18\t \x03(\v2\x18.eitri.v1.ExposureStatusR\texposures\x12(\n" +
1782 "\x10host_uplink_addr\x18\n" + 1790 "\x10host_uplink_addr\x18\n" +
1783 " \x01(\tR\x0ehostUplinkAddr\"\xbc\x04\n" + 1791 " \x01(\tR\x0ehostUplinkAddr\"\xb9\x04\n" +
1784 "\tVMDesired\x12\x13\n" + 1792 "\x06VMSpec\x12\x13\n" +
1785 "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12\x12\n" + 1793 "\x05vm_id\x18\x01 \x01(\tR\x04vmId\x12\x12\n" +
1786 "\x04name\x18\x02 \x01(\tR\x04name\x12\x1b\n" + 1794 "\x04name\x18\x02 \x01(\tR\x04name\x12\x1b\n" +
1787 "\timage_url\x18\x03 \x01(\tR\bimageUrl\x12!\n" + 1795 "\timage_url\x18\x03 \x01(\tR\bimageUrl\x12!\n" +
@@ -1804,12 +1812,12 @@ const file_proto_eitri_v1_sync_proto_rawDesc = "" +
1804 "\rssh_host_cert\x18\x11 \x01(\tR\vsshHostCert\x12<\n" + 1812 "\rssh_host_cert\x18\x11 \x01(\tR\vsshHostCert\x12<\n" +
1805 "\x1bssh_user_ca_authorized_keys\x18\x12 \x03(\tR\x17sshUserCaAuthorizedKeys\x12,\n" + 1813 "\x1bssh_user_ca_authorized_keys\x18\x12 \x03(\tR\x17sshUserCaAuthorizedKeys\x12,\n" +
1806 "\x12host_cert_required\x18\x13 \x01(\bR\x10hostCertRequired\x12\x18\n" + 1814 "\x12host_cert_required\x18\x13 \x01(\bR\x10hostCertRequired\x12\x18\n" +
1807 "\anetwork\x18\x14 \x01(\tR\anetworkJ\x04\b\r\x10\x0eJ\x04\b\x0e\x10\x0fJ\x04\b\x0f\x10\x10J\x04\b\x10\x10\x11R\x10ssh_host_key_pem\"\xc9\x01\n" + 1815 "\anetwork\x18\x14 \x01(\tR\anetworkJ\x04\b\r\x10\x0eJ\x04\b\x0e\x10\x0fJ\x04\b\x0f\x10\x10J\x04\b\x10\x10\x11R\x10ssh_host_key_pem\"\xb7\x01\n" +
1808 "\x14DesiredStateSnapshot\x12\x14\n" + 1816 "\bSnapshot\x12\x14\n" +
1809 "\x05epoch\x18\x01 \x01(\x04R\x05epoch\x12%\n" + 1817 "\x05epoch\x18\x01 \x01(\x04R\x05epoch\x12\"\n" +
1810 "\x03vms\x18\x02 \x03(\v2\x13.eitri.v1.VMDesiredR\x03vms\x12;\n" + 1818 "\x03vms\x18\x02 \x03(\v2\x10.eitri.v1.VMSpecR\x03vms\x12;\n" +
1811 "\ragent_upgrade\x18\x03 \x01(\v2\x16.eitri.v1.AgentUpgradeR\fagentUpgrade\x127\n" + 1819 "\ragent_upgrade\x18\x03 \x01(\v2\x16.eitri.v1.AgentUpgradeR\fagentUpgrade\x124\n" +
1812 "\texposures\x18\x04 \x03(\v2\x19.eitri.v1.ExposureDesiredR\texposures\"R\n" + 1820 "\texposures\x18\x04 \x03(\v2\x16.eitri.v1.ExposureSpecR\texposures\"R\n" +
1813 "\fAgentUpgrade\x12\x18\n" + 1821 "\fAgentUpgrade\x12\x18\n" +
1814 "\aversion\x18\x01 \x01(\tR\aversion\x12\x10\n" + 1822 "\aversion\x18\x01 \x01(\tR\aversion\x12\x10\n" +
1815 "\x03url\x18\x02 \x01(\tR\x03url\x12\x16\n" + 1823 "\x03url\x18\x02 \x01(\tR\x03url\x12\x16\n" +
@@ -1824,15 +1832,15 @@ const file_proto_eitri_v1_sync_proto_rawDesc = "" +
1824 "\x04port\x18\x02 \x01(\rR\x04port\"1\n" + 1832 "\x04port\x18\x02 \x01(\rR\x04port\"1\n" +
1825 "\tTCPOpened\x12\x0e\n" + 1833 "\tTCPOpened\x12\x0e\n" +
1826 "\x02ok\x18\x01 \x01(\bR\x02ok\x12\x14\n" + 1834 "\x02ok\x18\x01 \x01(\bR\x02ok\x12\x14\n" +
1827 "\x05error\x18\x02 \x01(\tR\x05error\"\x8e\x01\n" + 1835 "\x05error\x18\x02 \x01(\tR\x05error\"\x8b\x01\n" +
1828 "\x0fExposureDesired\x12\x0e\n" + 1836 "\fExposureSpec\x12\x0e\n" +
1829 "\x02id\x18\x01 \x01(\tR\x02id\x12\x13\n" + 1837 "\x02id\x18\x01 \x01(\tR\x02id\x12\x13\n" +
1830 "\x05vm_id\x18\x02 \x01(\tR\x04vmId\x12\x1d\n" + 1838 "\x05vm_id\x18\x02 \x01(\tR\x04vmId\x12\x1d\n" +
1831 "\n" + 1839 "\n" +
1832 "guest_port\x18\x03 \x01(\rR\tguestPort\x12\x1b\n" + 1840 "guest_port\x18\x03 \x01(\rR\tguestPort\x12\x1b\n" +
1833 "\thost_port\x18\x04 \x01(\rR\bhostPort\x12\x1a\n" + 1841 "\thost_port\x18\x04 \x01(\rR\bhostPort\x12\x1a\n" +
1834 "\bprotocol\x18\x05 \x01(\tR\bprotocol\"\x86\x01\n" + 1842 "\bprotocol\x18\x05 \x01(\tR\bprotocol\"\x86\x01\n" +
1835 "\x0eExposureActual\x12\x0e\n" + 1843 "\x0eExposureStatus\x12\x0e\n" +
1836 "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" + 1844 "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" +
1837 "\x05state\x18\x02 \x01(\tR\x05state\x12\x16\n" + 1845 "\x05state\x18\x02 \x01(\tR\x05state\x12\x16\n" +
1838 "\x06reason\x18\x03 \x01(\tR\x06reason\x126\n" + 1846 "\x06reason\x18\x03 \x01(\tR\x06reason\x126\n" +
@@ -1856,45 +1864,45 @@ func file_proto_eitri_v1_sync_proto_rawDescGZIP() []byte {
1856 1864
1857 var file_proto_eitri_v1_sync_proto_msgTypes = make([]protoimpl.MessageInfo, 19) 1865 var file_proto_eitri_v1_sync_proto_msgTypes = make([]protoimpl.MessageInfo, 19)
1858 var file_proto_eitri_v1_sync_proto_goTypes = []any{ 1866 var file_proto_eitri_v1_sync_proto_goTypes = []any{
1859 (*AgentMessage)(nil), // 0: eitri.v1.AgentMessage 1867 (*AgentMessage)(nil), // 0: eitri.v1.AgentMessage
1860 (*ServerMessage)(nil), // 1: eitri.v1.ServerMessage 1868 (*ServerMessage)(nil), // 1: eitri.v1.ServerMessage
1861 (*Hello)(nil), // 2: eitri.v1.Hello 1869 (*Hello)(nil), // 2: eitri.v1.Hello
1862 (*Capacity)(nil), // 3: eitri.v1.Capacity 1870 (*Capacity)(nil), // 3: eitri.v1.Capacity
1863 (*HostFacts)(nil), // 4: eitri.v1.HostFacts 1871 (*HostFacts)(nil), // 4: eitri.v1.HostFacts
1864 (*HostMetrics)(nil), // 5: eitri.v1.HostMetrics 1872 (*HostMetrics)(nil), // 5: eitri.v1.HostMetrics
1865 (*ActualVM)(nil), // 6: eitri.v1.ActualVM 1873 (*VMStatus)(nil), // 6: eitri.v1.VMStatus
1866 (*QuarantinedVM)(nil), // 7: eitri.v1.QuarantinedVM 1874 (*QuarantinedVM)(nil), // 7: eitri.v1.QuarantinedVM
1867 (*ActualStateReport)(nil), // 8: eitri.v1.ActualStateReport 1875 (*Report)(nil), // 8: eitri.v1.Report
1868 (*VMDesired)(nil), // 9: eitri.v1.VMDesired 1876 (*VMSpec)(nil), // 9: eitri.v1.VMSpec
1869 (*DesiredStateSnapshot)(nil), // 10: eitri.v1.DesiredStateSnapshot 1877 (*Snapshot)(nil), // 10: eitri.v1.Snapshot
1870 (*AgentUpgrade)(nil), // 11: eitri.v1.AgentUpgrade 1878 (*AgentUpgrade)(nil), // 11: eitri.v1.AgentUpgrade
1871 (*ConsoleOpen)(nil), // 12: eitri.v1.ConsoleOpen 1879 (*ConsoleOpen)(nil), // 12: eitri.v1.ConsoleOpen
1872 (*ConsoleOpened)(nil), // 13: eitri.v1.ConsoleOpened 1880 (*ConsoleOpened)(nil), // 13: eitri.v1.ConsoleOpened
1873 (*TCPOpen)(nil), // 14: eitri.v1.TCPOpen 1881 (*TCPOpen)(nil), // 14: eitri.v1.TCPOpen
1874 (*TCPOpened)(nil), // 15: eitri.v1.TCPOpened 1882 (*TCPOpened)(nil), // 15: eitri.v1.TCPOpened
1875 (*ExposureDesired)(nil), // 16: eitri.v1.ExposureDesired 1883 (*ExposureSpec)(nil), // 16: eitri.v1.ExposureSpec
1876 (*ExposureActual)(nil), // 17: eitri.v1.ExposureActual 1884 (*ExposureStatus)(nil), // 17: eitri.v1.ExposureStatus
1877 (*ExposureSessions)(nil), // 18: eitri.v1.ExposureSessions 1885 (*ExposureSessions)(nil), // 18: eitri.v1.ExposureSessions
1878 } 1886 }
1879 var file_proto_eitri_v1_sync_proto_depIdxs = []int32{ 1887 var file_proto_eitri_v1_sync_proto_depIdxs = []int32{
1880 2, // 0: eitri.v1.AgentMessage.hello:type_name -> eitri.v1.Hello 1888 2, // 0: eitri.v1.AgentMessage.hello:type_name -> eitri.v1.Hello
1881 8, // 1: eitri.v1.AgentMessage.report:type_name -> eitri.v1.ActualStateReport 1889 8, // 1: eitri.v1.AgentMessage.report:type_name -> eitri.v1.Report
1882 13, // 2: eitri.v1.AgentMessage.console_opened:type_name -> eitri.v1.ConsoleOpened 1890 13, // 2: eitri.v1.AgentMessage.console_opened:type_name -> eitri.v1.ConsoleOpened
1883 15, // 3: eitri.v1.AgentMessage.tcp_opened:type_name -> eitri.v1.TCPOpened 1891 15, // 3: eitri.v1.AgentMessage.tcp_opened:type_name -> eitri.v1.TCPOpened
1884 10, // 4: eitri.v1.ServerMessage.snapshot:type_name -> eitri.v1.DesiredStateSnapshot 1892 10, // 4: eitri.v1.ServerMessage.snapshot:type_name -> eitri.v1.Snapshot
1885 12, // 5: eitri.v1.ServerMessage.console_open:type_name -> eitri.v1.ConsoleOpen 1893 12, // 5: eitri.v1.ServerMessage.console_open:type_name -> eitri.v1.ConsoleOpen
1886 14, // 6: eitri.v1.ServerMessage.tcp_open:type_name -> eitri.v1.TCPOpen 1894 14, // 6: eitri.v1.ServerMessage.tcp_open:type_name -> eitri.v1.TCPOpen
1887 3, // 7: eitri.v1.Hello.capacity:type_name -> eitri.v1.Capacity 1895 3, // 7: eitri.v1.Hello.capacity:type_name -> eitri.v1.Capacity
1888 4, // 8: eitri.v1.Hello.facts:type_name -> eitri.v1.HostFacts 1896 4, // 8: eitri.v1.Hello.facts:type_name -> eitri.v1.HostFacts
1889 6, // 9: eitri.v1.ActualStateReport.vms:type_name -> eitri.v1.ActualVM 1897 6, // 9: eitri.v1.Report.vms:type_name -> eitri.v1.VMStatus
1890 7, // 10: eitri.v1.ActualStateReport.quarantined:type_name -> eitri.v1.QuarantinedVM 1898 7, // 10: eitri.v1.Report.quarantined:type_name -> eitri.v1.QuarantinedVM
1891 3, // 11: eitri.v1.ActualStateReport.capacity:type_name -> eitri.v1.Capacity 1899 3, // 11: eitri.v1.Report.capacity:type_name -> eitri.v1.Capacity
1892 5, // 12: eitri.v1.ActualStateReport.metrics:type_name -> eitri.v1.HostMetrics 1900 5, // 12: eitri.v1.Report.metrics:type_name -> eitri.v1.HostMetrics
1893 17, // 13: eitri.v1.ActualStateReport.exposures:type_name -> eitri.v1.ExposureActual 1901 17, // 13: eitri.v1.Report.exposures:type_name -> eitri.v1.ExposureStatus
1894 9, // 14: eitri.v1.DesiredStateSnapshot.vms:type_name -> eitri.v1.VMDesired 1902 9, // 14: eitri.v1.Snapshot.vms:type_name -> eitri.v1.VMSpec
1895 11, // 15: eitri.v1.DesiredStateSnapshot.agent_upgrade:type_name -> eitri.v1.AgentUpgrade 1903 11, // 15: eitri.v1.Snapshot.agent_upgrade:type_name -> eitri.v1.AgentUpgrade
1896 16, // 16: eitri.v1.DesiredStateSnapshot.exposures:type_name -> eitri.v1.ExposureDesired 1904 16, // 16: eitri.v1.Snapshot.exposures:type_name -> eitri.v1.ExposureSpec
1897 18, // 17: eitri.v1.ExposureActual.sessions:type_name -> eitri.v1.ExposureSessions 1905 18, // 17: eitri.v1.ExposureStatus.sessions:type_name -> eitri.v1.ExposureSessions
1898 18, // [18:18] is the sub-list for method output_type 1906 18, // [18:18] is the sub-list for method output_type
1899 18, // [18:18] is the sub-list for method input_type 1907 18, // [18:18] is the sub-list for method input_type
1900 18, // [18:18] is the sub-list for extension type_name 1908 18, // [18:18] is the sub-list for extension type_name
internal/server/api/api.go
Old New
@@ -657,7 +657,7 @@ func (a *API) buildVMResponses(vms []store.VM, states map[string]regState) []typ
657 if rs := states[vm.HostID]; rs.ok { 657 if rs := states[vm.HostID]; rs.ok {
658 for _, av := range rs.st.Report.VMs { 658 for _, av := range rs.st.Report.VMs {
659 if av.VMID == vm.ID { 659 if av.VMID == vm.ID {
660 actualPower = av.Power 660 actualPower = av.PowerState
661 phase = av.Phase 661 phase = av.Phase
662 statusDetail = av.StatusDetail 662 statusDetail = av.StatusDetail
663 break 663 break
internal/server/api/narration_test.go
Old New
@@ -34,8 +34,8 @@ func TestWhatAHostIsDoingReachesTheWire(t *testing.T) {
34 vmID := createTestVM(t, ts, host["host_id"], "web-1") 34 vmID := createTestVM(t, ts, host["host_id"], "web-1")
35 35
36 reg.UpdateReport(host["host_id"], registry.Report{ 36 reg.UpdateReport(host["host_id"], registry.Report{
37 VMs: []registry.ActualVM{{ 37 VMs: []registry.VMStatus{{
38 VMID: vmID, Power: "stopped", Phase: "creating", 38 VMID: vmID, PowerState: "stopped", Phase: "creating",
39 StatusDetail: "downloading image 1.2/3.7 GiB", 39 StatusDetail: "downloading image 1.2/3.7 GiB",
40 }}, 40 }},
41 }) 41 })
@@ -54,7 +54,7 @@ func TestAVMOnASilentAgentReadsAsItAlwaysDid(t *testing.T) {
54 vmID := createTestVM(t, ts, host["host_id"], "web-1") 54 vmID := createTestVM(t, ts, host["host_id"], "web-1")
55 55
56 reg.UpdateReport(host["host_id"], registry.Report{ 56 reg.UpdateReport(host["host_id"], registry.Report{
57 VMs: []registry.ActualVM{{VMID: vmID, Power: "stopped", Phase: "creating"}}, 57 VMs: []registry.VMStatus{{VMID: vmID, PowerState: "stopped", Phase: "creating"}},
58 }) 58 })
59 59
60 vm := vmByID(t, ts.URL, vmID) 60 vm := vmByID(t, ts.URL, vmID)
internal/server/registry/registry.go
Old New
@@ -36,8 +36,8 @@ type Metrics struct {
36 DiskUsedGB, DiskFreeGB int64 36 DiskUsedGB, DiskFreeGB int64
37 } 37 }
38 38
39 type ActualVM struct { 39 type VMStatus struct {
40 VMID, Power, Phase, IP, LastError string 40 VMID, PowerState, Phase, IP, LastError string
41 // StatusDetail is what the host is doing about this VM right now, in the 41 // StatusDetail is what the host is doing about this VM right now, in the
42 // host's own words. Empty is the normal state of a settled VM, and is also 42 // host's own words. Empty is the normal state of a settled VM, and is also
43 // what an agent too old to say anything leaves behind. 43 // what an agent too old to say anything leaves behind.
@@ -70,7 +70,7 @@ type ExposureStatus struct {
70 type ExposureSessions struct{ Active, Refused, Dropped int64 } 70 type ExposureSessions struct{ Active, Refused, Dropped int64 }
71 71
72 type Report struct { 72 type Report struct {
73 VMs []ActualVM 73 VMs []VMStatus
74 Quarantined []QuarantinedVM 74 Quarantined []QuarantinedVM
75 Exposures []ExposureStatus 75 Exposures []ExposureStatus
76 Capacity Capacity 76 Capacity Capacity
internal/server/registry/registry_test.go
Old New
@@ -11,7 +11,7 @@ import (
11 func TestGetReturnsDefensiveCopy(t *testing.T) { 11 func TestGetReturnsDefensiveCopy(t *testing.T) {
12 r := New(time.Now) 12 r := New(time.Now)
13 r.UpdateReport("h1", Report{ 13 r.UpdateReport("h1", Report{
14 VMs: []ActualVM{{VMID: "vm1", Phase: "ready"}}, 14 VMs: []VMStatus{{VMID: "vm1", Phase: "ready"}},
15 Quarantined: []QuarantinedVM{{VMID: "q1", VMSpecJSON: []byte("{}")}}, 15 Quarantined: []QuarantinedVM{{VMID: "q1", VMSpecJSON: []byte("{}")}},
16 }) 16 })
17 st, _ := r.Get("h1") 17 st, _ := r.Get("h1")
@@ -26,7 +26,7 @@ func TestReportRoundTripsAndOnlineWindow(t *testing.T) {
26 now := time.Now() 26 now := time.Now()
27 r := New(func() time.Time { return now }) 27 r := New(func() time.Time { return now })
28 r.UpdateReport("h1", Report{ 28 r.UpdateReport("h1", Report{
29 VMs: []ActualVM{{VMID: "vm1", Power: "running", Phase: "ready", IP: "10.77.1.2"}}, 29 VMs: []VMStatus{{VMID: "vm1", PowerState: "running", Phase: "ready", IP: "10.77.1.2"}},
30 Capacity: Capacity{VCPUs: 8, MemMB: 16384, DiskGB: 200}, 30 Capacity: Capacity{VCPUs: 8, MemMB: 16384, DiskGB: 200},
31 LastSeenEpoch: 4, 31 LastSeenEpoch: 4,
32 }) 32 })
internal/server/store/store.go
Old New
@@ -477,7 +477,7 @@ func (s *Store) Close() error { return s.db.Close() }
477 func (s *Store) Ping(ctx context.Context) error { return s.db.PingContext(ctx) } 477 func (s *Store) Ping(ctx context.Context) error { return s.db.PingContext(ctx) }
478 478
479 // Epoch reads the current epoch value directly. Production callers get the 479 // Epoch reads the current epoch value directly. Production callers get the
480 // epoch via DesiredForHost (paired with a matching desired-VM read in the 480 // epoch via SnapshotForHost (paired with a matching desired-VM read in the
481 // same tx); this exists as a test/observability hook for asserting exactly 481 // same tx); this exists as a test/observability hook for asserting exactly
482 // which mutations bump the epoch. 482 // which mutations bump the epoch.
483 func (s *Store) Epoch() (uint64, error) { 483 func (s *Store) Epoch() (uint64, error) {
@@ -1545,7 +1545,7 @@ func scanVM(rows *sql.Rows) (VM, error) {
1545 // queryVMs runs a vmColumns-projected SELECT against vms, with where appended 1545 // queryVMs runs a vmColumns-projected SELECT against vms, with where appended
1546 // verbatim after `FROM vms` (e.g. " WHERE id=?", or "" for none) and args 1546 // verbatim after `FROM vms` (e.g. " WHERE id=?", or "" for none) and args
1547 // bound in order, scanning every matching row. listVMs, VMByTenantName, GetVM, 1547 // bound in order, scanning every matching row. listVMs, VMByTenantName, GetVM,
1548 // and DesiredForHost all share this — they differ only in WHERE clause, 1548 // and SnapshotForHost all share this — they differ only in WHERE clause,
1549 // row-count expectations, and whether q is *sql.DB or an in-flight *sql.Tx. 1549 // row-count expectations, and whether q is *sql.DB or an in-flight *sql.Tx.
1550 func queryVMs(q querier, where string, args ...any) ([]VM, error) { 1550 func queryVMs(q querier, where string, args ...any) ([]VM, error) {
1551 rows, err := q.Query(`SELECT `+vmColumns+` FROM vms`+where, args...) 1551 rows, err := q.Query(`SELECT `+vmColumns+` FROM vms`+where, args...)
@@ -1624,7 +1624,7 @@ func (s *Store) Snapshot() ([]Host, map[string]Alloc, []VM, error) {
1624 return hosts, alloc, vms, tx.Commit() 1624 return hosts, alloc, vms, tx.Commit()
1625 } 1625 }
1626 1626
1627 func (s *Store) DesiredForHost(hostID string) (uint64, []VM, error) { 1627 func (s *Store) SnapshotForHost(hostID string) (uint64, []VM, error) {
1628 tx, err := s.db.Begin() 1628 tx, err := s.db.Begin()
1629 if err != nil { 1629 if err != nil {
1630 return 0, nil, err 1630 return 0, nil, err
internal/server/store/store_test.go
Old New
@@ -251,8 +251,8 @@ func TestRecordVMHostKeyCertifiesOnlyItsOwnHostsVM(t *testing.T) {
251 require.NoError(t, s.RecordVMHostKey("vm1", h.ID, pubkey, cert)) 251 require.NoError(t, s.RecordVMHostKey("vm1", h.ID, pubkey, cert))
252 252
253 // Both halves must round-trip through the read paths the snapshot and the 253 // Both halves must round-trip through the read paths the snapshot and the
254 // gate rely on: DesiredForHost (agent-facing) and VMByTenantName. 254 // gate rely on: SnapshotForHost (agent-facing) and VMByTenantName.
255 _, vms, err := s.DesiredForHost(h.ID) 255 _, vms, err := s.SnapshotForHost(h.ID)
256 require.NoError(t, err) 256 require.NoError(t, err)
257 require.Len(t, vms, 1) 257 require.Len(t, vms, 1)
258 assert.Equal(t, pubkey, vms[0].SSHHostPubKey) 258 assert.Equal(t, pubkey, vms[0].SSHHostPubKey)
@@ -610,7 +610,7 @@ func TestEnrollmentAssignsSequentialCIDRsAndIsOneTimeUse(t *testing.T) {
610 assert.Equal(t, "10.77.2.0/24", h2.BridgeCIDR) 610 assert.Equal(t, "10.77.2.0/24", h2.BridgeCIDR)
611 } 611 }
612 612
613 func TestDesiredStateMutationsBumpEpochButStatusWritesDoNot(t *testing.T) { 613 func TestSpecMutationsBumpEpochButStatusWritesDoNot(t *testing.T) {
614 s := newStore(t) 614 s := newStore(t)
615 h := enrollHost(t, s) 615 h := enrollHost(t, s)
616 e0, _ := s.Epoch() 616 e0, _ := s.Epoch()
@@ -652,13 +652,13 @@ func TestNameUniqueAmongLiveRowsOnly(t *testing.T) {
652 assert.NoError(t, s.CreateVM(mk("vm3")), "tombstoned row must not block the name") 652 assert.NoError(t, s.CreateVM(mk("vm3")), "tombstoned row must not block the name")
653 } 653 }
654 654
655 func TestDesiredForHostIncludesTombstonedAndEpochConsistently(t *testing.T) { 655 func TestSnapshotForHostIncludesTombstonedAndEpochConsistently(t *testing.T) {
656 s := newStore(t) 656 s := newStore(t)
657 h := enrollHost(t, s) 657 h := enrollHost(t, s)
658 require.NoError(t, s.CreateVM(VM{ID: "vm1", HostID: h.ID, Name: "a", ImageURL: "u", 658 require.NoError(t, s.CreateVM(VM{ID: "vm1", HostID: h.ID, Name: "a", ImageURL: "u",
659 ImageSHA256: "s", VCPUs: 1, MemMB: 512, DiskGB: 5, PowerState: "running"})) 659 ImageSHA256: "s", VCPUs: 1, MemMB: 512, DiskGB: 5, PowerState: "running"}))
660 require.NoError(t, s.TombstoneVM("vm1")) 660 require.NoError(t, s.TombstoneVM("vm1"))
661 epoch, vms, err := s.DesiredForHost(h.ID) 661 epoch, vms, err := s.SnapshotForHost(h.ID)
662 require.NoError(t, err) 662 require.NoError(t, err)
663 e, _ := s.Epoch() 663 e, _ := s.Epoch()
664 assert.Equal(t, e, epoch) 664 assert.Equal(t, e, epoch)
internal/server/syncsvc/exposures_test.go
Old New
@@ -59,8 +59,8 @@ func TestSnapshotDropsExposuresOfATombstonedVM(t *testing.T) {
59 func TestApplyReportFoldsExposureStateIntoTheRegistry(t *testing.T) { 59 func TestApplyReportFoldsExposureStateIntoTheRegistry(t *testing.T) {
60 f := setup(t) 60 f := setup(t)
61 61
62 f.svc.applyReport(f.host.ID, &pb.ActualStateReport{ 62 f.svc.applyReport(f.host.ID, &pb.Report{
63 Exposures: []*pb.ExposureActual{ 63 Exposures: []*pb.ExposureStatus{
64 {Id: "e1", State: "active"}, 64 {Id: "e1", State: "active"},
65 {Id: "e2", State: "failed", Reason: "listen tcp 0.0.0.0:30080: bind: address already in use"}, 65 {Id: "e2", State: "failed", Reason: "listen tcp 0.0.0.0:30080: bind: address already in use"},
66 }, 66 },
@@ -79,12 +79,12 @@ func TestApplyReportFoldsExposureStateIntoTheRegistry(t *testing.T) {
79 func TestApplyReportRecordsTheHostUplinkAddress(t *testing.T) { 79 func TestApplyReportRecordsTheHostUplinkAddress(t *testing.T) {
80 f := setup(t) 80 f := setup(t)
81 81
82 f.svc.applyReport(f.host.ID, &pb.ActualStateReport{HostUplinkAddr: "192.168.0.190"}) 82 f.svc.applyReport(f.host.ID, &pb.Report{HostUplinkAddr: "192.168.0.190"})
83 h, err := f.st.GetHost(f.host.ID) 83 h, err := f.st.GetHost(f.host.ID)
84 require.NoError(t, err) 84 require.NoError(t, err)
85 assert.Equal(t, "192.168.0.190", h.UplinkAddr) 85 assert.Equal(t, "192.168.0.190", h.UplinkAddr)
86 86
87 f.svc.applyReport(f.host.ID, &pb.ActualStateReport{}) 87 f.svc.applyReport(f.host.ID, &pb.Report{})
88 h, err = f.st.GetHost(f.host.ID) 88 h, err = f.st.GetHost(f.host.ID)
89 require.NoError(t, err) 89 require.NoError(t, err)
90 assert.Equal(t, "192.168.0.190", h.UplinkAddr, "silence is not a statement") 90 assert.Equal(t, "192.168.0.190", h.UplinkAddr, "silence is not a statement")
internal/server/syncsvc/hostcert.go
Old New
@@ -64,7 +64,7 @@ func (s *Service) signAndRecordHostCert(hostID, vmID, pubLine string) error {
64 // 64 //
65 // A failure is logged and dropped, like every other per-VM failure in a report: 65 // A failure is logged and dropped, like every other per-VM failure in a report:
66 // the key rides every later report too, so the next tick tries again. 66 // the key rides every later report too, so the next tick tries again.
67 func (s *Service) certifyReportedHostKeys(hostID string, vms []*pb.ActualVM) { 67 func (s *Service) certifyReportedHostKeys(hostID string, vms []*pb.VMStatus) {
68 if s.certs == nil { 68 if s.certs == nil {
69 return 69 return
70 } 70 }
internal/server/syncsvc/hostcert_test.go
Old New
@@ -45,9 +45,9 @@ func testPubKey(t *testing.T) string {
45 45
46 // reportKey builds the report a host sends for a VM that has generated its host 46 // reportKey builds the report a host sends for a VM that has generated its host
47 // key and is waiting for the certificate. 47 // key and is waiting for the certificate.
48 func reportKey(vmID, pubkey string) *pb.ActualStateReport { 48 func reportKey(vmID, pubkey string) *pb.Report {
49 return &pb.ActualStateReport{Vms: []*pb.ActualVM{ 49 return &pb.Report{Vms: []*pb.VMStatus{
50 {VmId: vmID, Phase: "creating", Power: "stopped", SshHostPubkey: pubkey}, 50 {VmId: vmID, Phase: "creating", PowerState: "stopped", SshHostPubkey: pubkey},
51 }} 51 }}
52 } 52 }
53 53
internal/server/syncsvc/narration_test.go
Old New
@@ -15,9 +15,9 @@ import (
15 func TestApplyReportCarriesWhatAHostIsDoing(t *testing.T) { 15 func TestApplyReportCarriesWhatAHostIsDoing(t *testing.T) {
16 f := setup(t) 16 f := setup(t)
17 17
18 f.svc.applyReport(f.host.ID, &pb.ActualStateReport{ 18 f.svc.applyReport(f.host.ID, &pb.Report{
19 Vms: []*pb.ActualVM{{ 19 Vms: []*pb.VMStatus{{
20 VmId: "vm1", Power: "stopped", Phase: "creating", 20 VmId: "vm1", PowerState: "stopped", Phase: "creating",
21 StatusDetail: "downloading image 1.2/3.7 GiB", 21 StatusDetail: "downloading image 1.2/3.7 GiB",
22 }}, 22 }},
23 }) 23 })
@@ -35,8 +35,8 @@ func TestApplyReportCarriesWhatAHostIsDoing(t *testing.T) {
35 func TestApplyReportFromAnAgentThatSaysNothing(t *testing.T) { 35 func TestApplyReportFromAnAgentThatSaysNothing(t *testing.T) {
36 f := setup(t) 36 f := setup(t)
37 37
38 f.svc.applyReport(f.host.ID, &pb.ActualStateReport{ 38 f.svc.applyReport(f.host.ID, &pb.Report{
39 Vms: []*pb.ActualVM{{VmId: "vm1", Power: "stopped", Phase: "creating"}}, 39 Vms: []*pb.VMStatus{{VmId: "vm1", PowerState: "stopped", Phase: "creating"}},
40 }) 40 })
41 41
42 got, ok := f.reg.Get(f.host.ID) 42 got, ok := f.reg.Get(f.host.ID)
@@ -51,8 +51,8 @@ func TestApplyReportFromAnAgentThatSaysNothing(t *testing.T) {
51 func TestApplyReportCountsExposureSessions(t *testing.T) { 51 func TestApplyReportCountsExposureSessions(t *testing.T) {
52 f := setup(t) 52 f := setup(t)
53 53
54 f.svc.applyReport(f.host.ID, &pb.ActualStateReport{ 54 f.svc.applyReport(f.host.ID, &pb.Report{
55 Exposures: []*pb.ExposureActual{{ 55 Exposures: []*pb.ExposureStatus{{
56 Id: "e1", State: "active", 56 Id: "e1", State: "active",
57 Sessions: &pb.ExposureSessions{Active: 7, Refused: 12, Dropped: 3}, 57 Sessions: &pb.ExposureSessions{Active: 7, Refused: 12, Dropped: 3},
58 }}, 58 }},
@@ -73,8 +73,8 @@ func TestApplyReportCountsExposureSessions(t *testing.T) {
73 func TestApplyReportFromAnAgentThatCountsNothing(t *testing.T) { 73 func TestApplyReportFromAnAgentThatCountsNothing(t *testing.T) {
74 f := setup(t) 74 f := setup(t)
75 75
76 f.svc.applyReport(f.host.ID, &pb.ActualStateReport{ 76 f.svc.applyReport(f.host.ID, &pb.Report{
77 Exposures: []*pb.ExposureActual{{Id: "e1", State: "active"}}, 77 Exposures: []*pb.ExposureStatus{{Id: "e1", State: "active"}},
78 }) 78 })
79 79
80 got, ok := f.reg.Get(f.host.ID) 80 got, ok := f.reg.Get(f.host.ID)
internal/server/syncsvc/network_test.go
Old New
@@ -70,9 +70,9 @@ func TestApplyReportRecordsBothAddresses(t *testing.T) {
70 f := setup(t) 70 f := setup(t)
71 networkedVM(t, f, "vm1") 71 networkedVM(t, f, "vm1")
72 72
73 f.svc.applyReport(f.host.ID, &pb.ActualStateReport{ 73 f.svc.applyReport(f.host.ID, &pb.Report{
74 Vms: []*pb.ActualVM{{ 74 Vms: []*pb.VMStatus{{
75 VmId: "vm1", Power: "running", Phase: "ready", 75 VmId: "vm1", PowerState: "running", Phase: "ready",
76 Ip: "10.77.1.2", NetworkIp: "192.168.0.42", 76 Ip: "10.77.1.2", NetworkIp: "192.168.0.42",
77 }}, 77 }},
78 }) 78 })
@@ -91,8 +91,8 @@ func TestApplyReportRecordsTheLeaseBeforeTheGuestIsReady(t *testing.T) {
91 f := setup(t) 91 f := setup(t)
92 networkedVM(t, f, "vm1") 92 networkedVM(t, f, "vm1")
93 93
94 f.svc.applyReport(f.host.ID, &pb.ActualStateReport{ 94 f.svc.applyReport(f.host.ID, &pb.Report{
95 Vms: []*pb.ActualVM{{VmId: "vm1", Power: "running", Phase: "creating", NetworkIp: "192.168.0.42"}}, 95 Vms: []*pb.VMStatus{{VmId: "vm1", PowerState: "running", Phase: "creating", NetworkIp: "192.168.0.42"}},
96 }) 96 })
97 97
98 vm, err := f.st.GetVM("vm1") 98 vm, err := f.st.GetVM("vm1")
@@ -107,9 +107,9 @@ func TestApplyReportRecordsTheLeaseBeforeTheGuestIsReady(t *testing.T) {
107 func TestApplyReportKeepsTheLastKnownLease(t *testing.T) { 107 func TestApplyReportKeepsTheLastKnownLease(t *testing.T) {
108 f := setup(t) 108 f := setup(t)
109 networkedVM(t, f, "vm1") 109 networkedVM(t, f, "vm1")
110 ready := func(netIP string) *pb.ActualStateReport { 110 ready := func(netIP string) *pb.Report {
111 return &pb.ActualStateReport{Vms: []*pb.ActualVM{{ 111 return &pb.Report{Vms: []*pb.VMStatus{{
112 VmId: "vm1", Power: "running", Phase: "ready", Ip: "10.77.1.2", NetworkIp: netIP, 112 VmId: "vm1", PowerState: "running", Phase: "ready", Ip: "10.77.1.2", NetworkIp: netIP,
113 }}} 113 }}}
114 } 114 }
115 115
@@ -147,8 +147,8 @@ func TestApplyReportKeepsTheLastKnownLease(t *testing.T) {
147 func TestApplyReportSkipsUnchangedLeases(t *testing.T) { 147 func TestApplyReportSkipsUnchangedLeases(t *testing.T) {
148 f := setup(t) 148 f := setup(t)
149 networkedVM(t, f, "vm1") 149 networkedVM(t, f, "vm1")
150 rep := &pb.ActualStateReport{Vms: []*pb.ActualVM{{ 150 rep := &pb.Report{Vms: []*pb.VMStatus{{
151 VmId: "vm1", Power: "running", Phase: "ready", Ip: "10.77.1.2", NetworkIp: "192.168.0.42", 151 VmId: "vm1", PowerState: "running", Phase: "ready", Ip: "10.77.1.2", NetworkIp: "192.168.0.42",
152 }}} 152 }}}
153 153
154 f.svc.applyReport(f.host.ID, rep) 154 f.svc.applyReport(f.host.ID, rep)
@@ -167,11 +167,11 @@ func TestApplyReportForgetsAReapedVMsLease(t *testing.T) {
167 f := setup(t) 167 f := setup(t)
168 networkedVM(t, f, "vm1") 168 networkedVM(t, f, "vm1")
169 169
170 f.svc.applyReport(f.host.ID, &pb.ActualStateReport{ 170 f.svc.applyReport(f.host.ID, &pb.Report{
171 Vms: []*pb.ActualVM{{VmId: "vm1", Power: "running", Phase: "ready", NetworkIp: "192.168.0.42"}}, 171 Vms: []*pb.VMStatus{{VmId: "vm1", PowerState: "running", Phase: "ready", NetworkIp: "192.168.0.42"}},
172 }) 172 })
173 require.NoError(t, f.st.TombstoneVM("vm1")) 173 require.NoError(t, f.st.TombstoneVM("vm1"))
174 f.svc.applyReport(f.host.ID, &pb.ActualStateReport{Destroyed: []string{"vm1"}}) 174 f.svc.applyReport(f.host.ID, &pb.Report{Destroyed: []string{"vm1"}})
175 175
176 f.svc.netIPTrack.mu.Lock() 176 f.svc.netIPTrack.mu.Lock()
177 defer f.svc.netIPTrack.mu.Unlock() 177 defer f.svc.netIPTrack.mu.Unlock()
internal/server/syncsvc/persistent_test.go
Old New
@@ -13,7 +13,7 @@ import (
13 // agent, but it has not stopped being on the wire, and this is the test that 13 // agent, but it has not stopped being on the wire, and this is the test that
14 // fails if someone finishes the job too early. 14 // fails if someone finishes the job too early.
15 // 15 //
16 // A v0.0.5 agent — which exists, in production — reads VMDesired.persistent as 16 // A v0.0.5 agent — which exists, in production — reads VMSpec.persistent as
17 // a restart policy, and proto3 gives an absent bool the value false. False 17 // a restart policy, and proto3 gives an absent bool the value false. False
18 // means ephemeral, and an ephemeral guest that goes lost is marked failed 18 // means ephemeral, and an ephemeral guest that goes lost is marked failed
19 // forever and never booted again. Every guest goes lost when its host reboots. 19 // forever and never booted again. Every guest goes lost when its host reboots.
internal/server/syncsvc/syncsvc.go
Old New
@@ -325,12 +325,12 @@ func (s *Service) handleConn(ctx context.Context, conn quic.Connection) {
325 325
326 // buildSnapshot reads hostID's desired state — its VMs and the exposures it 326 // buildSnapshot reads hostID's desired state — its VMs and the exposures it
327 // should be serving — and renders it as one snapshot. 327 // should be serving — and renders it as one snapshot.
328 func (s *Service) buildSnapshot(hostID string) (*pb.DesiredStateSnapshot, error) { 328 func (s *Service) buildSnapshot(hostID string) (*pb.Snapshot, error) {
329 epoch, vms, err := s.st.DesiredForHost(hostID) 329 epoch, vms, err := s.st.SnapshotForHost(hostID)
330 if err != nil { 330 if err != nil {
331 return nil, fmt.Errorf("desired for host: %w", err) 331 return nil, fmt.Errorf("desired for host: %w", err)
332 } 332 }
333 snap := &pb.DesiredStateSnapshot{Epoch: epoch, Vms: make([]*pb.VMDesired, 0, len(vms))} 333 snap := &pb.Snapshot{Epoch: epoch, Vms: make([]*pb.VMSpec, 0, len(vms))}
334 snap.AgentUpgrade = s.offerFor(hostID) 334 snap.AgentUpgrade = s.offerFor(hostID)
335 caCache := map[string][]string{} // tenant -> canonical CA lines, legacy rows only 335 caCache := map[string][]string{} // tenant -> canonical CA lines, legacy rows only
336 for _, v := range vms { 336 for _, v := range vms {
@@ -374,7 +374,7 @@ func (s *Service) buildSnapshot(hostID string) (*pb.DesiredStateSnapshot, error)
374 } 374 }
375 cas = legacy 375 cas = legacy
376 } 376 }
377 snap.Vms = append(snap.Vms, &pb.VMDesired{ 377 snap.Vms = append(snap.Vms, &pb.VMSpec{
378 VmId: v.ID, Name: v.Name, ImageUrl: v.ImageURL, ImageSha256: v.ImageSHA256, 378 VmId: v.ID, Name: v.Name, ImageUrl: v.ImageURL, ImageSha256: v.ImageSHA256,
379 CloudInit: v.CloudInit, Vcpus: v.VCPUs, MemMb: v.MemMB, DiskGb: v.DiskGB, 379 CloudInit: v.CloudInit, Vcpus: v.VCPUs, MemMb: v.MemMB, DiskGb: v.DiskGB,
380 // Always true, and no longer read from anywhere: every VM is 380 // Always true, and no longer read from anywhere: every VM is
@@ -406,7 +406,7 @@ func (s *Service) buildSnapshot(hostID string) (*pb.DesiredStateSnapshot, error)
406 // In-range by construction: the API validates ports and closes the 406 // In-range by construction: the API validates ports and closes the
407 // protocol at the two the agent can bind (validateExposure), and the 407 // protocol at the two the agent can bind (validateExposure), and the
408 // store accepts rows only from the API. 408 // store accepts rows only from the API.
409 snap.Exposures = append(snap.Exposures, &pb.ExposureDesired{ 409 snap.Exposures = append(snap.Exposures, &pb.ExposureSpec{
410 Id: e.ID, VmId: e.VMID, 410 Id: e.ID, VmId: e.VMID,
411 GuestPort: uint32(e.GuestPort), HostPort: uint32(e.HostPort), 411 GuestPort: uint32(e.GuestPort), HostPort: uint32(e.HostPort),
412 Protocol: e.Protocol, 412 Protocol: e.Protocol,
@@ -506,7 +506,7 @@ func (s *Service) failWrite(conn quic.Connection, hostID string, err error) {
506 506
507 // applyReport updates the registry and durably records VM status changes. 507 // applyReport updates the registry and durably records VM status changes.
508 // Errors within the report are logged and skipped — they must never kill the stream. 508 // Errors within the report are logged and skipped — they must never kill the stream.
509 func (s *Service) applyReport(hostID string, rep *pb.ActualStateReport) { 509 func (s *Service) applyReport(hostID string, rep *pb.Report) {
510 // Build registry report. 510 // Build registry report.
511 r := registry.Report{ 511 r := registry.Report{
512 LastSeenEpoch: rep.GetLastSeenEpoch(), 512 LastSeenEpoch: rep.GetLastSeenEpoch(),
@@ -647,17 +647,17 @@ func (s *Service) applyReport(hostID string, rep *pb.ActualStateReport) {
647 } 647 }
648 } 648 }
649 649
650 // toRegistryVMs maps reported ActualVMs to registry rows. Returns nil (not an 650 // toRegistryVMs maps reported VMStatus rows to registry rows. Returns nil (not an
651 // empty slice) for empty input, matching the original append-into-nil behavior. 651 // empty slice) for empty input, matching the original append-into-nil behavior.
652 func toRegistryVMs(in []*pb.ActualVM) []registry.ActualVM { 652 func toRegistryVMs(in []*pb.VMStatus) []registry.VMStatus {
653 if len(in) == 0 { 653 if len(in) == 0 {
654 return nil 654 return nil
655 } 655 }
656 out := make([]registry.ActualVM, 0, len(in)) 656 out := make([]registry.VMStatus, 0, len(in))
657 for _, v := range in { 657 for _, v := range in {
658 out = append(out, registry.ActualVM{ 658 out = append(out, registry.VMStatus{
659 VMID: v.GetVmId(), 659 VMID: v.GetVmId(),
660 Power: v.GetPower(), 660 PowerState: v.GetPowerState(),
661 Phase: v.GetPhase(), 661 Phase: v.GetPhase(),
662 IP: v.GetIp(), 662 IP: v.GetIp(),
663 LastError: v.GetLastError(), 663 LastError: v.GetLastError(),
@@ -687,7 +687,7 @@ func toRegistryQuarantined(in []*pb.QuarantinedVM) []registry.QuarantinedVM {
687 687
688 // toRegistryExposures maps reported exposure state to registry rows. Returns 688 // toRegistryExposures maps reported exposure state to registry rows. Returns
689 // nil (not an empty slice) for empty input, matching append-into-nil behavior. 689 // nil (not an empty slice) for empty input, matching append-into-nil behavior.
690 func toRegistryExposures(in []*pb.ExposureActual) []registry.ExposureStatus { 690 func toRegistryExposures(in []*pb.ExposureStatus) []registry.ExposureStatus {
691 if len(in) == 0 { 691 if len(in) == 0 {
692 return nil 692 return nil
693 } 693 }
internal/server/syncsvc/syncsvc_test.go
Old New
@@ -200,7 +200,7 @@ func TestReportWritesThroughAndHardDeletesAckedTombstones(t *testing.T) {
200 c.recv(t) // initial snapshot 200 c.recv(t) // initial snapshot
201 201
202 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{ 202 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{
203 Report: &pb.ActualStateReport{ 203 Report: &pb.Report{
204 Destroyed: []string{"vm1"}, // level-triggered ack 204 Destroyed: []string{"vm1"}, // level-triggered ack
205 Capacity: &pb.Capacity{Vcpus: 8}, 205 Capacity: &pb.Capacity{Vcpus: 8},
206 LastSeenEpoch: 2, 206 LastSeenEpoch: 2,
@@ -256,8 +256,8 @@ func TestApplyReportSkipsUnchangedWrites(t *testing.T) {
256 256
257 ready := func(ip string) *pb.AgentMessage { 257 ready := func(ip string) *pb.AgentMessage {
258 return &pb.AgentMessage{Msg: &pb.AgentMessage_Report{ 258 return &pb.AgentMessage{Msg: &pb.AgentMessage_Report{
259 Report: &pb.ActualStateReport{Vms: []*pb.ActualVM{ 259 Report: &pb.Report{Vms: []*pb.VMStatus{
260 {VmId: "vm1", Power: "running", Phase: "ready", Ip: ip}, 260 {VmId: "vm1", PowerState: "running", Phase: "ready", Ip: ip},
261 }}}} 261 }}}}
262 } 262 }
263 263
@@ -295,8 +295,8 @@ func TestReportWithValidIPUpdatesRegistryAndStore(t *testing.T) {
295 c.recv(t) 295 c.recv(t)
296 296
297 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{ 297 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{
298 Report: &pb.ActualStateReport{Vms: []*pb.ActualVM{ 298 Report: &pb.Report{Vms: []*pb.VMStatus{
299 {VmId: "vm1", Power: "running", Phase: "ready", Ip: "10.77.1.2"}, 299 {VmId: "vm1", PowerState: "running", Phase: "ready", Ip: "10.77.1.2"},
300 }}}}) 300 }}}})
301 301
302 require.Eventually(t, func() bool { 302 require.Eventually(t, func() bool {
@@ -320,8 +320,8 @@ func TestReportRecoversFromAnUnusableAddress(t *testing.T) {
320 320
321 ready := func(ip string) *pb.AgentMessage { 321 ready := func(ip string) *pb.AgentMessage {
322 return &pb.AgentMessage{Msg: &pb.AgentMessage_Report{ 322 return &pb.AgentMessage{Msg: &pb.AgentMessage_Report{
323 Report: &pb.ActualStateReport{Vms: []*pb.ActualVM{ 323 Report: &pb.Report{Vms: []*pb.VMStatus{
324 {VmId: "vm1", Power: "running", Phase: "ready", Ip: ip}, 324 {VmId: "vm1", PowerState: "running", Phase: "ready", Ip: ip},
325 }}}} 325 }}}}
326 } 326 }
327 327
@@ -356,7 +356,7 @@ func TestHardDeleteTriggersRepush(t *testing.T) {
356 356
357 // Send a report acking the destroyed VM. 357 // Send a report acking the destroyed VM.
358 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{ 358 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{
359 Report: &pb.ActualStateReport{ 359 Report: &pb.Report{
360 Destroyed: []string{"vm1"}, 360 Destroyed: []string{"vm1"},
361 LastSeenEpoch: snap.Epoch, 361 LastSeenEpoch: snap.Epoch,
362 }}}) 362 }}})
@@ -623,7 +623,7 @@ func TestMaxAgeEnforcedMidSession(t *testing.T) {
623 623
624 time.Sleep(1800 * time.Millisecond) // credential ages past maxCredAge 624 time.Sleep(1800 * time.Millisecond) // credential ages past maxCredAge
625 625
626 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{Report: &pb.ActualStateReport{}}}) 626 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{Report: &pb.Report{}}})
627 // The server must close the connection with CodeAuthRejected; the next 627 // The server must close the connection with CodeAuthRejected; the next
628 // read on the down-stream surfaces it. 628 // read on the down-stream surfaces it.
629 var msg pb.ServerMessage 629 var msg pb.ServerMessage
@@ -815,7 +815,7 @@ func TestReportMetricsLandInRegistry(t *testing.T) {
815 c := mustDial(t, f) 815 c := mustDial(t, f)
816 c.recv(t) // initial snapshot 816 c.recv(t) // initial snapshot
817 817
818 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{Report: &pb.ActualStateReport{ 818 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{Report: &pb.Report{
819 Capacity: &pb.Capacity{Vcpus: 8}, 819 Capacity: &pb.Capacity{Vcpus: 8},
820 Metrics: &pb.HostMetrics{UptimeS: 3600, Load1: 1.5, MemUsedMb: 2048, MemAvailableMb: 6144, DiskUsedGb: 20, DiskFreeGb: 80}, 820 Metrics: &pb.HostMetrics{UptimeS: 3600, Load1: 1.5, MemUsedMb: 2048, MemAvailableMb: 6144, DiskUsedGb: 20, DiskFreeGb: 80},
821 LastSeenEpoch: 2, 821 LastSeenEpoch: 2,
@@ -934,7 +934,7 @@ func TestReportRecordsTheHostsGuestSubnet(t *testing.T) {
934 c.recv(t) 934 c.recv(t)
935 935
936 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{ 936 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{
937 Report: &pb.ActualStateReport{GuestCidr: "192.168.64.0/24"}}}) 937 Report: &pb.Report{GuestCidr: "192.168.64.0/24"}}})
938 require.Eventually(t, func() bool { 938 require.Eventually(t, func() bool {
939 h, err := f.st.GetHost(f.host.ID) 939 h, err := f.st.GetHost(f.host.ID)
940 return err == nil && h.BridgeCIDR == "192.168.64.0/24" 940 return err == nil && h.BridgeCIDR == "192.168.64.0/24"
@@ -943,7 +943,7 @@ func TestReportRecordsTheHostsGuestSubnet(t *testing.T) {
943 // Empty is "no answer", never "no network": a host that cannot see its own 943 // Empty is "no answer", never "no network": a host that cannot see its own
944 // network yet must not erase what it told us when it could. 944 // network yet must not erase what it told us when it could.
945 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{ 945 c.send(t, &pb.AgentMessage{Msg: &pb.AgentMessage_Report{
946 Report: &pb.ActualStateReport{GuestCidr: ""}}}) 946 Report: &pb.Report{GuestCidr: ""}}})
947 require.Never(t, func() bool { 947 require.Never(t, func() bool {
948 h, err := f.st.GetHost(f.host.ID) 948 h, err := f.st.GetHost(f.host.ID)
949 return err == nil && h.BridgeCIDR != "192.168.64.0/24" 949 return err == nil && h.BridgeCIDR != "192.168.64.0/24"
internal/transport/contract_test.go
Old New
@@ -18,13 +18,13 @@ import (
18 // the framing is altered incompatibly, proto.Equal fails here. 18 // the framing is altered incompatibly, proto.Equal fails here.
19 19
20 func TestAgentMessageReportRoundTrip(t *testing.T) { 20 func TestAgentMessageReportRoundTrip(t *testing.T) {
21 in := &pb.AgentMessage{Msg: &pb.AgentMessage_Report{Report: &pb.ActualStateReport{ 21 in := &pb.AgentMessage{Msg: &pb.AgentMessage_Report{Report: &pb.Report{
22 Vms: []*pb.ActualVM{ 22 Vms: []*pb.VMStatus{
23 {VmId: "vm-1", Power: "on", Phase: "running", Ip: "10.0.0.5", LastError: ""}, 23 {VmId: "vm-1", PowerState: "on", Phase: "running", Ip: "10.0.0.5", LastError: ""},
24 {VmId: "vm-2", Power: "off", Phase: "stopped"}, 24 {VmId: "vm-2", PowerState: "off", Phase: "stopped"},
25 {VmId: "vm-3", Power: "off", Phase: "creating", StatusDetail: "downloading image 1.2/3.7 GiB"}, 25 {VmId: "vm-3", PowerState: "off", Phase: "creating", StatusDetail: "downloading image 1.2/3.7 GiB"},
26 }, 26 },
27 Exposures: []*pb.ExposureActual{{ 27 Exposures: []*pb.ExposureStatus{{
28 Id: "e-1", State: "active", 28 Id: "e-1", State: "active",
29 Sessions: &pb.ExposureSessions{Active: 7, Refused: 12, Dropped: 3}, 29 Sessions: &pb.ExposureSessions{Active: 7, Refused: 12, Dropped: 3},
30 }}, 30 }},
@@ -41,9 +41,9 @@ func TestAgentMessageReportRoundTrip(t *testing.T) {
41 } 41 }
42 42
43 func TestServerMessageSnapshotRoundTrip(t *testing.T) { 43 func TestServerMessageSnapshotRoundTrip(t *testing.T) {
44 in := &pb.ServerMessage{Msg: &pb.ServerMessage_Snapshot{Snapshot: &pb.DesiredStateSnapshot{ 44 in := &pb.ServerMessage{Msg: &pb.ServerMessage_Snapshot{Snapshot: &pb.Snapshot{
45 Epoch: 7, 45 Epoch: 7,
46 Vms: []*pb.VMDesired{{ 46 Vms: []*pb.VMSpec{{
47 VmId: "vm-1", Name: "web", ImageUrl: "https://img/x.qcow2", ImageSha256: "abc", 47 VmId: "vm-1", Name: "web", ImageUrl: "https://img/x.qcow2", ImageSha256: "abc",
48 CloudInit: "#cloud-config", Vcpus: 4, MemMb: 8192, DiskGb: 40, 48 CloudInit: "#cloud-config", Vcpus: 4, MemMb: 8192, DiskGb: 40,
49 Persistent: true, PowerState: "on", Tombstoned: false, SshAuthorizedKey: "ssh-ed25519 AAAA", 49 Persistent: true, PowerState: "on", Tombstoned: false, SshAuthorizedKey: "ssh-ed25519 AAAA",
@@ -63,15 +63,15 @@ func TestServerMessageSnapshotRoundTrip(t *testing.T) {
63 // know. Field 99 is nothing in this schema and stands in for whatever a later 63 // know. Field 99 is nothing in this schema and stands in for whatever a later
64 // release adds beside status_detail. 64 // release adds beside status_detail.
65 func TestAReportFromANewerAgentDecodesOnAnOlderServer(t *testing.T) { 65 func TestAReportFromANewerAgentDecodesOnAnOlderServer(t *testing.T) {
66 raw, err := proto.Marshal(&pb.ActualVM{ 66 raw, err := proto.Marshal(&pb.VMStatus{
67 VmId: "vm-1", Power: "off", Phase: "creating", 67 VmId: "vm-1", PowerState: "off", Phase: "creating",
68 StatusDetail: "downloading image 1.2/3.7 GiB", 68 StatusDetail: "downloading image 1.2/3.7 GiB",
69 }) 69 })
70 require.NoError(t, err) 70 require.NoError(t, err)
71 raw = protowire.AppendTag(raw, 99, protowire.BytesType) 71 raw = protowire.AppendTag(raw, 99, protowire.BytesType)
72 raw = protowire.AppendString(raw, "a field from a release this peer predates") 72 raw = protowire.AppendString(raw, "a field from a release this peer predates")
73 73
74 var got pb.ActualVM 74 var got pb.VMStatus
75 require.NoError(t, proto.Unmarshal(raw, &got), "an unknown field must not fail the frame") 75 require.NoError(t, proto.Unmarshal(raw, &got), "an unknown field must not fail the frame")
76 assert.Equal(t, "vm-1", got.GetVmId()) 76 assert.Equal(t, "vm-1", got.GetVmId())
77 assert.Equal(t, "creating", got.GetPhase()) 77 assert.Equal(t, "creating", got.GetPhase())
@@ -84,13 +84,13 @@ func TestAReportFromANewerAgentDecodesOnAnOlderServer(t *testing.T) {
84 // status_detail is empty (nothing to add) and absent counters are NIL — not a 84 // status_detail is empty (nothing to add) and absent counters are NIL — not a
85 // zeroed struct claiming this port has turned nobody away. 85 // zeroed struct claiming this port has turned nobody away.
86 func TestAReportFromAnOlderAgentReadsAsSilence(t *testing.T) { 86 func TestAReportFromAnOlderAgentReadsAsSilence(t *testing.T) {
87 raw, err := proto.Marshal(&pb.ActualStateReport{ 87 raw, err := proto.Marshal(&pb.Report{
88 Vms: []*pb.ActualVM{{VmId: "vm-1", Power: "off", Phase: "creating"}}, 88 Vms: []*pb.VMStatus{{VmId: "vm-1", PowerState: "off", Phase: "creating"}},
89 Exposures: []*pb.ExposureActual{{Id: "e-1", State: "active"}}, 89 Exposures: []*pb.ExposureStatus{{Id: "e-1", State: "active"}},
90 }) 90 })
91 require.NoError(t, err) 91 require.NoError(t, err)
92 92
93 var got pb.ActualStateReport 93 var got pb.Report
94 require.NoError(t, proto.Unmarshal(raw, &got)) 94 require.NoError(t, proto.Unmarshal(raw, &got))
95 assert.Empty(t, got.GetVms()[0].GetStatusDetail()) 95 assert.Empty(t, got.GetVms()[0].GetStatusDetail())
96 assert.Nil(t, got.GetExposures()[0].GetSessions(), "an uncounted port must not decode as zeros") 96 assert.Nil(t, got.GetExposures()[0].GetSessions(), "an uncounted port must not decode as zeros")
internal/transport/fieldnumbers_test.go
Old New
@@ -81,9 +81,9 @@ var wireSchema = map[string]map[string]protoreflect.FieldNumber{
81 "disk_used_gb": 7, 81 "disk_used_gb": 7,
82 "disk_free_gb": 8, 82 "disk_free_gb": 8,
83 }, 83 },
84 "ActualVM": { 84 "VMStatus": {
85 "vm_id": 1, 85 "vm_id": 1,
86 "power": 2, 86 "power_state": 2,
87 "phase": 3, 87 "phase": 3,
88 "ip": 4, 88 "ip": 4,
89 "last_error": 5, 89 "last_error": 5,
@@ -97,7 +97,7 @@ var wireSchema = map[string]map[string]protoreflect.FieldNumber{
97 "vmspec_json": 3, 97 "vmspec_json": 3,
98 "destroy_at_unix": 4, 98 "destroy_at_unix": 4,
99 }, 99 },
100 "ActualStateReport": { 100 "Report": {
101 "vms": 1, 101 "vms": 1,
102 "destroyed": 2, 102 "destroyed": 2,
103 "quarantined": 3, 103 "quarantined": 3,
@@ -109,7 +109,7 @@ var wireSchema = map[string]map[string]protoreflect.FieldNumber{
109 "exposures": 9, 109 "exposures": 9,
110 "host_uplink_addr": 10, 110 "host_uplink_addr": 10,
111 }, 111 },
112 "VMDesired": { 112 "VMSpec": {
113 "vm_id": 1, 113 "vm_id": 1,
114 "name": 2, 114 "name": 2,
115 "image_url": 3, 115 "image_url": 3,
@@ -127,7 +127,7 @@ var wireSchema = map[string]map[string]protoreflect.FieldNumber{
127 "host_cert_required": 19, 127 "host_cert_required": 19,
128 "network": 20, 128 "network": 20,
129 }, 129 },
130 "DesiredStateSnapshot": { 130 "Snapshot": {
131 "epoch": 1, 131 "epoch": 1,
132 "vms": 2, 132 "vms": 2,
133 "agent_upgrade": 3, 133 "agent_upgrade": 3,
@@ -153,14 +153,14 @@ var wireSchema = map[string]map[string]protoreflect.FieldNumber{
153 "ok": 1, 153 "ok": 1,
154 "error": 2, 154 "error": 2,
155 }, 155 },
156 "ExposureDesired": { 156 "ExposureSpec": {
157 "id": 1, 157 "id": 1,
158 "vm_id": 2, 158 "vm_id": 2,
159 "guest_port": 3, 159 "guest_port": 3,
160 "host_port": 4, 160 "host_port": 4,
161 "protocol": 5, 161 "protocol": 5,
162 }, 162 },
163 "ExposureActual": { 163 "ExposureStatus": {
164 "id": 1, 164 "id": 1,
165 "state": 2, 165 "state": 2,
166 "reason": 3, 166 "reason": 3,
proto/eitri/v1/sync.proto
Old New
@@ -5,7 +5,7 @@ option go_package = "github.com/a73x/eitri/internal/pb;pb";
5 message AgentMessage { 5 message AgentMessage {
6 oneof msg { 6 oneof msg {
7 Hello hello = 1; 7 Hello hello = 1;
8 ActualStateReport report = 2; 8 Report report = 2;
9 ConsoleOpened console_opened = 3; 9 ConsoleOpened console_opened = 3;
10 TCPOpened tcp_opened = 4; 10 TCPOpened tcp_opened = 4;
11 } 11 }
@@ -13,7 +13,7 @@ message AgentMessage {
13 13
14 message ServerMessage { 14 message ServerMessage {
15 oneof msg { 15 oneof msg {
16 DesiredStateSnapshot snapshot = 1; 16 Snapshot snapshot = 1;
17 ConsoleOpen console_open = 2; 17 ConsoleOpen console_open = 2;
18 TCPOpen tcp_open = 3; 18 TCPOpen tcp_open = 3;
19 } 19 }
@@ -27,7 +27,7 @@ message Hello {
27 string provisioner = 5; // "cloudhv" 27 string provisioner = 5; // "cloudhv"
28 reserved 6; // was bridge_cidr: the fleet told the host its 28 reserved 6; // was bridge_cidr: the fleet told the host its
29 reserved "bridge_cidr"; // subnet. The host reports it now — see 29 reserved "bridge_cidr"; // subnet. The host reports it now — see
30 // ActualStateReport.guest_cidr. 30 // Report.guest_cidr.
31 uint64 last_seen_epoch = 7; // for the restore runbook 31 uint64 last_seen_epoch = 7; // for the restore runbook
32 Capacity capacity = 8; 32 Capacity capacity = 8;
33 string credential = 9; // Bearer host credential, verified in first frame 33 string credential = 9; // Bearer host credential, verified in first frame
@@ -70,9 +70,12 @@ message HostMetrics {
70 int64 disk_free_gb = 8; 70 int64 disk_free_gb = 8;
71 } 71 }
72 72
73 message ActualVM { 73 // VMStatus is the half of a VM its host owns: what it observed, never what it
74 // was told. One VM, two halves — VMSpec travels down in a Snapshot, VMStatus
75 // travels back in a Report, and the control plane holds both on one row.
76 message VMStatus {
74 string vm_id = 1; 77 string vm_id = 1;
75 string power = 2; // "running"|"stopped" 78 string power_state = 2; // "running"|"stopped" as observed; VMSpec.power_state is what was asked for
76 string phase = 3; // "creating"|"ready"|"failed"|"quarantined" 79 string phase = 3; // "creating"|"ready"|"failed"|"quarantined"
77 string ip = 4; // the guest's address on its host's NAT underlay — every guest has one, from boot 80 string ip = 4; // the guest's address on its host's NAT underlay — every guest has one, from boot
78 string last_error = 5; 81 string last_error = 5;
@@ -92,7 +95,7 @@ message ActualVM {
92 string status_detail = 7; 95 string status_detail = 7;
93 // The address the site's DHCP server granted this guest on its SECOND NIC, 96 // The address the site's DHCP server granted this guest on its SECOND NIC,
94 // the one attached to the named host network its spec asked for (see 97 // the one attached to the named host network its spec asked for (see
95 // VMDesired.network). Empty for the guests that have no such NIC — the 98 // VMSpec.network). Empty for the guests that have no such NIC — the
96 // majority — and for one DHCP round-trip after a networked guest boots, 99 // majority — and for one DHCP round-trip after a networked guest boots,
97 // because the host learns it by watching the exchange rather than granting 100 // because the host learns it by watching the exchange rather than granting
98 // it. Never a substitute for ip: that one is known before the guest is even 101 // it. Never a substitute for ip: that one is known before the guest is even
@@ -108,8 +111,10 @@ message QuarantinedVM {
108 int64 destroy_at_unix = 4; 111 int64 destroy_at_unix = 4;
109 } 112 }
110 113
111 message ActualStateReport { 114 // Report is what one host observes, level-triggered: the status of every VM
112 repeated ActualVM vms = 1; 115 // and exposure it holds, and the host's own numbers.
116 message Report {
117 repeated VMStatus vms = 1;
113 // LEVEL-TRIGGERED destroy ack: ALL tombstoned vm_ids with no local 118 // LEVEL-TRIGGERED destroy ack: ALL tombstoned vm_ids with no local
114 // record/disk/process, repeated every report until hard-deleted server-side. 119 // record/disk/process, repeated every report until hard-deleted server-side.
115 repeated string destroyed = 2; 120 repeated string destroyed = 2;
@@ -127,7 +132,7 @@ message ActualStateReport {
127 // knowable at connect time at all. Capacity is in both messages for the same 132 // knowable at connect time at all. Capacity is in both messages for the same
128 // reason: an opening value, then the ongoing truth. 133 // reason: an opening value, then the ongoing truth.
129 string guest_cidr = 8; 134 string guest_cidr = 8;
130 repeated ExposureActual exposures = 9; 135 repeated ExposureStatus exposures = 9;
131 // The address this host presents on the network it reaches the control 136 // The address this host presents on the network it reaches the control
132 // plane over — the address an operator dials to reach a published guest 137 // plane over — the address an operator dials to reach a published guest
133 // port. Empty means "not yet known", never "no address": a host that cannot 138 // port. Empty means "not yet known", never "no address": a host that cannot
@@ -137,7 +142,8 @@ message ActualStateReport {
137 string host_uplink_addr = 10; 142 string host_uplink_addr = 10;
138 } 143 }
139 144
140 message VMDesired { 145 // VMSpec is the half of a VM the control plane owns. See VMStatus.
146 message VMSpec {
141 string vm_id = 1; 147 string vm_id = 1;
142 string name = 2; 148 string name = 2;
143 string image_url = 3; 149 string image_url = 3;
@@ -167,7 +173,7 @@ message VMDesired {
167 reserved 16; 173 reserved 16;
168 reserved "ssh_host_key_pem"; 174 reserved "ssh_host_key_pem";
169 // The certificate the control plane signed for the public key the host 175 // The certificate the control plane signed for the public key the host
170 // reported in ActualVM.ssh_host_pubkey (authorized_keys form); seed installs 176 // reported in VMStatus.ssh_host_pubkey (authorized_keys form); seed installs
171 // it as /etc/ssh/ssh_host_ed25519_key-cert.pub. Empty until the round trip 177 // it as /etc/ssh/ssh_host_ed25519_key-cert.pub. Empty until the round trip
172 // completes, and forever when the jump gate is off. 178 // completes, and forever when the jump gate is off.
173 string ssh_host_cert = 17; 179 string ssh_host_cert = 17;
@@ -186,11 +192,12 @@ message VMDesired {
186 string network = 20; 192 string network = 20;
187 } 193 }
188 194
189 message DesiredStateSnapshot { 195 // Snapshot is the FULL spec for one host; the agent converges toward it.
196 message Snapshot {
190 uint64 epoch = 1; // agents refuse epoch < highest seen 197 uint64 epoch = 1; // agents refuse epoch < highest seen
191 repeated VMDesired vms = 2; // FULL set for this host, including tombstoned 198 repeated VMSpec vms = 2; // FULL set for this host, including tombstoned
192 AgentUpgrade agent_upgrade = 3; // optional operator-initiated agent self-upgrade 199 AgentUpgrade agent_upgrade = 3; // optional operator-initiated agent self-upgrade
193 repeated ExposureDesired exposures = 4; // FULL set for this host 200 repeated ExposureSpec exposures = 4; // FULL set for this host
194 } 201 }
195 202
196 // AgentUpgrade asks the agent to replace its own binary: download url, verify 203 // AgentUpgrade asks the agent to replace its own binary: download url, verify
@@ -234,13 +241,13 @@ message TCPOpened {
234 string error = 2; 241 string error = 2;
235 } 242 }
236 243
237 // ExposureDesired is one published guest port a host should be serving: bind 244 // ExposureSpec is one published guest port a host should be serving: bind
238 // host_port on the host, pipe every accepted connection (or every datagram) 245 // host_port on the host, pipe every accepted connection (or every datagram)
239 // to guest_port inside the guest. It rides the snapshot at TOP LEVEL rather 246 // to guest_port inside the guest. It rides the snapshot at TOP LEVEL rather
240 // than nested in VMDesired, because exposures are their own objects converging 247 // than nested in VMSpec, because exposures are their own objects converging
241 // on their own cadence — an exposure can be created while its VM is still 248 // on their own cadence — an exposure can be created while its VM is still
242 // imaging, and it binds immediately. 249 // imaging, and it binds immediately.
243 message ExposureDesired { 250 message ExposureSpec {
244 string id = 1; 251 string id = 1;
245 string vm_id = 2; 252 string vm_id = 2;
246 uint32 guest_port = 3; 253 uint32 guest_port = 3;
@@ -248,11 +255,11 @@ message ExposureDesired {
248 string protocol = 5; // "tcp"|"udp" 255 string protocol = 5; // "tcp"|"udp"
249 } 256 }
250 257
251 // ExposureActual is one exposure's state as its host observes it: "active" 258 // ExposureStatus is one exposure's state as its host observes it: "active"
252 // once the host socket is bound, "failed" with the OS error otherwise. 259 // once the host socket is bound, "failed" with the OS error otherwise.
253 // "active" means the HOST half of the pipe exists — whether anything answers 260 // "active" means the HOST half of the pipe exists — whether anything answers
254 // inside the guest is the guest's half, and this does not pretend otherwise. 261 // inside the guest is the guest's half, and this does not pretend otherwise.
255 message ExposureActual { 262 message ExposureStatus {
256 string id = 1; 263 string id = 1;
257 string state = 2; // "active"|"failed" 264 string state = 2; // "active"|"failed"
258 string reason = 3; // the OS error, when failed; a bound port's ongoing trouble otherwise 265 string reason = 3; // the OS error, when failed; a bound port's ongoing trouble otherwise