[lxc-devel] [lxd/master] add resource api

brauner on Github lxc-bot at linuxcontainers.org
Thu Oct 5 14:23:56 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 545 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20171005/1248f3dd/attachment.bin>
-------------- next part --------------
From 315b39caa69ace1f08938f76ceca95f6c4834b17 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 5 Oct 2017 15:02:06 +0200
Subject: [PATCH 1/6] resources: add api structs

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/api/resource.go | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)
 create mode 100644 shared/api/resource.go

diff --git a/shared/api/resource.go b/shared/api/resource.go
new file mode 100644
index 000000000..20e8ceb20
--- /dev/null
+++ b/shared/api/resource.go
@@ -0,0 +1,29 @@
+package api
+
+// Resources represents the system resources avaible for LXD
+type Resources struct {
+	CPU    CPU    `json:"cpu" yaml:"cpu"`
+	Memory Memory `json:"memory" yaml:"memory"`
+}
+
+// Socket represents a cpu socket on the system
+type Socket struct {
+	Cores          uint64 `json:"cores" yaml:"cores"`
+	Frequency      uint64 `json:"frequency,omitempty" yaml:"frequency,omitempty"`
+	FrequencyTurbo uint64 `json:"frequency_turbo,omitempty" yaml:"Frequency_turbo,omitempty"`
+	Name           string `json:"name,omitempty" yaml:"name,omitempty"`
+	Vendor         string `json:"vendor,omitempty" yaml:"vendor,omitempty"`
+	Threads        uint64 `json:"threads" yaml:"threads"`
+}
+
+// CPU represents the cpu resources available on the system
+type CPU struct {
+	Sockets []Socket `json:"sockets" yaml:"sockets"`
+	Total   uint64   `json:"total" yaml:"total"`
+}
+
+// Memory represents the memory resources available on the system
+type Memory struct {
+	Used  uint64 `json:"used" yaml:"used"`
+	Total uint64 `json:"total" yaml:"total"`
+}

From a5cefef7503a6591f7bdeaa08097ef46321d37aa Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 5 Oct 2017 15:55:42 +0200
Subject: [PATCH 2/6] client: add GetServerResources()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 client/interfaces.go |  1 +
 client/lxd_server.go | 19 +++++++++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/client/interfaces.go b/client/interfaces.go
