garm/vendor/github.com/lxc/lxd/client/operations.go
Gabriel Adrian Samfira bbbe67bf7c Vendor packages and add Makefile
* Vendors packages
  * Adds a Makefile that uses docker to build a static binary against musl
using alpine linux.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2022-06-30 10:20:32 +00:00

325 lines
6.6 KiB
Go

package lxd
import (
"encoding/json"
"errors"
"fmt"
"sync"
"github.com/gorilla/websocket"
"github.com/lxc/lxd/shared/api"
)
// The Operation type represents an ongoing LXD operation (asynchronous processing)
type operation struct {
api.Operation
r *ProtocolLXD
listener *EventListener
handlerReady bool
handlerLock sync.Mutex
chActive chan bool
}
// AddHandler adds a function to be called whenever an event is received
func (op *operation) AddHandler(function func(api.Operation)) (*EventTarget, error) {
// Make sure we have a listener setup
err := op.setupListener()
if err != nil {
return nil, err
}
// Make sure we're not racing with ourselves
op.handlerLock.Lock()
defer op.handlerLock.Unlock()
// If we're done already, just return
if op.StatusCode.IsFinal() {
return nil, nil
}
// Wrap the function to filter unwanted messages
wrapped := func(event api.Event) {
op.handlerLock.Lock()
newOp := api.Operation{}
err := json.Unmarshal(event.Metadata, &newOp)
if err != nil || newOp.ID != op.ID {
op.handlerLock.Unlock()
return
}
op.handlerLock.Unlock()
function(newOp)
}
return op.listener.AddHandler([]string{"operation"}, wrapped)
}
// Cancel will request that LXD cancels the operation (if supported)
func (op *operation) Cancel() error {
return op.r.DeleteOperation(op.ID)
}
// Get returns the API operation struct
func (op *operation) Get() api.Operation {
return op.Operation
}
// GetWebsocket returns a raw websocket connection from the operation
func (op *operation) GetWebsocket(secret string) (*websocket.Conn, error) {
return op.r.GetOperationWebsocket(op.ID, secret)
}
// RemoveHandler removes a function to be called whenever an event is received
func (op *operation) RemoveHandler(target *EventTarget) error {
// Make sure we're not racing with ourselves
op.handlerLock.Lock()
defer op.handlerLock.Unlock()
// If the listener is gone, just return
if op.listener == nil {
return nil
}
return op.listener.RemoveHandler(target)
}
// Refresh pulls the current version of the operation and updates the struct
func (op *operation) Refresh() error {
// Get the current version of the operation
newOp, _, err := op.r.GetOperation(op.ID)
if err != nil {
return err
}
// Update the operation struct
op.Operation = *newOp
return nil
}
// Wait lets you wait until the operation reaches a final state
func (op *operation) Wait() error {
op.handlerLock.Lock()
// Check if not done already
if op.StatusCode.IsFinal() {
if op.Err != "" {
op.handlerLock.Unlock()
return errors.New(op.Err)
}
op.handlerLock.Unlock()
return nil
}
op.handlerLock.Unlock()
// Make sure we have a listener setup
err := op.setupListener()
if err != nil {
return err
}
<-op.chActive
// We're done, parse the result
if op.Err != "" {
return errors.New(op.Err)
}
return nil
}
func (op *operation) setupListener() error {
// Make sure we're not racing with ourselves
op.handlerLock.Lock()
defer op.handlerLock.Unlock()
// We already have a listener setup
if op.handlerReady {
return nil
}
op.handlerReady = true
// Get a new listener
if op.listener == nil {
listener, err := op.r.GetEvents()
if err != nil {
return err
}
op.listener = listener
}
// Setup the handler
chReady := make(chan bool)
_, err := op.listener.AddHandler([]string{"operation"}, func(event api.Event) {
<-chReady
// We don't want concurrency while processing events
op.handlerLock.Lock()
defer op.handlerLock.Unlock()
// Check if we're done already (because of another event)
if op.listener == nil {
return
}
// Get an operation struct out of this data
newOp := api.Operation{}
err := json.Unmarshal(event.Metadata, &newOp)
if err != nil || newOp.ID != op.ID {
return
}
// Update the struct
op.Operation = newOp
// And check if we're done
if op.StatusCode.IsFinal() {
op.listener.Disconnect()
op.listener = nil
close(op.chActive)
return
}
})
if err != nil {
op.listener.Disconnect()
op.listener = nil
close(op.chActive)
close(chReady)
return err
}
// Monitor event listener
go func() {
<-chReady
// We don't want concurrency while accessing the listener
op.handlerLock.Lock()
// Check if we're done already (because of another event)
listener := op.listener
if listener == nil {
op.handlerLock.Unlock()
return
}
op.handlerLock.Unlock()
// Wait for the listener or operation to be done
select {
case <-listener.ctx.Done():
op.handlerLock.Lock()
if op.listener != nil {
op.Err = fmt.Sprintf("%v", listener.err)
close(op.chActive)
}
op.handlerLock.Unlock()
case <-op.chActive:
return
}
}()
// And do a manual refresh to avoid races
err = op.Refresh()
if err != nil {
op.listener.Disconnect()
op.listener = nil
close(op.chActive)
close(chReady)
return err
}
// Check if not done already
if op.StatusCode.IsFinal() {
op.listener.Disconnect()
op.listener = nil
close(op.chActive)
close(chReady)
if op.Err != "" {
return errors.New(op.Err)
}
return nil
}
// Start processing background updates
close(chReady)
return nil
}
// The remoteOperation type represents an ongoing LXD operation between two servers
type remoteOperation struct {
targetOp Operation
handlers []func(api.Operation)
handlerLock sync.Mutex
chDone chan bool
chPost chan bool
err error
}
// AddHandler adds a function to be called whenever an event is received
func (op *remoteOperation) AddHandler(function func(api.Operation)) (*EventTarget, error) {
var err error
var target *EventTarget
op.handlerLock.Lock()
defer op.handlerLock.Unlock()
// Attach to the existing target operation
if op.targetOp != nil {
target, err = op.targetOp.AddHandler(function)
if err != nil {
return nil, err
}
} else {
// Generate a mock EventTarget
target = &EventTarget{
function: func(api.Event) { function(api.Operation{}) },
types: []string{"operation"},
}
}
// Add the handler to our list
op.handlers = append(op.handlers, function)
return target, nil
}
// CancelTarget attempts to cancel the target operation
func (op *remoteOperation) CancelTarget() error {
if op.targetOp == nil {
return fmt.Errorf("No associated target operation")
}
return op.targetOp.Cancel()
}
// GetTarget returns the target operation
func (op *remoteOperation) GetTarget() (*api.Operation, error) {
if op.targetOp == nil {
return nil, fmt.Errorf("No associated target operation")
}
opAPI := op.targetOp.Get()
return &opAPI, nil
}
// Wait lets you wait until the operation reaches a final state
func (op *remoteOperation) Wait() error {
<-op.chDone
if op.chPost != nil {
<-op.chPost
}
return op.err
}