Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
716f8ff
Scaffolding
sarah-witt Apr 6, 2026
f7194d3
validate
sarah-witt Apr 6, 2026
910c717
update labeler
sarah-witt Apr 6, 2026
71d2a93
format
sarah-witt Apr 6, 2026
96d6e94
update changelog
sarah-witt Apr 6, 2026
8fdb3d9
Add can_connect metric
sarah-witt Apr 6, 2026
4a4795b
lint
sarah-witt Apr 6, 2026
5ac5e02
collect system and system statistics
sarah-witt Apr 9, 2026
52f6410
Add API component
sarah-witt Apr 9, 2026
ad2c4fb
Add support for volumes
sarah-witt Apr 13, 2026
c0904d2
Simplify code
sarah-witt Apr 13, 2026
dea21a1
Merge branch 'master' into sarah/add-dell-powerflex-int
sarah-witt Apr 13, 2026
c4c9e75
Add support for storage pools
sarah-witt Apr 13, 2026
ef44177
simplify code
sarah-witt Apr 13, 2026
327d42e
Add support for protection domains
sarah-witt Apr 13, 2026
a40ac86
Add sds support
sarah-witt Apr 14, 2026
d9698fa
parameterize exception test
sarah-witt Apr 14, 2026
11519ee
Add sdc metrics
sarah-witt Apr 14, 2026
51dcffa
Add device metrics
sarah-witt Apr 14, 2026
2d43fd4
Add common metrics
sarah-witt Apr 14, 2026
cbab999
remove resource prefix and tag by resource
sarah-witt Apr 14, 2026
c5cf3ae
Add auth token config
sarah-witt Apr 14, 2026
0bc8b17
Add resource filters and events
sarah-witt Apr 17, 2026
46fd703
handle token workflow
sarah-witt Apr 20, 2026
bf8c9c3
Merge branch 'master' into sarah/add-dell-powerflex-int
sarah-witt May 8, 2026
786b0f7
Merge branch 'sarah/add-dell-powerflex-int' of github.com:DataDog/int…
sarah-witt May 8, 2026
e3a564a
update event query and fix tests
sarah-witt May 8, 2026
ebd5ab9
Add support for alerts
sarah-witt May 8, 2026
56c2864
change to last updated
sarah-witt May 8, 2026
37f46a1
Add more storage pool metrics
sarah-witt May 8, 2026
1c0dcca
rename constants
sarah-witt May 8, 2026
bbd1f57
address comments and clean up tests
sarah-witt May 11, 2026
5ce84bf
Address more feedback
sarah-witt May 11, 2026
69edaff
simplify code more
sarah-witt May 11, 2026
42c01d4
remove dead code and add test for unauthenticated mode
sarah-witt May 11, 2026
caa4e0d
Add overrides and sync models
sarah-witt May 11, 2026
f3a5c04
validate
sarah-witt May 11, 2026
40336d2
Add more debug logging and add collect_events_and_alerts function
sarah-witt May 11, 2026
50a4bd7
replace direct access with .get
sarah-witt May 11, 2026
4d371e2
fix types
sarah-witt May 11, 2026
74aab6b
fix tag fallback
sarah-witt May 11, 2026
febea60
update constant naming and spacing, device statistics powered off by …
sarah-witt May 11, 2026
76c7d89
Add test for device disabled by default
sarah-witt May 11, 2026
3ea80d5
Add severity remapping
sarah-witt May 11, 2026
bc0ce94
Allow multiple filters per resource
sarah-witt May 11, 2026
63e60aa
match nutanix and vsphere filters better
sarah-witt May 11, 2026
3e2e837
Add e2e
sarah-witt May 11, 2026
9e8e2b5
Fix types
sarah-witt May 11, 2026
38a6a40
validate config
sarah-witt May 11, 2026
68a899b
fill coverage gaps and more types
sarah-witt May 11, 2026
f244e0e
Add comment for get_alerts
sarah-witt May 11, 2026
f1c8b08
Add count metric for each resource and fix resource filters bug
sarah-witt May 12, 2026
27fa2b7
use min collection interval to handle expiry and also handle api erro…
sarah-witt May 12, 2026
a63d817
remove unneeded tests
sarah-witt May 12, 2026
360eb7c
clean up tests
sarah-witt May 12, 2026
e1c9611
more test cleanup
sarah-witt May 12, 2026
4eb56e7
remove unneeded test
sarah-witt May 12, 2026
5d87bf5
fix style
sarah-witt May 12, 2026
1660e3d
apply user defined tags and fix metadata
sarah-witt May 12, 2026
6543709
remove unneeded defaults
sarah-witt May 12, 2026
98db57b
validate ci
sarah-witt May 12, 2026
79fbfe9
lint
sarah-witt May 12, 2026
d7be809
ignore type
sarah-witt May 12, 2026
eb8bc69
Merge branch 'master' into sarah/add-dell-powerflex-int
sarah-witt May 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ coverage:
target: 75
flags:
- datadog_cluster_agent
Dell_Powerflex:
target: 75
flags:
- dell_powerflex
Directory:
target: 75
flags:
Expand Down Expand Up @@ -1082,6 +1086,11 @@ flags:
paths:
- ddev/src/ddev
- ddev/tests
dell_powerflex:
carryforward: true
paths:
- dell_powerflex/datadog_checks/dell_powerflex
- dell_powerflex/tests
directory:
carryforward: true
paths:
Expand Down
4 changes: 3 additions & 1 deletion .ddev/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ prefect = "Prefect"
n8n = "n8n"
control_m = "Control-M"
nifi = "Apache NiFi"
dell_powerflex = "Dell Powerflex"

