From 7e97d22e9169531c4ff44bf798adbe50d8969504 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Fri, 6 Mar 2015 15:41:20 -0800 Subject: [PATCH] Update cAdvisor dependency and remove perigee. Perigee dependency is unused. --- Godeps/Godeps.json | 15 +- .../google/cadvisor/client/client.go | 13 +- .../google/cadvisor/client/client_test.go | 54 +- .../google/cadvisor/info/v1/container.go | 463 ++++++++++++++++++ .../google/cadvisor/info/v1/container_test.go | 79 +++ .../google/cadvisor/info/v1/machine.go | 162 ++++++ .../google/cadvisor/info/v1/test/datagen.go | 79 +++ .../src/github.com/racker/perigee/.gitignore | 2 - .../src/github.com/racker/perigee/LICENSE | 202 -------- .../src/github.com/racker/perigee/README.md | 120 ----- .../src/github.com/racker/perigee/api.go | 269 ---------- .../src/github.com/racker/perigee/api_test.go | 226 --------- 12 files changed, 841 insertions(+), 843 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/google/cadvisor/info/v1/container.go create mode 100644 Godeps/_workspace/src/github.com/google/cadvisor/info/v1/container_test.go create mode 100644 Godeps/_workspace/src/github.com/google/cadvisor/info/v1/machine.go create mode 100644 Godeps/_workspace/src/github.com/google/cadvisor/info/v1/test/datagen.go delete mode 100644 Godeps/_workspace/src/github.com/racker/perigee/.gitignore delete mode 100644 Godeps/_workspace/src/github.com/racker/perigee/LICENSE delete mode 100644 Godeps/_workspace/src/github.com/racker/perigee/README.md delete mode 100644 Godeps/_workspace/src/github.com/racker/perigee/api.go delete mode 100644 Godeps/_workspace/src/github.com/racker/perigee/api_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index f8938ea999..67046acf22 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -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", @@ -227,11 +227,6 @@ "ImportPath": "github.com/prometheus/procfs", "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", diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/client/client.go b/Godeps/_workspace/src/github.com/google/cadvisor/client/client.go index 5c016e0588..9a211185f0 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/client/client.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/client/client.go @@ -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 diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/client/client_test.go b/Godeps/_workspace/src/github.com/google/cadvisor/client/client_test.go index 67bf3c2b6c..c309650acf 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/client/client_test.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/client/client_test.go @@ -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, + 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 { 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) } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/container.go b/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/container.go new file mode 100644 index 0000000000..2625626c95 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/container.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/container_test.go b/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/container_test.go new file mode 100644 index 0000000000..58dc79e393 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/container_test.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/machine.go b/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/machine.go new file mode 100644 index 0000000000..719a3eac1b --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/machine.go @@ -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) +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/test/datagen.go b/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/test/datagen.go new file mode 100644 index 0000000000..24431692f0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/info/v1/test/datagen.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/racker/perigee/.gitignore b/Godeps/_workspace/src/github.com/racker/perigee/.gitignore deleted file mode 100644 index 49ca32aa20..0000000000 --- a/Godeps/_workspace/src/github.com/racker/perigee/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -bin/* -pkg/* diff --git a/Godeps/_workspace/src/github.com/racker/perigee/LICENSE b/Godeps/_workspace/src/github.com/racker/perigee/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/Godeps/_workspace/src/github.com/racker/perigee/LICENSE +++ /dev/null @@ -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. diff --git a/Godeps/_workspace/src/github.com/racker/perigee/README.md b/Godeps/_workspace/src/github.com/racker/perigee/README.md deleted file mode 100644 index 81cbf4a95f..0000000000 --- a/Godeps/_workspace/src/github.com/racker/perigee/README.md +++ /dev/null @@ -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. - diff --git a/Godeps/_workspace/src/github.com/racker/perigee/api.go b/Godeps/_workspace/src/github.com/racker/perigee/api.go deleted file mode 100644 index 0fcbadbee5..0000000000 --- a/Godeps/_workspace/src/github.com/racker/perigee/api.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/racker/perigee/api_test.go b/Godeps/_workspace/src/github.com/racker/perigee/api_test.go deleted file mode 100644 index da943b247b..0000000000 --- a/Godeps/_workspace/src/github.com/racker/perigee/api_test.go +++ /dev/null @@ -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"]) - } -}