Commit 7e97d22e authored by Victor Marmol's avatar Victor Marmol

Update cAdvisor dependency and remove perigee.

Perigee dependency is unused.
parent a8dd10da
......@@ -151,13 +151,13 @@
},
{
"ImportPath": "github.com/google/cadvisor/client",
"Comment": "0.6.2",
"Rev": "89088df70eca64cf9d6b9a23a3d2bc21a30916d6"
"Comment": "0.10.1-30-gb5e2f37",
"Rev": "b5e2f3788e4a39a0836c5490e6bf31832400c1f3"
},
{
"ImportPath": "github.com/google/cadvisor/info",
"Comment": "0.6.2",
"Rev": "89088df70eca64cf9d6b9a23a3d2bc21a30916d6"
"ImportPath": "github.com/google/cadvisor/info/v1",
"Comment": "0.10.1-30-gb5e2f37",
"Rev": "b5e2f3788e4a39a0836c5490e6bf31832400c1f3"
},
{
"ImportPath": "github.com/google/gofuzz",
......@@ -228,11 +228,6 @@
"Rev": "6c34ef819e19b4e16f410100ace4aa006f0e3bf8"
},
{
"ImportPath": "github.com/racker/perigee",
"Comment": "v0.0.0-18-g0c00cb0",
"Rev": "0c00cb0a026b71034ebc8205263c77dad3577db5"
},
{
"ImportPath": "github.com/rackspace/gophercloud",
"Comment": "v1.0.0-490-g32d0a89",
"Rev": "32d0a893a8ef70abe76dc5153e2925b39cbea7f7"
......
......@@ -24,7 +24,7 @@ import (
"path"
"strings"
"github.com/google/cadvisor/info"
info "github.com/google/cadvisor/info/v1"
)
// Client represents the base URL for a cAdvisor client.
......@@ -142,19 +142,22 @@ func (self *Client) httpGetJsonData(data, postData interface{}, url, infoName st
resp, err = http.Get(url)
}
if err != nil {
return fmt.Errorf("unable to get %q: %v", infoName, err)
return fmt.Errorf("unable to get %q from %q: %v", infoName, url, err)
}
if resp == nil {
return fmt.Errorf("received empty response from %q", infoName)
return fmt.Errorf("received empty response for %q from %q", infoName, url)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
err = fmt.Errorf("unable to read all %q: %v", infoName, err)
err = fmt.Errorf("unable to read all %q from %q: %v", infoName, url, err)
return err
}
if resp.StatusCode != 200 {
return fmt.Errorf("request %q failed with error: %q", url, strings.TrimSpace(string(body)))
}
if err = json.Unmarshal(body, data); err != nil {
err = fmt.Errorf("unable to unmarshal %q (%v): %v", infoName, string(body), err)
err = fmt.Errorf("unable to unmarshal %q (Body: %q) from %q with error: %v", infoName, string(body), url, err)
return err
}
return nil
......
......@@ -21,11 +21,12 @@ import (
"net/http/httptest"
"path"
"reflect"
"strings"
"testing"
"time"
"github.com/google/cadvisor/info"
itest "github.com/google/cadvisor/info/test"
info "github.com/google/cadvisor/info/v1"
itest "github.com/google/cadvisor/info/v1/test"
"github.com/kr/pretty"
)
......@@ -43,22 +44,25 @@ func testGetJsonData(
return nil
}
func cadvisorTestClient(path string, expectedPostObj, expectedPostObjEmpty, replyObj interface{}, t *testing.T) (*Client, *httptest.Server, error) {
func cadvisorTestClient(path string, expectedPostObj *info.ContainerInfoRequest, replyObj interface{}, t *testing.T) (*Client, *httptest.Server, error) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == path {
if expectedPostObj != nil {
expectedPostObjEmpty := new(info.ContainerInfoRequest)
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(expectedPostObjEmpty); err != nil {
t.Errorf("Received invalid object: %v", err)
}
if !reflect.DeepEqual(expectedPostObj, expectedPostObjEmpty) {
t.Errorf("Received unexpected object: %+v", expectedPostObjEmpty)
if expectedPostObj.NumStats != expectedPostObjEmpty.NumStats ||
expectedPostObj.Start.Unix() != expectedPostObjEmpty.Start.Unix() ||
expectedPostObj.End.Unix() != expectedPostObjEmpty.End.Unix() {
t.Errorf("Received unexpected object: %+v, expected: %+v", expectedPostObjEmpty, expectedPostObj)
}
}
encoder := json.NewEncoder(w)
encoder.Encode(replyObj)
} else if r.URL.Path == "/api/v1.2/machine" {
fmt.Fprint(w, `{"num_cores":8,"memory_capacity":31625871360}`)
fmt.Fprint(w, `{"num_cores":8,"memory_capacity":31625871360, "disk_map":["8:0":{"name":"sda","major":8,"minor":0,"size":10737418240}]}`)
} else {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Page not found.")
......@@ -78,8 +82,16 @@ func TestGetMachineinfo(t *testing.T) {
minfo := &info.MachineInfo{
NumCores: 8,
MemoryCapacity: 31625871360,
}
client, server, err := cadvisorTestClient("/api/v1.2/machine", nil, nil, minfo, t)
DiskMap: map[string]info.DiskInfo{
"8:0": {
Name: "sda",
Major: 8,
Minor: 0,
Size: 10737418240,
},
},
}
client, server, err := cadvisorTestClient("/api/v1.2/machine", nil, minfo, t)
if err != nil {
t.Fatalf("unable to get a client %v", err)
}
......@@ -101,7 +113,7 @@ func TestGetContainerInfo(t *testing.T) {
}
containerName := "/some/container"
cinfo := itest.GenerateRandomContainerInfo(containerName, 4, query, 1*time.Second)
client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.2/containers%v", containerName), query, &info.ContainerInfoRequest{}, cinfo, t)
client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.2/containers%v", containerName), query, cinfo, t)
if err != nil {
t.Fatalf("unable to get a client %v", err)
}
......@@ -116,6 +128,30 @@ func TestGetContainerInfo(t *testing.T) {
}
}
// Test a request failing
func TestRequestFails(t *testing.T) {
errorText := "there was an error"
// Setup a server that simply fails.
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, errorText, 500)
}))
client, err := NewClient(ts.URL)
if err != nil {
ts.Close()
t.Fatal(err)
}
defer ts.Close()
_, err = client.ContainerInfo("/", &info.ContainerInfoRequest{NumStats: 3})
if err == nil {
t.Fatalf("Expected non-nil error")
}
expectedError := fmt.Sprintf("request failed with error: %q", errorText)
if strings.Contains(err.Error(), expectedError) {
t.Fatalf("Expected error %q but received %q", expectedError, err)
}
}
func TestGetSubcontainersInfo(t *testing.T) {
query := &info.ContainerInfoRequest{
NumStats: 3,
......@@ -129,7 +165,7 @@ func TestGetSubcontainersInfo(t *testing.T) {
*cinfo1,
*cinfo2,
}
client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.2/subcontainers%v", containerName), query, &info.ContainerInfoRequest{}, response, t)
client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.2/subcontainers%v", containerName), query, response, t)
if err != nil {
t.Fatalf("unable to get a client %v", err)
}
......
// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 v1
import (
"testing"
"time"
)
func TestStatsStartTime(t *testing.T) {
N := 10
stats := make([]*ContainerStats, 0, N)
ct := time.Now()
for i := 0; i < N; i++ {
s := &ContainerStats{
Timestamp: ct.Add(time.Duration(i) * time.Second),
}
stats = append(stats, s)
}
cinfo := &ContainerInfo{
ContainerReference: ContainerReference{
Name: "/some/container",
},
Stats: stats,
}
ref := ct.Add(time.Duration(N-1) * time.Second)
end := cinfo.StatsEndTime()
if !ref.Equal(end) {
t.Errorf("end time is %v; should be %v", end, ref)
}
}
func TestStatsEndTime(t *testing.T) {
N := 10
stats := make([]*ContainerStats, 0, N)
ct := time.Now()
for i := 0; i < N; i++ {
s := &ContainerStats{
Timestamp: ct.Add(time.Duration(i) * time.Second),
}
stats = append(stats, s)
}
cinfo := &ContainerInfo{
ContainerReference: ContainerReference{
Name: "/some/container",
},
Stats: stats,
}
ref := ct
start := cinfo.StatsStartTime()
if !ref.Equal(start) {
t.Errorf("start time is %v; should be %v", start, ref)
}
}
func createStats(cpuUsage, memUsage uint64, timestamp time.Time) *ContainerStats {
stats := &ContainerStats{}
stats.Cpu.Usage.PerCpu = []uint64{cpuUsage}
stats.Cpu.Usage.Total = cpuUsage
stats.Cpu.Usage.System = 0
stats.Cpu.Usage.User = cpuUsage
stats.Memory.Usage = memUsage
stats.Timestamp = timestamp
return stats
}
// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 v1
type FsInfo struct {
// Block device associated with the filesystem.
Device string `json:"device"`
// Total number of bytes available on the filesystem.
Capacity uint64 `json:"capacity"`
}
type Node struct {
Id int `json:"node_id"`
// Per-node memory
Memory uint64 `json:"memory"`
Cores []Core `json:"cores"`
Caches []Cache `json:"caches"`
}
type Core struct {
Id int `json:"core_id"`
Threads []int `json:"thread_ids"`
Caches []Cache `json:"caches"`
}
type Cache struct {
// Size of memory cache in bytes.
Size uint64 `json:"size"`
// Type of memory cache: data, instruction, or unified.
Type string `json:"type"`
// Level (distance from cpus) in a multi-level cache hierarchy.
Level int `json:"level"`
}
func (self *Node) FindCore(id int) (bool, int) {
for i, n := range self.Cores {
if n.Id == id {
return true, i
}
}
return false, -1
}
func (self *Node) AddThread(thread int, core int) {
var coreIdx int
if core == -1 {
// Assume one hyperthread per core when topology data is missing.
core = thread
}
ok, coreIdx := self.FindCore(core)
if !ok {
// New core
core := Core{Id: core}
self.Cores = append(self.Cores, core)
coreIdx = len(self.Cores) - 1
}
self.Cores[coreIdx].Threads = append(self.Cores[coreIdx].Threads, thread)
}
func (self *Node) AddNodeCache(c Cache) {
self.Caches = append(self.Caches, c)
}
func (self *Node) AddPerCoreCache(c Cache) {
for idx := range self.Cores {
self.Cores[idx].Caches = append(self.Cores[idx].Caches, c)
}
}
type DiskInfo struct {
// device name
Name string `json:"name"`
// Major number
Major uint64 `json:"major"`
// Minor number
Minor uint64 `json:"minor"`
// Size in bytes
Size uint64 `json:"size"`
// I/O Scheduler - one of "none", "noop", "cfq", "deadline"
Scheduler string `json:"scheduler"`
}
type NetInfo struct {
// Device name
Name string `json:"name"`
// Mac Address
MacAddress string `json:"mac_address"`
// Speed in MBits/s
Speed int64 `json:"speed"`
// Maximum Transmission Unit
Mtu int64 `json:"mtu"`
}
type MachineInfo struct {
// The number of cores in this machine.
NumCores int `json:"num_cores"`
// Maximum clock speed for the cores, in KHz.
CpuFrequency uint64 `json:"cpu_frequency_khz"`
// The amount of memory (in bytes) in this machine
MemoryCapacity int64 `json:"memory_capacity"`
// The machine id
MachineID string `json:"machine_id"`
// The system uuid
SystemUUID string `json:"system_uuid"`
// Filesystems on this machine.
Filesystems []FsInfo `json:"filesystems"`
// Disk map
DiskMap map[string]DiskInfo `json:"disk_map"`
// Network devices
NetworkDevices []NetInfo `json:"network_devices"`
// Machine Topology
// Describes cpu/memory layout and hierarchy.
Topology []Node `json:"topology"`
}
type VersionInfo struct {
// Kernel version.
KernelVersion string `json:"kernel_version"`
// OS image being used for cadvisor container, or host image if running on host directly.
ContainerOsVersion string `json:"container_os_version"`
// Docker version.
DockerVersion string `json:"docker_version"`
// cAdvisor version.
CadvisorVersion string `json:"cadvisor_version"`
}
type MachineInfoFactory interface {
GetMachineInfo() (*MachineInfo, error)
GetVersionInfo() (*VersionInfo, error)
}
// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 test
import (
"fmt"
"math/rand"
"time"
info "github.com/google/cadvisor/info/v1"
)
func GenerateRandomStats(numStats, numCores int, duration time.Duration) []*info.ContainerStats {
ret := make([]*info.ContainerStats, numStats)
perCoreUsages := make([]uint64, numCores)
currentTime := time.Now()
for i := range perCoreUsages {
perCoreUsages[i] = uint64(rand.Int63n(1000))
}
for i := 0; i < numStats; i++ {
stats := new(info.ContainerStats)
stats.Timestamp = currentTime
currentTime = currentTime.Add(duration)
percore := make([]uint64, numCores)
for i := range perCoreUsages {
perCoreUsages[i] += uint64(rand.Int63n(1000))
percore[i] = perCoreUsages[i]
stats.Cpu.Usage.Total += percore[i]
}
stats.Cpu.Usage.PerCpu = percore
stats.Cpu.Usage.User = stats.Cpu.Usage.Total
stats.Cpu.Usage.System = 0
stats.Memory.Usage = uint64(rand.Int63n(4096))
ret[i] = stats
}
return ret
}
func GenerateRandomContainerSpec(numCores int) info.ContainerSpec {
ret := info.ContainerSpec{
CreationTime: time.Now(),
HasCpu: true,
Cpu: info.CpuSpec{},
HasMemory: true,
Memory: info.MemorySpec{},
}
ret.Cpu.Limit = uint64(1000 + rand.Int63n(2000))
ret.Cpu.MaxLimit = uint64(1000 + rand.Int63n(2000))
ret.Cpu.Mask = fmt.Sprintf("0-%d", numCores-1)
ret.Memory.Limit = uint64(4096 + rand.Int63n(4096))
return ret
}
func GenerateRandomContainerInfo(containerName string, numCores int, query *info.ContainerInfoRequest, duration time.Duration) *info.ContainerInfo {
stats := GenerateRandomStats(query.NumStats, numCores, duration)
spec := GenerateRandomContainerSpec(numCores)
ret := &info.ContainerInfo{
ContainerReference: info.ContainerReference{
Name: containerName,
},
Spec: spec,
Stats: stats,
}
return ret
}
# perigee
Perigee provides a REST client that, while it should be generic enough to use with most any RESTful API, is nonetheless optimized to the needs of the OpenStack APIs.
Perigee grew out of the need to refactor out common API access code from the [gorax](http://github.com/racker/gorax) project.
Several things influenced the name of the project.
Numerous elements of the OpenStack ecosystem are named after astronomical artifacts.
Additionally, perigee occurs when two orbiting bodies are closest to each other.
Perigee seemed appropriate for something aiming to bring OpenStack and other RESTful services closer to the end-user.
**This library is still in the very early stages of development. Unless you want to contribute, it probably isn't what you want**
## Installation and Testing
To install:
```bash
go get github.com/racker/perigee
```
To run unit tests:
```bash
go test github.com/racker/perigee
```
## Contributing
The following guidelines are preliminary, as this project is just starting out.
However, this should serve as a working first-draft.
### Branching
The master branch must always be a valid build.
The `go get` command will not work otherwise.
Therefore, development must occur on a different branch.
When creating a feature branch, do so off the master branch:
```bash
git checkout master
git pull
git checkout -b featureBranch
git checkout -b featureBranch-wip # optional
```
Perform all your editing and testing in the WIP-branch.
Feel free to make as many commits as you see fit.
You may even open "WIP" pull requests from your feature branch to seek feedback.
WIP pull requests will **never** be merged, however.
To get code merged, you'll need to "squash" your changes into one or more clean commits in the feature branch.
These steps should be followed:
```bash
git checkout featureBranch
git merge --squash featureBranch-wip
git commit -a
git push origin featureBranch
```
You may now open a nice, clean, self-contained pull request from featureBranch to master.
The `git commit -a` command above will open a text editor so that
you may provide a comprehensive description of the changes.
In general, when submitting a pull request against master,
be sure to answer the following questions:
- What is the problem?
- Why is it a problem?
- What is your solution?
- How does your solution work? (Recommended for non-trivial changes.)
- Why should we use your solution over someone elses? (Recommended especially if multiple solutions being discussed.)
Remember that monster-sized pull requests are a bear to code-review,
so having helpful commit logs are an absolute must to review changes as quickly as possible.
Finally, (s)he who breaks master is ultimately responsible for fixing master.
### Source Representation
The Go community firmly believes in a consistent representation for all Go source code.
We do too.
Make sure all source code is passed through "go fmt" *before* you create your pull request.
Please note, however, that we fully acknowledge and recognize that we no longer rely upon punch-cards for representing source files.
Therefore, no 80-column limit exists.
However, if a line exceeds 132 columns, you may want to consider splitting the line.
### Unit and Integration Tests
Pull requests that include non-trivial code changes without accompanying unit tests will be flatly rejected.
While we have no way of enforcing this practice,
you can ensure your code is thoroughly tested by always [writing tests first by intention.](http://en.wikipedia.org/wiki/Test-driven_development)
When creating a pull request, if even one test fails, the PR will be rejected.
Make sure all unit tests pass.
Make sure all integration tests pass.
### Documentation
Private functions and methods which are obvious to anyone unfamiliar with gorax needn't be accompanied by documentation.
However, this is a code-smell; if submitting a PR, expect to justify your decision.
Public functions, regardless of how obvious, **must** have accompanying godoc-style documentation.
This is not to suggest you should provide a tome for each function, however.
Sometimes a link to more information is more appropriate, provided the link is stable, reliable, and pertinent.
Changing documentation often results in bizarre diffs in pull requests, due to text often spanning multiple lines.
To work around this, put [one logical thought or sentence on a single line.](http://rhodesmill.org/brandon/2012/one-sentence-per-line/)
While this looks weird in a plain-text editor,
remember that both godoc and HTML viewers will reflow text.
The source code and its comments should be easy to edit with minimal diff pollution.
Let software dedicated to presenting the documentation to human readers deal with its presentation.
## Examples
t.b.d.
// vim: ts=8 sw=8 noet ai
package perigee
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"strings"
)
// The UnexpectedResponseCodeError structure represents a mismatch in understanding between server and client in terms of response codes.
// Most often, this is due to an actual error condition (e.g., getting a 404 for a resource when you expect a 200).
// However, it needn't always be the case (e.g., getting a 204 (No Content) response back when a 200 is expected).
type UnexpectedResponseCodeError struct {
Url string
Expected []int
Actual int
Body []byte
}
func (err *UnexpectedResponseCodeError) Error() string {
return fmt.Sprintf("Expected HTTP response code %d when accessing URL(%s); got %d instead with the following body:\n%s", err.Expected, err.Url, err.Actual, string(err.Body))
}
// Request issues an HTTP request, marshaling parameters, and unmarshaling results, as configured in the provided Options parameter.
// The Response structure returned, if any, will include accumulated results recovered from the HTTP server.
// See the Response structure for more details.
func Request(method string, url string, opts Options) (*Response, error) {
var body io.Reader
var response Response
client := opts.CustomClient
if client == nil {
client = new(http.Client)
}
contentType := opts.ContentType
body = nil
if opts.ReqBody != nil {
if contentType == "" {
contentType = "application/json"
}
if contentType == "application/json" {
bodyText, err := json.Marshal(opts.ReqBody)
if err != nil {
return nil, err
}
body = strings.NewReader(string(bodyText))
if opts.DumpReqJson {
log.Printf("Making request:\n%#v\n", string(bodyText))
}
} else {
// assume opts.ReqBody implements the correct interface
body = opts.ReqBody.(io.Reader)
}
}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
if contentType != "" {
req.Header.Add("Content-Type", contentType)
}
if opts.ContentLength > 0 {
req.ContentLength = opts.ContentLength
req.Header.Add("Content-Length", string(opts.ContentLength))
}
if opts.MoreHeaders != nil {
for k, v := range opts.MoreHeaders {
req.Header.Add(k, v)
}
}
if accept := req.Header.Get("Accept"); accept == "" {
accept = opts.Accept
if accept == "" {
accept = "application/json"
}
req.Header.Add("Accept", accept)
}
if opts.SetHeaders != nil {
err = opts.SetHeaders(req)
if err != nil {
return &response, err
}
}
httpResponse, err := client.Do(req)
if httpResponse != nil {
response.HttpResponse = *httpResponse
response.StatusCode = httpResponse.StatusCode
}
if err != nil {
return &response, err
}
// This if-statement is legacy code, preserved for backward compatibility.
if opts.StatusCode != nil {
*opts.StatusCode = httpResponse.StatusCode
}
acceptableResponseCodes := opts.OkCodes
if len(acceptableResponseCodes) != 0 {
if not_in(httpResponse.StatusCode, acceptableResponseCodes) {
b, _ := ioutil.ReadAll(httpResponse.Body)
httpResponse.Body.Close()
return &response, &UnexpectedResponseCodeError{
Url: url,
Expected: acceptableResponseCodes,
Actual: httpResponse.StatusCode,
Body: b,
}
}
}
if opts.Results != nil {
defer httpResponse.Body.Close()
jsonResult, err := ioutil.ReadAll(httpResponse.Body)
response.JsonResult = jsonResult
if err != nil {
return &response, err
}
err = json.Unmarshal(jsonResult, opts.Results)
// This if-statement is legacy code, preserved for backward compatibility.
if opts.ResponseJson != nil {
*opts.ResponseJson = jsonResult
}
}
return &response, err
}
// not_in returns false if, and only if, the provided needle is _not_
// in the given set of integers.
func not_in(needle int, haystack []int) bool {
for _, straw := range haystack {
if needle == straw {
return false
}
}
return true
}
// Post makes a POST request against a server using the provided HTTP client.
// The url must be a fully-formed URL string.
// DEPRECATED. Use Request() instead.
func Post(url string, opts Options) error {
r, err := Request("POST", url, opts)
if opts.Response != nil {
*opts.Response = r
}
return err
}
// Get makes a GET request against a server using the provided HTTP client.
// The url must be a fully-formed URL string.
// DEPRECATED. Use Request() instead.
func Get(url string, opts Options) error {
r, err := Request("GET", url, opts)
if opts.Response != nil {
*opts.Response = r
}
return err
}
// Delete makes a DELETE request against a server using the provided HTTP client.
// The url must be a fully-formed URL string.
// DEPRECATED. Use Request() instead.
func Delete(url string, opts Options) error {
r, err := Request("DELETE", url, opts)
if opts.Response != nil {
*opts.Response = r
}
return err
}
// Put makes a PUT request against a server using the provided HTTP client.
// The url must be a fully-formed URL string.
// DEPRECATED. Use Request() instead.
func Put(url string, opts Options) error {
r, err := Request("PUT", url, opts)
if opts.Response != nil {
*opts.Response = r
}
return err
}
// Options describes a set of optional parameters to the various request calls.
//
// The custom client can be used for a variety of purposes beyond selecting encrypted versus unencrypted channels.
// Transports can be defined to provide augmented logging, header manipulation, et. al.
//
// If the ReqBody field is provided, it will be embedded as a JSON object.
// Otherwise, provide nil.
//
// If JSON output is to be expected from the response,
// provide either a pointer to the container structure in Results,
// or a pointer to a nil-initialized pointer variable.
// The latter method will cause the unmarshaller to allocate the container type for you.
// If no response is expected, provide a nil Results value.
//
// The MoreHeaders map, if non-nil or empty, provides a set of headers to add to those
// already present in the request. At present, only Accepted and Content-Type are set
// by default.
//
// OkCodes provides a set of acceptable, positive responses.
//
// If provided, StatusCode specifies a pointer to an integer, which will receive the
// returned HTTP status code, successful or not. DEPRECATED; use the Response.StatusCode field instead for new software.
//
// ResponseJson, if specified, provides a means for returning the raw JSON. This is
// most useful for diagnostics. DEPRECATED; use the Response.JsonResult field instead for new software.
//
// DumpReqJson, if set to true, will cause the request to appear to stdout for debugging purposes.
// This attribute may be removed at any time in the future; DO NOT use this attribute in production software.
//
// Response, if set, provides a way to communicate the complete set of HTTP response, raw JSON, status code, and
// other useful attributes back to the caller. Note that the Request() method returns a Response structure as part
// of its public interface; you don't need to set the Response field here to use this structure. The Response field
// exists primarily for legacy or deprecated functions.
//
// SetHeaders allows the caller to provide code to set any custom headers programmatically. Typically, this
// facility can invoke, e.g., SetBasicAuth() on the request to easily set up authentication.
// Any error generated will terminate the request and will propegate back to the caller.
type Options struct {
CustomClient *http.Client
ReqBody interface{}
Results interface{}
MoreHeaders map[string]string
OkCodes []int
StatusCode *int `DEPRECATED`
DumpReqJson bool `UNSUPPORTED`
ResponseJson *[]byte `DEPRECATED`
Response **Response
ContentType string `json:"Content-Type,omitempty"`
ContentLength int64 `json:"Content-Length,omitempty"`
Accept string `json:"Accept,omitempty"`
SetHeaders func(r *http.Request) error
}
// Response contains return values from the various request calls.
//
// HttpResponse will return the http response from the request call.
// Note: HttpResponse.Body is always closed and will not be available from this return value.
//
// StatusCode specifies the returned HTTP status code, successful or not.
//
// If Results is specified in the Options:
// - JsonResult will contain the raw return from the request call
// This is most useful for diagnostics.
// - Result will contain the unmarshalled json either in the Result passed in
// or the unmarshaller will allocate the container type for you.
type Response struct {
HttpResponse http.Response
JsonResult []byte
Results interface{}
StatusCode int
}
package perigee
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestNormal(t *testing.T) {
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("testing"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
response, err := Request("GET", ts.URL, Options{})
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if response.StatusCode != 200 {
t.Fatalf("response code %d is not 200", response.StatusCode)
}
}
func TestOKCodes(t *testing.T) {
expectCode := 201
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(expectCode)
w.Write([]byte("testing"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
options := Options{
OkCodes: []int{expectCode},
}
results, err := Request("GET", ts.URL, options)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if results.StatusCode != expectCode {
t.Fatalf("response code %d is not %d", results.StatusCode, expectCode)
}
}
func TestLocation(t *testing.T) {
newLocation := "http://www.example.com"
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", newLocation)
w.Write([]byte("testing"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
response, err := Request("GET", ts.URL, Options{})
if err != nil {
t.Fatalf("should not have error: %s", err)
}
location, err := response.HttpResponse.Location()
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if location.String() != newLocation {
t.Fatalf("location returned \"%s\" is not \"%s\"", location.String(), newLocation)
}
}
func TestHeaders(t *testing.T) {
newLocation := "http://www.example.com"
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", newLocation)
w.Write([]byte("testing"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
response, err := Request("GET", ts.URL, Options{})
if err != nil {
t.Fatalf("should not have error: %s", err)
}
location := response.HttpResponse.Header.Get("Location")
if location == "" {
t.Fatalf("Location should not empty")
}
if location != newLocation {
t.Fatalf("location returned \"%s\" is not \"%s\"", location, newLocation)
}
}
func TestCustomHeaders(t *testing.T) {
var contentType, accept, contentLength string
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m := map[string][]string(r.Header)
contentType = m["Content-Type"][0]
accept = m["Accept"][0]
contentLength = m["Content-Length"][0]
})
ts := httptest.NewServer(handler)
defer ts.Close()
_, err := Request("GET", ts.URL, Options{
ContentLength: 5,
ContentType: "x-application/vb",
Accept: "x-application/c",
ReqBody: strings.NewReader("Hello"),
})
if err != nil {
t.Fatalf(err.Error())
}
if contentType != "x-application/vb" {
t.Fatalf("I expected x-application/vb; got ", contentType)
}
if contentLength != "5" {
t.Fatalf("I expected 5 byte content length; got ", contentLength)
}
if accept != "x-application/c" {
t.Fatalf("I expected x-application/c; got ", accept)
}
}
func TestJson(t *testing.T) {
newLocation := "http://www.example.com"
jsonBytes := []byte(`{"foo": {"bar": "baz"}}`)
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", newLocation)
w.Write(jsonBytes)
})
ts := httptest.NewServer(handler)
defer ts.Close()
type Data struct {
Foo struct {
Bar string `json:"bar"`
} `json:"foo"`
}
var data Data
response, err := Request("GET", ts.URL, Options{Results: &data})
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if bytes.Compare(jsonBytes, response.JsonResult) != 0 {
t.Fatalf("json returned \"%s\" is not \"%s\"", response.JsonResult, jsonBytes)
}
if data.Foo.Bar != "baz" {
t.Fatalf("Results returned %v", data)
}
}
func TestSetHeaders(t *testing.T) {
var wasCalled bool
handler := http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hi"))
})
ts := httptest.NewServer(handler)
defer ts.Close()
_, err := Request("GET", ts.URL, Options{
SetHeaders: func(r *http.Request) error {
wasCalled = true
return nil
},
})
if err != nil {
t.Fatal(err)
}
if !wasCalled {
t.Fatal("I expected header setter callback to be called, but it wasn't")
}
myError := fmt.Errorf("boo")
_, err = Request("GET", ts.URL, Options{
SetHeaders: func(r *http.Request) error {
return myError
},
})
if err != myError {
t.Fatal("I expected errors to propegate back to the caller.")
}
}
func TestBodilessMethodsAreSentWithoutContentHeaders(t *testing.T) {
var h map[string][]string
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h = r.Header
})
ts := httptest.NewServer(handler)
defer ts.Close()
_, err := Request("GET", ts.URL, Options{})
if err != nil {
t.Fatalf(err.Error())
}
if len(h["Content-Type"]) != 0 {
t.Fatalf("I expected nothing for Content-Type but got ", h["Content-Type"])
}
if len(h["Content-Length"]) != 0 {
t.Fatalf("I expected nothing for Content-Length but got ", h["Content-Length"])
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment