The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6197
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) ===
From c222666f66fd129e49a92f19dda1365a2c9392f0 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 12 Sep 2019 15:46:44 +0100 Subject: [PATCH 1/3] client/interfaces: Populates InstanceServer with rest of functions Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- client/interfaces.go | 57 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/client/interfaces.go b/client/interfaces.go index 2fe2e1aadb..a7179830d3 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 *ContainerCopyArgs) (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 *ContainerExecArgs) (op Operation, err error) + ConsoleInstance(instanceName string, console api.InstanceConsolePost, args *ContainerConsoleArgs) (op Operation, err error) + GetInstanceConsoleLog(instanceName string, args *ContainerConsoleLogArgs) (content io.ReadCloser, err error) + DeleteInstanceConsoleLog(instanceName string, args *ContainerConsoleLogArgs) (err error) + + GetInstanceFile(instanceName string, path string) (content io.ReadCloser, resp *ContainerFileResponse, err error) + CreateInstanceFile(instanceName string, path string, args ContainerFileArgs) (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 *ContainerSnapshotCopyArgs) (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 ContainerBackupArgs) (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) From 30b857bdf6717cf8d920818254624250cfd41d81 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 12 Sep 2019 15:50:34 +0100 Subject: [PATCH 2/3] client/lxd/instances: Adds instance related functions These functions use the /1.0/instances endpoints on the server. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- client/lxd_instances.go | 2154 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 2154 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..e2345814d8 --- /dev/null +++ b/client/lxd_instances.go @@ -0,0 +1,2154 @@ +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 ContainerBackupArgs) (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.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 *ContainerCopyArgs) (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.ContainerOnly { + 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.ContainerOnly = args.ContainerOnly + 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, + } + + // 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.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 *ContainerExecArgs) (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, path string) (io.ReadCloser, *ContainerFileResponse, 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": path}) + 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 := ContainerFileResponse{ + 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, path string, args ContainerFileArgs) 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(path)) + + 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, path 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(path)), 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 *ContainerSnapshotCopyArgs) (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 *ContainerConsoleArgs) (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 *ContainerConsoleLogArgs) (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 *ContainerConsoleLogArgs) 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 6b9769a8c026549a45f32228efe5fa69c3e6f262 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 12 Sep 2019 11:59:51 +0100 Subject: [PATCH 3/3] lxc: Switches cli tool to use InstanceServer Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxc/action.go | 4 ++-- lxc/config/remote.go | 4 ++-- lxc/console.go | 2 +- lxc/copy.go | 6 +++--- lxc/delete.go | 2 +- lxc/exec.go | 2 +- lxc/export.go | 2 +- lxc/file.go | 8 ++++---- lxc/image.go | 2 +- lxc/info.go | 6 +++--- lxc/init.go | 8 ++++---- lxc/list.go | 4 ++-- lxc/main.go | 6 +++--- lxc/monitor.go | 2 +- lxc/move.go | 4 ++-- lxc/project.go | 2 +- lxc/publish.go | 4 ++-- lxc/query.go | 2 +- lxc/remote.go | 20 ++++++++++---------- lxc/restore.go | 2 +- lxc/snapshot.go | 2 +- lxc/utils.go | 6 +++--- 22 files changed, 50 insertions(+), 50 deletions(-) diff --git a/lxc/action.go b/lxc/action.go index e41e5f530e..a1ac453bad 100644 --- a/lxc/action.go +++ b/lxc/action.go @@ -142,7 +142,7 @@ func (c *cmdAction) doAction(action string, conf *config.Config, nameArg string) return err } - d, err := conf.GetContainerServer(remote) + d, err := conf.GetInstanceServer(remote) if err != nil { return err } @@ -211,7 +211,7 @@ func (c *cmdAction) Run(cmd *cobra.Command, args []string) error { return nil } - d, err := conf.GetContainerServer(conf.DefaultRemote) + d, err := conf.GetInstanceServer(conf.DefaultRemote) if err != nil { return err } diff --git a/lxc/config/remote.go b/lxc/config/remote.go index cd7a7d8eca..40f64a9d9c 100644 --- a/lxc/config/remote.go +++ b/lxc/config/remote.go @@ -50,8 +50,8 @@ func (c *Config) ParseRemote(raw string) (string, string, error) { return result[0], result[1], nil } -// GetContainerServer returns a ContainerServer struct for the remote -func (c *Config) GetContainerServer(name string) (lxd.ContainerServer, error) { +// GetInstanceServer returns a InstanceServer struct for the remote +func (c *Config) GetInstanceServer(name string) (lxd.InstanceServer, error) { // Handle "local" on non-Linux if name == "local" && runtime.GOOS != "linux" { return nil, ErrNotLinux diff --git a/lxc/console.go b/lxc/console.go index 9404a0f89f..2b096e5074 100644 --- a/lxc/console.go +++ b/lxc/console.go @@ -120,7 +120,7 @@ func (c *cmdConsole) Run(cmd *cobra.Command, args []string) error { return err } - d, err := conf.GetContainerServer(remote) + d, err := conf.GetInstanceServer(remote) if err != nil { return err } diff --git a/lxc/copy.go b/lxc/copy.go index ae3c2ddba9..bd9d515888 100644 --- a/lxc/copy.go +++ b/lxc/copy.go @@ -88,19 +88,19 @@ func (c *cmdCopy) copyContainer(conf *config.Config, sourceResource string, } // Connect to the source host - source, err := conf.GetContainerServer(sourceRemote) + source, err := conf.GetInstanceServer(sourceRemote) if err != nil { return err } // Connect to the destination host - var dest lxd.ContainerServer + var dest lxd.InstanceServer if sourceRemote == destRemote { // Source and destination are the same dest = source } else { // Destination is different, connect to it - dest, err = conf.GetContainerServer(destRemote) + dest, err = conf.GetInstanceServer(destRemote) if err != nil { return err } diff --git a/lxc/delete.go b/lxc/delete.go index 8b89fc05d0..c6103c6b15 100644 --- a/lxc/delete.go +++ b/lxc/delete.go @@ -50,7 +50,7 @@ func (c *cmdDelete) promptDelete(name string) error { return nil } -func (c *cmdDelete) doDelete(d lxd.ContainerServer, name string) error { +func (c *cmdDelete) doDelete(d lxd.InstanceServer, name string) error { var op lxd.Operation var err error diff --git a/lxc/exec.go b/lxc/exec.go index 9447848757..31d4df9583 100644 --- a/lxc/exec.go +++ b/lxc/exec.go @@ -115,7 +115,7 @@ func (c *cmdExec) Run(cmd *cobra.Command, args []string) error { return err } - d, err := conf.GetContainerServer(remote) + d, err := conf.GetInstanceServer(remote) if err != nil { return err } diff --git a/lxc/export.go b/lxc/export.go index 6266bbe0b5..29929f3e91 100644 --- a/lxc/export.go +++ b/lxc/export.go @@ -58,7 +58,7 @@ func (c *cmdExport) Run(cmd *cobra.Command, args []string) error { return err } - d, err := conf.GetContainerServer(remote) + d, err := conf.GetInstanceServer(remote) if err != nil { return err } diff --git a/lxc/file.go b/lxc/file.go index 0c03d3c1a6..1945db217b 100644 --- a/lxc/file.go +++ b/lxc/file.go @@ -36,7 +36,7 @@ type cmdFile struct { flagRecursive bool } -func fileGetWrapper(server lxd.ContainerServer, container string, path string) (buf io.ReadCloser, resp *lxd.ContainerFileResponse, err error) { +func fileGetWrapper(server lxd.InstanceServer, container string, path string) (buf io.ReadCloser, resp *lxd.ContainerFileResponse, err error) { // Signal handling chSignal := make(chan os.Signal) signal.Notify(chSignal, os.Interrupt) @@ -648,7 +648,7 @@ func (c *cmdFilePush) Run(cmd *cobra.Command, args []string) error { return nil } -func (c *cmdFile) recursivePullFile(d lxd.ContainerServer, container string, p string, targetDir string) error { +func (c *cmdFile) recursivePullFile(d lxd.InstanceServer, container string, p string, targetDir string) error { buf, resp, err := d.GetContainerFile(container, p) if err != nil { return err @@ -723,7 +723,7 @@ func (c *cmdFile) recursivePullFile(d lxd.ContainerServer, container string, p s return nil } -func (c *cmdFile) recursivePushFile(d lxd.ContainerServer, container string, source string, target string) error { +func (c *cmdFile) recursivePushFile(d lxd.InstanceServer, container string, source string, target string) error { source = filepath.Clean(source) sourceDir, _ := filepath.Split(source) sourceLen := len(sourceDir) @@ -821,7 +821,7 @@ func (c *cmdFile) recursivePushFile(d lxd.ContainerServer, container string, sou return filepath.Walk(source, sendFile) } -func (c *cmdFile) recursiveMkdir(d lxd.ContainerServer, container string, p string, mode *os.FileMode, uid int64, gid int64) error { +func (c *cmdFile) recursiveMkdir(d lxd.InstanceServer, container string, p string, mode *os.FileMode, uid int64, gid int64) error { /* special case, every container has a /, we don't need to do anything */ if p == "/" { return nil diff --git a/lxc/image.go b/lxc/image.go index 538a8775f6..61c589486b 100644 --- a/lxc/image.go +++ b/lxc/image.go @@ -656,7 +656,7 @@ func (c *cmdImageImport) Run(cmd *cobra.Command, args []string) error { rootfsFile = shared.HostPath(filepath.Clean(rootfsFile)) } - d, err := conf.GetContainerServer(remote) + d, err := conf.GetInstanceServer(remote) if err != nil { return err } diff --git a/lxc/info.go b/lxc/info.go index dce77937cf..f33f5fe2bd 100644 --- a/lxc/info.go +++ b/lxc/info.go @@ -69,7 +69,7 @@ func (c *cmdInfo) Run(cmd *cobra.Command, args []string) error { } } - d, err := conf.GetContainerServer(remote) + d, err := conf.GetInstanceServer(remote) if err != nil { return err } @@ -302,7 +302,7 @@ func (c *cmdInfo) renderCPU(cpu api.ResourcesCPUSocket, prefix string) { } } -func (c *cmdInfo) remoteInfo(d lxd.ContainerServer) error { +func (c *cmdInfo) remoteInfo(d lxd.InstanceServer) error { // Targeting if c.flagTarget != "" { if !d.IsClustered() { @@ -417,7 +417,7 @@ func (c *cmdInfo) remoteInfo(d lxd.ContainerServer) error { return nil } -func (c *cmdInfo) containerInfo(d lxd.ContainerServer, remote config.Remote, name string, showLog bool) error { +func (c *cmdInfo) containerInfo(d lxd.InstanceServer, remote config.Remote, name string, showLog bool) error { // Sanity checks if c.flagTarget != "" { return fmt.Errorf(i18n.G("--target cannot be used with containers")) diff --git a/lxc/init.go b/lxc/init.go index 4586bf0884..65b2b0276d 100644 --- a/lxc/init.go +++ b/lxc/init.go @@ -73,7 +73,7 @@ func (c *cmdInit) Run(cmd *cobra.Command, args []string) error { return err } -func (c *cmdInit) create(conf *config.Config, args []string) (lxd.ContainerServer, string, error) { +func (c *cmdInit) create(conf *config.Config, args []string) (lxd.InstanceServer, string, error) { var name string var image string var remote string @@ -134,7 +134,7 @@ func (c *cmdInit) create(conf *config.Config, args []string) (lxd.ContainerServe } } - d, err := conf.GetContainerServer(remote) + d, err := conf.GetInstanceServer(remote) if err != nil { return nil, "", err } @@ -325,7 +325,7 @@ func (c *cmdInit) create(conf *config.Config, args []string) (lxd.ContainerServe return d, name, nil } -func (c *cmdInit) guessImage(conf *config.Config, d lxd.ContainerServer, remote string, iremote string, image string) (string, string) { +func (c *cmdInit) guessImage(conf *config.Config, d lxd.InstanceServer, remote string, iremote string, image string) (string, string) { if remote != iremote { return iremote, image } @@ -355,7 +355,7 @@ func (c *cmdInit) guessImage(conf *config.Config, d lxd.ContainerServer, remote return fields[0], fields[1] } -func (c *cmdInit) checkNetwork(d lxd.ContainerServer, name string) { +func (c *cmdInit) checkNetwork(d lxd.InstanceServer, name string) { ct, _, err := d.GetContainer(name) if err != nil { return diff --git a/lxc/list.go b/lxc/list.go index d3a484f83d..aaef88c392 100644 --- a/lxc/list.go +++ b/lxc/list.go @@ -201,7 +201,7 @@ func (c *cmdList) shouldShow(filters []string, state *api.Container) bool { return true } -func (c *cmdList) listContainers(conf *config.Config, d lxd.ContainerServer, cinfos []api.Container, filters []string, columns []column) error { +func (c *cmdList) listContainers(conf *config.Config, d lxd.InstanceServer, cinfos []api.Container, filters []string, columns []column) error { threads := 10 if len(cinfos) < threads { threads = len(cinfos) @@ -371,7 +371,7 @@ func (c *cmdList) Run(cmd *cobra.Command, args []string) error { } // Connect to LXD - d, err := conf.GetContainerServer(remote) + d, err := conf.GetInstanceServer(remote) if err != nil { return err } diff --git a/lxc/main.go b/lxc/main.go index afd68a23eb..c63d040273 100644 --- a/lxc/main.go +++ b/lxc/main.go @@ -365,12 +365,12 @@ func (c *cmdGlobal) PostRun(cmd *cobra.Command, args []string) error { } type remoteResource struct { - server lxd.ContainerServer + server lxd.InstanceServer name string } func (c *cmdGlobal) ParseServers(remotes ...string) ([]remoteResource, error) { - servers := map[string]lxd.ContainerServer{} + servers := map[string]lxd.InstanceServer{} resources := []remoteResource{} for _, remote := range remotes { @@ -394,7 +394,7 @@ func (c *cmdGlobal) ParseServers(remotes ...string) ([]remoteResource, error) { } // New connection - d, err := c.conf.GetContainerServer(remoteName) + d, err := c.conf.GetInstanceServer(remoteName) if err != nil { return nil, err } diff --git a/lxc/monitor.go b/lxc/monitor.go index 8c633375f9..22f98cfc7b 100644 --- a/lxc/monitor.go +++ b/lxc/monitor.go @@ -74,7 +74,7 @@ func (c *cmdMonitor) Run(cmd *cobra.Command, args []string) error { } } - d, err := conf.GetContainerServer(remote) + d, err := conf.GetInstanceServer(remote) if err != nil { return err } diff --git a/lxc/move.go b/lxc/move.go index 5985b38932..5d12ec1204 100644 --- a/lxc/move.go +++ b/lxc/move.go @@ -108,7 +108,7 @@ func (c *cmdMove) Run(cmd *cobra.Command, args []string) error { return fmt.Errorf(i18n.G("Can't override configuration or profiles in local rename")) } - source, err := conf.GetContainerServer(sourceRemote) + source, err := conf.GetInstanceServer(sourceRemote) if err != nil { return err } @@ -212,7 +212,7 @@ func moveClusterContainer(conf *config.Config, sourceResource, destResource, tar } // Connect to the source host - source, err := conf.GetContainerServer(sourceRemote) + source, err := conf.GetInstanceServer(sourceRemote) if err != nil { return errors.Wrap(err, i18n.G("Failed to connect to cluster member")) } diff --git a/lxc/project.go b/lxc/project.go index 0624c6fabe..35d2b5af7d 100644 --- a/lxc/project.go +++ b/lxc/project.go @@ -693,7 +693,7 @@ func (c *cmdProjectSwitch) Run(cmd *cobra.Command, args []string) error { } // Make sure the project exists - d, err := conf.GetContainerServer(remote) + d, err := conf.GetInstanceServer(remote) if err != nil { return err } diff --git a/lxc/publish.go b/lxc/publish.go index bba6c5ceb4..3b5031779d 100644 --- a/lxc/publish.go +++ b/lxc/publish.go @@ -83,14 +83,14 @@ func (c *cmdPublish) Run(cmd *cobra.Command, args []string) error { return fmt.Errorf(i18n.G("There is no \"image name\". Did you want an alias?")) } - d, err := conf.GetContainerServer(iRemote) + d, err := conf.GetInstanceServer(iRemote) if err != nil { return err } s := d if cRemote != iRemote { - s, err = conf.GetContainerServer(cRemote) + s, err = conf.GetInstanceServer(cRemote) if err != nil { return err } diff --git a/lxc/query.go b/lxc/query.go index 5e8f948a5b..88180c98ed 100644 --- a/lxc/query.go +++ b/lxc/query.go @@ -68,7 +68,7 @@ func (c *cmdQuery) Run(cmd *cobra.Command, args []string) error { } // Attempt to connect - d, err := conf.GetContainerServer(remote) + d, err := conf.GetInstanceServer(remote) if err != nil { return err } diff --git a/lxc/remote.go b/lxc/remote.go index 62dbf0ff63..e6fd440da0 100644 --- a/lxc/remote.go +++ b/lxc/remote.go @@ -225,7 +225,7 @@ func (c *cmdRemoteAdd) Run(cmd *cobra.Command, args []string) error { if c.flagPublic { d, err = conf.GetImageServer(server) } else { - d, err = conf.GetContainerServer(server) + d, err = conf.GetInstanceServer(server) } // Handle Unix socket connections @@ -286,7 +286,7 @@ func (c *cmdRemoteAdd) Run(cmd *cobra.Command, args []string) error { if c.flagPublic { d, err = conf.GetImageServer(server) } else { - d, err = conf.GetContainerServer(server) + d, err = conf.GetInstanceServer(server) } if err != nil { @@ -301,11 +301,11 @@ func (c *cmdRemoteAdd) Run(cmd *cobra.Command, args []string) error { } if c.flagAuthType == "candid" { - d.(lxd.ContainerServer).RequireAuthenticated(false) + d.(lxd.InstanceServer).RequireAuthenticated(false) } // Get server information - srv, _, err := d.(lxd.ContainerServer).GetServer() + srv, _, err := d.(lxd.InstanceServer).GetServer() if err != nil { return err } @@ -321,14 +321,14 @@ func (c *cmdRemoteAdd) Run(cmd *cobra.Command, args []string) error { conf.Remotes[server] = remote // Re-setup the client - d, err = conf.GetContainerServer(server) + d, err = conf.GetInstanceServer(server) if err != nil { return err } - d.(lxd.ContainerServer).RequireAuthenticated(false) + d.(lxd.InstanceServer).RequireAuthenticated(false) - srv, _, err = d.(lxd.ContainerServer).GetServer() + srv, _, err = d.(lxd.InstanceServer).GetServer() if err != nil { return err } @@ -380,16 +380,16 @@ func (c *cmdRemoteAdd) Run(cmd *cobra.Command, args []string) error { } req.Type = "client" - err = d.(lxd.ContainerServer).CreateCertificate(req) + err = d.(lxd.InstanceServer).CreateCertificate(req) if err != nil { return err } } else { - d.(lxd.ContainerServer).RequireAuthenticated(true) + d.(lxd.InstanceServer).RequireAuthenticated(true) } // And check if trusted now - srv, _, err = d.(lxd.ContainerServer).GetServer() + srv, _, err = d.(lxd.InstanceServer).GetServer() if err != nil { return err } diff --git a/lxc/restore.go b/lxc/restore.go index 2bbc26065e..acf134cb3d 100644 --- a/lxc/restore.go +++ b/lxc/restore.go @@ -53,7 +53,7 @@ func (c *cmdRestore) Run(cmd *cobra.Command, args []string) error { return err } - d, err := conf.GetContainerServer(remote) + d, err := conf.GetInstanceServer(remote) if err != nil { return err } diff --git a/lxc/snapshot.go b/lxc/snapshot.go index c98db99634..d5b008e16b 100644 --- a/lxc/snapshot.go +++ b/lxc/snapshot.go @@ -58,7 +58,7 @@ func (c *cmdSnapshot) Run(cmd *cobra.Command, args []string) error { return err } - d, err := conf.GetContainerServer(remote) + d, err := conf.GetInstanceServer(remote) if err != nil { return err } diff --git a/lxc/utils.go b/lxc/utils.go index 4de856bc1b..3c866b5c60 100644 --- a/lxc/utils.go +++ b/lxc/utils.go @@ -130,7 +130,7 @@ func runBatch(names []string, action func(name string) error) []batchResult { } // Add a device to a container -func containerDeviceAdd(client lxd.ContainerServer, name string, devName string, dev map[string]string) error { +func containerDeviceAdd(client lxd.InstanceServer, name string, devName string, dev map[string]string) error { // Get the container entry container, etag, err := client.GetContainer(name) if err != nil { @@ -154,7 +154,7 @@ func containerDeviceAdd(client lxd.ContainerServer, name string, devName string, } // Add a device to a profile -func profileDeviceAdd(client lxd.ContainerServer, name string, devName string, dev map[string]string) error { +func profileDeviceAdd(client lxd.InstanceServer, name string, devName string, dev map[string]string) error { // Get the profile entry profile, profileEtag, err := client.GetProfile(name) if err != nil { @@ -179,7 +179,7 @@ func profileDeviceAdd(client lxd.ContainerServer, name string, devName string, d } // Create the specified image alises, updating those that already exist -func ensureImageAliases(client lxd.ContainerServer, aliases []api.ImageAlias, fingerprint string) error { +func ensureImageAliases(client lxd.InstanceServer, aliases []api.ImageAlias, fingerprint string) error { if len(aliases) == 0 { return nil }
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel