[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