The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6193
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) === - Adds InstanceServer interface to `client` package which embeds old ContainerServer interface. - Adds InstanceServer specific connection and factory functions. Includes https://github.com/lxc/lxd/pull/6191
From bdb0a4e19c57f51391609d83df1dd36f19fe155a Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 11 Sep 2019 11:11:22 +0100 Subject: [PATCH 1/9] lxd/db/containers: Adds instanceType filter to ContainerNodeAddress - Supports instance.TypeAny if no filter is needed. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/db/containers.go | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/lxd/db/containers.go b/lxd/db/containers.go index 068da2532a..0334215e06 100644 --- a/lxd/db/containers.go +++ b/lxd/db/containers.go @@ -175,30 +175,53 @@ SELECT instances.name FROM instances // with the given name in the given project. // // It returns the empty string if the container is hosted on this node. -func (c *ClusterTx) ContainerNodeAddress(project string, name string) (string, error) { +func (c *ClusterTx) ContainerNodeAddress(project string, name string, instanceType instance.Type) (string, error) { var stmt string - args := []interface{}{project} + + args := make([]interface{}, 0, 4) // Expect up to 4 filters. + var filters strings.Builder + + // Project filter. + filters.WriteString("projects.name = ?") + args = append(args, project) + + // Instance type filter. + if instanceType != instance.TypeAny { + filters.WriteString(" AND instances.type = ?") + args = append(args, instanceType) + } if strings.Contains(name, shared.SnapshotDelimiter) { parts := strings.SplitN(name, shared.SnapshotDelimiter, 2) - stmt = ` + + // Instance name filter. + filters.WriteString(" AND instances.name = ?") + args = append(args, parts[0]) + + // Snapshot name filter. + filters.WriteString(" AND instances_snapshots.name = ?") + args = append(args, parts[1]) + + stmt = fmt.Sprintf(` SELECT nodes.id, nodes.address FROM nodes JOIN instances ON instances.node_id = nodes.id JOIN projects ON projects.id = instances.project_id JOIN instances_snapshots ON instances_snapshots.instance_id = instances.id - WHERE projects.name = ? AND instances.name = ? AND instances_snapshots.name = ? -` - args = append(args, parts[0], parts[1]) + WHERE %s +`, filters.String()) } else { - stmt = ` + // Instance name filter. + filters.WriteString(" AND instances.name = ?") + args = append(args, name) + + stmt = fmt.Sprintf(` SELECT nodes.id, nodes.address FROM nodes JOIN instances ON instances.node_id = nodes.id JOIN projects ON projects.id = instances.project_id - WHERE projects.name = ? AND instances.name = ? -` - args = append(args, name) + WHERE %s +`, filters.String()) } var address string From 95c2715955df6f90095304cbb93a176302577eb9 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 11 Sep 2019 11:12:49 +0100 Subject: [PATCH 2/9] lxd/cluster/connect: Adds instanceType filter to ConnectIfContainerIsRemote Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/cluster/connect.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lxd/cluster/connect.go b/lxd/cluster/connect.go index 710d164fe6..b97625ffd1 100644 --- a/lxd/cluster/connect.go +++ b/lxd/cluster/connect.go @@ -7,6 +7,7 @@ import ( lxd "github.com/lxc/lxd/client" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" "github.com/pkg/errors" @@ -37,11 +38,11 @@ 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) (lxd.ContainerServer, error) { +func ConnectIfContainerIsRemote(cluster *db.Cluster, project, name string, cert *shared.CertInfo, instanceType instance.Type) (lxd.ContainerServer, error) { var address string // Node address err := cluster.Transaction(func(tx *db.ClusterTx) error { var err error - address, err = tx.ContainerNodeAddress(project, name) + address, err = tx.ContainerNodeAddress(project, name, instanceType) return err }) if err != nil { From 8d898dffcf39d1183cf47da5b7d6772037b41c30 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 11 Sep 2019 11:13:47 +0100 Subject: [PATCH 3/9] lxd/response: Adds instanceType filter to ForwardedResponseIfContainerIsRemote Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/response.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lxd/response.go b/lxd/response.go index 76b58b3b3c..bd0980fb6b 100644 --- a/lxd/response.go +++ b/lxd/response.go @@ -18,6 +18,7 @@ import ( lxd "github.com/lxc/lxd/client" "github.com/lxc/lxd/lxd/cluster" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/lxd/util" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" @@ -191,9 +192,9 @@ func ForwardedResponseIfTargetIsRemote(d *Daemon, request *http.Request) Respons // ForwardedResponseIfContainerIsRemote redirects a request to the node running // the container with the given name. If the container is local, nothing gets // done and nil is returned. -func ForwardedResponseIfContainerIsRemote(d *Daemon, r *http.Request, project, name string) (Response, error) { +func ForwardedResponseIfContainerIsRemote(d *Daemon, r *http.Request, project, name string, instanceType instance.Type) (Response, error) { cert := d.endpoints.NetworkCert() - client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, name, cert) + client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, name, cert, instanceType) if err != nil { return nil, err } From 6d22940c5d1c6870ae5a22b98d7477406948f4b2 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 11 Sep 2019 11:14:10 +0100 Subject: [PATCH 4/9] lxd: Updates use of ForwardedResponseIfContainerIsRemote to supply instanceType - This has the effect of validating whether the supplied instance name exists as the same type as the context of the endpoint. - Instance type is derived from the context of the endpoint. - If context is /1.0/containers then instance.TypeContainer is used. - If context is /1.0/instances then instance.TypeAny is used. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/container_backup.go | 49 ++++++++++++++++++++++++++++++++++----- lxd/container_console.go | 18 ++++++++++++-- lxd/container_delete.go | 10 +++++++- lxd/container_exec.go | 12 +++++++--- lxd/container_file.go | 10 +++++++- lxd/container_get.go | 11 ++++++++- lxd/container_logs.go | 26 ++++++++++++++++++--- lxd/container_metadata.go | 44 +++++++++++++++++++++++++++++------ lxd/container_patch.go | 10 +++++++- lxd/container_post.go | 18 ++++++++++---- lxd/container_put.go | 10 +++++++- lxd/container_snapshot.go | 25 +++++++++++++++++--- lxd/container_state.go | 18 ++++++++++++-- lxd/containers_post.go | 2 +- lxd/images.go | 13 +++++++---- 15 files changed, 235 insertions(+), 41 deletions(-) diff --git a/lxd/container_backup.go b/lxd/container_backup.go index bbf436e126..7f950a222c 100644 --- a/lxd/container_backup.go +++ b/lxd/container_backup.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/lxd/util" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" @@ -18,11 +19,17 @@ import ( ) func containerBackupsGet(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) cname := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname, instanceType) if err != nil { return SmartError(err) } @@ -64,11 +71,17 @@ func containerBackupsGet(d *Daemon, r *http.Request) Response { } func containerBackupsPost(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } @@ -176,12 +189,18 @@ func containerBackupsPost(d *Daemon, r *http.Request) Response { } func containerBackupGet(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] backupName := mux.Vars(r)["backupName"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } @@ -199,12 +218,18 @@ func containerBackupGet(d *Daemon, r *http.Request) Response { } func containerBackupPost(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] backupName := mux.Vars(r)["backupName"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } @@ -253,12 +278,18 @@ func containerBackupPost(d *Daemon, r *http.Request) Response { } func containerBackupDelete(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] backupName := mux.Vars(r)["backupName"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } @@ -294,12 +325,18 @@ func containerBackupDelete(d *Daemon, r *http.Request) Response { } func containerBackupExportGet(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] backupName := mux.Vars(r)["backupName"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } diff --git a/lxd/container_console.go b/lxd/container_console.go index c498f6cad7..2ccf9094f4 100644 --- a/lxd/container_console.go +++ b/lxd/container_console.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "strconv" + "strings" "sync" "syscall" @@ -18,6 +19,7 @@ import ( "github.com/lxc/lxd/lxd/cluster" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/lxd/util" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" @@ -254,6 +256,12 @@ func (s *consoleWs) Do(op *operation) error { } func containerConsolePost(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] @@ -270,7 +278,7 @@ func containerConsolePost(d *Daemon, r *http.Request) Response { // Forward the request if the container is remote. cert := d.endpoints.NetworkCert() - client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, name, cert) + client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, name, cert, instanceType) if err != nil { return SmartError(err) } @@ -343,11 +351,17 @@ func containerConsolePost(d *Daemon, r *http.Request) Response { } func containerConsoleLogGet(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Forward the request if the container is remote. - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } diff --git a/lxd/container_delete.go b/lxd/container_delete.go index 6e6d653928..319f7cd853 100644 --- a/lxd/container_delete.go +++ b/lxd/container_delete.go @@ -3,17 +3,25 @@ package main import ( "fmt" "net/http" + "strings" "github.com/gorilla/mux" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/instance" ) func containerDelete(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } diff --git a/lxd/container_exec.go b/lxd/container_exec.go index e53fad8727..358b88f555 100644 --- a/lxd/container_exec.go +++ b/lxd/container_exec.go @@ -19,13 +19,13 @@ import ( "github.com/lxc/lxd/lxd/cluster" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" + log "github.com/lxc/lxd/shared/log15" "github.com/lxc/lxd/shared/logger" "github.com/lxc/lxd/shared/netutils" "github.com/lxc/lxd/shared/version" - - log "github.com/lxc/lxd/shared/log15" ) type execWs struct { @@ -342,6 +342,12 @@ func (s *execWs) Do(op *operation) error { } func containerExecPost(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] @@ -357,7 +363,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response { // Forward the request if the container is remote. cert := d.endpoints.NetworkCert() - client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, name, cert) + client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, name, cert, instanceType) if err != nil { return SmartError(err) } diff --git a/lxd/container_file.go b/lxd/container_file.go index e9358c091a..21f8b33693 100644 --- a/lxd/container_file.go +++ b/lxd/container_file.go @@ -7,17 +7,25 @@ import ( "net/http" "os" "path/filepath" + "strings" "github.com/gorilla/mux" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/shared" ) func containerFileHandler(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } diff --git a/lxd/container_get.go b/lxd/container_get.go index 565699364d..7dacde2f60 100644 --- a/lxd/container_get.go +++ b/lxd/container_get.go @@ -2,16 +2,25 @@ package main import ( "net/http" + "strings" "github.com/gorilla/mux" + + "github.com/lxc/lxd/lxd/instance" ) func containerGet(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } diff --git a/lxd/container_logs.go b/lxd/container_logs.go index 92e026d3e2..72b3dfa80e 100644 --- a/lxd/container_logs.go +++ b/lxd/container_logs.go @@ -9,6 +9,7 @@ import ( "github.com/gorilla/mux" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/version" ) @@ -38,11 +39,18 @@ func containerLogsGet(d *Daemon, r *http.Request) Response { * However, we should check this name and ensure it's a valid container * name just so that people can't list arbitrary directories. */ + + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } @@ -84,11 +92,17 @@ func validLogFileName(fname string) bool { } func containerLogGet(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } @@ -115,11 +129,17 @@ func containerLogGet(d *Daemon, r *http.Request) Response { } func containerLogDelete(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } diff --git a/lxd/container_metadata.go b/lxd/container_metadata.go index adfce53c57..78b7f182c9 100644 --- a/lxd/container_metadata.go +++ b/lxd/container_metadata.go @@ -10,20 +10,26 @@ import ( "path/filepath" "strings" - "gopkg.in/yaml.v2" - "github.com/gorilla/mux" + "gopkg.in/yaml.v2" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" ) func containerMetadataGet(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } @@ -75,11 +81,17 @@ func containerMetadataGet(d *Daemon, r *http.Request) Response { } func containerMetadataPut(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } @@ -124,11 +136,17 @@ func containerMetadataPut(d *Daemon, r *http.Request) Response { // Return a list of templates used in a container or the content of a template func containerMetadataTemplatesGet(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } @@ -213,11 +231,17 @@ func containerMetadataTemplatesGet(d *Daemon, r *http.Request) Response { // Add a container template file func containerMetadataTemplatesPostPut(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } @@ -280,12 +304,18 @@ func containerMetadataTemplatesPostPut(d *Daemon, r *http.Request) Response { // Delete a container template func containerMetadataTemplatesDelete(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } diff --git a/lxd/container_patch.go b/lxd/container_patch.go index 18f454dcf0..c7be6f8f64 100644 --- a/lxd/container_patch.go +++ b/lxd/container_patch.go @@ -6,11 +6,13 @@ import ( "fmt" "io/ioutil" "net/http" + "strings" "github.com/gorilla/mux" "github.com/lxc/lxd/lxd/db" deviceConfig "github.com/lxc/lxd/lxd/device/config" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/lxd/util" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" @@ -18,13 +20,19 @@ import ( ) func containerPatch(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) // Get the container name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } diff --git a/lxd/container_post.go b/lxd/container_post.go index 3865b646c1..e030133472 100644 --- a/lxd/container_post.go +++ b/lxd/container_post.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "net/http" + "strings" "github.com/gorilla/mux" "github.com/pborman/uuid" @@ -14,6 +15,7 @@ import ( lxd "github.com/lxc/lxd/client" "github.com/lxc/lxd/lxd/cluster" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/instance" driver "github.com/lxc/lxd/lxd/storage" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" @@ -27,6 +29,12 @@ var internalClusterContainerMovedCmd = APIEndpoint{ } func containerPost(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] @@ -70,7 +78,7 @@ func containerPost(d *Daemon, r *http.Request) Response { targetNodeOffline = node.IsOffline(config.OfflineThreshold()) // Load source node. - address, err := tx.ContainerNodeAddress(project, name) + address, err := tx.ContainerNodeAddress(project, name, instanceType) if err != nil { return errors.Wrap(err, "Failed to get address of container's node") } @@ -121,7 +129,7 @@ func containerPost(d *Daemon, r *http.Request) Response { // and we'll either forward the request or load the container. if targetNode == "" || !sourceNodeOffline { // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } @@ -181,7 +189,7 @@ func containerPost(d *Daemon, r *http.Request) Response { return SmartError(err) } if pool.Driver == "ceph" { - return containerPostClusteringMigrateWithCeph(d, c, project, name, req.Name, targetNode) + return containerPostClusteringMigrateWithCeph(d, c, project, name, req.Name, targetNode, instanceType) } // If this is not a ceph-based container, make sure @@ -393,7 +401,7 @@ func containerPostClusteringMigrate(d *Daemon, c container, oldName, newName, ne } // Special case migrating a container backed by ceph across two cluster nodes. -func containerPostClusteringMigrateWithCeph(d *Daemon, c container, project, oldName, newName, newNode string) Response { +func containerPostClusteringMigrateWithCeph(d *Daemon, c container, project, oldName, newName, newNode string, instanceType instance.Type) Response { run := func(*operation) error { // If source node is online (i.e. we're serving the request on // it, and c != nil), let's unmap the RBD volume locally @@ -467,7 +475,7 @@ func containerPostClusteringMigrateWithCeph(d *Daemon, c container, project, old // Create the container mount point on the target node cert := d.endpoints.NetworkCert() - client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, newName, cert) + client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, newName, cert, instanceType) if err != nil { return errors.Wrap(err, "Failed to connect to target node") } diff --git a/lxd/container_put.go b/lxd/container_put.go index 27b5be9f43..6ac1426f96 100644 --- a/lxd/container_put.go +++ b/lxd/container_put.go @@ -4,11 +4,13 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "github.com/gorilla/mux" "github.com/lxc/lxd/lxd/db" deviceConfig "github.com/lxc/lxd/lxd/device/config" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/lxd/state" "github.com/lxc/lxd/lxd/util" "github.com/lxc/lxd/shared" @@ -21,13 +23,19 @@ import ( * the named snapshot */ func containerPut(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) // Get the container name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go index 67fe2e71d4..d800cb0590 100644 --- a/lxd/container_snapshot.go +++ b/lxd/container_snapshot.go @@ -13,6 +13,7 @@ import ( "github.com/gorilla/mux" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/lxd/util" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" @@ -20,11 +21,17 @@ import ( ) func containerSnapshotsGet(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) cname := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname, instanceType) if err != nil { return SmartError(err) } @@ -81,11 +88,17 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response { } func containerSnapshotsPost(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } @@ -170,11 +183,17 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response { } func containerSnapshotHandler(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) containerName := mux.Vars(r)["name"] snapshotName := mux.Vars(r)["snapshotName"] - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, containerName) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, containerName, instanceType) if err != nil { return SmartError(err) } diff --git a/lxd/container_state.go b/lxd/container_state.go index 8b86a0a362..c47309546a 100644 --- a/lxd/container_state.go +++ b/lxd/container_state.go @@ -4,21 +4,29 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "time" "github.com/gorilla/mux" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" ) func containerState(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } @@ -39,11 +47,17 @@ func containerState(d *Daemon, r *http.Request) Response { } func containerStatePut(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) name := mux.Vars(r)["name"] // Handle requests targeted to a container on a different node - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { return SmartError(err) } diff --git a/lxd/containers_post.go b/lxd/containers_post.go index 106c1e3feb..e6858c0077 100644 --- a/lxd/containers_post.go +++ b/lxd/containers_post.go @@ -864,7 +864,7 @@ func clusterCopyContainerInternal(d *Daemon, source container, project string, r var err error // Load source node. - nodeAddress, err = tx.ContainerNodeAddress(project, name) + nodeAddress, err = tx.ContainerNodeAddress(project, name, source.Type()) if err != nil { return errors.Wrap(err, "Failed to get address of container's node") } diff --git a/lxd/images.go b/lxd/images.go index 2ea8330c9f..383ae47643 100644 --- a/lxd/images.go +++ b/lxd/images.go @@ -23,12 +23,12 @@ import ( "github.com/gorilla/mux" "github.com/pkg/errors" - "gopkg.in/yaml.v2" lxd "github.com/lxc/lxd/client" "github.com/lxc/lxd/lxd/cluster" "github.com/lxc/lxd/lxd/db" + "github.com/lxc/lxd/lxd/instance" "github.com/lxc/lxd/lxd/node" "github.com/lxc/lxd/lxd/state" "github.com/lxc/lxd/lxd/task" @@ -36,12 +36,11 @@ import ( "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/ioprogress" + log "github.com/lxc/lxd/shared/log15" "github.com/lxc/lxd/shared/logger" "github.com/lxc/lxd/shared/logging" "github.com/lxc/lxd/shared/osarch" "github.com/lxc/lxd/shared/version" - - log "github.com/lxc/lxd/shared/log15" ) var imagesCmd = APIEndpoint{ @@ -637,6 +636,12 @@ func imageCreateInPool(d *Daemon, info *api.Image, storagePool string) error { } func imagesPost(d *Daemon, r *http.Request) Response { + // Instance type. + instanceType := instance.TypeAny + if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") { + instanceType = instance.TypeContainer + } + project := projectParam(r) var err error @@ -698,7 +703,7 @@ func imagesPost(d *Daemon, r *http.Request) Response { if name != "" { post.Seek(0, 0) r.Body = post - response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name) + response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType) if err != nil { cleanup(builddir, post) return SmartError(err) From 74f72089d45036705b0d996efef7326d8de82b7f Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 11 Sep 2019 17:30:59 +0100 Subject: [PATCH 5/9] lxd/instance/instance: Uses API instance types for string comparison Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/instance.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lxd/instance/instance.go b/lxd/instance/instance.go index 2c3a497e87..2ae566c1b3 100644 --- a/lxd/instance/instance.go +++ b/lxd/instance/instance.go @@ -2,6 +2,8 @@ package instance import ( "fmt" + + "github.com/lxc/lxd/shared/api" ) // Type indicates the type of instance. @@ -19,7 +21,7 @@ const ( // If an invalid name is supplied an error will be returned. func New(name string) (Type, error) { // If "container" or "" is supplied, return type as TypeContainer. - if name == "container" || name == "" { + if api.InstanceType(name) == api.InstanceTypeContainer || name == "" { return TypeContainer, nil } @@ -30,7 +32,7 @@ func New(name string) (Type, error) { // Returns empty string if value is not a valid instance type. func (instanceType Type) String() string { if instanceType == TypeContainer { - return "container" + return string(api.InstanceTypeContainer) } return "" From 3835468f0edfd6531d346b45ce464b06e71029cc Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Wed, 11 Sep 2019 17:31:26 +0100 Subject: [PATCH 6/9] shared/api: Adds new instance types - Removes earlier API extension comments. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- shared/api/instance.go | 132 ++++++++++++++++++++++++++++++++ shared/api/instance_backup.go | 31 ++++++++ shared/api/instance_console.go | 17 ++++ shared/api/instance_exec.go | 26 +++++++ shared/api/instance_snapshot.go | 60 +++++++++++++++ shared/api/instance_state.go | 84 ++++++++++++++++++++ 6 files changed, 350 insertions(+) create mode 100644 shared/api/instance.go create mode 100644 shared/api/instance_backup.go create mode 100644 shared/api/instance_console.go create mode 100644 shared/api/instance_exec.go create mode 100644 shared/api/instance_snapshot.go create mode 100644 shared/api/instance_state.go diff --git a/shared/api/instance.go b/shared/api/instance.go new file mode 100644 index 0000000000..7806b71adf --- /dev/null +++ b/shared/api/instance.go @@ -0,0 +1,132 @@ +package api + +import ( + "time" +) + +// InstanceType represents the type if instance being returned or requested via the API. +type InstanceType string + +// InstanceTypeAny defines the instance type value for requesting any instance type. +const InstanceTypeAny = InstanceType("") + +// InstanceTypeContainer defines the instance type value for a container. +const InstanceTypeContainer = InstanceType("container") + +// InstancesPost represents the fields available for a new LXD instance. +// +// API extension: instances +type InstancesPost struct { + InstancePut `yaml:",inline"` + + Name string `json:"name" yaml:"name"` + Source InstanceSource `json:"source" yaml:"source"` + InstanceType string `json:"instance_type" yaml:"instance_type"` + Type InstanceType `json:"type" yaml:"type"` +} + +// InstancePost represents the fields required to rename/move a LXD instance. +// +// API extension: instances +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"` + Target *InstancePostTarget `json:"target" yaml:"target"` +} + +// InstancePostTarget represents the migration target host and operation. +// +// API extension: instances +type InstancePostTarget struct { + Certificate string `json:"certificate" yaml:"certificate"` + Operation string `json:"operation,omitempty" yaml:"operation,omitempty"` + Websockets map[string]string `json:"secrets,omitempty" yaml:"secrets,omitempty"` +} + +// InstancePut represents the modifiable fields of a LXD instance. +// +// API extension: instances +type InstancePut struct { + Architecture string `json:"architecture" yaml:"architecture"` + Config map[string]string `json:"config" yaml:"config"` + Devices map[string]map[string]string `json:"devices" yaml:"devices"` + Ephemeral bool `json:"ephemeral" yaml:"ephemeral"` + Profiles []string `json:"profiles" yaml:"profiles"` + Restore string `json:"restore,omitempty" yaml:"restore,omitempty"` + Stateful bool `json:"stateful" yaml:"stateful"` + Description string `json:"description" yaml:"description"` +} + +// Instance represents a LXD instance. +// +// API extension: instances +type Instance struct { + InstancePut `yaml:",inline"` + + CreatedAt time.Time `json:"created_at" yaml:"created_at"` + ExpandedConfig map[string]string `json:"expanded_config" yaml:"expanded_config"` + ExpandedDevices map[string]map[string]string `json:"expanded_devices" yaml:"expanded_devices"` + Name string `json:"name" yaml:"name"` + Status string `json:"status" yaml:"status"` + StatusCode StatusCode `json:"status_code" yaml:"status_code"` + LastUsedAt time.Time `json:"last_used_at" yaml:"last_used_at"` + Location string `json:"location" yaml:"location"` + Type string `json:"type" yaml:"type"` +} + +// InstanceFull is a combination of Instance, InstanceBackup, InstanceState and InstanceSnapshot. +// +// API extension: instances +type InstanceFull struct { + Instance `yaml:",inline"` + + Backups []InstanceBackup `json:"backups" yaml:"backups"` + State *InstanceState `json:"state" yaml:"state"` + Snapshots []InstanceSnapshot `json:"snapshots" yaml:"snapshots"` +} + +// Writable converts a full Instance struct into a InstancePut struct (filters read-only fields). +// +// API extension: instances +func (c *Instance) Writable() InstancePut { + return c.InstancePut +} + +// IsActive checks whether the instance state indicates the instance is active. +// +// API extension: instances +func (c Instance) IsActive() bool { + switch c.StatusCode { + case Stopped: + return false + case Error: + return false + default: + return true + } +} + +// InstanceSource represents the creation source for a new instance. +// +// API extension: instances +type InstanceSource struct { + Type string `json:"type" yaml:"type"` + Certificate string `json:"certificate" yaml:"certificate"` + Alias string `json:"alias,omitempty" yaml:"alias,omitempty"` + Fingerprint string `json:"fingerprint,omitempty" yaml:"fingerprint,omitempty"` + Properties map[string]string `json:"properties,omitempty" yaml:"properties,omitempty"` + Server string `json:"server,omitempty" yaml:"server,omitempty"` + Secret string `json:"secret,omitempty" yaml:"secret,omitempty"` + Protocol string `json:"protocol,omitempty" yaml:"protocol,omitempty"` + BaseImage string `json:"base-image,omitempty" yaml:"base-image,omitempty"` + Mode string `json:"mode,omitempty" yaml:"mode,omitempty"` + Operation string `json:"operation,omitempty" yaml:"operation,omitempty"` + 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"` + 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 new file mode 100644 index 0000000000..64b282337b --- /dev/null +++ b/shared/api/instance_backup.go @@ -0,0 +1,31 @@ +package api + +import "time" + +// InstanceBackupsPost represents the fields available for a new LXD instance backup. +// +// API extension: instances +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"` + OptimizedStorage bool `json:"optimized_storage" yaml:"optimized_storage"` +} + +// InstanceBackup represents a LXD instance backup. +// +// API extension: instances +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"` + OptimizedStorage bool `json:"optimized_storage" yaml:"optimized_storage"` +} + +// InstanceBackupPost represents the fields available for the renaming of a instance backup. +// +// API extension: instances +type InstanceBackupPost struct { + Name string `json:"name" yaml:"name"` +} diff --git a/shared/api/instance_console.go b/shared/api/instance_console.go new file mode 100644 index 0000000000..614beb1245 --- /dev/null +++ b/shared/api/instance_console.go @@ -0,0 +1,17 @@ +package api + +// InstanceConsoleControl represents a message on the instance console "control" socket. +// +// API extension: instances +type InstanceConsoleControl struct { + Command string `json:"command" yaml:"command"` + Args map[string]string `json:"args" yaml:"args"` +} + +// InstanceConsolePost represents a LXD instance console request. +// +// API extension: instances +type InstanceConsolePost struct { + Width int `json:"width" yaml:"width"` + Height int `json:"height" yaml:"height"` +} diff --git a/shared/api/instance_exec.go b/shared/api/instance_exec.go new file mode 100644 index 0000000000..4579b2c89d --- /dev/null +++ b/shared/api/instance_exec.go @@ -0,0 +1,26 @@ +package api + +// InstanceExecControl represents a message on the instance exec "control" socket. +// +// API extension: instances +type InstanceExecControl struct { + Command string `json:"command" yaml:"command"` + Args map[string]string `json:"args" yaml:"args"` + Signal int `json:"signal" yaml:"signal"` +} + +// InstanceExecPost represents a LXD instance exec request. +// +// API extension: instances +type InstanceExecPost struct { + Command []string `json:"command" yaml:"command"` + WaitForWS bool `json:"wait-for-websocket" yaml:"wait-for-websocket"` + Interactive bool `json:"interactive" yaml:"interactive"` + Environment map[string]string `json:"environment" yaml:"environment"` + Width int `json:"width" yaml:"width"` + Height int `json:"height" yaml:"height"` + RecordOutput bool `json:"record-output" yaml:"record-output"` + User uint32 `json:"user" yaml:"user"` + Group uint32 `json:"group" yaml:"group"` + Cwd string `json:"cwd" yaml:"cwd"` +} diff --git a/shared/api/instance_snapshot.go b/shared/api/instance_snapshot.go new file mode 100644 index 0000000000..bdd93544b2 --- /dev/null +++ b/shared/api/instance_snapshot.go @@ -0,0 +1,60 @@ +package api + +import ( + "time" +) + +// InstanceSnapshotsPost represents the fields available for a new LXD instance snapshot. +// +// API extension: instances +type InstanceSnapshotsPost struct { + Name string `json:"name" yaml:"name"` + Stateful bool `json:"stateful" yaml:"stateful"` + + // API extension: snapshot_expiry_creation + ExpiresAt *time.Time `json:"expires_at" yaml:"expires_at"` +} + +// InstanceSnapshotPost represents the fields required to rename/move a LXD instance snapshot. +// +// API extension: instances +type InstanceSnapshotPost struct { + Name string `json:"name" yaml:"name"` + Migration bool `json:"migration" yaml:"migration"` + Target *InstancePostTarget `json:"target" yaml:"target"` + Live bool `json:"live,omitempty" yaml:"live,omitempty"` +} + +// InstanceSnapshotPut represents the modifiable fields of a LXD instance snapshot. +// +// API extension: instances +type InstanceSnapshotPut struct { + Architecture string `json:"architecture" yaml:"architecture"` + Config map[string]string `json:"config" yaml:"config"` + Devices map[string]map[string]string `json:"devices" yaml:"devices"` + Ephemeral bool `json:"ephemeral" yaml:"ephemeral"` + Profiles []string `json:"profiles" yaml:"profiles"` + ExpiresAt time.Time `json:"expires_at" yaml:"expires_at"` +} + +// InstanceSnapshot represents a LXD instance snapshot. +// +// API extension: instances +type InstanceSnapshot struct { + InstanceSnapshotPut `yaml:",inline"` + + CreatedAt time.Time `json:"created_at" yaml:"created_at"` + ExpandedConfig map[string]string `json:"expanded_config" yaml:"expanded_config"` + ExpandedDevices map[string]map[string]string `json:"expanded_devices" yaml:"expanded_devices"` + LastUsedAt time.Time `json:"last_used_at" yaml:"last_used_at"` + Name string `json:"name" yaml:"name"` + Stateful bool `json:"stateful" yaml:"stateful"` +} + +// Writable converts a full InstanceSnapshot struct into a InstanceSnapshotPut struct +// (filters read-only fields). +// +// API extension: instances +func (c *InstanceSnapshot) Writable() InstanceSnapshotPut { + return c.InstanceSnapshotPut +} diff --git a/shared/api/instance_state.go b/shared/api/instance_state.go new file mode 100644 index 0000000000..cd7823cbac --- /dev/null +++ b/shared/api/instance_state.go @@ -0,0 +1,84 @@ +package api + +// InstanceStatePut represents the modifiable fields of a LXD instance's state. +// +// API extension: instances +type InstanceStatePut struct { + Action string `json:"action" yaml:"action"` + Timeout int `json:"timeout" yaml:"timeout"` + Force bool `json:"force" yaml:"force"` + Stateful bool `json:"stateful" yaml:"stateful"` +} + +// InstanceState represents a LXD instance's state. +// +// API extension: instances +type InstanceState struct { + Status string `json:"status" yaml:"status"` + StatusCode StatusCode `json:"status_code" yaml:"status_code"` + Disk map[string]InstanceStateDisk `json:"disk" yaml:"disk"` + Memory InstanceStateMemory `json:"memory" yaml:"memory"` + Network map[string]InstanceStateNetwork `json:"network" yaml:"network"` + Pid int64 `json:"pid" yaml:"pid"` + Processes int64 `json:"processes" yaml:"processes"` + CPU InstanceStateCPU `json:"cpu" yaml:"cpu"` +} + +// InstanceStateDisk represents the disk information section of a LXD instance's state. +// +// API extension: instances +type InstanceStateDisk struct { + Usage int64 `json:"usage" yaml:"usage"` +} + +// InstanceStateCPU represents the cpu information section of a LXD instance's state. +// +// API extension: instances +type InstanceStateCPU struct { + Usage int64 `json:"usage" yaml:"usage"` +} + +// InstanceStateMemory represents the memory information section of a LXD instance's state. +// +// API extension: instances +type InstanceStateMemory struct { + Usage int64 `json:"usage" yaml:"usage"` + UsagePeak int64 `json:"usage_peak" yaml:"usage_peak"` + SwapUsage int64 `json:"swap_usage" yaml:"swap_usage"` + SwapUsagePeak int64 `json:"swap_usage_peak" yaml:"swap_usage_peak"` +} + +// InstanceStateNetwork represents the network information section of a LXD instance's state. +// +// API extension: instances +type InstanceStateNetwork struct { + Addresses []InstanceStateNetworkAddress `json:"addresses" yaml:"addresses"` + Counters InstanceStateNetworkCounters `json:"counters" yaml:"counters"` + Hwaddr string `json:"hwaddr" yaml:"hwaddr"` + HostName string `json:"host_name" yaml:"host_name"` + Mtu int `json:"mtu" yaml:"mtu"` + State string `json:"state" yaml:"state"` + Type string `json:"type" yaml:"type"` +} + +// InstanceStateNetworkAddress represents a network address as part of the network section of a LXD +// instance's state. +// +// API extension: instances +type InstanceStateNetworkAddress struct { + Family string `json:"family" yaml:"family"` + Address string `json:"address" yaml:"address"` + Netmask string `json:"netmask" yaml:"netmask"` + Scope string `json:"scope" yaml:"scope"` +} + +// InstanceStateNetworkCounters represents packet counters as part of the network section of a LXD +// instance's state. +// +// API extension: instances +type InstanceStateNetworkCounters struct { + BytesReceived int64 `json:"bytes_received" yaml:"bytes_received"` + BytesSent int64 `json:"bytes_sent" yaml:"bytes_sent"` + PacketsReceived int64 `json:"packets_received" yaml:"packets_received"` + PacketsSent int64 `json:"packets_sent" yaml:"packets_sent"` +} From 4fcc56d0bd0008b5c6e9cbe213f34947bcc8bd52 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 12 Sep 2019 11:57:02 +0100 Subject: [PATCH 7/9] client/connection: Adds connect functions that return InstanceServer type Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- client/connection.go | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/client/connection.go b/client/connection.go index c9a25e1066..6cfa0fb199 100644 --- a/client/connection.go +++ b/client/connection.go @@ -51,14 +51,14 @@ type ConnectionArgs struct { SkipGetServer bool } -// ConnectLXD lets you connect to a remote LXD daemon over HTTPs. +// ConnectLXDInstance lets you connect to a remote LXD daemon over HTTPs. // // A client certificate (TLSClientCert) and key (TLSClientKey) must be provided. // // If connecting to a LXD daemon running in PKI mode, the PKI CA (TLSCA) must also be provided. // // Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert). -func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) { +func ConnectLXDInstance(url string, args *ConnectionArgs) (InstanceServer, error) { logger.Debugf("Connecting to a remote LXD over HTTPs") // Cleanup URL @@ -67,12 +67,23 @@ func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) { return httpsLXD(url, args) } -// ConnectLXDUnix lets you connect to a remote LXD daemon over a local unix socket. +// ConnectLXD lets you connect to a remote LXD daemon over HTTPs. +// +// A client certificate (TLSClientCert) and key (TLSClientKey) must be provided. +// +// If connecting to a LXD daemon running in PKI mode, the PKI CA (TLSCA) must also be provided. +// +// Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert). +func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) { + return ConnectLXDInstance(url, args) +} + +// ConnectLXDInstanceUnix lets you connect to a remote LXD daemon over a local unix socket. // // If the path argument is empty, then $LXD_SOCKET will be used, if // unset $LXD_DIR/unix.socket will be used and if that one isn't set // either, then the path will default to /var/lib/lxd/unix.socket. -func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, error) { +func ConnectLXDInstanceUnix(path string, args *ConnectionArgs) (InstanceServer, error) { logger.Debugf("Connecting to a local LXD over a Unix socket") // Use empty args if not specified @@ -122,6 +133,15 @@ func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, error) return &server, nil } +// ConnectLXDUnix lets you connect to a remote LXD daemon over a local unix socket. +// +// If the path argument is empty, then $LXD_SOCKET will be used, if +// unset $LXD_DIR/unix.socket will be used and if that one isn't set +// either, then the path will default to /var/lib/lxd/unix.socket. +func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, error) { + return ConnectLXDInstanceUnix(path, args) +} + // ConnectPublicLXD lets you connect to a remote public LXD daemon over HTTPs. // // Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert). @@ -170,7 +190,7 @@ func ConnectSimpleStreams(url string, args *ConnectionArgs) (ImageServer, error) } // Internal function called by ConnectLXD and ConnectPublicLXD -func httpsLXD(url string, args *ConnectionArgs) (ContainerServer, error) { +func httpsLXD(url string, args *ConnectionArgs) (InstanceServer, error) { // Use empty args if not specified if args == nil { args = &ConnectionArgs{} From 056d14da15c48a1eb82cedf68a6f9375fcd77e6a Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 12 Sep 2019 11:57:49 +0100 Subject: [PATCH 8/9] client/interfaces: Creates InstanceServer interface - Creates new arg types for instances and aliases existing types. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- client/interfaces.go | 68 +++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/client/interfaces.go b/client/interfaces.go index 6e8cc7df3e..ae5cf82c9b 100644 --- a/client/interfaces.go +++ b/client/interfaces.go @@ -233,6 +233,16 @@ type ContainerServer interface { RawOperation(method string, path string, data interface{}, queryETag string) (op Operation, ETag string, err error) } +// The InstanceServer type represents a full featured LXD server with Instances support. +// +// API extension: instances +type InstanceServer interface { + ContainerServer + + UseInstanceTarget(name string) (client InstanceServer) + UseInstanceProject(name string) (client InstanceServer) +} + // The ConnectionInfo struct represents general information for a connection type ConnectionInfo struct { Addresses []string @@ -243,8 +253,8 @@ type ConnectionInfo struct { Project string } -// The ContainerBackupArgs struct is used when creating a container from a backup -type ContainerBackupArgs struct { +// The InstanceBackupArgs struct is used when creating a container from a backup +type InstanceBackupArgs struct { // The backup file BackupFile io.Reader @@ -252,6 +262,9 @@ type ContainerBackupArgs struct { PoolName string } +// The ContainerBackupArgs struct is used when creating a container from a backup +type ContainerBackupArgs InstanceBackupArgs + // The BackupFileRequest struct is used for a backup download request type BackupFileRequest struct { // Writer for the backup file @@ -356,8 +369,8 @@ type StoragePoolVolumeMoveArgs struct { StoragePoolVolumeCopyArgs } -// The ContainerCopyArgs struct is used to pass additional options during container copy -type ContainerCopyArgs struct { +// The InstanceCopyArgs struct is used to pass additional options during container copy +type InstanceCopyArgs struct { // If set, the container will be renamed on copy Name string @@ -375,8 +388,11 @@ type ContainerCopyArgs struct { Refresh bool } -// The ContainerSnapshotCopyArgs struct is used to pass additional options during container copy -type ContainerSnapshotCopyArgs struct { +// The ContainerCopyArgs struct is used to pass additional options during container copy +type ContainerCopyArgs InstanceCopyArgs + +// The InstanceSnapshotCopyArgs struct is used to pass additional options during container copy +type InstanceSnapshotCopyArgs struct { // If set, the container will be renamed on copy Name string @@ -388,9 +404,12 @@ type ContainerSnapshotCopyArgs struct { Live bool } -// The ContainerConsoleArgs struct is used to pass additional options during a +// The ContainerSnapshotCopyArgs struct is used to pass additional options during container copy +type ContainerSnapshotCopyArgs InstanceSnapshotCopyArgs + +// The InstanceConsoleArgs struct is used to pass additional options during a // container console session -type ContainerConsoleArgs struct { +type InstanceConsoleArgs struct { // Bidirectional fd to pass to the container Terminal io.ReadWriteCloser @@ -401,13 +420,21 @@ type ContainerConsoleArgs struct { ConsoleDisconnect chan bool } -// The ContainerConsoleLogArgs struct is used to pass additional options during a +// The ContainerConsoleArgs struct is used to pass additional options during a +// container console session +type ContainerConsoleArgs InstanceConsoleArgs + +// The InstanceConsoleLogArgs struct is used to pass additional options during a // container console log request -type ContainerConsoleLogArgs struct { +type InstanceConsoleLogArgs struct { } -// The ContainerExecArgs struct is used to pass additional options during container exec -type ContainerExecArgs struct { +// The ContainerConsoleLogArgs struct is used to pass additional options during a +// container console log request +type ContainerConsoleLogArgs InstanceConsoleLogArgs + +// The InstanceExecArgs struct is used to pass additional options during container exec +type InstanceExecArgs struct { // Standard input Stdin io.ReadCloser @@ -424,8 +451,11 @@ type ContainerExecArgs struct { DataDone chan bool } -// The ContainerFileArgs struct is used to pass the various options for a container file upload -type ContainerFileArgs struct { +// The ContainerExecArgs struct is used to pass additional options during container exec +type ContainerExecArgs InstanceExecArgs + +// The InstanceFileArgs struct is used to pass the various options for a container file upload +type InstanceFileArgs struct { // File content Content io.ReadSeeker @@ -445,8 +475,11 @@ type ContainerFileArgs struct { WriteMode string } -// The ContainerFileResponse struct is used as part of the response for a container file download -type ContainerFileResponse struct { +// The ContainerFileArgs struct is used to pass the various options for a container file upload +type ContainerFileArgs InstanceFileArgs + +// The InstanceFileResponse struct is used as part of the response for a container file download +type InstanceFileResponse struct { // User id that owns the file UID int64 @@ -462,3 +495,6 @@ type ContainerFileResponse struct { // If a directory, the list of files inside it Entries []string } + +// The ContainerFileResponse struct is used as part of the response for a container file download +type ContainerFileResponse InstanceFileResponse From fd53023acfd4a307a6263c79e97f299d75232639 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Thu, 12 Sep 2019 11:58:41 +0100 Subject: [PATCH 9/9] client/lxd/server: Adds UseInstanceTarget and UseInstanceProject functions And aliases existing functions to return old type. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- client/lxd_server.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/client/lxd_server.go b/client/lxd_server.go index 4cc20cc1c6..c1f3ec656c 100644 --- a/client/lxd_server.go +++ b/client/lxd_server.go @@ -89,8 +89,8 @@ func (r *ProtocolLXD) GetServerResources() (*api.Resources, error) { return &resources, nil } -// UseProject returns a client that will use a specific project. -func (r *ProtocolLXD) UseProject(name string) ContainerServer { +// UseInstanceProject returns a client that will use a specific project. +func (r *ProtocolLXD) UseInstanceProject(name string) InstanceServer { return &ProtocolLXD{ server: r.server, http: r.http, @@ -106,10 +106,15 @@ func (r *ProtocolLXD) UseProject(name string) ContainerServer { } } -// UseTarget returns a client that will target a specific cluster member. +// UseProject returns a client that will use a specific project. +func (r *ProtocolLXD) UseProject(name string) ContainerServer { + return ContainerServer(r.UseInstanceProject(name)) +} + +// UseInstanceTarget returns a client that will target a specific cluster member. // Use this member-specific operations such as specific container // placement, preparing a new storage pool or network, ... -func (r *ProtocolLXD) UseTarget(name string) ContainerServer { +func (r *ProtocolLXD) UseInstanceTarget(name string) InstanceServer { return &ProtocolLXD{ server: r.server, http: r.http, @@ -124,3 +129,10 @@ func (r *ProtocolLXD) UseTarget(name string) ContainerServer { clusterTarget: name, } } + +// UseTarget returns a client that will target a specific cluster member. +// Use this member-specific operations such as specific container +// placement, preparing a new storage pool or network, ... +func (r *ProtocolLXD) UseTarget(name string) ContainerServer { + return ContainerServer(r.UseInstanceTarget(name)) +}
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel