[lxc-devel] [lxd/master] Fix listing and creating cluster containers inside projects

freeekanayaka on Github lxc-bot at linuxcontainers.org
Mon Oct 15 13:54:49 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 686 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20181015/a607ecbc/attachment.bin>
-------------- next part --------------
From b8d35a02146e163c7c44adce21824931ec6fa658 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Mon, 15 Oct 2018 14:24:29 +0200
Subject: [PATCH 1/3] Make containers on other nodes visible also in the
 non-default project

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/container_console.go  |  2 +-
 lxd/container_exec.go     |  2 +-
 lxd/containers_get.go     | 12 ++++++---
 lxd/containers_post.go    |  7 +++--
 lxd/response.go           | 13 +++++++---
 test/main.sh              |  1 +
 test/suites/clustering.sh | 54 +++++++++++++++++++++++++++++++++++++++
 7 files changed, 80 insertions(+), 11 deletions(-)

diff --git a/lxd/container_console.go b/lxd/container_console.go
index 821b9ff556..f5270af7e2 100644
--- a/lxd/container_console.go
+++ b/lxd/container_console.go
@@ -282,7 +282,7 @@ func containerConsolePost(d *Daemon, r *http.Request) Response {
 		}
 
 		opAPI := op.Get()
-		return ForwardedOperationResponse(&opAPI)
+		return ForwardedOperationResponse(project, &opAPI)
 	}
 
 	c, err := containerLoadByProjectAndName(d.State(), project, name)
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index c73f403558..2a1d5049e0 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -360,7 +360,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 		}
 
 		opAPI := op.Get()
