diff --git a/cmd/spin-machine/commands_test.go b/cmd/spin-machine/commands_test.go index a4621d6..15cbdff 100644 --- a/cmd/spin-machine/commands_test.go +++ b/cmd/spin-machine/commands_test.go @@ -228,7 +228,7 @@ func TestFlagsAreTheSpec(t *testing.T) { every.Cmdline.Console = "" deferred := defaults - deferred.IncomingDefer = true + deferred.Incoming = "defer" emulated := defaults emulated.Accel, emulated.QEMU = "tcg", filepath.Join(dir, "bin/qemu-system-x86_64-tcg") diff --git a/cmd/spin-machine/main.go b/cmd/spin-machine/main.go index 0afc569..7ccd1b5 100644 --- a/cmd/spin-machine/main.go +++ b/cmd/spin-machine/main.go @@ -287,12 +287,7 @@ func (o *machineFlags) spec() (machine.Spec, error) { if o.qmp != "" { s.Monitors = []machine.Monitor{{Socket: o.qmp}} } - // defer is QEMU's own word for "the source comes over QMP": see restore. - if o.incoming == "defer" { - s.IncomingDefer = true - } else { - s.Incoming = o.incoming - } + s.Incoming = o.incoming s.Serial = o.console disk := o.disk @@ -664,7 +659,7 @@ func (o *restoreFlags) register(fs *flag.FlagSet) { // // `boot --incoming file:PATH` loads the same file in one step. This is the other // form, and the one a caller that resumes checkpoints uses: the machine is started -// with -incoming defer (Spec.IncomingDefer), and the state is named over QMP once +// with -incoming defer (Spec.Incoming "defer"), and the state is named over QMP once // the caller is ready for it. Driving that by hand is what this command is for. func restore(o restoreFlags) error { if o.qmp == "" || o.from == "" { diff --git a/docs/migration.md b/docs/migration.md index b1945c9..0d7a02a 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -5,9 +5,9 @@ A checkpoint is one state file — memory, device state and CPU state, written b Stopping a VM here and resuming it there is a lifecycle, and this repository does not implement one — it builds the machine that lifecycle runs on, and offers the arguments it -needs: `Incoming` (a state to load at exec time, `-incoming file:…`) and `IncomingDefer` -(start with no state and wait to be told where it is, over QMP, for a caller that starts the -machine before it has the state in hand). +needs: `Incoming`, either a state to load at exec time (`-incoming file:…`) or `defer` (start +with no state and wait to be told where it is, over QMP, for a caller that starts the machine +before it has the state in hand). `spin-machine save --qmp … --to state` is the first half by hand, and the second is either `spin-machine boot --incoming file:state` or `boot --incoming defer` followed by diff --git a/machine/machine.go b/machine/machine.go index e0df891..699ae99 100644 --- a/machine/machine.go +++ b/machine/machine.go @@ -124,7 +124,7 @@ const virtioModern = "disable-legacy=on" // qemuOpt escapes a value for a QEMU option string, which splits on a single comma // and reads a doubled one as a literal. Every free-form value a caller supplies goes // through it: a disk path "a,readonly=off" is otherwise a second option, not part of -// the path. A value picked from a fixed set — a disk's format and cache mode, the +// the path. A value picked from a fixed set — a disk's format, the // accelerator, a CPU model name — is not escaped but refused by validate when it is not // one of the set, because an escaped "qcow2,,x" is still not a format. func qemuOpt(v string) string { return strings.ReplaceAll(v, ",", ",,") } @@ -588,16 +588,12 @@ type Spec struct { // what the line is made of. Cmdline Cmdline - // IncomingDefer starts QEMU with no machine state, waiting to be told over - // QMP where to load it from (migrate-incoming): how a checkpoint is resumed by - // a caller that starts the machine before it has the state in hand, or wants a - // word with QEMU before the first byte is read. Whoever drives that owns the - // lifecycle; this is the machine argument they need. - IncomingDefer bool - - // Incoming names that source at exec time instead — a migration URI, most - // usefully "file:/path/to/state". It is how a VM saved on one machine is - // resumed, rather than booted, on another. + // Incoming is -incoming: where the machine's state comes from when it is resumed + // rather than booted. A migration URI names it at exec time, most usefully + // "file:/path/to/state". "defer", QEMU's own word, starts QEMU with no state, + // waiting to be told over QMP where to load it from (migrate-incoming): how a + // checkpoint is resumed by a caller that starts the machine before it has the + // state in hand, or wants a word with QEMU before the first byte is read. // // The guest does not know this happened: it continues from the instruction it // was stopped at, with the memory, the devices and the clock it had. Which is @@ -828,8 +824,6 @@ func (s Spec) validate() error { // The two forms of -incoming are one flag, and QEMU takes it once. A spec carrying // both is a caller that has not decided whether the source is named at exec time or // over QMP afterwards, and guessing for them is how a VM restores from the wrong one. - case s.IncomingDefer && s.Incoming != "": - return fmt.Errorf("both IncomingDefer and Incoming %q: -incoming takes one form", s.Incoming) case s.Memory.SizeMB < 1: return fmt.Errorf("memory is %d MB", s.Memory.SizeMB) case len(s.Disks) > maxDisks: @@ -1196,11 +1190,7 @@ func (s Spec) appendFDSets(args []string) []string { // appendIncoming is -incoming, when the machine is to be restored rather than booted. func (s Spec) appendIncoming(args []string) []string { - // -incoming, in whichever of its two forms this machine was given. validate has - // already refused a spec carrying both. - if s.IncomingDefer { - args = append(args, "-incoming", "defer") - } else if s.Incoming != "" { + if s.Incoming != "" { args = append(args, "-incoming", s.Incoming) } return args @@ -1275,25 +1265,10 @@ func (s Spec) fingerprint(hostCPU func() (string, error)) (string, error) { } write(f.name, sum) } - ident, err := s.identity(hostCPU) - if err != nil { - return "", err - } - write("identity", ident) - - return hex.EncodeToString(h.Sum(nil)), nil -} -// identity is everything the fingerprint hashes except the contents of those five -// files: the machine's shape, its device topology, and the host's own CPU when the -// guest is being shown it. -func (s Spec) identity(hostCPU func() (string, error)) (string, error) { + // The machine's shape, its device topology, and the host's own CPU when the guest is + // being shown it. shape := s.Shape() - - var b strings.Builder - write := func(key, value string) { - _, _ = fmt.Fprintf(&b, "%s=%d:%s\n", key, len(value), value) - } write("machine", shape.Machine) write("accel", shape.Accel) write("cpu", shape.CPU) @@ -1323,7 +1298,7 @@ func (s Spec) identity(hostCPU func() (string, error)) (string, error) { write("host-cpu", cpu) } - return b.String(), nil + return hex.EncodeToString(h.Sum(nil)), nil } // topology is the machine's device list — which models, at which slots — with diff --git a/machine/machine_test.go b/machine/machine_test.go index 8240115..b091d7e 100644 --- a/machine/machine_test.go +++ b/machine/machine_test.go @@ -723,7 +723,6 @@ func TestValidateRefuses(t *testing.T) { {"a NIC with a 64-bit MAC", func(s *Spec) { s.NICs = []NIC{{TapFD: 3, MAC: "02:00:5e:10:00:00:00:01"}} }}, // Both forms of -incoming: QEMU takes the flag once, and which source a VM // restores from is not something to guess at on the caller's behalf. - {"both forms of -incoming", func(s *Spec) { s.IncomingDefer, s.Incoming = true, "file:/state" }}, } { t.Run(tc.what, func(t *testing.T) { s := spec(t) @@ -830,7 +829,7 @@ func TestIncomingNamesTheSourceOnTheCommandLine(t *testing.T) { want string }{ {"a URI at exec time", func(s *Spec) { s.Incoming = "file:/state" }, "file:/state"}, - {"deferred to QMP", func(s *Spec) { s.IncomingDefer = true }, "defer"}, + {"deferred to QMP", func(s *Spec) { s.Incoming = "defer" }, "defer"}, {"a boot", func(*Spec) {}, ""}, } { t.Run(tc.what, func(t *testing.T) { diff --git a/machine/qemu_accepts_test.go b/machine/qemu_accepts_test.go index b5c8394..4803efe 100644 --- a/machine/qemu_accepts_test.go +++ b/machine/qemu_accepts_test.go @@ -148,7 +148,7 @@ func TestQEMUAcceptsEveryArgument(t *testing.T) { // where to load it from. name: "restore target", spec: func(s Spec) Spec { - s.IncomingDefer = true + s.Incoming = "defer" return s }, }, {