[lxc-devel] [lxd/master] Container copies
stgraber on Github
lxc-bot at linuxcontainers.org
Thu Jun 15 22:12:07 UTC 2017
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/20170615/050ec6fa/attachment.bin>
-------------- next part --------------
From efdb5b8120484e7d91c935f830bf9538bc5c8b47 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Jun 2017 17:54:06 -0400
Subject: [PATCH 1/2] client: Fail if source isn't listening on network
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This checks that the source image server lists at least one address for
network connections.
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
client/lxd_images.go | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/client/lxd_images.go b/client/lxd_images.go
index f9065e3e5..edc7a4530 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -443,6 +443,10 @@ func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (
// tryCopyImage iterates through the source server URLs until one lets it download the image
func (r *ProtocolLXD) tryCopyImage(req api.ImagesPost, urls []string) (*RemoteOperation, error) {
+ if len(urls) == 0 {
+ return nil, fmt.Errorf("The source server isn't listening on the network")
+ }
+
rop := RemoteOperation{
chDone: make(chan bool),
}
From ff19d6afcd1a2ed9632a488c4902d272a42ccdb6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Jun 2017 18:02:10 -0400
Subject: [PATCH 2/2] client: Implement container and snapshot 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/interfaces.go | 20 +++++
client/lxd_containers.go | 186 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 203 insertions(+), 3 deletions(-)
diff --git a/client/interfaces.go b/client/interfaces.go
index e5541564d..1d76196b5 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -57,6 +57,7 @@ type ContainerServer interface {
GetContainer(name string) (container *api.Container, ETag string, err error)
CreateContainer(container api.ContainersPost) (op *Operation, err error)
CreateContainerFromImage(source ImageServer, image api.Image, imgcontainer api.ContainersPost) (op *RemoteOperation, err error)
+ CopyContainer(source ContainerServer, container api.Container, args *ContainerCopyArgs) (op *RemoteOperation, err error)
UpdateContainer(name string, container api.ContainerPut, ETag string) (op *Operation, err error)
RenameContainer(name string, container api.ContainerPost) (op *Operation, err error)
MigrateContainer(name string, container api.ContainerPost) (op *Operation, err error)
@@ -72,6 +73,7 @@ type ContainerServer interface {
GetContainerSnapshots(containerName string) (snapshots []api.ContainerSnapshot, err error)
GetContainerSnapshot(containerName string, name string) (snapshot *api.ContainerSnapshot, ETag string, err error)
CreateContainerSnapshot(containerName string, snapshot api.ContainerSnapshotsPost) (op *Operation, err error)
+ CopyContainerSnapshot(source ContainerServer, snapshot api.ContainerSnapshot, args *ContainerSnapshotCopyArgs) (op *RemoteOperation, err error)
RenameContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op *Operation, err error)
MigrateContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op *Operation, err error)
DeleteContainerSnapshot(containerName string, name string) (op *Operation, err error)
@@ -224,6 +226,24 @@ type ImageCopyArgs struct {
Public bool
}
+// The ContainerCopyArgs struct is used to pass additional options during container copy
+type ContainerCopyArgs struct {
+ // If set, the container will be renamed on copy
+ Name string
+
+ // If set, the container running state will be transferred (live migration)
+ Live bool
+
+ // If set, only the container will copied, its snapshots won't
+ ContainerOnly bool
+}
+
+// The ContainerSnapshotCopyArgs struct is used to pass additional options during container copy
+type ContainerSnapshotCopyArgs struct {
+ // If set, the container will be renamed on copy
+ Name string
+}
+
// The ContainerExecArgs struct is used to pass additional options during container exec
type ContainerExecArgs struct {
// Standard input
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index ead43a04a..4f1b6c402 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -78,17 +78,27 @@ func (r *ProtocolLXD) CreateContainer(container api.ContainersPost) (*Operation,
return op, nil
}
-func (r *ProtocolLXD) tryCreateContainerFromImage(req api.ContainersPost, urls []string) (*RemoteOperation, error) {
+func (r *ProtocolLXD) tryCreateContainer(req api.ContainersPost, urls []string) (*RemoteOperation, error) {
+ if len(urls) == 0 {
+ return nil, fmt.Errorf("The source server isn't listening on the network")
+ }
+
rop := RemoteOperation{
chDone: make(chan bool),
}
+ operation := req.Source.Operation
+
// Forward targetOp to remote op
go func() {
success := false
errors := []string{}
for _, serverURL := range urls {
- req.Source.Server = serverURL
+ if operation == "" {
+ req.Source.Server = serverURL
+ } else {
+ req.Source.Operation = fmt.Sprintf("%s/1.0/operations/%s", serverURL, operation)
+ }
op, err := r.CreateContainer(req)
if err != nil {
@@ -182,7 +192,92 @@ func (r *ProtocolLXD) CreateContainerFromImage(source ImageServer, image api.Ima
req.Source.Secret = secret
}
- return r.tryCreateContainerFromImage(req, info.Addresses)
+ return r.tryCreateContainer(req, info.Addresses)
+}
+
+// CopyContainer copies a container from a remote server. Additional options can be passed using ContainerCopyArgs
+func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Container, args *ContainerCopyArgs) (*RemoteOperation, error) {
+ // Base request
+ req := api.ContainersPost{
+ Name: container.Name,
+ ContainerPut: container.Writable(),
+ }
+ req.Source.BaseImage = container.Config["volatile.base_image"]
+ req.Source.Live = container.StatusCode == api.Running
+
+ // Process the copy arguments
+ if args != nil {
+ // Sanity checks
+ if args.ContainerOnly && !r.HasExtension("container_only_migration") {
+ return nil, fmt.Errorf("The server is missing the required \"container_only_migration\" API extension")
+ }
+
+ // Allow overriding the target name
+ if args.Name != "" {
+ req.Name = args.Name
+ }
+
+ req.Source.Live = args.Live
+ req.Source.ContainerOnly = args.ContainerOnly
+ }
+
+ // Optimization for the local copy case
+ if r == source {
+ // Local copy source fields
+ req.Source.Type = "copy"
+ req.Source.Source = container.Name
+
+ // Copy the container
+ op, err := r.CreateContainer(req)
+ if err != nil {
+ return nil, err
+ }
+
+ rop := RemoteOperation{
+ targetOp: op,
+ chDone: make(chan bool),
+ }
+
+ // Forward targetOp to remote op
+ go func() {
+ rop.err = rop.targetOp.Wait()
+ close(rop.chDone)
+ }()
+
+ return &rop, nil
+ }
+
+ // Get source server connection information
+ info, err := source.GetConnectionInfo()
+ if err != nil {
+ return nil, err
+ }
+
+ // Get a source operation
+ sourceReq := api.ContainerPost{
+ Migration: true,
+ Live: req.Source.Live,
+ ContainerOnly: req.Source.ContainerOnly,
+ }
+
+ op, err := source.MigrateContainer(container.Name, sourceReq)
+ if err != nil {
+ return nil, err
+ }
+
+ sourceSecrets := map[string]string{}
+ for k, v := range op.Metadata {
+ sourceSecrets[k] = v.(string)
+ }
+
+ // Migration source fields
+ req.Source.Type = "migration"
+ req.Source.Mode = "pull"
+ req.Source.Operation = op.ID
+ req.Source.Websockets = sourceSecrets
+ req.Source.Certificate = info.Certificate
+
+ return r.tryCreateContainer(req, info.Addresses)
}
// UpdateContainer updates the container definition
@@ -540,6 +635,91 @@ func (r *ProtocolLXD) CreateContainerSnapshot(containerName string, snapshot api
return op, nil
}
+// CopyContainerSnapshot copies a snapshot from a remote server into a new container. Additional options can be passed using ContainerCopyArgs
+func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api.ContainerSnapshot, args *ContainerSnapshotCopyArgs) (*RemoteOperation, error) {
+ // Base request
+ fields := strings.SplitN(snapshot.Name, shared.SnapshotDelimiter, 2)
+ cName := fields[0]
+ sName := fields[1]
+
+ req := api.ContainersPost{
+ Name: cName,
+ ContainerPut: api.ContainerPut{
+ Architecture: snapshot.Architecture,
+ Config: snapshot.Config,
+ Devices: snapshot.Devices,
+ Ephemeral: snapshot.Ephemeral,
+ Profiles: snapshot.Profiles,
+ Stateful: snapshot.Stateful,
+ },
+ }
+ req.Source.BaseImage = snapshot.Config["volatile.base_image"]
+
+ // Process the copy arguments
+ if args != nil {
+ // Allow overriding the target name
+ if args.Name != "" {
+ req.Name = args.Name
+ }
+ }
+
+ // Optimization for the local copy case
+ if r == source {
+ // Local copy source fields
+ req.Source.Type = "copy"
+ req.Source.Source = snapshot.Name
+
+ // Copy the container
+ op, err := r.CreateContainer(req)
+ if err != nil {
+ return nil, err
+ }
+
+ rop := RemoteOperation{
+ targetOp: op,
+ chDone: make(chan bool),
+ }
+
+ // Forward targetOp to remote op
+ go func() {
+ rop.err = rop.targetOp.Wait()
+ close(rop.chDone)
+ }()
+
+ return &rop, nil
+ }
+
+ // Get source server connection information
+ info, err := source.GetConnectionInfo()
+ if err != nil {
+ return nil, err
+ }
+
+ // Get a source operation
+ sourceReq := api.ContainerSnapshotPost{
+ Migration: true,
+ }
+
+ op, err := source.MigrateContainerSnapshot(cName, sName, sourceReq)
+ if err != nil {
+ return nil, err
+ }
+
+ sourceSecrets := map[string]string{}
+ for k, v := range op.Metadata {
+ sourceSecrets[k] = v.(string)
+ }
+
+ // Migration source fields
+ req.Source.Type = "migration"
+ req.Source.Mode = "pull"
+ req.Source.Operation = op.ID
+ req.Source.Websockets = sourceSecrets
+ req.Source.Certificate = info.Certificate
+
+ return r.tryCreateContainer(req, info.Addresses)
+}
+
// RenameContainerSnapshot requests that LXD renames the snapshot
func (r *ProtocolLXD) RenameContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (*Operation, error) {
// Sanity check
More information about the lxc-devel
mailing list