The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/8241
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === Fixes #7934
From 27026e3042e5028c2f45d684ca79883cb2497898 Mon Sep 17 00:00:00 2001 From: Kevin Turner <kevintur...@utexas.edu> Date: Thu, 10 Dec 2020 22:21:34 -0600 Subject: [PATCH 1/6] client: Adds support for bulk instance state change. Signed-off-by: Kevin Turner <kevintur...@utexas.edu> --- client/interfaces.go | 1 + client/lxd_instances.go | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/client/interfaces.go b/client/interfaces.go index 85a84a73ee..d50d2532d4 100644 --- a/client/interfaces.go +++ b/client/interfaces.go @@ -87,6 +87,7 @@ type InstanceServer interface { // Container functions GetContainerNames() (names []string, err error) GetContainers() (containers []api.Container, err error) + PutInstances(state api.InstancesPut, ETag string) (Operation, error) GetContainersFull() (containers []api.ContainerFull, err error) GetContainer(name string) (container *api.Container, ETag string, err error) CreateContainer(container api.ContainersPost) (op Operation, err error) diff --git a/client/lxd_instances.go b/client/lxd_instances.go index 0195d7504d..9fef38fc66 100644 --- a/client/lxd_instances.go +++ b/client/lxd_instances.go @@ -91,6 +91,22 @@ func (r *ProtocolLXD) GetInstances(instanceType api.InstanceType) ([]api.Instanc return instances, nil } +// PutInstances +func (r *ProtocolLXD) PutInstances(state api.InstancesPut, ETag string) (Operation, error) { + path, v, err := r.instanceTypeToPath(api.InstanceTypeAny) + if err != nil { + return nil, err + } + + // Send the request + op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s?%s", path, v.Encode()), state, ETag) + if err != nil { + return nil, err + } + + return op, nil +} + // GetInstancesFull returns a list of instances including snapshots, backups and state. func (r *ProtocolLXD) GetInstancesFull(instanceType api.InstanceType) ([]api.InstanceFull, error) { instances := []api.InstanceFull{} From 5264907d11ee846ba840569ec7ce26b1d75a3503 Mon Sep 17 00:00:00 2001 From: Kevin Turner <kevintur...@utexas.edu> Date: Thu, 10 Dec 2020 22:23:56 -0600 Subject: [PATCH 2/6] lxc: Adds support for bulk instance state change. Signed-off-by: Kevin Turner <kevintur...@utexas.edu> --- lxc/action.go | 138 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 88 insertions(+), 50 deletions(-) diff --git a/lxc/action.go b/lxc/action.go index 292a82372a..d589ef4b6e 100644 --- a/lxc/action.go +++ b/lxc/action.go @@ -130,6 +130,65 @@ func (c *cmdAction) Command(action string) *cobra.Command { return cmd } +func (c *cmdAction) doActionAll(action string, resources []remoteResource) error { + for _, resource := range resources { + if resource.name != "" { + // both --all and instance name given + return fmt.Errorf(i18n.G("Both --all and instance name given")) + } + + remote := resource.remote + d, err := c.global.conf.GetInstanceServer(remote) + if err != nil { + return err + } + + state := false + + // Pause is called freeze + if action == "pause" { + action = "freeze" + } + + // Only store state if asked to + if action == "stop" && c.flagStateful { + state = true + } + + req := api.InstancesPut{ + Action: action, + Timeout: c.flagTimeout, + Force: c.flagForce, + Stateful: state, + } + + op, err := d.PutInstances(req, "") + if err != nil { + return err + } + + progress := utils.ProgressRenderer { + Quiet: c.global.flagQuiet, + } + + _, err = op.AddHandler(progress.UpdateOp) + if err != nil { + progress.Done("") + return err + } + + err = utils.CancelableWait(op, &progress) + if err != nil { + progress.Done("") + return err + } + + progress.Done("") + } + + return nil +} + func (c *cmdAction) doAction(action string, conf *config.Config, nameArg string) error { state := false @@ -231,30 +290,13 @@ func (c *cmdAction) Run(cmd *cobra.Command, args []string) error { return err } - for _, resource := range resources { - // We don't allow instance names with --all. - if resource.name != "" { - return fmt.Errorf(i18n.G("Both --all and instance name given")) - } - - ctslist, err := resource.server.GetInstances(api.InstanceTypeAny) - if err != nil { - return err - } + if c.flagConsole != "" { + return fmt.Errorf(i18n.G("--console can't be used with --all")) + } - for _, ct := range ctslist { - switch cmd.Name() { - case "start": - if ct.StatusCode == api.Running { - continue - } - case "stop": - if ct.StatusCode == api.Stopped { - continue - } - } - names = append(names, fmt.Sprintf("%s:%s", resource.remote, ct.Name)) - } + err = c.doActionAll(cmd.Name(), resources) + if err != nil { + return err } } else { names = args @@ -263,44 +305,40 @@ func (c *cmdAction) Run(cmd *cobra.Command, args []string) error { cmd.Usage() return nil } - } - if c.flagConsole != "" { - if c.flagAll { - return fmt.Errorf(i18n.G("--console can't be used with --all")) - } - - if len(names) != 1 { + if c.flagConsole != "" && len(names) != 1 { return fmt.Errorf(i18n.G("--console only works with a single instance")) } - } - // Run the action for every listed instance - results := runBatch(names, func(name string) error { return c.doAction(cmd.Name(), conf, name) }) - // Single instance is easy - if len(results) == 1 { - return results[0].err - } + // Run the action for every listed instance + results := runBatch(names, func(name string) error { return c.doAction(cmd.Name(), conf, name) }) - // Do fancier rendering for batches - success := true - for _, result := range results { - if result.err == nil { - continue + // Single instance is easy + if len(results) == 1 { + return results[0].err } - success = false - msg := fmt.Sprintf(i18n.G("error: %v"), result.err) - for _, line := range strings.Split(msg, "\n") { - fmt.Fprintln(os.Stderr, fmt.Sprintf("%s: %s", result.name, line)) + // Do fancier rendering for batches + success := true + + for _, result := range results { + if result.err == nil { + continue + } + + success = false + msg := fmt.Sprintf(i18n.G("error: %v"), result.err) + for _, line := range strings.Split(msg, "\n") { + fmt.Fprintln(os.Stderr, fmt.Sprintf("%s: %s", result.name, line)) + } } - } - if !success { - fmt.Fprintln(os.Stderr, "") - return fmt.Errorf(i18n.G("Some instances failed to %s"), cmd.Name()) + if !success { + fmt.Fprintln(os.Stderr, "") + return fmt.Errorf(i18n.G("Some instances failed to %s"), cmd.Name()) + } } return nil From 0aba934af053ad2b74c5b43c77f2d7c067615b2b Mon Sep 17 00:00:00 2001 From: Kevin Turner <kevintur...@utexas.edu> Date: Thu, 10 Dec 2020 22:29:14 -0600 Subject: [PATCH 3/6] lxd: Adds support for bulk instance state change. Signed-off-by: Kevin Turner <kevintur...@utexas.edu> --- lxd/instance_put.go | 1 + lxd/instances.go | 1 + lxd/instances_put.go | 207 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 lxd/instances_put.go diff --git a/lxd/instance_put.go b/lxd/instance_put.go index 5390756615..25f781a173 100644 --- a/lxd/instance_put.go +++ b/lxd/instance_put.go @@ -18,6 +18,7 @@ import ( "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/osarch" + ) /* diff --git a/lxd/instances.go b/lxd/instances.go index 9bb2272725..de2f70284c 100644 --- a/lxd/instances.go +++ b/lxd/instances.go @@ -27,6 +27,7 @@ var instancesCmd = APIEndpoint{ Get: APIEndpointAction{Handler: containersGet, AccessHandler: allowProjectPermission("containers", "view")}, Post: APIEndpointAction{Handler: containersPost, AccessHandler: allowProjectPermission("containers", "manage-containers")}, + Put: APIEndpointAction{Handler: containersPut, AccessHandler: allowProjectPermission("containers", "manage-containers")}, } var instanceCmd = APIEndpoint{ diff --git a/lxd/instances_put.go b/lxd/instances_put.go new file mode 100644 index 0000000000..86cd9369eb --- /dev/null +++ b/lxd/instances_put.go @@ -0,0 +1,207 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/instance" + "github.com/lxc/lxd/lxd/operations" + "github.com/lxc/lxd/lxd/response" + "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" + "github.com/lxc/lxd/lxd/cgroup" +) + + +/* +* Update state in bulk +*/ +func containersPut(d *Daemon, r *http.Request) response.Response { + project := projectParam(r) + + c, err := instance.LoadByProject(d.State(), project) + if err != nil { + return response.BadRequest(err) + } + + raw := api.InstancesPut{} + raw.Timeout = -1 + if err := json.NewDecoder(r.Body).Decode(&raw); err != nil { + return response.BadRequest(err) + } + + action := shared.InstanceAction(raw.Action) + + var names[] string + var instances[] instance.Instance + for _, inst := range c { + switch action { + case shared.Start: + if inst.IsRunning() { + continue + } + case shared.Stop: + if !inst.IsRunning() { + continue + } + } + + instances = append(instances, inst) + names = append(names, inst.Name()) + } + + // Don't mess with containers while in setup mode + <-d.readyChan + + var opType db.OperationType + var do func(*operations.Operation) error + switch action { + case shared.Start: + opType = db.OperationContainerStart + do = func(op *operations.Operation) error { + for _, inst := range instances { + inst.SetOperation(op) + if err = inst.Start(raw.Stateful); err != nil { + return err + } + } + return nil + } + case shared.Stop: + opType = db.OperationContainerStop + if raw.Stateful { + do = func(op *operations.Operation) error { + for _, inst := range instances { + inst.SetOperation(op) + err := inst.Stop(raw.Stateful) + if err != nil { + return err + } + } + + return nil + } + } else if raw.Timeout == 0 || raw.Force { + do = func(op *operations.Operation) error { + for _, inst := range instances { + inst.SetOperation(op) + err = inst.Stop(false) + if err != nil { + return err + } + } + + return nil + } + } else { + do = func(op *operations.Operation) error { + for _, inst := range instances { + inst.SetOperation(op) + if inst.IsFrozen() { + err := inst.Unfreeze() + if err != nil { + return err + } + } + + err = inst.Shutdown(time.Duration(raw.Timeout) * time.Second) + if err != nil { + return err + } + } + + return nil + } + } + case shared.Restart: + opType = db.OperationContainerRestart + do = func(op *operations.Operation) error { + for _, inst := range instances { + inst.SetOperation(op) + ephemeral := inst.IsEphemeral() + + if ephemeral { + // Unset ephemeral flag + args := db.InstanceArgs{ + Architecture: inst.Architecture(), + Config: inst.LocalConfig(), + Description: inst.Description(), + Devices: inst.LocalDevices(), + Ephemeral: false, + Profiles: inst.Profiles(), + Project: inst.Project(), + Type: inst.Type(), + Snapshot: inst.IsSnapshot(), + } + + err := inst.Update(args, false) + if err != nil { + return err + } + + // On function return, set the flag back on + defer func() { + args.Ephemeral = ephemeral + inst.Update(args, false) + }() + } + + timeout := raw.Timeout + if raw.Force { + timeout = 0 + } + + res := inst.Restart(time.Duration(timeout)) + if res != nil { + return res + } + + } + + return nil + } + case shared.Freeze: + if !d.os.CGInfo.Supports(cgroup.Freezer, nil) { + return response.BadRequest(fmt.Errorf("This system doesn't support freezing instances")) + } + + opType = db.OperationContainerFreeze + do = func(op *operations.Operation) error { + for _, inst := range instances { + inst.SetOperation(op) + return inst.Freeze() + } + + return nil + } + case shared.Unfreeze: + if !d.os.CGInfo.Supports(cgroup.Freezer, nil) { + return response.BadRequest(fmt.Errorf("This system doesn't support unfreezing instances")) + } + + opType = db.OperationContainerUnfreeze + do = func(op *operations.Operation) error { + for _, inst := range instances { + inst.SetOperation(op) + return inst.Unfreeze() + } + + return nil + } + default: + return response.BadRequest(fmt.Errorf("unknown action %s", raw.Action)) + } + + resources := map[string][]string{} + resources["containers"] = names + + op, err := operations.OperationCreate(d.State(), project, operations.OperationClassTask, opType, resources, nil, do, nil, nil) + if err != nil { + return response.InternalError(err) + } + + return operations.OperationResponse(op) +} \ No newline at end of file From f87c511a1365e68307b330c118f1a7350186e376 Mon Sep 17 00:00:00 2001 From: Kevin Turner <kevintur...@utexas.edu> Date: Thu, 10 Dec 2020 22:33:40 -0600 Subject: [PATCH 4/6] api: Adds support for bulk instance state change. Signed-off-by: Kevin Turner <kevintur...@utexas.edu> --- doc/api-extensions.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/api-extensions.md b/doc/api-extensions.md index bdc2cab260..ac39862554 100644 --- a/doc/api-extensions.md +++ b/doc/api-extensions.md @@ -1257,3 +1257,8 @@ with `ovn.ingress_mode=routed`. ## projects\_limits\_instances Adds `limits.instances` to the available project configuration keys. If set, it limits the total number of instances (VMs and containers) that can be used in the project. + +## instances +Adds the following endpoint for bulk state change (see [RESTful API](rest-api.md) for details): + +* `PUT /1.0/instances` \ No newline at end of file From 05c1fbade25be998a14812d01fec67a6a1d73392 Mon Sep 17 00:00:00 2001 From: Kevin Turner <kevintur...@utexas.edu> Date: Thu, 10 Dec 2020 22:35:06 -0600 Subject: [PATCH 5/6] doc: Adds doc for bulk instance state change endpoint. Signed-off-by: Kevin Turner <kevintur...@utexas.edu> --- doc/rest-api.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/doc/rest-api.md b/doc/rest-api.md index 4a367a03b1..fbb76be9d2 100644 --- a/doc/rest-api.md +++ b/doc/rest-api.md @@ -727,6 +727,23 @@ Input (using a backup): Raw compressed tarball as provided by a backup download. +#### PUT + * Description: change the state of all instances + * Authentication: trusted + * Operation: async + * Return: background operation of standard error + +Input: + +```js +{ + "action": "start", // State change action (stop, start, restart, freeze or unfreeze) + "timeout": 30, // A timeout after which the state change is considered as failed + "force": true, // Force the state change (currently only valid for stop and restart where it means killing the instance) + "stateful": true // Whether to store or restore runtime state before stopping or starting (only valid for stop and start, defaults to false) +} +``` + ### `/1.0/instances/<name>` #### GET * Description: Instance information @@ -1434,7 +1451,7 @@ Input: "action": "stop", // State change action (stop, start, restart, freeze or unfreeze) "timeout": 30, // A timeout after which the state change is considered as failed "force": true, // Force the state change (currently only valid for stop and restart where it means killing the instance) - "stateful": true // Whether to store or restore runtime state before stopping or startiong (only valid for stop and start, defaults to false) + "stateful": true // Whether to store or restore runtime state before stopping or starting (only valid for stop and start, defaults to false) } ``` From 8c07d6b447bf5db34514a3a36a32bf1ae7c561f6 Mon Sep 17 00:00:00 2001 From: Kevin Turner <kevintur...@utexas.edu> Date: Thu, 10 Dec 2020 22:36:38 -0600 Subject: [PATCH 6/6] shared/api: Adds support for bulk instance state change. Signed-off-by: Kevin Turner <kevintur...@utexas.edu> --- shared/api/instance.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shared/api/instance.go b/shared/api/instance.go index cec6b4a258..23437157e6 100644 --- a/shared/api/instance.go +++ b/shared/api/instance.go @@ -28,6 +28,13 @@ type InstancesPost struct { Type InstanceType `json:"type" yaml:"type"` } +type InstancesPut struct { + Action string `json:"action" yaml:"action"` + Timeout int `json:"source" yaml:"source"` + Force bool `json:"force" yaml:"timeout"` + Stateful bool `json:"stateful" yaml:"stateful"` +} + // InstancePost represents the fields required to rename/move a LXD instance. // // API extension: instances
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel