Merge "Added Capability to get volumes and volumes with details"

This commit is contained in:
Jenkins 2016-12-14 22:36:04 +00:00 committed by Gerrit Code Review
commit 9c1196d0c6
3 changed files with 509 additions and 0 deletions

79
examples/40-volume-v2.go Normal file
View File

@ -0,0 +1,79 @@
// Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
//
// 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 main
import (
"fmt"
"net/http"
"time"
"git.openstack.org/openstack/golang-client.git/volume"
"git.openstack.org/openstack/golang-client.git/openstack"
)
// Volume examples.
func main() {
config := getConfig()
// Authenticate with a username, password, tenant id.
creds := openstack.AuthOpts{
AuthUrl: config.Host,
Project: config.ProjectName,
Username: config.Username,
Password: config.Password,
}
auth, err := openstack.DoAuthRequest(creds)
if err != nil {
panicString := fmt.Sprint("There was an error authenticating:", err)
panic(panicString)
}
if !auth.GetExpiration().After(time.Now()) {
panic("There was an error. The auth token has an invalid expiration.")
}
// Find the endpoint for the volume v2 service.
url, err := auth.GetEndpoint("volumev2", "")
if url == "" || err != nil {
panic("v2 volume service url not found during authentication")
}
// Make a new client with these creds
sess, err := openstack.NewSession(nil, auth, nil)
if err != nil {
panicString := fmt.Sprint("Error crating new Session:", err)
panic(panicString)
}
volumeService := volume.Service{
Session: *sess,
Client: *http.DefaultClient,
URL: url, // We're forcing Volume v2 for now
}
volumesDetails, err := volumeService.VolumesDetail()
if err != nil {
panicString := fmt.Sprint("Cannot access volumes:", err)
panic(panicString)
}
var volumeIDs = make([]string, 0)
for _, element := range volumesDetails {
volumeIDs = append(volumeIDs, element.ID)
}
if len(volumeIDs) == 0 {
panicString := fmt.Sprint("No volumes found, check to make sure access is correct")
panic(panicString)
}
}

210
volume/volume.go Normal file
View File

@ -0,0 +1,210 @@
// Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
//
// 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 volume implements a client library for accessing OpenStack Volume service
Volumes and VolumeDetails can be retrieved using the api.
In addition more complex filtering and sort queries can by using the VolumeQueryParameters.
*/
package volume
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"git.openstack.org/openstack/golang-client.git/openstack"
"git.openstack.org/openstack/golang-client.git/util"
)
// Service is a client service that can make
// requests against a OpenStack volume service.
// Below is an example on creating an volume service and getting volumes:
// volumeService := volume.VolumeService{Client: *http.DefaultClient, TokenId: tokenId, Url: "http://volumeservicelocation"}
// volumes:= volumeService.Volumes()
type Service struct {
Session openstack.Session
Client http.Client
URL string
}
// Response is a structure for all properties of
// an volume for a non detailed query
type Response struct {
ID string `json:"id"`
Links []map[string]string `json:"links"`
Name string `json:"name"`
}
// DetailResponse is a structure for all properties of
// an volume for a detailed query
type DetailResponse struct {
ID string `json:"id"`
Attachments []map[string]string `json:"attachments"`
Links []map[string]string `json:"links"`
Metadata map[string]string `json:"metadata"`
Protected bool `json:"protected"`
Status string `json:"status"`
MigrationStatus string `json:"migration_status"`
UserID string `json:"user_id"`
Encrypted bool `json:"encrypted"`
Multiattach bool `json:"multiattach"`
CreatedAt util.RFC8601DateTime `json:"created_at"`
Description string `json:"description"`
Volume_type string `json:"volume_type"`
Name string `json:"name"`
Source_volid string `json:"source_volid"`
Snapshot_id string `json:"snapshot_id"`
Size int64 `json:"size"`
Aavailability_zone string `json:"availability_zone"`
Rreplication_status string `json:"replication_status"`
Consistencygroup_id string `json:"consistencygroup_id"`
}
// QueryParameters is a structure that
// contains the filter, sort, and paging parameters for
// an volume or volumedetail query.
type QueryParameters struct {
All_tenant int64
Marker string
Limit int64
SortKey string
SortDirection SortDirection
}
// SortDirection of the sort, ascending or descending.
type SortDirection string
const (
// Desc specifies the sort direction to be descending.
Desc SortDirection = "desc"
// Asc specifies the sort direction to be ascending.
Asc SortDirection = "asc"
)
// Volumes will issue a get request to OpenStack to retrieve the list of volumes.
func (volumeService Service) Volumes() (volume []Response, err error) {
return volumeService.QueryVolumes(nil)
}
// VolumesDetail will issue a get request to OpenStack to retrieve the list of volumes complete with
// additional details.
func (volumeService Service) VolumesDetail() (volume []DetailResponse, err error) {
return volumeService.QueryVolumesDetail(nil)
}
// QueryVolumes will issue a get request with the specified VolumeQueryParameters to retrieve the list of
// volumes.
func (volumeService Service) QueryVolumes(queryParameters *QueryParameters) ([]Response, error) {
volumesContainer := volumesResponse{}
err := volumeService.queryVolumes(false /*includeDetails*/, &volumesContainer, queryParameters)
if err != nil {
return nil, err
}
return volumesContainer.Volumes, nil
}
// QueryVolumesDetail will issue a get request with the specified QueryParameters to retrieve the list of
// volumes with additional details.
func (volumeService Service) QueryVolumesDetail(queryParameters *QueryParameters) ([]DetailResponse, error) {
volumesDetailContainer := volumesDetailResponse{}
err := volumeService.queryVolumes(true /*includeDetails*/, &volumesDetailContainer, queryParameters)
if err != nil {
return nil, err
}
return volumesDetailContainer.Volumes, nil
}
func (volumeService Service) queryVolumes(includeDetails bool, volumesResponseContainer interface{}, queryParameters *QueryParameters) error {
urlPostFix := "/volumes"
if includeDetails {
urlPostFix = urlPostFix + "/detail"
}
reqURL, err := buildQueryURL(volumeService, queryParameters, urlPostFix)
if err != nil {
return err
}
var headers http.Header = http.Header{}
headers.Set("Accept", "application/json")
resp, err := volumeService.Session.Get(reqURL.String(), nil, &headers)
if err != nil {
return err
}
err = util.CheckHTTPResponseStatusCode(resp)
if err != nil {
return err
}
rbody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return errors.New("aaa")
}
if err = json.Unmarshal(rbody, &volumesResponseContainer); err != nil {
return err
}
return nil
}
func buildQueryURL(volumeService Service, queryParameters *QueryParameters, volumePartialURL string) (*url.URL, error) {
reqURL, err := url.Parse(volumeService.URL)
if err != nil {
return nil, err
}
if queryParameters != nil {
values := url.Values{}
if queryParameters.All_tenant != 0 {
values.Set("all_tenant", fmt.Sprintf("%d", queryParameters.All_tenant))
}
if queryParameters.Limit != 0 {
values.Set("limit", fmt.Sprintf("%d", queryParameters.Limit))
}
if queryParameters.Marker != "" {
values.Set("marker", queryParameters.Marker)
}
if queryParameters.SortKey != "" {
values.Set("sort_key", queryParameters.SortKey)
}
if queryParameters.SortDirection != "" {
values.Set("sort_dir", string(queryParameters.SortDirection))
}
if len(values) > 0 {
reqURL.RawQuery = values.Encode()
}
}
reqURL.Path += volumePartialURL
return reqURL, nil
}
type volumesDetailResponse struct {
Volumes []DetailResponse `json:"volumes"`
}
type volumesResponse struct {
Volumes []Response `json:"volumes"`
}

220
volume/volume_test.go Normal file
View File

