mirror of
https://github.com/cirruslabs/orchard.git
synced 2026-09-30 12:01:15 +02:00
Simplify state reconciliation and support changing Softnet settings (#364)
* Simplify state reconciliation and support changing Softnet settings * Remove unused "updateFunc" parameter from syncOnDiskVMs() * Don't take an address of a loop variable * ensure → ensures * updateVMState(): don't forget to update VMState * Introduce TestSpecUpdateSoftnet integration test * Update OpenAPI specification to include generation/observedGeneration
This commit is contained in:
@@ -142,7 +142,24 @@ func (controller *Controller) initAPI() *gin.Engine {
|
||||
controller.createVM(c).Respond(c)
|
||||
})
|
||||
v1.PUT("/vms/:name", func(c *gin.Context) {
|
||||
controller.updateVM(c).Respond(c)
|
||||
if strings.HasPrefix(c.GetHeader("User-Agent"), "Orchard/0") {
|
||||
// Backward compatibility for older Orchard Workers that still
|
||||
// use the PUT /vms/{name} API endpoint to update a VM status
|
||||
//
|
||||
// Note that we include the "0" here to avoid targeting users
|
||||
// of the github.com/cirruslabs/orchard/pkg/client package. For
|
||||
// them, the UA string should normally be "Orchard/unknown-unknown".
|
||||
//
|
||||
// After some months/years we can remove this workaround and at
|
||||
// the very worst the workers simply won't progress with the VMs
|
||||
// assigned to them. An upgrade to a newer version will fix that.
|
||||
controller.updateVMState(c).Respond(c)
|
||||
} else {
|
||||
controller.updateVMSpec(c).Respond(c)
|
||||
}
|
||||
})
|
||||
v1.PUT("/vms/:name/state", func(c *gin.Context) {
|
||||
controller.updateVMState(c).Respond(c)
|
||||
})
|
||||
v1.GET("/vms/:name", func(c *gin.Context) {
|
||||
controller.getVM(c).Respond(c)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/cirruslabs/orchard/internal/responder"
|
||||
"github.com/cirruslabs/orchard/internal/version"
|
||||
v1pkg "github.com/cirruslabs/orchard/pkg/resource/v1"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (controller *Controller) controllerInfo(ctx *gin.Context) responder.Responder {
|
||||
@@ -17,6 +18,7 @@ func (controller *Controller) controllerInfo(ctx *gin.Context) responder.Respond
|
||||
|
||||
capabilities := []v1pkg.ControllerCapability{
|
||||
v1pkg.ControllerCapabilityRPCV1,
|
||||
v1pkg.ControllerCapabilityVMStateEndpoint,
|
||||
}
|
||||
|
||||
if controller.experimentalRPCV2 {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/cirruslabs/orchard/internal/simplename"
|
||||
"github.com/cirruslabs/orchard/pkg/resource/v1"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/uuid"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
@@ -108,7 +109,7 @@ func (controller *Controller) createVM(ctx *gin.Context) responder.Responder {
|
||||
return response
|
||||
}
|
||||
|
||||
func (controller *Controller) updateVM(ctx *gin.Context) responder.Responder {
|
||||
func (controller *Controller) updateVMSpec(ctx *gin.Context) responder.Responder {
|
||||
if responder := controller.authorize(ctx, v1.ServiceAccountRoleComputeWrite); responder != nil {
|
||||
return responder
|
||||
}
|
||||
@@ -119,12 +120,60 @@ func (controller *Controller) updateVM(ctx *gin.Context) responder.Responder {
|
||||
return responder.JSON(http.StatusBadRequest, NewErrorResponse("invalid JSON was provided"))
|
||||
}
|
||||
|
||||
if userVM.Name == "" {
|
||||
return responder.JSON(http.StatusPreconditionFailed, NewErrorResponse("VM name is empty"))
|
||||
}
|
||||
name := ctx.Param("name")
|
||||
|
||||
return controller.storeUpdate(func(txn storepkg.Transaction) responder.Responder {
|
||||
dbVM, err := txn.GetVM(userVM.Name)
|
||||
dbVM, err := txn.GetVM(name)
|
||||
if err != nil {
|
||||
return responder.Error(err)
|
||||
}
|
||||
|
||||
if dbVM.TerminalState() {
|
||||
return responder.JSON(http.StatusPreconditionFailed,
|
||||
NewErrorResponse("cannot update VM in a terminal state"))
|
||||
}
|
||||
|
||||
// Softnet-specific logic: automatically enable Softnet when NetSoftnetAllow or NetSoftnetBlock are set
|
||||
// and propagate deprecated and non-deprecated boolean fields into each other
|
||||
if userVM.NetSoftnetDeprecated || userVM.NetSoftnet || len(userVM.NetSoftnetAllow) != 0 || len(userVM.NetSoftnetBlock) != 0 {
|
||||
userVM.NetSoftnetDeprecated = true
|
||||
userVM.NetSoftnet = true
|
||||
}
|
||||
|
||||
if cmp.Equal(dbVM.VMSpec, userVM.VMSpec) {
|
||||
// Nothing was changed
|
||||
return responder.JSON(http.StatusOK, dbVM)
|
||||
}
|
||||
|
||||
// VM specification was changed
|
||||
dbVM.VMSpec = userVM.VMSpec
|
||||
dbVM.Generation++
|
||||
|
||||
if err := txn.SetVM(*dbVM); err != nil {
|
||||
controller.logger.Errorf("failed to update VM in the DB: %v", err)
|
||||
|
||||
return responder.Code(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return responder.JSON(http.StatusOK, dbVM)
|
||||
})
|
||||
}
|
||||
|
||||
func (controller *Controller) updateVMState(ctx *gin.Context) responder.Responder {
|
||||
if responder := controller.authorize(ctx, v1.ServiceAccountRoleComputeWrite); responder != nil {
|
||||
return responder
|
||||
}
|
||||
|
||||
var userVM v1.VM
|
||||
|
||||
if err := ctx.ShouldBindJSON(&userVM); err != nil {
|
||||
return responder.JSON(http.StatusBadRequest, NewErrorResponse("invalid JSON was provided"))
|
||||
}
|
||||
|
||||
name := ctx.Param("name")
|
||||
|
||||
return controller.storeUpdate(func(txn storepkg.Transaction) responder.Responder {
|
||||
dbVM, err := txn.GetVM(name)
|
||||
if err != nil {
|
||||
return responder.Error(err)
|
||||
}
|
||||
@@ -141,6 +190,7 @@ func (controller *Controller) updateVM(ctx *gin.Context) responder.Responder {
|
||||
dbVM.Status = userVM.Status
|
||||
dbVM.StatusMessage = userVM.StatusMessage
|
||||
dbVM.ImageFQN = userVM.ImageFQN
|
||||
dbVM.VMState = userVM.VMState
|
||||
|
||||
if err := txn.SetVM(*dbVM); err != nil {
|
||||
controller.logger.Errorf("failed to update VM in the DB: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user