[overrides.metrics-prefix]
krakend = "krakend.api."
Expand All @@ -52,6 +53,7 @@ prefect = "prefect.server."
n8n = "n8n."
control_m = "control_m."
nifi = "nifi."
dell_powerflex = "dell_powerflex."

[overrides.ci.ddev]
platforms = ["linux", "windows"]
Expand Down Expand Up @@ -201,7 +203,6 @@ kube_scheduler = "kube_scheduler"
nifi = "nifi"
nginx_ingress_controller = "nginx-ingress-controller"


[overrides.dep.updates]
exclude = [
'pyasn1', # https://github.com/pyasn1/pyasn1/issues/52
Expand Down Expand Up @@ -252,3 +253,4 @@ prefect = ["linux", "windows", "mac_os"]
n8n = ["linux", "windows", "mac_os"]
control_m = ["linux", "windows", "mac_os"]
nifi = ["linux", "windows", "mac_os"]
dell_powerflex = ["linux", "windows", "mac_os"]
4 changes: 4 additions & 0 deletions .github/workflows/config/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,10 @@ integration/delinea_secret_server:
- changed-files:
- any-glob-to-any-file:
- delinea_secret_server/**/*
integration/dell_powerflex:
- changed-files:
- any-glob-to-any-file:
- dell_powerflex/**/*
integration/directory:
- changed-files:
- any-glob-to-any-file:
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/test-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,26 @@ jobs:
minimum-base-package: ${{ inputs.minimum-base-package }}
pytest-args: ${{ inputs.pytest-args }}
secrets: inherit
jc346754:
uses: ./.github/workflows/test-target.yml
with:
job-name: Dell Powerflex
target: dell_powerflex
platform: linux
runner: '["ubuntu-22.04"]'
repo: "${{ inputs.repo }}"
context: ${{ inputs.context }}
python-version: "${{ inputs.python-version }}"
latest: ${{ inputs.latest }}
agent-image: "${{ inputs.agent-image }}"
agent-image-py2: "${{ inputs.agent-image-py2 }}"
agent-image-windows: "${{ inputs.agent-image-windows }}"
agent-image-windows-py2: "${{ inputs.agent-image-windows-py2 }}"
test-py2: ${{ inputs.test-py2 }}
test-py3: ${{ inputs.test-py3 }}
minimum-base-package: ${{ inputs.minimum-base-package }}
pytest-args: ${{ inputs.pytest-args }}
secrets: inherit
jc8f84c3:
uses: ./.github/workflows/test-target.yml
with:
Expand Down
3 changes: 3 additions & 0 deletions dell_powerflex/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# CHANGELOG - Dell PowerFlex

<!-- towncrier release notes start -->
11 changes: 11 additions & 0 deletions dell_powerflex/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Dell PowerFlex

## Overview
This check monitors [integration name] through the Datadog Agent.
To learn more, visit https://docs.datadoghq.com/integrations/Dell PowerFlex/

## Setup
Visit our documentation to learn more.

## Support
Need help? Contact Datadog support.
100 changes: 100 additions & 0 deletions dell_powerflex/assets/configuration/spec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Dell PowerFlex
files:
- name: dell_powerflex.yaml
options:
- template: init_config
options:
- template: init_config/default
- template: instances
options:
- name: powerflex_gateway_url
required: true
display_priority: 3
description: URL of the PowerFlex Gateway REST API.
value:
example: https://localhost:443
type: string
- name: powerflex_username
display_priority: 2
description: Username for PowerFlex Gateway authentication via Keycloak.
value:
type: string
- name: powerflex_password
secret: true
display_priority: 1
description: Password for PowerFlex Gateway authentication via Keycloak.
value:
type: string
example: <PASSWORD>
- name: powerflex_client_id
description: OAuth2 client ID for Keycloak authentication.
value:
type: string
example: powerflexUI
default: powerflexUI
- name: collect_events
description: Enable collection of CRITICAL and MAJOR severity events from the PowerFlex Gateway.
value:
type: boolean
example: false
- name: collect_alerts
description: Enable collection of alerts from the PowerFlex Gateway.
value:
type: boolean
example: false
- name: resource_filters
description: |
Filter resources by property regex patterns and control statistics
collection per resource type. Exclude takes precedence over include.
If no filter is configured for a resource type, all resources of
that type are collected with statistics enabled, except for devices
which have statistics disabled by default.

Each filter entry has the following fields:
resource: The resource type to filter.
property: The API property name to match against.
patterns: List of regex patterns to match against the property value.
type: Either 'include' (default) or 'exclude'. If a resource matches
both an include and exclude filter, it is excluded.
collect_statistics: When false, skip per-resource statistics API
calls to reduce load. Defaults to true.

Supported resource types and common filterable properties:
volume: name, id, volumeType, storagePoolId, ancestorVolumeId
storage_pool: name, id, mediaType, protectionDomainId
protection_domain: name, id, protectionDomainState
sds: name, id, protectionDomainId, sdsState, faultSetId
sdc: id, sdcGuid, sdcType, sdcIp
device: name, id, storagePoolId, sdsId, deviceCurrentPathName
value:
type: array
items:
type: object
example:
- resource: volume
property: name
patterns:
- ".*"
- resource: storage_pool
property: name
collect_statistics: true
patterns:
- "^prod-"
- resource: protection_domain
property: name
patterns:
- ".*"
- resource: sds
property: name
type: exclude
patterns:
- "^standby-"
- resource: sdc
property: sdcType
patterns:
- "^AppSdc$"
- resource: device
property: name
collect_statistics: false
- template: instances/http
- template: instances/default
1 change: 1 addition & 0 deletions dell_powerflex/changelog.d/23183.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Initial Release
4 changes: 4 additions & 0 deletions dell_powerflex/datadog_checks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# (C) Datadog, Inc. 2026-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)
__path__ = __import__('pkgutil').extend_path(__path__, __name__) # type: ignore
4 changes: 4 additions & 0 deletions dell_powerflex/datadog_checks/dell_powerflex/__about__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# (C) Datadog, Inc. 2026-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)
__version__ = '0.0.1'
7 changes: 7 additions & 0 deletions dell_powerflex/datadog_checks/dell_powerflex/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# (C) Datadog, Inc. 2026-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)
from .__about__ import __version__
from .check import DellPowerflexCheck

__all__ = ['__version__', 'DellPowerflexCheck']
126 changes: 126 additions & 0 deletions dell_powerflex/datadog_checks/dell_powerflex/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# (C) Datadog, Inc. 2026-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)
import logging
from time import time
from typing import Any

from datadog_checks.base.utils.http import RequestsWrapper

TOKEN_PATH = '/auth/realms/powerflex/protocol/openid-connect/token'


class PowerFlexAPI:
def __init__(
self,
http: RequestsWrapper,
gateway_url: str,
logger: logging.Logger,
username: str | None = None,
password: str | None = None,
client_id: str = 'powerflexUI',
min_collection_interval: float = 15,
) -> None:
self._http = http
self._gateway_url = gateway_url
self._username = username
self._password = password
self._client_id = client_id
self._log = logger
self._min_collection_interval = min_collection_interval
self._token: str | None = None
self._token_expiry: float = 0.0

def _ensure_authenticated(self) -> None:
if not self._username:
self._log.debug('No username configured, skipping authentication')
return
if self._token and time() < (self._token_expiry - self._min_collection_interval):
return
self._authenticate()

def _authenticate(self) -> None:
url = f'{self._gateway_url}{TOKEN_PATH}'
response = self._http.post(
url,
data={
'grant_type': 'password',
'client_id': self._client_id,
'username': self._username,
'password': self._password,
},
)
response.raise_for_status()
data = response.json()
self._token = data['access_token']
expires_in = data.get('expires_in', 300)
self._token_expiry = time() + expires_in
self._http.options['headers']['Authorization'] = f'Bearer {self._token}'
self._log.debug('Refreshed PowerFlex auth token, expires in %ds', expires_in)

def _get(self, path: str) -> Any:
self._ensure_authenticated()
response = self._http.get(f"{self._gateway_url}{path}")
response.raise_for_status()
return response.json()

def get_version(self) -> str:
return self._get('/api/version')

def get_systems(self) -> list[dict]:
return self._get('/api/types/System/instances')

def get_system_statistics(self, system_id: str) -> dict:
return self._get(f'/api/instances/System::{system_id}/relationships/Statistics')

def get_volumes(self) -> list[dict]:
return self._get('/api/types/Volume/instances')

def get_volume_statistics(self, volume_id: str) -> dict:
return self._get(f'/api/instances/Volume::{volume_id}/relationships/Statistics')

def get_storage_pools(self) -> list[dict]:
return self._get('/api/types/StoragePool/instances')

def get_storage_pool_statistics(self, pool_id: str) -> dict:
return self._get(f'/api/instances/StoragePool::{pool_id}/relationships/Statistics')

def get_sdc_list(self) -> list[dict]:
return self._get('/api/types/Sdc/instances')

def get_sdc_statistics(self, sdc_id: str) -> dict:
return self._get(f'/api/instances/Sdc::{sdc_id}/relationships/Statistics')

def get_sds_list(self) -> list[dict]:
return self._get('/api/types/Sds/instances')

def get_sds_statistics(self, sds_id: str) -> dict:
return self._get(f'/api/instances/Sds::{sds_id}/relationships/Statistics')

def get_devices(self) -> list[dict]:
return self._get('/api/types/Device/instances')

def get_device_statistics(self, device_id: str) -> dict:
return self._get(f'/api/instances/Device::{device_id}/relationships/Statistics')

def get_protection_domains(self) -> list[dict]:
return self._get('/api/types/ProtectionDomain/instances')

def get_protection_domain_statistics(self, pd_id: str) -> dict:
return self._get(f'/api/instances/ProtectionDomain::{pd_id}/relationships/Statistics')

def get_alerts(self, since: str) -> list[dict]:
# All severities are collected as alerts are less noisy than events.
query = f'/rest/v1/alerts?filter=last_updated ge {since}'
response = self._get(query)
results = response.get('results', []) if isinstance(response, dict) else []
self._log.debug('Collected %d alerts', len(results))
return results

def get_events(self, since: str) -> list[dict]:
query = f'/rest/v1/events?filter=timestamp ge {since}'
response = self._get(query)
results = response.get('results', []) if isinstance(response, dict) else []
events = [e for e in results if e.get('severity') in ('CRITICAL', 'MAJOR')]
self._log.debug('Collected %d events', len(events))
return events
Loading
Loading