github.com/containers/podman/v4@v4.9.4/pkg/machine/applehv/rest.go (about)

     1  //go:build darwin
     2  // +build darwin
     3  
     4  package applehv
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"net/http"
    13  	"time"
    14  
    15  	"github.com/containers/podman/v4/pkg/machine"
    16  	"github.com/crc-org/vfkit/pkg/rest/define"
    17  	"github.com/sirupsen/logrus"
    18  	"golang.org/x/sys/unix"
    19  )
    20  
    21  type Endpoint string
    22  
    23  const (
    24  	inspect = "/vm/inspect"
    25  	state   = "/vm/state"
    26  	version = "/version"
    27  )
    28  
    29  func (vf *VfkitHelper) get(endpoint string, payload io.Reader) (*http.Response, error) {
    30  	client := &http.Client{}
    31  	req, err := http.NewRequest(http.MethodGet, endpoint, payload)
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  	return client.Do(req)
    36  }
    37  
    38  func (vf *VfkitHelper) post(endpoint string, payload io.Reader) (*http.Response, error) {
    39  	client := &http.Client{}
    40  	req, err := http.NewRequest(http.MethodPost, endpoint, payload)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	return client.Do(req)
    45  }
    46  
    47  // getRawState asks vfkit for virtual machine state unmodified (see state())
    48  func (vf *VfkitHelper) getRawState() (machine.Status, error) {
    49  	var response define.VMState
    50  	endPoint := vf.Endpoint + state
    51  	serverResponse, err := vf.get(endPoint, nil)
    52  	if err != nil {
    53  		if errors.Is(err, unix.ECONNREFUSED) {
    54  			logrus.Debugf("connection refused: %s", endPoint)
    55  		}
    56  		return "", err
    57  	}
    58  	err = json.NewDecoder(serverResponse.Body).Decode(&response)
    59  	if err != nil {
    60  		return "", err
    61  	}
    62  	return ToMachineStatus(response.State)
    63  
    64  }
    65  
    66  // state asks vfkit for the virtual machine state. in case the vfkit
    67  // service is not responding, we assume the service is not running
    68  // and return a stopped status
    69  func (vf *VfkitHelper) state() (machine.Status, error) {
    70  	vmState, err := vf.getRawState()
    71  	if err == nil {
    72  		return vmState, err
    73  	}
    74  	if errors.Is(err, unix.ECONNREFUSED) {
    75  		return machine.Stopped, nil
    76  	}
    77  	return "", err
    78  }
    79  
    80  func (vf *VfkitHelper) stateChange(newState define.StateChange) error {
    81  	b, err := json.Marshal(define.VMState{State: string(newState)})
    82  	if err != nil {
    83  		return err
    84  	}
    85  	payload := bytes.NewReader(b)
    86  	_, err = vf.post(vf.Endpoint+state, payload)
    87  	return err
    88  }
    89  
    90  func (vf *VfkitHelper) stop(force, wait bool) error {
    91  	waitDuration := time.Millisecond * 10
    92  	// TODO Add ability to wait until stopped
    93  	if force {
    94  		if err := vf.stateChange(define.HardStop); err != nil {
    95  			return err
    96  		}
    97  	} else {
    98  		if err := vf.stateChange(define.Stop); err != nil {
    99  			return err
   100  		}
   101  	}
   102  	if !wait {
   103  		return nil
   104  	}
   105  	waitErr := fmt.Errorf("failed waiting for vm to stop")
   106  	// Backoff to wait on the machine shutdown
   107  	for i := 0; i < 11; i++ {
   108  		_, err := vf.getRawState()
   109  		if err != nil || errors.Is(err, unix.ECONNREFUSED) {
   110  			waitErr = nil
   111  			break
   112  		}
   113  		waitDuration = waitDuration * 2
   114  		logrus.Debugf("backoff wait time: %s", waitDuration.String())
   115  		time.Sleep(waitDuration)
   116  	}
   117  	return waitErr
   118  }