Skip to content

Commit

Permalink
[receiver/purefa] Pure Storage FlashArray - Purity v6.6.11+ Native Op…
Browse files Browse the repository at this point in the history
…enMetrics Support (#36251)

This PR implements the changes required to scrape metrics from arrays
with newer Purity versions.

In Pure Storage FlashArray Purity version 6.6.11, they released a
feature that allows OpenMetrics to be scraped directly from the array
without the use of an OpenMetrics Exporter.

These changes include
- Enabling support to disable TLS Insecure Skip Verify if required
(default is to verify)
- Enabling support for the `namespace` HTTP param that is supported by
the Purity OpenMetrics exporter.

#### Link to tracking issue
None

<!--Describe what testing was performed and which tests were added.-->
#### Testing
Tests have been updated for the scraper and default configs for the new
Namespace and TLSInsecureSkipVerify configurations.

I have tested this with a live FlashArray running 6.6.11


#### Documentation

The Readme was updated to show a new example of a scrape using Purity
version 6.6.11+.
  • Loading branch information
chrroberts-pure authored Dec 10, 2024
1 parent c808052 commit 24802b6
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 32 deletions.
27 changes: 27 additions & 0 deletions .chloggen/36251-purefa-native-om-support.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: 'enhancement'

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: purefareceiver

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Implements support for scraping Pure Storage FlashArray with Purity version 6.6.11+

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [36251]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [user]
32 changes: 19 additions & 13 deletions receiver/purefareceiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
<!-- end autogenerated section -->

The Pure Storage FlashArray receiver, receives metrics from Pure Storage internal services hosts.
The Pure Storage FlashArray receiver, receives metrics from the Pure Storage FlashArray.

## Configuration

The following settings are required:
- `endpoint` (default: `http://172.0.0.0:9490/metrics/array`): The URL of the scraper selected endpoint
- `endpoint` (default: `http://127.0.0.0:9490/metrics/array`): The URL of the scraper selected endpoint.
- `fa_array_name` (no default): The array's pretty name to be used as a metrics label.
- `namespace` (default:purefa): The selected Pure Storage OpenMetrics Namespace to query.

In the below examples array01 is using the [Pure Storage FlashArray OpenMetrics exporter](https://github.com/PureStorage-OpenConnect/pure-fa-openmetrics-exporter), while array02 is using the native on-box metrics provided in Purity//FA v6.6.11+.

Example:

Expand Down Expand Up @@ -55,15 +59,17 @@ receivers:
env: dev
settings:
reload_intervals:
array: 10s
hosts: 13s
directories: 15s
pods: 30s
volumes: 25s
array: 20s
hosts: 60s
directories: 60s
pods: 60s
volumes: 60s

purefa/array02:
fa_array_name: foobar02
endpoint: http://127.0.0.1:9490/metrics
endpoint: https://127.0.0.1/metrics
tls:
insecure_skip_verify: true
array:
- address: array02
auth:
Expand All @@ -87,11 +93,11 @@ receivers:
env: production
settings:
reload_intervals:
array: 15s
hosts: 15s
directories: 15s
pods: 30s
volumes: 25s
array: 20s
hosts: 60s
directories: 60s
pods: 60s
volumes: 60s

service:
extensions: [bearertokenauth/array01,bearertokenauth/array02]
Expand Down
8 changes: 7 additions & 1 deletion receiver/purefareceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ type Config struct {
// Env represents the respective environment value valid to scrape
Env string `mapstructure:"env"`

// ArrayName represents the display name that is appended to the received metrics, as the `host` label if not provided by OpenMetrics output, and to the `fa_array_name` label always.
// ArrayName represents the display name that is appended to the received metrics, as the `host` label if not provided by OpenMetrics output, and to the `fa_array_name` label always
ArrayName string `mapstructure:"fa_array_name"`

// Namespace selects the OpenMetrics namespace requested
Namespace string `mapstructure:"namespace"`
}

type Settings struct {
Expand All @@ -63,6 +66,9 @@ func (c *Config) Validate() error {
if c.ArrayName == "" {
errs = multierr.Append(errs, errors.New("the array's pretty name as 'fa_array_name' must be provided"))
}
if c.Namespace == "" {
errs = multierr.Append(errs, errors.New("a specified namespace must be provided"))
}
if c.Settings.ReloadIntervals.Array == 0 {
errs = multierr.Append(errs, errors.New("reload interval for 'array' must be provided"))
}
Expand Down
11 changes: 6 additions & 5 deletions receiver/purefareceiver/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ func TestLoadConfig(t *testing.T) {
expected: &Config{
ClientConfig: clientConfig,
ArrayName: "foobar.example.com",
Namespace: "purefa",
Settings: &Settings{
ReloadIntervals: &ReloadIntervals{
Array: 15 * time.Second,
Hosts: 15 * time.Second,
Directories: 15 * time.Second,
Pods: 15 * time.Second,
Volumes: 15 * time.Second,
Array: 60 * time.Second,
Hosts: 60 * time.Second,
Directories: 60 * time.Second,
Pods: 60 * time.Second,
Volumes: 60 * time.Second,
},
},
},
Expand Down
11 changes: 6 additions & 5 deletions receiver/purefareceiver/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ func NewFactory() receiver.Factory {
func createDefaultConfig() component.Config {
return &Config{
ArrayName: "foobar.example.com",
Namespace: "purefa",
ClientConfig: confighttp.NewDefaultClientConfig(),
Settings: &Settings{
ReloadIntervals: &ReloadIntervals{
Array: 15 * time.Second,
Hosts: 15 * time.Second,
Directories: 15 * time.Second,
Pods: 15 * time.Second,
Volumes: 15 * time.Second,
Array: 60 * time.Second,
Hosts: 60 * time.Second,
Directories: 60 * time.Second,
Pods: 60 * time.Second,
Volumes: 60 * time.Second,
},
},
}
Expand Down
2 changes: 1 addition & 1 deletion receiver/purefareceiver/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
go.opentelemetry.io/collector/component/componenttest v0.115.1-0.20241206185113-3f3e208e71b8
go.opentelemetry.io/collector/config/configauth v0.115.1-0.20241206185113-3f3e208e71b8
go.opentelemetry.io/collector/config/confighttp v0.115.1-0.20241206185113-3f3e208e71b8
go.opentelemetry.io/collector/config/configtls v1.21.1-0.20241206185113-3f3e208e71b8
go.opentelemetry.io/collector/confmap v1.21.1-0.20241206185113-3f3e208e71b8
go.opentelemetry.io/collector/consumer v1.21.1-0.20241206185113-3f3e208e71b8
go.opentelemetry.io/collector/consumer/consumertest v0.115.1-0.20241206185113-3f3e208e71b8
Expand Down Expand Up @@ -144,7 +145,6 @@ require (
go.opentelemetry.io/collector/config/configcompression v1.21.1-0.20241206185113-3f3e208e71b8 // indirect
go.opentelemetry.io/collector/config/configopaque v1.21.1-0.20241206185113-3f3e208e71b8 // indirect
go.opentelemetry.io/collector/config/configtelemetry v0.115.1-0.20241206185113-3f3e208e71b8 // indirect
go.opentelemetry.io/collector/config/configtls v1.21.1-0.20241206185113-3f3e208e71b8 // indirect
go.opentelemetry.io/collector/config/internal v0.115.1-0.20241206185113-3f3e208e71b8 // indirect
go.opentelemetry.io/collector/consumer/consumererror v0.115.1-0.20241206185113-3f3e208e71b8 // indirect
go.opentelemetry.io/collector/consumer/consumerprofiles v0.115.1-0.20241206185113-3f3e208e71b8 // indirect
Expand Down
18 changes: 17 additions & 1 deletion receiver/purefareceiver/internal/scraper.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/discovery"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/receiver"
)

Expand All @@ -34,6 +35,8 @@ const (
type scraper struct {
scraperType ScraperType
endpoint string
namespace string
tlsSettings configtls.ClientConfig
configs []ScraperConfig
scrapeInterval time.Duration
labels model.LabelSet
Expand All @@ -42,13 +45,17 @@ type scraper struct {
func NewScraper(_ context.Context,
scraperType ScraperType,
endpoint string,
namespace string,
tlsSettings configtls.ClientConfig,
configs []ScraperConfig,
scrapeInterval time.Duration,
labels model.LabelSet,
) Scraper {
return &scraper{
scraperType: scraperType,
endpoint: endpoint,
namespace: namespace,
tlsSettings: tlsSettings,
configs: configs,
scrapeInterval: scrapeInterval,
labels: labels,
Expand All @@ -71,17 +78,26 @@ func (h *scraper) ToPrometheusReceiverConfig(host component.Host, _ receiver.Fac

httpConfig := configutil.HTTPClientConfig{}
httpConfig.BearerToken = configutil.Secret(bearerToken)
httpConfig.TLSConfig = configutil.TLSConfig{
CAFile: h.tlsSettings.CAFile,
CertFile: h.tlsSettings.CertFile,
KeyFile: h.tlsSettings.KeyFile,
InsecureSkipVerify: h.tlsSettings.InsecureSkipVerify,
ServerName: h.tlsSettings.ServerName,
}

scrapeConfig := &config.ScrapeConfig{
HTTPClientConfig: httpConfig,
ScrapeProtocols: config.DefaultScrapeProtocols,
ScrapeInterval: model.Duration(h.scrapeInterval),
ScrapeTimeout: model.Duration(h.scrapeInterval),
JobName: fmt.Sprintf("%s/%s/%s", "purefa", h.scraperType, arr.Address),
HonorTimestamps: true,
Scheme: u.Scheme,
MetricsPath: fmt.Sprintf("/metrics/%s", h.scraperType),
Params: url.Values{
"endpoint": {arr.Address},
"endpoint": {arr.Address},
"namespace": {h.namespace},
},

ServiceDiscoveryConfigs: discovery.Configs{
Expand Down
12 changes: 11 additions & 1 deletion receiver/purefareceiver/internal/scraper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configauth"
"go.opentelemetry.io/collector/config/configtls"
"go.opentelemetry.io/collector/extension/extensiontest"

"github.com/open-telemetry/opentelemetry-collector-contrib/extension/bearertokenauthextension"
Expand All @@ -37,6 +38,11 @@ func TestToPrometheusConfig(t *testing.T) {
}

endpoint := "http://example.com"
namespace := "purefa"
tlsSettings := configtls.ClientConfig{
InsecureSkipVerify: true,
ServerName: "TestThisServerName",
}
interval := 15 * time.Second
cfgs := []ScraperConfig{
{
Expand All @@ -47,15 +53,19 @@ func TestToPrometheusConfig(t *testing.T) {
},
}

scraper := NewScraper(context.Background(), "hosts", endpoint, cfgs, interval, model.LabelSet{})
scraper := NewScraper(context.Background(), "hosts", endpoint, namespace, tlsSettings, cfgs, interval, model.LabelSet{})

// test
scCfgs, err := scraper.ToPrometheusReceiverConfig(host, prFactory)

// verify
assert.NoError(t, err)
assert.Len(t, scCfgs, 1)
assert.NotNil(t, scCfgs[0].ScrapeProtocols)
assert.EqualValues(t, "purefa", scCfgs[0].Params.Get("namespace"))
assert.EqualValues(t, "the-token", scCfgs[0].HTTPClientConfig.BearerToken)
assert.True(t, scCfgs[0].HTTPClientConfig.TLSConfig.InsecureSkipVerify)
assert.Equal(t, "TestThisServerName", scCfgs[0].HTTPClientConfig.TLSConfig.ServerName)
assert.Equal(t, "array01", scCfgs[0].Params.Get("endpoint"))
assert.Equal(t, "/metrics/hosts", scCfgs[0].MetricsPath)
assert.Equal(t, "purefa/hosts/array01", scCfgs[0].JobName)
Expand Down
10 changes: 5 additions & 5 deletions receiver/purefareceiver/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,35 +53,35 @@ func (r *purefaReceiver) Start(ctx context.Context, compHost component.Host) err
"fa_array_name": ArrayName,
}

arrScraper := internal.NewScraper(ctx, internal.ScraperTypeArray, r.cfg.Endpoint, r.cfg.Array, r.cfg.Settings.ReloadIntervals.Array, commomLabel)
arrScraper := internal.NewScraper(ctx, internal.ScraperTypeArray, r.cfg.Endpoint, r.cfg.Namespace, r.cfg.TLSSetting, r.cfg.Array, r.cfg.Settings.ReloadIntervals.Array, commomLabel)
if scCfgs, err := arrScraper.ToPrometheusReceiverConfig(compHost, fact); err == nil {
scrapeCfgs = append(scrapeCfgs, scCfgs...)
} else {
return err
}

hostScraper := internal.NewScraper(ctx, internal.ScraperTypeHosts, r.cfg.Endpoint, r.cfg.Hosts, r.cfg.Settings.ReloadIntervals.Hosts, labelSet)
hostScraper := internal.NewScraper(ctx, internal.ScraperTypeHosts, r.cfg.Endpoint, r.cfg.Namespace, r.cfg.TLSSetting, r.cfg.Hosts, r.cfg.Settings.ReloadIntervals.Hosts, labelSet)
if scCfgs, err := hostScraper.ToPrometheusReceiverConfig(compHost, fact); err == nil {
scrapeCfgs = append(scrapeCfgs, scCfgs...)
} else {
return err
}

directoriesScraper := internal.NewScraper(ctx, internal.ScraperTypeDirectories, r.cfg.Endpoint, r.cfg.Directories, r.cfg.Settings.ReloadIntervals.Directories, commomLabel)
directoriesScraper := internal.NewScraper(ctx, internal.ScraperTypeDirectories, r.cfg.Endpoint, r.cfg.Namespace, r.cfg.TLSSetting, r.cfg.Directories, r.cfg.Settings.ReloadIntervals.Directories, commomLabel)
if scCfgs, err := directoriesScraper.ToPrometheusReceiverConfig(compHost, fact); err == nil {
scrapeCfgs = append(scrapeCfgs, scCfgs...)
} else {
return err
}

podsScraper := internal.NewScraper(ctx, internal.ScraperTypePods, r.cfg.Endpoint, r.cfg.Pods, r.cfg.Settings.ReloadIntervals.Pods, commomLabel)
podsScraper := internal.NewScraper(ctx, internal.ScraperTypePods, r.cfg.Endpoint, r.cfg.Namespace, r.cfg.TLSSetting, r.cfg.Pods, r.cfg.Settings.ReloadIntervals.Pods, commomLabel)
if scCfgs, err := podsScraper.ToPrometheusReceiverConfig(compHost, fact); err == nil {
scrapeCfgs = append(scrapeCfgs, scCfgs...)
} else {
return err
}

volumesScraper := internal.NewScraper(ctx, internal.ScraperTypeVolumes, r.cfg.Endpoint, r.cfg.Volumes, r.cfg.Settings.ReloadIntervals.Volumes, labelSet)
volumesScraper := internal.NewScraper(ctx, internal.ScraperTypeVolumes, r.cfg.Endpoint, r.cfg.Namespace, r.cfg.TLSSetting, r.cfg.Volumes, r.cfg.Settings.ReloadIntervals.Volumes, labelSet)
if scCfgs, err := volumesScraper.ToPrometheusReceiverConfig(compHost, fact); err == nil {
scrapeCfgs = append(scrapeCfgs, scCfgs...)
} else {
Expand Down
1 change: 1 addition & 0 deletions receiver/purefareceiver/testdata/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ receivers:

purefa/with_custom_intervals:
fa_array_name: foobar.example.com
namespace: purefa
endpoint: http://172.31.60.208:9490/metrics
array:
- address: array01
Expand Down

0 comments on commit 24802b6

Please sign in to comment.