[lxc-devel] [lxd/master] VM exec and delete fixes

stgraber on Github lxc-bot at linuxcontainers.org
Sat Jan 18 10:27:07 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200118/5bc80ed7/attachment.bin>
-------------- next part --------------
From dd208c34185c680ab9baa48ad5747be926470582 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 18 Jan 2020 08:42:30 +0200
Subject: [PATCH 1/5] lxd/exec: Pass full req through
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_exec.go              | 51 +++++++++++-------------------
 lxd/container_lxc.go               | 12 +++----
 lxd/instance/drivers/vm_qemu.go    | 17 +++-------
 lxd/instance/instance_interface.go |  2 +-
 4 files changed, 30 insertions(+), 52 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index cb6f224c99..e8da9b5036 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -30,23 +30,16 @@ import (
 )
 
 type execWs struct {
-	command  []string
-	instance instance.Instance
-	env      map[string]string
+	req api.InstanceExecPost
 
+	instance         instance.Instance
 	rootUid          int64
 	rootGid          int64
 	conns            map[int]*websocket.Conn
 	connsLock        sync.Mutex
 	allConnected     chan bool
 	controlConnected chan bool
-	interactive      bool
 	fds              map[int]string
-	width            int
-	height           int
-	uid              uint32
-	gid              uint32
-	cwd              string
 }
 
 func (s *execWs) Metadata() interface{} {
@@ -61,9 +54,9 @@ func (s *execWs) Metadata() interface{} {
 
 	return shared.Jmap{
 		"fds":         fds,
-		"command":     s.command,
-		"environment": s.env,
-		"interactive": s.interactive,
+		"command":     s.req.Command,
+		"environment": s.req.Environment,
+		"interactive": s.req.Interactive,
 	}
 }
 
@@ -119,7 +112,7 @@ func (s *execWs) Do(op *operations.Operation) error {
 	var stdout *os.File
 	var stderr *os.File
 
-	if s.interactive {
+	if s.req.Interactive {
 		ttys = make([]*os.File, 1)
 		ptys = make([]*os.File, 1)
 		ptys[0], ttys[0], err = shared.OpenPty(s.rootUid, s.rootGid)
@@ -131,8 +124,8 @@ func (s *execWs) Do(op *operations.Operation) error {
 		stdout = ttys[0]
 		stderr = ttys[0]
 
-		if s.width > 0 && s.height > 0 {
-			shared.SetSize(int(ptys[0].Fd()), s.width, s.height)
+		if s.req.Width > 0 && s.req.Height > 0 {
+			shared.SetSize(int(ptys[0].Fd()), s.req.Width, s.req.Height)
 		}
 	} else {
 		ttys = make([]*os.File, 3)
@@ -154,7 +147,7 @@ func (s *execWs) Do(op *operations.Operation) error {
 	attachedChildIsDead := make(chan bool, 1)
 	var wgEOF sync.WaitGroup
 
-	if s.interactive {
+	if s.req.Interactive {
 		wgEOF.Add(1)
 		go func() {
 			logger.Debugf("Interactive child process handler waiting")
@@ -293,7 +286,7 @@ func (s *execWs) Do(op *operations.Operation) error {
 		s.connsLock.Unlock()
 
 		if conn == nil {
-			if s.interactive {
+			if s.req.Interactive {
 				controlExit <- true
 			}
 		} else {
@@ -317,12 +310,12 @@ func (s *execWs) Do(op *operations.Operation) error {
 		return cmdErr
 	}
 
-	cmd, err := s.instance.Exec(s.command, s.env, stdin, stdout, stderr, s.cwd, s.uid, s.gid)
+	cmd, err := s.instance.Exec(s.req, stdin, stdout, stderr)
 	if err != nil {
 		return err
 	}
 
-	if s.interactive {
+	if s.req.Interactive {
 		// Start the interactive process handler.
 		attachedChildIsBorn <- cmd
 	}
@@ -385,8 +378,8 @@ func containerExecPost(d *Daemon, r *http.Request) response.Response {
 		return response.BadRequest(fmt.Errorf("Container is frozen"))
 	}
 
+	// Process environment.
 	env := map[string]string{}
-
 	for k, v := range inst.ExpandedConfig() {
 		if strings.HasPrefix(k, "environment.") {
 			env[strings.TrimPrefix(k, "environment.")] = v
@@ -429,6 +422,9 @@ func containerExecPost(d *Daemon, r *http.Request) response.Response {
 		env["LANG"] = "C.UTF-8"
 	}
 
+	// Apply to request.
+	post.Environment = env
+
 	if post.WaitForWS {
 		ws := &execWs{}
 		ws.fds = map[int]string{}
@@ -454,7 +450,6 @@ func containerExecPost(d *Daemon, r *http.Request) response.Response {
 		}
 		ws.allConnected = make(chan bool, 1)
 		ws.controlConnected = make(chan bool, 1)
-		ws.interactive = post.Interactive
 		for i := -1; i < len(ws.conns)-1; i++ {
 			ws.fds[i], err = shared.RandomCryptoString()
 			if err != nil {
@@ -462,16 +457,8 @@ func containerExecPost(d *Daemon, r *http.Request) response.Response {
 			}
 		}
 
-		ws.command = post.Command
 		ws.instance = inst
-		ws.env = env
-
-		ws.width = post.Width
-		ws.height = post.Height
-
-		ws.cwd = post.Cwd
-		ws.uid = post.User
-		ws.gid = post.Group
+		ws.req = post
 
 		resources := map[string][]string{}
 		resources["containers"] = []string{ws.instance.Name()}
@@ -502,7 +489,7 @@ func containerExecPost(d *Daemon, r *http.Request) response.Response {
 			defer stderr.Close()
 
 			// Run the command
-			cmd, err := inst.Exec(post.Command, env, nil, stdout, stderr, post.Cwd, post.User, post.Group)
+			cmd, err := inst.Exec(post, nil, stdout, stderr)
 			if err != nil {
 				return err
 			}
@@ -519,7 +506,7 @@ func containerExecPost(d *Daemon, r *http.Request) response.Response {
 				"2": fmt.Sprintf("/%s/containers/%s/logs/%s", version.APIVersion, inst.Name(), filepath.Base(stderr.Name())),
 			}
 		} else {
-			cmd, err := inst.Exec(post.Command, env, nil, nil, nil, post.Cwd, post.User, post.Group)
+			cmd, err := inst.Exec(post, nil, nil, nil)
 			if err != nil {
 				return err
 			}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index ba32e1ed86..fdb21fc928 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -5681,11 +5681,11 @@ func (c *containerLXC) ConsoleLog(opts lxc.ConsoleLogOptions) (string, error) {
 	return string(msg), nil
 }
 
-func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File, cwd string, uid uint32, gid uint32) (instance.Cmd, error) {
+func (c *containerLXC) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File, stderr *os.File) (instance.Cmd, error) {
 	// Prepare the environment
 	envSlice := []string{}
 
-	for k, v := range env {
+	for k, v := range req.Environment {
 		envSlice = append(envSlice, fmt.Sprintf("%s=%s", k, v))
 	}
 
@@ -5704,9 +5704,9 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 		cname,
 		c.state.OS.LxcPath,
 		filepath.Join(c.LogPath(), "lxc.conf"),
-		cwd,
-		fmt.Sprintf("%d", uid),
-		fmt.Sprintf("%d", gid),
+		req.Cwd,
+		fmt.Sprintf("%d", req.User),
+		fmt.Sprintf("%d", req.Group),
 	}
 
 	args = append(args, "--")
@@ -5715,7 +5715,7 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 
 	args = append(args, "--")
 	args = append(args, "cmd")
-	args = append(args, command...)
+	args = append(args, req.Command...)
 
 	cmd := exec.Cmd{}
 	cmd.Path = c.state.OS.ExecPath
diff --git a/lxd/instance/drivers/vm_qemu.go b/lxd/instance/drivers/vm_qemu.go
index a2eb2968dd..29bf11973f 100644
--- a/lxd/instance/drivers/vm_qemu.go
+++ b/lxd/instance/drivers/vm_qemu.go
@@ -2493,7 +2493,7 @@ func (vm *qemu) forwardSignal(control *websocket.Conn, sig unix.Signal) error {
 }
 
 // Exec a command inside the instance.
-func (vm *qemu) Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File, cwd string, uid uint32, gid uint32) (instance.Cmd, error) {
+func (vm *qemu) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File, stderr *os.File) (instance.Cmd, error) {
 	var instCmd *Cmd
 
 	// Because this function will exit before the remote command has finished, we create a
@@ -2528,17 +2528,8 @@ func (vm *qemu) Exec(command []string, env map[string]string, stdin *os.File, st
 	}
 	cleanupFuncs = append(cleanupFuncs, agent.Disconnect)
 
-	post := api.InstanceExecPost{
-		Command:     command,
-		WaitForWS:   true,
-		Interactive: stdin == stdout,
-		Environment: env,
-		User:        uid,
-		Group:       gid,
-		Cwd:         cwd,
-	}
-
-	if post.Interactive {
+	req.WaitForWS = true
+	if req.Interactive {
 		// Set console to raw.
 		oldttystate, err := termios.MakeRaw(int(stdin.Fd()))
 		if err != nil {
@@ -2578,7 +2569,7 @@ func (vm *qemu) Exec(command []string, env map[string]string, stdin *os.File, st
 		Control:  controlHander,
 	}
 
-	op, err := agent.ExecInstance("", post, &args)
+	op, err := agent.ExecInstance("", req, &args)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/instance/instance_interface.go b/lxd/instance/instance_interface.go
index 8c9e284e47..59a39167ad 100644
--- a/lxd/instance/instance_interface.go
+++ b/lxd/instance/instance_interface.go
@@ -51,7 +51,7 @@ type Instance interface {
 
 	// Console - Allocate and run a console tty.
 	Console() (*os.File, chan error, error)
-	Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File, cwd string, uid uint32, gid uint32) (Cmd, error)
+	Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File, stderr *os.File) (Cmd, error)
 
 	// Status
 	Render() (interface{}, interface{}, error)

From 86c70c80d5f6a0665087c54dd7552361efc77f33 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 18 Jan 2020 09:13:52 +0200
Subject: [PATCH 2/5] lxd/exec: Forward control messages
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_exec.go               | 18 ++++++--
 lxd/container_lxc.go                | 13 +++---
 lxd/instance/drivers/vm_qemu.go     | 72 ++++++++---------------------
 lxd/instance/drivers/vm_qemu_cmd.go | 16 +++----
 lxd/instance/instance_interface.go  |  4 +-
 5 files changed, 51 insertions(+), 72 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index e8da9b5036..8bef4a1d4b 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -40,6 +40,7 @@ type execWs struct {
 	allConnected     chan bool
 	controlConnected chan bool
 	fds              map[int]string
+	remoteControl    *websocket.Conn
 }
 
 func (s *execWs) Metadata() interface{} {
@@ -162,6 +163,16 @@ func (s *execWs) Do(op *operations.Operation) error {
 				return
 			}
 
+			// Handle cases where the instance provides us a control websocket.
+			if s.remoteControl != nil {
+				s.connsLock.Lock()
+				conn := s.conns[-1]
+				s.connsLock.Unlock()
+
+				<-shared.WebsocketProxy(conn, s.remoteControl)
+				return
+			}
+
 			logger.Debugf("Interactive child process handler started for child PID %d", attachedChild.PID())
 			for {
 				s.connsLock.Lock()
@@ -310,7 +321,7 @@ func (s *execWs) Do(op *operations.Operation) error {
 		return cmdErr
 	}
 
-	cmd, err := s.instance.Exec(s.req, stdin, stdout, stderr)
+	cmd, wsControl, err := s.instance.Exec(s.req, stdin, stdout, stderr)
 	if err != nil {
 		return err
 	}
@@ -318,6 +329,7 @@ func (s *execWs) Do(op *operations.Operation) error {
 	if s.req.Interactive {
 		// Start the interactive process handler.
 		attachedChildIsBorn <- cmd
+		s.remoteControl = wsControl
 	}
 
 	exitCode, err := cmd.Wait()
@@ -489,7 +501,7 @@ func containerExecPost(d *Daemon, r *http.Request) response.Response {
 			defer stderr.Close()
 
 			// Run the command
-			cmd, err := inst.Exec(post, nil, stdout, stderr)
+			cmd, _, err := inst.Exec(post, nil, stdout, stderr)
 			if err != nil {
 				return err
 			}
@@ -506,7 +518,7 @@ func containerExecPost(d *Daemon, r *http.Request) response.Response {
 				"2": fmt.Sprintf("/%s/containers/%s/logs/%s", version.APIVersion, inst.Name(), filepath.Base(stderr.Name())),
 			}
 		} else {
-			cmd, err := inst.Exec(post, nil, nil, nil)
+			cmd, _, err := inst.Exec(post, nil, nil, nil)
 			if err != nil {
 				return err
 			}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index fdb21fc928..3d670c00d3 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -19,6 +19,7 @@ import (
 	"time"
 
 	"github.com/flosch/pongo2"
+	"github.com/gorilla/websocket"
 	"github.com/pkg/errors"
 	"golang.org/x/sys/unix"
 	lxc "gopkg.in/lxc/go-lxc.v2"
@@ -5681,7 +5682,7 @@ func (c *containerLXC) ConsoleLog(opts lxc.ConsoleLogOptions) (string, error) {
 	return string(msg), nil
 }
 
-func (c *containerLXC) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File, stderr *os.File) (instance.Cmd, error) {
+func (c *containerLXC) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File, stderr *os.File) (instance.Cmd, *websocket.Conn, error) {
 	// Prepare the environment
 	envSlice := []string{}
 
@@ -5693,7 +5694,7 @@ func (c *containerLXC) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os
 	logPath := filepath.Join(c.LogPath(), "forkexec.log")
 	logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	// Prepare the subcommand
@@ -5746,21 +5747,21 @@ func (c *containerLXC) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os
 	rStatus, wStatus, err := shared.Pipe()
 	defer rStatus.Close()
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	cmd.ExtraFiles = []*os.File{stdin, stdout, stderr, wStatus}
 	err = cmd.Start()
 	if err != nil {
 		wStatus.Close()
-		return nil, err
+		return nil, nil, err
 	}
 	wStatus.Close()
 
 	attachedPid := -1
 	if err := json.NewDecoder(rStatus).Decode(&attachedPid); err != nil {
 		logger.Errorf("Failed to retrieve PID of executing child process: %s", err)
-		return nil, err
+		return nil, nil, err
 	}
 
 	instCmd := &ContainerLXCCmd{
@@ -5768,7 +5769,7 @@ func (c *containerLXC) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os
 		attachedChildPid: attachedPid,
 	}
 
-	return instCmd, nil
+	return instCmd, nil, nil
 }
 
 func (c *containerLXC) cpuState() api.InstanceStateCPU {
diff --git a/lxd/instance/drivers/vm_qemu.go b/lxd/instance/drivers/vm_qemu.go
index 29bf11973f..a9156481ab 100644
--- a/lxd/instance/drivers/vm_qemu.go
+++ b/lxd/instance/drivers/vm_qemu.go
@@ -2,7 +2,6 @@ package drivers
 
 import (
 	"bytes"
-	"encoding/json"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -2470,30 +2469,8 @@ func (vm *qemu) Console() (*os.File, chan error, error) {
 	return console, chDisconnect, nil
 }
 
-func (vm *qemu) forwardSignal(control *websocket.Conn, sig unix.Signal) error {
-	logger.Debugf("Forwarding signal to lxd-agent: %s", sig)
-
-	w, err := control.NextWriter(websocket.TextMessage)
-	if err != nil {
-		return err
-	}
-
-	msg := api.InstanceExecControl{}
-	msg.Command = "signal"
-	msg.Signal = int(sig)
-
-	buf, err := json.Marshal(msg)
-	if err != nil {
-		return err
-	}
-	_, err = w.Write(buf)
-
-	w.Close()
-	return err
-}
-
 // Exec a command inside the instance.
-func (vm *qemu) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File, stderr *os.File) (instance.Cmd, error) {
+func (vm *qemu) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File, stderr *os.File) (instance.Cmd, *websocket.Conn, error) {
 	var instCmd *Cmd
 
 	// Because this function will exit before the remote command has finished, we create a
@@ -2518,13 +2495,13 @@ func (vm *qemu) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File,
 
 	client, err := vm.getAgentClient()
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	agent, err := lxdClient.ConnectLXDHTTP(nil, client)
 	if err != nil {
 		logger.Errorf("Failed to connect to lxd-agent on %s: %v", vm.Name(), err)
-		return nil, fmt.Errorf("Failed to connect to lxd-agent")
+		return nil, nil, fmt.Errorf("Failed to connect to lxd-agent")
 	}
 	cleanupFuncs = append(cleanupFuncs, agent.Disconnect)
 
@@ -2533,7 +2510,7 @@ func (vm *qemu) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File,
 		// Set console to raw.
 		oldttystate, err := termios.MakeRaw(int(stdin.Fd()))
 		if err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		cleanupFuncs = append(cleanupFuncs, func() {
 			termios.Restore(int(stdin.Fd()), oldttystate)
@@ -2541,24 +2518,13 @@ func (vm *qemu) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File,
 	}
 
 	dataDone := make(chan bool)
-	signalSendCh := make(chan unix.Signal)
-	signalResCh := make(chan error)
-
-	// This is the signal control handler, it receives signals from lxc CLI and forwards them
-	// to the VM agent.
-	controlHander := func(control *websocket.Conn) {
-		closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
-		defer control.WriteMessage(websocket.CloseMessage, closeMsg)
-
-		for {
-			select {
-			case signal := <-signalSendCh:
-				err := vm.forwardSignal(control, signal)
-				signalResCh <- err
-			case <-dataDone:
-				return
-			}
-		}
+
+	// Retrieve the raw control websocket and pass it to the generic exec handler.
+	var wsControl *websocket.Conn
+	chControl := make(chan struct{})
+	controlHander := func(conn *websocket.Conn) {
+		wsControl = conn
+		close(chControl)
 	}
 
 	args := lxdClient.InstanceExecArgs{
@@ -2571,19 +2537,19 @@ func (vm *qemu) Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File,
 
 	op, err := agent.ExecInstance("", req, &args)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
+	// Wait for the control websocket to be connected.
+	<-chControl
+
 	instCmd = &Cmd{
-		cmd:              op,
-		attachedChildPid: -1, // Process is not running on LXD host.
-		dataDone:         args.DataDone,
-		cleanupFunc:      cleanupFunc,
-		signalSendCh:     signalSendCh,
-		signalResCh:      signalResCh,
+		cmd:         op,
+		dataDone:    args.DataDone,
+		cleanupFunc: cleanupFunc,
 	}
 
-	return instCmd, nil
+	return instCmd, wsControl, nil
 }
 
 // Render returns info about the instance.
diff --git a/lxd/instance/drivers/vm_qemu_cmd.go b/lxd/instance/drivers/vm_qemu_cmd.go
index 2b117334f0..dc1c7d6c6c 100644
--- a/lxd/instance/drivers/vm_qemu_cmd.go
+++ b/lxd/instance/drivers/vm_qemu_cmd.go
@@ -1,6 +1,8 @@
 package drivers
 
 import (
+	"fmt"
+
 	"golang.org/x/sys/unix"
 
 	lxdClient "github.com/lxc/lxd/client"
@@ -8,23 +10,19 @@ import (
 
 // Cmd represents a running command for an Qemu VM.
 type Cmd struct {
-	attachedChildPid int
-	cmd              lxdClient.Operation
-	dataDone         chan bool
-	signalSendCh     chan unix.Signal
-	signalResCh      chan error
-	cleanupFunc      func()
+	cmd         lxdClient.Operation
+	dataDone    chan bool
+	cleanupFunc func()
 }
 
 // PID returns the attached child's process ID.
 func (c *Cmd) PID() int {
-	return c.attachedChildPid
+	return -1
 }
 
 // Signal sends a signal to the command.
 func (c *Cmd) Signal(sig unix.Signal) error {
-	c.signalSendCh <- sig
-	return <-c.signalResCh
+	return fmt.Errorf("Not supported")
 }
 
 // Wait for the command to end and returns its exit code and any error.
diff --git a/lxd/instance/instance_interface.go b/lxd/instance/instance_interface.go
index 59a39167ad..192420923b 100644
--- a/lxd/instance/instance_interface.go
+++ b/lxd/instance/instance_interface.go
@@ -5,6 +5,8 @@ import (
 	"os"
 	"time"
 
+	"github.com/gorilla/websocket"
+
 	"github.com/lxc/lxd/lxd/backup"
 	"github.com/lxc/lxd/lxd/db"
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
@@ -51,7 +53,7 @@ type Instance interface {
 
 	// Console - Allocate and run a console tty.
 	Console() (*os.File, chan error, error)
-	Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File, stderr *os.File) (Cmd, error)
+	Exec(req api.InstanceExecPost, stdin *os.File, stdout *os.File, stderr *os.File) (Cmd, *websocket.Conn, error)
 
 	// Status
 	Render() (interface{}, interface{}, error)

From 565dcb3abd5f26178e3ed5547676d8898718c411 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 18 Jan 2020 09:28:55 +0200
Subject: [PATCH 3/5] lxd/containers: Fix error handling on stop
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 3d670c00d3..ca6e59c495 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2625,6 +2625,7 @@ func (c *containerLXC) Stop(stateful bool) error {
 	// Load cgroup abstraction
 	cg, err := c.cgroup(nil)
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 

From 19f3e9b0559f2d37c0e400c334ae061c7793fb0e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 18 Jan 2020 09:29:17 +0200
Subject: [PATCH 4/5] lxd/vm: Fix stop race condition
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/instance/drivers/vm_qemu.go | 38 ++++++++++++++++++++++++++++++---
 1 file changed, 35 insertions(+), 3 deletions(-)

diff --git a/lxd/instance/drivers/vm_qemu.go b/lxd/instance/drivers/vm_qemu.go
index a9156481ab..4f78d69dce 100644
--- a/lxd/instance/drivers/vm_qemu.go
+++ b/lxd/instance/drivers/vm_qemu.go
@@ -30,6 +30,7 @@ import (
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/instance/drivers/qmp"
 	"github.com/lxc/lxd/lxd/instance/instancetype"
+	"github.com/lxc/lxd/lxd/instance/operationlock"
 	"github.com/lxc/lxd/lxd/maas"
 	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/project"
@@ -456,17 +457,28 @@ func (vm *qemu) Freeze() error {
 
 // OnStop is run when the instance stops.
 func (vm *qemu) OnStop(target string) error {
+	// Pick up the existing stop operation lock created in Stop() function.
+	op := operationlock.Get(vm.id)
+	if op != nil && op.Action() != "stop" {
+		return fmt.Errorf("Instance is already running a %s operation", op.Action())
+	}
+
+	// Cleanup.
 	vm.cleanupDevices()
 	os.Remove(vm.pidFilePath())
 	os.Remove(vm.getMonitorPath())
 	vm.unmount()
 
-	// Record power state
+	// Record power state.
 	err := vm.state.Cluster.ContainerSetState(vm.id, "STOPPED")
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
+	// Done after this.
+	defer op.Done(nil)
+
 	if target == "reboot" {
 		return vm.Start(false)
 	}
@@ -1469,18 +1481,27 @@ func (vm *qemu) pid() (int, error) {
 
 // Stop stops the VM.
 func (vm *qemu) Stop(stateful bool) error {
+	// Check that we're not already stopped.
+	if !vm.IsRunning() {
+		return fmt.Errorf("The instance is already stopped")
+	}
+
+	// Check that no stateful stop was requested.
 	if stateful {
 		return fmt.Errorf("Stateful stop isn't supported for VMs at this time")
 	}
 
-	if !vm.IsRunning() {
-		return fmt.Errorf("Instance is not running")
+	// Setup a new operation.
+	op, err := operationlock.Create(vm.id, "stop", false, true)
+	if err != nil {
+		return err
 	}
 
 	// Connect to the monitor.
 	monitor, err := qmp.Connect(vm.getMonitorPath(), vm.getMonitorEventHandler())
 	if err != nil {
 		// If we fail to connect, it's most likely because the VM is already off.
+		op.Done(nil)
 		return nil
 	}
 
@@ -1488,9 +1509,11 @@ func (vm *qemu) Stop(stateful bool) error {
 	chDisconnect, err := monitor.Wait()
 	if err != nil {
 		if err == qmp.ErrMonitorDisconnect {
+			op.Done(nil)
 			return nil
 		}
 
+		op.Done(err)
 		return err
 	}
 
@@ -1498,15 +1521,24 @@ func (vm *qemu) Stop(stateful bool) error {
 	err = monitor.Quit()
 	if err != nil {
 		if err == qmp.ErrMonitorDisconnect {
+			op.Done(nil)
 			return nil
 		}
 
+		op.Done(err)
 		return err
 	}
 
 	// Wait for QEMU to exit (can take a while if pending I/O).
 	<-chDisconnect
 
+	// Wait for OnStop.
+	err = op.Wait()
+	if err != nil && vm.IsRunning() {
+		return err
+	}
+
+	vm.state.Events.SendLifecycle(vm.project, "virtual-machine-stopped", fmt.Sprintf("/1.0/virtual-machines/%s", vm.name), nil)
 	return nil
 }
 

From 7d5e22c0cf34f230413c107a751683814f09c63e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 18 Jan 2020 09:36:24 +0200
Subject: [PATCH 5/5] lxd/vm: Add locking for stop and shutdown
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/instance/drivers/vm_qemu.go | 40 +++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/lxd/instance/drivers/vm_qemu.go b/lxd/instance/drivers/vm_qemu.go
index 4f78d69dce..e283e2fe78 100644
--- a/lxd/instance/drivers/vm_qemu.go
+++ b/lxd/instance/drivers/vm_qemu.go
@@ -492,9 +492,16 @@ func (vm *qemu) Shutdown(timeout time.Duration) error {
 		return fmt.Errorf("The instance is already stopped")
 	}
 
+	// Setup a new operation
+	op, err := operationlock.Create(vm.id, "stop", true, true)
+	if err != nil {
+		return err
+	}
+
 	// Connect to the monitor.
 	monitor, err := qmp.Connect(vm.getMonitorPath(), vm.getMonitorEventHandler())
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
@@ -502,9 +509,11 @@ func (vm *qemu) Shutdown(timeout time.Duration) error {
 	chDisconnect, err := monitor.Wait()
 	if err != nil {
 		if err == qmp.ErrMonitorDisconnect {
+			op.Done(nil)
 			return nil
 		}
 
+		op.Done(err)
 		return err
 	}
 
@@ -512,9 +521,11 @@ func (vm *qemu) Shutdown(timeout time.Duration) error {
 	err = monitor.Powerdown()
 	if err != nil {
 		if err == qmp.ErrMonitorDisconnect {
+			op.Done(nil)
 			return nil
 		}
 
+		op.Done(err)
 		return err
 	}
 
@@ -522,14 +533,19 @@ func (vm *qemu) Shutdown(timeout time.Duration) error {
 	if timeout > 0 {
 		select {
 		case <-chDisconnect:
+			op.Done(nil)
+			vm.state.Events.SendLifecycle(vm.project, "instance-shutdown", fmt.Sprintf("/1.0/virtual-machines/%s", vm.name), nil)
 			return nil
 		case <-time.After(timeout):
+			op.Done(fmt.Errorf("Instance was not shutdown after timeout"))
 			return fmt.Errorf("Instance was not shutdown after timeout")
 		}
 	} else {
 		<-chDisconnect // Block until VM is not running if no timeout provided.
 	}
 
+	op.Done(nil)
+	vm.state.Events.SendLifecycle(vm.project, "instance-shutdown", fmt.Sprintf("/1.0/virtual-machines/%s", vm.name), nil)
 	return nil
 }
 
@@ -553,29 +569,41 @@ func (vm *qemu) Start(stateful bool) error {
 		return fmt.Errorf("The instance is already running")
 	}
 
+	// Setup a new operation
+	op, err := operationlock.Create(vm.id, "start", false, false)
+	if err != nil {
+		return errors.Wrap(err, "Create instance start operation")
+	}
+	defer op.Done(nil)
+
 	// Mount the instance's config volume.
 	_, err = vm.mount()
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
 	err = vm.generateConfigShare()
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
 	err = os.MkdirAll(vm.LogPath(), 0700)
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
 	err = os.MkdirAll(vm.DevicesPath(), 0711)
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
 	err = os.MkdirAll(vm.ShmountsPath(), 0711)
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
@@ -591,6 +619,7 @@ func (vm *qemu) Start(stateful bool) error {
 	if !shared.PathExists(vm.getNvramPath()) {
 		err = vm.setupNvram()
 		if err != nil {
+			op.Done(err)
 			return err
 		}
 	}
@@ -602,6 +631,7 @@ func (vm *qemu) Start(stateful bool) error {
 		// Start the device.
 		runConf, err := vm.deviceStart(dev.Name, dev.Config, false)
 		if err != nil {
+			op.Done(err)
 			return errors.Wrapf(err, "Failed to start device '%s'", dev.Name)
 		}
 
@@ -615,17 +645,20 @@ func (vm *qemu) Start(stateful bool) error {
 	// Get qemu configuration
 	qemuBinary, qemuType, qemuConfig, err := vm.qemuArchConfig()
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
 	confFile, err := vm.generateQemuConfigFile(qemuType, qemuConfig, devConfs)
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
 	// Check qemu is installed.
 	_, err = exec.LookPath(qemuBinary)
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
@@ -670,6 +703,7 @@ func (vm *qemu) Start(stateful bool) error {
 				return nil
 			})
 		if err != nil {
+			op.Done(err)
 			return err
 		}
 	}
@@ -685,18 +719,21 @@ func (vm *qemu) Start(stateful bool) error {
 
 	_, err = shared.RunCommand(qemuBinary, args...)
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
 	// Start QMP monitoring.
 	monitor, err := qmp.Connect(vm.getMonitorPath(), vm.getMonitorEventHandler())
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
 	// Start the VM.
 	err = monitor.Start()
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
@@ -717,9 +754,12 @@ func (vm *qemu) Start(stateful bool) error {
 		return nil
 	})
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
+	vm.state.Events.SendLifecycle(vm.project, "virtual-machine-started", fmt.Sprintf("/1.0/virtual-machines/%s", vm.name), nil)
+
 	return nil
 }
 


More information about the lxc-devel mailing list