diff --git a/examples/README.md b/examples/README.md index d4d702e..b31c7b4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -121,4 +121,49 @@ You should have an output similar to this: ```{r, engine='bash', count_lines} Connecting to unix:///var/run/libvirt/libvirt-sock Domain should be shut off now +``` + +#### libvirtgo_run_command + +[libvirtgo_run_command](./libvirtgo_run_command) demonstrates how to use + the [libvirtGoMonitorLinux](https://godoc.org/github.com/digitalocean/go-qemu/libvirtGoMonitorLinux) + package to send a QMP command to the specified domain. + +To run: +```{r, engine='bash', count_lines} + $ go get github.com/digitalocean/go-qemu + $ go run examples/libvirtgo_run_command/main.go -uri="qemu:///system" -domainName="centos7" +``` + + +You should see an output similar to this: +```{r, engine='bash', count_lines} +query-cpus: {"return":[{"current":true,"CPU":0,"qom_path":"/machine/unattached/device[0]","pc":-2130342250,"halted":true,"thread_id":2462}],"id":"libvirt-36"} +``` + +#### libvirtgo_events + +[libvirtgo_events](./libvirtgo_events) demonstrates how to use + the [libvirtGoMonitorLinux](https://godoc.org/github.com/digitalocean/go-qemu/libvirtGoMonitorLinux) + package to wait for lifecycle events from the specified domain. + +To run: +```{r, engine='bash', count_lines} + $ go get github.com/digitalocean/go-qemu + + Terminal 1: + $ go run examples/libvirtgo_events/main.go -uri="qemu:///system" -domainName="ubuntu14.04" + + Terminal 2: + virsh -c qemu:///system + virsh # start ubuntu14.04 +``` + + +You should see an output similar to this on Terminal 1: +```{r, engine='bash', count_lines} +Waiting for Domain events... +Press the Enter key to stop +Event: qmp.Event{Event:"Domain event=\"resumed\" detail=\"unpaused\"", Data:map[string]interface {}{"details":libvirt.DomainLifecycleEvent{Event:4, Detail:0}}, Timestamp:struct { Seconds int64 "json:\"seconds\""; Microseconds int64 "json:\"microseconds\"" }{Seconds:11, Microseconds:0}} +Event: qmp.Event{Event:"Domain event=\"started\" detail=\"booted\"", Data:map[string]interface {}{"details":libvirt.DomainLifecycleEvent{Event:2, Detail:0}}, Timestamp:struct { Seconds int64 "json:\"seconds\""; Microseconds int64 "json:\"microseconds\"" }{Seconds:12, Microseconds:0}} ``` \ No newline at end of file diff --git a/examples/libvirtgo_events/main.go b/examples/libvirtgo_events/main.go new file mode 100644 index 0000000..910eb6e --- /dev/null +++ b/examples/libvirtgo_events/main.go @@ -0,0 +1,55 @@ +// Copyright 2016 The go-qemu Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "flag" + "fmt" + "log" + + "github.com/digitalocean/go-qemu/qmp" +) + +var ( + uri = flag.String("uri", "qemu:///system", "URI to connect to the libvirtd host.") + domainName = flag.String("domainName", "mydomain", "The domain to run commands against.") +) + +func main() { + flag.Parse() + + mon := qmp.NewLibvirtGoMonitor(*uri, *domainName) + + err := mon.Connect() + if err != nil { + log.Fatalf("Unable to connect: %v\n", err) + } + + events, err := mon.Events() + if err != nil { + log.Fatalf("Unable to register for events: %v\n", err) + } + + fmt.Println("Waiting for Domain events...") + go func() { + for event := range events { + fmt.Printf("Event: %#v\n", event) + } + }() + + fmt.Println("Press the Enter key to stop") + fmt.Scanln() + mon.Disconnect() +} diff --git a/examples/libvirtgo_run_command/main.go b/examples/libvirtgo_run_command/main.go new file mode 100644 index 0000000..a59bfa0 --- /dev/null +++ b/examples/libvirtgo_run_command/main.go @@ -0,0 +1,57 @@ +// Copyright 2016 The go-qemu Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "flag" + "fmt" + "log" + + qemu "github.com/digitalocean/go-qemu" + "github.com/digitalocean/go-qemu/qmp" +) + +var ( + uri = flag.String("uri", "qemu:///system", "URI to connect to the libvirtd host.") + domainName = flag.String("domainName", "mydomain", "The domain to run commands against.") +) + +func main() { + flag.Parse() + + mon := qmp.NewLibvirtGoMonitor(*uri, *domainName) + + if err := mon.Connect(); err != nil { + log.Fatalf("failed to connect: %v", err) + } + + domain, err := qemu.NewDomain(mon, *domainName) + if err != nil { + log.Fatalf("failed to create domain object: %v", err) + } + defer domain.Close() + + // domain.CPUs will forward QMP commands to the monitor + cpus, err := domain.CPUs() + if err != nil { + log.Fatalf("failed to get cpus: %v", err) + } + + fmt.Printf("CPUs: %v\n", cpus) + + if err = mon.Disconnect(); err != nil { + log.Fatalf("Unable to disconnect: %v\n", err) + } +} diff --git a/qmp/README.md b/qmp/README.md index 729280b..1a8c40e 100644 --- a/qmp/README.md +++ b/qmp/README.md @@ -7,7 +7,7 @@ Package `qmp` enables interaction with QEMU instances via the QEMU Machine Proto ### Libvirt -If your environment is managed by Libvirt, QMP interaction must be proxied through the Libvirt daemon. This can be be done through two available drivers: +If your environment is managed by Libvirt, QMP interaction must be proxied through the Libvirt daemon. This can be done through three available drivers: #### RPC @@ -19,6 +19,15 @@ conn, err := net.DialTimeout("tcp", "192.168.1.1:16509", 2*time.Second) monitor := libvirtrpc.New("stage-lb-1", conn) ``` +### Libvirt-go + +This monitor provides communication with the Libvirt daemon using the [libvirt-go](https://github.com/rgbkrk/libvirt-go) package as its internal implementation. +At the moment, libvirt-go only supports `Linux`. + +```go +monitor, err := qmp.NewLibvirtGoMonitor("qemu:///system", "centos7") +``` + #### virsh A connection to the monitor socket is provided by proxing requests through the `virsh` executable. diff --git a/qmp/libvirtgo.go b/qmp/libvirtgo.go new file mode 100644 index 0000000..ba6262b --- /dev/null +++ b/qmp/libvirtgo.go @@ -0,0 +1,23 @@ +// Copyright 2016 The go-qemu Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package qmp + +// LibvirtGoMonitor is a Monitor that wraps the libvirt-go package to +// communicate with a QEMU Machine Protocol (QMP) socket. +// Communication is proxied via the libvirtd daemon. Multiple +// connections to the same hypervisor and domain are permitted. +type LibvirtGoMonitor struct { + Monitor +} diff --git a/qmp/libvirtgo_linux.go b/qmp/libvirtgo_linux.go new file mode 100644 index 0000000..3efa6d1 --- /dev/null +++ b/qmp/libvirtgo_linux.go @@ -0,0 +1,339 @@ +// Copyright 2016 The go-qemu Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package qmp + +import ( + "errors" + "fmt" + "sync" + "time" + + libvirt "github.com/rgbkrk/libvirt-go" +) + +// LibvirtGoMonitorLinux is a Monitor that wraps the libvirt-go package to +// communicate with a QEMU Machine Protocol (QMP) socket. +// Communication is proxied via the libvirtd daemon. Multiple +// connections to the same hypervisor and domain are permitted. +type LibvirtGoMonitorLinux struct { + LibvirtGoMonitor + domainName string + domain *libvirt.VirDomain + uri string + virConn *libvirt.VirConnection + once sync.Once + doneChan chan bool + eventsChan chan Event + callbackID int + eventsLoopInterval time.Duration + // internal implementation + libvirtGoMonitorInternal +} + +var errNoLibvirtConnection = errors.New("need a established connection to proceed") + +const eventsLoopIntervalDefault = 1 * time.Second + +type libvirtGoMonitorInternal interface { + lookupDomainByNameInternal(virConn *libvirt.VirConnection, domainName string) (libvirt.VirDomain, error) + newVirConnectionInternal(uri string) (libvirt.VirConnection, error) + closeConnectionInternal(virConn *libvirt.VirConnection) (int, error) + qemuMonitorCommandInternal(domain *libvirt.VirDomain, cmd string) (string, error) + eventRegisterDefaultImplInternal() int + domainEventRegisterInternal(virConn *libvirt.VirConnection, domain *libvirt.VirDomain, callback *libvirt.DomainEventCallback, fn func()) int + domainEventDeregisterInternal(virConn *libvirt.VirConnection, callbackID int) int + eventRunDefaultImplInternal() int +} + +type libvirtGoMonitorInternalImpl struct { + libvirtGoMonitorInternal +} + +// NewLibvirtGoMonitor configures a connection to the provided hypervisor +// and domain. Defaults events loop interval to 1 second. +// An error is returned if the provided libvirt connection URI is invalid. +// +// Hypervisor URIs may be local or remote, e.g., +// qemu:///system +// qemu+ssh://libvirt@example.com/system +func NewLibvirtGoMonitor(uri, domain string) *LibvirtGoMonitorLinux { + return NewLibvirtGoMonitorEventsLoopInterval(uri, domain, eventsLoopIntervalDefault) +} + +// NewLibvirtGoMonitorEventsLoopInterval configures a connection to the provided hypervisor +// and domain. Sets events loop interval to the Duration provided. +// An error is returned if the provided libvirt connection URI is invalid. +// +// Hypervisor URIs may be local or remote, e.g., +// qemu:///system +// qemu+ssh://libvirt@example.com/system +func NewLibvirtGoMonitorEventsLoopInterval(uri, domain string, eventsLoopInterval time.Duration) *LibvirtGoMonitorLinux { + return &LibvirtGoMonitorLinux{ + uri: uri, + domainName: domain, + libvirtGoMonitorInternal: &libvirtGoMonitorInternalImpl{}, + eventsLoopInterval: eventsLoopIntervalDefault, + } +} + +// Connect sets up QEMU QMP connection via libvirt using +// the libvirt-go package. +// An error is returned if the libvirtd daemon is unreachable. +func (mon *LibvirtGoMonitorLinux) Connect() error { + // Already connected? + if mon.virConn != nil { + return nil + } + + // As per the libvirt core library: + // For proper event handling, it is important + // that the event implementation is registered + // before a connection to the Hypervisor is opened. + err := mon.eventRegisterDefaultImpl() + if err != nil { + return err + } + + virConn, err := mon.newVirConnectionInternal(mon.uri) + if err != nil { + return err + } + + domain, err := mon.lookupDomainByNameInternal(&virConn, mon.domainName) + if err != nil { + mon.closeConnectionInternal(&virConn) + return err + } + + mon.domain = &domain + mon.virConn = &virConn + mon.eventsChan = make(chan Event) + mon.doneChan = make(chan bool) + + return err +} + +// Disconnect tears down open QMP socket connections. +func (mon *LibvirtGoMonitorLinux) Disconnect() error { + var err error + if mon.domain != nil { + mon.domain.Free() + mon.domain = nil + } + + if mon.virConn != nil { + err = domainEventDeregister(mon) + _, closeErr := mon.closeConnectionInternal(mon.virConn) + if closeErr != nil { + // close connection error takes precedence + err = closeErr + } + mon.virConn = nil + close(mon.doneChan) // stop listenning to events + close(mon.eventsChan) // stop sending events to clients + } + + return err +} + +// Run executes the given QAPI command against a domain's QEMU instance. +// For a list of available QAPI commands, see: +// http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD +func (mon *LibvirtGoMonitorLinux) Run(cmd []byte) ([]byte, error) { + if mon.virConn == nil { + return nil, errNoLibvirtConnection + } + + result, err := mon.libvirtGoMonitorInternal.qemuMonitorCommandInternal(mon.domain, string(cmd)) + if err != nil { + return nil, err + } + + return []byte(result), nil +} + +// Events streams QEMU QMP Events. +// If a problem is encountered setting up the event monitor connection +// an error will be returned. Errors encountered during streaming will +// cause the returned event channel to be closed. +func (mon *LibvirtGoMonitorLinux) Events() (<-chan Event, error) { + if mon.virConn == nil { + return nil, errNoLibvirtConnection + } + + callbackID, err := domainEventRegister(mon) + if err != nil { + return nil, err + } + mon.callbackID = callbackID + + go mon.pollEventsLoop(mon.eventsLoopInterval, mon.doneChan) + + return mon.eventsChan, nil +} + +// eventRegisterDefaultImpl registers a default event implementation. +func (mon *LibvirtGoMonitorLinux) eventRegisterDefaultImpl() error { + eventRegisterID := mon.eventRegisterDefaultImplInternal() + if eventRegisterID == -1 { + errMessage := fmt.Sprintf( + "EventRegisterDefaultImpl returned an unexpected value %d\n", + eventRegisterID) + return errors.New(errMessage) + } + + return nil +} + +// domainEventRegister register with libvirt to receive events for the +// specified domain. +func domainEventRegister(mon *LibvirtGoMonitorLinux) (int, error) { + callback := libvirt.DomainEventCallback(newEventCallback(mon)) + + //TODO: add ability to register for more event types + callbackID := mon.domainEventRegisterInternal( + mon.virConn, + mon.domain, + &callback, + func() { + // empty on purpose until it's decided + // what to do here. + }, + ) + if callbackID == -1 { + return -1, errors.New("domain event registration failed") + } + + return callbackID, nil +} + +// domainEventDeregister stops the registration with libvirt from receiving +// events for the specified domain. +func domainEventDeregister(mon *LibvirtGoMonitorLinux) error { + ret := mon.domainEventDeregisterInternal(mon.virConn, mon.callbackID) + if ret != 0 { + errMessage := fmt.Sprintf("DomainEventDeregister returned an unexpected value: %d\n", ret) + return errors.New(errMessage) + } + return nil +} + +// newEventCallback convenient function to provide access +// to the eventChan to the returned closure/callback. +func newEventCallback(mon *LibvirtGoMonitorLinux) libvirt.DomainEventCallback { + return func(c *libvirt.VirConnection, + d *libvirt.VirDomain, + eventDetails interface{}, f func()) int { + + // if monitor is not connected, + // we should not continue processing messages + // as the mon.eventsChan will be closed. + if mon.virConn == nil { + return 0 + } + + // We only support lifecycleEvents for now + if lifecycleEvent, ok := eventDetails.(libvirt.DomainLifecycleEvent); ok { + mon.eventsChan <- constructEvent(lifecycleEvent) + f() + } + + // according to the libvirt-go documentation + // the return code from DomainEventCallback is ignored. + return 0 + } +} + +// pollEventsLoop keeps polling libvirt for new domain events +func (mon *LibvirtGoMonitorLinux) pollEventsLoop(eventsLoopInterval time.Duration, doneChan chan bool) { + checkInterval := time.Tick(eventsLoopInterval) + for { + select { + case <-doneChan: // stop polling for events + return + case <-checkInterval: + ret := mon.eventRunDefaultImplInternal() + if ret == -1 { + doneChan <- true + return + } + } + } +} + +// constructEvent helper function to map DomainLifecycleEvent +// into Event. +func constructEvent(eventDetails libvirt.DomainLifecycleEvent) Event { + // This timestamp represents the moment in time when + // the event was received on this end and not when it + // actually occurred. + now := time.Now() + data := make(map[string]interface{}) + data["details"] = eventDetails + event := Event{ + Event: eventDetails.String(), + Data: data, + } + event.Timestamp.Microseconds = int64(now.Second() / int(time.Microsecond)) + event.Timestamp.Seconds = int64(now.Second()) + return event +} + +/* + Libvirt-go proxy methods + +*/ + +func (mon *libvirtGoMonitorInternalImpl) lookupDomainByNameInternal(virConn *libvirt.VirConnection, domainName string) (libvirt.VirDomain, error) { + return virConn.LookupDomainByName(domainName) +} + +func (mon *libvirtGoMonitorInternalImpl) newVirConnectionInternal(uri string) (libvirt.VirConnection, error) { + return libvirt.NewVirConnection(uri) +} + +func (mon *libvirtGoMonitorInternalImpl) closeConnectionInternal(virConn *libvirt.VirConnection) (int, error) { + return virConn.CloseConnection() +} + +func (mon *libvirtGoMonitorInternalImpl) qemuMonitorCommandInternal(domain *libvirt.VirDomain, cmd string) (string, error) { + return domain.QemuMonitorCommand( + libvirt.VIR_DOMAIN_QEMU_MONITOR_COMMAND_DEFAULT, + cmd) +} + +func (mon *libvirtGoMonitorInternalImpl) eventRegisterDefaultImplInternal() int { + return libvirt.EventRegisterDefaultImpl() +} + +func (mon *libvirtGoMonitorInternalImpl) domainEventRegisterInternal( + virConn *libvirt.VirConnection, + domain *libvirt.VirDomain, + callback *libvirt.DomainEventCallback, fn func()) int { + return virConn.DomainEventRegister( + *domain, + libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, + callback, + fn, + ) +} + +func (mon *libvirtGoMonitorInternalImpl) domainEventDeregisterInternal(virConn *libvirt.VirConnection, callbackID int) int { + return virConn.DomainEventDeregister(callbackID) +} + +func (mon *libvirtGoMonitorInternalImpl) eventRunDefaultImplInternal() int { + return libvirt.EventRunDefaultImpl() +} diff --git a/qmp/libvirtgo_linux_test.go b/qmp/libvirtgo_linux_test.go new file mode 100644 index 0000000..1306024 --- /dev/null +++ b/qmp/libvirtgo_linux_test.go @@ -0,0 +1,202 @@ +// Copyright 2016 The go-qemu Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package qmp + +import ( + "errors" + "sync" + "testing" + + libvirt "github.com/rgbkrk/libvirt-go" +) + +func TestLibvirtGoConnetOK(t *testing.T) { + libvirtGoMonitor := NewLibvirtGoMonitor("testURI", "testDomain") + libvirtGoMonitor.libvirtGoMonitorInternal = &fakeLibvirtGoMonitorInternal{} + err := libvirtGoMonitor.Connect() + if err != nil { + t.Errorf("unexpected error: %v\n", err) + } +} + +func TestLibvirtGoConnectionFailed(t *testing.T) { + libvirtGoMonitor := NewLibvirtGoMonitor("testURI", "testDomain") + libvirtGoMonitor.libvirtGoMonitorInternal = &fakeLibvirtGoMonitorInternal{ + connErr: errors.New("[TEST] server down"), + } + err := libvirtGoMonitor.Connect() + if err == nil { + t.Errorf("connection error expected") + } +} + +func TestLibvirtGoDomainLookupFailed(t *testing.T) { + libvirtGoMonitor := NewLibvirtGoMonitor("testURI", "testDomain") + libvirtGoMonitor.libvirtGoMonitorInternal = &fakeLibvirtGoMonitorInternal{ + domainErr: errors.New("[TEST] domain not found"), + } + err := libvirtGoMonitor.Connect() + if err == nil { + t.Errorf("domain error expected") + } +} + +func TestLibvirtGoRunOK(t *testing.T) { + libvirtGoMonitor := NewLibvirtGoMonitor("testURI", "testDomain") + expectedResult := `{"current":true,"CPU":0,"qom_path":"/machine/unattached/device[0]","pc":33504,"halted":false,"thread_id":25213}],"id":"libvirt-32"}` + libvirtGoMonitor.libvirtGoMonitorInternal = &fakeLibvirtGoMonitorInternal{ + expectedResult: expectedResult, + } + err := libvirtGoMonitor.Connect() + if err != nil { + t.Errorf("unexpected error: %v\n", err) + } + + result, err := libvirtGoMonitor.Run([]byte("{\"execute\" : \"query-cpus\"}")) + if err != nil { + t.Fatalf("unexpected error: %v\n", err) + } + + if expectedResult != string(result) { + t.Fatalf("Unexpected value. Expected %s and got %s\n", expectedResult, string(result)) + } +} + +func TestLibvirtGoRunNoConnection(t *testing.T) { + libvirtGoMonitor := NewLibvirtGoMonitor("testURI", "testDomain") + _, err := libvirtGoMonitor.Run([]byte("{\"execute\" : \"query-cpus\"}")) + if err == nil { + t.Fatalf("no connection error expected") + } +} + +func TestLibvirtGoEventsOK(t *testing.T) { + libvirtGoMonitor := NewLibvirtGoMonitor("testURI", "testDomain") + var wg sync.WaitGroup + expectedEvent := libvirt.DomainLifecycleEvent{ + Event: libvirt.VIR_DOMAIN_EVENT_STARTED, + Detail: 0, + } + libvirtGoMonitor.libvirtGoMonitorInternal = &fakeLibvirtGoMonitorInternal{ + expectedEvent: expectedEvent, + } + + err := libvirtGoMonitor.Connect() + if err != nil { + t.Errorf("unexpected error: %v\n", err) + } + + events, err := libvirtGoMonitor.Events() + if err != nil { + t.Errorf("unexpected error: %v\n", err) + } + var resultEvent Event + wg.Add(1) + go func(wg *sync.WaitGroup) { + for event := range events { + resultEvent = event + wg.Done() + break + } + }(&wg) + + wg.Wait() + + detailsEvent, found := resultEvent.Data["details"] + if !found { + t.Errorf("Expected at least one Event") + } + if expectedEvent != detailsEvent { + t.Errorf("Unexpected event. Expected %#v and got %#v\n", expectedEvent, detailsEvent) + } +} + +func TestLibvirtGoEventsNoConnection(t *testing.T) { + libvirtGoMonitor := NewLibvirtGoMonitor("testURI", "testDomain") + libvirtGoMonitor.libvirtGoMonitorInternal = &fakeLibvirtGoMonitorInternal{} + _, err := libvirtGoMonitor.Events() + if err == nil { + t.Fatalf("no connection error expected") + } +} + +func TestLibvirtGoEventsDomainEventRegisterFailed(t *testing.T) { + libvirtGoMonitor := NewLibvirtGoMonitor("testURI", "testDomain") + libvirtGoMonitor.libvirtGoMonitorInternal = &fakeLibvirtGoMonitorInternal{ + domainEventRetCode: -1, + } + err := libvirtGoMonitor.Connect() + if err != nil { + t.Errorf("unexpected error: %v\n", err) + } + + _, err = libvirtGoMonitor.Events() + if err == nil { + t.Fatalf("domain register error expected") + } +} + +type fakeLibvirtGoMonitorInternal struct { + virnConn libvirt.VirConnection + domain libvirt.VirDomain + connErr error + domainErr error + qemuErr error + closeErr error + retCode int + domainEventRetCode int + expectedResult string + wg *sync.WaitGroup + expectedEvent libvirt.DomainLifecycleEvent +} + +func (fake *fakeLibvirtGoMonitorInternal) newVirConnectionInternal(uri string) (libvirt.VirConnection, error) { + return fake.virnConn, fake.connErr +} + +func (fake *fakeLibvirtGoMonitorInternal) lookupDomainByNameInternal(virConn *libvirt.VirConnection, domainName string) (libvirt.VirDomain, error) { + return fake.domain, fake.domainErr +} + +func (fake *fakeLibvirtGoMonitorInternal) qemuMonitorCommandInternal(domain *libvirt.VirDomain, cmd string) (string, error) { + return fake.expectedResult, fake.qemuErr +} + +func (fake *fakeLibvirtGoMonitorInternal) domainEventRegisterInternal(virConn *libvirt.VirConnection, domain *libvirt.VirDomain, callback *libvirt.DomainEventCallback, fn func()) int { + if fake.domainEventRetCode != -1 { + go fake.simulateSendingEvents(*callback) + } + return fake.domainEventRetCode +} + +func (fake *fakeLibvirtGoMonitorInternal) eventRegisterDefaultImplInternal() int { + return fake.retCode +} + +func (fake *fakeLibvirtGoMonitorInternal) closeConnectionInternal(virConn *libvirt.VirConnection) (int, error) { + return fake.retCode, fake.closeErr +} + +func (fake *fakeLibvirtGoMonitorInternal) domainEventDeregisterInternal(virConn *libvirt.VirConnection, callbackID int) int { + return fake.retCode +} + +func (fake *fakeLibvirtGoMonitorInternal) eventRunDefaultImplInternal() int { + return fake.retCode +} + +func (fake *fakeLibvirtGoMonitorInternal) simulateSendingEvents(callback libvirt.DomainEventCallback) { + callback(nil, nil, fake.expectedEvent, func() {}) +} diff --git a/qmp/libvirtgo_notsupported.go b/qmp/libvirtgo_notsupported.go new file mode 100644 index 0000000..ae83376 --- /dev/null +++ b/qmp/libvirtgo_notsupported.go @@ -0,0 +1,43 @@ +// +build !linux + +// Copyright 2016 The go-qemu Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package qmp + +import "fmt" + +func (mon LibvirtGoMonitor) Connect() error { + return notSupportedError() +} + +func (mon *LibvirtGoMonitor) Disconnect() error { + return notSupportedError() +} + +func (mon LibvirtGoMonitor) Run(cmd []byte) ([]byte, error) { + return nil, notSupportedError() +} + +func (mon *LibvirtGoMonitor) Events() (<-chan Event, error) { + return nil, notSupportedError() +} + +func NewLibvirtGoMonitor(uri, domain string) Monitor { + return &LibvirtGoMonitor{} +} + +func notSupportedError() error { + return fmt.Errorf("libvirt-go is only supported on Linux") +}