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

Reply via email to