Update cAdvisor dependency and remove perigee.

Perigee dependency is unused.
pull/6/head
Victor Marmol 2015-03-06 15:41:20 -08:00
parent a8dd10dab7
commit 7e97d22e91
12 changed files with 841 additions and 843 deletions

15
Godeps/Godeps.json generated
View File

@ -151,13 +151,13 @@
}, },
{ {
"ImportPath": "github.com/google/cadvisor/client", "ImportPath": "github.com/google/cadvisor/client",
"Comment": "0.6.2", "Comment": "0.10.1-30-gb5e2f37",
"Rev": "89088df70eca64cf9d6b9a23a3d2bc21a30916d6" "Rev": "b5e2f3788e4a39a0836c5490e6bf31832400c1f3"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/info", "ImportPath": "github.com/google/cadvisor/info/v1",
"Comment": "0.6.2", "Comment": "0.10.1-30-gb5e2f37",
"Rev": "89088df70eca64cf9d6b9a23a3d2bc21a30916d6" "Rev": "b5e2f3788e4a39a0836c5490e6bf31832400c1f3"
}, },
{ {
"ImportPath": "github.com/google/gofuzz", "ImportPath": "github.com/google/gofuzz",
@ -227,11 +227,6 @@
"ImportPath": "github.com/prometheus/procfs", "ImportPath": "github.com/prometheus/procfs",
"Rev": "6c34ef819e19b4e16f410100ace4aa006f0e3bf8" "Rev": "6c34ef819e19b4e16f410100ace4aa006f0e3bf8"
}, },
{
"ImportPath": "github.com/racker/perigee",
"Comment": "v0.0.0-18-g0c00cb0",
"Rev": "0c00cb0a026b71034ebc8205263c77dad3577db5"
},
{ {
"ImportPath": "github.com/rackspace/gophercloud", "ImportPath": "github.com/rackspace/gophercloud",
"Comment": "v1.0.0-490-g32d0a89", "Comment": "v1.0.0-490-g32d0a89",

View File

@ -24,7 +24,7 @@ import (
"path" "path"
"strings" "strings"
"github.com/google/cadvisor/info" info "github.com/google/cadvisor/info/v1"
) )
// Client represents the base URL for a cAdvisor client. // 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) resp, err = http.Get(url)
} }
if err != nil { 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 { 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() defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { 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 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 { 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 err
} }
return nil return nil

View File

@ -21,11 +21,12 @@ import (
"net/http/httptest" "net/http/httptest"
"path" "path"
"reflect" "reflect"
"strings"
"testing" "testing"
"time" "time"
"github.com/google/cadvisor/info" info "github.com/google/cadvisor/info/v1"
itest "github.com/google/cadvisor/info/test" itest "github.com/google/cadvisor/info/v1/test"
"github.com/kr/pretty" "github.com/kr/pretty"
) )
@ -43,22 +44,25 @@ func testGetJsonData(
return nil 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) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == path { if r.URL.Path == path {
if expectedPostObj != nil { if expectedPostObj != nil {
expectedPostObjEmpty := new(info.ContainerInfoRequest)
decoder := json.NewDecoder(r.Body) decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(expectedPostObjEmpty); err != nil { if err := decoder.Decode(expectedPostObjEmpty); err != nil {
t.Errorf("Received invalid object: %v", err) t.Errorf("Received invalid object: %v", err)
} }
if !reflect.DeepEqual(expectedPostObj, expectedPostObjEmpty) { if expectedPostObj.NumStats != expectedPostObjEmpty.NumStats ||
t.Errorf("Received unexpected object: %+v", expectedPostObjEmpty) 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 := json.NewEncoder(w)
encoder.Encode(replyObj) encoder.Encode(replyObj)
} else if r.URL.Path == "/api/v1.2/machine" { } 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 { } else {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Page not found.") fmt.Fprintf(w, "Page not found.")
@ -78,8 +82,16 @@ func TestGetMachineinfo(t *testing.T) {
minfo := &info.MachineInfo{ minfo := &info.MachineInfo{
NumCores: 8, NumCores: 8,
MemoryCapacity: 31625871360, MemoryCapacity: 31625871360,
DiskMap: map[string]info.DiskInfo{
"8:0": {
Name: "sda",
Major: 8,
Minor: 0,
Size: 10737418240,
},
},
} }
client, server, err := cadvisorTestClient("/api/v1.2/machine", nil, nil, minfo, t) client, server, err := cadvisorTestClient("/api/v1.2/machine", nil, minfo, t)
if err != nil { if err != nil {
t.Fatalf("unable to get a client %v", err) t.Fatalf("unable to get a client %v", err)
} }
@ -101,7 +113,7 @@ func TestGetContainerInfo(t *testing.T) {
} }
containerName := "/some/container" containerName := "/some/container"
cinfo := itest.GenerateRandomContainerInfo(containerName, 4, query, 1*time.Second) 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 { if err != nil {
t.Fatalf("unable to get a client %v", err) 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) { func TestGetSubcontainersInfo(t *testing.T) {
query := &info.ContainerInfoRequest{ query := &info.ContainerInfoRequest{
NumStats: 3, NumStats: 3,
@ -129,7 +165,7 @@ func TestGetSubcontainersInfo(t *testing.T) {
*cinfo1, *cinfo1,
*cinfo2, *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 { if err != nil {
t.Fatalf("unable to get a client %v", err) t.Fatalf("unable to get a client %v", err)
} }

View File

@ -0,0 +1,463 @@
// 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 (
"reflect"
"time"
)
type CpuSpec struct {
Limit uint64 `json:"limit"`
MaxLimit uint64 `json:"max_limit"`
Mask string `json:"mask,omitempty"`
}
type MemorySpec struct {
// The amount of memory requested. Default is unlimited (-1).
// Units: bytes.
Limit uint64 `json:"limit,omitempty"`
// The amount of guaranteed memory. Default is 0.
// Units: bytes.
Reservation uint64 `json:"reservation,omitempty"`
// The amount of swap space requested. Default is unlimited (-1).
// Units: bytes.
SwapLimit uint64 `json:"swap_limit,omitempty"`
}
type ContainerSpec struct {
// Time at which the container was created.
CreationTime time.Time `json:"creation_time,omitempty"`
HasCpu bool `json:"has_cpu"`
Cpu CpuSpec `json:"cpu,omitempty"`
HasMemory bool `json:"has_memory"`
Memory MemorySpec `json:"memory,omitempty"`
HasNetwork bool `json:"has_network"`
HasFilesystem bool `json:"has_filesystem"`
// HasDiskIo when true, indicates that DiskIo stats will be available.
HasDiskIo bool `json:"has_diskio"`
}
// Container reference contains enough information to uniquely identify a container
type ContainerReference struct {
// The absolute name of the container. This is unique on the machine.
Name string `json:"name"`
// Other names by which the container is known within a certain namespace.
// This is unique within that namespace.
Aliases []string `json:"aliases,omitempty"`
// Namespace under which the aliases of a container are unique.
// An example of a namespace is "docker" for Docker containers.
Namespace string `json:"namespace,omitempty"`
}
// Sorts by container name.
type ContainerReferenceSlice []ContainerReference
func (self ContainerReferenceSlice) Len() int { return len(self) }
func (self ContainerReferenceSlice) Swap(i, j int) { self[i], self[j] = self[j], self[i] }
func (self ContainerReferenceSlice) Less(i, j int) bool { return self[i].Name < self[j].Name }
// ContainerInfoQuery is used when users check a container info from the REST api.
// It specifies how much data users want to get about a container
type ContainerInfoRequest struct {
// Max number of stats to return.
NumStats int `json:"num_stats,omitempty"`
// Start time for which to query information.
// If ommitted, the beginning of time is assumed.
Start time.Time `json:"start,omitempty"`
// End time for which to query information.
// If ommitted, current time is assumed.
End time.Time `json:"end,omitempty"`
}
func (self *ContainerInfoRequest) Equals(other ContainerInfoRequest) bool {
return self.NumStats == other.NumStats &&
self.Start.Equal(other.Start) &&
self.End.Equal(other.End)
}
type ContainerInfo struct {
ContainerReference
// The direct subcontainers of the current container.
Subcontainers []ContainerReference `json:"subcontainers,omitempty"`
// The isolation used in the container.
Spec ContainerSpec `json:"spec,omitempty"`
// Historical statistics gathered from the container.
Stats []*ContainerStats `json:"stats,omitempty"`
}
// TODO(vmarmol): Refactor to not need this equality comparison.
// ContainerInfo may be (un)marshaled by json or other en/decoder. In that
// case, the Timestamp field in each stats/sample may not be precisely
// en/decoded. This will lead to small but acceptable differences between a
// ContainerInfo and its encode-then-decode version. Eq() is used to compare
// two ContainerInfo accepting small difference (<10ms) of Time fields.
func (self *ContainerInfo) Eq(b *ContainerInfo) bool {
// If both self and b are nil, then Eq() returns true
if self == nil {
return b == nil
}
if b == nil {
return self == nil
}
// For fields other than time.Time, we will compare them precisely.
// This would require that any slice should have same order.
if !reflect.DeepEqual(self.ContainerReference, b.ContainerReference) {
return false
}
if !reflect.DeepEqual(self.Subcontainers, b.Subcontainers) {
return false
}
if !self.Spec.Eq(&b.Spec) {
return false
}
for i, expectedStats := range b.Stats {
selfStats := self.Stats[i]
if !expectedStats.Eq(selfStats) {
return false
}
}
return true
}
func (self *ContainerSpec) Eq(b *ContainerSpec) bool {
// Creation within 1s of each other.
diff := self.CreationTime.Sub(b.CreationTime)
if (diff > time.Second) || (diff < -time.Second) {
return false
}
if self.HasCpu != b.HasCpu {
return false
}
if !reflect.DeepEqual(self.Cpu, b.Cpu) {
return false
}
if self.HasMemory != b.HasMemory {
return false
}
if !reflect.DeepEqual(self.Memory, b.Memory) {
return false
}
if self.HasNetwork != b.HasNetwork {
return false
}
if self.HasFilesystem != b.HasFilesystem {
return false
}
if self.HasDiskIo != b.HasDiskIo {
return false
}
return true
}
func (self *ContainerInfo) StatsAfter(ref time.Time) []*ContainerStats {
n := len(self.Stats) + 1
for i, s := range self.Stats {
if s.Timestamp.After(ref) {
n = i
break
}
}
if n > len(self.Stats) {
return nil
}
return self.Stats[n:]
}
func (self *ContainerInfo) StatsStartTime() time.Time {
var ret time.Time
for _, s := range self.Stats {
if s.Timestamp.Before(ret) || ret.IsZero() {
ret = s.Timestamp
}
}
return ret
}
func (self *ContainerInfo) StatsEndTime() time.Time {
var ret time.Time
for i := len(self.Stats) - 1; i >= 0; i-- {
s := self.Stats[i]
if s.Timestamp.After(ret) {
ret = s.Timestamp
}
}
return ret
}
// This mirrors kernel internal structure.
type LoadStats struct {
// Number of sleeping tasks.
NrSleeping uint64 `json:"nr_sleeping"`
// Number of running tasks.
NrRunning uint64 `json:"nr_running"`
// Number of tasks in stopped state
NrStopped uint64 `json:"nr_stopped"`
// Number of tasks in uninterruptible state
NrUinterruptible uint64 `json:"nr_uninterruptible"`
// Number of tasks waiting on IO
NrIoWait uint64 `json:"nr_io_wait"`
}
// All CPU usage metrics are cumulative from the creation of the container
type CpuStats struct {
Usage struct {
// Total CPU usage.
// Units: nanoseconds
Total uint64 `json:"total"`
// Per CPU/core usage of the container.
// Unit: nanoseconds.
PerCpu []uint64 `json:"per_cpu_usage,omitempty"`
// Time spent in user space.
// Unit: nanoseconds
User uint64 `json:"user"`
// Time spent in kernel space.
// Unit: nanoseconds
System uint64 `json:"system"`
} `json:"usage"`
// Smoothed average of number of runnable threads x 1000.
// We multiply by thousand to avoid using floats, but preserving precision.
// Load is smoothed over the last 10 seconds. Instantaneous value can be read
// from LoadStats.NrRunning.
LoadAverage int32 `json:"load_average"`
}
type PerDiskStats struct {
Major uint64 `json:"major"`
Minor uint64 `json:"minor"`
Stats map[string]uint64 `json:"stats"`
}
type DiskIoStats struct {
IoServiceBytes []PerDiskStats `json:"io_service_bytes,omitempty"`
IoServiced []PerDiskStats `json:"io_serviced,omitempty"`
IoQueued []PerDiskStats `json:"io_queued,omitempty"`
Sectors []PerDiskStats `json:"sectors,omitempty"`
IoServiceTime []PerDiskStats `json:"io_service_time,omitempty"`
IoWaitTime []PerDiskStats `json:"io_wait_time,omitempty"`
IoMerged []PerDiskStats `json:"io_merged,omitempty"`
IoTime []PerDiskStats `json:"io_time,omitempty"`
}
type MemoryStats struct {
// Current memory usage, this includes all memory regardless of when it was
// accessed.
// Units: Bytes.
Usage uint64 `json:"usage"`
// The amount of working set memory, this includes recently accessed memory,
// dirty memory, and kernel memory. Working set is <= "usage".
// Units: Bytes.
WorkingSet uint64 `json:"working_set"`
ContainerData MemoryStatsMemoryData `json:"container_data,omitempty"`
HierarchicalData MemoryStatsMemoryData `json:"hierarchical_data,omitempty"`
}
type MemoryStatsMemoryData struct {
Pgfault uint64 `json:"pgfault"`
Pgmajfault uint64 `json:"pgmajfault"`
}
type NetworkStats struct {
// Cumulative count of bytes received.
RxBytes uint64 `json:"rx_bytes"`
// Cumulative count of packets received.
RxPackets uint64 `json:"rx_packets"`
// Cumulative count of receive errors encountered.
RxErrors uint64 `json:"rx_errors"`
// Cumulative count of packets dropped while receiving.
RxDropped uint64 `json:"rx_dropped"`
// Cumulative count of bytes transmitted.
TxBytes uint64 `json:"tx_bytes"`
// Cumulative count of packets transmitted.
TxPackets uint64 `json:"tx_packets"`
// Cumulative count of transmit errors encountered.
TxErrors uint64 `json:"tx_errors"`
// Cumulative count of packets dropped while transmitting.
TxDropped uint64 `json:"tx_dropped"`
}
type FsStats struct {
// The block device name associated with the filesystem.
Device string `json:"device,omitempty"`
// Number of bytes that can be consumed by the container on this filesystem.
Limit uint64 `json:"capacity"`
// Number of bytes that is consumed by the container on this filesystem.
Usage uint64 `json:"usage"`
// Number of reads completed
// This is the total number of reads completed successfully.
ReadsCompleted uint64 `json:"reads_completed"`
// Number of reads merged
// Reads and writes which are adjacent to each other may be merged for
// efficiency. Thus two 4K reads may become one 8K read before it is
// ultimately handed to the disk, and so it will be counted (and queued)
// as only one I/O. This field lets you know how often this was done.
ReadsMerged uint64 `json:"reads_merged"`
// Number of sectors read
// This is the total number of sectors read successfully.
SectorsRead uint64 `json:"sectors_read"`
// Number of milliseconds spent reading
// This is the total number of milliseconds spent by all reads (as
// measured from __make_request() to end_that_request_last()).
ReadTime uint64 `json:"read_time"`
// Number of writes completed
// This is the total number of writes completed successfully.
WritesCompleted uint64 `json:"writes_completed"`
// Number of writes merged
// See the description of reads merged.
WritesMerged uint64 `json:"writes_merged"`
// Number of sectors written
// This is the total number of sectors written successfully.
SectorsWritten uint64 `json:"sectors_written"`
// Number of milliseconds spent writing
// This is the total number of milliseconds spent by all writes (as
// measured from __make_request() to end_that_request_last()).
WriteTime uint64 `json:"write_time"`
// Number of I/Os currently in progress
// The only field that should go to zero. Incremented as requests are
// given to appropriate struct request_queue and decremented as they finish.
IoInProgress uint64 `json:"io_in_progress"`
// Number of milliseconds spent doing I/Os
// This field increases so long as field 9 is nonzero.
IoTime uint64 `json:"io_time"`
// weighted number of milliseconds spent doing I/Os
// This field is incremented at each I/O start, I/O completion, I/O
// merge, or read of these stats by the number of I/Os in progress
// (field 9) times the number of milliseconds spent doing I/O since the
// last update of this field. This can provide an easy measure of both
// I/O completion time and the backlog that may be accumulating.
WeightedIoTime uint64 `json:"weighted_io_time"`
}
type ContainerStats struct {
// The time of this stat point.
Timestamp time.Time `json:"timestamp"`
Cpu CpuStats `json:"cpu,omitempty"`
DiskIo DiskIoStats `json:"diskio,omitempty"`
Memory MemoryStats `json:"memory,omitempty"`
Network NetworkStats `json:"network,omitempty"`
// Filesystem statistics
Filesystem []FsStats `json:"filesystem,omitempty"`
// Task load stats
TaskStats LoadStats `json:"task_stats,omitempty"`
}
func timeEq(t1, t2 time.Time, tolerance time.Duration) bool {
// t1 should not be later than t2
if t1.After(t2) {
t1, t2 = t2, t1
}
diff := t2.Sub(t1)
if diff <= tolerance {
return true
}
return false
}
func durationEq(a, b time.Duration, tolerance time.Duration) bool {
if a > b {
a, b = b, a
}
diff := a - b
if diff <= tolerance {
return true
}
return false
}
const (
// 10ms, i.e. 0.01s
timePrecision time.Duration = 10 * time.Millisecond
)
// This function is useful because we do not require precise time
// representation.
func (a *ContainerStats) Eq(b *ContainerStats) bool {
if !timeEq(a.Timestamp, b.Timestamp, timePrecision) {
return false
}
return a.StatsEq(b)
}
// Checks equality of the stats values.
func (a *ContainerStats) StatsEq(b *ContainerStats) bool {
// TODO(vmarmol): Consider using this through reflection.
if !reflect.DeepEqual(a.Cpu, b.Cpu) {
return false
}
if !reflect.DeepEqual(a.Memory, b.Memory) {
return false
}
if !reflect.DeepEqual(a.DiskIo, b.DiskIo) {
return false
}
if !reflect.DeepEqual(a.Network, b.Network) {
return false
}
if !reflect.DeepEqual(a.Filesystem, b.Filesystem) {
return false
}
return true
}
// Saturate CPU usage to 0.
func calculateCpuUsage(prev, cur uint64) uint64 {
if prev > cur {
return 0
}
return cur - prev
}

View File

@ -0,0 +1,79 @@
// 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
}

View File

@ -0,0 +1,162 @@
// 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)
}

View File

@ -0,0 +1,79 @@
// 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
}

View File

@ -1,2 +0,0 @@
bin/*
pkg/*

View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -1,120 +0,0 @@
# 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.

View File

@ -1,269 +0,0 @@
// 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
}

View File

@ -1,226 +0,0 @@
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"])
}
}