-		return ForwardedOperationResponse(&opAPI)
+		return ForwardedOperationResponse(project, &opAPI)
 	}
 
 	c, err := containerLoadByProjectAndName(d.State(), project, name)
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 1115f7265e..d93190be33 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -151,7 +151,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 				cert := d.endpoints.NetworkCert()
 
 				if recursion == 1 {
-					cs, err := doContainersGetFromNode(address, cert)
+					cs, err := doContainersGetFromNode(project, address, cert)
 					if err != nil {
 						for _, name := range containers {
 							resultListAppend(name, api.Container{}, err)
@@ -167,7 +167,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 					return
 				}
 
-				cs, err := doContainersFullGetFromNode(address, cert)
+				cs, err := doContainersFullGetFromNode(project, address, cert)
 				if err != nil {
 					for _, name := range containers {
 						resultFullListAppend(name, api.ContainerFull{}, err)
@@ -262,13 +262,15 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 
 // Fetch information about the containers on the given remote node, using the
 // rest API and with a timeout of 30 seconds.
-func doContainersGetFromNode(node string, cert *shared.CertInfo) ([]api.Container, error) {
+func doContainersGetFromNode(project, node string, cert *shared.CertInfo) ([]api.Container, error) {
 	f := func() ([]api.Container, error) {
 		client, err := cluster.Connect(node, cert, true)
 		if err != nil {
 			return nil, errors.Wrapf(err, "Failed to connect to node %s", node)
 		}
 
+		client = client.UseProject(project)
+
 		containers, err := client.GetContainers()
 		if err != nil {
 			return nil, errors.Wrapf(err, "Failed to get containers from node %s", node)
@@ -297,13 +299,15 @@ func doContainersGetFromNode(node string, cert *shared.CertInfo) ([]api.Containe
 	return containers, err
 }
 
-func doContainersFullGetFromNode(node string, cert *shared.CertInfo) ([]api.ContainerFull, error) {
+func doContainersFullGetFromNode(project, node string, cert *shared.CertInfo) ([]api.ContainerFull, error) {
 	f := func() ([]api.ContainerFull, error) {
 		client, err := cluster.Connect(node, cert, true)
 		if err != nil {
 			return nil, errors.Wrapf(err, "Failed to connect to node %s", node)
 		}
 
+		client = client.UseProject(project)
+
 		containers, err := client.GetContainersFull()
 		if err != nil {
 			return nil, errors.Wrapf(err, "Failed to get containers from node %s", node)
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 0594d7f174..72076d4c66 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -660,14 +660,17 @@ func containersPost(d *Daemon, r *http.Request) Response {
 				return SmartError(err)
 			}
 
+			client = client.UseProject(project)
+			client = client.UseTarget(targetNode)
+
 			logger.Debugf("Forward container post request to %s", address)
-			op, err := client.UseTarget(targetNode).CreateContainer(req)
+			op, err := client.CreateContainer(req)
 			if err != nil {
 				return SmartError(err)
 			}
 
 			opAPI := op.Get()
-			return ForwardedOperationResponse(&opAPI)
+			return ForwardedOperationResponse(project, &opAPI)
 		}
 	}
 
diff --git a/lxd/response.go b/lxd/response.go
index d3490db2f0..62bcc9669f 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -386,11 +386,15 @@ func OperationResponse(op *operation) Response {
 //
 // Returned when the operation has been created on another node
 type forwardedOperationResponse struct {
-	op *api.Operation
+	op      *api.Operation
+	project string
 }
 
 func (r *forwardedOperationResponse) Render(w http.ResponseWriter) error {
 	url := fmt.Sprintf("/%s/operations/%s", version.APIVersion, r.op.ID)
+	if r.project != "" {
+		url += fmt.Sprintf("?project=%s", r.project)
+	}
 
 	body := api.ResponseRaw{
 		Type:       api.AsyncResponse,
@@ -412,8 +416,11 @@ func (r *forwardedOperationResponse) String() string {
 
 // ForwardedOperationResponse creates a response that forwards the metadata of
 // an operation created on another node.
-func ForwardedOperationResponse(op *api.Operation) Response {
-	return &forwardedOperationResponse{op}
+func ForwardedOperationResponse(project string, op *api.Operation) Response {
+	return &forwardedOperationResponse{
+		op:      op,
+		project: project,
+	}
 }
 
 // Error response
diff --git a/test/main.sh b/test/main.sh
index eb1f02700a..e1f9be3b33 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -227,6 +227,7 @@ run_test test_clustering_publish "clustering publish"
 run_test test_clustering_profiles "clustering profiles"
 run_test test_clustering_join_api "clustering join api"
 run_test test_clustering_shutdown_nodes "clustering shutdown"
+run_test test_clustering_projects "clustering projects"
 #run_test test_clustering_upgrade "clustering upgrade"
 
 # shellcheck disable=SC2034
diff --git a/test/suites/clustering.sh b/test/suites/clustering.sh
index 17af533c07..8377ed2342 100644
--- a/test/suites/clustering.sh
+++ b/test/suites/clustering.sh
@@ -984,3 +984,57 @@ test_clustering_shutdown_nodes() {
   kill_lxd "${LXD_ONE_DIR}"
   kill_lxd "${LXD_THREE_DIR}"
 }
+
+test_clustering_projects() {
+  setup_clustering_bridge
+  prefix="lxd$$"
+  bridge="${prefix}"
+
+  setup_clustering_netns 1
+  LXD_ONE_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+  chmod +x "${LXD_ONE_DIR}"
+  ns1="${prefix}1"
+  spawn_lxd_and_bootstrap_cluster "${ns1}" "${bridge}" "${LXD_ONE_DIR}"
+
+  # Add a newline at the end of each line. YAML as weird rules..
+  cert=$(sed ':a;N;$!ba;s/\n/\n\n/g' "${LXD_ONE_DIR}/server.crt")
+
+  # Spawn a second node
+  setup_clustering_netns 2
+  LXD_TWO_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+  chmod +x "${LXD_TWO_DIR}"
+  ns2="${prefix}2"
+  spawn_lxd_and_join_cluster "${ns2}" "${bridge}" "${cert}" 2 1 "${LXD_TWO_DIR}"
+
+  LXD_DIR="${LXD_TWO_DIR}" ensure_import_testimage
+
+  # Create a test project
+  LXD_DIR="${LXD_ONE_DIR}" lxc project create p1 -c features.images=false
+  LXD_DIR="${LXD_ONE_DIR}" lxc project switch p1
+  LXD_DIR="${LXD_ONE_DIR}" lxc profile device add default root disk path="/" pool="data"
+
+  # Create a container in the project.
+  #LXD_DIR="${LXD_ONE_DIR}" deps/import-busybox --project p1 --alias testimage
+  LXD_DIR="${LXD_ONE_DIR}" lxc init --target node1 testimage c1
+
+  # The container is visible through both nodes
+  LXD_DIR="${LXD_ONE_DIR}" lxc list | grep -q c1
+  LXD_DIR="${LXD_TWO_DIR}" lxc list | grep -q c1
+
+  LXD_DIR="${LXD_ONE_DIR}" lxc delete c1
+  #LXD_DIR="${LXD_ONE_DIR}" lxc image delete testimage
+
+  LXD_DIR="${LXD_ONE_DIR}" lxc project switch default
+
+  LXD_DIR="${LXD_TWO_DIR}" lxd shutdown
+  LXD_DIR="${LXD_ONE_DIR}" lxd shutdown
+  sleep 2
+  rm -f "${LXD_TWO_DIR}/unix.socket"
+  rm -f "${LXD_ONE_DIR}/unix.socket"
+
+  teardown_clustering_netns
+  teardown_clustering_bridge
+
+  kill_lxd "${LXD_ONE_DIR}"
+  kill_lxd "${LXD_TWO_DIR}"
+}

From 36d5b09d1379369c32f21d367a34cbfc86bc93fb Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Mon, 15 Oct 2018 15:00:11 +0200
Subject: [PATCH 2/3] Propagate events about all projects to all cluster nodes

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/api_project.go        | 4 ++++
 lxd/cluster/events.go     | 5 +++++
 lxd/events.go             | 2 +-
 test/suites/clustering.sh | 2 +-
 4 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/lxd/api_project.go b/lxd/api_project.go
index b4f7c25259..0c5d6fc605 100644
--- a/lxd/api_project.go
+++ b/lxd/api_project.go
@@ -84,6 +84,10 @@ func apiProjectsPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("Project names may not contain slashes"))
 	}
 
+	if project.Name == "*" {
+		return BadRequest(fmt.Errorf("Reserved project name"))
+	}
+
 	if shared.StringInSlice(project.Name, []string{".", ".."}) {
 		return BadRequest(fmt.Errorf("Invalid project name '%s'", project.Name))
 	}
diff --git a/lxd/cluster/events.go b/lxd/cluster/events.go
index cd55cbdb51..93cb77a91f 100644
--- a/lxd/cluster/events.go
+++ b/lxd/cluster/events.go
@@ -116,5 +116,10 @@ func eventsConnect(address string, cert *shared.CertInfo) (*lxd.EventListener, e
 	if err != nil {
 		return nil, err
 	}
+
+	// Set the project to the special wildcard in order to get notified
+	// about all events across all projects.
+	client = client.UseProject("*")
+
 	return client.GetEvents()
 }
diff --git a/lxd/events.go b/lxd/events.go
index d553e021eb..be5538db7e 100644
--- a/lxd/events.go
+++ b/lxd/events.go
@@ -148,7 +148,7 @@ func eventBroadcast(event shared.Jmap) error {
 	eventsLock.Lock()
 	listeners := eventListeners
 	for _, listener := range listeners {
-		if event["project"] != "" && event["project"] != listener.project {
+		if event["project"] != "" && listener.project != "*" && event["project"] != listener.project {
 			continue
 		}
 
diff --git a/test/suites/clustering.sh b/test/suites/clustering.sh
index 8377ed2342..c7ab3b82f2 100644
--- a/test/suites/clustering.sh
+++ b/test/suites/clustering.sh
@@ -1015,7 +1015,7 @@ test_clustering_projects() {
 
   # Create a container in the project.
   #LXD_DIR="${LXD_ONE_DIR}" deps/import-busybox --project p1 --alias testimage
-  LXD_DIR="${LXD_ONE_DIR}" lxc init --target node1 testimage c1
+  LXD_DIR="${LXD_ONE_DIR}" lxc init --target node2 testimage c1
 
   # The container is visible through both nodes
   LXD_DIR="${LXD_ONE_DIR}" lxc list | grep -q c1

From 3651ef6ee89bddaec1d5032a18db67f805caf39f Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Mon, 15 Oct 2018 15:51:12 +0200
Subject: [PATCH 3/3] Support creating project-bound container using an image
 on another node

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 client/lxd_images.go      | 12 +++++++-----
 lxd/api_cluster_test.go   |  2 +-
 lxd/container.go          |  6 +++++-
 lxd/db/images.go          |  4 ++--
 test/suites/clustering.sh |  8 +++-----
 5 files changed, 18 insertions(+), 14 deletions(-)

diff --git a/client/lxd_images.go b/client/lxd_images.go
index 43b8237c9c..3b1bd11465 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -78,9 +78,9 @@ func (r *ProtocolLXD) GetPrivateImage(fingerprint string, secret string) (*api.I
 	image := api.Image{}
 
 	// Build the API path
-	path := fmt.Sprintf("/images/%s", url.QueryEscape(fingerprint))
+	path := fmt.Sprintf("/images/%s?project=%s", url.QueryEscape(fingerprint), r.project)
 	if secret != "" {
-		path = fmt.Sprintf("%s?secret=%s", path, url.QueryEscape(secret))
+		path += fmt.Sprintf("&secret=%s", url.QueryEscape(secret))
 	}
 
 	// Fetch the raw value
@@ -99,9 +99,11 @@ func (r *ProtocolLXD) GetPrivateImageFile(fingerprint string, secret string, req
 		return nil, fmt.Errorf("No file requested")
 	}
 
+	uri := fmt.Sprintf("/1.0/images/%s/export?project=%s", url.QueryEscape(fingerprint), r.project)
+
 	// Attempt to download from host
 	if secret == "" && shared.PathExists("/dev/lxd/sock") && os.Geteuid() == 0 {
-		unixURI := fmt.Sprintf("http://unix.socket/1.0/images/%s/export", url.QueryEscape(fingerprint))
+		unixURI := fmt.Sprintf("http://unix.socket%s", uri)
 
 		// Setup the HTTP client
 		devlxdHTTP, err := unixHTTPClient(nil, "/dev/lxd/sock")
@@ -114,9 +116,9 @@ func (r *ProtocolLXD) GetPrivateImageFile(fingerprint string, secret string, req
 	}
 
 	// Build the URL
-	uri := fmt.Sprintf("%s/1.0/images/%s/export", r.httpHost, url.QueryEscape(fingerprint))
+	uri = fmt.Sprintf("%s%s", r.httpHost, uri)
 	if secret != "" {
-		uri = fmt.Sprintf("%s?secret=%s", uri, url.QueryEscape(secret))
+		uri += fmt.Sprintf("&secret=%s", url.QueryEscape(secret))
 	}
 
 	return lxdDownloadImage(fingerprint, uri, r.httpUserAgent, r.http, req)
diff --git a/lxd/api_cluster_test.go b/lxd/api_cluster_test.go
index c33776d500..c284ce3509 100644
--- a/lxd/api_cluster_test.go
+++ b/lxd/api_cluster_test.go
@@ -427,7 +427,7 @@ func TestCluster_LeaveWithImages(t *testing.T) {
 	// If we now associate the image with the other node as well, leaving
 	// the cluster is fine.
 	daemon = daemons[0]
-	err = daemon.State().Cluster.ImageAssociateNode("abc")
+	err = daemon.State().Cluster.ImageAssociateNode("default", "abc")
 	require.NoError(t, err)
 
 	err = client.DeleteClusterMember("rusp-0", false)
diff --git a/lxd/container.go b/lxd/container.go
index f171bb38cc..ba8ac1edbc 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -773,11 +773,15 @@ func containerCreateFromImage(d *Daemon, args db.ContainerArgs, hash string) (co
 		if err != nil {
 			return nil, err
 		}
+
+		client = client.UseProject(args.Project)
+
 		err = imageImportFromNode(filepath.Join(d.os.VarDir, "images"), client, hash)
 		if err != nil {
 			return nil, err
 		}
-		err = d.cluster.ImageAssociateNode(hash)
+
+		err = d.cluster.ImageAssociateNode(args.Project, hash)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxd/db/images.go b/lxd/db/images.go
index 11a8104416..56bd993fde 100644
--- a/lxd/db/images.go
+++ b/lxd/db/images.go
@@ -521,8 +521,8 @@ WHERE images.fingerprint = ?
 
 // ImageAssociateNode creates a new entry in the images_nodes table for
 // tracking that the current node has the given image.
-func (c *Cluster) ImageAssociateNode(fingerprint string) error {
-	imageID, _, err := c.ImageGet("default", fingerprint, false, true)
+func (c *Cluster) ImageAssociateNode(project, fingerprint string) error {
+	imageID, _, err := c.ImageGet(project, fingerprint, false, true)
 	if err != nil {
 		return err
 	}
diff --git a/test/suites/clustering.sh b/test/suites/clustering.sh
index c7ab3b82f2..c3fb76a1a6 100644
--- a/test/suites/clustering.sh
+++ b/test/suites/clustering.sh
@@ -1006,15 +1006,13 @@ test_clustering_projects() {
   ns2="${prefix}2"
   spawn_lxd_and_join_cluster "${ns2}" "${bridge}" "${cert}" 2 1 "${LXD_TWO_DIR}"
 
-  LXD_DIR="${LXD_TWO_DIR}" ensure_import_testimage
-
   # Create a test project
-  LXD_DIR="${LXD_ONE_DIR}" lxc project create p1 -c features.images=false
+  LXD_DIR="${LXD_ONE_DIR}" lxc project create p1
   LXD_DIR="${LXD_ONE_DIR}" lxc project switch p1
   LXD_DIR="${LXD_ONE_DIR}" lxc profile device add default root disk path="/" pool="data"
 
   # Create a container in the project.
-  #LXD_DIR="${LXD_ONE_DIR}" deps/import-busybox --project p1 --alias testimage
+  LXD_DIR="${LXD_ONE_DIR}" deps/import-busybox --project p1 --alias testimage
   LXD_DIR="${LXD_ONE_DIR}" lxc init --target node2 testimage c1
 
   # The container is visible through both nodes
@@ -1022,7 +1020,7 @@ test_clustering_projects() {
   LXD_DIR="${LXD_TWO_DIR}" lxc list | grep -q c1
 
   LXD_DIR="${LXD_ONE_DIR}" lxc delete c1
-  #LXD_DIR="${LXD_ONE_DIR}" lxc image delete testimage
+  LXD_DIR="${LXD_ONE_DIR}" lxc image delete testimage
 
   LXD_DIR="${LXD_ONE_DIR}" lxc project switch default
 


More information about the lxc-devel mailing list