The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/3902

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) ===
This adds a resource API for a LXD server. Sending a request will currently yield cpu and memory information. Further resources for appropriate LXD objects might be added later.

Signed-off-by: Christian Brauner <[email protected]>
From 315b39caa69ace1f08938f76ceca95f6c4834b17 Mon Sep 17 00:00:00 2001
From: Christian Brauner <[email protected]>
Date: Thu, 5 Oct 2017 15:02:06 +0200
Subject: [PATCH 1/6] resources: add api structs

Signed-off-by: Christian Brauner <[email protected]>
---
 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 <[email protected]>
Date: Thu, 5 Oct 2017 15:55:42 +0200
Subject: [PATCH 2/6] client: add GetServerResources()

Signed-off-by: Christian Brauner <[email protected]>
---
 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 <[email protected]>
Date: Thu, 5 Oct 2017 15:02:21 +0200
Subject: [PATCH 3/6] resources: add helpers

Signed-off-by: Christian Brauner <[email protected]>
---
 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 <[email protected]>
Date: Thu, 5 Oct 2017 15:58:25 +0200
Subject: [PATCH 4/6] resources: add api endpoints

Signed-off-by: Christian Brauner <[email protected]>
---
 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 <[email protected]>
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 <[email protected]>
---
 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 <[email protected]>
Date: Thu, 5 Oct 2017 15:59:29 +0200
Subject: [PATCH 6/6] api: add "resources" extension

Signed-off-by: Christian Brauner <[email protected]>
---
 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,
_______________________________________________
lxc-devel mailing list
[email protected]
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to