Merge "Nova Hypervisors API"
diff --git a/openstack/compute/v2/hypervisors/doc.go b/openstack/compute/v2/hypervisors/doc.go
new file mode 100644
index 0000000..026f3dd
--- /dev/null
+++ b/openstack/compute/v2/hypervisors/doc.go
@@ -0,0 +1,3 @@
+// Package hypervisors gives information and control of the os-hypervisors
+// portion of the compute API
+package hypervisors
diff --git a/openstack/compute/v2/hypervisors/requests.go b/openstack/compute/v2/hypervisors/requests.go
new file mode 100644
index 0000000..3a66fcc
--- /dev/null
+++ b/openstack/compute/v2/hypervisors/requests.go
@@ -0,0 +1,21 @@
+package hypervisors
+
+import (
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/pagination"
+)
+
+// List makes a request against the API to list hypervisors.
+func List(client *gophercloud.ServiceClient) pagination.Pager {
+ return pagination.NewPager(client, hypervisorsListDetailURL(client), func(r pagination.PageResult) pagination.Page {
+ return HypervisorPage{pagination.SinglePageBase(r)}
+ })
+}
+
+
+func AggregateList(client *gophercloud.ServiceClient) pagination.Pager {
+ return pagination.NewPager(client, aggregatesListURL(client), func(r pagination.PageResult) pagination.Page {
+ return AggregatePage{pagination.SinglePageBase(r)}
+ })
+}
+
diff --git a/openstack/compute/v2/hypervisors/results.go b/openstack/compute/v2/hypervisors/results.go
new file mode 100644
index 0000000..6a22517
--- /dev/null
+++ b/openstack/compute/v2/hypervisors/results.go
@@ -0,0 +1,180 @@
+package hypervisors
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/gophercloud/gophercloud/pagination"
+)
+
+type Topology struct {
+ Sockets int `json:"sockets"`
+ Cores int `json:"cores"`
+ Threads int `json:"threads"`
+}
+
+type CPUInfo struct {
+ Vendor string `json:"vendor"`
+ Arch string `json:"arch"`
+ Model string `json:"model"`
+ Features []string `json:"features"`
+ Topology Topology `json:"topology"`
+}
+
+type Service struct {
+ Host string `json:"host"`
+ ID int `json:"id"`
+ DisabledReason string `json:"disabled_reason"`
+}
+
+type Hypervisor struct {
+ // A structure that contains cpu information like arch, model, vendor, features and topology
+ CPUInfo CPUInfo `json:"-"`
+ // The current_workload is the number of tasks the hypervisor is responsible for.
+ // This will be equal or greater than the number of active VMs on the system
+ // (it can be greater when VMs are being deleted and the hypervisor is still cleaning up).
+ CurrentWorkload int `json:"current_workload"`
+ // Status of the hypervisor, either "enabled" or "disabled"
+ Status string `json:"status"`
+ // State of the hypervisor, either "up" or "down"
+ State string `json:"state"`
+ // Actual free disk on this hypervisor in GB
+ DiskAvailableLeast int `json:"disk_available_least"`
+ // The hypervisor's IP address
+ HostIP string `json:"host_ip"`
+ // The free disk remaining on this hypervisor in GB
+ FreeDiskGB int `json:"-"`
+ // The free RAM in this hypervisor in MB
+ FreeRamMB int `json:"free_ram_mb"`
+ // The hypervisor host name
+ HypervisorHostname string `json:"hypervisor_hostname"`
+ // The hypervisor type
+ HypervisorType string `json:"hypervisor_type"`
+ // The hypervisor version
+ HypervisorVersion int `json:"-"`
+ // Unique ID of the hypervisor
+ ID int `json:"id"`
+ // The disk in this hypervisor in GB
+ LocalGB int `json:"-"`
+ // The disk used in this hypervisor in GB
+ LocalGBUsed int `json:"local_gb_used"`
+ // The memory of this hypervisor in MB
+ MemoryMB int `json:"memory_mb"`
+ // The memory used in this hypervisor in MB
+ MemoryMBUsed int `json:"memory_mb_used"`
+ // The number of running vms on this hypervisor
+ RunningVMs int `json:"running_vms"`
+ // The hypervisor service object
+ Service Service `json:"service"`
+ // The number of vcpu in this hypervisor
+ VCPUs int `json:"vcpus"`
+ // The number of vcpu used in this hypervisor
+ VCPUsUsed int `json:"vcpus_used"`
+}
+
+type Aggregate struct{
+ Name string `json:name`
+ Hosts []string `json:hosts`
+ ID int `json:id`
+
+}
+
+func (r *Hypervisor) UnmarshalJSON(b []byte) error {
+
+ type tmp Hypervisor
+ var s *struct {
+ tmp
+ CPUInfo interface{} `json:"cpu_info"`
+ HypervisorVersion interface{} `json:"hypervisor_version"`
+ FreeDiskGB interface{} `json:"free_disk_gb"`
+ LocalGB interface{} `json:"local_gb"`
+ }
+
+ err := json.Unmarshal(b, &s)
+ if err != nil {
+ return err
+ }
+
+ *r = Hypervisor(s.tmp)
+
+ // Newer versions pass the CPU into around as the correct types, this just needs
+ // converting and copying into place. Older versions pass CPU info around as a string
+ // and can simply be unmarshalled by the json parser
+ var tmpb []byte;
+
+ switch t := s.CPUInfo.(type) {
+ case string:
+ tmpb = []byte(t)
+ case map[string]interface{}:
+ tmpb, err = json.Marshal(t)
+ if err != nil {
+ return err
+ }
+ default:
+ return fmt.Errorf("CPUInfo has unexpected type: %T", t)
+ }
+
+ err = json.Unmarshal(tmpb, &r.CPUInfo)
+ if err != nil {
+ return err
+ }
+
+ // These fields may be passed in in scientific notation
+ switch t := s.HypervisorVersion.(type) {
+ case int:
+ r.HypervisorVersion = t
+ case float64:
+ r.HypervisorVersion = int(t)
+ default:
+ return fmt.Errorf("Hypervisor version of unexpected type")
+ }
+
+ switch t := s.FreeDiskGB.(type) {
+ case int:
+ r.FreeDiskGB = t
+ case float64:
+ r.FreeDiskGB = int(t)
+ default:
+ return fmt.Errorf("Free disk GB of unexpected type")
+ }
+
+ switch t := s.LocalGB.(type) {
+ case int:
+ r.LocalGB = t
+ case float64:
+ r.LocalGB = int(t)
+ default:
+ return fmt.Errorf("Local GB of unexpected type")
+ }
+
+ return nil
+}
+
+type HypervisorPage struct {
+ pagination.SinglePageBase
+}
+
+func (page HypervisorPage) IsEmpty() (bool, error) {
+ va, err := ExtractHypervisors(page)
+ return len(va) == 0, err
+}
+
+func ExtractHypervisors(p pagination.Page) ([]Hypervisor, error) {
+ var h struct {
+ Hypervisors []Hypervisor `json:"hypervisors"`
+ }
+ err := (p.(HypervisorPage)).ExtractInto(&h)
+ return h.Hypervisors, err
+}
+
+func ExtractAggregates(p pagination.Page) ([]Aggregate, error) {
+ var h struct {
+ Aggregates []Aggregate `json:"aggregates"`
+ }
+ fmt.Printf("AA: %s\n", p)
+ err := (p.(AggregatePage)).ExtractInto(&h)
+ return h.Aggregates, err
+}
+
+type AggregatePage struct {
+ pagination.SinglePageBase
+}
diff --git a/openstack/compute/v2/hypervisors/testing/fixtures.go b/openstack/compute/v2/hypervisors/testing/fixtures.go
new file mode 100644
index 0000000..1dd0058
--- /dev/null
+++ b/openstack/compute/v2/hypervisors/testing/fixtures.go
@@ -0,0 +1,135 @@
+package testing
+
+import (
+ "fmt"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/hypervisors"
+ "github.com/gophercloud/gophercloud/testhelper"
+ "github.com/gophercloud/gophercloud/testhelper/client"
+ "net/http"
+ "testing"
+)
+
+// The first hypervisor represents what the specification says (~Newton)
+// The second is exactly the same, but what you can get off a real system (~Kilo)
+const HypervisorListBody = `
+{
+ "hypervisors": [
+ {
+ "cpu_info": {
+ "arch": "x86_64",
+ "model": "Nehalem",
+ "vendor": "Intel",
+ "features": [
+ "pge",
+ "clflush"
+ ],
+ "topology": {
+ "cores": 1,
+ "threads": 1,
+ "sockets": 4
+ }
+ },
+ "current_workload": 0,
+ "status": "enabled",
+ "state": "up",
+ "disk_available_least": 0,
+ "host_ip": "1.1.1.1",
+ "free_disk_gb": 1028,
+ "free_ram_mb": 7680,
+ "hypervisor_hostname": "fake-mini",
+ "hypervisor_type": "fake",
+ "hypervisor_version": 2002000,
+ "id": 1,
+ "local_gb": 1028,
+ "local_gb_used": 0,
+ "memory_mb": 8192,
+ "memory_mb_used": 512,
+ "running_vms": 0,
+ "service": {
+ "host": "e6a37ee802d74863ab8b91ade8f12a67",
+ "id": 2,
+ "disabled_reason": null
+ },
+ "vcpus": 1,
+ "vcpus_used": 0
+ },
+ {
+ "cpu_info": "{\"arch\": \"x86_64\", \"model\": \"Nehalem\", \"vendor\": \"Intel\", \"features\": [\"pge\", \"clflush\"], \"topology\": {\"cores\": 1, \"threads\": 1, \"sockets\": 4}}",
+ "current_workload": 0,
+ "status": "enabled",
+ "state": "up",
+ "disk_available_least": 0,
+ "host_ip": "1.1.1.1",
+ "free_disk_gb": 1028,
+ "free_ram_mb": 7680,
+ "hypervisor_hostname": "fake-mini",
+ "hypervisor_type": "fake",
+ "hypervisor_version": 2.002e+06,
+ "id": 1,
+ "local_gb": 1028,
+ "local_gb_used": 0,
+ "memory_mb": 8192,
+ "memory_mb_used": 512,
+ "running_vms": 0,
+ "service": {
+ "host": "e6a37ee802d74863ab8b91ade8f12a67",
+ "id": 2,
+ "disabled_reason": null
+ },
+ "vcpus": 1,
+ "vcpus_used": 0
+ }
+ ]
+}`
+
+var (
+ HypervisorFake = hypervisors.Hypervisor{
+ CPUInfo: hypervisors.CPUInfo{
+ Arch: "x86_64",
+ Model: "Nehalem",
+ Vendor: "Intel",
+ Features: []string{
+ "pge",
+ "clflush",
+ },
+ Topology: hypervisors.Topology{
+ Cores: 1,
+ Threads: 1,
+ Sockets: 4,
+ },
+ },
+ CurrentWorkload: 0,
+ Status: "enabled",
+ State: "up",
+ DiskAvailableLeast: 0,
+ HostIP: "1.1.1.1",
+ FreeDiskGB: 1028,
+ FreeRamMB: 7680,
+ HypervisorHostname: "fake-mini",
+ HypervisorType: "fake",
+ HypervisorVersion: 2002000,
+ ID: 1,
+ LocalGB: 1028,
+ LocalGBUsed: 0,
+ MemoryMB: 8192,
+ MemoryMBUsed: 512,
+ RunningVMs: 0,
+ Service: hypervisors.Service{
+ Host: "e6a37ee802d74863ab8b91ade8f12a67",
+ ID: 2,
+ DisabledReason: "",
+ },
+ VCPUs: 1,
+ VCPUsUsed: 0,
+ }
+)
+
+func HandleHypervisorListSuccessfully(t *testing.T) {
+ testhelper.Mux.HandleFunc("/os-hypervisors/detail", func(w http.ResponseWriter, r *http.Request) {
+ testhelper.TestMethod(t, r, "GET")
+ testhelper.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ fmt.Fprintf(w, HypervisorListBody)
+ })
+}
diff --git a/openstack/compute/v2/hypervisors/testing/requests_test.go b/openstack/compute/v2/hypervisors/testing/requests_test.go
new file mode 100644
index 0000000..14b0ebe
--- /dev/null
+++ b/openstack/compute/v2/hypervisors/testing/requests_test.go
@@ -0,0 +1,52 @@
+package testing
+
+import (
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/hypervisors"
+ "github.com/gophercloud/gophercloud/pagination"
+ "github.com/gophercloud/gophercloud/testhelper"
+ "github.com/gophercloud/gophercloud/testhelper/client"
+ "testing"
+)
+
+func TestListHypervisors(t *testing.T) {
+ testhelper.SetupHTTP()
+ defer testhelper.TeardownHTTP()
+ HandleHypervisorListSuccessfully(t)
+
+ pages := 0
+ err := hypervisors.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
+ pages++
+
+ actual, err := hypervisors.ExtractHypervisors(page)
+ if err != nil {
+ return false, err
+ }
+
+ if len(actual) != 2 {
+ t.Fatalf("Expected 2 hypervisors, got %d", len(actual))
+ }
+ testhelper.CheckDeepEquals(t, HypervisorFake, actual[0])
+ testhelper.CheckDeepEquals(t, HypervisorFake, actual[1])
+
+ return true, nil
+ })
+
+ testhelper.AssertNoErr(t, err)
+
+ if pages != 1 {
+ t.Errorf("Expected 1 page, saw %d", pages)
+ }
+}
+
+func TestListAllHypervisors(t *testing.T) {
+ testhelper.SetupHTTP()
+ defer testhelper.TeardownHTTP()
+ HandleHypervisorListSuccessfully(t)
+
+ allPages, err := hypervisors.List(client.ServiceClient()).AllPages()
+ testhelper.AssertNoErr(t, err)
+ actual, err := hypervisors.ExtractHypervisors(allPages)
+ testhelper.AssertNoErr(t, err)
+ testhelper.CheckDeepEquals(t, HypervisorFake, actual[0])
+ testhelper.CheckDeepEquals(t, HypervisorFake, actual[1])
+}
diff --git a/openstack/compute/v2/hypervisors/urls.go b/openstack/compute/v2/hypervisors/urls.go
new file mode 100644
index 0000000..0cb3518
--- /dev/null
+++ b/openstack/compute/v2/hypervisors/urls.go
@@ -0,0 +1,11 @@
+package hypervisors
+
+import "github.com/gophercloud/gophercloud"
+
+func hypervisorsListDetailURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL("os-hypervisors", "detail")
+}
+
+func aggregatesListURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL("os-aggregates")
+}