Added Capability to get images and images with details

new file:   examples/30-image-v1.go
	modified:   examples/config.json.dist
	modified:   examples/setup.go
	new file:   image/v1/image.go
        new file:   image/v1/image_test.go
	new file:   misc/customTypes.go
	new file:   misc/customTypes_test.go
	modified:   misc/util.go
	new file:   testUtil/testUtil.go

Change-Id: I6277a5c8915f45f4b7585855d836015ffd9e1c12
This commit is contained in:
Chris Robinson 2014-10-16 16:08:14 -07:00
parent 03aa5209e0
commit c0dcd0876f
9 changed files with 849 additions and 0 deletions

56
examples/30-image-v1.go Normal file
View File

@ -0,0 +1,56 @@
// 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"
"git.openstack.org/stackforge/golang-client.git/identity/v2"
"git.openstack.org/stackforge/golang-client.git/image/v1"
"git.openstack.org/stackforge/golang-client.git/misc"
"net/http"
"time"
)
// Image examples.
func main() {
config := getConfig()
// Authenticate with a username, password, tenant id.
auth, err := identity.AuthUserNameTenantName(config.Host,
config.Username,
config.Password,
config.ProjectName)
if err != nil {
panicString := fmt.Sprint("There was an error authenticating:", err)
panic(panicString)
}
if !auth.Access.Token.Expires.After(time.Now()) {
panic("There was an error. The auth token has an invalid expiration.")
}
var session = misc.Session{Token: auth.Access.Token.Id, TenantId: config.ProjectID, TenantName: config.ProjectName}
imageService := image.ImageService{Session: session, Client: *http.DefaultClient, Url: config.ImageHost}
imagesDetails, err := imageService.ImagesDetail()
if err != nil {
fmt.Println("Cannot access images:", err)
return
}
fmt.Println("Writing Image Ids:", err)
for _, element := range imagesDetails {
fmt.Println("ImageID: " + element.Id)
}
}

View File

@ -1,5 +1,6 @@
{
"Host": "https://.../tokens",
"ImageHost": "https://.../",
"Username": "",
"Password": "",
"ProjectID": "",

View File

@ -26,6 +26,7 @@ import (
// integration tests.
type testconfig struct {
Host string
ImageHost string
Username string
Password string
ProjectID string

241
image/v1/image.go Normal file
View File

@ -0,0 +1,241 @@
// 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 image implements a client library for accessing OpenStack Image V1 service
Images and ImageDetails can be retrieved using the api.
In addition more complex filtering and sort queries can by using the ImageQueryParameters.
*/
package image
import (
"fmt"
"git.openstack.org/stackforge/golang-client.git/misc"
"net/http"
"net/url"
)
// ImageResponse is a structure for all properties of
// an image for a non detailed query
type ImageResponse struct {
CheckSum string `json:"checksum"`
ContainerFormat string `json:"container_format"`
DiskFormat string `json:"disk_format"`
Id string `json:"id"`
Name string `json:"name"`
Size int64 `json:"size"`
}
// ImageDetailResponse is a structure for all properties of
// an image for a detailed query
type ImageDetailResponse struct {
CheckSum string `json:"checksum"`
ContainerFormat string `json:"container_format"`
CreatedAt misc.RFC8601DateTime `json:"created_at"`
Deleted bool `json:"deleted"`
DeletedAt *misc.RFC8601DateTime `json:"deleted_at"`
DiskFormat string `json:"disk_format"`
Id string `json:"id"`
IsPublic bool `json:"is_public"`
MinDisk int64 `json:"min_disk"`
MinRam int64 `json:"min_ram"`
Name string `json:"name"`
Owner *string `json:"owner"`
UpdatedAt misc.RFC8601DateTime `json:"updated_at"`
Properties map[string]string `json:"properties"`
Protected bool `json:"protected"`
Status string `json:"status"`
Size int64 `json:"size"`
VirtualSize *int64 `json:"virtual_size"` // Note: Property exists in OpenStack dev stack payloads but not Helion public cloud.
}
// ImageQueryParameters is a structure that
// contains the filter, sort, and paging parameters for
// an image or imagedetail query.
type ImageQueryParameters struct{ url.Values }
// NewImageQueryParameters creates an initialized value. This value can
// then be used to add the supported parameters. Multiple values for the
// same query parameter overwrite previous values.
func NewImageQueryParameters() ImageQueryParameters {
return ImageQueryParameters{url.Values{}}
}
// NameFilter will add a "name" query parameter with the value that is specified.
// All the images with the specified Name will be retrieved.
func (i *ImageQueryParameters) NameFilter(value string) *ImageQueryParameters {
return i.set("name", value)
}
// StatusFiler will add a "status" query parameter with the value that is specified.
// All the images with the specified Status will be retrieved.
func (i *ImageQueryParameters) StatusFilter(value string) *ImageQueryParameters {
return i.set("status", value)
}
// ContainerFormat will add a "container_format" query parameter with the value that is specified.
// All the images with the specified ContainerFormat will be retrieved.
func (i *ImageQueryParameters) ContainerFormatFilter(value string) *ImageQueryParameters {
return i.set("container_format", value)
}
// DiskFormatFilter will add a "disk_format" query parameter with the value that is specified.
// All the images with the specified DiskFormat will be retrieved.
func (i *ImageQueryParameters) DiskFormatFilter(value string) *ImageQueryParameters {
return i.set("disk_format", value)
}
// MinSizeFilter will add a "size_min" query parameter with the value that is specified.
// All the images with at least the min size bytes will be retrieved. If MaxSizeFilter
// is also specified then all images will be within the range of min and max specified.
func (i *ImageQueryParameters) MinSizeFilter(value int64) *ImageQueryParameters {
return i.set("size_min", fmt.Sprintf("%d", value))
}
// MaxSizeFilter will add a "size_max" query parameter with the value that is specified.
// All the images with no more than max size bytes will be retrieved. If MinSizeFilter
// is also specified then all images will be within the range of min and max specified.
func (i *ImageQueryParameters) MaxSizeFilter(value int64) *ImageQueryParameters {
return i.set("size_max", fmt.Sprintf("%d", value))
}
// SortKey will add a "sort_key" query parameter with the value that is specified.
// The value of the SortKey can only be "name", "status", "container_format", "disk_format",
// "size", "id", "created_at", or "updated_at" when querying for Images or ImagesDetails
func (i *ImageQueryParameters) SortKey(value string) *ImageQueryParameters {
return i.set("sort_key", value)
}
// SortDirection will add a "sort_dir" query parameter with the specified value. This will
// ensure that the sort will be ordered as ascending or descending.
// "asc" and "desc" are the only allowed values that can be specified for the sort direction.
func (i *ImageQueryParameters) SortDirection(value SortDirection) *ImageQueryParameters {
return i.set("sort_dir", string(value))
}
// MarkerSort will add a "marker" query parameter with the value that is specified.
// The value specified must be an image id value. All the images that are after the
// specified image will be returned. Marker and Limit query parameters can be used
// in combination to get a specific page of image results.
func (i *ImageQueryParameters) Marker(value string) *ImageQueryParameters {
return i.set("marker", value)
}
// Limit will add a "limit" query parameter with the value that is specified.
// The number of Images returned will not be larger than the number specified.
func (i *ImageQueryParameters) Limit(value int64) *ImageQueryParameters {
return i.set("limit", fmt.Sprintf("%d", value))
}
func (i *ImageQueryParameters) set(name string, value string) *ImageQueryParameters {
i.Set(name, value)
return i
}
// Direction of the sort, ascending or descending.
type SortDirection string
const (
Desc SortDirection = "desc"
Asc SortDirection = "asc"
)
// ImageService is a client service that can make
// requests against a OpenStack version 1 image service.
type ImageService struct {
Client http.Client
Session misc.Session
Url string
}
// Images will issue a get request to OpenStack to retrieve the list of images.
func (imageService ImageService) Images() (image []ImageResponse, err error) {
return imageService.QueryImages(nil)
}
// ImagesDetail will issue a get request to OpenStack to retrieve the list of images complete with
// additional details.
func (imageService ImageService) ImagesDetail() (image []ImageDetailResponse, err error) {
return imageService.QueryImagesDetail(nil)
}
// QueryImages will issue a get request with the specified ImageQueryParameters to retrieve the list of
// images.
func (imageService ImageService) QueryImages(queryParameters *ImageQueryParameters) (images []ImageResponse, err error) {
reqUrl, err := buildQueryUrl(imageService, queryParameters, "/images")
if err != nil {
return nil, err
}
imagesContainer := imagesResponse{}
err = misc.GetJson(reqUrl.String(), imageService.Session.Token, imageService.Client, &imagesContainer)
if err != nil {
return nil, err
}
images = imagesContainer.Images
if err != nil {
return nil, err
}
err = nil
return
}
// QueryImagesDetails will issue a get request with the specified ImageQueryParameters to retrieve the list of
// images with additional details.
func (imageService ImageService) QueryImagesDetail(queryParameters *ImageQueryParameters) (images []ImageDetailResponse, err error) {
reqUrl, err := buildQueryUrl(imageService, queryParameters, "/images/detail")
if err != nil {
return nil, err
}
imagesContainer := imagesDetailResponse{}
err = misc.GetJson(reqUrl.String(), imageService.Session.Token, imageService.Client, &imagesContainer)
if err != nil {
return nil, err
}
images = imagesContainer.Images
if err != nil {
return nil, err
}
err = nil
return
}
func buildQueryUrl(imageService ImageService, queryParameters *ImageQueryParameters, imagePartialUrl string) (url *url.URL, err error) {
url, err = url.Parse(imageService.Url)
if err != nil {
return nil, err
}
if queryParameters != nil {
url.RawQuery = queryParameters.Encode()
}
url.Path += imagePartialUrl
return url, nil
}
type imagesDetailResponse struct {
Images []ImageDetailResponse `json:"images"`
}
type imagesResponse struct {
Images []ImageResponse `json:"images"`
}

295
image/v1/image_test.go Normal file
View File

@ -0,0 +1,295 @@
// 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.
// image.go
package image_test
import (
"errors"
"git.openstack.org/stackforge/golang-client.git/image/v1"
"git.openstack.org/stackforge/golang-client.git/misc"
"git.openstack.org/stackforge/golang-client.git/testUtil"
"net/http"
"strings"
"testing"
)
var tokn = "eaaafd18-0fed-4b3a-81b4-663c99ec1cbb"
var session = misc.Session{Token: tokn, TenantId: "tenantId"}
func TestListImages(t *testing.T) {
anon := func(imageService *image.ImageService) {
images, err := imageService.Images()
if err != nil {
t.Error(err)
}
if len(images) != 3 {
t.Error(errors.New("Incorrect number of images found"))
}
expectedImage := image.ImageResponse{
Name: "Ubuntu Server 14.04.1 LTS (amd64 20140927) - Partner Image",
ContainerFormat: "bare",
DiskFormat: "qcow2",
CheckSum: "6798a7d67ff0b241b6fe165798914d86",
Id: "bec3cab5-4722-40b9-a78a-3489218e22fe",
Size: 255525376}
// Verify first one matches expected values
testUtil.Equals(t, expectedImage, images[0])
}
testImageServiceAction(t, "images", sampleImagesData, anon)
}
func TestListImageDetails(t *testing.T) {
anon := func(imageService *image.ImageService) {
images, err := imageService.ImagesDetail()
if err != nil {
t.Error(err)
}
if len(images) != 2 {
t.Error(errors.New("Incorrect number of images found"))
}
createdAt, _ := misc.NewDateTime(`"2014-09-29T14:44:31"`)
updatedAt, _ := misc.NewDateTime(`"2014-09-29T15:33:37"`)
owner := "10014302369510"
virtualSize := int64(2525125)
expectedImageDetail := image.ImageDetailResponse{
Status: "active",
Name: "Ubuntu Server 12.04.5 LTS (amd64 20140927) - Partner Image",
Deleted: false,
ContainerFormat: "bare",
CreatedAt: createdAt,
DiskFormat: "qcow2",
UpdatedAt: updatedAt,
MinDisk: 8,
Protected: false,
Id: "8ca068c5-6fde-4701-bab8-322b3e7c8d81",
MinRam: 0,
CheckSum: "de1831ea85702599a27e7e63a9a444c3",
Owner: &owner,
IsPublic: true,
DeletedAt: nil,
Properties: map[string]string{
"com.ubuntu.cloud__1__milestone": "release",
"com.hp__1__os_distro": "com.ubuntu",
"description": "Ubuntu Server 12.04.5 LTS (amd64 20140927) for HP Public Cloud. Ubuntu Server is the world's most popular Linux for cloud environments. Updates and patches for Ubuntu 12.04.5 LTS will be available until 2017-04-26. Ubuntu Server is the perfect platform for all workloads from web applications to NoSQL databases and Hadoop. More information regarding Ubuntu Cloud is available from http://www.ubuntu.com/cloud and instructions for using Juju to deploy workloads are available from http://juju.ubuntu.com EULA: http://www.ubuntu.com/about/about-ubuntu/licensing Privacy Policy: http://www.ubuntu.com/privacy-policy",
"com.ubuntu.cloud__1__suite": "precise",
"com.ubuntu.cloud__1__serial": "20140927",
"com.hp__1__bootable_volume": "True",
"com.hp__1__vendor": "Canonical",
"com.hp__1__image_lifecycle": "active",
"com.hp__1__image_type": "disk",
"os_version": "12.04",
"architecture": "x86_64",
"os_type": "linux-ext4",
"com.ubuntu.cloud__1__stream": "server",
"com.ubuntu.cloud__1__official": "True",
"com.ubuntu.cloud__1__published_at": "2014-09-29T15:33:36"},
Size: 261423616,
VirtualSize: &virtualSize}
testUtil.Equals(t, expectedImageDetail, images[0])
}
testImageServiceAction(t, "images/detail", sampleImageDetailsData, anon)
}
// When two name values are supplied the last one always wins, rather
// then support adding two names where one is ignored, only adding one
func TestSettingSameFieldTwiceShouldResultInOnlyLastValue(t *testing.T) {
anon := func(queryParameters *image.ImageQueryParameters) {
queryParameters.NameFilter("first").NameFilter("second")
}
testImageQueryParameter(t, "images?name=second", anon)
}
func TestNameFilterUrlProduced(t *testing.T) {
anon := func(queryParameters *image.ImageQueryParameters) {
queryParameters.NameFilter("CentOS deprecated")
}
testImageQueryParameter(t, "images?name=CentOS+deprecated", anon)
}
func TestStatusUrlProduced(t *testing.T) {
anon := func(queryParameters *image.ImageQueryParameters) {
queryParameters.StatusFilter("active")
}
testImageQueryParameter(t, "images?status=active", anon)
}
func TestMinMaxSizeUrlProduced(t *testing.T) {
anon := func(queryParameters *image.ImageQueryParameters) {
queryParameters.MinSizeFilter(100158).MaxSizeFilter(5300014)
}
testImageQueryParameter(t, "images?size_max=5300014&size_min=100158", anon)
}
func TestMarkerLimitUrlProduced(t *testing.T) {
anon := func(queryParameters *image.ImageQueryParameters) {
queryParameters.Marker("bec3cab5-4722-40b9-a78a-3489218e22fe").Limit(20)
}
testImageQueryParameter(t, "images?limit=20&marker=bec3cab5-4722-40b9-a78a-3489218e22fe", anon)
}
func TestContainerFormatFilterUrlProduced(t *testing.T) {
anon := func(queryParameters *image.ImageQueryParameters) {
queryParameters.ContainerFormatFilter("bare")
}
testImageQueryParameter(t, "images?container_format=bare", anon)
}
func TestSortKeySortUrlProduced(t *testing.T) {
anon := func(queryParameters *image.ImageQueryParameters) {
queryParameters.SortKey("id")
}
testImageQueryParameter(t, "images?sort_key=id", anon)
}
func TestSortDirSortUrlProduced(t *testing.T) {
anon := func(queryParameters *image.ImageQueryParameters) {
queryParameters.SortDirection(image.Asc)
}
testImageQueryParameter(t, "images?sort_dir=asc", anon)
}
func testImageQueryParameter(t *testing.T, uriEndsWith string, queryParamAction func(*image.ImageQueryParameters)) {
anon := func(imageService *image.ImageService) {
queryParameters := image.NewImageQueryParameters()
queryParamAction(&queryParameters)
_, _ = imageService.QueryImages(&queryParameters)
}
testImageServiceAction(t, uriEndsWith, sampleImagesData, anon)
}
func testImageServiceAction(t *testing.T, uriEndsWith string, testData string, imageServiceAction func(*image.ImageService)) {
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()
imageService := image.ImageService{Session: session, Client: *http.DefaultClient, Url: apiServer.URL}
imageServiceAction(&imageService)
}
var sampleImagesData = `{
"images":[
{
"name":"Ubuntu Server 14.04.1 LTS (amd64 20140927) - Partner Image",
"container_format":"bare",
"disk_format":"qcow2",
"checksum":"6798a7d67ff0b241b6fe165798914d86",
"id":"bec3cab5-4722-40b9-a78a-3489218e22fe",
"size":255525376
},
{
"name":"Ubuntu Server 12.04.5 LTS (amd64 20140927) - Partner Image",
"container_format":"bare",
"disk_format":"qcow2",
"checksum":"de1831ea85702599a27e7e63a9a444c3",
"id":"8ca068c5-6fde-4701-bab8-322b3e7c8d81",
"size":261423616
},
{
"name":"HP_LR-PC_Load_Generator_12-02_Windows-2008R2x64",
"container_format":"bare",
"disk_format":"qcow2",
"checksum":"052d70c2b4d4988a8816197381e9083a",
"id":"12b9c19b-8823-4f40-9531-0f05fb0933f2",
"size":14012055552
}
]
}`
var sampleImageDetailsData = `{
"images":[
{
"status":"active",
"name":"Ubuntu Server 12.04.5 LTS (amd64 20140927) - Partner Image",
"deleted":false,
"container_format":"bare",
"created_at":"2014-09-29T14:44:31",
"disk_format":"qcow2",
"updated_at":"2014-09-29T15:33:37",
"min_disk":8,
"protected":false,
"id":"8ca068c5-6fde-4701-bab8-322b3e7c8d81",
"min_ram":0,
"checksum":"de1831ea85702599a27e7e63a9a444c3",
"owner":"10014302369510",
"is_public":true,
"deleted_at":null,
"properties":{
"com.ubuntu.cloud__1__milestone":"release",
"com.hp__1__os_distro":"com.ubuntu",
"description":"Ubuntu Server 12.04.5 LTS (amd64 20140927) for HP Public Cloud. Ubuntu Server is the world's most popular Linux for cloud environments. Updates and patches for Ubuntu 12.04.5 LTS will be available until 2017-04-26. Ubuntu Server is the perfect platform for all workloads from web applications to NoSQL databases and Hadoop. More information regarding Ubuntu Cloud is available from http://www.ubuntu.com/cloud and instructions for using Juju to deploy workloads are available from http://juju.ubuntu.com EULA: http://www.ubuntu.com/about/about-ubuntu/licensing Privacy Policy: http://www.ubuntu.com/privacy-policy",
"com.ubuntu.cloud__1__suite":"precise",
"com.ubuntu.cloud__1__serial":"20140927",
"com.hp__1__bootable_volume":"True",
"com.hp__1__vendor":"Canonical",
"com.hp__1__image_lifecycle":"active",
"com.hp__1__image_type":"disk",
"os_version":"12.04",
"architecture":"x86_64",
"os_type":"linux-ext4",
"com.ubuntu.cloud__1__stream":"server",
"com.ubuntu.cloud__1__official":"True",
"com.ubuntu.cloud__1__published_at":"2014-09-29T15:33:36"
},
"size":261423616,
"virtual_size":2525125
},
{
"status":"active",
"name":"Windows Server 2008 Enterprise SP2 x64 Volume License 20140415 (b)",
"deleted":false,
"container_format":"bare",
"created_at":"2014-04-25T19:53:24",
"disk_format":"qcow2",
"updated_at":"2014-04-25T19:57:11",
"min_disk":30,
"protected":true,
"id":"1294610e-fdc4-579b-829b-d0c9f5c0a612",
"min_ram":0,
"checksum":"37208aa6d49929f12132235c5f834f2d",
"owner":null,
"is_public":true,
"deleted_at":null,
"properties":{
"hp_image_license":"1002",
"com.hp__1__os_distro":"com.microsoft.server",
"com.hp__1__image_lifecycle":"active",
"com.hp__1__image_type":"disk",
"architecture":"x86_64",
"com.hp__1__license_os":"1002",
"com.hp__1__bootable_volume":"true"
},
"size":6932856832,
"virtual_size":null
}
]
}`

57
misc/customTypes.go Normal file
View File

@ -0,0 +1,57 @@
// 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 misc
import (
"time"
)
// RFC8601DateTime is a type for decoding and encoding json
// date times that follow RFC 8601 format. The type currently
// decodes and encodes with exactly precision to seconds. If more
// formats of RFC8601 need to be supported additional work
// will be needed.
type RFC8601DateTime struct {
time.Time
}
// NewDateTime creates a new RFC8601DateTime taking a string as input.
// It must follow the "2006-01-02T15:04:05" pattern.
func NewDateTime(input string) (val RFC8601DateTime, err error) {
val = RFC8601DateTime{}
err = val.formatValue(input)
return val, err
}
// UnmarshalJSON converts the bytes give to a RFC8601DateTime
// Errors will occur if the bytes when converted to a string
// don't match the format "2006-01-02T15:04:05".
func (r *RFC8601DateTime) UnmarshalJSON(data []byte) error {
return r.formatValue(string(data))
}
// MarshalJSON converts a RFC8601DateTime to a []byte.
func (r RFC8601DateTime) MarshalJSON() ([]byte, error) {
val := r.Time.Format(format)
return []byte(val), nil
}
func (r *RFC8601DateTime) formatValue(input string) (err error) {
timeVal, err := time.Parse(format, input)
r.Time = timeVal
return
}
const format = `"2006-01-02T15:04:05"`

58
misc/customTypes_test.go Normal file
View File

@ -0,0 +1,58 @@
// 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 misc_test
import (
"encoding/json"
"git.openstack.org/stackforge/golang-client.git/misc"
"git.openstack.org/stackforge/golang-client.git/testUtil"
"testing"
"time"
)
var testValue = `{"created_at":"2014-09-29T14:44:31"}`
var testTime, _ = time.Parse(`"2006-01-02T15:04:05"`, `"2014-09-29T14:44:31"`)
var timeTestValue = timeTest{CreatedAt: misc.RFC8601DateTime{testTime}}
func TestMarshalTimeTest(t *testing.T) {
bytes, _ := json.Marshal(timeTestValue)
testUtil.Equals(t, testValue, string(bytes))
}
func TestUnmarshalValidTimeTest(t *testing.T) {
val := timeTest{}
err := json.Unmarshal([]byte(testValue), &val)
testUtil.IsNil(t, err)
testUtil.Equals(t, timeTestValue.CreatedAt.Time, val.CreatedAt.Time)
}
func TestUnmarshalInvalidDataFormatTimeTest(t *testing.T) {
val := timeTest{}
err := json.Unmarshal([]byte("something other than date time"), &val)
testUtil.Assert(t, err != nil, "expected an error")
}
// Added this test to ensure that its understood that
// only one specific format is supported at this time.
func TestUnmarshalInvalidDateTimeFormatTimeTest(t *testing.T) {
val := timeTest{}
err := json.Unmarshal([]byte("2014-09-29T14:44"), &val)
testUtil.Assert(t, err != nil, "expected an error")
}
type timeTest struct {
CreatedAt misc.RFC8601DateTime `json:"created_at"`
}

View File

@ -16,11 +16,39 @@ package misc
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
)
var zeroByte = new([]byte) //pointer to empty []byte
type Session struct {
Token string
TenantName string
TenantId string
}
//GetJson sends an Http Request with using the "GET" method and with
//an "Accept" header set to "application/json" and the authenication token
//set to the specified token value. The request is made by the
//specified client. The val interface should be a pointer to the
//structure that the json response should be decoded into.
func GetJson(url string, token string, client http.Client, val interface{}) (err error) {
req, err := createJsonGetRequest(url, token)
if err != nil {
return err
}
err = executeRequestCheckStatusDecodeJsonResponse(client, req, val)
if err != nil {
return err
}
return nil
}
//CallAPI sends an HTTP request using "method" to "url".
//For uploading / sending file, caller needs to set the "content". Otherwise,
//set it to zero length []byte. If Header fields need to be set, then set it in
@ -108,3 +136,32 @@ func CheckHttpResponseStatusCode(resp *http.Response) error {
}
return errors.New("Error: unexpected response status code")
}
func createJsonGetRequest(url string, token string) (req *http.Request, err error) {
req, err = http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", "application/json")
req.Header.Set("X-Auth-Token", token)
return req, nil
}
func executeRequestCheckStatusDecodeJsonResponse(client http.Client, req *http.Request, val interface{}) (err error) {
resp, err := client.Do(req)
if err != nil {
return err
}
err = CheckHttpResponseStatusCode(resp)
if err != nil {
return err
}
err = json.NewDecoder(resp.Body).Decode(&val)
defer resp.Body.Close()
return err
}

83
testUtil/testUtil.go Normal file
View File

@ -0,0 +1,83 @@
// 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.
// image.go
package testUtil
import (
"errors"
"fmt"
"net/http"
"net/http/httptest"
"path/filepath"
"reflect"
"runtime"
"testing"
)
// Equals fails the test if exp is not equal to act.
// Code was copied from https://github.com/benbjohnson/testing MIT license
func Equals(tb testing.TB, exp, act interface{}) {
if !reflect.DeepEqual(exp, act) {
_, file, line, _ := runtime.Caller(1)
fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act)
tb.FailNow()
}
}
// Assert fails the test if the condition is false.
// Code was copied from https://github.com/benbjohnson/testing MIT license
func Assert(tb testing.TB, condition bool, msg string, v ...interface{}) {
if !condition {
_, file, line, _ := runtime.Caller(1)
fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...)
tb.FailNow()
}
}
// IsNil ensures that the act interface is nil
// otherwise an error is raised.
func IsNil(tb testing.TB, act interface{}) {
if act != nil {
tb.Error("expected nil", act)
tb.FailNow()
}
}
// Creates a httptest.Server that can be used to test GetJson requests. Just specify the token,
// json payload that is to be read by the response, and a verification func that can be used
// to do additional validation of the request that is built
func CreateGetJsonTestRequestServer(t *testing.T, expectedAuthTokenValue string, jsonPayload string, verifyRequest func(*http.Request)) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
headerValuesEqual(t, r, "X-Auth-Token", expectedAuthTokenValue)
headerValuesEqual(t, r, "Accept", "application/json")
verifyRequest(r)
if r.Method == "GET" {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(jsonPayload))
w.WriteHeader(200)
return
}
t.Error(errors.New("Failed: r.Method == GET"))
}))
}
func headerValuesEqual(t *testing.T, req *http.Request, name string, expectedValue string) {
actualValue := req.Header.Get(name)
if actualValue != expectedValue {
t.Error(errors.New(fmt.Sprintf("Expected Header {Name:'%s', Value:'%s', actual value '%s'", name, expectedValue, actualValue)))
}
}