diff --git a/frontend/src/host_orchestrator/api/v1/messages.go b/frontend/src/host_orchestrator/api/v1/messages.go index d8e8b60dd7..1aebc6d983 100644 --- a/frontend/src/host_orchestrator/api/v1/messages.go +++ b/frontend/src/host_orchestrator/api/v1/messages.go @@ -141,3 +141,8 @@ type CreateSnapshotResponse struct { type EmptyResponse struct{} type StopCVDResponse = EmptyResponse + +type StartCVDRequest struct { + // Start from the relevant snaphost if not empty. + SnapshotID string `json:"snapshot_id,omitempty"` +} diff --git a/frontend/src/host_orchestrator/orchestrator/controller.go b/frontend/src/host_orchestrator/orchestrator/controller.go index 94d96751e1..1e8252cb1a 100644 --- a/frontend/src/host_orchestrator/orchestrator/controller.go +++ b/frontend/src/host_orchestrator/orchestrator/controller.go @@ -71,6 +71,8 @@ func (c *Controller) AddRoutes(router *mux.Router) { httpHandler(newCreateCVDBugReportHandler(c.Config, c.OperationManager))).Methods("POST") router.Handle("/cvds/{group}/{name}", httpHandler(newExecCVDCommandHandler(c.Config, c.OperationManager, "remove"))).Methods("DELETE") + router.Handle("/cvds/{group}/{name}/:start", + httpHandler(newStartCVDHandler(c.Config, c.OperationManager))).Methods("POST") router.Handle("/cvds/{group}/{name}/:stop", httpHandler(newExecCVDCommandHandler(c.Config, c.OperationManager, "stop"))).Methods("POST") router.Handle("/cvds/{group}/{name}/:powerwash", @@ -295,6 +297,35 @@ func (h *createSnapshotHandler) Handle(r *http.Request) (interface{}, error) { return NewCreateSnapshotAction(opts).Run() } +type startCVDHandler struct { + Config Config + OM OperationManager +} + +func newStartCVDHandler(c Config, om OperationManager) *startCVDHandler { + return &startCVDHandler{Config: c, OM: om} +} + +func (h *startCVDHandler) Handle(r *http.Request) (interface{}, error) { + req := &apiv1.StartCVDRequest{} + err := json.NewDecoder(r.Body).Decode(req) + if err != nil { + return nil, operator.NewBadRequestError("Malformed JSON in request", err) + } + vars := mux.Vars(r) + group := vars["group"] + name := vars["name"] + opts := StartCVDActionOpts{ + Request: req, + Selector: CVDSelector{Group: group, Name: name}, + Paths: h.Config.Paths, + OperationManager: h.OM, + ExecContext: exec.CommandContext, + CVDUser: h.Config.CVDUser, + } + return NewStartCVDAction(opts).Run() +} + type getCVDLogsHandler struct { Config Config } diff --git a/frontend/src/host_orchestrator/orchestrator/startcvdaction.go b/frontend/src/host_orchestrator/orchestrator/startcvdaction.go new file mode 100644 index 0000000000..75053efdfb --- /dev/null +++ b/frontend/src/host_orchestrator/orchestrator/startcvdaction.go @@ -0,0 +1,78 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package orchestrator + +import ( + "log" + "os/user" + "path/filepath" + + apiv1 "github.com/google/android-cuttlefish/frontend/src/host_orchestrator/api/v1" + "github.com/google/android-cuttlefish/frontend/src/host_orchestrator/orchestrator/cvd" + "github.com/google/android-cuttlefish/frontend/src/liboperator/operator" +) + +type StartCVDActionOpts struct { + Request *apiv1.StartCVDRequest + Selector CVDSelector + Paths IMPaths + OperationManager OperationManager + ExecContext ExecContext + CVDUser *user.User +} + +type StartCVDAction struct { + req *apiv1.StartCVDRequest + selector CVDSelector + paths IMPaths + om OperationManager + execContext cvd.CVDExecContext +} + +func NewStartCVDAction(opts StartCVDActionOpts) *StartCVDAction { + return &StartCVDAction{ + req: opts.Request, + selector: opts.Selector, + paths: opts.Paths, + om: opts.OperationManager, + execContext: newCVDExecContext(opts.ExecContext, opts.CVDUser), + } +} + +func (a *StartCVDAction) Run() (apiv1.Operation, error) { + op := a.om.New() + go func(op apiv1.Operation) { + result := &OperationResult{} + result.Value, result.Error = a.exec(op) + if err := a.om.Complete(op.Name, result); err != nil { + log.Printf("error completing operation %q: %v\n", op.Name, err) + } + }(op) + return op, nil +} + +func (a *StartCVDAction) exec(op apiv1.Operation) (*apiv1.EmptyResponse, error) { + args := a.selector.ToCVDCLI() + args = append(args, "start") + if a.req.SnapshotID != "" { + dir := filepath.Join(a.paths.SnapshotsRootDir, a.req.SnapshotID) + args = append(args, "--snapshot_path", dir) + } + cmd := cvd.NewCommand(a.execContext, args, cvd.CommandOpts{}) + if err := cmd.Run(); err != nil { + return nil, operator.NewInternalError("cvd start failed", err) + } + return &apiv1.EmptyResponse{}, nil +}