[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