@ -0,0 +1,220 @@
// Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
// 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.
// volume.go
package volume_test
import (
"errors"
"net/http"
"strings"
"testing"
"git.openstack.org/openstack/golang-client.git/volume/v2"
"git.openstack.org/openstack/golang-client.git/openstack"
"git.openstack.org/openstack/golang-client.git/testUtil"
"git.openstack.org/openstack/golang-client.git/util"
)
var tokn = "ae5aebe5-6a5d-4a40-840a-9736a067aff4"
func TestListVolumes(t *testing.T) {
anon := func(volumeService *volume.Service) {
volumes, err := volumeService.Volumes()
if err != nil {
t.Error(err)
}
if len(volumes) != 2 {
t.Error(errors.New("Incorrect number of volumes found"))
}
expectedVolume := volume.Response{
Name: "volume_test1",
ID: "f5fc9874-fc89-4814-a358-23ba83a6115f",
Links: []map[string]string{{"href": "http://172.16.197.131:8776/v2/1d8837c5fcef4892951397df97661f97/volumes/f5fc9874-fc89-4814-a358-23ba83a6115f", "rel": "self"},
{"href": "http://172.16.197.131:8776/1d8837c5fcef4892951397df97661f97/volumes/f5fc9874-fc89-4814-a358-23ba83a6115f", "rel": "bookmark"}}}
// Verify first one matches expected values
testUtil.Equals(t, expectedVolume, volumes[0])
}
testVolumeServiceAction(t, "volumes", sampleVolumesData, anon)
}
func TestListVolumeDetails(t *testing.T) {
anon := func(volumeService *volume.Service) {
volumes, err := volumeService.VolumesDetail()
if err != nil {
t.Error(err)
}
if len(volumes) != 2 {
t.Error(errors.New("Incorrect number of volumes found"))
}
createdAt, _ := util.NewDateTime(`"2014-09-29T14:44:31"`)
expectedVolumeDetail := volume.DetailResponse{
ID: "30becf77-63fe-4f5e-9507-a0578ffe0949",
Attachments: []map[string]string{{"attachment_id": "ddb2ac07-ed62-49eb-93da-73b258dd9bec", "host_name": "host_test", "volume_id": "30becf77-63fe-4f5e-9507-a0578ffe0949", "device": "/dev/vdb", "id": "30becf77-63fe-4f5e-9507-a0578ffe0949", "server_id": "0f081aae-1b0c-4b89-930c-5f2562460c72"}},
Links: []map[string]string{{"href": "http://172.16.197.131:8776/v2/1d8837c5fcef4892951397df97661f97/volumes/30becf77-63fe-4f5e-9507-a0578ffe0949", "rel": "self"},
{"href": "http://172.16.197.131:8776/1d8837c5fcef4892951397df97661f97/volumes/30becf77-63fe-4f5e-9507-a0578ffe0949", "rel": "bookmark"}},
Metadata: map[string]string{"readonly": "false", "attached_mode": "rw"},
Protected: false,
Status: "available",
MigrationStatus: "",
UserID: "a971aa69-c61a-4a49-b392-b0e41609bc5d",
Encrypted: false,
Multiattach: false,
CreatedAt: createdAt,
Description: "test volume",
Volume_type: "test_type",
Name: "test_volume",
Source_volid: "4b58bbb8-3b00-4f87-8243-8c622707bbab",
Snapshot_id: "cc488e4a-9649-4e5f-ad12-20ab37c683b5",
Size: 2,
Aavailability_zone: "default_cluster",
Rreplication_status: "",
Consistencygroup_id: ""}
testUtil.Equals(t, expectedVolumeDetail, volumes[0])
}
testVolumeServiceAction(t, "volumes/detail", sampleVolumeDetailsData, anon)
}
func TestLimitFilterUrlProduced(t *testing.T) {
testVolumeQueryParameter(t, "volumes?limit=2",
volume.QueryParameters{Limit: 2})
}
func TestAll_tenantFilterUrlProduced(t *testing.T) {
testVolumeQueryParameter(t, "volumes?all_tenant=1",
volume.QueryParameters{All_tenant: 1})
}
func TestMarkerUrlProduced(t *testing.T) {
testVolumeQueryParameter(t, "volumes?marker=1776335d-72f1-48c9-b0e7-74c62cb8fede",
volume.QueryParameters{Marker: "1776335d-72f1-48c9-b0e7-74c62cb8fede"})
}
func TestSortKeySortUrlProduced(t *testing.T) {
testVolumeQueryParameter(t, "volumes?sort_key=id",
volume.QueryParameters{SortKey: "id"})
}
func TestSortDirSortUrlProduced(t *testing.T) {
testVolumeQueryParameter(t, "volumes?sort_dir=asc",
volume.QueryParameters{SortDirection: volume.Asc})
}
func testVolumeQueryParameter(t *testing.T, uriEndsWith string, queryParameters volume.QueryParameters) {
anon := func(volumeService *volume.Service) {
_, _ = volumeService.QueryVolumes(&queryParameters)
}
testVolumeServiceAction(t, uriEndsWith, sampleVolumesData, anon)
}
func testVolumeServiceAction(t *testing.T, uriEndsWith string, testData string, volumeServiceAction func(*volume.Service)) {
anon := func(req *http.Request) {
reqURL := req.URL.String()
if !strings.HasSuffix(reqURL, uriEndsWith) {
t.Error(errors.New("Incorrect url created, expected:" + uriEndsWith + " at the end, actual url:" + reqURL))
}
}
apiServer := testUtil.CreateGetJSONTestRequestServer(t, tokn, testData, anon)
defer apiServer.Close()
auth := openstack.AuthToken{
Access: openstack.AccessType{
Token: openstack.Token{
ID: tokn,
},
},
}
sess, _ := openstack.NewSession(http.DefaultClient, auth, nil)
volumeService := volume.Service{
Session: *sess,
URL: apiServer.URL,
}
volumeServiceAction(&volumeService)
}
var sampleVolumesData = `{
"volumes":[
{
"name":"volume_test1",
"id":"f5fc9874-fc89-4814-a358-23ba83a6115f",
"links":[{"href": "http://172.16.197.131:8776/v2/1d8837c5fcef4892951397df97661f97/volumes/f5fc9874-fc89-4814-a358-23ba83a6115f", "rel": "self"},
{"href": "http://172.16.197.131:8776/1d8837c5fcef4892951397df97661f97/volumes/f5fc9874-fc89-4814-a358-23ba83a6115f", "rel": "bookmark"}]
},
{
"name":"volume_test1",
"id":"60055a0a-2451-4d78-af9c-f2302150602f",
"links":[{"href": "http://172.16.197.131:8776/v2/1d8837c5fcef4892951397df97661f97/volumes/60055a0a-2451-4d78-af9c-f2302150602f", "rel": "self"},
{"href": "http://172.16.197.131:8776/1d8837c5fcef4892951397df97661f97/volumes/60055a0a-2451-4d78-af9c-f2302150602f", "rel": "bookmark"}]
}
]
}`
var sampleVolumeDetailsData = `{
"volumes":[
{
"id":"30becf77-63fe-4f5e-9507-a0578ffe0949",
"attachments":[{"attachment_id": "ddb2ac07-ed62-49eb-93da-73b258dd9bec", "host_name": "host_test", "volume_id": "30becf77-63fe-4f5e-9507-a0578ffe0949", "device": "/dev/vdb", "id": "30becf77-63fe-4f5e-9507-a0578ffe0949", "server_id": "0f081aae-1b0c-4b89-930c-5f2562460c72"}],
"links":[{"href": "http://172.16.197.131:8776/v2/1d8837c5fcef4892951397df97661f97/volumes/30becf77-63fe-4f5e-9507-a0578ffe0949", "rel": "self"},
{"href": "http://172.16.197.131:8776/1d8837c5fcef4892951397df97661f97/volumes/30becf77-63fe-4f5e-9507-a0578ffe0949", "rel": "bookmark"}],
"metadata":{"readonly": "false", "attached_mode": "rw"},
"protected":false,
"status":"available",
"migrationStatus":null,
"user_id":"a971aa69-c61a-4a49-b392-b0e41609bc5d",
"encrypted":false,
"multiattach":false,
"created_at":"2014-09-29T14:44:31",
"description":"test volume",
"volume_type":"test_type",
"name":"test_volume",
"source_volid":"4b58bbb8-3b00-4f87-8243-8c622707bbab",
"snapshot_id":"cc488e4a-9649-4e5f-ad12-20ab37c683b5",
"size":2,
"availability_zone":"default_cluster",
"replication_status":null,
"consistencygroup_id":null
},
{
"id":"242d3d14-2efd-4c63-9a6b-ef6bc8eed756",
"attachments":[{"attachment_id": "9d4fb045-f957-489b-9e7d-f6f156002c04", "host_name": "host_test2", "volume_id": "242d3d14-2efd-4c63-9a6b-ef6bc8eed756", "device": "/dev/vdb", "id": "242d3d14-2efd-4c63-9a6b-ef6bc8eed756", "server_id": "9f47bd1c-c596-424d-abbe-e5e1a7a65fdc"}],
"links":[{"href": "http://172.16.197.131:8776/v2/1d8837c5fcef4892951397df97661f97/volumes/242d3d14-2efd-4c63-9a6b-ef6bc8eed756", "rel": "self"},
{"href": "http://172.16.197.131:8776/1d8837c5fcef4892951397df97661f97/volumes/242d3d14-2efd-4c63-9a6b-ef6bc8eed756", "rel": "bookmark"}],
"metadata":{"readonly": "false", "attached_mode": "rw"},
"protected":false,
"status":"available",
"migrationStatus":null,
"user_id":"a971aa69-c61a-4a49-b392-b0e41609bc5d",
"encrypted":false,
"multiattach":false,
"created_at":"2014-09-29T14:44:35",
"description":"test volume 2",
"volume_type":"test_type",
"name":"test_volume2",
"source_volid":null,
"snapshot_id":null,
"size":2,
"availability_zone":"default_cluster",
"replication_status":null,
"consistencygroup_id":null
}
]
}`