[lxc-devel] [lxd/master] Allows bulk instance state changes.

kevtheappdev on Github lxc-bot at linuxcontainers.org
Fri Dec 11 05:51:04 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 313 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20201210/883d5516/attachment.bin>
-------------- next part --------------
From 27026e3042e5028c2f45d684ca79883cb2497898 Mon Sep 17 00:00:00 2001
From: Kevin Turner <kevinturner at 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 <kevinturner at 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 <kevinturner at 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 <kevinturner at 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 <kevinturner at 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 <kevinturner at 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 <kevinturner at 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 <kevinturner at 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 <kevinturner at 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 <kevinturner at 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 <kevinturner at 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 <kevinturner at 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


More information about the lxc-devel mailing list