[lxc-devel] [lxd/master] Container: Pass name rather than ID to LXC start, stopns and stop hooks

tomponline on Github lxc-bot at linuxcontainers.org
Fri Nov 13 14:33:02 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 766 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20201113/71c3b744/attachment.bin>
-------------- next part --------------
From d9d2a053653613b8ad593fbcbacd93dbecb3ec19 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 13 Nov 2020 12:00:31 +0000
Subject: [PATCH 1/4] lxd/instance/drivers/driver/lxc: Updates initLXC to use
 project and instance name in callhook hook commands

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/instance/drivers/driver_lxc.go | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/lxd/instance/drivers/driver_lxc.go b/lxd/instance/drivers/driver_lxc.go
index 101a0078d7..4645fc6dc1 100644
--- a/lxd/instance/drivers/driver_lxc.go
+++ b/lxd/instance/drivers/driver_lxc.go
@@ -943,17 +943,20 @@ func (c *lxc) initLXC(config bool) error {
 		return err
 	}
 
-	err = lxcSetConfigItem(cc, "lxc.hook.pre-start", fmt.Sprintf("/proc/%d/exe callhook %s %d start", os.Getpid(), shared.VarPath(""), c.id))
+	// Call the onstart hook on start.
+	err = lxcSetConfigItem(cc, "lxc.hook.pre-start", fmt.Sprintf("/proc/%d/exe callhook %s %s %s start", os.Getpid(), shared.VarPath(""), strconv.Quote(c.Project()), strconv.Quote(c.Name())))
 	if err != nil {
 		return err
 	}
 
-	err = lxcSetConfigItem(cc, "lxc.hook.stop", fmt.Sprintf("%s callhook %s %d stopns", c.state.OS.ExecPath, shared.VarPath(""), c.id))
+	// Call the onstopns hook on stop but before namespaces are unmounted.
+	err = lxcSetConfigItem(cc, "lxc.hook.stop", fmt.Sprintf("%s callhook %s %s %s stopns", c.state.OS.ExecPath, shared.VarPath(""), strconv.Quote(c.Project()), strconv.Quote(c.Name())))
 	if err != nil {
 		return err
 	}
 
-	err = lxcSetConfigItem(cc, "lxc.hook.post-stop", fmt.Sprintf("%s callhook %s %d stop", c.state.OS.ExecPath, shared.VarPath(""), c.id))
+	// Call the onstop hook on stop.
+	err = lxcSetConfigItem(cc, "lxc.hook.post-stop", fmt.Sprintf("%s callhook %s %s %s stop", c.state.OS.ExecPath, shared.VarPath(""), strconv.Quote(c.Project()), strconv.Quote(c.Name())))
 	if err != nil {
 		return err
 	}

