[lxc-devel] [lxd/master] Support direct `copy` within a cluster
stgraber on Github
lxc-bot at linuxcontainers.org
Fri Apr 26 23:51:10 UTC 2019
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/20190426/30b4d3ea/attachment.bin>
-------------- next part --------------
From 0f66ec308c2f03a43578e2efff7da306a5c3cb56 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 Apr 2019 19:47:56 -0400
Subject: [PATCH 1/5] client: Fix copy from snapshot
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>
---
client/lxd_containers.go | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index 17210f01b5..eaa3f1fa94 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -1105,8 +1105,32 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, containerNam
}
}
+ sourceInfo, err := source.GetConnectionInfo()
+ if err != nil {
+ return nil, fmt.Errorf("Failed to get source connection info: %v", err)
+ }
+
+ destInfo, err := r.GetConnectionInfo()
+ if err != nil {
+ return nil, fmt.Errorf("Failed to get destination connection info: %v", err)
+ }
+
+ container, _, err := source.GetContainer(cName)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to get container info: %v", err)
+ }
+
// Optimization for the local copy case
- if r == source && r.clusterTarget == "" {
+ if destInfo.URL == sourceInfo.URL && destInfo.SocketPath == sourceInfo.SocketPath && (!r.IsClustered() || container.Location == r.clusterTarget) {
+ // Project handling
+ if destInfo.Project != sourceInfo.Project {
+ if !r.HasExtension("container_copy_project") {
+ return nil, fmt.Errorf("The server is missing the required \"container_copy_project\" API extension")
+ }
+
+ req.Source.Project = sourceInfo.Project
+ }
+
// Local copy source fields
req.Source.Type = "copy"
req.Source.Source = fmt.Sprintf("%s/%s", cName, sName)
From a37a67dd17e214200dfcc4f7b6dc798e5afcf04f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 Apr 2019 19:16:56 -0400
Subject: [PATCH 2/5] lxd/storage/ceph: Fix snapshot of running containers
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/storage_ceph.go | 32 ++++++++++----------------------
1 file changed, 10 insertions(+), 22 deletions(-)
diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index af574e0529..9e0be80504 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -1128,25 +1128,15 @@ func (s *storageCeph) ContainerCopy(target container, source container,
sourceContainerName := source.Name()
logger.Debugf(`Copying RBD container storage %s to %s`, sourceContainerName, target.Name())
- revert := true
-
- ourStart, err := source.StorageStart()
- if err != nil {
- logger.Errorf(`Failed to initialize storage for container "%s": %s`, sourceContainerName, err)
- return err
- }
- logger.Debugf(`Initialized storage for container "%s"`,
- sourceContainerName)
- if ourStart {
- defer source.StorageStop()
- }
-
+ // Handle cross pool copies
_, sourcePool, _ := source.Storage().GetContainerPoolInfo()
_, targetPool, _ := target.Storage().GetContainerPoolInfo()
if sourcePool != targetPool {
return s.doCrossPoolContainerCopy(target, source, containerOnly, false, nil)
}
+ revert := true
+
snapshots, err := source.Snapshots()
if err != nil {
logger.Errorf(`Failed to retrieve snapshots of container "%s": %s`, sourceContainerName, err)
@@ -1370,21 +1360,12 @@ func (s *storageCeph) ContainerCopy(target container, source container,
logger.Debugf(`Copied RBD container storage %s to %s`, sourceContainerName, target.Name())
revert = false
-
return nil
}
func (s *storageCeph) ContainerRefresh(target container, source container, snapshots []container) error {
logger.Debugf(`Refreshing RBD container storage for %s from %s`, target.Name(), source.Name())
- ourStart, err := source.StorageStart()
- if err != nil {
- return err
- }
- if ourStart {
- defer source.StorageStop()
- }
-
return s.doCrossPoolContainerCopy(target, source, len(snapshots) == 0, true, snapshots)
}
@@ -1862,6 +1843,13 @@ func (s *storageCeph) ContainerSnapshotStart(c container) (bool, error) {
containerMntPoint := getSnapshotMountPoint(c.Project(), s.pool.Name, containerName)
RBDFilesystem := s.getRBDFilesystem()
mountFlags, mountOptions := lxdResolveMountoptions(s.getRBDMountOptions())
+ if RBDFilesystem == "xfs" {
+ idx := strings.Index(mountOptions, "nouuid")
+ if idx < 0 {
+ mountOptions += ",nouuid"
+ }
+ }
+
err = tryMount(
RBDDevPath,
containerMntPoint,
From f0b21676b8bd4d9d1d22ececac03e28360419e3a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 Apr 2019 19:16:20 -0400
Subject: [PATCH 3/5] api: Add cluster_internal_copy API extension
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>
---
doc/api-extensions.md | 5 +++++
shared/version/api.go | 1 +
2 files changed, 6 insertions(+)
diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 5b93381a6e..67a93d82c5 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -747,3 +747,8 @@ Adds support for RBAC (role based access control). This introduces new config ke
* rbac.agent.username
* rbac.agent.private\_key
* rbac.agent.public\_key
+
+## cluster\_internal\_copy
+This makes it possible to do a normal "POST /1.0/containers" to copy a
+container between cluster nodes with LXD internally detecting whether a
+migration is required.
diff --git a/shared/version/api.go b/shared/version/api.go
index 8e25895f8e..02546c9005 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -150,6 +150,7 @@ var APIExtensions = []string{
"network_nat_address",
"container_nic_routes",
"rbac",
+ "cluster_internal_copy",
}
// APIExtensionsCount returns the number of available API extensions.
From d901df4c4f3998c189bbe54cedbd4d31f7c8bb2d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 Apr 2019 19:49:06 -0400
Subject: [PATCH 4/5] client: Add support for cluster_internal_copy
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>
---
client/lxd_containers.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index eaa3f1fa94..4351c7f632 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -355,7 +355,7 @@ func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Contai
}
// Optimization for the local copy case
- if destInfo.URL == sourceInfo.URL && destInfo.SocketPath == sourceInfo.SocketPath && (!r.IsClustered() || container.Location == r.clusterTarget) {
+ if destInfo.URL == sourceInfo.URL && destInfo.SocketPath == sourceInfo.SocketPath && (!r.IsClustered() || container.Location == r.clusterTarget || r.HasExtension("cluster_internal_copy")) {
// Project handling
if destInfo.Project != sourceInfo.Project {
if !r.HasExtension("container_copy_project") {
@@ -1121,7 +1121,7 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, containerNam
}
// Optimization for the local copy case
- if destInfo.URL == sourceInfo.URL && destInfo.SocketPath == sourceInfo.SocketPath && (!r.IsClustered() || container.Location == r.clusterTarget) {
+ if destInfo.URL == sourceInfo.URL && destInfo.SocketPath == sourceInfo.SocketPath && (!r.IsClustered() || container.Location == r.clusterTarget || r.HasExtension("cluster_internal_copy")) {
// Project handling
if destInfo.Project != sourceInfo.Project {
if !r.HasExtension("container_copy_project") {
From 3540585adb22b4fcde2a1a8f0850ad0153e1fb6d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 Apr 2019 19:49:53 -0400
Subject: [PATCH 5/5] lxd/containers: Add support for internal cluster copy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Closes #5700
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
lxd/containers_post.go | 243 ++++++++++++++++++++++++++++++++---------
1 file changed, 191 insertions(+), 52 deletions(-)
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 5d6229e36b..c51d85510e 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -231,57 +231,9 @@ func createFromMigration(d *Daemon, project string, req *api.ContainersPost) Res
}
}
- // Grab the container's root device if one is specified
- storagePool := ""
- storagePoolProfile := ""
-
- localRootDiskDeviceKey, localRootDiskDevice, _ := shared.GetRootDiskDevice(req.Devices)
- if localRootDiskDeviceKey != "" {
- storagePool = localRootDiskDevice["pool"]
- }
-
- // Handle copying/moving between two storage-api LXD instances.
- if storagePool != "" {
- _, err := d.cluster.StoragePoolGetID(storagePool)
- if err == db.ErrNoSuchObject {
- storagePool = ""
- // Unset the local root disk device storage pool if not
- // found.
- localRootDiskDevice["pool"] = ""
- }
- }
-
- // If we don't have a valid pool yet, look through profiles
- if storagePool == "" {
- for _, pName := range req.Profiles {
- _, p, err := d.cluster.ProfileGet(project, pName)
- if err != nil {
- return SmartError(err)
- }
-
- k, v, _ := shared.GetRootDiskDevice(p.Devices)
- if k != "" && v["pool"] != "" {
- // Keep going as we want the last one in the profile chain
- storagePool = v["pool"]
- storagePoolProfile = pName
- }
- }
- }
-
- // If there is just a single pool in the database, use that
- if storagePool == "" {
- logger.Debugf("No valid storage pool in the container's local root disk device and profiles found")
- pools, err := d.cluster.StoragePools()
- if err != nil {
- if err == db.ErrNoSuchObject {
- return BadRequest(fmt.Errorf("This LXD instance does not have any storage pools configured"))
- }
- return SmartError(err)
- }
-
- if len(pools) == 1 {
- storagePool = pools[0]
- }
+ storagePool, storagePoolProfile, localRootDiskDeviceKey, localRootDiskDevice, resp := containerFindStoragePool(d, project, req)
+ if resp != nil {
+ return resp
}
if storagePool == "" {
@@ -496,9 +448,56 @@ func createFromCopy(d *Daemon, project string, req *api.ContainersPost) Response
return SmartError(err)
}
+ // Check if we need to redirect to migration
+ clustered, err := cluster.Enabled(d.db)
+ if err != nil {
+ return SmartError(err)
+ }
+
+ // When clustered, use the node name, otherwise use the hostname.
+ if clustered {
+ var serverName string
+ err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
+ serverName, err = tx.NodeName()
+ return err
+ })
+ if err != nil {
+ return SmartError(err)
+ }
+
+ if serverName != source.Location() {
+ // Check if we are copying from a ceph-based container.
+ sourcePoolName, err := d.cluster.ContainerPool(source.Project(), source.Name())
+ if err != nil {
+ err = errors.Wrap(err, "Failed to fetch container's pool name")
+ return SmartError(err)
+ }
+
+ destPoolName, _, _, _, resp := containerFindStoragePool(d, targetProject, req)
+ if resp != nil {
+ return resp
+ }
+
+ if sourcePoolName != destPoolName {
+ // Redirect to migration
+ return clusterCopyContainerInternal(d, source, project, req)
+ }
+
+ _, pool, err := d.cluster.StoragePoolGet(sourcePoolName)
+ if err != nil {
+ err = errors.Wrap(err, "Failed to fetch container's pool info")
+ return SmartError(err)
+ }
+
+ if pool.Driver != "ceph" {
+ // Redirect to migration
+ return clusterCopyContainerInternal(d, source, project, req)
+ }
+ }
+ }
+
// Config override
sourceConfig := source.LocalConfig()
-
if req.Config == nil {
req.Config = make(map[string]string)
}
@@ -795,3 +794,143 @@ func containersPost(d *Daemon, r *http.Request) Response {
return BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
}
}
+
+func containerFindStoragePool(d *Daemon, project string, req *api.ContainersPost) (string, string, string, map[string]string, Response) {
+ // Grab the container's root device if one is specified
+ storagePool := ""
+ storagePoolProfile := ""
+
+ localRootDiskDeviceKey, localRootDiskDevice, _ := shared.GetRootDiskDevice(req.Devices)
+ if localRootDiskDeviceKey != "" {
+ storagePool = localRootDiskDevice["pool"]
+ }
+
+ // Handle copying/moving between two storage-api LXD instances.
+ if storagePool != "" {
+ _, err := d.cluster.StoragePoolGetID(storagePool)
+ if err == db.ErrNoSuchObject {
+ storagePool = ""
+ // Unset the local root disk device storage pool if not
+ // found.
+ localRootDiskDevice["pool"] = ""
+ }
+ }
+
+ // If we don't have a valid pool yet, look through profiles
+ if storagePool == "" {
+ for _, pName := range req.Profiles {
+ _, p, err := d.cluster.ProfileGet(project, pName)
+ if err != nil {
+ return "", "", "", nil, SmartError(err)
+ }
+
+ k, v, _ := shared.GetRootDiskDevice(p.Devices)
+ if k != "" && v["pool"] != "" {
+ // Keep going as we want the last one in the profile chain
+ storagePool = v["pool"]
+ storagePoolProfile = pName
+ }
+ }
+ }
+
+ // If there is just a single pool in the database, use that
+ if storagePool == "" {
+ logger.Debugf("No valid storage pool in the container's local root disk device and profiles found")
+ pools, err := d.cluster.StoragePools()
+ if err != nil {
+ if err == db.ErrNoSuchObject {
+ return "", "", "", nil, BadRequest(fmt.Errorf("This LXD instance does not have any storage pools configured"))
+ }
+ return "", "", "", nil, SmartError(err)
+ }
+
+ if len(pools) == 1 {
+ storagePool = pools[0]
+ }
+ }
+
+ return storagePool, storagePoolProfile, localRootDiskDeviceKey, localRootDiskDevice, nil
+}
+
+func clusterCopyContainerInternal(d *Daemon, source container, project string, req *api.ContainersPost) Response {
+ name := req.Source.Source
+
+ // Locate the source of the container
+ var nodeAddress string
+ err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
+ var err error
+
+ // Load source node.
+ nodeAddress, err = tx.ContainerNodeAddress(project, name)
+ if err != nil {
+ return errors.Wrap(err, "Failed to get address of container's node")
+ }
+
+ return nil
+ })
+ if err != nil {
+ return SmartError(err)
+ }
+
+ if nodeAddress == "" {
+ return BadRequest(fmt.Errorf("The container source is currently offline"))
+ }
+
+ // Connect to the container source
+ client, err := cluster.Connect(nodeAddress, d.endpoints.NetworkCert(), false)
+ if err != nil {
+ return SmartError(err)
+ }
+
+ client = client.UseProject(source.Project())
+
+ // Setup websockets
+ var opAPI api.Operation
+ if shared.IsSnapshot(req.Source.Source) {
+ cName, sName, _ := containerGetParentAndSnapshotName(req.Source.Source)
+
+ pullReq := api.ContainerSnapshotPost{
+ Migration: true,
+ Live: req.Source.Live,
+ Name: req.Name,
+ }
+
+ op, err := client.MigrateContainerSnapshot(cName, sName, pullReq)
+ if err != nil {
+ return SmartError(err)
+ }
+
+ opAPI = op.Get()
+ } else {
+ pullReq := api.ContainerPost{
+ Migration: true,
+ Live: req.Source.Live,
+ ContainerOnly: req.Source.ContainerOnly,
+ Name: req.Name,
+ }
+
+ op, err := client.MigrateContainer(req.Source.Source, pullReq)
+ if err != nil {
+ return SmartError(err)
+ }
+
+ opAPI = op.Get()
+ }
+
+ websockets := map[string]string{}
+ for k, v := range opAPI.Metadata {
+ websockets[k] = v.(string)
+ }
+
+ // Reset the source for a migration
+ req.Source.Type = "migration"
+ req.Source.Certificate = string(d.endpoints.NetworkCert().PublicKey())
+ req.Source.Mode = "pull"
+ req.Source.Operation = fmt.Sprintf("https://%s/1.0/operations/%s", nodeAddress, opAPI.ID)
+ req.Source.Websockets = websockets
+ req.Source.Source = ""
+ req.Source.Project = ""
+
+ // Run the migration
+ return createFromMigration(d, project, req)
+}
More information about the lxc-devel
mailing list