// Package providers beschreibt die herstellerneutrale Schnittstelle zu // Virtualisierungsplattformen. // // Der Zuschnitt folgt SYNCOVA_ARCHITECTURE.md §5: Proxmox ist in V1 der einzige // Provider, VMware und Hyper-V müssen sich später ergänzen lassen, ohne die // Backup Engine anzufassen. Deshalb enthält dieses Paket ausschließlich // allgemeine Begriffe — kein Feld heißt „vmid", keine Konstante nennt eine // Proxmox-Speicherart. // // Die Abhängigkeitsrichtung ist einseitig: Provider dürfen von der Engine // gelesen werden, die Engine niemals vom Provider. package providers import ( "context" "errors" "io" "time" ) // GuestType benennt die Art eines virtuellen Gasts. type GuestType string const ( // GuestTypeVirtualMachine ist eine vollwertige virtuelle Maschine. GuestTypeVirtualMachine GuestType = "virtual_machine" // GuestTypeContainer ist ein Betriebssystemcontainer. // // Proxmox kennt neben QEMU auch LXC. Der Unterschied ist für die Sicherung // wesentlich: ein Container hat kein BIOS und keine virtuellen Platten im // selben Sinn. GuestTypeContainer GuestType = "container" ) // PowerState ist der Betriebszustand eines Gasts. type PowerState string const ( // PowerStateRunning bezeichnet einen laufenden Gast. PowerStateRunning PowerState = "running" // PowerStateStopped bezeichnet einen angehaltenen Gast. PowerStateStopped PowerState = "stopped" // PowerStatePaused bezeichnet einen pausierten Gast. PowerStatePaused PowerState = "paused" // PowerStateUnknown bezeichnet einen nicht ermittelbaren Zustand. // // Er wird ausdrücklich geführt statt „stopped" anzunehmen: eine falsch als // angehalten geltende VM würde ohne Rückfrage überschrieben. PowerStateUnknown PowerState = "unknown" ) // ConsistencyLevel beschreibt, wie konsistent die gesicherten Daten sind. // // Die Stufe gehört ins Manifest, weil sie darüber entscheidet, was eine // Wiederherstellung wert ist. Ein Backup ohne diese Angabe verspricht mehr, als // es halten kann. type ConsistencyLevel string const ( // ConsistencyCrashConsistent entspricht dem Zustand nach einem Stromausfall. // // Dateisysteme erholen sich davon meist; Datenbanken nicht immer. ConsistencyCrashConsistent ConsistencyLevel = "crash_consistent" // ConsistencyFilesystemConsistent bedeutet geleerte Dateisystempuffer. ConsistencyFilesystemConsistent ConsistencyLevel = "filesystem_consistent" // ConsistencyApplicationConsistent bedeutet zusätzlich ruhiggestellte Anwendungen. ConsistencyApplicationConsistent ConsistencyLevel = "application_consistent" ) // Cluster ist ein Verbund von Wirten. type Cluster struct { // Identifier ist die stabile Kennung des Verbunds. Identifier string `json:"identifier"` // Name ist die sprechende Bezeichnung. Name string `json:"name"` // Version ist die Version der Verwaltungssoftware. Version string `json:"version,omitempty"` // HostCount ist die Zahl der Wirte im Verbund. HostCount int `json:"host_count"` // Quorate meldet, ob der Verbund beschlussfähig ist. // // Ein Verbund ohne Quorum nimmt keine ändernden Aufrufe an. Ohne diese // Angabe liefe eine Sicherung in eine Reihe unverständlicher Fehler. Quorate bool `json:"quorate"` } // Host ist ein einzelner Virtualisierungswirt. type Host struct { // Identifier ist die stabile Kennung des Wirts. Identifier string `json:"identifier"` // Name ist der Knotenname. Name string `json:"name"` // ClusterID benennt den Verbund, dem der Wirt angehört. ClusterID string `json:"cluster_id,omitempty"` // Online meldet die Erreichbarkeit. Online bool `json:"online"` // CPUCount ist die Zahl der logischen Prozessoren. CPUCount int `json:"cpu_count,omitempty"` // MemoryBytes ist der Gesamtarbeitsspeicher. MemoryBytes int64 `json:"memory_bytes,omitempty"` // Version ist die Version der Wirtsoftware. Version string `json:"version,omitempty"` } // Guest ist eine virtuelle Maschine oder ein Container. type Guest struct { // Identifier ist die plattformweite Kennung. Identifier string `json:"identifier"` // Name ist die sprechende Bezeichnung. Name string `json:"name"` // GuestType ist die Art des Gasts. GuestType GuestType `json:"guest_type"` // HostID benennt den Wirt, auf dem der Gast liegt. HostID string `json:"host_id"` // PowerState ist der Betriebszustand. PowerState PowerState `json:"power_state"` // CPUCount ist die Zahl zugewiesener Prozessoren. CPUCount int `json:"cpu_count,omitempty"` // MemoryBytes ist der zugewiesene Arbeitsspeicher. MemoryBytes int64 `json:"memory_bytes,omitempty"` // OperatingSystem ist das gemeldete Betriebssystem, sofern bekannt. OperatingSystem string `json:"operating_system,omitempty"` // Tags sind plattformseitig vergebene Etiketten. // // Sie erlauben es, Sicherungsaufträge nach Etikett statt nach Kennung zu // bilden — eine neue VM wird damit ohne Konfigurationsänderung erfasst. Tags []string `json:"tags,omitempty"` // Protected meldet einen plattformseitigen Löschschutz. Protected bool `json:"protected,omitempty"` } // Disk ist eine virtuelle Platte eines Gasts. type Disk struct { // Identifier ist die Kennung innerhalb des Gasts, etwa der Gerätename. Identifier string `json:"identifier"` // StorageID benennt den Speicher, auf dem die Platte liegt. StorageID string `json:"storage_id"` // Volume ist der plattformseitige Bezeichner des Datenträgers. Volume string `json:"volume"` // SizeBytes ist die eingerichtete Größe. SizeBytes int64 `json:"size_bytes"` // Format ist das Abbildformat, etwa qcow2 oder raw. Format string `json:"format,omitempty"` // ExcludedFromBackup meldet eine von der Sicherung ausgenommene Platte. // // Das ist keine Nebensache: Eine Wiederherstellung liefert dann eine // unvollständige Maschine. Wer es nicht weiß, hält sie für vollständig. ExcludedFromBackup bool `json:"excluded_from_backup"` // ReadOnly meldet einen schreibgeschützten Datenträger, etwa ein Abbild. ReadOnly bool `json:"read_only,omitempty"` } // GuestMetadata sind die Konfigurationsdaten eines Gasts. // // Ohne sie liesse sich eine Maschine zwar mit ihren Daten, aber nicht in ihrer // Gestalt wiederherstellen — falsche Netzkarte, fehlende serielle Schnittstelle, // anderes BIOS. Eine bootfähige, aber unbrauchbare VM ist kein Restore. type GuestMetadata struct { // GuestID ist die Kennung des Gasts. GuestID string `json:"guest_id"` // RawConfiguration ist die unveränderte Konfiguration der Plattform. // // Sie wird wortgetreu mitgesichert. Eine von uns umgedeutete Fassung // verlöre genau die Felder, die wir heute noch nicht kennen. RawConfiguration map[string]string `json:"raw_configuration"` // FirmwareType benennt BIOS oder UEFI. FirmwareType string `json:"firmware_type,omitempty"` // NetworkInterfaces sind die Netzwerkkarten in ihrer Reihenfolge. NetworkInterfaces []NetworkInterface `json:"network_interfaces,omitempty"` // BootOrder ist die Startreihenfolge der Geräte. BootOrder []string `json:"boot_order,omitempty"` // GuestAgentEnabled meldet einen eingerichteten Gastdienst. // // Ohne ihn ist keine anwendungskonsistente Sicherung möglich. GuestAgentEnabled bool `json:"guest_agent_enabled"` } // NetworkInterface ist eine virtuelle Netzwerkkarte. type NetworkInterface struct { // Identifier ist der Gerätename. Identifier string `json:"identifier"` // MACAddress ist die Hardwareadresse. // // Sie muss erhalten bleiben: Lizenzbindungen und DHCP-Reservierungen hängen // daran. Eine wiederhergestellte VM mit neuer MAC ist für das Netz eine // andere Maschine. MACAddress string `json:"mac_address,omitempty"` // Bridge ist die Netzbrücke des Wirts. Bridge string `json:"bridge,omitempty"` // Model ist das nachgebildete Kartenmodell. Model string `json:"model,omitempty"` // VLANTag ist das VLAN-Etikett; 0 bedeutet keines. VLANTag int `json:"vlan_tag,omitempty"` } // Snapshot ist ein Zeitpunktabbild eines Gasts. type Snapshot struct { // Identifier ist der Name des Abbilds. Identifier string `json:"identifier"` // GuestID ist der zugehörige Gast. GuestID string `json:"guest_id"` // CreatedAt ist der Erstellungszeitpunkt in UTC. CreatedAt time.Time `json:"created_at"` // Description erklärt den Zweck des Abbilds. Description string `json:"description,omitempty"` // IncludesMemory meldet ein mitgesichertes Arbeitsspeicherabbild. IncludesMemory bool `json:"includes_memory"` // ConsistencyLevel ist die erreichte Konsistenzstufe. ConsistencyLevel ConsistencyLevel `json:"consistency_level"` } // SnapshotOptions steuern das Anlegen eines Abbilds. type SnapshotOptions struct { // Name ist der gewünschte Name. Name string // Description erklärt den Zweck. Description string // IncludeMemory sichert den Arbeitsspeicher mit. IncludeMemory bool // QuiesceGuest stellt die Anwendungen im Gast ruhig. // // Das setzt einen laufenden Gastdienst voraus. Fehlt er, meldet der // Provider das — er senkt die Konsistenzstufe niemals stillschweigend. QuiesceGuest bool // Timeout begrenzt die Wartezeit; 0 wählt den Standard des Providers. Timeout time.Duration } // BlockRange beschreibt einen zusammenhängenden Bereich einer Platte. type BlockRange struct { // OffsetBytes ist der Beginn des Bereichs. OffsetBytes int64 `json:"offset_bytes"` // LengthBytes ist die Länge des Bereichs. LengthBytes int64 `json:"length_bytes"` } // ChangedBlockResult ist das Ergebnis einer Abfrage geänderter Blöcke. type ChangedBlockResult struct { // Ranges sind die geänderten Bereiche in aufsteigender Reihenfolge. Ranges []BlockRange `json:"ranges"` // BlockSizeBytes ist die Granularität der Nachverfolgung. BlockSizeBytes int64 `json:"block_size_bytes"` // ChangeTrackingID ist die Kennung für die nächste Abfrage. ChangeTrackingID string `json:"change_tracking_id,omitempty"` // FullReadRequired meldet, dass die gesamte Platte gelesen werden muss. // // Das ist der ehrliche Ausgang, wenn die Nachverfolgung zurückgesetzt // wurde. Eine leere Bereichsliste zurückzugeben wäre bequem und falsch: // die Sicherung hielte die Platte für unverändert. FullReadRequired bool `json:"full_read_required"` } // RestoreTargetKind benennt das Ziel einer Wiederherstellung. type RestoreTargetKind string const ( // RestoreToOriginalHost stellt am Ursprungsort wieder her. RestoreToOriginalHost RestoreTargetKind = "original_host" // RestoreToAlternateHost stellt auf einem anderen Wirt wieder her. RestoreToAlternateHost RestoreTargetKind = "alternate_host" // RestoreAsNewGuest legt einen neuen Gast an und lässt das Original bestehen. RestoreAsNewGuest RestoreTargetKind = "new_guest" ) // RestoreRequest beschreibt eine Wiederherstellung. type RestoreRequest struct { // TargetKind ist die Art des Ziels. TargetKind RestoreTargetKind // SourceGuestID ist der ursprüngliche Gast. SourceGuestID string // TargetGuestID ist die Kennung des Ziels; leer wählt die nächste freie. TargetGuestID string // TargetHostID ist der Zielwirt; leer wählt den Ursprungswirt. TargetHostID string // TargetStorageID lenkt die Platten auf einen anderen Speicher. TargetStorageID string // ArchiveReference benennt das wiederherzustellende Abbild. ArchiveReference string // Metadata ist die wiederherzustellende Konfiguration. Metadata *GuestMetadata // StartAfterRestore startet den Gast nach der Wiederherstellung. // // Standardmäßig bleibt er aus. Eine wiederhergestellte Maschine, die sich // unaufgefordert mit derselben Adresse ins Netz meldet wie das noch // laufende Original, richtet mehr Schaden an als der Ausfall. StartAfterRestore bool // OverwriteExisting erlaubt das Überschreiben eines vorhandenen Gasts. OverwriteExisting bool // ProgressCallback meldet den Fortschritt. ProgressCallback func(RestoreProgress) } // RestoreProgress meldet den Stand einer Wiederherstellung. type RestoreProgress struct { // Stage benennt den aktuellen Schritt. Stage string `json:"stage"` // PercentComplete ist der Fortschritt in Prozent, sofern bekannt. PercentComplete float64 `json:"percent_complete"` // Message ist eine erläuternde Meldung der Plattform. Message string `json:"message,omitempty"` } // RestoreResult beschreibt eine abgeschlossene Wiederherstellung. type RestoreResult struct { // GuestID ist die Kennung des wiederhergestellten Gasts. GuestID string `json:"guest_id"` // HostID ist der Wirt, auf dem er liegt. HostID string `json:"host_id"` // Started meldet, ob der Gast gestartet wurde. Started bool `json:"started"` // Duration ist die Gesamtdauer. Duration time.Duration `json:"duration"` // Warnings sind Hinweise, die den Erfolg nicht aufheben, aber Beachtung // verlangen — etwa eine auf einen anderen Speicher verschobene Platte. Warnings []string `json:"warnings,omitempty"` } // DiskReadRequest beschreibt den Lesezugriff auf Plattendaten. type DiskReadRequest struct { // GuestID ist der Gast. GuestID string // SnapshotID ist das Abbild, aus dem gelesen wird. SnapshotID string // DiskIdentifier benennt die Platte. DiskIdentifier string // Ranges beschränkt das Lesen auf bestimmte Bereiche; leer liest alles. Ranges []BlockRange } // VirtualizationProvider ist die herstellerneutrale Schnittstelle // (SYNCOVA_ARCHITECTURE.md §5). // // Jede Umsetzung muss zwei Regeln einhalten: // // 1. Nicht unterstützte Fähigkeiten werden mit ErrNotSupported gemeldet, nicht // mit einem leeren Ergebnis umgangen. Ein leerer Rückgabewert sähe aus wie // „nichts zu tun" und führte zu einem Backup, das nichts enthält. // 2. Kein Aufruf verändert den Gast, ohne dass der Aufrufer es verlangt hat. type VirtualizationProvider interface { // Name benennt den Provider für Protokolle und Oberfläche. Name() string // Connect stellt die Verbindung her und prüft die Anmeldedaten. Connect(connectContext context.Context) error // Disconnect gibt die Verbindung frei. Disconnect() error // ListClusters ermittelt die erreichbaren Verbünde. ListClusters(listContext context.Context) ([]Cluster, error) // ListHosts ermittelt die Wirte eines Verbunds. ListHosts(listContext context.Context, clusterID string) ([]Host, error) // ListVMs ermittelt die Gäste; ein leerer Wirt bedeutet alle Wirte. ListVMs(listContext context.Context, hostID string) ([]Guest, error) // GetVMInfo liefert die Angaben zu einem einzelnen Gast. GetVMInfo(infoContext context.Context, guestID string) (*Guest, error) // GetVMDisks liefert die Platten eines Gasts. GetVMDisks(diskContext context.Context, guestID string) ([]Disk, error) // GetVMMetaData liefert die Konfiguration eines Gasts. GetVMMetaData(metadataContext context.Context, guestID string) (*GuestMetadata, error) // CreateSnapshot legt ein Zeitpunktabbild an. CreateSnapshot(snapshotContext context.Context, guestID string, snapshotOptions SnapshotOptions) (*Snapshot, error) // RemoveSnapshot entfernt ein Zeitpunktabbild. RemoveSnapshot(removeContext context.Context, guestID string, snapshotID string) error // ReadChangedBlocks ermittelt die seit einer früheren Sicherung geänderten // Bereiche. // // Ist keine Nachverfolgung verfügbar, wird ErrNotSupported gemeldet. Der // Aufrufer liest dann die ganze Platte — langsamer, aber richtig. ReadChangedBlocks(blockContext context.Context, guestID string, diskIdentifier string, previousTrackingID string) (*ChangedBlockResult, error) // OpenDisk öffnet die Plattendaten zum Lesen. // // Der zurückgegebene Datenstrom wird von der Backup Engine verarbeitet; der // Provider kennt weder Chunking noch Verschlüsselung. OpenDisk(openContext context.Context, readRequest DiskReadRequest) (io.ReadCloser, error) // RestoreVM stellt einen Gast wieder her. RestoreVM(restoreContext context.Context, restoreRequest RestoreRequest) (*RestoreResult, error) } // ErrNotSupported meldet eine von dieser Plattform nicht unterstützte Fähigkeit. // // Der Fehler ist ein vollwertiges Ergebnis, kein Versagen: er erlaubt es dem // Aufrufer, auf ein anderes Verfahren auszuweichen. Ihn zu verschweigen wäre // ein Fake-Feature (PROMPT.md §138). var ErrNotSupported = errors.New("diese fähigkeit wird von der plattform nicht unterstützt") // ErrGuestNotFound meldet einen nicht vorhandenen Gast. var ErrGuestNotFound = errors.New("der gast wurde auf der plattform nicht gefunden") // ErrNotConnected meldet einen Aufruf ohne bestehende Verbindung. var ErrNotConnected = errors.New("es besteht keine verbindung zur plattform") // ErrGuestRunning meldet einen Eingriff, der einen angehaltenen Gast verlangt. var ErrGuestRunning = errors.New("der gast läuft; der vorgang verlangt einen angehaltenen gast")