The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/5167

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
This fixes #5163 and also a couple of other bugs that I've noticed while working on it:

- it was not possible to create a project-bound container on a node using a client connected to another node

- images were not transferred correctly between nodes, when bound to projects

There are likely other bugs around projects when used with clustering, but this is a good first pass.
From b8d35a02146e163c7c44adce21824931ec6fa658 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanay...@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.ekanay...@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.ekanay...@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.ekanay...@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.ekanay...@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.ekanay...@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
 
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to