mirror of https://github.com/prometheus/prometheus
Merge pull request #3105 from sak0/dev
discovery openstack: support discovery hypervisors, add rule option.pull/3020/merge
commit
d0a02703a2
@ -0,0 +1,145 @@
|
||||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/hypervisors"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
)
|
||||
|
||||
const (
|
||||
openstackLabelHypervisorHostIP = openstackLabelPrefix + "hypervisor_host_ip"
|
||||
openstackLabelHypervisorHostName = openstackLabelPrefix + "hypervisor_hostname"
|
||||
openstackLabelHypervisorStatus = openstackLabelPrefix + "hypervisor_status"
|
||||
openstackLabelHypervisorState = openstackLabelPrefix + "hypervisor_state"
|
||||
openstackLabelHypervisorType = openstackLabelPrefix + "hypervisor_type"
|
||||
)
|
||||
|
||||
// HypervisorDiscovery discovers OpenStack hypervisors.
|
||||
type HypervisorDiscovery struct {
|
||||
authOpts *gophercloud.AuthOptions
|
||||
region string
|
||||
interval time.Duration
|
||||
logger log.Logger
|
||||
port int
|
||||
}
|
||||
|
||||
// NewHypervisorDiscovery returns a new hypervisor discovery.
|
||||
func NewHypervisorDiscovery(opts *gophercloud.AuthOptions,
|
||||
interval time.Duration, port int, region string, l log.Logger) *HypervisorDiscovery {
|
||||
return &HypervisorDiscovery{authOpts: opts,
|
||||
region: region, interval: interval, port: port, logger: l}
|
||||
}
|
||||
|
||||
// Run implements the TargetProvider interface.
|
||||
func (h *HypervisorDiscovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
||||
// Get an initial set right away.
|
||||
tg, err := h.refresh()
|
||||
if err != nil {
|
||||
h.logger.Error(err)
|
||||
} else {
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(h.interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
tg, err := h.refresh()
|
||||
if err != nil {
|
||||
h.logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HypervisorDiscovery) refresh() (*config.TargetGroup, error) {
|
||||
var err error
|
||||
t0 := time.Now()
|
||||
defer func() {
|
||||
refreshDuration.Observe(time.Since(t0).Seconds())
|
||||
if err != nil {
|
||||
refreshFailuresCount.Inc()
|
||||
}
|
||||
}()
|
||||
|
||||
provider, err := openstack.AuthenticatedClient(*h.authOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create OpenStack session: %s", err)
|
||||
}
|
||||
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
|
||||
Region: h.region,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create OpenStack compute session: %s", err)
|
||||
}
|
||||
|
||||
tg := &config.TargetGroup{
|
||||
Source: fmt.Sprintf("OS_" + h.region),
|
||||
}
|
||||
// OpenStack API reference
|
||||
// https://developer.openstack.org/api-ref/compute/#list-hypervisors-details
|
||||
pagerHypervisors := hypervisors.List(client)
|
||||
err = pagerHypervisors.EachPage(func(page pagination.Page) (bool, error) {
|
||||
hypervisorList, err := hypervisors.ExtractHypervisors(page)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not extract hypervisors: %s", err)
|
||||
}
|
||||
for _, hypervisor := range hypervisorList {
|
||||
labels := model.LabelSet{
|
||||
openstackLabelHypervisorHostIP: model.LabelValue(hypervisor.HostIP),
|
||||
}
|
||||
addr := net.JoinHostPort(hypervisor.HostIP, fmt.Sprintf("%d", h.port))
|
||||
labels[model.AddressLabel] = model.LabelValue(addr)
|
||||
labels[openstackLabelHypervisorHostName] = model.LabelValue(hypervisor.HypervisorHostname)
|
||||
labels[openstackLabelHypervisorHostIP] = model.LabelValue(hypervisor.HostIP)
|
||||
labels[openstackLabelHypervisorStatus] = model.LabelValue(hypervisor.Status)
|
||||
labels[openstackLabelHypervisorState] = model.LabelValue(hypervisor.State)
|
||||
labels[openstackLabelHypervisorType] = model.LabelValue(hypervisor.HypervisorType)
|
||||
tg.Targets = append(tg.Targets, labels)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tg, nil
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
)
|
||||
|
||||
type OpenstackSDHypervisorTestSuite struct {
|
||||
suite.Suite
|
||||
Mock *SDMock
|
||||
}
|
||||
|
||||
func (s *OpenstackSDHypervisorTestSuite) TearDownSuite() {
|
||||
s.Mock.ShutdownServer()
|
||||
}
|
||||
|
||||
func (s *OpenstackSDHypervisorTestSuite) SetupTest() {
|
||||
s.Mock = NewSDMock(s.T())
|
||||
s.Mock.Setup()
|
||||
|
||||
s.Mock.HandleHypervisorListSuccessfully()
|
||||
|
||||
s.Mock.HandleVersionsSuccessfully()
|
||||
s.Mock.HandleAuthSuccessfully()
|
||||
}
|
||||
|
||||
func TestOpenstackSDHypervisorSuite(t *testing.T) {
|
||||
suite.Run(t, new(OpenstackSDHypervisorTestSuite))
|
||||
}
|
||||
|
||||
func (s *OpenstackSDHypervisorTestSuite) openstackAuthSuccess() (Discovery, error) {
|
||||
conf := config.OpenstackSDConfig{
|
||||
IdentityEndpoint: s.Mock.Endpoint(),
|
||||
Password: "test",
|
||||
Username: "test",
|
||||
DomainName: "12345",
|
||||
Region: "RegionOne",
|
||||
Role: "hypervisor",
|
||||
}
|
||||
return NewDiscovery(&conf, log.Base())
|
||||
}
|
||||
|
||||
func (s *OpenstackSDHypervisorTestSuite) TestOpenstackSDHypervisorRefresh() {
|
||||
hypervisor, _ := s.openstackAuthSuccess()
|
||||
tg, err := hypervisor.refresh()
|
||||
assert.Nil(s.T(), err)
|
||||
require.NotNil(s.T(), tg)
|
||||
require.NotNil(s.T(), tg.Targets)
|
||||
require.Len(s.T(), tg.Targets, 2)
|
||||
|
||||
assert.Equal(s.T(), tg.Targets[0]["__address__"], model.LabelValue("172.16.70.14:0"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_hostname"], model.LabelValue("nc14.cloud.com"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_type"], model.LabelValue("QEMU"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_host_ip"], model.LabelValue("172.16.70.14"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_state"], model.LabelValue("up"))
|
||||
assert.Equal(s.T(), tg.Targets[0]["__meta_openstack_hypervisor_status"], model.LabelValue("enabled"))
|
||||
|
||||
assert.Equal(s.T(), tg.Targets[1]["__address__"], model.LabelValue("172.16.70.13:0"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_hostname"], model.LabelValue("cc13.cloud.com"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_type"], model.LabelValue("QEMU"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_host_ip"], model.LabelValue("172.16.70.13"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_state"], model.LabelValue("up"))
|
||||
assert.Equal(s.T(), tg.Targets[1]["__meta_openstack_hypervisor_status"], model.LabelValue("enabled"))
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/util/strutil"
|
||||
)
|
||||
|
||||
const (
|
||||
openstackLabelPrefix = model.MetaLabelPrefix + "openstack_"
|
||||
openstackLabelInstanceID = openstackLabelPrefix + "instance_id"
|
||||
openstackLabelInstanceName = openstackLabelPrefix + "instance_name"
|
||||
openstackLabelInstanceStatus = openstackLabelPrefix + "instance_status"
|
||||
openstackLabelInstanceFlavor = openstackLabelPrefix + "instance_flavor"
|
||||
openstackLabelPublicIP = openstackLabelPrefix + "public_ip"
|
||||
openstackLabelPrivateIP = openstackLabelPrefix + "private_ip"
|
||||
openstackLabelTagPrefix = openstackLabelPrefix + "tag_"
|
||||
)
|
||||
|
||||
// InstanceDiscovery discovers OpenStack instances.
|
||||
type InstanceDiscovery struct {
|
||||
authOpts *gophercloud.AuthOptions
|
||||
region string
|
||||
interval time.Duration
|
||||
logger log.Logger
|
||||
port int
|
||||
}
|
||||
|
||||
// NewInstanceDiscovery returns a new instance discovery.
|
||||
func NewInstanceDiscovery(opts *gophercloud.AuthOptions,
|
||||
interval time.Duration, port int, region string, l log.Logger) *InstanceDiscovery {
|
||||
return &InstanceDiscovery{authOpts: opts,
|
||||
region: region, interval: interval, port: port, logger: l}
|
||||
}
|
||||
|
||||
// Run implements the TargetProvider interface.
|
||||
func (i *InstanceDiscovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) {
|
||||
// Get an initial set right away.
|
||||
tg, err := i.refresh()
|
||||
if err != nil {
|
||||
i.logger.Error(err)
|
||||
} else {
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(i.interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
tg, err := i.refresh()
|
||||
if err != nil {
|
||||
i.logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- []*config.TargetGroup{tg}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InstanceDiscovery) refresh() (*config.TargetGroup, error) {
|
||||
var err error
|
||||
t0 := time.Now()
|
||||
defer func() {
|
||||
refreshDuration.Observe(time.Since(t0).Seconds())
|
||||
if err != nil {
|
||||
refreshFailuresCount.Inc()
|
||||
}
|
||||
}()
|
||||
|
||||
provider, err := openstack.AuthenticatedClient(*i.authOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create OpenStack session: %s", err)
|
||||
}
|
||||
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
|
||||
Region: i.region,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create OpenStack compute session: %s", err)
|
||||
}
|
||||
|
||||
// OpenStack API reference
|
||||
// https://developer.openstack.org/api-ref/compute/#list-floating-ips
|
||||
pagerFIP := floatingips.List(client)
|
||||
floatingIPList := make(map[string][]string)
|
||||
err = pagerFIP.EachPage(func(page pagination.Page) (bool, error) {
|
||||
result, err := floatingips.ExtractFloatingIPs(page)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not extract floatingips: %s", err)
|
||||
}
|
||||
for _, ip := range result {
|
||||
// Skip not associated ips
|
||||
if ip.InstanceID != "" {
|
||||
floatingIPList[ip.InstanceID] = append(floatingIPList[ip.InstanceID], ip.IP)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// OpenStack API reference
|
||||
// https://developer.openstack.org/api-ref/compute/#list-servers
|
||||
opts := servers.ListOpts{}
|
||||
pager := servers.List(client, opts)
|
||||
tg := &config.TargetGroup{
|
||||
Source: fmt.Sprintf("OS_" + i.region),
|
||||
}
|
||||
err = pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
instanceList, err := servers.ExtractServers(page)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not extract instances: %s", err)
|
||||
}
|
||||
|
||||
for _, s := range instanceList {
|
||||
labels := model.LabelSet{
|
||||
openstackLabelInstanceID: model.LabelValue(s.ID),
|
||||
}
|
||||
if len(s.Addresses) == 0 {
|
||||
i.logger.Info("Got no IP address for instance %s", s.ID)
|
||||
continue
|
||||
}
|
||||
for _, address := range s.Addresses {
|
||||
md, ok := address.([]interface{})
|
||||
if !ok {
|
||||
i.logger.Warn("Invalid type for address, expected array")
|
||||
continue
|
||||
}
|
||||
if len(md) == 0 {
|
||||
i.logger.Debugf("Got no IP address for instance %s", s.ID)
|
||||
continue
|
||||
}
|
||||
md1, ok := md[0].(map[string]interface{})
|
||||
if !ok {
|
||||
i.logger.Warn("Invalid type for address, expected dict")
|
||||
continue
|
||||
}
|
||||
addr, ok := md1["addr"].(string)
|
||||
if !ok {
|
||||
i.logger.Warn("Invalid type for address, expected string")
|
||||
continue
|
||||
}
|
||||
labels[openstackLabelPrivateIP] = model.LabelValue(addr)
|
||||
addr = net.JoinHostPort(addr, fmt.Sprintf("%d", i.port))
|
||||
labels[model.AddressLabel] = model.LabelValue(addr)
|
||||
// Only use first private IP
|
||||
break
|
||||
}
|
||||
if val, ok := floatingIPList[s.ID]; ok && len(val) > 0 {
|
||||
labels[openstackLabelPublicIP] = model.LabelValue(val[0])
|
||||
}
|
||||
labels[openstackLabelInstanceStatus] = model.LabelValue(s.Status)
|
||||
labels[openstackLabelInstanceName] = model.LabelValue(s.Name)
|
||||
id, ok := s.Flavor["id"].(string)
|
||||
if !ok {
|
||||
i.logger.Warn("Invalid type for instance id, excepted string")
|
||||
continue
|
||||
}
|
||||
labels[openstackLabelInstanceFlavor] = model.LabelValue(id)
|
||||
for k, v := range s.Metadata {
|
||||
name := strutil.SanitizeLabelName(k)
|
||||
labels[openstackLabelTagPrefix+model.LabelName(name)] = model.LabelValue(v)
|
||||
}
|
||||
tg.Targets = append(tg.Targets, labels)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tg, nil
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
// Package hypervisors gives information and control of the os-hypervisors
|
||||
// portion of the compute API
|
||||
package hypervisors
|
@ -0,0 +1,13 @@
|
||||
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)}
|
||||
})
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
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"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package hypervisors
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func hypervisorsListDetailURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("os-hypervisors", "detail")
|
||||
}
|
Loading…
Reference in new issue