Skip to content

two instance ensure APIs is one too many #804

Closed
@gjcolombo

Description

@gjcolombo

Today the server API offers two distinct ways to initialize a new VM:

  • The caller can pass an InstanceEnsureRequest that specifies some, but not all, of the components it wants the new VM to have. Propolis then supplements this configuration with some default devices (e.g. serial ports), converts some of the information in the ensure request into concrete configuration data (e.g. slot numbers to PCI BDFs), and adds any devices specified in the config TOML passed to the server on its command line.
  • Alternatively, the caller can pass a VersionedInstanceSpec that fully specifies all the components the new VM should have. The server does not add any extra devices by default and ignores the device list in the config TOML (though the TOML is still used to specify the bootrom path).

In both cases, the server's first task is to try to convert its ensure parameters into a server-internal Spec structure that describes the new VM's components and their configuration. If this succeeds, machine initialization uses the resulting Spec to set up the new VM.

Both of these APIs accept an optional request to initialize via live migration, which contains the address of the migration source Propolis. If this is supplied:

  • The target creates its internal Spec as usual, but before creating any VM components, it starts the live migration protocol.
  • At the beginning of the protocol, the source Propolis sends its VersionedInstanceSpec to the target Propolis.
  • The target compares this spec to its own Spec to make sure the two specs describe "migration-compatible" VMs; that is, the two specs must describe the same guest-visible abstractions and configuration. If they don't, migration fails.
  • If the compat check passes, the target creates its VM components, then runs the rest of the LM protocol to get a copy of the source's component state. The target imports this state into its components, then resumes the VM.

Two APIs is one too many. Having two APIs is a maintenance headache, especially as we continue adding new Propolis configuration options (boot order and CPUID landed recently; configurable MSR dispositions and hypervisor enlightenments are on the horizon). We should consolidate down to a single VM creation API.


I think a better API would look something like this:

enum ReplacementComponent {
    // one variant for each ComponentV0 variant that can be replaced
    // during migration; see below
}

enum InstanceInitMethod {
    New {
        // N.B. doesn't need to be versioned since the client, sled agent, is
        // updated in lockstep with Propolis
        spec: InstanceSpecV0
    },
    MigrationTarget {
        migration_id: Uuid,
        source_addr: SocketAddr,
        replacement_components: HashMap<InstanceSpecKey, ReplacementComponent>
    }
}

struct InstanceEnsureRequest {
    // same as the existing instance properties struct
    properties: InstanceProperties,
    init: InstanceInitMethod
}

To create a new VM, a client creates a spec for it and passes an InstanceInitMethod::New in its ensure request. This will require Omicron to switch from using instance ensure requests to instance specs. This should mostly be a matter of moving Propolis's ensure-request-to-instance-spec logic up into Nexus; it shouldn't require any database schema changes. One additional benefit of doing this (besides simplifying Propolis's API) is that putting this logic in Nexus makes it easier to update. RFD 505 talks about all the tradeoffs of this approach in more detail.

In the new API, the migration interface doesn't take a full instance spec. Instead, the target Propolis takes the source spec that it gets from the migration protocol, replaces any entries therein that have entries in the caller-supplied replacement_components map, and uses the amended spec to initialize the new VM. The replacement map allows Nexus to e.g. specify new Crucible volume construction requests with new client generation numbers, but does not allow it to replace any device configuration. This approach has a couple of big benefits:

  1. Propolis no longer needs a huge migration-compatibility-check module: the target's configuration is compatible with the source's because it is the source's configuration. Removing this module makes adding new guest-visible features significantly easier.
  2. Nexus doesn't need to remember how it originally configured a VM in order to migrate it. This is a nice bug-prevention mechanism: today, changes to how Nexus constructs an InstanceEnsureRequest (or to how Propolis amends it!) can prevent a VM from ever being able to migrate (if the changes deterministically produce incompatible VM configuration). Even in the absence of bugs, this reduces the amount of instance initialization logic Nexus has to carry around ("I started this VM at this version of the code, so I have to use this specific logic to produce the correct spec/ensure request for it").

The server no longer reads device information from config TOMLs (it only reads the bootrom path; we might also be able to eliminate this someday, but I'm declaring it out of scope for this issue). The propolis-server-config library will stick around, though, and will provide a way for clients to convert a config TOML to a set of instance spec components so that they can easily use the new interface.

I've drafted enough of the server side of these changes to be pretty confident this scheme can be made to work and that it will make the system nicer overall.

Metadata

Metadata

Assignees

Labels

apiRelated to the API.serverRelated specifically to the Propolis server API and its VM management functions.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions