Skip to content

Commit

Permalink
Gnocchi: implement resource types List method (gophercloud#55)
Browse files Browse the repository at this point in the history
* Gnocchi: implement resource types List method

Add new resourcetypes package with List method.
Add tests and a documentation example.

* Gnocchi: change resource type attributes

Rename resource type attributes to Attributes struct, use cutom field
"Extra" with map[string]interface{} type instead of different fields.
Those fields will be different for different attribute types.

* Gnocchi: change resource type attributes struct

Migrate from list to map representation for the attribute details to
make it more similar to the actual Gnocchi response JSON.
  • Loading branch information
ozerovandrei authored and jtopjian committed Jun 28, 2018
1 parent 18450e9 commit d7844b1
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 0 deletions.
32 changes: 32 additions & 0 deletions acceptance/gnocchi/metric/v1/resourcetypes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// +build acceptance metric resourcetypes

package v1

import (
"testing"

"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/utils/acceptance/clients"
"github.com/gophercloud/utils/gnocchi/metric/v1/resourcetypes"
)

func TestResourceTypesList(t *testing.T) {
client, err := clients.NewGnocchiV1Client()
if err != nil {
t.Fatalf("Unable to create a Gnocchi client: %v", err)
}

allPages, err := resourcetypes.List(client).AllPages()
if err != nil {
t.Fatalf("Unable to list resource types: %v", err)
}

allResourceTypes, err := resourcetypes.ExtractResourceTypes(allPages)
if err != nil {
t.Fatalf("Unable to extract resource types: %v", err)
}

for _, resourceType := range allResourceTypes {
tools.PrintResource(t, resourceType)
}
}
20 changes: 20 additions & 0 deletions gnocchi/metric/v1/resourcetypes/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
Package resourcetypes provides ability to manage resource types through the Gnocchi API.
Example of Listing resource types
allPages, err := resourcetypes.List(client).AllPages()
if err != nil {
panic(err)
}
allResourceTypes, err := resourcetypes.ExtractResourceTypes(allPages)
if err != nil {
panic(err)
}
for _, resourceType := range allResourceTypes {
fmt.Printf("%+v\n", resourceType)
}
*/
package resourcetypes
13 changes: 13 additions & 0 deletions gnocchi/metric/v1/resourcetypes/requests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package resourcetypes

import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)

// List makes a request against the Gnocchi API to list resource types.
func List(client *gophercloud.ServiceClient) pagination.Pager {
return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
return ResourceTypePage{pagination.SinglePageBase(r)}
})
}
121 changes: 121 additions & 0 deletions gnocchi/metric/v1/resourcetypes/results.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package resourcetypes

