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