index f6cb6006c..7905dcfd3 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -43,6 +43,7 @@ type ContainerServer interface {
 
 	// Server functions
 	GetServer() (server *api.Server, ETag string, err error)
+	GetServerResources() (*api.Resources, error)
 	UpdateServer(server api.ServerPut, ETag string) (err error)
 	HasExtension(extension string) bool
 
diff --git a/client/lxd_server.go b/client/lxd_server.go
index 2e42f3944..6f50a2cc8 100644
--- a/client/lxd_server.go
+++ b/client/lxd_server.go
@@ -1,6 +1,8 @@
 package lxd
 
 import (
+	"fmt"
+
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 )
@@ -53,3 +55,20 @@ func (r *ProtocolLXD) HasExtension(extension string) bool {
 
 	return false
 }
+
+// GetServerResources returns the resources available to a given LXD server
+func (r *ProtocolLXD) GetServerResources() (*api.Resources, error) {
+	if !r.HasExtension("resources") {
+		return nil, fmt.Errorf("The server is missing the required \"resources\" API extension")
+	}
+
+	resources := api.Resources{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/resources", nil, "", &resources)
+	if err != nil {
+		return nil, err
+	}
+
+	return &resources, nil
+}

From 2bd515ce8b0399cd46fca96ad4a1c16af1a30edf Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 5 Oct 2017 15:02:21 +0200
Subject: [PATCH 3/6] resources: add helpers

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/resources_utils.go | 339 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 339 insertions(+)
 create mode 100644 lxd/resources_utils.go

diff --git a/lxd/resources_utils.go b/lxd/resources_utils.go
new file mode 100644
index 000000000..9acb6413d
--- /dev/null
+++ b/lxd/resources_utils.go
@@ -0,0 +1,339 @@
+package main
+
+import (
+	"bufio"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/shared/api"
+)
+
+type thread struct {
+	ID             uint64
+	vendor         string
+	name           string
+	coreID         uint64
+	socketID       uint64
+	frequency      uint64
+	frequencyTurbo uint64
+}
+
+func parseNumberFromFile(file string) (int64, error) {
+	buf, err := ioutil.ReadFile(file)
+	if err != nil {
+		return int64(0), err
+	}
+
+	str := strings.TrimSpace(string(buf))
+	nr, err := strconv.Atoi(str)
+	if err != nil {
+		return int64(0), err
+	}
+
+	return int64(nr), nil
+}
+
+func parseCpuinfo() ([]thread, error) {
+	f, err := os.Open("/proc/cpuinfo")
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	threads := []thread{}
+	scanner := bufio.NewScanner(f)
+	var t *thread
+	for scanner.Scan() {
+		line := scanner.Text()
+		if strings.HasPrefix(line, "processor") {
+			i := strings.Index(line, ":")
+			if i < 0 {
+				return nil, err
+			}
+			i++
+
+			line = line[i:]
+			line = strings.TrimSpace(line)
+
+			id, err := strconv.Atoi(line)
+			if err != nil {
+				return nil, err
+			}
+
+			t = &thread{}
+			t.ID = uint64(id)
+
+			path := fmt.Sprintf("/sys/devices/system/cpu/cpu%d/topology/core_id", t.ID)
+			coreID, err := parseNumberFromFile(path)
+			if err != nil {
+				return nil, err
+			}
+
+			t.coreID = uint64(coreID)
+
+			path = fmt.Sprintf("/sys/devices/system/cpu/cpu%d/topology/physical_package_id", t.ID)
+			sockID, err := parseNumberFromFile(path)
+			if err != nil {
+				return nil, err
+			}
+
+			t.socketID = uint64(sockID)
+
+			path = fmt.Sprintf("/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", t.ID)
+			maxFreq, err := parseNumberFromFile(path)
+			if err != nil {
+				return nil, err
+			}
+
+			t.frequencyTurbo = uint64(maxFreq / 1000)
+
+			threads = append(threads, *t)
+		} else if strings.HasPrefix(line, "vendor_id") {
+			i := strings.Index(line, ":")
+			if i < 0 {
+				return nil, err
+			}
+			i++
+
+			line = line[i:]
+			line = strings.TrimSpace(line)
+
+			if t != nil {
+				threads[len(threads)-1].name = line
+			}
+		} else if strings.HasPrefix(line, "model name") {
+			i := strings.Index(line, ":")
+			if i < 0 {
+				return nil, err
+			}
+			i++
+
+			line = line[i:]
+			line = strings.TrimSpace(line)
+
+			if t != nil {
+				threads[len(threads)-1].vendor = line
+			}
+		} else if strings.HasPrefix(line, "cpu MHz") {
+			i := strings.Index(line, ":")
+			if i < 0 {
+				return nil, err
+			}
+			i++
+
+			line = line[i:]
+			line = strings.TrimSpace(line)
+
+			if t != nil {
+				tmp, err := strconv.ParseFloat(line, 64)
+				if err != nil {
+					return nil, err
+				}
+
+				threads[len(threads)-1].frequency = uint64(tmp)
+			}
+		}
+	}
+
+	if len(threads) == 0 {
+		return nil, db.NoSuchObjectError
+	}
+
+	return threads, err
+}
+
+func parseSysDevSystemCpu() ([]thread, error) {
+	ents, err := ioutil.ReadDir("/sys/devices/system/cpu/")
+	if err != nil {
+		return nil, err
+	}
+
+	threads := []thread{}
+	for _, ent := range ents {
+		entName := ent.Name()
+		if !strings.HasPrefix(entName, "cpu") {
+			continue
+		}
+
+		entName = entName[len("cpu"):]
+		idx, err := strconv.Atoi(entName)
+		if err != nil {
+			continue
+		}
+
+		t := thread{}
+		t.ID = uint64(idx)
+		path := fmt.Sprintf("/sys/devices/system/cpu/cpu%d/topology/core_id", t.ID)
+		coreID, err := parseNumberFromFile(path)
+		if err != nil {
+			return nil, err
+		}
+
+		t.coreID = uint64(coreID)
+
+		path = fmt.Sprintf("/sys/devices/system/cpu/cpu%d/topology/physical_package_id", t.ID)
+		sockID, err := parseNumberFromFile(path)
+		if err != nil {
+			return nil, err
+		}
+
+		t.socketID = uint64(sockID)
+
+		path = fmt.Sprintf("/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", t.ID)
+		maxFreq, err := parseNumberFromFile(path)
+		if err != nil {
+			return nil, err
+		}
+
+		t.frequencyTurbo = uint64(maxFreq / 1000)
+
+		threads = append(threads, t)
+	}
+
+	if len(threads) == 0 {
+		return nil, db.NoSuchObjectError
+	}
+
+	return threads, err
+}
+
+func getThreads() ([]thread, error) {
+	threads, err := parseCpuinfo()
+	if err == nil {
+		return threads, nil
+	}
+
+	threads, err = parseSysDevSystemCpu()
+	if err != nil {
+		return nil, err
+	}
+
+	return threads, nil
+}
+
+func cpuResource() (api.CPU, error) {
+	c := api.CPU{}
+
+	threads, err := getThreads()
+	if err != nil {
+		return c, err
+	}
+
+	var cur *api.Socket
+	c.Total = uint64(len(threads))
+	c.Sockets = append(c.Sockets, api.Socket{})
+	for _, v := range threads {
+		if uint64(len(c.Sockets)) <= v.socketID {
+			c.Sockets = append(c.Sockets, api.Socket{})
+			cur = &c.Sockets[v.socketID]
+		} else {
+			cur = &c.Sockets[v.socketID]
+		}
+
+		if v.coreID+1 > cur.Cores {
+			cur.Cores++
+		}
+
+		cur.Threads++
+		cur.Name = v.name
+		cur.Vendor = v.vendor
+		cur.Frequency = v.frequency
+		cur.FrequencyTurbo = v.frequencyTurbo
+	}
+
+	return c, nil
+}
+
+func memoryResource() (api.Memory, error) {
+	var buffers uint64
+	var cached uint64
+	var free uint64
+	var total uint64
+
+	mem := api.Memory{}
+	f, err := os.Open("/proc/meminfo")
+	if err != nil {
+		return mem, err
+	}
+	defer f.Close()
+
+	cleanLine := func(l string) (string, error) {
+		l = strings.TrimSpace(l)
+		idx := strings.LastIndex(l, "kB")
+		if idx < 0 {
+			return "", fmt.Errorf(`Failed to detect "kB" suffix`)
+		}
+
+		return strings.TrimSpace(l[:idx]), nil
+	}
+
+	scanner := bufio.NewScanner(f)
+	found := 0
+	for scanner.Scan() {
+		var err error
+		line := scanner.Text()
+
+		if strings.HasPrefix(line, "MemTotal:") {
+			line, err = cleanLine(line[len("MemTotal:"):])
+			if err != nil {
+				return mem, err
+			}
+
+			total, err = strconv.ParseUint(line, 10, 64)
+			if err != nil {
+				return mem, err
+			}
+
+			found++
+		} else if strings.HasPrefix(line, "MemFree:") {
+			line, err = cleanLine(line[len("MemFree:"):])
+			if err != nil {
+				return mem, err
+			}
+
+			free, err = strconv.ParseUint(line, 10, 64)
+			if err != nil {
+				return mem, err
+			}
+
+			found++
+		} else if strings.HasPrefix(line, "Cached:") {
+			line, err = cleanLine(line[len("Cached:"):])
+			if err != nil {
+				return mem, err
+			}
+
+			cached, err = strconv.ParseUint(line, 10, 64)
+			if err != nil {
+				return mem, err
+			}
+
+			found++
+		} else if strings.HasPrefix(line, "Buffers:") {
+			line, err = cleanLine(line[len("Buffers:"):])
+			if err != nil {
+				return mem, err
+			}
+
+			buffers, err = strconv.ParseUint(line, 10, 64)
+			if err != nil {
+				return mem, err
+			}
+
+			found++
+		}
+
+		if found == 4 {
+			break
+		}
+	}
+
+	mem.Total = total * 1024
+	mem.Used = (total - free - cached - buffers) * 1024
+
+	return mem, err
+}

From 3b6085409968791d53a03da118ed07456dadad07 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 5 Oct 2017 15:58:25 +0200
Subject: [PATCH 4/6] resources: add api endpoints

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/resources.go | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)
 create mode 100644 lxd/resources.go

diff --git a/lxd/resources.go b/lxd/resources.go
new file mode 100644
index 000000000..5ca8c4f79
--- /dev/null
+++ b/lxd/resources.go
@@ -0,0 +1,30 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/lxc/lxd/shared/api"
+)
+
+// /1.0/resources
+// Get system resources
+func serverResourcesGet(d *Daemon, r *http.Request) Response {
+	res := api.Resources{}
+
+	cpu, err := cpuResource()
+	if err != nil {
+		return SmartError(err)
+	}
+
+	mem, err := memoryResource()
+	if err != nil {
+		return SmartError(err)
+	}
+
+	res.CPU = cpu
+	res.Memory = mem
+
+	return SyncResponse(true, res)
+}
+
+var serverResourceCmd = Command{name: "resources", get: serverResourcesGet}

