Skip to content

Commit

Permalink
[receiver/podman] Add timeout config option (open-telemetry#9014)
Browse files Browse the repository at this point in the history
* [receiver/podman] Add timeout config option

* docs: update changelog

* feat: add missing license to new test file

Co-authored-by: Juraci Paixão Kröhling <[email protected]>
  • Loading branch information
rogercoll and jpkrohling committed Apr 7, 2022
1 parent 211079f commit 03b379f
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- `jaegerremotesamplingextension`: Add local and remote sampling stores (#8818)
- `attributesprocessor`: Add support to filter on log body (#8996)
- `prometheusremotewriteexporter`: Translate resource attributes to the target info metric (#8493)
- `podmanreceiver`: Add API timeout configuration option (#9014)
- `cmd/mdatagen`: Add `sem_conv_version` field to metadata.yaml that is used to set metrics SchemaURL (#9010)

### 🛑 Breaking changes 🛑
Expand Down
4 changes: 3 additions & 1 deletion receiver/podmanreceiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ The following settings are required:
The following settings are optional:

- `collection_interval` (default = `10s`): The interval at which to gather container stats.
- `timeout` (default = `5s`): The maximum amount of time to wait for Podman API responses.

Example:

```yaml
receivers:
podman_stats:
endpoint: unix:https://run/podman/podman.sock
timeout: 10s
collection_interval: 10s
```

Expand Down Expand Up @@ -81,4 +83,4 @@ Recommended build tags to use when including this receiver in your build:

- `containers_image_openpgp`
- `exclude_graphdriver_btrfs`
- `exclude_graphdriver_devicemapper`
- `exclude_graphdriver_devicemapper`
7 changes: 6 additions & 1 deletion receiver/podmanreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package podmanreceiver // import "github.com/open-telemetry/opentelemetry-collec

import (
"errors"
"time"

"go.opentelemetry.io/collector/config"
"go.opentelemetry.io/collector/receiver/scraperhelper"
Expand All @@ -27,7 +28,11 @@ type Config struct {
scraperhelper.ScraperControllerSettings `mapstructure:",squash"`

// The URL of the podman server. Default is "unix:https:///run/podman/podman.sock"
Endpoint string `mapstructure:"endpoint"`
Endpoint string `mapstructure:"endpoint"`

// The maximum amount of time to wait for Podman API responses. Default is 5s
Timeout time.Duration `mapstructure:"timeout"`

APIVersion string `mapstructure:"api_version"`
SSHKey string `mapstructure:"ssh_key"`
SSHPassphrase string `mapstructure:"ssh_passphrase"`
Expand Down
2 changes: 2 additions & 0 deletions receiver/podmanreceiver/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ func TestLoadConfig(t *testing.T) {
assert.Equal(t, "podman_stats", dcfg.ID().String())
assert.Equal(t, "unix:https:///run/podman/podman.sock", dcfg.Endpoint)
assert.Equal(t, 10*time.Second, dcfg.CollectionInterval)
assert.Equal(t, 5*time.Second, dcfg.Timeout)

ascfg := cfg.Receivers[config.NewComponentIDWithName(typeStr, "all")].(*Config)
assert.Equal(t, "podman_stats/all", ascfg.ID().String())
assert.Equal(t, "http:https://example.com/", ascfg.Endpoint)
assert.Equal(t, 2*time.Second, ascfg.CollectionInterval)
assert.Equal(t, 20*time.Second, ascfg.Timeout)
}
1 change: 1 addition & 0 deletions receiver/podmanreceiver/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func createDefaultConfig() *Config {
CollectionInterval: 10 * time.Second,
},
Endpoint: "unix:https:///run/podman/podman.sock",
Timeout: 5 * time.Second,
APIVersion: defaultAPIVersion,
}
}
Expand Down
23 changes: 14 additions & 9 deletions receiver/podmanreceiver/podman_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,16 @@ type containerStatsReport struct {
type clientFactory func(logger *zap.Logger, cfg *Config) (client, error)

type client interface {
stats() ([]containerStats, error)
ping(context.Context) error
stats(context.Context) ([]containerStats, error)
}

type podmanClient struct {
conn *http.Client
endpoint string

// The maximum amount of time to wait for Podman API responses
timeout time.Duration
}

func newPodmanClient(logger *zap.Logger, cfg *Config) (client, error) {
Expand All @@ -76,10 +80,7 @@ func newPodmanClient(logger *zap.Logger, cfg *Config) (client, error) {
c := &podmanClient{
conn: connection,
endpoint: fmt.Sprintf("http:https://d/v%s/libpod", cfg.APIVersion),
}
err = c.ping()
if err != nil {
return nil, err
timeout: cfg.Timeout,
}
return c, nil
}
Expand All @@ -96,11 +97,13 @@ func (c *podmanClient) request(ctx context.Context, path string, params url.Valu
return c.conn.Do(req)
}

func (c *podmanClient) stats() ([]containerStats, error) {
func (c *podmanClient) stats(ctx context.Context) ([]containerStats, error) {
params := url.Values{}
params.Add("stream", "false")

resp, err := c.request(context.Background(), "/containers/stats", params)
statsCtx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.request(statsCtx, "/containers/stats", params)
if err != nil {
return nil, err
}
Expand All @@ -122,8 +125,10 @@ func (c *podmanClient) stats() ([]containerStats, error) {
return report.Stats, nil
}

func (c *podmanClient) ping() error {
resp, err := c.request(context.Background(), "/_ping", nil)
func (c *podmanClient) ping(ctx context.Context) error {
pingCtx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.request(pingCtx, "/_ping", nil)
if err != nil {
return err
}
Expand Down
77 changes: 77 additions & 0 deletions receiver/podmanreceiver/podman_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright The OpenTelemetry Authors
//
// 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:https://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 podmanreceiver

import (
"context"
"fmt"
"io/ioutil"
"net"
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)

func tmpSock(t *testing.T) (net.Listener, string) {
f, err := ioutil.TempFile(os.TempDir(), "testsock")
if err != nil {
t.Fatal(err)
}
addr := f.Name()
os.Remove(addr)

listener, err := net.Listen("unix", addr)
if err != nil {
t.Fatal(err)
}

return listener, addr
}

func TestWatchingTimeouts(t *testing.T) {
listener, addr := tmpSock(t)
defer listener.Close()
defer os.Remove(addr)

config := &Config{
Endpoint: fmt.Sprintf("unix:https://%s", addr),
Timeout: 50 * time.Millisecond,
}

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

expectedError := "context deadline exceeded"

shouldHaveTaken := time.Now().Add(100 * time.Millisecond).UnixNano()

err = cli.ping(context.Background())
require.Error(t, err)

containers, err := cli.stats(context.Background())
require.Error(t, err)
assert.Contains(t, err.Error(), expectedError)
assert.Nil(t, containers)

assert.GreaterOrEqual(
t, time.Now().UnixNano(), shouldHaveTaken,
"Client timeouts don't appear to have been exercised.",
)
}
4 changes: 2 additions & 2 deletions receiver/podmanreceiver/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ func (r *receiver) start(context.Context, component.Host) error {
return err
}

func (r *receiver) scrape(context.Context) (pdata.Metrics, error) {
func (r *receiver) scrape(ctx context.Context) (pdata.Metrics, error) {
var err error

stats, err := r.client.stats()
stats, err := r.client.stats(ctx)
if err != nil {
r.set.Logger.Error("error fetching stats", zap.Error(err))
return pdata.Metrics{}, err
Expand Down
6 changes: 5 additions & 1 deletion receiver/podmanreceiver/receiver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,18 @@ func (c mockClient) factory(logger *zap.Logger, cfg *Config) (client, error) {
return c, nil
}

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

func (c mockClient) ping(context.Context) error {
return nil
}

type mockConsumer chan pdata.Metrics

func (m mockConsumer) Capabilities() consumer.Capabilities {
Expand Down
1 change: 1 addition & 0 deletions receiver/podmanreceiver/testdata/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ receivers:
podman_stats/all:
endpoint: http:https://example.com/
collection_interval: 2s
timeout: 20s

processors:
nop:
Expand Down

0 comments on commit 03b379f

Please sign in to comment.