go-libvirt

repository·main·Indexed 22 days ago

https://github.com/digitalocean/go-libvirt

A pure Go interface for interacting with libvirt via its RPC interface. It uses XDR-encoded RPC packets to avoid C bindings, supporting both local and remote connections via URIs (including TCP, TLS, and SSH). The library maps libvirt C API functions to Go by removing the 'vir' prefix and provides tools for re-generating bindings for specific libvirt versions.

Tokens
34.2K
Snippets
141
Records
199
Agent score
78%

What's inside go-libvirt

  1. How to use the go-libvirt library

    main

    The go-libvirt package provides a pure Go interface for interacting with libvirt via its RPC interface (using XDR encoding).

    Mapping libvirt C functions to Go

    Functions in the Go package are mapped from the libvirt C API by removing the vir prefix. For example:

    • virDomainShutdown() becomes DomainShutdown()
    • virDomainShutdownFlags() becomes DomainShutdownFlags()

    Best Practices

    • Use generated code: Most of the library is generated. While libvirt.go contains some hand-written routines, it is recommended to call the generated routines in libvirt.gen.go directly, as the hand-written ones may be removed in future versions.
    • Vendoring: Because the API is not considered stable, it is highly recommended to vendor go-libvirt into your project.
    • Error Handling: Always check for errors returned by connection and API calls, as the library may return 'unimplemented' errors if the generated code does not match your local libvirt version.
  2. Re-generate go-libvirt bindings for a specific libvirt version

    main

    If you are using a version of libvirt that is newer or different from the one used to build the current go-libvirt distribution, you may need to re-run the code generators to avoid missing functions or 'unimplemented' errors.

    Steps to re-generate:

    1. Download the libvirt source code for your target version.
    2. Unpack the distribution and enter the directory.
    3. Run the configuration step (you don't need to build the whole project, just run the configuration to generate required files):
      • For libvirt < v6.7.0:
        mkdir build; cd build
        ../autogen.sh
      • For libvirt >= v6.7.0:
        meson setup build
    4. Set the LIBVIRT_SOURCE environment variable to the path of your unpacked libvirt directory.
    5. Navigate to the go-libvirt directory and run:
      go generate ./...
  3. Use TypedParam for dynamic values

    main

    The TypedParamValue type is a discriminated union used to pass different types of parameters to libvirt. Use the provided constructor functions to create these values safely.

    Supported types via constructors:

    • NewTypedParamValueInt(v int32) (Type ID: 1)
    • NewTypedParamValueUint(v uint32) (Type ID: 2)
    • NewTypedParamValueLlong(v int64) (Type ID: 3)
    • NewTypedParamValueUllong(v uint64) (Type ID: 4)
    • NewTypedParamValueDouble(v float64) (Type ID: 5)
    • NewTypedParamValueBoolean(v int32) (Type ID: 6)
    • NewTypedParamValueString(v string) (Type ID: 7)
    // Example of creating a typed parameter for a scheduler
    param := NewTypedParamValueString("fifo")
    // This can then be passed to methods like DomainSetSchedulerParameters
  4. Domain Event Messages

    main

    When subscribing to domain events, the following message structures are used to communicate lifecycle and status changes:

    Lifecycle & Status Messages

    • Lifecycle Event: DomainEventLifecycleMsg contains the Dom (Domain), Event (int32), and Detail (int32).
    • Reboot: DomainEventRebootMsg contains the Dom (Domain).
    • RTC Change: DomainEventRtcChangeMsg contains the Dom (Domain) and Offset (int64).
    • Watchdog: DomainEventWatchdogMsg contains the Dom (Domain) and Action (int32).
    • IO Error: DomainEventIOErrorMsg contains Dom (Domain), SrcPath (string), DevAlias (string), and Action (int32). DomainEventIOErrorReasonMsg adds a Reason (string).
    • Graphics: DomainEventGraphicsMsg contains Dom (Domain), Phase (int32), Local and Remote (DomainEventGraphicsAddress), AuthScheme (string), and Subject ([]DomainEventGraphicsIdentity).
    • Block Job: DomainEventBlockJobMsg contains Dom (Domain), Path (string), Type (int32), and Status (int32).
    • Disk Change: DomainEventDiskChangeMsg contains Dom (Domain), OldSrcPath (OptString), NewSrcPath (OptString), DevAlias (string), and Reason (int32).
    • Tray Change: DomainEventTrayChangeMsg contains Dom (Domain), DevAlias (string), and Reason (int32).
    • PM Events: DomainEventPmwakeupMsg (contains Dom), DomainEventPmsuspendMsg (contains Dom), and DomainEventPmsuspendDiskMsg (contains Dom).
    • Balloon Change: DomainEventBalloonChangeMsg contains Dom (Domain) and Actual (uint64).
  5. Perform Domain Migration

    main

    The library supports complex migration workflows (specifically the migrate_begin3 series) which involve multiple steps and the exchange of cookies and XML data.

    Migration Workflow Types:

    • Begin: DomainMigrateBegin3Args starts the process, returning a CookieOut and XML.
    • Prepare: DomainMigratePrepare3Args or DomainMigratePrepareTunnel3Args prepares the migration, requiring a CookieIn and returning a CookieOut.
    • Perform: DomainMigratePerform3Args executes the migration using the provided cookies and connection URIs.
    • Finish: DomainMigrateFinish3Args completes the migration.
    • Confirm: DomainMigrateConfirm3Args is used to confirm the migration state.
  6. Perform domain migration with DomainMigratePrepare, Perform, and Finish

    main

    Domain migration is a multi-step process involving preparation, execution, and finalization.

    1. Prepare: Call DomainMigratePrepare to initiate the migration. It returns a rCookie (byte slice) and an rUriOut (OptString) which are required for subsequent steps.
    2. Perform: Call DomainMigratePerform using the Dom, the Cookie from the prepare step, and the target Uri.
    3. Finish: Call DomainMigrateFinish using the Dname, the Cookie, and the target Uri to complete the migration and receive the migrated Domain object.
    // 1. Prepare
    cookie, uriOut, err := l.DomainMigratePrepare(uriIn, flags, dname, bandwidth)
    
    // 2. Perform
    err = l.DomainMigratePerform(dom, cookie, uri, flags, dname, bandwidth)
    
    // 3. Finish
    domMigrated, err := l.DomainMigrateFinish(dname, cookie, uri, flags)
  7. Connect to libvirt via standard URI

    main

    You can connect to a local or remote libvirt instance using a URI. The following example demonstrates connecting to the local QEMU system and listing active/inactive domains.

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/url"
    
    	"github.com/digitalocean/go-libvirt"
    )
    
    func main() {
    	uri, _ := url.Parse(string(libvirt.QEMUSystem))
    	l, err := libvirt.ConnectToURI(uri)
    	if err != nil {
    		log.Fatalf("failed to connect: %v", err)
    	}
    
    	v, err := l.ConnectGetLibVersion()
    	if err != nil {
    		log.Fatalf("failed to retrieve libvirt version: %v", err)
    	}
    	fmt.Println("Version:", v)
    
    	flags := libvirt.ConnectListDomainsActive | libvirt.ConnectListDomainsInactive
    	domains, _, err := l.ConnectListAllDomains(1, flags)
    	if err != nil {
    		log.Fatalf("failed to retrieve domains: %v", err)
    	}
    
    	fmt.Println("ID\tName\t\tUUID")
    	fmt.Printf("--------------------------------------------------------\n")
    	for _, d := range domains {
    		fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID)
    	}
    
    	if err = l.Disconnect(); err != nil {
    		log.Fatalf("failed to disconnect: %v", err)
    	}
    }
  8. Connect to libvirt via TLS over TCP

    main

    To connect to a remote libvirt instance using TLS, use libvirt.NewWithDialer with a TLS dialer from the github.com/digitalocean/go-libvirt/socket/dialers package.

    Note: To connect to a remote machine, you must have the CA, client certificate, and private key (typically located in ~/.pki/libvirt/ or /etc/pki/libvirt/).

    package main
    
            import (
            	"crypto/tls"
            	"crypto/x509"
    
            	"fmt"
            	"io/ioutil"
            	"log"
    
            	"github.com/digitalocean/go-libvirt"
            	"github.com/digitalocean/go-libvirt/socket/dialers"
            )
    
    func main() {
            // This dials libvirt on the local machine
            // It connects to libvirt via TLS over TCP
            // To connect to a remote machine, you need to have the ca/cert/key of it.
            // The private key is at ~/.pki/libvirt/clientkey.pem
            // or /etc/pki/libvirt/private/clientkey.pem
            // The Client Cert is at ~/.pki/libvirt/clientcert.pem
            // or /etc/pki/libvirt/clientcert.pem
            // The CA Cert is at ~/.pki/libvirt/cacert.pem
            // or /etc/pki/CA/cacert.pem
    
            // Use host name or IP which is valid in certificate
            addr := "10.10.10.10"
    
            l := libvirt.NewWithDialer(dialers.NewTLS(addr))
            if err := l.Connect(); err != nil {
            	log.Fatalf("failed to connect: %v", err)
            }
    
            v, err := l.Version()
            if err != nil {
            	log.Fatalf("failed to retrieve libvirt version: %v", err)
            }
            fmt.Println("Version:", v)
    
            // Return both running and stopped VMs
            flags := libvirt.ConnectListDomainsActive | libvirt.ConnectListDomainsInactive
            domains, _, err := l.ConnectListAllDomains(1, flags)
            if err != nil {
            	log.Fatalf("failed to retrieve domains: %v", err)
            }
    
            fmt.Println("ID\tName\t\tUUID")
            fmt.Println("--------------------------------------------------------")
            for _, d := range domains {
            	fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID)
            }
    
            if err := l.Disconnect(); err != nil {
            	log.Fatalf("failed to disconnect: %v", err)
            }
    }
  9. Decode XDR data with xdr.Unmarshal

    main

    Use xdr.Unmarshal to decode XDR-encoded data into a Go struct. Version 2 of the library accepts an io.Reader (such as a bytes.Reader, a file, or a network connection) and a pointer to the target struct. It returns the number of bytes read and any error encountered.

    package main
    
    import (
    	"bytes"
        "fmt"
    
        "github.com/davecgh/go-xdr/xdr2"
    )
    
    func main() {
    	// Hypothetical image header format.
    	type ImageHeader struct {
    		Signature   [3]byte
    		Version     uint32
    		IsGrayscale bool
    		NumSections uint32
    	}
    
    	// XDR encoded data.
    	encodedData := []byte{
    		0xAB, 0xCD, 0xEF, 0x00, // Signature
    		0x00, 0x00, 0x00, 0x02, // Version
    		0x00, 0x00, 0x00, 0x01, // IsGrayscale
    		0x00, 0x00, 0x00, 0x0A, // NumSections
    	}
    
    	// Declare a variable to provide Unmarshal with a concrete type and instance
    	// to decode into.
    	var h ImageHeader
    	bytesRead, err := xdr.Unmarshal(bytes.NewReader(encodedData), &h)
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
      
    	fmt.Println("bytes read:", bytesRead)
    	fmt.Printf("h: %+v", h)
    }
  10. Encode Go structs with xdr.Marshal

    main

    Use xdr.Marshal to encode a Go struct into the XDR data representation. Version 2 of the library accepts an io.Writer (such as a bytes.Buffer, a file, or a network connection) and a pointer to the data to be encoded. It returns the number of bytes written and any error encountered.

    package main
    
    import (
    	"bytes"
        "fmt"
    
        "github.com/davecgh/go-xdr/xdr2"
    )
    
    func main() {
    	// Hypothetical image header format.
    	type ImageHeader struct {
    		Signature   [3]byte
    		Version     uint32
    		IsGrayscale bool
    		NumSections uint32
    	}
    
    	// Sample image header data.
    	h := ImageHeader{[3]byte{0xAB, 0xCD, 0xEF}, 2, true, 10}
    
    	// Use marshal to automatically determine the appropriate underlying XDR
    	// types and encode.
    	var w bytes.Buffer
    	bytesWritten, err := xdr.Marshal(&w, &h)
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
    
    	encodedData := w.Bytes()
    	fmt.Println("bytes written:", bytesWritten)
    	fmt.Println("encoded data:", encodedData)
    }
  11. Perform FSTRIM on a domain with DomainFstrim

    main

    Trigger an FSTRIM operation on a domain's mount point.

    Parameters:

    • Dom (Domain): The target domain.
    • MountPoint (OptString): The mount point to trim.
    • Minimum (uint64): Minimum size for the trim operation.
    • Flags (uint32): Operation flags.
    func (l *Libvirt) DomainFstrim(Dom Domain, MountPoint OptString, Minimum uint64, Flags uint32) (err error)