Skip to content

Commit

Permalink
Podman stats error (#9411)
Browse files Browse the repository at this point in the history
* feat: error structure for container stats

* feat: update CHANGELOG.md

* feat: add custom error

* feat: unexport no stats error

* feat: add podman client stats tests

* fix: test files linter errors
  • Loading branch information
rogercoll authored May 24, 2022
1 parent f77fc63 commit f5a8d75
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
- `datadogexporter`: Update Kubernetes example manifest to new executable name. (#9425).
- `riakreceiver`: Fix issue where user configured metric settings were ignored. (#9561)
- `sqlserverreceiver`: Update `sqlserver.transaction_log.growth.count` and `sqlserver.transaction_log.shrink.count` to be monotonic sums. (#9522)
- `podmanreceiver`: Container Stats Error structure (#9397)

## v0.49.0

Expand Down
19 changes: 16 additions & 3 deletions receiver/podmanreceiver/podman_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ import (
"go.uber.org/zap"
)

var (
errNoStatsFound = fmt.Errorf("No stats found")
)

type containerStats struct {
AvgCPU float64
ContainerID string
Expand All @@ -52,8 +56,14 @@ type containerStats struct {
Duration uint64
}

type containerStatsReportError struct {
Cause string
Message string
Response int64
}

type containerStatsReport struct {
Error string
Error containerStatsReportError
Stats []containerStats
}

Expand Down Expand Up @@ -119,9 +129,12 @@ func (c *podmanClient) stats(ctx context.Context) ([]containerStats, error) {
if err != nil {
return nil, err
}
if report.Error != "" {
return nil, errors.New(report.Error)
if report.Error.Message != "" {
return nil, errors.New(report.Error.Message)
} else if report.Stats == nil {
return nil, errNoStatsFound
}

return report.Stats, nil
}

Expand Down
97 changes: 97 additions & 0 deletions receiver/podmanreceiver/podman_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import (
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -78,3 +81,97 @@ func TestWatchingTimeouts(t *testing.T) {
"Client timeouts don't appear to have been exercised.",
)
}

func TestStats(t *testing.T) {
// stats sample
statsExample := `{"Error":null,"Stats":[{"AvgCPU":42.04781177856639,"ContainerID":"e6af5805edae6c950003abd5451808b277b67077e400f0a6f69d01af116ef014","Name":"charming_sutherland","PerCPU":null,"CPU":42.04781177856639,"CPUNano":309165846000,"CPUSystemNano":54515674,"SystemNano":1650912926385978706,"MemUsage":27717632,"MemLimit":7942234112,"MemPerc":0.34899036730888044,"NetInput":430,"NetOutput":330,"BlockInput":0,"BlockOutput":0,"PIDs":118,"UpTime":309165846000,"Duration":309165846000}]}`

listener, addr := tmpSock(t)
defer listener.Close()
defer os.Remove(addr)

srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/stats") {
_, err := w.Write([]byte(statsExample))
assert.NoError(t, err)
} else {
_, err := w.Write([]byte{})
assert.NoError(t, err)
}
}))
srv.Listener = listener
srv.Start()
defer srv.Close()

config := &Config{
Endpoint: fmt.Sprintf("unix://%s", addr),
// default timeout
Timeout: 5 * time.Second,
}

cli, err := newPodmanClient(zap.NewNop(), config)
assert.NotNil(t, cli)
assert.Nil(t, err)

expectedStats := containerStats{
AvgCPU: 42.04781177856639,
ContainerID: "e6af5805edae6c950003abd5451808b277b67077e400f0a6f69d01af116ef014",
Name: "charming_sutherland",
PerCPU: nil,
CPU: 42.04781177856639,
CPUNano: 309165846000,
CPUSystemNano: 54515674,
SystemNano: 1650912926385978706,
MemUsage: 27717632,
MemLimit: 7942234112,
MemPerc: 0.34899036730888044,
NetInput: 430,
NetOutput: 330,
BlockInput: 0,
BlockOutput: 0,
PIDs: 118,
UpTime: 309165846000 * time.Nanosecond,
Duration: 309165846000,
}

stats, err := cli.stats(context.Background())
assert.NoError(t, err)
assert.Equal(t, expectedStats, stats[0])
}

func TestStatsError(t *testing.T) {
// If the stats request fails, the API returns the following Error structure: https://docs.podman.io/en/latest/_static/api.html#operation/ContainersStatsAllLibpod
// For example, if we query the stats with an invalid container ID, the API returns the following message
statsError := `{"Error":{},"Stats":null}`

listener, addr := tmpSock(t)
defer listener.Close()
defer os.Remove(addr)

srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/stats") {
_, err := w.Write([]byte(statsError))
assert.NoError(t, err)
} else {
_, err := w.Write([]byte{})
assert.NoError(t, err)
}
}))
srv.Listener = listener
srv.Start()
defer srv.Close()

config := &Config{
Endpoint: fmt.Sprintf("unix://%s", addr),
// default timeout
Timeout: 5 * time.Second,
}

cli, err := newPodmanClient(zap.NewNop(), config)
assert.NotNil(t, cli)
assert.Nil(t, err)

stats, err := cli.stats(context.Background())
assert.Nil(t, stats)
assert.EqualError(t, err, errNoStatsFound.Error())
}
6 changes: 3 additions & 3 deletions receiver/podmanreceiver/receiver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func TestScraperLoop(t *testing.T) {
Stats: []containerStats{{
ContainerID: "c1",
}},
Error: "",
Error: containerStatsReportError{},
}
}()

Expand All @@ -96,8 +96,8 @@ func (c mockClient) factory(logger *zap.Logger, cfg *Config) (client, error) {

func (c mockClient) stats(context.Context) ([]containerStats, error) {
report := <-c
if report.Error != "" {
return nil, errors.New(report.Error)
if report.Error.Message != "" {
return nil, errors.New(report.Error.Message)
}
return report.Stats, nil
}
Expand Down

0 comments on commit f5a8d75

Please sign in to comment.