Add Get/SetBlockIoTune to go-libvirt API. (#45)

* Add Get/SetBlockIoTune to go-libvirt API.

This adds two libvirt entry points to the go-libvirt API:
virDomainSetBlockIoTune and virDomainGetBlockIoTune. These can be used
to control block device throttling for a VM.
This commit is contained in:
Geoff Hickey 2017-08-31 11:21:31 -04:00 committed by GitHub
parent 85dc33f30e
commit c8e4b6a7b8
4 changed files with 382 additions and 2 deletions

View File

@ -53,6 +53,8 @@ const (
ProcDomainUndefineFlags = 231 ProcDomainUndefineFlags = 231
ProcDomainDestroyFlags = 234 ProcDomainDestroyFlags = 234
ProcDomainReset = 245 ProcDomainReset = 245
ProcDomainSetBlockIOTune = 252
ProcDomainGetBlockIOTune = 253
ProcDomainShutdownFlags = 258 ProcDomainShutdownFlags = 258
ProcConnectListAllDomains = 273 ProcConnectListAllDomains = 273
ProcConnectListAllStoragePools = 281 ProcConnectListAllStoragePools = 281
@ -78,4 +80,8 @@ const (
// UUIDSize is the length of a UUID, in bytes. // UUIDSize is the length of a UUID, in bytes.
UUIDSize = 16 UUIDSize = 16
// TypedParamFieldLength is VIR_TYPED_PARAM_FIELD_LENGTH, and is the maximum
// length of the Field string in virTypedParameter structs.
TypedParamFieldLength = 80
) )

View File

@ -19,6 +19,7 @@ package libvirt
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/binary"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -94,6 +95,86 @@ type qemuError struct {
// DomainXMLFlags specifies options for dumping a domain's XML. // DomainXMLFlags specifies options for dumping a domain's XML.
type DomainXMLFlags uint32 type DomainXMLFlags uint32
// DomainAffectFlags specifies options for whether an operation affects the
// running VM, or the persistent VM configuration on disk. See FlagDomain...
// consts for values.
type DomainAffectFlags uint32
// Consts used for flags
// virDomainModificationImpact and virTypedParameterFlags values. These are
// combined here because they are both used to set the same flags fields in the
// libvirt API.
const (
// FlagDomainAffectCurrent means affect the current domain state
FlagDomainAffectCurrent DomainAffectFlags = 0
// FlagDomainAffectLive means affect the running domain state
FlagDomainAffectLive = 1 << (iota - 1)
// FlagDomainAffectConfig means affect the persistent domain state.
FlagDomainAffectConfig
// FlagTypedParamStringOkay tells the server that this client understands
// TypedParameStrings.
FlagTypedParamStringOkay
)
// Consts relating to TypedParams:
const (
// TypedParamInt is a C int.
TypedParamInt = iota + 1
// TypedParamUInt is a C unsigned int.
TypedParamUInt
// TypedParamLLong is a C long long int.
TypedParamLLong
// TypedParamULLong is a C unsigned long long int.
TypedParamULLong
// TypedParamDouble is a C double.
TypedParamDouble
// TypedParamBoolean is a C char.
TypedParamBoolean
// TypedParamString is a C char*.
TypedParamString
// TypedParamLast is just an end-of-enum marker.
TypedParamLast
)
// TypedParam represents libvirt's virTypedParameter, which is used to pass
// typed parameters to libvirt functions. The `Value` field defined as a union
// in libvirt, and given the current set of types inside it, is 8 bytes long.
type TypedParam struct {
Field string
Type int
Value [8]byte
}
// NewTypedParamInt returns a TypedParam encoding for an int.
func NewTypedParamInt(name string, v int) *TypedParam {
// Truncate the field name if it's longer than the limit.
if len(name) > constants.TypedParamFieldLength {
name = name[:constants.TypedParamFieldLength]
}
tp := TypedParam{
Field: name,
Type: TypedParamInt,
}
binary.BigEndian.PutUint32(tp.Value[:], uint32(v))
return &tp
}
// NewTypedParamULongLong returns a TypedParam encoding for an unsigned long long.
func NewTypedParamULongLong(name string, v uint64) *TypedParam {
// Truncate the field name if it's longer than the limit.
if len(name) > constants.TypedParamFieldLength {
name = name[:constants.TypedParamFieldLength]
}
tp := TypedParam{
Field: name,
Type: TypedParamULLong,
}
binary.BigEndian.PutUint64(tp.Value[:], v)
return &tp
}
const ( const (
// DomainXMLFlagSecure dumps XML with sensitive information included. // DomainXMLFlagSecure dumps XML with sensitive information included.
DomainXMLFlagSecure DomainXMLFlags = 1 << iota DomainXMLFlagSecure DomainXMLFlags = 1 << iota
@ -601,7 +682,7 @@ func (l *Libvirt) Events(dom string) (<-chan DomainEvent, error) {
res := <-resp res := <-resp
if res.Status != StatusOK { if res.Status != StatusOK {
err := decodeError(res.Payload) err = decodeError(res.Payload)
if err == ErrUnsupported { if err == ErrUnsupported {
return nil, ErrEventsNotSupported return nil, ErrEventsNotSupported
} }
@ -1199,6 +1280,148 @@ func (l *Libvirt) Reset(dom string) error {
return nil return nil
} }
// BlockLimit contains a name and value pair for a Get/SetBlockIOTune limit. The
// Name field is the name of the limit (to see a list of the limits that can be
// applied, execute the 'blkdeviotune' command on a VM in virsh). Callers can
// use the QEMUBlockIO... constants below for the Name value. The Value field is
// the limit to apply.
type BlockLimit struct {
Name string
Value uint64
}
// BlockIOTune-able values. These tunables are different for different
// hypervisors; currently only the tunables for QEMU are defined here. These are
// not necessarily the only possible values; different libvirt versions may add
// or remove parameters from this list.
const (
QEMUBlockIOTotalBytesSec = "total_bytes_sec"
QEMUBlockIOReadBytesSec = "read_bytes_sec"
QEMUBlockIOWriteBytesSec = "write_bytes_sec"
QEMUBlockIOTotalIOPSSec = "total_iops_sec"
QEMUBlockIOReadIOPSSec = "read_iops_sec"
QEMUBlockIOWriteIOPSSec = "write_iops_sec"
QEMUBlockIOTotalBytesSecMax = "total_bytes_sec_max"
QEMUBlockIOReadBytesSecMax = "read_bytes_sec_max"
QEMUBlockIOWriteBytesSecMax = "write_bytes_sec_max"
QEMUBlockIOTotalIOPSSecMax = "total_iops_sec_max"
QEMUBlockIOReadIOPSSecMax = "read_iops_sec_max"
QEMUBlockIOWriteIOPSSecMax = "write_iops_sec_max"
QEMUBlockIOSizeIOPSSec = "size_iops_sec"
QEMUBlockIOTotalBytesSecMaxLength = "total_bytes_sec_max_length"
QEMUBlockIOReadBytesSecMaxLength = "read_bytes_sec_max_length"
QEMUBlockIOWriteBytesSecMaxLength = "write_bytes_sec_max_length"
QEMUBlockIOTotalIOPSSecMaxLength = "total_iops_sec_max_length"
QEMUBlockIOReadIOPSSecMaxLength = "read_iops_sec_max_length"
QEMUBlockIOWriteIOPSSecMaxLength = "write_iops_sec_max_length"
)
// SetBlockIOTune changes the per-device block I/O tunables within a guest.
// Parameters are the name of the VM, the name of the disk device to which the
// limits should be applied, and 1 or more BlockLimit structs containing the
// actual limits.
//
// The limits which can be applied here are enumerated in the QEMUBlockIO...
// constants above, and you can also see the full list by executing the
// 'blkdeviotune' command on a VM in virsh.
//
// Example usage:
// SetBlockIOTune("vm-name", "vda", BlockLimit{libvirt.QEMUBlockIOWriteBytesSec, 1000000})
func (l *Libvirt) SetBlockIOTune(dom string, disk string, limits ...BlockLimit) error {
d, err := l.lookup(dom)
if err != nil {
return err
}
// https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainSetBlockIoTune
payload := struct {
Domain Domain
Disk string
Params []TypedParam
Flags DomainAffectFlags
}{
Domain: *d,
Disk: disk,
Flags: FlagDomainAffectLive,
}
for _, limit := range limits {
tp := NewTypedParamULongLong(limit.Name, limit.Value)
payload.Params = append(payload.Params, *tp)
}
buf, err := encode(&payload)
if err != nil {
return err
}
resp, err := l.request(constants.ProcDomainSetBlockIOTune, constants.ProgramRemote, &buf)
if err != nil {
return err
}
r := <-resp
if r.Status != StatusOK {
return decodeError(r.Payload)
}
return nil
}
// GetBlockIOTune returns a slice containing the current block I/O tunables for
// a disk.
func (l *Libvirt) GetBlockIOTune(dom string, disk string) ([]BlockLimit, error) {
d, err := l.lookup(dom)
if err != nil {
return nil, err
}
payload := struct {
Domain Domain
Disk []string
ParamCount uint32
Flags DomainAffectFlags
}{
Domain: *d,
Disk: []string{disk},
ParamCount: 32,
Flags: FlagTypedParamStringOkay,
}
buf, err := encode(&payload)
if err != nil {
return nil, err
}
resp, err := l.request(constants.ProcDomainGetBlockIOTune, constants.ProgramRemote, &buf)
if err != nil {
return nil, err
}
r := <-resp
if r.Status != StatusOK {
return nil, decodeError(r.Payload)
}
dec := xdr.NewDecoder(bytes.NewReader(r.Payload))
result := struct {
Limits []TypedParam
ParamCount uint32
}{}
_, err = dec.Decode(&result)
if err != nil {
return nil, err
}
limits := make([]BlockLimit, len(result.Limits))
for ix := range result.Limits {
limits[ix].Name = result.Limits[ix].Field
limits[ix].Value = binary.BigEndian.Uint64(result.Limits[ix].Value[:])
}
return limits, nil
}
// lookup returns a domain as seen by libvirt. // lookup returns a domain as seen by libvirt.
func (l *Libvirt) lookup(name string) (*Domain, error) { func (l *Libvirt) lookup(name string) (*Domain, error) {
payload := struct { payload := struct {

View File

@ -437,3 +437,27 @@ func TestReset(t *testing.T) {
t.Fatalf("unexpected reset error: %v", err) t.Fatalf("unexpected reset error: %v", err)
} }
} }
func TestSetBlockIOTune(t *testing.T) {
conn := libvirttest.New()
l := New(conn)
if err := l.SetBlockIOTune("test", "vda", BlockLimit{"write_bytes_sec", 5000000}); err != nil {
t.Fatalf("unexpected SetBlockIOTune error: %v", err)
}
}
func TestGetBlockIOTune(t *testing.T) {
conn := libvirttest.New()
l := New(conn)
limits, err := l.GetBlockIOTune("do-test", "vda")
if err != nil {
t.Fatalf("unexpected GetBlockIOTune error: %v", err)
}
lim := BlockLimit{"write_bytes_sec", 500000}
if limits[2] != lim {
t.Fatalf("unexpected result in limits list: %v", limits[2])
}
}

View File

@ -21,8 +21,9 @@ import (
"sync/atomic" "sync/atomic"
"fmt" "fmt"
"github.com/digitalocean/go-libvirt/internal/constants"
"os" "os"
"github.com/digitalocean/go-libvirt/internal/constants"
) )
var testDomainResponse = []byte{ var testDomainResponse = []byte{
@ -399,6 +400,128 @@ var testRebootReply = []byte{
0x00, 0x00, 0x00, 0x00, // status 0x00, 0x00, 0x00, 0x00, // status
} }
var testSetBlockIoTuneReply = []byte{
0x00, 0x00, 0x00, 0x1c, // length
0x20, 0x00, 0x80, 0x86, // program
0x00, 0x00, 0x00, 0x01, // version
0x00, 0x00, 0x00, 0xfc, // procedure
0x00, 0x00, 0x00, 0x00, // type
0x00, 0x00, 0x00, 0x00, // serial
0x00, 0x00, 0x00, 0x00, // status
}
// This result block was obtained by calling `fmt.Printf("%#v", r.Payload)` on
// the result returned by an actual call to GetBlockIoTune, and then adding the
// standard header to the beginning. The length parameter has to be correct!
var testGetBlockIoTuneReply = []byte{
0x00, 0x00, 0x02, 0xe0, // length
0x20, 0x00, 0x80, 0x86, // program
0x00, 0x00, 0x00, 0x01, // version
0x00, 0x00, 0x00, 0xfd, // procedure
0x00, 0x00, 0x00, 0x00, // type
0x00, 0x00, 0x00, 0x00, // serial
0x00, 0x00, 0x00, 0x00, // status
0x0, 0x0, 0x0, 0x13, // 13 TypedParams follow
0x0, 0x0, 0x0, 0xf, // field name is 15 bytes, padded to a multiple of 4
0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x0,
0x0, 0x0, 0x0, 0x4, // type
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // value
0x0, 0x0, 0x0, 0xe,
0x72, 0x65, 0x61, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xf,
0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xa1, 0x20,
0x0, 0x0, 0x0, 0xe,
0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x69, 0x6f, 0x70, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xd,
0x72, 0x65, 0x61, 0x64, 0x5f, 0x69, 0x6f, 0x70, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xe,
0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x6f, 0x70, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x13,
0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x12,
0x72, 0x65, 0x61, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x13,
0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc3, 0x50,
0x0, 0x0, 0x0, 0x12,
0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x69, 0x6f, 0x70, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x11,
0x72, 0x65, 0x61, 0x64, 0x5f, 0x69, 0x6f, 0x70, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x12,
0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x6f, 0x70, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xd,
0x73, 0x69, 0x7a, 0x65, 0x5f, 0x69, 0x6f, 0x70, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x1a,
0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x19,
0x72, 0x65, 0x61, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x1a,
0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x0, 0x0, 0x0, 0x19,
0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x69, 0x6f, 0x70, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x18,
0x72, 0x65, 0x61, 0x64, 0x5f, 0x69, 0x6f, 0x70, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x19,
0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x6f, 0x70, 0x73, 0x5f, 0x73, 0x65, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, // End of TypedParams
}
// MockLibvirt provides a mock libvirt server for testing. // MockLibvirt provides a mock libvirt server for testing.
type MockLibvirt struct { type MockLibvirt struct {
net.Conn net.Conn
@ -486,6 +609,10 @@ func (m *MockLibvirt) handleRemote(procedure uint32, conn net.Conn) {
conn.Write(m.reply(testCreateWithFlags)) conn.Write(m.reply(testCreateWithFlags))
case constants.ProcDomainShutdownFlags: case constants.ProcDomainShutdownFlags:
conn.Write(m.reply(testShutdownReply)) conn.Write(m.reply(testShutdownReply))
case constants.ProcDomainSetBlockIOTune:
conn.Write(m.reply(testSetBlockIoTuneReply))
case constants.ProcDomainGetBlockIOTune:
conn.Write(m.reply(testGetBlockIoTuneReply))
default: default:
fmt.Fprintln(os.Stderr, "unknown procedure", procedure) fmt.Fprintln(os.Stderr, "unknown procedure", procedure)
} }