diff --git a/protect/control/v1/common.proto b/protect/control/v1/common.proto index 0a409ab..9cf69f1 100644 --- a/protect/control/v1/common.proto +++ b/protect/control/v1/common.proto @@ -132,6 +132,28 @@ enum ZoneVirtualizationBackend { ZONE_VIRTUALIZATION_BACKEND_KVM = 4; } +// Hypervisor family a zone snapshot was exported from. A snapshot may only be +// imported on a host running the same backend: the guest-state records inside +// it are backend-private, so a Xen stream carries nothing a KVM host can act +// on, and vice versa. The tag is checked before any of that state is +// interpreted, which is what lets a second backend be added later without +// either being able to consume the other's stream. +enum ZoneSnapshotBackend { + ZONE_SNAPSHOT_BACKEND_UNKNOWN = 0; + ZONE_SNAPSHOT_BACKEND_XEN_PV = 1; + ZONE_SNAPSHOT_BACKEND_XEN_PVH = 2; + ZONE_SNAPSHOT_BACKEND_KVM = 3; +} + +// Guest instruction set a zone snapshot was exported for. Held separately from +// the backend: one backend spans architectures whose guest state has nothing in +// common. +enum ZoneSnapshotArch { + ZONE_SNAPSHOT_ARCH_UNKNOWN = 0; + ZONE_SNAPSHOT_ARCH_X86_64 = 1; + ZONE_SNAPSHOT_ARCH_AARCH64 = 2; +} + // Strategy used when expanding the selected node set beyond the seed node. // - UNSPECIFIED is treated as COMPACT. // - COMPACT: Expand to the nearest unselected NUMA node by SLIT distance from the already-selected set, tie-breaking by most free memory. @@ -261,6 +283,7 @@ enum ZoneState { ZONE_STATE_SUSPENDED = 9; ZONE_STATE_RESUMING = 10; ZONE_STATE_FORKING = 11; + ZONE_STATE_IMPORTING = 12; } enum ZoneNetworkIpVersion { diff --git a/protect/control/v1/control.proto b/protect/control/v1/control.proto index c61dd0a..ea79621 100644 --- a/protect/control/v1/control.proto +++ b/protect/control/v1/control.proto @@ -31,6 +31,8 @@ service ControlService { rpc SuspendZone(SuspendZoneRequest) returns (SuspendZoneReply); rpc ResumeZone(ResumeZoneRequest) returns (ResumeZoneReply); rpc ForkZone(ForkZoneRequest) returns (ForkZoneReply); + rpc ExportZone(ExportZoneRequest) returns (stream ExportZoneReply); + rpc ImportZone(stream ImportZoneRequest) returns (ImportZoneReply); rpc ResolveZoneId(ResolveZoneIdRequest) returns (ResolveZoneIdReply); rpc ResolveZoneIds(ResolveZoneIdsRequest) returns (ResolveZoneIdsReply); @@ -145,6 +147,83 @@ message ForkZoneReply { string zone_id = 1; } +// Exports a zone's complete state -- vcpus, guest memory, hypervisor +// configuration, device topology, the contents of its writable disks, and the +// zone's own control-plane record and workloads -- as a snapshot: a stream of +// opaque bytes the caller stores and later hands back to `ImportZone`. +// +// The zone is cooperatively suspended for the export and resumed afterwards +// unless `leave_suspended` is set, so a snapshot is a consistent point rather +// than a smear of a running guest. +message ExportZoneRequest { + reserved 4; + reserved "include_disk_contents"; + + string zone_id = 1; + // Leave the zone suspended once the export completes instead of resuming it. + // Use this when the snapshot is about to become the live copy elsewhere. + bool leave_suspended = 2; + // Compress record payloads. Guest memory compresses well; the cost is host + // CPU during the export. + bool compress = 3; +} + +// Streamed reply for `ExportZone`. The first message carries `start`, +// describing the snapshot about to be streamed; then `chunk`s carrying its +// bytes in order; then a final `complete`. The concatenation of every `chunk` +// is the snapshot, and nothing else in the stream belongs in the stored bytes. +message ExportZoneReply { + oneof reply { + ExportZoneStart start = 1; + bytes chunk = 2; + ExportZoneComplete complete = 3; + } +} + +message ExportZoneStart { + string zone_id = 1; + ZoneSnapshotBackend backend = 2; + ZoneSnapshotArch arch = 3; + uint32 format_version = 4; +} + +// Closes the reply stream once the export is complete, so a caller can check +// that the snapshot it wrote is as long as what the daemon sent. +message ExportZoneComplete { + uint64 total_bytes = 1; +} + +// Recreates a zone from a snapshot produced by `ExportZone`. The first message +// must carry `start`; every message after it carries a `chunk` of the +// snapshot, in the order it was received. +// +// The daemon checks the snapshot against this host before acting on any of the +// guest state it carries: the backend and architecture must match, and the +// guest's CPU, clock, zone agent and hypervisor state must be ones this host +// can continue. Read-only images the snapshot references by path must exist +// here unchanged in size. A refusal names every incompatibility found. +message ImportZoneRequest { + oneof request { + ImportZoneStart start = 1; + bytes chunk = 2; + } +} + +message ImportZoneStart { + reserved 3; + reserved "allow_foreign_host"; + + // Overrides the zone name recorded in the snapshot. Empty keeps it. + string name = 1; + // Import under this zone id instead of a freshly allocated one. Empty + // allocates a new id, which is what lets one snapshot seed many zones. + string zone_id = 2; +} + +message ImportZoneReply { + string zone_id = 1; +} + // Resolves zone "friendly name" to a singular zone UUID, if possible. Matching is exact. // See also `ResolveZoneIdsRequest` (plural). message ResolveZoneIdRequest {