From 25bf93b31a892f662de892f6c4b4c0ef86e8f468 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 13 Nov 2020 12:01:12 +0000
Subject: [PATCH 2/4] lxd/instance/drivers/driver/lxc: Updates startCommon to
 quote hook command arguments

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/instance/drivers/driver_lxc.go | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/lxd/instance/drivers/driver_lxc.go b/lxd/instance/drivers/driver_lxc.go
index 4645fc6dc1..4705a36d90 100644
--- a/lxd/instance/drivers/driver_lxc.go
+++ b/lxd/instance/drivers/driver_lxc.go
@@ -2187,19 +2187,19 @@ func (c *lxc) startCommon() (string, []func() error, error) {
 
 			if c.state.OS.Shiftfs && !c.IsPrivileged() && diskIdmap == nil {
 				// Host side mark mount.
-				err = lxcSetConfigItem(c.c, "lxc.hook.pre-start", fmt.Sprintf("/bin/mount -t shiftfs -o mark,passthrough=3 %s %s", c.RootfsPath(), c.RootfsPath()))
+				err = lxcSetConfigItem(c.c, "lxc.hook.pre-start", fmt.Sprintf("/bin/mount -t shiftfs -o mark,passthrough=3 %s %s", strconv.Quote(c.RootfsPath()), strconv.Quote(c.RootfsPath())))
 				if err != nil {
 					return "", nil, errors.Wrapf(err, "Failed to setup device mount shiftfs '%s'", dev.Name)
 				}
 
 				// Container side shift mount.
-				err = lxcSetConfigItem(c.c, "lxc.hook.pre-mount", fmt.Sprintf("/bin/mount -t shiftfs -o passthrough=3 %s %s", c.RootfsPath(), c.RootfsPath()))
+				err = lxcSetConfigItem(c.c, "lxc.hook.pre-mount", fmt.Sprintf("/bin/mount -t shiftfs -o passthrough=3 %s %s", strconv.Quote(c.RootfsPath()), strconv.Quote(c.RootfsPath())))
 				if err != nil {
 					return "", nil, errors.Wrapf(err, "Failed to setup device mount shiftfs '%s'", dev.Name)
 				}
 
 				// Host side umount of mark mount.
-				err = lxcSetConfigItem(c.c, "lxc.hook.start-host", fmt.Sprintf("/bin/umount -l %s", c.RootfsPath()))
+				err = lxcSetConfigItem(c.c, "lxc.hook.start-host", fmt.Sprintf("/bin/umount -l %s", strconv.Quote(c.RootfsPath())))
 				if err != nil {
 					return "", nil, errors.Wrapf(err, "Failed to setup device mount shiftfs '%s'", dev.Name)
 				}
@@ -2228,17 +2228,17 @@ func (c *lxc) startCommon() (string, []func() error, error) {
 						return "", nil, errors.Wrapf(fmt.Errorf("shiftfs is required but isn't supported on system"), "Failed to setup device mount '%s'", dev.Name)
 					}
 
-					err = lxcSetConfigItem(c.c, "lxc.hook.pre-start", fmt.Sprintf("/bin/mount -t shiftfs -o mark,passthrough=3 %s %s", mount.DevPath, mount.DevPath))
+					err = lxcSetConfigItem(c.c, "lxc.hook.pre-start", fmt.Sprintf("/bin/mount -t shiftfs -o mark,passthrough=3 %s %s", strconv.Quote(mount.DevPath), strconv.Quote(mount.DevPath)))
 					if err != nil {
 						return "", nil, errors.Wrapf(err, "Failed to setup device mount shiftfs '%s'", dev.Name)
 					}
 
-					err = lxcSetConfigItem(c.c, "lxc.hook.pre-mount", fmt.Sprintf("/bin/mount -t shiftfs -o passthrough=3 %s %s", mount.DevPath, mount.DevPath))
+					err = lxcSetConfigItem(c.c, "lxc.hook.pre-mount", fmt.Sprintf("/bin/mount -t shiftfs -o passthrough=3 %s %s", strconv.Quote(mount.DevPath), strconv.Quote(mount.DevPath)))
 					if err != nil {
 						return "", nil, errors.Wrapf(err, "Failed to setup device mount shiftfs '%s'", dev.Name)
 					}
 
-					err = lxcSetConfigItem(c.c, "lxc.hook.start-host", fmt.Sprintf("/bin/umount -l %s", mount.DevPath))
+					err = lxcSetConfigItem(c.c, "lxc.hook.start-host", fmt.Sprintf("/bin/umount -l %s", strconv.Quote(mount.DevPath)))
 					if err != nil {
 						return "", nil, errors.Wrapf(err, "Failed to setup device mount shiftfs '%s'", dev.Name)
 					}

From 63351b856b3ddefc4b7bb12631731b19cfb0c2f5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 13 Nov 2020 11:08:49 +0000
Subject: [PATCH 3/4] lxd/main/callhook: Updates cmdCallhook to support using
 project name and instance name in arguments

Removes support for unused hook "network-up".

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/main_callhook.go | 68 +++++++++++++++++++++++++-------------------
 1 file changed, 38 insertions(+), 30 deletions(-)

diff --git a/lxd/main_callhook.go b/lxd/main_callhook.go
index e2c7adfb11..84390f87ec 100644
--- a/lxd/main_callhook.go
+++ b/lxd/main_callhook.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"fmt"
+	"net/url"
 	"os"
 	"path/filepath"
 	"time"
@@ -17,7 +18,7 @@ type cmdCallhook struct {
 
 func (c *cmdCallhook) Command() *cobra.Command {
 	cmd := &cobra.Command{}
-	cmd.Use = "callhook <path> <id> <hook>"
+	cmd.Use = "callhook <path> [<instance id>|<instance project> <instance name>] <hook>"
 	cmd.Short = "Call container lifecycle hook in LXD"
 	cmd.Long = `Description:
   Call container lifecycle hook in LXD
@@ -32,7 +33,7 @@ func (c *cmdCallhook) Command() *cobra.Command {
 }
 
 func (c *cmdCallhook) Run(cmd *cobra.Command, args []string) error {
-	// Sanity checks
+	// Sanity checks.
 	if len(args) < 2 {
 		cmd.Help()
 
@@ -44,16 +45,28 @@ func (c *cmdCallhook) Run(cmd *cobra.Command, args []string) error {
 	}
 
 	path := args[0]
-	id := args[1]
-	state := args[2]
+
+	var projectName string
+	var instanceRef string
+	var hook string
+
+	if len(args) == 3 {
+		instanceRef = args[1]
+		hook = args[2]
+	} else if len(args) == 4 {
+		projectName = args[1]
+		instanceRef = args[2]
+		hook = args[3]
+	}
+
 	target := ""
 
-	// Only root should run this
+	// Only root should run this.
 	if os.Geteuid() != 0 {
 		return fmt.Errorf("This must be run as root")
 	}
 
-	// Connect to LXD
+	// Connect to LXD.
 	socket := os.Getenv("LXD_SOCKET")
 	if socket == "" {
 		socket = filepath.Join(path, "unix.socket")
@@ -67,40 +80,35 @@ func (c *cmdCallhook) Run(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	// Prepare the request URL
-	url := fmt.Sprintf("/internal/containers/%s/on%s", id, state)
-	if state == "stopns" {
-		target = os.Getenv("LXC_TARGET")
-		netns := os.Getenv("LXC_NET_NS")
-		if target == "" {
-			target = "unknown"
-		}
-		url = fmt.Sprintf("%s?target=%s&netns=%s", url, target, netns)
-	} else if state == "stop" {
+	// Prepare the request URL query parameters.
+	v := url.Values{}
+	if projectName != "" {
+		v.Set("project", projectName)
+	}
+
+	if hook == "stop" || hook == "stopns" {
 		target = os.Getenv("LXC_TARGET")
 		if target == "" {
 			target = "unknown"
 		}
-		url = fmt.Sprintf("%s?target=%s", url, target)
-	} else if state == "network-up" {
-		url = fmt.Sprintf("%s?device=%s&host_name=%s", url, args[3], os.Getenv("LXC_NET_PEER"))
+		v.Set("target", target)
+	}
+
+	if hook == "stopns" {
+		v.Set("netns", os.Getenv("LXC_NET_NS"))
 	}
 
-	// Setup the request
-	hook := make(chan error, 1)
+	// Setup the request.
+	response := make(chan error, 1)
 	go func() {
+		url := fmt.Sprintf("/internal/containers/%s/%s?%s", url.PathEscape(instanceRef), url.PathEscape(fmt.Sprintf("on%s", hook)), v.Encode())
 		_, _, err := d.RawQuery("GET", url, nil, "")
-		if err != nil {
-			hook <- err
-			return
-		}
-
-		hook <- nil
+		response <- err
 	}()
 
-	// Handle the timeout
+	// Handle the timeout.
 	select {
-	case err := <-hook:
+	case err := <-response:
 		if err != nil {
 			return err
 		}
@@ -112,7 +120,7 @@ func (c *cmdCallhook) Run(cmd *cobra.Command, args []string) error {
 	// If the container is rebooting, we purposefully tell LXC that this hook failed so that
 	// it won't reboot the container, which lets LXD start it again in the OnStop function.
 	// Other hook types can return without error safely.
-	if state == "stop" && target == "reboot" {
+	if hook == "stop" && target == "reboot" {
 		return fmt.Errorf("Reboot must be handled by LXD")
 	}
 

From 67b7c35ac82086b304945f3b3c663d5b953e4067 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 13 Nov 2020 11:06:25 +0000
Subject: [PATCH 4/4] lxd/api/internal: Adds support for using instance name
 and project name in container hook routes

This allows the stop hooks to be run even if the instance has been recovered to a different DB ID while the container is running.

However in order to ensure previously started containers can still stop cleanly we keep support for using instance ID too.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/api_internal.go | 74 ++++++++++++++++++++++++---------------------
 1 file changed, 39 insertions(+), 35 deletions(-)

diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index e10e3404c0..5bc6d48b80 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -25,6 +25,7 @@ import (
 	"github.com/lxc/lxd/lxd/instance/instancetype"
 	"github.com/lxc/lxd/lxd/project"
 	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/lxd/state"
 	storagePools "github.com/lxc/lxd/lxd/storage"
 	storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
 	"github.com/lxc/lxd/shared"
@@ -65,19 +66,19 @@ var internalReadyCmd = APIEndpoint{
 }
 
 var internalContainerOnStartCmd = APIEndpoint{
-	Path: "containers/{id}/onstart",
+	Path: "containers/{instanceRef}/onstart",
 
 	Get: APIEndpointAction{Handler: internalContainerOnStart},
 }
 
 var internalContainerOnStopNSCmd = APIEndpoint{
-	Path: "containers/{id}/onstopns",
+	Path: "containers/{instanceRef}/onstopns",
 
 	Get: APIEndpointAction{Handler: internalContainerOnStopNS},
 }
 
 var internalContainerOnStopCmd = APIEndpoint{
-	Path: "containers/{id}/onstop",
+	Path: "containers/{instanceRef}/onstop",
 
 	Get: APIEndpointAction{Handler: internalContainerOnStop},
 }
@@ -138,24 +139,43 @@ func internalShutdown(d *Daemon, r *http.Request) response.Response {
 	return response.EmptySyncResponse
 }
 
-func internalContainerOnStart(d *Daemon, r *http.Request) response.Response {
-	id, err := strconv.Atoi(mux.Vars(r)["id"])
-	if err != nil {
-		return response.SmartError(err)
-	}
+// internalContainerHookLoadFromRequestReference loads the container from the instance reference in the request.
+// It detects whether the instance reference is an instance ID or instance name and loads instance accordingly.
+func internalContainerHookLoadFromReference(s *state.State, r *http.Request) (instance.Instance, error) {
+	var inst instance.Instance
+	instanceRef := mux.Vars(r)["instanceRef"]
+	projectName := projectParam(r)
 
-	inst, err := instance.LoadByID(d.State(), id)
-	if err != nil {
-		return response.SmartError(err)
+	instanceID, err := strconv.Atoi(instanceRef)
+	if err == nil {
+		inst, err = instance.LoadByID(s, instanceID)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		inst, err = instance.LoadByProjectAndName(s, projectName, instanceRef)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	if inst.Type() != instancetype.Container {
-		return response.SmartError(fmt.Errorf("Instance is not container type"))
+		return nil, fmt.Errorf("Instance is not container type")
+	}
+
+	return inst, nil
+}
+
+func internalContainerOnStart(d *Daemon, r *http.Request) response.Response {
+	inst, err := internalContainerHookLoadFromReference(d.State(), r)
+	if err != nil {
+		logger.Error("The start hook failed to load", log.Ctx{"instance": inst.Name(), "err": err})
+		return response.SmartError(err)
 	}
 
 	err = inst.OnHook(instance.HookStart, nil)
 	if err != nil {
-		logger.Error("The start hook failed", log.Ctx{"container": inst.Name(), "err": err})
+		logger.Error("The start hook failed", log.Ctx{"instance": inst.Name(), "err": err})
 		return response.SmartError(err)
 	}
 
@@ -163,8 +183,9 @@ func internalContainerOnStart(d *Daemon, r *http.Request) response.Response {
 }
 
 func internalContainerOnStopNS(d *Daemon, r *http.Request) response.Response {
-	id, err := strconv.Atoi(mux.Vars(r)["id"])
+	inst, err := internalContainerHookLoadFromReference(d.State(), r)
 	if err != nil {
+		logger.Error("The stopns hook failed to load", log.Ctx{"instance": inst.Name(), "err": err})
 		return response.SmartError(err)
 	}
 
@@ -174,15 +195,6 @@ func internalContainerOnStopNS(d *Daemon, r *http.Request) response.Response {
 	}
 	netns := queryParam(r, "netns")
 
-	inst, err := instance.LoadByID(d.State(), id)
-	if err != nil {
-		return response.SmartError(err)
-	}
-
-	if inst.Type() != instancetype.Container {
-		return response.SmartError(fmt.Errorf("Instance is not container type"))
-	}
-
 	args := map[string]string{
 		"target": target,
 		"netns":  netns,
@@ -190,7 +202,7 @@ func internalContainerOnStopNS(d *Daemon, r *http.Request) response.Response {
 
 	err = inst.OnHook(instance.HookStopNS, args)
 	if err != nil {
-		logger.Error("The stopns hook failed", log.Ctx{"container": inst.Name(), "err": err})
+		logger.Error("The stopns hook failed", log.Ctx{"instance": inst.Name(), "err": err})
 		return response.SmartError(err)
 	}
 
@@ -198,8 +210,9 @@ func internalContainerOnStopNS(d *Daemon, r *http.Request) response.Response {
 }
 
 func internalContainerOnStop(d *Daemon, r *http.Request) response.Response {
-	id, err := strconv.Atoi(mux.Vars(r)["id"])
+	inst, err := internalContainerHookLoadFromReference(d.State(), r)
 	if err != nil {
+		logger.Error("The stop hook failed to load", log.Ctx{"instance": inst.Name(), "err": err})
 		return response.SmartError(err)
 	}
 
@@ -208,22 +221,13 @@ func internalContainerOnStop(d *Daemon, r *http.Request) response.Response {
 		target = "unknown"
 	}
 
-	inst, err := instance.LoadByID(d.State(), id)
-	if err != nil {
-		return response.SmartError(err)
-	}
-
-	if inst.Type() != instancetype.Container {
-		return response.SmartError(fmt.Errorf("Instance is not container type"))
-	}
-
 	args := map[string]string{
 		"target": target,
 	}
 
 	err = inst.OnHook(instance.HookStop, args)
 	if err != nil {
-		logger.Error("The stop hook failed", log.Ctx{"container": inst.Name(), "err": err})
+		logger.Error("The stop hook failed", log.Ctx{"instance": inst.Name(), "err": err})
 		return response.SmartError(err)
 	}
 


More information about the lxc-devel mailing list