Implement restart policy for VMs (#83)

* Implement restart policy for VMs

* Do not update VM.Resource, we only use it as a read-only specification

* Err()/setErr(): use atomic.Pointer instead of sync.Mutex
This commit is contained in:
Nikolay Edigaryev
2023-04-24 19:30:08 +04:00
committed by GitHub
parent 010df300a3
commit 60e564da88
22 changed files with 482 additions and 382 deletions
+24 -2
View File
@@ -173,8 +173,9 @@ func (client *Client) request(
}()
if response.StatusCode != http.StatusOK {
return fmt.Errorf("%w to make a request: %d %s",
ErrFailed, response.StatusCode, http.StatusText(response.StatusCode))
return fmt.Errorf("%w to make a request: %d %s%s",
ErrFailed, response.StatusCode, http.StatusText(response.StatusCode),
detailsFromErrorResponseBody(response.Body))
}
if out != nil {
@@ -191,6 +192,27 @@ func (client *Client) request(
return nil
}
func detailsFromErrorResponseBody(body io.Reader) string {
bodyBytes, err := io.ReadAll(body)
if err != nil {
return ""
}
var errorResponse struct {
Message string `json:"message"`
}
if err := json.Unmarshal(bodyBytes, &errorResponse); err != nil {
return ""
}
if errorResponse.Message != "" {
return fmt.Sprintf(" (%s)", errorResponse.Message)
}
return ""
}
func (client *Client) wsRequest(
_ context.Context,
path string,
+4 -18
View File
@@ -23,23 +23,23 @@ func (service *VMsService) Create(ctx context.Context, vm *v1.VM) error {
return nil
}
func (service *VMsService) FindForWorker(ctx context.Context, worker string) (map[string]v1.VM, error) {
func (service *VMsService) FindForWorker(ctx context.Context, worker string) ([]v1.VM, error) {
allVms, err := service.List(ctx)
if err != nil {
return nil, err
}
var filteredVms = make(map[string]v1.VM)
var result []v1.VM
for _, vmResource := range allVms {
if vmResource.Worker != worker {
continue
}
filteredVms[vmResource.UID] = vmResource
result = append(result, vmResource)
}
return filteredVms, nil
return result, nil
}
func (service *VMsService) List(ctx context.Context) ([]v1.VM, error) {
@@ -66,20 +66,6 @@ func (service *VMsService) Get(ctx context.Context, name string) (*v1.VM, error)
return &vm, nil
}
func (service *VMsService) Stop(ctx context.Context, name string) (*v1.VM, error) {
var vm v1.VM
err := service.client.request(ctx, http.MethodGet, fmt.Sprintf("vms/%s", name),
nil, &vm, nil)
if err != nil {
return nil, err
}
vm.Status = v1.VMStatusStopping
return service.Update(ctx, vm)
}
func (service *VMsService) Update(ctx context.Context, vm v1.VM) (*v1.VM, error) {
var updatedVM v1.VM
err := service.client.request(ctx, http.MethodPut, fmt.Sprintf("vms/%s", vm.Name),
+26
View File
@@ -0,0 +1,26 @@
package v1
import (
"errors"
"fmt"
)
var ErrInvalidRestartPolicy = errors.New("invalid restart policy")
type RestartPolicy string
const (
RestartPolicyNever RestartPolicy = "Never"
RestartPolicyOnFailure RestartPolicy = "OnFailure"
)
func NewRestartPolicyFromString(s string) (RestartPolicy, error) {
switch s {
case string(RestartPolicyNever):
return RestartPolicyNever, nil
case string(RestartPolicyOnFailure):
return RestartPolicyOnFailure, nil
default:
return "", fmt.Errorf("%w %q", ErrInvalidRestartPolicy, s)
}
}
+26
View File
@@ -0,0 +1,26 @@
package v1_test
import (
v1 "github.com/cirruslabs/orchard/pkg/resource/v1"
"github.com/stretchr/testify/assert"
"testing"
)
func TestNewRestartPolicyFromString(t *testing.T) {
_, err := v1.NewRestartPolicyFromString("")
assert.Error(t, err, "empty restart policy should yield an error")
_, err = v1.NewRestartPolicyFromString("non-existent")
assert.Error(t, err, "non-existent restart policy should yield an error")
_, err = v1.NewRestartPolicyFromString("never")
assert.Error(t, err, "improperly capitalized but existent policy should yield an error")
restartPolicy, err := v1.NewRestartPolicyFromString("Never")
assert.NoError(t, err, "Never policy should be parsed correctly")
assert.Equal(t, v1.RestartPolicyNever, restartPolicy)
restartPolicy, err = v1.NewRestartPolicyFromString("OnFailure")
assert.NoError(t, err, "OnFailure policy should be parsed correctly")
assert.Equal(t, v1.RestartPolicyOnFailure, restartPolicy)
}
+8 -12
View File
@@ -33,10 +33,13 @@ type VM struct {
// Worker field is set by the Controller to assign this VM to a specific Worker.
Worker string `json:"worker"`
Username string `json:"username"`
Password string `json:"password"`
StartupScript *VMScript `json:"startup_script"`
ShutdownScript *VMScript `json:"shutdown_script"`
Username string `json:"username"`
Password string `json:"password"`
StartupScript *VMScript `json:"startup_script"`
RestartPolicy RestartPolicy `json:"restart_policy"`
RestartedAt time.Time `json:"restarted_at"`
RestartCount uint64 `json:"restart_count"`
// UID is a useful field for avoiding data races within a single Name.
//
@@ -67,7 +70,7 @@ type VMScript struct {
}
func (vm VM) TerminalState() bool {
return vm.Status == VMStatusStopped || vm.Status == VMStatusFailed
return vm.Status == VMStatusFailed
}
type VMStatus string
@@ -83,13 +86,6 @@ const (
// VMStatusFailed is set by both the Controller and the Worker to indicate a failure
// that prevented the VM resource from reaching the VMStatusRunning state.
VMStatusFailed VMStatus = "failed"
// VMStatusStopping is set by the Controller to indicate that a VM resource needs to be stopped but not deleted.
VMStatusStopping VMStatus = "stopping"
// VMStatusStopped is set by both the Worker to indicate that a particular VM resource has been stopped successfully
// (either via API or from within a VM via `sudo shutdown -now`).
VMStatusStopped VMStatus = "stopped"
)
type ControllerInfo struct {