[lxc-devel] [lxd/master] lxd: Switch over to instance types
tomponline on Github
lxc-bot at linuxcontainers.org
Fri Sep 13 13:22:37 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/20190913/6fe70672/attachment-0001.bin>
-------------- next part --------------
From 78a8703a0829f917d2a5b84f5e167459e3489cb5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 13 Sep 2019 10:44:07 +0100
Subject: [PATCH 1/5] shared/api/instance: Adds InstanceOnly field to
InstancePost and InstanceSource
- Deprecates ContainerOnly field.
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
shared/api/instance.go | 6 ++++--
shared/api/instance_backup.go | 6 ++++--
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/shared/api/instance.go b/shared/api/instance.go
index 7806b71adf..f94fbc7501 100644
--- a/shared/api/instance.go
+++ b/shared/api/instance.go
@@ -32,7 +32,8 @@ type InstancePost struct {
Name string `json:"name" yaml:"name"`
Migration bool `json:"migration" yaml:"migration"`
Live bool `json:"live" yaml:"live"`
- ContainerOnly bool `json:"container_only" yaml:"container_only"`
+ InstanceOnly bool `json:"instance_only" yaml:"instance_only"`
+ ContainerOnly bool `json:"container_only" yaml:"container_only"` // Deprecated, use InstanceOnly.
Target *InstancePostTarget `json:"target" yaml:"target"`
}
@@ -126,7 +127,8 @@ type InstanceSource struct {
Websockets map[string]string `json:"secrets,omitempty" yaml:"secrets,omitempty"`
Source string `json:"source,omitempty" yaml:"source,omitempty"`
Live bool `json:"live,omitempty" yaml:"live,omitempty"`
- ContainerOnly bool `json:"container_only,omitempty" yaml:"container_only,omitempty"`
+ InstanceOnly bool `json:"instance_only,omitempty" yaml:"instance_only,omitempty"`
+ ContainerOnly bool `json:"container_only,omitempty" yaml:"container_only,omitempty"` // Deprecated, use InstanceOnly.
Refresh bool `json:"refresh,omitempty" yaml:"refresh,omitempty"`
Project string `json:"project,omitempty" yaml:"project,omitempty"`
}
diff --git a/shared/api/instance_backup.go b/shared/api/instance_backup.go
index 64b282337b..afd143c982 100644
--- a/shared/api/instance_backup.go
+++ b/shared/api/instance_backup.go
@@ -8,7 +8,8 @@ import "time"
type InstanceBackupsPost struct {
Name string `json:"name" yaml:"name"`
ExpiresAt time.Time `json:"expires_at" yaml:"expires_at"`
- ContainerOnly bool `json:"container_only" yaml:"container_only"`
+ InstanceOnly bool `json:"instance_only" yaml:"instance_only"`
+ ContainerOnly bool `json:"container_only" yaml:"container_only"` // Deprecated, use InstanceOnly.
OptimizedStorage bool `json:"optimized_storage" yaml:"optimized_storage"`
}
@@ -19,7 +20,8 @@ type InstanceBackup struct {
Name string `json:"name" yaml:"name"`
CreatedAt time.Time `json:"created_at" yaml:"created_at"`
ExpiresAt time.Time `json:"expires_at" yaml:"expires_at"`
- ContainerOnly bool `json:"container_only" yaml:"container_only"`
+ InstanceOnly bool `json:"instance_only" yaml:"instance_only"`
+ ContainerOnly bool `json:"container_only" yaml:"container_only"` // Deprecated, use InstanceOnly.
OptimizedStorage bool `json:"optimized_storage" yaml:"optimized_storage"`
}
From 5c8d3898510ba2cc982f821f453c312dfaf560dd Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 12 Sep 2019 15:46:44 +0100
Subject: [PATCH 2/5] client/interfaces: Populates InstanceServer with rest of
functions
- Also adds structs used for arguments to the Instance functions.
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
client/interfaces.go | 191 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 182 insertions(+), 9 deletions(-)
diff --git a/client/interfaces.go b/client/interfaces.go
index 2fe2e1aadb..a80428f7de 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -136,6 +136,63 @@ type InstanceServer interface {
UpdateContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker) (err error)
DeleteContainerTemplateFile(name string, templateName string) (err error)
+ // Instance functions.
+ GetInstanceNames(instanceType api.InstanceType) (names []string, err error)
+ GetInstances(instanceType api.InstanceType) (instances []api.Instance, err error)
+ GetInstancesFull(instanceType api.InstanceType) (instances []api.InstanceFull, err error)
+ GetInstance(name string) (instance *api.Instance, ETag string, err error)
+ CreateInstance(instance api.InstancesPost) (op Operation, err error)
+ CreateInstanceFromImage(source ImageServer, image api.Image, imgcontainer api.InstancesPost) (op RemoteOperation, err error)
+ CopyInstance(source InstanceServer, instance api.Instance, args *InstanceCopyArgs) (op RemoteOperation, err error)
+ UpdateInstance(name string, instance api.InstancePut, ETag string) (op Operation, err error)
+ RenameInstance(name string, instance api.InstancePost) (op Operation, err error)
+ MigrateInstance(name string, instance api.InstancePost) (op Operation, err error)
+ DeleteInstance(name string) (op Operation, err error)
+
+ ExecInstance(instanceName string, exec api.InstanceExecPost, args *InstanceExecArgs) (op Operation, err error)
+ ConsoleInstance(instanceName string, console api.InstanceConsolePost, args *InstanceConsoleArgs) (op Operation, err error)
+ GetInstanceConsoleLog(instanceName string, args *InstanceConsoleLogArgs) (content io.ReadCloser, err error)
+ DeleteInstanceConsoleLog(instanceName string, args *InstanceConsoleLogArgs) (err error)
+
+ GetInstanceFile(instanceName string, path string) (content io.ReadCloser, resp *InstanceFileResponse, err error)
+ CreateInstanceFile(instanceName string, path string, args InstanceFileArgs) (err error)
+ DeleteInstanceFile(instanceName string, path string) (err error)
+
+ GetInstanceSnapshotNames(instanceName string) (names []string, err error)
+ GetInstanceSnapshots(instanceName string) (snapshots []api.InstanceSnapshot, err error)
+ GetInstanceSnapshot(instanceName string, name string) (snapshot *api.InstanceSnapshot, ETag string, err error)
+ CreateInstanceSnapshot(instanceName string, snapshot api.InstanceSnapshotsPost) (op Operation, err error)
+ CopyInstanceSnapshot(source InstanceServer, instanceName string, snapshot api.InstanceSnapshot, args *InstanceSnapshotCopyArgs) (op RemoteOperation, err error)
+ RenameInstanceSnapshot(instanceName string, name string, instance api.InstanceSnapshotPost) (op Operation, err error)
+ MigrateInstanceSnapshot(instanceName string, name string, instance api.InstanceSnapshotPost) (op Operation, err error)
+ DeleteInstanceSnapshot(instanceName string, name string) (op Operation, err error)
+ UpdateInstanceSnapshot(instanceName string, name string, instance api.InstanceSnapshotPut, ETag string) (op Operation, err error)
+
+ GetInstanceBackupNames(instanceName string) (names []string, err error)
+ GetInstanceBackups(instanceName string) (backups []api.InstanceBackup, err error)
+ GetInstanceBackup(instanceName string, name string) (backup *api.InstanceBackup, ETag string, err error)
+ CreateInstanceBackup(instanceName string, backup api.InstanceBackupsPost) (op Operation, err error)
+ RenameInstanceBackup(instanceName string, name string, backup api.InstanceBackupPost) (op Operation, err error)
+ DeleteInstanceBackup(instanceName string, name string) (op Operation, err error)
+ GetInstanceBackupFile(instanceName string, name string, req *BackupFileRequest) (resp *BackupFileResponse, err error)
+ CreateInstanceFromBackup(args InstanceBackupArgs) (op Operation, err error)
+
+ GetInstanceState(name string) (state *api.InstanceState, ETag string, err error)
+ UpdateInstanceState(name string, state api.InstanceStatePut, ETag string) (op Operation, err error)
+
+ GetInstanceLogfiles(name string) (logfiles []string, err error)
+ GetInstanceLogfile(name string, filename string) (content io.ReadCloser, err error)
+ DeleteInstanceLogfile(name string, filename string) (err error)
+
+ GetInstanceMetadata(name string) (metadata *api.ImageMetadata, ETag string, err error)
+ SetInstanceMetadata(name string, metadata api.ImageMetadata, ETag string) (err error)
+
+ GetInstanceTemplateFiles(instanceName string) (templates []string, err error)
+ GetInstanceTemplateFile(instanceName string, templateName string) (content io.ReadCloser, err error)
+ CreateInstanceTemplateFile(instanceName string, templateName string, content io.ReadSeeker) (err error)
+ UpdateInstanceTemplateFile(instanceName string, templateName string, content io.ReadSeeker) (err error)
+ DeleteInstanceTemplateFile(name string, templateName string) (err error)
+
// Event handling functions
GetEvents() (listener *EventListener, err error)
@@ -233,7 +290,7 @@ type InstanceServer interface {
RawOperation(method string, path string, data interface{}, queryETag string) (op Operation, ETag string, err error)
}
-// The ConnectionInfo struct represents general information for a connection
+// The ConnectionInfo struct represents general information for a connection.
type ConnectionInfo struct {
Addresses []string
Certificate string
@@ -243,7 +300,7 @@ type ConnectionInfo struct {
Project string
}
-// The BackupFileRequest struct is used for a backup download request
+// The BackupFileRequest struct is used for a backup download request.
type BackupFileRequest struct {
// Writer for the backup file
BackupFile io.WriteSeeker
@@ -255,13 +312,13 @@ type BackupFileRequest struct {
Canceler *cancel.Canceler
}
-// The BackupFileResponse struct is used as the response for backup downloads
+// The BackupFileResponse struct is used as the response for backup downloads.
type BackupFileResponse struct {
// Size of backup file
Size int64
}
-// The ImageCreateArgs struct is used for direct image upload
+// The ImageCreateArgs struct is used for direct image upload.
type ImageCreateArgs struct {
// Reader for the meta file
MetaFile io.Reader
@@ -279,7 +336,7 @@ type ImageCreateArgs struct {
ProgressHandler func(progress ioprogress.ProgressData)
}
-// The ImageFileRequest struct is used for an image download request
+// The ImageFileRequest struct is used for an image download request.
type ImageFileRequest struct {
// Writer for the metadata file
MetaFile io.WriteSeeker
@@ -298,7 +355,7 @@ type ImageFileRequest struct {
DeltaSourceRetriever func(fingerprint string, file string) string
}
-// The ImageFileResponse struct is used as the response for image downloads
+// The ImageFileResponse struct is used as the response for image downloads.
type ImageFileResponse struct {
// Filename for the metadata file
MetaName string
@@ -313,7 +370,7 @@ type ImageFileResponse struct {
RootfsSize int64
}
-// The ImageCopyArgs struct is used to pass additional options during image copy
+// The ImageCopyArgs struct is used to pass additional options during image copy.
type ImageCopyArgs struct {
// Aliases to add to the copied image.
Aliases []api.ImageAlias
@@ -329,7 +386,7 @@ type ImageCopyArgs struct {
}
// The StoragePoolVolumeCopyArgs struct is used to pass additional options
-// during storage volume copy
+// during storage volume copy.
type StoragePoolVolumeCopyArgs struct {
// New name for the target
Name string
@@ -342,7 +399,123 @@ type StoragePoolVolumeCopyArgs struct {
}
// The StoragePoolVolumeMoveArgs struct is used to pass additional options
-// during storage volume move
+// during storage volume move.
type StoragePoolVolumeMoveArgs struct {
StoragePoolVolumeCopyArgs
}
+
+// The InstanceBackupArgs struct is used when creating a instance from a backup.
+type InstanceBackupArgs struct {
+ // The backup file
+ BackupFile io.Reader
+
+ // Storage pool to use
+ PoolName string
+}
+
+// The InstanceCopyArgs struct is used to pass additional options during instance copy.
+type InstanceCopyArgs struct {
+ // If set, the instance will be renamed on copy
+ Name string
+
+ // If set, the instance running state will be transferred (live migration)
+ Live bool
+
+ // If set, only the instance will copied, its snapshots won't
+ InstanceOnly bool
+
+ // The transfer mode, can be "pull" (default), "push" or "relay"
+ Mode string
+
+ // API extension: container_incremental_copy
+ // Perform an incremental copy
+ Refresh bool
+}
+
+// The InstanceSnapshotCopyArgs struct is used to pass additional options during instance copy.
+type InstanceSnapshotCopyArgs struct {
+ // If set, the instance will be renamed on copy
+ Name string
+
+ // The transfer mode, can be "pull" (default), "push" or "relay"
+ Mode string
+
+ // API extension: container_snapshot_stateful_migration
+ // If set, the instance running state will be transferred (live migration)
+ Live bool
+}
+
+// The InstanceConsoleArgs struct is used to pass additional options during a
+// instance console session.
+type InstanceConsoleArgs struct {
+ // Bidirectional fd to pass to the instance
+ Terminal io.ReadWriteCloser
+
+ // Control message handler (window resize)
+ Control func(conn *websocket.Conn)
+
+ // Closing this Channel causes a disconnect from the instance's console
+ ConsoleDisconnect chan bool
+}
+
+// The InstanceConsoleLogArgs struct is used to pass additional options during a
+// instance console log request.
+type InstanceConsoleLogArgs struct {
+}
+
+// The InstanceExecArgs struct is used to pass additional options during instance exec.
+type InstanceExecArgs struct {
+ // Standard input
+ Stdin io.ReadCloser
+
+ // Standard output
+ Stdout io.WriteCloser
+
+ // Standard error
+ Stderr io.WriteCloser
+
+ // Control message handler (window resize, signals, ...)
+ Control func(conn *websocket.Conn)
+
+ // Channel that will be closed when all data operations are done
+ DataDone chan bool
+}
+
+// The InstanceFileArgs struct is used to pass the various options for a instance file upload.
+type InstanceFileArgs struct {
+ // File content
+ Content io.ReadSeeker
+
+ // User id that owns the file
+ UID int64
+
+ // Group id that owns the file
+ GID int64
+
+ // File permissions
+ Mode int
+
+ // File type (file or directory)
+ Type string
+
+ // File write mode (overwrite or append)
+ WriteMode string
+}
+
+// The InstanceFileResponse struct is used as part of the response for a instance file download.
+type InstanceFileResponse struct {
+ // User id that owns the file
+ UID int64
+
+ // Group id that owns the file
+ GID int64
+
+ // File permissions
+ Mode int
+
+ // File type (file or directory)
+ Type string
+
+ // If a directory, the list of files inside it
+ Entries []string
+}
From 2f56959aadff63472b949130106b44582de39b2a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 12 Sep 2019 15:50:34 +0100
Subject: [PATCH 3/5] client/lxd/instances: Adds instance related functions
These functions use the /1.0/instances endpoints on the server.
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
client/lxd_instances.go | 2156 +++++++++++++++++++++++++++++++++++++++
1 file changed, 2156 insertions(+)
create mode 100644 client/lxd_instances.go
diff --git a/client/lxd_instances.go b/client/lxd_instances.go
new file mode 100644
index 0000000000..07228f54bb
--- /dev/null
+++ b/client/lxd_instances.go
@@ -0,0 +1,2156 @@
+package lxd
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "github.com/gorilla/websocket"
+
+ "github.com/lxc/lxd/shared"
+ "github.com/lxc/lxd/shared/api"
+ "github.com/lxc/lxd/shared/cancel"
+ "github.com/lxc/lxd/shared/ioprogress"
+ "github.com/lxc/lxd/shared/units"
+)
+
+// Instance handling functions.
+
+// instanceTypeToPath converts the instance type to a URL path prefix and query string values.
+// If the remote server doesn't have the instances extension then the /containers endpoint is used
+// as long as the requested instanceType is any or container.
+func (r *ProtocolLXD) instanceTypeToPath(instanceType api.InstanceType) (string, url.Values, error) {
+ v := url.Values{}
+
+ // If the remote server doesn't support instances extension, check that only containers
+ // or any type has been requested and then fallback to using the old /containers endpoint.
+ if !r.HasExtension("instances") {
+ if instanceType == api.InstanceTypeContainer || instanceType == api.InstanceTypeAny {
+ return "/containers", v, nil
+ }
+
+ return "", v, fmt.Errorf("Requested instance type not supported by server")
+ }
+
+ // If a specific instance type has been requested, add the instance-type filter parameter
+ // to the returned URL values so that it can be used in the final URL if needed to filter
+ // the result set being returned.
+ if instanceType != api.InstanceTypeAny {
+ v.Set("instance-type", string(instanceType))
+ }
+
+ return "/instances", v, nil
+}
+
+// GetInstanceNames returns a list of instance names.
+func (r *ProtocolLXD) GetInstanceNames(instanceType api.InstanceType) ([]string, error) {
+ urls := []string{}
+
+ path, v, err := r.instanceTypeToPath(instanceType)
+ if err != nil {
+ return nil, err
+ }
+
+ // Fetch the raw value
+ _, err = r.queryStruct("GET", fmt.Sprintf("%s?%s", path, v.Encode()), nil, "", &urls)
+ if err != nil {
+ return nil, err
+ }
+
+ // Parse it
+ names := []string{}
+ prefix := path + "/"
+ for _, url := range urls {
+ fields := strings.Split(url, prefix)
+ names = append(names, fields[len(fields)-1])
+ }
+
+ return names, nil
+}
+
+// GetInstances returns a list of instances.
+func (r *ProtocolLXD) GetInstances(instanceType api.InstanceType) ([]api.Instance, error) {
+ instances := []api.Instance{}
+
+ path, v, err := r.instanceTypeToPath(instanceType)
+ if err != nil {
+ return nil, err
+ }
+
+ v.Set("recursion", "1")
+
+ // Fetch the raw value
+ _, err = r.queryStruct("GET", fmt.Sprintf("%s?%s", path, v.Encode()), nil, "", &instances)
+ if err != nil {
+ return nil, err
+ }
+
+ return instances, nil
+}
+
+// GetInstancesFull returns a list of instances including snapshots, backups and state.
+func (r *ProtocolLXD) GetInstancesFull(instanceType api.InstanceType) ([]api.InstanceFull, error) {
+ instances := []api.InstanceFull{}
+
+ path, v, err := r.instanceTypeToPath(instanceType)
+ if err != nil {
+ return nil, err
+ }
+
+ v.Set("recursion", "2")
+
+ if !r.HasExtension("container_full") {
+ return nil, fmt.Errorf("The server is missing the required \"container_full\" API extension")
+ }
+
+ // Fetch the raw value
+ _, err = r.queryStruct("GET", fmt.Sprintf("%s?%s", path, v.Encode()), nil, "", &instances)
+ if err != nil {
+ return nil, err
+ }
+
+ return instances, nil
+}
+
+// GetInstance returns the instance entry for the provided name.
+func (r *ProtocolLXD) GetInstance(name string) (*api.Instance, string, error) {
+ instance := api.Instance{}
+
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, "", err
+ }
+
+ // Fetch the raw value
+ etag, err := r.queryStruct("GET", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), nil, "", &instance)
+ if err != nil {
+ return nil, "", err
+ }
+
+ return &instance, etag, nil
+}
+
+// CreateInstanceFromBackup is a convenience function to make it easier to
+// create a instance from a backup
+func (r *ProtocolLXD) CreateInstanceFromBackup(args InstanceBackupArgs) (Operation, error) {
+ if !r.HasExtension("container_backup") {
+ return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+ }
+
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if args.PoolName == "" {
+ // Send the request
+ op, _, err := r.queryOperation("POST", path, args.BackupFile, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+ }
+
+ if !r.HasExtension("container_backup_override_pool") {
+ return nil, fmt.Errorf("The server is missing the required \"container_backup_override_pool\" API extension")
+ }
+
+ // Prepare the HTTP request
+ reqURL, err := r.setQueryAttributes(fmt.Sprintf("%s/1.0%s", r.httpHost, path))
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("POST", reqURL, args.BackupFile)
+ if err != nil {
+ return nil, err
+ }
+
+ req.Header.Set("Content-Type", "application/octet-stream")
+ req.Header.Set("X-LXD-pool", args.PoolName)
+
+ // Set the user agent
+ if r.httpUserAgent != "" {
+ req.Header.Set("User-Agent", r.httpUserAgent)
+ }
+
+ // Send the request
+ resp, err := r.do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ // Handle errors
+ response, _, err := lxdParseResponse(resp)
+ if err != nil {
+ return nil, err
+ }
+
+ // Get to the operation
+ respOperation, err := response.MetadataAsOperation()
+ if err != nil {
+ return nil, err
+ }
+
+ // Setup an Operation wrapper
+ op := operation{
+ Operation: *respOperation,
+ r: r,
+ chActive: make(chan bool),
+ }
+
+ return &op, nil
+}
+
+// CreateInstance requests that LXD creates a new instance.
+func (r *ProtocolLXD) CreateInstance(instance api.InstancesPost) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(instance.Type)
+ if err != nil {
+ return nil, err
+ }
+
+ if instance.Source.InstanceOnly || instance.Source.ContainerOnly {
+ if !r.HasExtension("container_only_migration") {
+ return nil, fmt.Errorf("The server is missing the required \"container_only_migration\" API extension")
+ }
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("POST", path, instance, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+func (r *ProtocolLXD) tryCreateInstance(req api.InstancesPost, 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 := map[string]error{}
+ for _, serverURL := range urls {
+ if operation == "" {
+ req.Source.Server = serverURL
+ } else {
+ req.Source.Operation = fmt.Sprintf("%s/1.0/operations/%s", serverURL, url.PathEscape(operation))
+ }
+
+ op, err := r.CreateInstance(req)
+ if err != nil {
+ errors[serverURL] = err
+ continue
+ }
+
+ rop.targetOp = op
+
+ for _, handler := range rop.handlers {
+ rop.targetOp.AddHandler(handler)
+ }
+
+ err = rop.targetOp.Wait()
+ if err != nil {
+ errors[serverURL] = err
+ continue
+ }
+
+ success = true
+ break
+ }
+
+ if !success {
+ rop.err = remoteOperationError("Failed instance creation", errors)
+ }
+
+ close(rop.chDone)
+ }()
+
+ return &rop, nil
+}
+
+// CreateInstanceFromImage is a convenience function to make it easier to create a instance from an existing image.
+func (r *ProtocolLXD) CreateInstanceFromImage(source ImageServer, image api.Image, req api.InstancesPost) (RemoteOperation, error) {
+ // Set the minimal source fields
+ req.Source.Type = "image"
+
+ // Optimization for the local image case
+ if r == source {
+ // Always use fingerprints for local case
+ req.Source.Fingerprint = image.Fingerprint
+ req.Source.Alias = ""
+
+ op, err := r.CreateInstance(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
+ }
+
+ // Minimal source fields for remote image
+ req.Source.Mode = "pull"
+
+ // If we have an alias and the image is public, use that
+ if req.Source.Alias != "" && image.Public {
+ req.Source.Fingerprint = ""
+ } else {
+ req.Source.Fingerprint = image.Fingerprint
+ req.Source.Alias = ""
+ }
+
+ // Get source server connection information
+ info, err := source.GetConnectionInfo()
+ if err != nil {
+ return nil, err
+ }
+
+ req.Source.Protocol = info.Protocol
+ req.Source.Certificate = info.Certificate
+
+ // Generate secret token if needed
+ if !image.Public {
+ secret, err := source.GetImageSecret(image.Fingerprint)
+ if err != nil {
+ return nil, err
+ }
+
+ req.Source.Secret = secret
+ }
+
+ return r.tryCreateInstance(req, info.Addresses)
+}
+
+// CopyInstance copies a instance from a remote server. Additional options can be passed using InstanceCopyArgs.
+func (r *ProtocolLXD) CopyInstance(source InstanceServer, instance api.Instance, args *InstanceCopyArgs) (RemoteOperation, error) {
+ // Base request
+ req := api.InstancesPost{
+ Name: instance.Name,
+ InstancePut: instance.Writable(),
+ }
+ req.Source.BaseImage = instance.Config["volatile.base_image"]
+
+ // Process the copy arguments
+ if args != nil {
+ // Sanity checks
+ if args.InstanceOnly {
+ if !r.HasExtension("container_only_migration") {
+ return nil, fmt.Errorf("The target server is missing the required \"container_only_migration\" API extension")
+ }
+
+ if !source.HasExtension("container_only_migration") {
+ return nil, fmt.Errorf("The source server is missing the required \"container_only_migration\" API extension")
+ }
+ }
+
+ if shared.StringInSlice(args.Mode, []string{"push", "relay"}) {
+ if !r.HasExtension("container_push") {
+ return nil, fmt.Errorf("The target server is missing the required \"container_push\" API extension")
+ }
+
+ if !source.HasExtension("container_push") {
+ return nil, fmt.Errorf("The source server is missing the required \"container_push\" API extension")
+ }
+ }
+
+ if args.Mode == "push" && !source.HasExtension("container_push_target") {
+ return nil, fmt.Errorf("The source server is missing the required \"container_push_target\" API extension")
+ }
+
+ if args.Refresh {
+ if !r.HasExtension("container_incremental_copy") {
+ return nil, fmt.Errorf("The target server is missing the required \"container_incremental_copy\" API extension")
+ }
+
+ if !source.HasExtension("container_incremental_copy") {
+ return nil, fmt.Errorf("The source server is missing the required \"container_incremental_copy\" API extension")
+ }
+ }
+
+ // Allow overriding the target name
+ if args.Name != "" {
+ req.Name = args.Name
+ }
+
+ req.Source.Live = args.Live
+ req.Source.InstanceOnly = args.InstanceOnly
+ req.Source.ContainerOnly = args.InstanceOnly // For legacy servers.
+ req.Source.Refresh = args.Refresh
+ }
+
+ if req.Source.Live {
+ req.Source.Live = instance.StatusCode == api.Running
+ }
+
+ 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)
+ }
+
+ // Optimization for the local copy case
+ if destInfo.URL == sourceInfo.URL && destInfo.SocketPath == sourceInfo.SocketPath && (!r.IsClustered() || instance.Location == r.clusterTarget || r.HasExtension("cluster_internal_copy")) {
+ // 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 = instance.Name
+
+ // Copy the instance
+ op, err := r.CreateInstance(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
+ }
+
+ // Source request
+ sourceReq := api.InstancePost{
+ Migration: true,
+ Live: req.Source.Live,
+ ContainerOnly: req.Source.ContainerOnly, // Deprecated, use InstanceOnly.
+ InstanceOnly: req.Source.InstanceOnly,
+ }
+
+ // Push mode migration
+ if args != nil && args.Mode == "push" {
+ // Get target server connection information
+ info, err := r.GetConnectionInfo()
+ if err != nil {
+ return nil, err
+ }
+
+ // Create the instance
+ req.Source.Type = "migration"
+ req.Source.Mode = "push"
+ req.Source.Refresh = args.Refresh
+
+ op, err := r.CreateInstance(req)
+ if err != nil {
+ return nil, err
+ }
+
+ opAPI := op.Get()
+
+ targetSecrets := map[string]string{}
+ for k, v := range opAPI.Metadata {
+ targetSecrets[k] = v.(string)
+ }
+
+ // Prepare the source request
+ target := api.InstancePostTarget{}
+ target.Operation = opAPI.ID
+ target.Websockets = targetSecrets
+ target.Certificate = info.Certificate
+ sourceReq.Target = &target
+
+ return r.tryMigrateInstance(source, instance.Name, sourceReq, info.Addresses)
+ }
+
+ // Get source server connection information
+ info, err := source.GetConnectionInfo()
+ if err != nil {
+ return nil, err
+ }
+
+ op, err := source.MigrateInstance(instance.Name, sourceReq)
+ if err != nil {
+ return nil, err
+ }
+ opAPI := op.Get()
+
+ sourceSecrets := map[string]string{}
+ for k, v := range opAPI.Metadata {
+ sourceSecrets[k] = v.(string)
+ }
+
+ // Relay mode migration
+ if args != nil && args.Mode == "relay" {
+ // Push copy source fields
+ req.Source.Type = "migration"
+ req.Source.Mode = "push"
+
+ // Start the process
+ targetOp, err := r.CreateInstance(req)
+ if err != nil {
+ return nil, err
+ }
+ targetOpAPI := targetOp.Get()
+
+ // Extract the websockets
+ targetSecrets := map[string]string{}
+ for k, v := range targetOpAPI.Metadata {
+ targetSecrets[k] = v.(string)
+ }
+
+ // Launch the relay
+ err = r.proxyMigration(targetOp.(*operation), targetSecrets, source, op.(*operation), sourceSecrets)
+ if err != nil {
+ return nil, err
+ }
+
+ // Prepare a tracking operation
+ rop := remoteOperation{
+ targetOp: targetOp,
+ chDone: make(chan bool),
+ }
+
+ // Forward targetOp to remote op
+ go func() {
+ rop.err = rop.targetOp.Wait()
+ close(rop.chDone)
+ }()
+
+ return &rop, nil
+ }
+
+ // Pull mode migration
+ req.Source.Type = "migration"
+ req.Source.Mode = "pull"
+ req.Source.Operation = opAPI.ID
+ req.Source.Websockets = sourceSecrets
+ req.Source.Certificate = info.Certificate
+
+ return r.tryCreateInstance(req, info.Addresses)
+}
+
+func (r *ProtocolLXD) proxyInstanceMigration(targetOp *operation, targetSecrets map[string]string, source InstanceServer, sourceOp *operation, sourceSecrets map[string]string) error {
+ // Sanity checks
+ for n := range targetSecrets {
+ _, ok := sourceSecrets[n]
+ if !ok {
+ return fmt.Errorf("Migration target expects the \"%s\" socket but source isn't providing it", n)
+ }
+ }
+
+ if targetSecrets["control"] == "" {
+ return fmt.Errorf("Migration target didn't setup the required \"control\" socket")
+ }
+
+ // Struct used to hold everything together
+ type proxy struct {
+ done chan bool
+ sourceConn *websocket.Conn
+ targetConn *websocket.Conn
+ }
+
+ proxies := map[string]*proxy{}
+
+ // Connect the control socket
+ sourceConn, err := source.GetOperationWebsocket(sourceOp.ID, sourceSecrets["control"])
+ if err != nil {
+ return err
+ }
+
+ targetConn, err := r.GetOperationWebsocket(targetOp.ID, targetSecrets["control"])
+ if err != nil {
+ return err
+ }
+
+ proxies["control"] = &proxy{
+ done: shared.WebsocketProxy(sourceConn, targetConn),
+ sourceConn: sourceConn,
+ targetConn: targetConn,
+ }
+
+ // Connect the data sockets
+ for name := range sourceSecrets {
+ if name == "control" {
+ continue
+ }
+
+ // Handle resets (used for multiple objects)
+ sourceConn, err := source.GetOperationWebsocket(sourceOp.ID, sourceSecrets[name])
+ if err != nil {
+ break
+ }
+
+ targetConn, err := r.GetOperationWebsocket(targetOp.ID, targetSecrets[name])
+ if err != nil {
+ break
+ }
+
+ proxies[name] = &proxy{
+ sourceConn: sourceConn,
+ targetConn: targetConn,
+ done: shared.WebsocketProxy(sourceConn, targetConn),
+ }
+ }
+
+ // Cleanup once everything is done
+ go func() {
+ // Wait for control socket
+ <-proxies["control"].done
+ proxies["control"].sourceConn.Close()
+ proxies["control"].targetConn.Close()
+
+ // Then deal with the others
+ for name, proxy := range proxies {
+ if name == "control" {
+ continue
+ }
+
+ <-proxy.done
+ proxy.sourceConn.Close()
+ proxy.targetConn.Close()
+ }
+ }()
+
+ return nil
+}
+
+// UpdateInstance updates the instance definition.
+func (r *ProtocolLXD) UpdateInstance(name string, instance api.InstancePut, ETag string) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), instance, ETag)
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+// RenameInstance requests that LXD renames the instance.
+func (r *ProtocolLXD) RenameInstance(name string, instance api.InstancePost) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ // Sanity check
+ if instance.Migration {
+ return nil, fmt.Errorf("Can't ask for a migration through RenameInstance")
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), instance, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+func (r *ProtocolLXD) tryMigrateInstance(source InstanceServer, name string, req api.InstancePost, urls []string) (RemoteOperation, error) {
+ if len(urls) == 0 {
+ return nil, fmt.Errorf("The target server isn't listening on the network")
+ }
+
+ rop := remoteOperation{
+ chDone: make(chan bool),
+ }
+
+ operation := req.Target.Operation
+
+ // Forward targetOp to remote op
+ go func() {
+ success := false
+ errors := map[string]error{}
+ for _, serverURL := range urls {
+ req.Target.Operation = fmt.Sprintf("%s/1.0/operations/%s", serverURL, url.PathEscape(operation))
+
+ op, err := source.MigrateInstance(name, req)
+ if err != nil {
+ errors[serverURL] = err
+ continue
+ }
+
+ rop.targetOp = op
+
+ for _, handler := range rop.handlers {
+ rop.targetOp.AddHandler(handler)
+ }
+
+ err = rop.targetOp.Wait()
+ if err != nil {
+ errors[serverURL] = err
+ continue
+ }
+
+ success = true
+ break
+ }
+
+ if !success {
+ rop.err = remoteOperationError("Failed instance migration", errors)
+ }
+
+ close(rop.chDone)
+ }()
+
+ return &rop, nil
+}
+
+// MigrateInstance requests that LXD prepares for a instance migration.
+func (r *ProtocolLXD) MigrateInstance(name string, instance api.InstancePost) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if instance.InstanceOnly || instance.ContainerOnly {
+ if !r.HasExtension("container_only_migration") {
+ return nil, fmt.Errorf("The server is missing the required \"container_only_migration\" API extension")
+ }
+ }
+
+ // Sanity check
+ if !instance.Migration {
+ return nil, fmt.Errorf("Can't ask for a rename through MigrateInstance")
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), instance, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+// DeleteInstance requests that LXD deletes the instance.
+func (r *ProtocolLXD) DeleteInstance(name string) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("DELETE", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), nil, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+// ExecInstance requests that LXD spawns a command inside the instance.
+func (r *ProtocolLXD) ExecInstance(instanceName string, exec api.InstanceExecPost, args *InstanceExecArgs) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if exec.RecordOutput {
+ if !r.HasExtension("container_exec_recording") {
+ return nil, fmt.Errorf("The server is missing the required \"container_exec_recording\" API extension")
+ }
+ }
+
+ if exec.User > 0 || exec.Group > 0 || exec.Cwd != "" {
+ if !r.HasExtension("container_exec_user_group_cwd") {
+ return nil, fmt.Errorf("The server is missing the required \"container_exec_user_group_cwd\" API extension")
+ }
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/exec", path, url.PathEscape(instanceName)), exec, "")
+ if err != nil {
+ return nil, err
+ }
+ opAPI := op.Get()
+
+ // Process additional arguments
+ if args != nil {
+ // Parse the fds
+ fds := map[string]string{}
+
+ value, ok := opAPI.Metadata["fds"]
+ if ok {
+ values := value.(map[string]interface{})
+ for k, v := range values {
+ fds[k] = v.(string)
+ }
+ }
+
+ // Call the control handler with a connection to the control socket
+ if args.Control != nil && fds["control"] != "" {
+ conn, err := r.GetOperationWebsocket(opAPI.ID, fds["control"])
+ if err != nil {
+ return nil, err
+ }
+
+ go args.Control(conn)
+ }
+
+ if exec.Interactive {
+ // Handle interactive sections
+ if args.Stdin != nil && args.Stdout != nil {
+ // Connect to the websocket
+ conn, err := r.GetOperationWebsocket(opAPI.ID, fds["0"])
+ if err != nil {
+ return nil, err
+ }
+
+ // And attach stdin and stdout to it
+ go func() {
+ shared.WebsocketSendStream(conn, args.Stdin, -1)
+ <-shared.WebsocketRecvStream(args.Stdout, conn)
+ conn.Close()
+
+ if args.DataDone != nil {
+ close(args.DataDone)
+ }
+ }()
+ } else {
+ if args.DataDone != nil {
+ close(args.DataDone)
+ }
+ }
+ } else {
+ // Handle non-interactive sessions
+ dones := map[int]chan bool{}
+ conns := []*websocket.Conn{}
+
+ // Handle stdin
+ if fds["0"] != "" {
+ conn, err := r.GetOperationWebsocket(opAPI.ID, fds["0"])
+ if err != nil {
+ return nil, err
+ }
+
+ conns = append(conns, conn)
+ dones[0] = shared.WebsocketSendStream(conn, args.Stdin, -1)
+ }
+
+ // Handle stdout
+ if fds["1"] != "" {
+ conn, err := r.GetOperationWebsocket(opAPI.ID, fds["1"])
+ if err != nil {
+ return nil, err
+ }
+
+ conns = append(conns, conn)
+ dones[1] = shared.WebsocketRecvStream(args.Stdout, conn)
+ }
+
+ // Handle stderr
+ if fds["2"] != "" {
+ conn, err := r.GetOperationWebsocket(opAPI.ID, fds["2"])
+ if err != nil {
+ return nil, err
+ }
+
+ conns = append(conns, conn)
+ dones[2] = shared.WebsocketRecvStream(args.Stderr, conn)
+ }
+
+ // Wait for everything to be done
+ go func() {
+ for i, chDone := range dones {
+ // Skip stdin, dealing with it separately below
+ if i == 0 {
+ continue
+ }
+
+ <-chDone
+ }
+
+ if fds["0"] != "" {
+ if args.Stdin != nil {
+ args.Stdin.Close()
+ }
+
+ // Empty the stdin channel but don't block on it as
+ // stdin may be stuck in Read()
+ go func() {
+ <-dones[0]
+ }()
+ }
+
+ for _, conn := range conns {
+ conn.Close()
+ }
+
+ if args.DataDone != nil {
+ close(args.DataDone)
+ }
+ }()
+ }
+ }
+
+ return op, nil
+}
+
+// GetInstanceFile retrieves the provided path from the instance.
+func (r *ProtocolLXD) GetInstanceFile(instanceName string, filePath string) (io.ReadCloser, *InstanceFileResponse, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Prepare the HTTP request
+ requestURL, err := shared.URLEncode(
+ fmt.Sprintf("%s/1.0%s/%s/files", r.httpHost, path, url.PathEscape(instanceName)),
+ map[string]string{"path": filePath})
+ if err != nil {
+ return nil, nil, err
+ }
+
+ requestURL, err = r.setQueryAttributes(requestURL)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ req, err := http.NewRequest("GET", requestURL, nil)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Set the user agent
+ if r.httpUserAgent != "" {
+ req.Header.Set("User-Agent", r.httpUserAgent)
+ }
+
+ // Send the request
+ resp, err := r.do(req)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Check the return value for a cleaner error
+ if resp.StatusCode != http.StatusOK {
+ _, _, err := lxdParseResponse(resp)
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ // Parse the headers
+ uid, gid, mode, fileType, _ := shared.ParseLXDFileHeaders(resp.Header)
+ fileResp := InstanceFileResponse{
+ UID: uid,
+ GID: gid,
+ Mode: mode,
+ Type: fileType,
+ }
+
+ if fileResp.Type == "directory" {
+ // Decode the response
+ response := api.Response{}
+ decoder := json.NewDecoder(resp.Body)
+
+ err = decoder.Decode(&response)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Get the file list
+ entries := []string{}
+ err = response.MetadataAsStruct(&entries)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ fileResp.Entries = entries
+
+ return nil, &fileResp, err
+ }
+
+ return resp.Body, &fileResp, err
+}
+
+// CreateInstanceFile tells LXD to create a file in the instance.
+func (r *ProtocolLXD) CreateInstanceFile(instanceName string, filePath string, args InstanceFileArgs) error {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return err
+ }
+
+ if args.Type == "directory" {
+ if !r.HasExtension("directory_manipulation") {
+ return fmt.Errorf("The server is missing the required \"directory_manipulation\" API extension")
+ }
+ }
+
+ if args.Type == "symlink" {
+ if !r.HasExtension("file_symlinks") {
+ return fmt.Errorf("The server is missing the required \"file_symlinks\" API extension")
+ }
+ }
+
+ if args.WriteMode == "append" {
+ if !r.HasExtension("file_append") {
+ return fmt.Errorf("The server is missing the required \"file_append\" API extension")
+ }
+ }
+
+ // Prepare the HTTP request
+ requestURL := fmt.Sprintf("%s/1.0%s/%s/files?path=%s", r.httpHost, path, url.PathEscape(instanceName), url.QueryEscape(filePath))
+
+ requestURL, err = r.setQueryAttributes(requestURL)
+ if err != nil {
+ return err
+ }
+
+ req, err := http.NewRequest("POST", requestURL, args.Content)
+ if err != nil {
+ return err
+ }
+
+ // Set the user agent
+ if r.httpUserAgent != "" {
+ req.Header.Set("User-Agent", r.httpUserAgent)
+ }
+
+ // Set the various headers
+ if args.UID > -1 {
+ req.Header.Set("X-LXD-uid", fmt.Sprintf("%d", args.UID))
+ }
+
+ if args.GID > -1 {
+ req.Header.Set("X-LXD-gid", fmt.Sprintf("%d", args.GID))
+ }
+
+ if args.Mode > -1 {
+ req.Header.Set("X-LXD-mode", fmt.Sprintf("%04o", args.Mode))
+ }
+
+ if args.Type != "" {
+ req.Header.Set("X-LXD-type", args.Type)
+ }
+
+ if args.WriteMode != "" {
+ req.Header.Set("X-LXD-write", args.WriteMode)
+ }
+
+ // Send the request
+ resp, err := r.do(req)
+ if err != nil {
+ return err
+ }
+
+ // Check the return value for a cleaner error
+ _, _, err = lxdParseResponse(resp)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// DeleteInstanceFile deletes a file in the instance.
+func (r *ProtocolLXD) DeleteInstanceFile(instanceName string, filePath string) error {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return err
+ }
+
+ if !r.HasExtension("file_delete") {
+ return fmt.Errorf("The server is missing the required \"file_delete\" API extension")
+ }
+
+ // Send the request
+ _, _, err = r.query("DELETE", fmt.Sprintf("%s/%s/files?path=%s", path, url.PathEscape(instanceName), url.QueryEscape(filePath)), nil, "")
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// GetInstanceSnapshotNames returns a list of snapshot names for the instance.
+func (r *ProtocolLXD) GetInstanceSnapshotNames(instanceName string) ([]string, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ urls := []string{}
+
+ // Fetch the raw value
+ _, err = r.queryStruct("GET", fmt.Sprintf("%s/%s/snapshots", path, url.PathEscape(instanceName)), nil, "", &urls)
+ if err != nil {
+ return nil, err
+ }
+
+ // Parse it
+ names := []string{}
+ for _, uri := range urls {
+ fields := strings.Split(uri, fmt.Sprintf("%s/%s/snapshots/", path, url.PathEscape(instanceName)))
+ names = append(names, fields[len(fields)-1])
+ }
+
+ return names, nil
+}
+
+// GetInstanceSnapshots returns a list of snapshots for the instance.
+func (r *ProtocolLXD) GetInstanceSnapshots(instanceName string) ([]api.InstanceSnapshot, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ snapshots := []api.InstanceSnapshot{}
+
+ // Fetch the raw value
+ _, err = r.queryStruct("GET", fmt.Sprintf("%s/%s/snapshots?recursion=1", path, url.PathEscape(instanceName)), nil, "", &snapshots)
+ if err != nil {
+ return nil, err
+ }
+
+ return snapshots, nil
+}
+
+// GetInstanceSnapshot returns a Snapshot struct for the provided instance and snapshot names
+func (r *ProtocolLXD) GetInstanceSnapshot(instanceName string, name string) (*api.InstanceSnapshot, string, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, "", err
+ }
+
+ snapshot := api.InstanceSnapshot{}
+
+ // Fetch the raw value
+ etag, err := r.queryStruct("GET", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), nil, "", &snapshot)
+ if err != nil {
+ return nil, "", err
+ }
+
+ return &snapshot, etag, nil
+}
+
+// CreateInstanceSnapshot requests that LXD creates a new snapshot for the instance.
+func (r *ProtocolLXD) CreateInstanceSnapshot(instanceName string, snapshot api.InstanceSnapshotsPost) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ // Validate the request
+ if snapshot.ExpiresAt != nil && !r.HasExtension("snapshot_expiry_creation") {
+ return nil, fmt.Errorf("The server is missing the required \"snapshot_expiry_creation\" API extension")
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/snapshots", path, url.PathEscape(instanceName)), snapshot, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+// CopyInstanceSnapshot copies a snapshot from a remote server into a new instance. Additional options can be passed using InstanceCopyArgs.
+func (r *ProtocolLXD) CopyInstanceSnapshot(source InstanceServer, instanceName string, snapshot api.InstanceSnapshot, args *InstanceSnapshotCopyArgs) (RemoteOperation, error) {
+ // Backward compatibility (with broken Name field)
+ fields := strings.Split(snapshot.Name, shared.SnapshotDelimiter)
+ cName := instanceName
+ sName := fields[len(fields)-1]
+
+ // Base request
+ req := api.InstancesPost{
+ Name: cName,
+ InstancePut: api.InstancePut{
+ Architecture: snapshot.Architecture,
+ Config: snapshot.Config,
+ Devices: snapshot.Devices,
+ Ephemeral: snapshot.Ephemeral,
+ Profiles: snapshot.Profiles,
+ },
+ }
+
+ if snapshot.Stateful && args.Live {
+ if !r.HasExtension("container_snapshot_stateful_migration") {
+ return nil, fmt.Errorf("The server is missing the required \"container_snapshot_stateful_migration\" API extension")
+ }
+ req.InstancePut.Stateful = snapshot.Stateful
+ req.Source.Live = args.Live
+ }
+ req.Source.BaseImage = snapshot.Config["volatile.base_image"]
+
+ // Process the copy arguments
+ if args != nil {
+ // Sanity checks
+ if shared.StringInSlice(args.Mode, []string{"push", "relay"}) {
+ if !r.HasExtension("container_push") {
+ return nil, fmt.Errorf("The target server is missing the required \"container_push\" API extension")
+ }
+
+ if !source.HasExtension("container_push") {
+ return nil, fmt.Errorf("The source server is missing the required \"container_push\" API extension")
+ }
+ }
+
+ if args.Mode == "push" && !source.HasExtension("container_push_target") {
+ return nil, fmt.Errorf("The source server is missing the required \"container_push_target\" API extension")
+ }
+
+ // Allow overriding the target name
+ if args.Name != "" {
+ req.Name = args.Name
+ }
+ }
+
+ 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)
+ }
+
+ instance, _, err := source.GetInstance(cName)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to get instance info: %v", err)
+ }
+
+ // Optimization for the local copy case
+ if destInfo.URL == sourceInfo.URL && destInfo.SocketPath == sourceInfo.SocketPath && (!r.IsClustered() || instance.Location == r.clusterTarget || r.HasExtension("cluster_internal_copy")) {
+ // 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)
+
+ // Copy the instance
+ op, err := r.CreateInstance(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
+ }
+
+ // Source request
+ sourceReq := api.InstanceSnapshotPost{
+ Migration: true,
+ Name: args.Name,
+ }
+ if snapshot.Stateful && args.Live {
+ sourceReq.Live = args.Live
+ }
+
+ // Push mode migration
+ if args != nil && args.Mode == "push" {
+ // Get target server connection information
+ info, err := r.GetConnectionInfo()
+ if err != nil {
+ return nil, err
+ }
+
+ // Create the instance
+ req.Source.Type = "migration"
+ req.Source.Mode = "push"
+
+ op, err := r.CreateInstance(req)
+ if err != nil {
+ return nil, err
+ }
+ opAPI := op.Get()
+
+ targetSecrets := map[string]string{}
+ for k, v := range opAPI.Metadata {
+ targetSecrets[k] = v.(string)
+ }
+
+ // Prepare the source request
+ target := api.InstancePostTarget{}
+ target.Operation = opAPI.ID
+ target.Websockets = targetSecrets
+ target.Certificate = info.Certificate
+ sourceReq.Target = &target
+
+ return r.tryMigrateInstanceSnapshot(source, cName, sName, sourceReq, info.Addresses)
+ }
+
+ // Get source server connection information
+ info, err := source.GetConnectionInfo()
+ if err != nil {
+ return nil, err
+ }
+
+ op, err := source.MigrateInstanceSnapshot(cName, sName, sourceReq)
+ if err != nil {
+ return nil, err
+ }
+ opAPI := op.Get()
+
+ sourceSecrets := map[string]string{}
+ for k, v := range opAPI.Metadata {
+ sourceSecrets[k] = v.(string)
+ }
+
+ // Relay mode migration
+ if args != nil && args.Mode == "relay" {
+ // Push copy source fields
+ req.Source.Type = "migration"
+ req.Source.Mode = "push"
+
+ // Start the process
+ targetOp, err := r.CreateInstance(req)
+ if err != nil {
+ return nil, err
+ }
+ targetOpAPI := targetOp.Get()
+
+ // Extract the websockets
+ targetSecrets := map[string]string{}
+ for k, v := range targetOpAPI.Metadata {
+ targetSecrets[k] = v.(string)
+ }
+
+ // Launch the relay
+ err = r.proxyMigration(targetOp.(*operation), targetSecrets, source, op.(*operation), sourceSecrets)
+ if err != nil {
+ return nil, err
+ }
+
+ // Prepare a tracking operation
+ rop := remoteOperation{
+ targetOp: targetOp,
+ chDone: make(chan bool),
+ }
+
+ // Forward targetOp to remote op
+ go func() {
+ rop.err = rop.targetOp.Wait()
+ close(rop.chDone)
+ }()
+
+ return &rop, nil
+ }
+
+ // Pull mode migration
+ req.Source.Type = "migration"
+ req.Source.Mode = "pull"
+ req.Source.Operation = opAPI.ID
+ req.Source.Websockets = sourceSecrets
+ req.Source.Certificate = info.Certificate
+
+ return r.tryCreateInstance(req, info.Addresses)
+}
+
+// RenameInstanceSnapshot requests that LXD renames the snapshot.
+func (r *ProtocolLXD) RenameInstanceSnapshot(instanceName string, name string, instance api.InstanceSnapshotPost) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ // Sanity check
+ if instance.Migration {
+ return nil, fmt.Errorf("Can't ask for a migration through RenameInstanceSnapshot")
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), instance, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+func (r *ProtocolLXD) tryMigrateInstanceSnapshot(source InstanceServer, instanceName string, name string, req api.InstanceSnapshotPost, urls []string) (RemoteOperation, error) {
+ if len(urls) == 0 {
+ return nil, fmt.Errorf("The target server isn't listening on the network")
+ }
+
+ rop := remoteOperation{
+ chDone: make(chan bool),
+ }
+
+ operation := req.Target.Operation
+
+ // Forward targetOp to remote op
+ go func() {
+ success := false
+ errors := map[string]error{}
+ for _, serverURL := range urls {
+ req.Target.Operation = fmt.Sprintf("%s/1.0/operations/%s", serverURL, url.PathEscape(operation))
+
+ op, err := source.MigrateInstanceSnapshot(instanceName, name, req)
+ if err != nil {
+ errors[serverURL] = err
+ continue
+ }
+
+ rop.targetOp = op
+
+ for _, handler := range rop.handlers {
+ rop.targetOp.AddHandler(handler)
+ }
+
+ err = rop.targetOp.Wait()
+ if err != nil {
+ errors[serverURL] = err
+ continue
+ }
+
+ success = true
+ break
+ }
+
+ if !success {
+ rop.err = remoteOperationError("Failed instance migration", errors)
+ }
+
+ close(rop.chDone)
+ }()
+
+ return &rop, nil
+}
+
+// MigrateInstanceSnapshot requests that LXD prepares for a snapshot migration.
+func (r *ProtocolLXD) MigrateInstanceSnapshot(instanceName string, name string, instance api.InstanceSnapshotPost) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ // Sanity check
+ if !instance.Migration {
+ return nil, fmt.Errorf("Can't ask for a rename through MigrateInstanceSnapshot")
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), instance, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+// DeleteInstanceSnapshot requests that LXD deletes the instance snapshot.
+func (r *ProtocolLXD) DeleteInstanceSnapshot(instanceName string, name string) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("DELETE", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), nil, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+// UpdateInstanceSnapshot requests that LXD updates the instance snapshot.
+func (r *ProtocolLXD) UpdateInstanceSnapshot(instanceName string, name string, instance api.InstanceSnapshotPut, ETag string) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if !r.HasExtension("snapshot_expiry") {
+ return nil, fmt.Errorf("The server is missing the required \"snapshot_expiry\" API extension")
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), instance, ETag)
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+// GetInstanceState returns a InstanceState entry for the provided instance name.
+func (r *ProtocolLXD) GetInstanceState(name string) (*api.InstanceState, string, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, "", err
+ }
+
+ state := api.InstanceState{}
+
+ // Fetch the raw value
+ etag, err := r.queryStruct("GET", fmt.Sprintf("%s/%s/state", path, url.PathEscape(name)), nil, "", &state)
+ if err != nil {
+ return nil, "", err
+ }
+
+ return &state, etag, nil
+}
+
+// UpdateInstanceState updates the instance to match the requested state.
+func (r *ProtocolLXD) UpdateInstanceState(name string, state api.InstanceStatePut, ETag string) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s/%s/state", path, url.PathEscape(name)), state, ETag)
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+// GetInstanceLogfiles returns a list of logfiles for the instance.
+func (r *ProtocolLXD) GetInstanceLogfiles(name string) ([]string, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ urls := []string{}
+
+ // Fetch the raw value
+ _, err = r.queryStruct("GET", fmt.Sprintf("%s/%s/logs", path, url.PathEscape(name)), nil, "", &urls)
+ if err != nil {
+ return nil, err
+ }
+
+ // Parse it
+ logfiles := []string{}
+ for _, uri := range logfiles {
+ fields := strings.Split(uri, fmt.Sprintf("%s/%s/logs/", path, url.PathEscape(name)))
+ logfiles = append(logfiles, fields[len(fields)-1])
+ }
+
+ return logfiles, nil
+}
+
+// GetInstanceLogfile returns the content of the requested logfile.
+//
+// Note that it's the caller's responsibility to close the returned ReadCloser
+func (r *ProtocolLXD) GetInstanceLogfile(name string, filename string) (io.ReadCloser, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ // Prepare the HTTP request
+ url := fmt.Sprintf("%s/1.0%s/%s/logs/%s", r.httpHost, path, url.PathEscape(name), url.PathEscape(filename))
+
+ url, err = r.setQueryAttributes(url)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ // Set the user agent
+ if r.httpUserAgent != "" {
+ req.Header.Set("User-Agent", r.httpUserAgent)
+ }
+
+ // Send the request
+ resp, err := r.do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ // Check the return value for a cleaner error
+ if resp.StatusCode != http.StatusOK {
+ _, _, err := lxdParseResponse(resp)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return resp.Body, err
+}
+
+// DeleteInstanceLogfile deletes the requested logfile.
+func (r *ProtocolLXD) DeleteInstanceLogfile(name string, filename string) error {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return err
+ }
+
+ // Send the request
+ _, _, err = r.query("DELETE", fmt.Sprintf("%s/%s/logs/%s", path, url.PathEscape(name), url.PathEscape(filename)), nil, "")
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// GetInstanceMetadata returns instance metadata.
+func (r *ProtocolLXD) GetInstanceMetadata(name string) (*api.ImageMetadata, string, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, "", err
+ }
+
+ if !r.HasExtension("container_edit_metadata") {
+ return nil, "", fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+ }
+
+ metadata := api.ImageMetadata{}
+
+ url := fmt.Sprintf("%s/%s/metadata", path, url.PathEscape(name))
+ etag, err := r.queryStruct("GET", url, nil, "", &metadata)
+ if err != nil {
+ return nil, "", err
+ }
+
+ return &metadata, etag, err
+}
+
+// SetInstanceMetadata sets the content of the instance metadata file.
+func (r *ProtocolLXD) SetInstanceMetadata(name string, metadata api.ImageMetadata, ETag string) error {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return err
+ }
+
+ if !r.HasExtension("container_edit_metadata") {
+ return fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+ }
+
+ url := fmt.Sprintf("%s/%s/metadata", path, url.PathEscape(name))
+ _, _, err = r.query("PUT", url, metadata, ETag)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// GetInstanceTemplateFiles returns the list of names of template files for a instance.
+func (r *ProtocolLXD) GetInstanceTemplateFiles(instanceName string) ([]string, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if !r.HasExtension("container_edit_metadata") {
+ return nil, fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+ }
+
+ templates := []string{}
+
+ url := fmt.Sprintf("%s/%s/metadata/templates", path, url.PathEscape(instanceName))
+ _, err = r.queryStruct("GET", url, nil, "", &templates)
+ if err != nil {
+ return nil, err
+ }
+
+ return templates, nil
+}
+
+// GetInstanceTemplateFile returns the content of a template file for a instance.
+func (r *ProtocolLXD) GetInstanceTemplateFile(instanceName string, templateName string) (io.ReadCloser, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if !r.HasExtension("container_edit_metadata") {
+ return nil, fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+ }
+
+ url := fmt.Sprintf("%s/1.0%s/%s/metadata/templates?path=%s", r.httpHost, path, url.PathEscape(instanceName), url.QueryEscape(templateName))
+
+ url, err = r.setQueryAttributes(url)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ // Set the user agent
+ if r.httpUserAgent != "" {
+ req.Header.Set("User-Agent", r.httpUserAgent)
+ }
+
+ // Send the request
+ resp, err := r.do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ // Check the return value for a cleaner error
+ if resp.StatusCode != http.StatusOK {
+ _, _, err := lxdParseResponse(resp)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return resp.Body, err
+}
+
+// CreateInstanceTemplateFile creates an a template for a instance.
+func (r *ProtocolLXD) CreateInstanceTemplateFile(instanceName string, templateName string, content io.ReadSeeker) error {
+ return r.setInstanceTemplateFile(instanceName, templateName, content, "POST")
+}
+
+// UpdateInstanceTemplateFile updates the content for a instance template file.
+func (r *ProtocolLXD) UpdateInstanceTemplateFile(instanceName string, templateName string, content io.ReadSeeker) error {
+ return r.setInstanceTemplateFile(instanceName, templateName, content, "PUT")
+}
+
+func (r *ProtocolLXD) setInstanceTemplateFile(instanceName string, templateName string, content io.ReadSeeker, httpMethod string) error {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return err
+ }
+
+ if !r.HasExtension("container_edit_metadata") {
+ return fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+ }
+
+ url := fmt.Sprintf("%s/1.0%s/%s/metadata/templates?path=%s", r.httpHost, path, url.PathEscape(instanceName), url.QueryEscape(templateName))
+
+ url, err = r.setQueryAttributes(url)
+ if err != nil {
+ return err
+ }
+
+ req, err := http.NewRequest(httpMethod, url, content)
+ if err != nil {
+ return err
+ }
+ req.Header.Set("Content-Type", "application/octet-stream")
+
+ // Set the user agent
+ if r.httpUserAgent != "" {
+ req.Header.Set("User-Agent", r.httpUserAgent)
+ }
+
+ // Send the request
+ resp, err := r.do(req)
+ // Check the return value for a cleaner error
+ if resp.StatusCode != http.StatusOK {
+ _, _, err := lxdParseResponse(resp)
+ if err != nil {
+ return err
+ }
+ }
+ return err
+}
+
+// DeleteInstanceTemplateFile deletes a template file for a instance.
+func (r *ProtocolLXD) DeleteInstanceTemplateFile(name string, templateName string) error {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return err
+ }
+
+ if !r.HasExtension("container_edit_metadata") {
+ return fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+ }
+ _, _, err = r.query("DELETE", fmt.Sprintf("%s/%s/metadata/templates?path=%s", path, url.PathEscape(name), url.QueryEscape(templateName)), nil, "")
+ return err
+}
+
+// ConsoleInstance requests that LXD attaches to the console device of a instance.
+func (r *ProtocolLXD) ConsoleInstance(instanceName string, console api.InstanceConsolePost, args *InstanceConsoleArgs) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if !r.HasExtension("console") {
+ return nil, fmt.Errorf("The server is missing the required \"console\" API extension")
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/console", path, url.PathEscape(instanceName)), console, "")
+ if err != nil {
+ return nil, err
+ }
+ opAPI := op.Get()
+
+ if args == nil || args.Terminal == nil {
+ return nil, fmt.Errorf("A terminal must be set")
+ }
+
+ if args.Control == nil {
+ return nil, fmt.Errorf("A control channel must be set")
+ }
+
+ // Parse the fds
+ fds := map[string]string{}
+
+ value, ok := opAPI.Metadata["fds"]
+ if ok {
+ values := value.(map[string]interface{})
+ for k, v := range values {
+ fds[k] = v.(string)
+ }
+ }
+
+ var controlConn *websocket.Conn
+ // Call the control handler with a connection to the control socket
+ if fds["control"] == "" {
+ return nil, fmt.Errorf("Did not receive a file descriptor for the control channel")
+ }
+
+ controlConn, err = r.GetOperationWebsocket(opAPI.ID, fds["control"])
+ if err != nil {
+ return nil, err
+ }
+
+ go args.Control(controlConn)
+
+ // Connect to the websocket
+ conn, err := r.GetOperationWebsocket(opAPI.ID, fds["0"])
+ if err != nil {
+ return nil, err
+ }
+
+ // Detach from console.
+ go func(consoleDisconnect <-chan bool) {
+ <-consoleDisconnect
+ msg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "Detaching from console")
+ // We don't care if this fails. This is just for convenience.
+ controlConn.WriteMessage(websocket.CloseMessage, msg)
+ controlConn.Close()
+ }(args.ConsoleDisconnect)
+
+ // And attach stdin and stdout to it
+ go func() {
+ shared.WebsocketSendStream(conn, args.Terminal, -1)
+ <-shared.WebsocketRecvStream(args.Terminal, conn)
+ conn.Close()
+ }()
+
+ return op, nil
+}
+
+// GetInstanceConsoleLog requests that LXD attaches to the console device of a instance.
+//
+// Note that it's the caller's responsibility to close the returned ReadCloser
+func (r *ProtocolLXD) GetInstanceConsoleLog(instanceName string, args *InstanceConsoleLogArgs) (io.ReadCloser, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if !r.HasExtension("console") {
+ return nil, fmt.Errorf("The server is missing the required \"console\" API extension")
+ }
+
+ // Prepare the HTTP request
+ url := fmt.Sprintf("%s/1.0%s/%s/console", r.httpHost, path, url.PathEscape(instanceName))
+
+ url, err = r.setQueryAttributes(url)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ // Set the user agent
+ if r.httpUserAgent != "" {
+ req.Header.Set("User-Agent", r.httpUserAgent)
+ }
+
+ // Send the request
+ resp, err := r.do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ // Check the return value for a cleaner error
+ if resp.StatusCode != http.StatusOK {
+ _, _, err := lxdParseResponse(resp)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return resp.Body, err
+}
+
+// DeleteInstanceConsoleLog deletes the requested instance's console log.
+func (r *ProtocolLXD) DeleteInstanceConsoleLog(instanceName string, args *InstanceConsoleLogArgs) error {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return err
+ }
+
+ if !r.HasExtension("console") {
+ return fmt.Errorf("The server is missing the required \"console\" API extension")
+ }
+
+ // Send the request
+ _, _, err = r.query("DELETE", fmt.Sprintf("%s/%s/console", path, url.PathEscape(instanceName)), nil, "")
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// GetInstanceBackupNames returns a list of backup names for the instance.
+func (r *ProtocolLXD) GetInstanceBackupNames(instanceName string) ([]string, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if !r.HasExtension("container_backup") {
+ return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+ }
+
+ // Fetch the raw value
+ urls := []string{}
+ _, err = r.queryStruct("GET", fmt.Sprintf("%s/%s/backups", path, url.PathEscape(instanceName)), nil, "", &urls)
+ if err != nil {
+ return nil, err
+ }
+
+ // Parse it
+ names := []string{}
+ for _, uri := range urls {
+ fields := strings.Split(uri, fmt.Sprintf("%s/%s/backups/", path, url.PathEscape(instanceName)))
+ names = append(names, fields[len(fields)-1])
+ }
+
+ return names, nil
+}
+
+// GetInstanceBackups returns a list of backups for the instance.
+func (r *ProtocolLXD) GetInstanceBackups(instanceName string) ([]api.InstanceBackup, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if !r.HasExtension("container_backup") {
+ return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+ }
+
+ // Fetch the raw value
+ backups := []api.InstanceBackup{}
+
+ _, err = r.queryStruct("GET", fmt.Sprintf("%s/%s/backups?recursion=1", path, url.PathEscape(instanceName)), nil, "", &backups)
+ if err != nil {
+ return nil, err
+ }
+
+ return backups, nil
+}
+
+// GetInstanceBackup returns a Backup struct for the provided instance and backup names.
+func (r *ProtocolLXD) GetInstanceBackup(instanceName string, name string) (*api.InstanceBackup, string, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, "", err
+ }
+
+ if !r.HasExtension("container_backup") {
+ return nil, "", fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+ }
+
+ // Fetch the raw value
+ backup := api.InstanceBackup{}
+ etag, err := r.queryStruct("GET", fmt.Sprintf("%s/%s/backups/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), nil, "", &backup)
+ if err != nil {
+ return nil, "", err
+ }
+
+ return &backup, etag, nil
+}
+
+// CreateInstanceBackup requests that LXD creates a new backup for the instance.
+func (r *ProtocolLXD) CreateInstanceBackup(instanceName string, backup api.InstanceBackupsPost) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if !r.HasExtension("container_backup") {
+ return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/backups", path, url.PathEscape(instanceName)), backup, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+// RenameInstanceBackup requests that LXD renames the backup.
+func (r *ProtocolLXD) RenameInstanceBackup(instanceName string, name string, backup api.InstanceBackupPost) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if !r.HasExtension("container_backup") {
+ return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/backups/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), backup, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+// DeleteInstanceBackup requests that LXD deletes the instance backup.
+func (r *ProtocolLXD) DeleteInstanceBackup(instanceName string, name string) (Operation, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if !r.HasExtension("container_backup") {
+ return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+ }
+
+ // Send the request
+ op, _, err := r.queryOperation("DELETE", fmt.Sprintf("%s/%s/backups/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), nil, "")
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
+}
+
+// GetInstanceBackupFile requests the instance backup content.
+func (r *ProtocolLXD) GetInstanceBackupFile(instanceName string, name string, req *BackupFileRequest) (*BackupFileResponse, error) {
+ path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+ if err != nil {
+ return nil, err
+ }
+
+ if !r.HasExtension("container_backup") {
+ return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+ }
+
+ // Build the URL
+ uri := fmt.Sprintf("%s/1.0%s/%s/backups/%s/export", r.httpHost, path, url.PathEscape(instanceName), url.PathEscape(name))
+ if r.project != "" {
+ uri += fmt.Sprintf("?project=%s", url.QueryEscape(r.project))
+ }
+
+ // Prepare the download request
+ request, err := http.NewRequest("GET", uri, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ if r.httpUserAgent != "" {
+ request.Header.Set("User-Agent", r.httpUserAgent)
+ }
+
+ // Start the request
+ response, doneCh, err := cancel.CancelableDownload(req.Canceler, r.http, request)
+ if err != nil {
+ return nil, err
+ }
+ defer response.Body.Close()
+ defer close(doneCh)
+
+ if response.StatusCode != http.StatusOK {
+ _, _, err := lxdParseResponse(response)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // Handle the data
+ body := response.Body
+ if req.ProgressHandler != nil {
+ body = &ioprogress.ProgressReader{
+ ReadCloser: response.Body,
+ Tracker: &ioprogress.ProgressTracker{
+ Length: response.ContentLength,
+ Handler: func(percent int64, speed int64) {
+ req.ProgressHandler(ioprogress.ProgressData{Text: fmt.Sprintf("%d%% (%s/s)", percent, units.GetByteSizeString(speed, 2))})
+ },
+ },
+ }
+ }
+
+ size, err := io.Copy(req.BackupFile, body)
+ if err != nil {
+ return nil, err
+ }
+
+ resp := BackupFileResponse{}
+ resp.Size = size
+
+ return &resp, nil
+}
From c7e6044d542aa31a4f1423c94e29c00f71de090b Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 12 Sep 2019 17:27:00 +0100
Subject: [PATCH 4/5] shared/netutils/network/linux: Updates NetnsGetifaddrs to
use Instance types
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
shared/netutils/network_linux.go | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/shared/netutils/network_linux.go b/shared/netutils/network_linux.go
index 9d1216122f..e593dc87cc 100644
--- a/shared/netutils/network_linux.go
+++ b/shared/netutils/network_linux.go
@@ -25,7 +25,7 @@ import (
// #cgo CFLAGS: -std=gnu11 -Wvla
import "C"
-func NetnsGetifaddrs(initPID int32) (map[string]api.ContainerStateNetwork, error) {
+func NetnsGetifaddrs(initPID int32) (map[string]api.InstanceStateNetwork, error) {
var netnsid_aware C.bool
var ifaddrs *C.struct_netns_ifaddrs
var netnsID C.__s32
@@ -57,15 +57,15 @@ func NetnsGetifaddrs(initPID int32) (map[string]api.ContainerStateNetwork, error
// We're using the interface name as key here but we should really
// switch to the ifindex at some point to handle ip aliasing correctly.
- networks := map[string]api.ContainerStateNetwork{}
+ networks := map[string]api.InstanceStateNetwork{}
for addr := ifaddrs; addr != nil; addr = addr.ifa_next {
var address [C.INET6_ADDRSTRLEN]C.char
addNetwork, networkExists := networks[C.GoString(addr.ifa_name)]
if !networkExists {
- addNetwork = api.ContainerStateNetwork{
- Addresses: []api.ContainerStateNetworkAddress{},
- Counters: api.ContainerStateNetworkCounters{},
+ addNetwork = api.InstanceStateNetwork{
+ Addresses: []api.InstanceStateNetworkAddress{},
+ Counters: api.InstanceStateNetworkCounters{},
}
}
@@ -117,7 +117,7 @@ func NetnsGetifaddrs(initPID int32) (map[string]api.ContainerStateNetwork, error
}
if addNetwork.Addresses == nil {
- addNetwork.Addresses = []api.ContainerStateNetworkAddress{}
+ addNetwork.Addresses = []api.InstanceStateNetworkAddress{}
}
goAddrString := C.GoString(address_str)
@@ -138,7 +138,7 @@ func NetnsGetifaddrs(initPID int32) (map[string]api.ContainerStateNetwork, error
scope = "link"
}
- address := api.ContainerStateNetworkAddress{}
+ address := api.InstanceStateNetworkAddress{}
address.Family = family
address.Address = goAddrString
address.Netmask = fmt.Sprintf("%d", int(addr.ifa_prefixlen))
From 6026e038cf228a90a3b5188a4f1910a10aa727bd Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 12 Sep 2019 18:02:46 +0100
Subject: [PATCH 5/5] lxd: Switches over to Instance types
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/api_1.0.go | 2 +-
lxd/api_cluster.go | 6 ++--
lxd/api_cluster_test.go | 6 ++--
lxd/api_internal.go | 2 +-
lxd/backup.go | 4 +--
lxd/certificates.go | 2 +-
lxd/cluster/connect.go | 8 +++---
lxd/cluster/notify.go | 6 ++--
lxd/cluster/notify_test.go | 4 +--
lxd/cluster/upgrade.go | 2 +-
lxd/container.go | 4 +--
lxd/container_backup.go | 6 ++--
lxd/container_console.go | 4 +--
lxd/container_exec.go | 4 +--
lxd/container_lxc.go | 52 ++++++++++++++++-----------------
lxd/container_patch.go | 2 +-
lxd/container_post.go | 6 ++--
lxd/container_put.go | 2 +-
lxd/container_snapshot.go | 14 ++++-----
lxd/container_state.go | 2 +-
lxd/container_test.go | 2 +-
lxd/containers_get.go | 56 ++++++++++++++++++------------------
lxd/containers_post.go | 26 ++++++++---------
lxd/images.go | 4 +--
lxd/init.go | 4 +--
lxd/main_init_auto.go | 2 +-
lxd/main_init_dump.go | 2 +-
lxd/main_init_interactive.go | 14 ++++-----
lxd/main_init_preseed.go | 2 +-
lxd/migrate_container.go | 2 +-
lxd/networks.go | 6 ++--
lxd/profiles.go | 2 +-
lxd/response.go | 4 +--
lxd/storage_pools.go | 8 +++---
34 files changed, 136 insertions(+), 136 deletions(-)
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 9bc2db9ea0..cf0b1aea2e 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -432,7 +432,7 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
if err != nil {
return SmartError(err)
}
- err = notifier(func(client lxd.ContainerServer) error {
+ err = notifier(func(client lxd.InstanceServer) error {
server, etag, err := client.GetServer()
if err != nil {
return err
diff --git a/lxd/api_cluster.go b/lxd/api_cluster.go
index 544d417894..6980defc7b 100644
--- a/lxd/api_cluster.go
+++ b/lxd/api_cluster.go
@@ -573,7 +573,7 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
return
}
- imageImport := func(client lxd.ContainerServer, fingerprint string, projects []string) error {
+ imageImport := func(client lxd.InstanceServer, fingerprint string, projects []string) error {
err := imageImportFromNode(filepath.Join(d.os.VarDir, "images"), client, fingerprint)
if err != nil {
return err
@@ -677,7 +677,7 @@ func clusterPutDisable(d *Daemon) Response {
//
// We pass to LXD client instances, one connected to ourselves (the joining
// node) and one connected to the target cluster node to join.
-func clusterInitMember(d, client lxd.ContainerServer, memberConfig []api.ClusterMemberConfigKey) error {
+func clusterInitMember(d, client lxd.InstanceServer, memberConfig []api.ClusterMemberConfigKey) error {
data := initDataNode{}
// Fetch all pools currently defined in the cluster.
@@ -788,7 +788,7 @@ func clusterInitMember(d, client lxd.ContainerServer, memberConfig []api.Cluster
// mode can be accepted into the cluster and obtain joining information such as
// the cluster private certificate.
func clusterAcceptMember(
- client lxd.ContainerServer,
+ client lxd.InstanceServer,
name, address string, schema, apiExt int,
pools []api.StoragePool, networks []api.Network) (*internalClusterPostAcceptResponse, error) {
diff --git a/lxd/api_cluster_test.go b/lxd/api_cluster_test.go
index d45d53e7a1..edaa436ce9 100644
--- a/lxd/api_cluster_test.go
+++ b/lxd/api_cluster_test.go
@@ -596,7 +596,7 @@ func TestCluster_NodeRename(t *testing.T) {
// Test helper for cluster-related APIs.
type clusterFixture struct {
t *testing.T
- clients map[*Daemon]lxd.ContainerServer
+ clients map[*Daemon]lxd.InstanceServer
daemons []*Daemon
}
@@ -716,9 +716,9 @@ func (f *clusterFixture) RegisterCertificate(daemon1, daemon2 *Daemon, name, pas
// Get a client for the given daemon connected via UNIX socket, creating one if
// needed.
-func (f *clusterFixture) ClientUnix(daemon *Daemon) lxd.ContainerServer {
+func (f *clusterFixture) ClientUnix(daemon *Daemon) lxd.InstanceServer {
if f.clients == nil {
- f.clients = make(map[*Daemon]lxd.ContainerServer)
+ f.clients = make(map[*Daemon]lxd.InstanceServer)
}
client, ok := f.clients[daemon]
if !ok {
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 588f75e05a..06b05f3f35 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -531,7 +531,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
defer initPool.StoragePoolUmount()
}
- existingSnapshots := []*api.ContainerSnapshot{}
+ existingSnapshots := []*api.InstanceSnapshot{}
needForce := fmt.Errorf(`The snapshot does not exist on disk. Pass ` +
`"force" to discard non-existing snapshots`)
diff --git a/lxd/backup.go b/lxd/backup.go
index fd58faf2d0..b3b252d17d 100644
--- a/lxd/backup.go
+++ b/lxd/backup.go
@@ -146,8 +146,8 @@ func (b *backup) Delete() error {
return doBackupDelete(b.state, b.name, b.container.Name())
}
-func (b *backup) Render() *api.ContainerBackup {
- return &api.ContainerBackup{
+func (b *backup) Render() *api.InstanceBackup {
+ return &api.InstanceBackup{
Name: strings.SplitN(b.name, "/", 2)[1],
CreatedAt: b.creationDate,
ExpiresAt: b.expiryDate,
diff --git a/lxd/certificates.go b/lxd/certificates.go
index 3630ec59ec..463c04a9d2 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -204,7 +204,7 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
req.Name = name
req.Type = "client"
- err = notifier(func(client lxd.ContainerServer) error {
+ err = notifier(func(client lxd.InstanceServer) error {
return client.CreateCertificate(req)
})
if err != nil {
diff --git a/lxd/cluster/connect.go b/lxd/cluster/connect.go
index b97625ffd1..fb8634f0ad 100644
--- a/lxd/cluster/connect.go
+++ b/lxd/cluster/connect.go
@@ -19,7 +19,7 @@ import (
// If 'notify' switch is true, then the user agent will be set to the special
// value 'lxd-cluster-notifier', which can be used in some cases to distinguish
// between a regular client request and an internal cluster request.
-func Connect(address string, cert *shared.CertInfo, notify bool) (lxd.ContainerServer, error) {
+func Connect(address string, cert *shared.CertInfo, notify bool) (lxd.InstanceServer, error) {
args := &lxd.ConnectionArgs{
TLSServerCert: string(cert.PublicKey()),
TLSClientCert: string(cert.PublicKey()),
@@ -38,7 +38,7 @@ func Connect(address string, cert *shared.CertInfo, notify bool) (lxd.ContainerS
// running the container with the given name. If it's not the local node will
// connect to it and return the connected client, otherwise it will just return
// nil.
-func ConnectIfContainerIsRemote(cluster *db.Cluster, project, name string, cert *shared.CertInfo, instanceType instance.Type) (lxd.ContainerServer, error) {
+func ConnectIfContainerIsRemote(cluster *db.Cluster, project, name string, cert *shared.CertInfo, instanceType instance.Type) (lxd.InstanceServer, error) {
var address string // Node address
err := cluster.Transaction(func(tx *db.ClusterTx) error {
var err error
@@ -62,7 +62,7 @@ func ConnectIfContainerIsRemote(cluster *db.Cluster, project, name string, cert
//
// If there is more than one node with a matching volume name, an error is
// returned.
-func ConnectIfVolumeIsRemote(cluster *db.Cluster, poolID int64, volumeName string, volumeType int, cert *shared.CertInfo) (lxd.ContainerServer, error) {
+func ConnectIfVolumeIsRemote(cluster *db.Cluster, poolID int64, volumeName string, volumeType int, cert *shared.CertInfo) (lxd.InstanceServer, error) {
var addresses []string // Node addresses
err := cluster.Transaction(func(tx *db.ClusterTx) error {
var err error
@@ -99,7 +99,7 @@ func ConnectIfVolumeIsRemote(cluster *db.Cluster, poolID int64, volumeName strin
return Connect(address, cert, false)
}
-// SetupTrust is a convenience around ContainerServer.CreateCertificate that
+// SetupTrust is a convenience around InstanceServer.CreateCertificate that
// adds the given client certificate to the trusted pool of the cluster at the
// given address, using the given password.
func SetupTrust(cert, targetAddress, targetCert, targetPassword string) error {
diff --git a/lxd/cluster/notify.go b/lxd/cluster/notify.go
index 1b6b5dce59..f360719402 100644
--- a/lxd/cluster/notify.go
+++ b/lxd/cluster/notify.go
@@ -16,7 +16,7 @@ import (
// Notifier is a function that invokes the given function against each node in
// the cluster excluding the invoking one.
-type Notifier func(hook func(lxd.ContainerServer) error) error
+type Notifier func(hook func(lxd.InstanceServer) error) error
// NotifierPolicy can be used to tweak the behavior of NewNotifier in case of
// some nodes are down.
@@ -38,7 +38,7 @@ func NewNotifier(state *state.State, cert *shared.CertInfo, policy NotifierPolic
// Fast-track the case where we're not clustered at all.
if address == "" {
- nullNotifier := func(func(lxd.ContainerServer) error) error { return nil }
+ nullNotifier := func(func(lxd.InstanceServer) error) error { return nil }
return nullNotifier, nil
}
@@ -73,7 +73,7 @@ func NewNotifier(state *state.State, cert *shared.CertInfo, policy NotifierPolic
return nil, err
}
- notifier := func(hook func(lxd.ContainerServer) error) error {
+ notifier := func(hook func(lxd.InstanceServer) error) error {
errs := make([]error, len(peers))
wg := sync.WaitGroup{}
wg.Add(len(peers))
diff --git a/lxd/cluster/notify_test.go b/lxd/cluster/notify_test.go
index 848820619e..0d00379e96 100644
--- a/lxd/cluster/notify_test.go
+++ b/lxd/cluster/notify_test.go
@@ -33,7 +33,7 @@ func TestNewNotifier(t *testing.T) {
require.NoError(t, err)
peers := make(chan string, 2)
- hook := func(client lxd.ContainerServer) error {
+ hook := func(client lxd.InstanceServer) error {
server, _, err := client.GetServer()
require.NoError(t, err)
peers <- server.Config["cluster.https_address"].(string)
@@ -88,7 +88,7 @@ func TestNewNotify_NotifyAlive(t *testing.T) {
assert.NoError(t, err)
i := 0
- hook := func(client lxd.ContainerServer) error {
+ hook := func(client lxd.InstanceServer) error {
i++
return nil
}
diff --git a/lxd/cluster/upgrade.go b/lxd/cluster/upgrade.go
index a3ddd4d75a..b7eabc2424 100644
--- a/lxd/cluster/upgrade.go
+++ b/lxd/cluster/upgrade.go
@@ -22,7 +22,7 @@ func NotifyUpgradeCompleted(state *state.State, cert *shared.CertInfo) error {
if err != nil {
return err
}
- return notifier(func(client lxd.ContainerServer) error {
+ return notifier(func(client lxd.InstanceServer) error {
info, err := client.GetConnectionInfo()
if err != nil {
return errors.Wrap(err, "failed to get connection info")
diff --git a/lxd/container.go b/lxd/container.go
index cd0edf6a2e..7b9e63c0a5 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -272,8 +272,8 @@ type container interface {
// Status
Render() (interface{}, interface{}, error)
- RenderFull() (*api.ContainerFull, interface{}, error)
- RenderState() (*api.ContainerState, error)
+ RenderFull() (*api.InstanceFull, interface{}, error)
+ RenderState() (*api.InstanceState, error)
IsPrivileged() bool
IsRunning() bool
IsFrozen() bool
diff --git a/lxd/container_backup.go b/lxd/container_backup.go
index 7f950a222c..242ec3ad3a 100644
--- a/lxd/container_backup.go
+++ b/lxd/container_backup.go
@@ -50,7 +50,7 @@ func containerBackupsGet(d *Daemon, r *http.Request) Response {
}
resultString := []string{}
- resultMap := []*api.ContainerBackup{}
+ resultMap := []*api.InstanceBackup{}
for _, backup := range backups {
if !recursion {
@@ -112,7 +112,7 @@ func containerBackupsPost(d *Daemon, r *http.Request) Response {
return InternalError(err)
}
- req := api.ContainerBackupsPost{}
+ req := api.InstanceBackupsPost{}
err = json.Unmarshal(body, &req)
if err != nil {
@@ -237,7 +237,7 @@ func containerBackupPost(d *Daemon, r *http.Request) Response {
return response
}
- req := api.ContainerBackupPost{}
+ req := api.InstanceBackupPost{}
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
return BadRequest(err)
diff --git a/lxd/container_console.go b/lxd/container_console.go
index 2ccf9094f4..e590dc6dc4 100644
--- a/lxd/container_console.go
+++ b/lxd/container_console.go
@@ -166,7 +166,7 @@ func (s *consoleWs) Do(op *operation) error {
break
}
- command := api.ContainerConsoleControl{}
+ command := api.InstanceConsoleControl{}
err = json.Unmarshal(buf, &command)
if err != nil {
@@ -265,7 +265,7 @@ func containerConsolePost(d *Daemon, r *http.Request) Response {
project := projectParam(r)
name := mux.Vars(r)["name"]
- post := api.ContainerConsolePost{}
+ post := api.InstanceConsolePost{}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return BadRequest(err)
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 358b88f555..7e507d879f 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -202,7 +202,7 @@ func (s *execWs) Do(op *operation) error {
break
}
- command := api.ContainerExecControl{}
+ command := api.InstanceExecControl{}
if err := json.Unmarshal(buf, &command); err != nil {
logger.Debugf("Failed to unmarshal control socket command: %s", err)
@@ -351,7 +351,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
project := projectParam(r)
name := mux.Vars(r)["name"]
- post := api.ContainerExecPost{}
+ post := api.InstanceExecPost{}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return BadRequest(err)
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index d9835a142e..f9df25a005 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3236,7 +3236,7 @@ func (c *containerLXC) Render() (interface{}, interface{}, error) {
// Prepare the ETag
etag := []interface{}{c.expiryDate}
- ct := api.ContainerSnapshot{
+ ct := api.InstanceSnapshot{
CreatedAt: c.creationDate,
ExpandedConfig: c.expandedConfig,
ExpandedDevices: c.expandedDevices.CloneNative(),
@@ -3264,7 +3264,7 @@ func (c *containerLXC) Render() (interface{}, interface{}, error) {
}
statusCode := lxcStatusCode(cState)
- ct := api.Container{
+ ct := api.Instance{
ExpandedConfig: c.expandedConfig,
ExpandedDevices: c.expandedDevices.CloneNative(),
Name: c.name,
@@ -3286,7 +3286,7 @@ func (c *containerLXC) Render() (interface{}, interface{}, error) {
return &ct, etag, nil
}
-func (c *containerLXC) RenderFull() (*api.ContainerFull, interface{}, error) {
+func (c *containerLXC) RenderFull() (*api.InstanceFull, interface{}, error) {
if c.IsSnapshot() {
return nil, nil, fmt.Errorf("RenderFull only works with containers")
}
@@ -3298,7 +3298,7 @@ func (c *containerLXC) RenderFull() (*api.ContainerFull, interface{}, error) {
}
// Convert to ContainerFull
- ct := api.ContainerFull{Container: *base.(*api.Container)}
+ ct := api.InstanceFull{Instance: *base.(*api.Instance)}
// Add the ContainerState
ct.State, err = c.RenderState()
@@ -3319,10 +3319,10 @@ func (c *containerLXC) RenderFull() (*api.ContainerFull, interface{}, error) {
}
if ct.Snapshots == nil {
- ct.Snapshots = []api.ContainerSnapshot{}
+ ct.Snapshots = []api.InstanceSnapshot{}
}
- ct.Snapshots = append(ct.Snapshots, *render.(*api.ContainerSnapshot))
+ ct.Snapshots = append(ct.Snapshots, *render.(*api.InstanceSnapshot))
}
// Add the ContainerBackups
@@ -3335,7 +3335,7 @@ func (c *containerLXC) RenderFull() (*api.ContainerFull, interface{}, error) {
render := backup.Render()
if ct.Backups == nil {
- ct.Backups = []api.ContainerBackup{}
+ ct.Backups = []api.InstanceBackup{}
}
ct.Backups = append(ct.Backups, *render)
@@ -3344,13 +3344,13 @@ func (c *containerLXC) RenderFull() (*api.ContainerFull, interface{}, error) {
return &ct, etag, nil
}
-func (c *containerLXC) RenderState() (*api.ContainerState, error) {
+func (c *containerLXC) RenderState() (*api.InstanceState, error) {
cState, err := c.getLxcState()
if err != nil {
return nil, err
}
statusCode := lxcStatusCode(cState)
- status := api.ContainerState{
+ status := api.InstanceState{
Status: statusCode.String(),
StatusCode: statusCode,
}
@@ -3994,10 +3994,10 @@ func (c *containerLXC) VolatileSet(changes map[string]string) error {
}
type backupFile struct {
- Container *api.Container `yaml:"container"`
- Snapshots []*api.ContainerSnapshot `yaml:"snapshots"`
- Pool *api.StoragePool `yaml:"pool"`
- Volume *api.StorageVolume `yaml:"volume"`
+ Container *api.Instance `yaml:"container"`
+ Snapshots []*api.InstanceSnapshot `yaml:"snapshots"`
+ Pool *api.StoragePool `yaml:"pool"`
+ Volume *api.StorageVolume `yaml:"volume"`
}
func writeBackupFile(c container) error {
@@ -4022,7 +4022,7 @@ func writeBackupFile(c container) error {
return errors.Wrap(err, "Failed to get snapshots")
}
- var sis []*api.ContainerSnapshot
+ var sis []*api.InstanceSnapshot
for _, s := range snapshots {
si, _, err := s.Render()
@@ -4030,7 +4030,7 @@ func writeBackupFile(c container) error {
return err
}
- sis = append(sis, si.(*api.ContainerSnapshot))
+ sis = append(sis, si.(*api.InstanceSnapshot))
}
poolName, err := c.StoragePool()
@@ -4050,7 +4050,7 @@ func writeBackupFile(c container) error {
}
data, err := yaml.Marshal(&backupFile{
- Container: ci.(*api.Container),
+ Container: ci.(*api.Instance),
Snapshots: sis,
Pool: pool,
Volume: volume,
@@ -5969,8 +5969,8 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
return nil, 0, attachedPid, nil
}
-func (c *containerLXC) cpuState() api.ContainerStateCPU {
- cpu := api.ContainerStateCPU{}
+func (c *containerLXC) cpuState() api.InstanceStateCPU {
+ cpu := api.InstanceStateCPU{}
if !c.state.OS.CGroupCPUacctController {
return cpu
@@ -5994,8 +5994,8 @@ func (c *containerLXC) cpuState() api.ContainerStateCPU {
return cpu
}
-func (c *containerLXC) diskState() map[string]api.ContainerStateDisk {
- disk := map[string]api.ContainerStateDisk{}
+func (c *containerLXC) diskState() map[string]api.InstanceStateDisk {
+ disk := map[string]api.InstanceStateDisk{}
// Initialize storage interface for the container.
err := c.initStorage()
@@ -6017,14 +6017,14 @@ func (c *containerLXC) diskState() map[string]api.ContainerStateDisk {
continue
}
- disk[dev.Name] = api.ContainerStateDisk{Usage: usage}
+ disk[dev.Name] = api.InstanceStateDisk{Usage: usage}
}
return disk
}
-func (c *containerLXC) memoryState() api.ContainerStateMemory {
- memory := api.ContainerStateMemory{}
+func (c *containerLXC) memoryState() api.InstanceStateMemory {
+ memory := api.InstanceStateMemory{}
if !c.state.OS.CGroupMemoryController {
return memory
@@ -6067,8 +6067,8 @@ func (c *containerLXC) memoryState() api.ContainerStateMemory {
return memory
}
-func (c *containerLXC) networkState() map[string]api.ContainerStateNetwork {
- result := map[string]api.ContainerStateNetwork{}
+func (c *containerLXC) networkState() map[string]api.InstanceStateNetwork {
+ result := map[string]api.InstanceStateNetwork{}
pid := c.InitPID()
if pid < 1 {
@@ -6105,7 +6105,7 @@ func (c *containerLXC) networkState() map[string]api.ContainerStateNetwork {
// setns() + netns_getifaddrs() style retrieval.
c.state.OS.NetnsGetifaddrs = false
- nw := map[string]api.ContainerStateNetwork{}
+ nw := map[string]api.InstanceStateNetwork{}
err = json.Unmarshal([]byte(out), &nw)
if err != nil {
logger.Error("Failure to read forkgetnet json", log.Ctx{"container": c.name, "err": err})
diff --git a/lxd/container_patch.go b/lxd/container_patch.go
index c7be6f8f64..829a16a423 100644
--- a/lxd/container_patch.go
+++ b/lxd/container_patch.go
@@ -65,7 +65,7 @@ func containerPatch(d *Daemon, r *http.Request) Response {
return BadRequest(err)
}
- req := api.ContainerPut{}
+ req := api.InstancePut{}
if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
return BadRequest(err)
}
diff --git a/lxd/container_post.go b/lxd/container_post.go
index e030133472..10729cc3e8 100644
--- a/lxd/container_post.go
+++ b/lxd/container_post.go
@@ -157,7 +157,7 @@ func containerPost(d *Daemon, r *http.Request) Response {
return BadRequest(err)
}
- req := api.ContainerPost{}
+ req := api.InstancePost{}
err = json.NewDecoder(rdr2).Decode(&req)
if err != nil {
return BadRequest(err)
@@ -348,11 +348,11 @@ func containerPostClusteringMigrate(d *Daemon, c container, oldName, newName, ne
// If the destination name is not set, we have generated a random name for
// the new container, so we need to rename it.
if isSameName {
- containerPost := api.ContainerPost{
+ instancePost := api.InstancePost{
Name: oldName,
}
- op, err := dest.RenameContainer(destName, containerPost)
+ op, err := dest.RenameInstance(destName, instancePost)
if err != nil {
return errors.Wrap(err, "Failed to issue rename container API request")
}
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 6ac1426f96..ae29ec4a3e 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -55,7 +55,7 @@ func containerPut(d *Daemon, r *http.Request) Response {
return PreconditionFailed(err)
}
- configRaw := api.ContainerPut{}
+ configRaw := api.InstancePut{}
if err := json.NewDecoder(r.Body).Decode(&configRaw); err != nil {
return BadRequest(err)
}
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index d800cb0590..585a22b02b 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -41,7 +41,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
recursion := util.IsRecursionRequest(r)
resultString := []string{}
- resultMap := []*api.ContainerSnapshot{}
+ resultMap := []*api.InstanceSnapshot{}
if !recursion {
snaps, err := d.cluster.ContainerGetSnapshots(project, cname)
@@ -76,7 +76,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
continue
}
- resultMap = append(resultMap, render.(*api.ContainerSnapshot))
+ resultMap = append(resultMap, render.(*api.InstanceSnapshot))
}
}
@@ -117,7 +117,7 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
return SmartError(err)
}
- req := api.ContainerSnapshotsPost{}
+ req := api.InstanceSnapshotsPost{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return BadRequest(err)
}
@@ -257,7 +257,7 @@ func snapshotPut(d *Daemon, r *http.Request, sc container, name string) Response
return InternalError(err)
}
- configRaw := api.ContainerSnapshotPut{}
+ configRaw := api.InstanceSnapshotPut{}
err = json.Unmarshal(body, &configRaw)
if err != nil {
@@ -308,7 +308,7 @@ func snapshotGet(sc container, name string) Response {
return SmartError(err)
}
- return SyncResponse(true, render.(*api.ContainerSnapshot))
+ return SyncResponse(true, render.(*api.InstanceSnapshot))
}
func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string) Response {
@@ -329,13 +329,13 @@ func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string
rdr2 := ioutil.NopCloser(bytes.NewBuffer(body))
rdr3 := ioutil.NopCloser(bytes.NewBuffer(body))
- req := api.ContainerPost{}
+ req := api.InstancePost{}
err = json.NewDecoder(rdr2).Decode(&req)
if err != nil {
return BadRequest(err)
}
- reqNew := api.ContainerSnapshotPost{}
+ reqNew := api.InstanceSnapshotPost{}
err = json.NewDecoder(rdr3).Decode(&reqNew)
if err != nil {
return BadRequest(err)
diff --git a/lxd/container_state.go b/lxd/container_state.go
index c47309546a..96497daaec 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -65,7 +65,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
return response
}
- raw := api.ContainerStatePut{}
+ raw := api.InstanceStatePut{}
// We default to -1 (i.e. no timeout) here instead of 0 (instant
// timeout).
diff --git a/lxd/container_test.go b/lxd/container_test.go
index 55f9fe3443..878b88b24c 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -105,7 +105,7 @@ func (suite *containerTestSuite) TestContainer_ProfilesOverwriteDefaultNic() {
out, _, err := c.Render()
suite.Req.Nil(err)
- state := out.(*api.Container)
+ state := out.(*api.Instance)
defer c.Delete()
suite.Equal(
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 60193da982..2dbb3a2713 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -44,8 +44,8 @@ func containersGet(d *Daemon, r *http.Request) Response {
func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
resultString := []string{}
- resultList := []*api.Container{}
- resultFullList := []*api.ContainerFull{}
+ resultList := []*api.Instance{}
+ resultFullList := []*api.InstanceFull{}
resultMu := sync.Mutex{}
// Instance type.
@@ -101,9 +101,9 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
}
// Append containers to list and handle errors
- resultListAppend := func(name string, c api.Container, err error) {
+ resultListAppend := func(name string, c api.Instance, err error) {
if err != nil {
- c = api.Container{
+ c = api.Instance{
Name: name,
Status: api.Error.String(),
StatusCode: api.Error,
@@ -115,9 +115,9 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
resultMu.Unlock()
}
- resultFullListAppend := func(name string, c api.ContainerFull, err error) {
+ resultFullListAppend := func(name string, c api.InstanceFull, err error) {
if err != nil {
- c = api.ContainerFull{Container: api.Container{
+ c = api.InstanceFull{Instance: api.Instance{
Name: name,
Status: api.Error.String(),
StatusCode: api.Error,
@@ -143,9 +143,9 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
if recursion > 0 && address == "0.0.0.0" {
for _, container := range containers {
if recursion == 1 {
- resultListAppend(container, api.Container{}, fmt.Errorf("unavailable"))
+ resultListAppend(container, api.Instance{}, fmt.Errorf("unavailable"))
} else {
- resultFullListAppend(container, api.ContainerFull{}, fmt.Errorf("unavailable"))
+ resultFullListAppend(container, api.InstanceFull{}, fmt.Errorf("unavailable"))
}
}
@@ -164,7 +164,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
cs, err := doContainersGetFromNode(project, address, cert, instanceType)
if err != nil {
for _, name := range containers {
- resultListAppend(name, api.Container{}, err)
+ resultListAppend(name, api.Instance{}, err)
}
return
@@ -180,7 +180,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
cs, err := doContainersFullGetFromNode(project, address, cert, instanceType)
if err != nil {
for _, name := range containers {
- resultFullListAppend(name, api.ContainerFull{}, err)
+ resultFullListAppend(name, api.InstanceFull{}, err)
}
return
@@ -224,9 +224,9 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
if recursion == 1 {
c, _, err := nodeCts[container].Render()
if err != nil {
- resultListAppend(container, api.Container{}, err)
+ resultListAppend(container, api.Instance{}, err)
} else {
- resultListAppend(container, *c.(*api.Container), err)
+ resultListAppend(container, *c.(*api.Instance), err)
}
continue
@@ -234,7 +234,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
c, _, err := nodeCts[container].RenderFull()
if err != nil {
- resultFullListAppend(container, api.ContainerFull{}, err)
+ resultFullListAppend(container, api.InstanceFull{}, err)
} else {
resultFullListAppend(container, *c, err)
}
@@ -276,8 +276,8 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
// Fetch information about the containers on the given remote node, using the
// rest API and with a timeout of 30 seconds.
-func doContainersGetFromNode(project, node string, cert *shared.CertInfo, instanceType instance.Type) ([]api.Container, error) {
- f := func() ([]api.Container, error) {
+func doContainersGetFromNode(project, node string, cert *shared.CertInfo, instanceType instance.Type) ([]api.Instance, error) {
+ f := func() ([]api.Instance, error) {
client, err := cluster.Connect(node, cert, true)
if err != nil {
return nil, errors.Wrapf(err, "Failed to connect to node %s", node)
@@ -285,9 +285,9 @@ func doContainersGetFromNode(project, node string, cert *shared.CertInfo, instan
client = client.UseProject(project)
- containers, err := client.GetContainers()
+ containers, err := client.GetInstances(api.InstanceType(instanceType.String()))
if err != nil {
- return nil, errors.Wrapf(err, "Failed to get containers from node %s", node)
+ return nil, errors.Wrapf(err, "Failed to get instances from node %s", node)
}
return containers, nil
@@ -296,7 +296,7 @@ func doContainersGetFromNode(project, node string, cert *shared.CertInfo, instan
timeout := time.After(30 * time.Second)
done := make(chan struct{})
- var containers []api.Container
+ var containers []api.Instance
var err error
go func() {
@@ -306,15 +306,15 @@ func doContainersGetFromNode(project, node string, cert *shared.CertInfo, instan
select {
case <-timeout:
- err = fmt.Errorf("Timeout getting containers from node %s", node)
+ err = fmt.Errorf("Timeout getting instances from node %s", node)
case <-done:
}
return containers, err
}
-func doContainersFullGetFromNode(project, node string, cert *shared.CertInfo, instanceType instance.Type) ([]api.ContainerFull, error) {
- f := func() ([]api.ContainerFull, error) {
+func doContainersFullGetFromNode(project, node string, cert *shared.CertInfo, instanceType instance.Type) ([]api.InstanceFull, error) {
+ f := func() ([]api.InstanceFull, error) {
client, err := cluster.Connect(node, cert, true)
if err != nil {
return nil, errors.Wrapf(err, "Failed to connect to node %s", node)
@@ -322,30 +322,30 @@ func doContainersFullGetFromNode(project, node string, cert *shared.CertInfo, in
client = client.UseProject(project)
- containers, err := client.GetContainersFull()
+ instances, err := client.GetInstancesFull(api.InstanceType(instanceType.String()))
if err != nil {
- return nil, errors.Wrapf(err, "Failed to get containers from node %s", node)
+ return nil, errors.Wrapf(err, "Failed to get instances from node %s", node)
}
- return containers, nil
+ return instances, nil
}
timeout := time.After(30 * time.Second)
done := make(chan struct{})
- var containers []api.ContainerFull
+ var instances []api.InstanceFull
var err error
go func() {
- containers, err = f()
+ instances, err = f()
done <- struct{}{}
}()
select {
case <-timeout:
- err = fmt.Errorf("Timeout getting containers from node %s", node)
+ err = fmt.Errorf("Timeout getting instances from node %s", node)
case <-done:
}
- return containers, err
+ return instances, err
}
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index e6858c0077..b087a6b762 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -30,7 +30,7 @@ import (
"github.com/lxc/lxd/shared/osarch"
)
-func createFromImage(d *Daemon, project string, req *api.ContainersPost) Response {
+func createFromImage(d *Daemon, project string, req *api.InstancesPost) Response {
var hash string
var err error
@@ -149,7 +149,7 @@ func createFromImage(d *Daemon, project string, req *api.ContainersPost) Respons
return OperationResponse(op)
}
-func createFromNone(d *Daemon, project string, req *api.ContainersPost) Response {
+func createFromNone(d *Daemon, project string, req *api.InstancesPost) Response {
args := db.ContainerArgs{
Project: project,
Config: req.Config,
@@ -185,7 +185,7 @@ func createFromNone(d *Daemon, project string, req *api.ContainersPost) Response
return OperationResponse(op)
}
-func createFromMigration(d *Daemon, project string, req *api.ContainersPost) Response {
+func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Response {
// Validate migration mode
if req.Source.Mode != "pull" && req.Source.Mode != "push" {
return NotImplemented(fmt.Errorf("Mode '%s' not implemented", req.Source.Mode))
@@ -427,7 +427,7 @@ func createFromMigration(d *Daemon, project string, req *api.ContainersPost) Res
return OperationResponse(op)
}
-func createFromCopy(d *Daemon, project string, req *api.ContainersPost) Response {
+func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response {
if req.Source.Source == "" {
return BadRequest(fmt.Errorf("must specify a source container"))
}
@@ -679,7 +679,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
}
// Parse the request
- req := api.ContainersPost{}
+ req := api.InstancesPost{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return BadRequest(err)
}
@@ -716,8 +716,8 @@ func containersPost(d *Daemon, r *http.Request) Response {
client = client.UseProject(project)
client = client.UseTarget(targetNode)
- logger.Debugf("Forward container post request to %s", address)
- op, err := client.CreateContainer(req)
+ logger.Debugf("Forward instance post request to %s", address)
+ op, err := client.CreateInstance(req)
if err != nil {
return SmartError(err)
}
@@ -798,7 +798,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
}
}
-func containerFindStoragePool(d *Daemon, project string, req *api.ContainersPost) (string, string, string, map[string]string, Response) {
+func containerFindStoragePool(d *Daemon, project string, req *api.InstancesPost) (string, string, string, map[string]string, Response) {
// Grab the container's root device if one is specified
storagePool := ""
storagePoolProfile := ""
@@ -855,7 +855,7 @@ func containerFindStoragePool(d *Daemon, project string, req *api.ContainersPost
return storagePool, storagePoolProfile, localRootDiskDeviceKey, localRootDiskDevice, nil
}
-func clusterCopyContainerInternal(d *Daemon, source container, project string, req *api.ContainersPost) Response {
+func clusterCopyContainerInternal(d *Daemon, source container, project string, req *api.InstancesPost) Response {
name := req.Source.Source
// Locate the source of the container
@@ -892,27 +892,27 @@ func clusterCopyContainerInternal(d *Daemon, source container, project string, r
if shared.IsSnapshot(req.Source.Source) {
cName, sName, _ := shared.ContainerGetParentAndSnapshotName(req.Source.Source)
- pullReq := api.ContainerSnapshotPost{
+ pullReq := api.InstanceSnapshotPost{
Migration: true,
Live: req.Source.Live,
Name: req.Name,
}
- op, err := client.MigrateContainerSnapshot(cName, sName, pullReq)
+ op, err := client.MigrateInstanceSnapshot(cName, sName, pullReq)
if err != nil {
return SmartError(err)
}
opAPI = op.Get()
} else {
- pullReq := api.ContainerPost{
+ pullReq := api.InstancePost{
Migration: true,
Live: req.Source.Live,
ContainerOnly: req.Source.ContainerOnly,
Name: req.Name,
}
- op, err := client.MigrateContainer(req.Source.Source, pullReq)
+ op, err := client.MigrateInstance(req.Source.Source, pullReq)
if err != nil {
return SmartError(err)
}
diff --git a/lxd/images.go b/lxd/images.go
index 383ae47643..2e3dfe594c 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -1406,7 +1406,7 @@ func imageDelete(d *Daemon, r *http.Request) Response {
return nil
}
- err = notifier(func(client lxd.ContainerServer) error {
+ err = notifier(func(client lxd.InstanceServer) error {
op, err := client.DeleteImage(imgInfo.Fingerprint)
if err != nil {
return errors.Wrap(err, "Failed to request to delete image from peer node")
@@ -1951,7 +1951,7 @@ func imageSecret(d *Daemon, r *http.Request) Response {
return OperationResponse(op)
}
-func imageImportFromNode(imagesDir string, client lxd.ContainerServer, fingerprint string) error {
+func imageImportFromNode(imagesDir string, client lxd.InstanceServer, fingerprint string) error {
// Prepare the temp files
buildDir, err := ioutil.TempDir(imagesDir, "lxd_build_")
if err != nil {
diff --git a/lxd/init.go b/lxd/init.go
index 74c16ef7b4..ba961a5b85 100644
--- a/lxd/init.go
+++ b/lxd/init.go
@@ -26,7 +26,7 @@ type initDataCluster struct {
// It's used both by the 'lxd init' command and by the PUT /1.0/cluster API.
//
// In case of error, the returned function can be used to revert the changes.
-func initDataNodeApply(d lxd.ContainerServer, config initDataNode) (func(), error) {
+func initDataNodeApply(d lxd.InstanceServer, config initDataNode) (func(), error) {
// Handle reverts
reverts := []func(){}
revert := func() {
@@ -339,7 +339,7 @@ func initDataNodeApply(d lxd.ContainerServer, config initDataNode) (func(), erro
// Helper to initialize LXD clustering.
//
// Used by the 'lxd init' command.
-func initDataClusterApply(d lxd.ContainerServer, config *initDataCluster) error {
+func initDataClusterApply(d lxd.InstanceServer, config *initDataCluster) error {
if config == nil || !config.Enabled {
return nil
}
diff --git a/lxd/main_init_auto.go b/lxd/main_init_auto.go
index a819bca3e2..4022c91f30 100644
--- a/lxd/main_init_auto.go
+++ b/lxd/main_init_auto.go
@@ -11,7 +11,7 @@ import (
"github.com/lxc/lxd/shared/api"
)
-func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*cmdInitData, error) {
+func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.InstanceServer) (*cmdInitData, error) {
// Sanity checks
if c.flagStorageBackend != "" && !shared.StringInSlice(c.flagStorageBackend, supportedStoragePoolDrivers) {
return nil, fmt.Errorf("The requested backend '%s' isn't supported by lxd init", c.flagStorageBackend)
diff --git a/lxd/main_init_dump.go b/lxd/main_init_dump.go
index 4ce72e3f90..087d123fa8 100644
--- a/lxd/main_init_dump.go
+++ b/lxd/main_init_dump.go
@@ -10,7 +10,7 @@ import (
"github.com/lxc/lxd/shared/api"
)
-func (c *cmdInit) RunDump(d lxd.ContainerServer) error {
+func (c *cmdInit) RunDump(d lxd.InstanceServer) error {
currentServer, _, err := d.GetServer()
if err != nil {
return errors.Wrap(err, "Failed to retrieve current server configuration")
diff --git a/lxd/main_init_interactive.go b/lxd/main_init_interactive.go
index af4e14ee6b..3abb254241 100644
--- a/lxd/main_init_interactive.go
+++ b/lxd/main_init_interactive.go
@@ -24,7 +24,7 @@ import (
"github.com/lxc/lxd/shared/idmap"
)
-func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*cmdInitData, error) {
+func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string, d lxd.InstanceServer) (*cmdInitData, error) {
// Initialize config
config := cmdInitData{}
config.Node.Config = map[string]interface{}{}
@@ -98,7 +98,7 @@ func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string, d lxd.Contai
return &config, nil
}
-func (c *cmdInit) askClustering(config *cmdInitData, d lxd.ContainerServer) error {
+func (c *cmdInit) askClustering(config *cmdInitData, d lxd.InstanceServer) error {
if cli.AskBool("Would you like to use LXD clustering? (yes/no) [default=no]: ", "no") {
config.Cluster = &initDataCluster{}
config.Cluster.Enabled = true
@@ -209,7 +209,7 @@ func (c *cmdInit) askClustering(config *cmdInitData, d lxd.ContainerServer) erro
return nil
}
-func (c *cmdInit) askMAAS(config *cmdInitData, d lxd.ContainerServer) error {
+func (c *cmdInit) askMAAS(config *cmdInitData, d lxd.InstanceServer) error {
if !cli.AskBool("Would you like to connect to a MAAS server? (yes/no) [default=no]: ", "no") {
return nil
}
@@ -230,7 +230,7 @@ func (c *cmdInit) askMAAS(config *cmdInitData, d lxd.ContainerServer) error {
return nil
}
-func (c *cmdInit) askNetworking(config *cmdInitData, d lxd.ContainerServer) error {
+func (c *cmdInit) askNetworking(config *cmdInitData, d lxd.InstanceServer) error {
if config.Cluster != nil || !cli.AskBool("Would you like to create a new local network bridge? (yes/no) [default=yes]: ", "yes") {
// At this time, only the Ubuntu kernel supports the Fan, detect it
fanKernel := false
@@ -386,7 +386,7 @@ func (c *cmdInit) askNetworking(config *cmdInitData, d lxd.ContainerServer) erro
return nil
}
-func (c *cmdInit) askStorage(config *cmdInitData, d lxd.ContainerServer) error {
+func (c *cmdInit) askStorage(config *cmdInitData, d lxd.InstanceServer) error {
if config.Cluster != nil {
if cli.AskBool("Do you want to configure a new local storage pool? (yes/no) [default=yes]: ", "yes") {
err := c.askStoragePool(config, d, "local")
@@ -412,7 +412,7 @@ func (c *cmdInit) askStorage(config *cmdInitData, d lxd.ContainerServer) error {
return c.askStoragePool(config, d, "all")
}
-func (c *cmdInit) askStoragePool(config *cmdInitData, d lxd.ContainerServer, poolType string) error {
+func (c *cmdInit) askStoragePool(config *cmdInitData, d lxd.InstanceServer, poolType string) error {
// Figure out the preferred storage driver
availableBackends := c.availableStorageDrivers(poolType)
@@ -599,7 +599,7 @@ your Linux distribution and run "lxd init" again afterwards.
return nil
}
-func (c *cmdInit) askDaemon(config *cmdInitData, d lxd.ContainerServer) error {
+func (c *cmdInit) askDaemon(config *cmdInitData, d lxd.InstanceServer) error {
// Detect lack of uid/gid
idmapset, err := idmap.DefaultIdmapSet("", "")
if (err != nil || len(idmapset.Idmap) == 0 || idmapset.Usable() != nil) && shared.RunningInUserNS() {
diff --git a/lxd/main_init_preseed.go b/lxd/main_init_preseed.go
index 0f75481d6c..c6b5349494 100644
--- a/lxd/main_init_preseed.go
+++ b/lxd/main_init_preseed.go
@@ -11,7 +11,7 @@ import (
"github.com/lxc/lxd/client"
)
-func (c *cmdInit) RunPreseed(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*cmdInitData, error) {
+func (c *cmdInit) RunPreseed(cmd *cobra.Command, args []string, d lxd.InstanceServer) (*cmdInitData, error) {
// Read the YAML
bytes, err := ioutil.ReadAll(os.Stdin)
if err != nil {
diff --git a/lxd/migrate_container.go b/lxd/migrate_container.go
index 47e3e2d209..d33a9d6d6a 100644
--- a/lxd/migrate_container.go
+++ b/lxd/migrate_container.go
@@ -1133,7 +1133,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
}
}
-func (s *migrationSourceWs) ConnectContainerTarget(target api.ContainerPostTarget) error {
+func (s *migrationSourceWs) ConnectContainerTarget(target api.InstancePostTarget) error {
return s.ConnectTarget(target.Certificate, target.Operation, target.Websockets)
}
diff --git a/lxd/networks.go b/lxd/networks.go
index 1b15144929..cad3d9d1c5 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -266,7 +266,7 @@ func networksPostCluster(d *Daemon, req api.NetworksPost) error {
if err != nil {
return err
}
- notifyErr := notifier(func(client lxd.ContainerServer) error {
+ notifyErr := notifier(func(client lxd.InstanceServer) error {
server, _, err := client.GetServer()
if err != nil {
return err
@@ -491,7 +491,7 @@ func networkDelete(d *Daemon, r *http.Request) Response {
if err != nil {
return SmartError(err)
}
- err = notifier(func(client lxd.ContainerServer) error {
+ err = notifier(func(client lxd.InstanceServer) error {
return client.DeleteNetwork(name)
})
if err != nil {
@@ -824,7 +824,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) Response {
return SmartError(err)
}
- err = notifier(func(client lxd.ContainerServer) error {
+ err = notifier(func(client lxd.InstanceServer) error {
memberLeases, err := client.GetNetworkLeases(name)
if err != nil {
return err
diff --git a/lxd/profiles.go b/lxd/profiles.go
index cc2354320d..6c6e7c6c17 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -257,7 +257,7 @@ func profilePut(d *Daemon, r *http.Request) Response {
return SmartError(err)
}
- err = notifier(func(client lxd.ContainerServer) error {
+ err = notifier(func(client lxd.InstanceServer) error {
return client.UseProject(project).UpdateProfile(name, profile.ProfilePut, "")
})
if err != nil {
diff --git a/lxd/response.go b/lxd/response.go
index bd0980fb6b..7bca1b4e17 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -111,7 +111,7 @@ func SyncResponseHeaders(success bool, metadata interface{}, headers map[string]
var EmptySyncResponse = &syncResponse{success: true, metadata: make(map[string]interface{})}
type forwardedResponse struct {
- client lxd.ContainerServer
+ client lxd.InstanceServer
request *http.Request
}
@@ -154,7 +154,7 @@ func (r *forwardedResponse) String() string {
// ForwardedResponse takes a request directed to a node and forwards it to
// another node, writing back the response it gegs.
-func ForwardedResponse(client lxd.ContainerServer, request *http.Request) Response {
+func ForwardedResponse(client lxd.InstanceServer, request *http.Request) Response {
return &forwardedResponse{
client: client,
request: request,
diff --git a/lxd/storage_pools.go b/lxd/storage_pools.go
index 443323eee8..a9c5e60b10 100644
--- a/lxd/storage_pools.go
+++ b/lxd/storage_pools.go
@@ -237,7 +237,7 @@ func storagePoolsPostCluster(d *Daemon, req api.StoragePoolsPost) error {
if err != nil {
return err
}
- notifyErr := notifier(func(client lxd.ContainerServer) error {
+ notifyErr := notifier(func(client lxd.InstanceServer) error {
server, _, err := client.GetServer()
if err != nil {
return err
@@ -369,7 +369,7 @@ func storagePoolPut(d *Daemon, r *http.Request) Response {
if err != nil {
return SmartError(err)
}
- err = notifier(func(client lxd.ContainerServer) error {
+ err = notifier(func(client lxd.InstanceServer) error {
return client.UpdateStoragePool(poolName, req, r.Header.Get("If-Match"))
})
if err != nil {
@@ -456,7 +456,7 @@ func storagePoolPatch(d *Daemon, r *http.Request) Response {
if err != nil {
return SmartError(err)
}
- err = notifier(func(client lxd.ContainerServer) error {
+ err = notifier(func(client lxd.InstanceServer) error {
return client.UpdateStoragePool(poolName, req, r.Header.Get("If-Match"))
})
if err != nil {
@@ -599,7 +599,7 @@ func storagePoolDelete(d *Daemon, r *http.Request) Response {
if err != nil {
return SmartError(err)
}
- err = notifier(func(client lxd.ContainerServer) error {
+ err = notifier(func(client lxd.InstanceServer) error {
_, _, err := client.GetServer()
if err != nil {
return err
More information about the lxc-devel
mailing list