From 7e867103fefdf608e745d489525a7f9916ead28a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 5 Oct 2017 15:58:51 +0200
Subject: [PATCH 5/6] lxc: add "resources" flag to lxc info

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxc/info.go | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/lxc/info.go b/lxc/info.go
index 2d3a7c989..ab97f14df 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -15,7 +15,8 @@ import (
 )
 
 type infoCmd struct {
-	showLog bool
+	showLog   bool
+	resources bool
 }
 
 func (c *infoCmd) showByDefault() bool {
@@ -37,6 +38,7 @@ lxc info [<remote>:]
 
 func (c *infoCmd) flags() {
 	gnuflag.BoolVar(&c.showLog, "show-log", false, i18n.G("Show the container's last 100 log lines?"))
+	gnuflag.BoolVar(&c.resources, "resources", false, i18n.G("Show the resources available to the server"))
 }
 
 func (c *infoCmd) run(conf *config.Config, args []string) error {
@@ -68,6 +70,22 @@ func (c *infoCmd) run(conf *config.Config, args []string) error {
 }
 
 func (c *infoCmd) remoteInfo(d lxd.ContainerServer) error {
+	if c.resources {
+		resources, err := d.GetServerResources()
+		if err != nil {
+			return err
+		}
+
+		resourceData, err := yaml.Marshal(&resources)
+		if err != nil {
+			return err
+		}
+
+		fmt.Printf("%s", resourceData)
+
+		return nil
+	}
+
 	serverStatus, _, err := d.GetServer()
 	if err != nil {
 		return err

From e4e858ae714259a2734b3e90b825b19147f2a435 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 5 Oct 2017 15:59:29 +0200
Subject: [PATCH 6/6] api: add "resources" extension

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 doc/api-extensions.md |  4 ++++
 doc/rest-api.md       | 37 +++++++++++++++++++++++++++++++++++++
 lxd/api_1.0.go        |  2 ++
 3 files changed, 43 insertions(+)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 92cc342cc..02436429f 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -339,3 +339,7 @@ use by another LXD instance.
 ## storage\_block\_filesystem\_btrfs
 This adds support for btrfs as a storage volume filesystem, in addition to ext4
 and xfs.
+
+## resources
+This adds support for querying an LXD daemon for the system resources it has
+available.
diff --git a/doc/rest-api.md b/doc/rest-api.md
index 278237d31..d7c7b3a46 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -209,6 +209,7 @@ won't work and PUT needs to be used instead.
          * `/1.0/operations/<uuid>/websocket`
      * `/1.0/profiles`
        * `/1.0/profiles/<name>`
+     * `/1.0/resources`
 
 # API details
 ## `/`
@@ -2337,3 +2338,39 @@ Input (none at present):
 
     {
     }
+
+## `/1.0/resources`
+### GET
+ * Description: information about the resources available to the LXD server
+ * Introduced: with API extension `resources`
+ * Authentication: guest, untrusted or trusted
+ * Operation: sync
+ * Return: dict representing the system resources
+
+    {
+        "type": "sync",
+        "status": "Success",
+        "status_code": 200,
+        "operation": "",
+        "error_code": 0,
+        "error": "",
+        "metadata": {
+           "cpu": {
+              "sockets": [
+                 {
+                    "cores": 2,
+                    "frequency": 2691,
+                    "frequency_turbo": 3400,
+                    "name": "GenuineIntel",
+                    "vendor": "Intel(R) Core(TM) i5-3340M CPU @ 2.70GHz",
+                    "threads": 4
+                 }
+              ],
+              "total": 4
+           },
+           "memory": {
+              "used": 4454240256,
+              "total": 8271765504
+           }
+        }
+    }
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 0dd41bfaa..58d613217 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -53,6 +53,7 @@ var api10 = []Command{
 	storagePoolVolumesCmd,
 	storagePoolVolumesTypeCmd,
 	storagePoolVolumeTypeCmd,
+	serverResourceCmd,
 }
 
 func api10Get(d *Daemon, r *http.Request) Response {
@@ -125,6 +126,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 			"storage_volatile_initial_source",
 			"storage_ceph_force_osd_reuse",
 			"storage_block_filesystem_btrfs",
+			"resources",
 		},
 		APIStatus:  "stable",
 		APIVersion: version.APIVersion,


More information about the lxc-devel mailing list