[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