import (
"encoding/json"

"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)

type commonResult struct {
gophercloud.Result
}

// Extract is a function that accepts a result and extracts a Gnocchi resource type.
func (r commonResult) Extract() (*ResourceType, error) {
var s *ResourceType
err := r.ExtractInto(&s)
return s, err
}

// GetResult represents the result of a get operation. Call its Extract
// method to interpret it as a Gnocchi resource type.
type GetResult struct {
commonResult
}

// ResourceType represents custom Gnocchi resource type.
type ResourceType struct {
// Attributes is a collection of keys and values of different resource types.
Attributes map[string]Attribute `json:"-"`

// Name is a human-readable resource type identifier.
Name string `json:"name"`

// State represents current status of a resource type.
State string `json:"state"`
}

// Attribute represents single attribute of a Gnocchi resource type.
type Attribute struct {
// Type is an attribute type.
Type string `json:"type"`

// Details represents different attribute fields.
Details map[string]interface{}
}

// UnmarshalJSON helps to unmarshal ResourceType fields into needed values.
func (r *ResourceType) UnmarshalJSON(b []byte) error {
type tmp ResourceType
var s struct {
tmp
Attributes map[string]interface{} `json:"attributes"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = ResourceType(s.tmp)

if s.Attributes == nil {
return nil
}

// Populate attributes from the JSON map structure.
attributes := make(map[string]Attribute)
for attributeName, attributeValues := range s.Attributes {
attribute := new(Attribute)
attribute.Details = make(map[string]interface{})

attributeValuesMap, ok := attributeValues.(map[string]interface{})
if !ok {
// Got some strange resource type attribute representation, skip it.
continue
}

// Populate extra and type attribute values.
for k, v := range attributeValuesMap {
if k == "type" {
if attributeType, ok := v.(string); ok {
attribute.Type = attributeType
}
} else {
attribute.Details[k] = v
}
}
attributes[attributeName] = *attribute
}

r.Attributes = attributes

return err
}

// ResourceTypePage abstracts the raw results of making a List() request against
// the Gnocchi API.
//
// As Gnocchi API may freely alter the response bodies of structures
// returned to the client, you may only safely access the data provided through
// the ExtractResources call.
type ResourceTypePage struct {
pagination.SinglePageBase
}

// IsEmpty checks whether a ResourceTypePage struct is empty.
func (r ResourceTypePage) IsEmpty() (bool, error) {
is, err := ExtractResourceTypes(r)
return len(is) == 0, err
}

// ExtractResourceTypes interprets the results of a single page from a List() call,
// producing a slice of ResourceType structs.
func ExtractResourceTypes(r pagination.Page) ([]ResourceType, error) {
var s []ResourceType
err := (r.(ResourceTypePage)).ExtractInto(&s)
if err != nil {
return nil, err
}

return s, err
}
2 changes: 2 additions & 0 deletions gnocchi/metric/v1/resourcetypes/testing/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// resource types unit tests
package testing
71 changes: 71 additions & 0 deletions gnocchi/metric/v1/resourcetypes/testing/fixtures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package testing

import "github.com/gophercloud/utils/gnocchi/metric/v1/resourcetypes"

// ResourceTypeListResult represents raw server response from a server to a list call.
const ResourceTypeListResult = `[
{
"attributes": {},
"name": "generic",
"state": "active"
},
{
"attributes": {
"parent_id": {
"required": false,
"type": "uuid"
}
},
"name": "identity_project",
"state": "active"
},
{
"attributes": {
"host": {
"max_length": 128,
"min_length": 0,
"required": true,
"type": "string"
}
},
"name": "compute_instance",
"state": "active"
}
]`

// ResourceType1 is an expected representation of a first resource from the ResourceTypeListResult.
var ResourceType1 = resourcetypes.ResourceType{
Name: "generic",
State: "active",
Attributes: map[string]resourcetypes.Attribute{},
}

// ResourceType2 is an expected representation of a first resource from the ResourceTypeListResult.
var ResourceType2 = resourcetypes.ResourceType{
Name: "identity_project",
State: "active",
Attributes: map[string]resourcetypes.Attribute{
"parent_id": resourcetypes.Attribute{
Type: "uuid",
Details: map[string]interface{}{
"required": false,
},
},
},
}

// ResourceType3 is an expected representation of a first resource from the ResourceTypeListResult.
var ResourceType3 = resourcetypes.ResourceType{
Name: "compute_instance",
State: "active",
Attributes: map[string]resourcetypes.Attribute{
"host": resourcetypes.Attribute{
Type: "string",
Details: map[string]interface{}{
"max_length": float64(128),
"min_length": float64(0),
"required": true,
},
},
},
}
52 changes: 52 additions & 0 deletions gnocchi/metric/v1/resourcetypes/testing/requests_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package testing

import (
"fmt"
"net/http"
"testing"

"github.com/gophercloud/gophercloud/pagination"
th "github.com/gophercloud/gophercloud/testhelper"
"github.com/gophercloud/utils/gnocchi/metric/v1/resourcetypes"
fake "github.com/gophercloud/utils/gnocchi/testhelper/client"
)

func TestList(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()

th.Mux.HandleFunc("/v1/resource_type", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)

w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)

fmt.Fprintf(w, ResourceTypeListResult)
})

count := 0

resourcetypes.List(fake.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
count++
actual, err := resourcetypes.ExtractResourceTypes(page)
if err != nil {
t.Errorf("Failed to extract resource types: %v", err)
return false, nil
}

expected := []resourcetypes.ResourceType{
ResourceType1,
ResourceType2,
ResourceType3,
}

th.CheckDeepEquals(t, expected, actual)

return true, nil
})

if count != 1 {
t.Errorf("Expected 1 page, got %d", count)
}
}
13 changes: 13 additions & 0 deletions gnocchi/metric/v1/resourcetypes/urls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package resourcetypes

import "github.com/gophercloud/gophercloud"

const resourcePath = "resource_type"

func rootURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL(resourcePath)
}

func listURL(c *gophercloud.ServiceClient) string {
return rootURL(c)
}

0 comments on commit d7844b1

Please sign in to comment.