[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