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
