[lxc-devel] [lxd/master] Add ContainerArgsList and ContainerArgsNodeList
freeekanayaka on Github
lxc-bot at linuxcontainers.org
Tue Aug 7 16:32:04 UTC 2018
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 378 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180807/c545d047/attachment.bin>
-------------- next part --------------
From 47e1a340ade7c67fea8c285f64087adb3d8814af Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Tue, 7 Aug 2018 18:02:04 +0200
Subject: [PATCH] Add ContainerArgsList and ContainerArgsNodeList
Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
lxd/db/containers.go | 223 ++++++++++++++++++++++++++++++++++++++
lxd/db/containers_test.go | 147 +++++++++++++++++++++++++
2 files changed, 370 insertions(+)
diff --git a/lxd/db/containers.go b/lxd/db/containers.go
index fdb6a6f400..bb258d045a 100644
--- a/lxd/db/containers.go
+++ b/lxd/db/containers.go
@@ -337,6 +337,229 @@ func (c *ClusterTx) ContainerNodeMove(oldName, newName, newNode string) error {
return nil
}
+// ContainerArgsList returns all container objects alll node.
+func (c *ClusterTx) ContainerArgsList() ([]ContainerArgs, error) {
+ return c.containerArgsList(false)
+}
+
+// ContainerArgsNodeList returns all container objects on the local node.
+func (c *ClusterTx) ContainerArgsNodeList() ([]ContainerArgs, error) {
+ return c.containerArgsList(true)
+}
+
+func (c *ClusterTx) containerArgsList(local bool) ([]ContainerArgs, error) {
+ // First query the containers table.
+ stmt := `
+SELECT containers.id, nodes.name, type, creation_date, architecture,
+ coalesce(containers.description, ''), ephemeral, last_use_date,
+ containers.name, stateful
+ FROM containers
+ JOIN nodes ON containers.node_id=nodes.id
+`
+ if local {
+ stmt += `
+ WHERE nodes.id=?
+`
+ }
+
+ stmt += `
+ORDER BY containers.name
+`
+
+ containers := make([]ContainerArgs, 0)
+
+ dest := func(i int) []interface{} {
+ containers = append(containers, ContainerArgs{})
+ return []interface{}{
+ &containers[i].ID,
+ &containers[i].Node,
+ &containers[i].Ctype,
+ &containers[i].CreationDate,
+ &containers[i].Architecture,
+ &containers[i].Description,
+ &containers[i].Ephemeral,
+ &containers[i].LastUsedDate,
+ &containers[i].Name,
+ &containers[i].Stateful,
+ }
+ }
+
+ args := make([]interface{}, 0)
+ if local {
+ args = append(args, c.nodeID)
+ }
+
+ err := query.SelectObjects(c.tx, dest, stmt, args...)
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to query containers")
+ }
+
+ // Make an index to populate configs and devices.
+ index := make(map[int]*ContainerArgs, len(containers))
+ for i := range containers {
+ index[containers[i].ID] = &containers[i]
+ containers[i].Config = map[string]string{}
+ containers[i].Devices = types.Devices{}
+ containers[i].Profiles = make([]string, 0)
+ }
+
+ // Query the containers_config table.
+ stmt = `
+SELECT container_id, key, value
+ FROM containers_config
+ JOIN containers ON containers.id=container_id
+`
+ if local {
+ stmt += `
+ JOIN nodes ON nodes.id=containers.node_id
+ WHERE nodes.id=?
+`
+ }
+
+ configs := make([]struct {
+ ContainerID int64
+ Key string
+ Value string
+ }, 0)
+
+ dest = func(i int) []interface{} {
+ configs = append(configs, struct {
+ ContainerID int64
+ Key string
+ Value string
+ }{})
+
+ return []interface{}{
+ &configs[i].ContainerID,
+ &configs[i].Key,
+ &configs[i].Value,
+ }
+ }
+
+ err = query.SelectObjects(c.tx, dest, stmt, args...)
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to query containers config")
+ }
+
+ for _, config := range configs {
+ index[int(config.ContainerID)].Config[config.Key] = config.Value
+ }
+
+ // Query the containers_devices/containers_devices_config tables.
+ stmt = `
+SELECT container_id, containers_devices.name, containers_devices.type,
+ coalesce(containers_devices_config.key, ''), coalesce(containers_devices_config.value, '')
+ FROM containers_devices
+ LEFT OUTER JOIN containers_devices_config ON containers_devices_config.container_device_id=containers_devices.id
+ JOIN containers ON containers.id=container_id
+`
+ if local {
+ stmt += `
+ JOIN nodes ON nodes.id=containers.node_id
+ WHERE nodes.id=?
+`
+ }
+
+ devices := make([]struct {
+ ContainerID int64
+ Name string
+ Type int64
+ Key string
+ Value string
+ }, 0)
+
+ dest = func(i int) []interface{} {
+ devices = append(devices, struct {
+ ContainerID int64
+ Name string
+ Type int64
+ Key string
+ Value string
+ }{})
+
+ return []interface{}{
+ &devices[i].ContainerID,
+ &devices[i].Name,
+ &devices[i].Type,
+ &devices[i].Key,
+ &devices[i].Value,
+ }
+ }
+
+ err = query.SelectObjects(c.tx, dest, stmt, args...)
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to query containers devices")
+ }
+
+ for _, device := range devices {
+ cid := int(device.ContainerID)
+ _, ok := index[cid].Devices[device.Name]
+ if !ok {
+ // First time we see this device, let's int the config
+ // and add the type.
+ index[cid].Devices[device.Name] = make(map[string]string)
+
+ typ, err := dbDeviceTypeToString(int(device.Type))
+ if err != nil {
+ return nil, errors.Wrapf(err, "unexpected device type code '%d'", device.Type)
+ }
+ index[cid].Devices[device.Name]["type"] = typ
+ }
+
+ if device.Key != "" {
+ index[cid].Devices[device.Name][device.Key] = device.Value
+ }
+
+ }
+
+ // Query the profiles table
+ stmt = `
+SELECT container_id, profiles.name FROM containers_profiles
+ JOIN profiles ON containers_profiles.profile_id=profiles.id
+ JOIN containers ON containers.id=container_id
+`
+
+ if local {
+ stmt += `
+ JOIN nodes ON nodes.id=containers.node_id
+ WHERE nodes.id=?
+`
+ }
+
+ stmt += `
+ORDER BY containers_profiles.apply_order
+`
+
+ profiles := make([]struct {
+ ContainerID int64
+ Name string
+ }, 0)
+
+ dest = func(i int) []interface{} {
+ profiles = append(profiles, struct {
+ ContainerID int64
+ Name string
+ }{})
+
+ return []interface{}{
+ &profiles[i].ContainerID,
+ &profiles[i].Name,
+ }
+ }
+
+ err = query.SelectObjects(c.tx, dest, stmt, args...)
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to query containers profiles")
+ }
+
+ for _, profile := range profiles {
+ id := int(profile.ContainerID)
+ index[id].Profiles = append(index[id].Profiles, profile.Name)
+ }
+
+ return containers, nil
+}
+
// ContainerRemove removes the container with the given name from the database.
func (c *Cluster) ContainerRemove(name string) error {
id, err := c.ContainerID(name)
diff --git a/lxd/db/containers_test.go b/lxd/db/containers_test.go
index f82d079c82..5f6e84dcb5 100644
--- a/lxd/db/containers_test.go
+++ b/lxd/db/containers_test.go
@@ -113,6 +113,96 @@ func TestContainersNodeList(t *testing.T) {
assert.Equal(t, names, []string{"c1"})
}
+// All containers are loaded in bulk.
+func TestContainerArgsList(t *testing.T) {
+ tx, cleanup := db.NewTestClusterTx(t)
+ defer cleanup()
+
+ nodeID1 := int64(1) // This is the default local node
+
+ nodeID2, err := tx.NodeAdd("node2", "1.2.3.4:666")
+ require.NoError(t, err)
+
+ addContainer(t, tx, nodeID2, "c1")
+ addContainer(t, tx, nodeID1, "c2")
+ addContainer(t, tx, nodeID1, "c3")
+ addContainer(t, tx, nodeID1, "c4")
+
+ addContainerConfig(t, tx, "c2", "x", "y")
+ addContainerConfig(t, tx, "c3", "z", "w")
+ addContainerConfig(t, tx, "c3", "a", "b")
+
+ addContainerDevice(t, tx, "c2", "eth0", "nic", nil)
+ addContainerDevice(t, tx, "c4", "root", "disk", map[string]string{"x": "y"})
+
+ containers, err := tx.ContainerArgsList()
+ require.NoError(t, err)
+ assert.Len(t, containers, 4)
+
+ assert.Equal(t, "c1", containers[0].Name)
+ assert.Equal(t, "c2", containers[1].Name)
+ assert.Equal(t, "c3", containers[2].Name)
+ assert.Equal(t, "c4", containers[3].Name)
+
+ assert.Equal(t, "node2", containers[0].Node)
+ assert.Equal(t, "none", containers[1].Node)
+ assert.Equal(t, "none", containers[2].Node)
+ assert.Equal(t, "none", containers[3].Node)
+
+ assert.Equal(t, map[string]string{}, containers[0].Config)
+ assert.Equal(t, map[string]string{"x": "y"}, containers[1].Config)
+ assert.Equal(t, map[string]string{"z": "w", "a": "b"}, containers[2].Config)
+ assert.Equal(t, map[string]string{}, containers[3].Config)
+
+ assert.Equal(t, types.Devices{}, containers[0].Devices)
+ assert.Equal(t, types.Devices{"eth0": map[string]string{"type": "nic"}}, containers[1].Devices)
+ assert.Equal(t, types.Devices{}, containers[2].Devices)
+ assert.Equal(t, types.Devices{"root": map[string]string{"type": "disk", "x": "y"}}, containers[3].Devices)
+}
+
+// All containers on a node are loaded in bulk.
+func TestContainerArgsNodeList(t *testing.T) {
+ tx, cleanup := db.NewTestClusterTx(t)
+ defer cleanup()
+
+ nodeID1 := int64(1) // This is the default local node
+
+ nodeID2, err := tx.NodeAdd("node2", "1.2.3.4:666")
+ require.NoError(t, err)
+
+ addContainer(t, tx, nodeID2, "c1")
+ addContainer(t, tx, nodeID1, "c2")
+ addContainer(t, tx, nodeID1, "c3")
+ addContainer(t, tx, nodeID1, "c4")
+
+ addContainerConfig(t, tx, "c2", "x", "y")
+ addContainerConfig(t, tx, "c3", "z", "w")
+ addContainerConfig(t, tx, "c3", "a", "b")
+
+ addContainerDevice(t, tx, "c2", "eth0", "nic", nil)
+ addContainerDevice(t, tx, "c4", "root", "disk", map[string]string{"x": "y"})
+
+ containers, err := tx.ContainerArgsNodeList()
+ require.NoError(t, err)
+ assert.Len(t, containers, 3)
+
+ assert.Equal(t, "c2", containers[0].Name)
+ assert.Equal(t, "c3", containers[1].Name)
+ assert.Equal(t, "c4", containers[2].Name)
+
+ assert.Equal(t, "none", containers[0].Node)
+ assert.Equal(t, "none", containers[1].Node)
+ assert.Equal(t, "none", containers[2].Node)
+
+ assert.Equal(t, map[string]string{"x": "y"}, containers[0].Config)
+ assert.Equal(t, map[string]string{"z": "w", "a": "b"}, containers[1].Config)
+ assert.Equal(t, map[string]string{}, containers[2].Config)
+
+ assert.Equal(t, types.Devices{"eth0": map[string]string{"type": "nic"}}, containers[0].Devices)
+ assert.Equal(t, types.Devices{}, containers[1].Devices)
+ assert.Equal(t, types.Devices{"root": map[string]string{"type": "disk", "x": "y"}}, containers[2].Devices)
+}
+
func addContainer(t *testing.T, tx *db.ClusterTx, nodeID int64, name string) {
stmt := `
INSERT INTO containers(node_id, name, architecture, type) VALUES (?, ?, 1, ?)
@@ -120,3 +210,60 @@ INSERT INTO containers(node_id, name, architecture, type) VALUES (?, ?, 1, ?)
_, err := tx.Tx().Exec(stmt, nodeID, name, db.CTypeRegular)
require.NoError(t, err)
}
+
+func addContainerConfig(t *testing.T, tx *db.ClusterTx, container, key, value string) {
+ id := getContainerID(t, tx, container)
+
+ stmt := `
+INSERT INTO containers_config(container_id, key, value) VALUES (?, ?, ?)
+`
+ _, err := tx.Tx().Exec(stmt, id, key, value)
+ require.NoError(t, err)
+}
+
+func addContainerDevice(t *testing.T, tx *db.ClusterTx, container, name, typ string, config map[string]string) {
+ id := getContainerID(t, tx, container)
+
+ code, err := db.DeviceTypeToInt(typ)
+ require.NoError(t, err)
+
+ stmt := `
+INSERT INTO containers_devices(container_id, name, type) VALUES (?, ?, ?)
+`
+ _, err = tx.Tx().Exec(stmt, id, name, code)
+ require.NoError(t, err)
+
+ deviceID := getDeviceID(t, tx, id, name)
+
+ for key, value := range config {
+ stmt := `
+INSERT INTO containers_devices_config(container_device_id, key, value) VALUES (?, ?, ?)
+`
+ _, err = tx.Tx().Exec(stmt, deviceID, key, value)
+ require.NoError(t, err)
+ }
+}
+
+// Return the container ID given its name.
+func getContainerID(t *testing.T, tx *db.ClusterTx, name string) int64 {
+ var id int64
+
+ stmt := "SELECT id FROM containers WHERE name=?"
+ row := tx.Tx().QueryRow(stmt, name)
+ err := row.Scan(&id)
+ require.NoError(t, err)
+
+ return id
+}
+
+// Return the device ID given its container ID and name.
+func getDeviceID(t *testing.T, tx *db.ClusterTx, containerID int64, name string) int64 {
+ var id int64
+
+ stmt := "SELECT id FROM containers_devices WHERE container_id=? AND name=?"
+ row := tx.Tx().QueryRow(stmt, containerID, name)
+ err := row.Scan(&id)
+ require.NoError(t, err)
+
+ return id
+}
More information about the lxc-devel
mailing list