Files
Paul Holzinger 7562f4ccdb pkg/machine/apple: machine stop timeout
The current timeout was not long enough. Systemd default is 90s so we
should wait for at least that long. Also it really doesn't make sense to
throw an error we saying we failed waiting for stop. We should hard
terminate the VM in case a graceful shutdown did not happen.

Fixes #22515

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2024-06-25 15:54:13 +02:00

123 lines
2.9 KiB
Go

//go:build darwin
package vfkit
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
"time"
"github.com/containers/podman/v5/pkg/machine/define"
"github.com/crc-org/vfkit/pkg/config"
rest "github.com/crc-org/vfkit/pkg/rest/define"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
const (
inspect = "/vm/inspect"
state = "/vm/state"
version = "/version"
)
func (vf *Helper) get(endpoint string, payload io.Reader) (*http.Response, error) {
client := &http.Client{}
req, err := http.NewRequest(http.MethodGet, endpoint, payload)
if err != nil {
return nil, err
}
return client.Do(req)
}
func (vf *Helper) post(endpoint string, payload io.Reader) (*http.Response, error) {
client := &http.Client{}
req, err := http.NewRequest(http.MethodPost, endpoint, payload)
if err != nil {
return nil, err
}
return client.Do(req)
}
// getRawState asks vfkit for virtual machine state unmodified (see state())
func (vf *Helper) getRawState() (define.Status, error) {
var response rest.VMState
endPoint := vf.Endpoint + state
serverResponse, err := vf.get(endPoint, nil)
if err != nil {
if errors.Is(err, unix.ECONNREFUSED) {
logrus.Debugf("connection refused: %s", endPoint)
}
return "", err
}
err = json.NewDecoder(serverResponse.Body).Decode(&response)
if err != nil {
return "", err
}
if err := serverResponse.Body.Close(); err != nil {
logrus.Error(err)
}
return ToMachineStatus(response.State)
}
// state asks vfkit for the virtual machine state. in case the vfkit
// service is not responding, we assume the service is not running
// and return a stopped status
func (vf *Helper) State() (define.Status, error) {
vmState, err := vf.getRawState()
if err == nil {
return vmState, nil
}
if errors.Is(err, unix.ECONNREFUSED) {
return define.Stopped, nil
}
return "", err
}
func (vf *Helper) stateChange(newState rest.StateChange) error {
b, err := json.Marshal(rest.VMState{State: string(newState)})
if err != nil {
return err
}
payload := bytes.NewReader(b)
serverResponse, err := vf.post(vf.Endpoint+state, payload)
_ = serverResponse.Body.Close()
return err
}
func (vf *Helper) Stop(force, wait bool) error {
state := rest.Stop
if force {
state = rest.HardStop
}
if err := vf.stateChange(state); err != nil {
return err
}
if !wait {
return nil
}
waitDuration := time.Millisecond * 500
// Wait up to 90s then hard force off
for i := 0; i < 180; i++ {
_, err := vf.getRawState()
if err != nil || errors.Is(err, unix.ECONNREFUSED) {
return nil
}
time.Sleep(waitDuration)
}
logrus.Warn("Failed to gracefully stop machine, performing hard stop")
// we waited long enough do a hard stop
return vf.stateChange(rest.HardStop)
}
// Helper describes the use of vfkit: cmdline and endpoint
type Helper struct {
LogLevel logrus.Level
Endpoint string
BinaryPath *define.VMFile
VirtualMachine *config.VirtualMachine
Rosetta bool
}