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

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) ===

From efdb5b8120484e7d91c935f830bf9538bc5c8b47 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <[email protected]>
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 <[email protected]>
---
 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?= <[email protected]>
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 <[email protected]>
---
 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
_______________________________________________
lxc-devel mailing list
[email protected]
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to