Skip to content

Commit e78c94e

Browse files
[PLINT-394] Add support for swift component (#17640)
* [PLINT-394] Add support for swift component ### What does this PR do? <!-- A brief description of the change being made with this pull request. --> Adds support for the swift component in openstack, adding a service check, response time metric, and container count/bytes metric. ### Motivation <!-- What inspired you to submit this pull request? --> ### Additional Notes <!-- Anything else we should know when reviewing? --> ### Review checklist (to be filled by reviewers) - [ ] Feature or bugfix MUST have appropriate tests (unit, integration, e2e) - [ ] [Changelog entries](https://datadoghq.dev/integrations-core/guidelines/pr/#changelog-entries) must be created for modifications to shipped code - [ ] Add the `qa/skip-qa` label if the PR doesn't need to be tested during QA. - [ ] If you need to backport this PR to another branch, you can add the `backport/<branch-name>` label to the PR and it will automatically open a backport PR once this one is merged * added changelog Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * updating swift license Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * reducing number of expected api calls due to different implementation of api_rest Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * changing swift port from 8080 to 6002 Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * removing ?format=json from the default swift endpoint Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * Fixed error in test_response_time tests for swift component * specifying the format=json parameter before the limit parameter Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * removing requests import Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * small refactor * moved out logic for removing project_id in get_response_time * fixed linter errors * fixed linter error * fixing test_response_time Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * removing debug statement Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * fixed test_containers_exception Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * improved test_containers_exception Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> --------- Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> Co-authored-by: Jose Manuel Almaza <josemanuel.almaza@datadoghq.com>
1 parent 19c8956 commit e78c94e

File tree

23 files changed

+614
-18
lines changed

23 files changed

+614
-18
lines changed

openstack_controller/assets/configuration/spec.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,13 @@ files:
662662
properties:
663663
- name: stacks
664664
type: boolean
665+
- name: swift
666+
anyOf:
667+
- type: boolean
668+
- type: object
669+
properties:
670+
- name: containers
671+
type: boolean
665672
example:
666673
compute: false
667674
- name: projects

openstack_controller/assets/service_checks.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,5 +164,21 @@
164164
"region_id"
165165
],
166166
"description": "Returns `CRITICAL` if the Agent is unable to query the HEAT API and `UNKNOWN` if endpoint is not found in the catalog. Returns `OK` otherwise."
167+
},
168+
{
169+
"agent_version": "7.55.0",
170+
"check": "openstack.swift.api.up",
171+
"statuses": [
172+
"ok",
173+
"critical"
174+
],
175+
"integration": "OpenStack",
176+
"name": "OpenStack SWIFT API Up",
177+
"groups": [
178+
"host",
179+
"keystone_server",
180+
"region_id"
181+
],
182+
"description": "Returns `CRITICAL` if the Agent is unable to query the SWIFT API and `UNKNOWN` if endpoint is not found in the catalog. Returns `OK` otherwise."
167183
}
168184
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added Swift component

openstack_controller/datadog_checks/openstack_controller/api/api_rest.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# All rights reserved
33
# Licensed under a 3-clause BSD style license (see LICENSE)
44

5-
65
from datadog_checks.openstack_controller.api.api import Api
76
from datadog_checks.openstack_controller.api.catalog import Catalog
87
from datadog_checks.openstack_controller.components.component import Component
@@ -31,12 +30,13 @@ def has_admin_role(self):
3130
def component_in_catalog(self, component_types):
3231
return self._catalog.has_component(component_types)
3332

34-
def get_response_time(self, endpoint_types):
33+
def get_response_time(self, endpoint_types, remove_project_id=True):
3534
endpoint = (
3635
self._catalog.get_endpoint_by_type(endpoint_types).replace(self._current_project_id, "")
37-
if self._current_project_id
36+
if self._current_project_id and remove_project_id
3837
else self._catalog.get_endpoint_by_type(endpoint_types)
3938
)
39+
self.log.debug("getting response time for `%s`", endpoint)
4040
response = self.http.get(endpoint)
4141
response.raise_for_status()
4242
return response.elapsed.total_seconds() * 1000
@@ -199,13 +199,11 @@ def get_identity_limits(self):
199199
return response.json().get('limits', [])
200200

201201
def get_block_storage_volumes(self, project_id):
202-
params = {}
203202
return self.make_paginated_request(
204203
'{}/volumes/detail'.format(self._catalog.get_endpoint_by_type(Component.Types.BLOCK_STORAGE.value)),
205204
'volumes',
206205
'id',
207206
next_signifier='volumes_links',
208-
params=params,
209207
)
210208

211209
def get_block_storage_transfers(self, project_id):
@@ -218,13 +216,11 @@ def get_block_storage_transfers(self, project_id):
218216
return response.json().get('transfers', {})
219217

220218
def get_block_storage_snapshots(self, project_id):
221-
params = {}
222219
return self.make_paginated_request(
223220
'{}/snapshots/detail'.format(self._catalog.get_endpoint_by_type(Component.Types.BLOCK_STORAGE.value)),
224221
'snapshots',
225222
'id',
226223
next_signifier='snapshots_links',
227-
params=params,
228224
)
229225

230226
def get_block_storage_pools(self, project_id):
@@ -358,27 +354,25 @@ def make_request(url, params):
358354

359355
if self.config.paginated_limit is None:
360356
response_json = make_request(url, params)
361-
objects = response_json.get(resource_name, [])
362-
return objects
357+
return response_json if resource_name is None else response_json.get(resource_name, [])
363358

359+
params['limit'] = self.config.paginated_limit
364360
while True:
365361
self.log.debug(
366362
"making paginated request [limit=%s, marker=%s]",
367363
self.config.paginated_limit,
368364
marker,
369365
)
370-
371-
params['limit'] = self.config.paginated_limit
372366
if marker is not None:
373367
params['marker'] = marker
374368

375369
response_json = make_request(url, params)
376-
resources = response_json.get(resource_name, [])
370+
resources = response_json if resource_name is None else response_json.get(resource_name, [])
377371
if len(resources) > 0:
378372
last_item = resources[-1]
379373
item_list.extend(resources)
380374

381-
if next_signifier == '{}_links'.format(resource_name):
375+
if next_signifier == f'{resource_name}_links':
382376
has_next_link = False
383377
links = response_json.get(next_signifier, [])
384378
for link in links:
@@ -389,7 +383,11 @@ def make_request(url, params):
389383
if not has_next_link:
390384
break
391385
else:
392-
next_item = response_json.get(next_signifier)
386+
next_item = (
387+
response_json[-1].get(next_signifier)
388+
if resource_name is None
389+
else response_json.get(next_signifier)
390+
)
393391
if next_item is None:
394392
break
395393

@@ -607,3 +605,13 @@ def get_heat_stacks(self, project_id):
607605
'id',
608606
next_signifier='links',
609607
)
608+
609+
def get_swift_containers(self, account_id):
610+
params = {'format': 'json'}
611+
return self.make_paginated_request(
612+
'{}'.format(self._catalog.get_endpoint_by_type(Component.Types.SWIFT.value)),
613+
None,
614+
'name',
615+
next_signifier='name',
616+
params=params,
617+
)

openstack_controller/datadog_checks/openstack_controller/api/api_sdk.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,11 @@ def authorize_project(self, project_id):
115115
self.connection.authorize()
116116
self.http.options['headers']['X-Auth-Token'] = self.connection.session.auth.get_token(self.connection.session)
117117

118-
def get_response_time(self, endpoint_types):
118+
def get_response_time(self, endpoint_types, remove_project_id=True):
119119
endpoint = self._catalog.get_endpoint_by_type(endpoint_types)
120-
endpoint = endpoint.replace(self._access.project_id, "") if self._access.project_id else endpoint
120+
endpoint = (
121+
endpoint.replace(self._access.project_id, "") if self._access.project_id and remove_project_id else endpoint
122+
)
121123
response = self.http.get(endpoint)
122124
response.raise_for_status()
123125
return response.elapsed.total_seconds() * 1000
@@ -394,3 +396,11 @@ def get_heat_stacks(self, project_id):
394396
self.connection.heat.stacks, project_id=project_id, limit=self.config.paginated_limit
395397
)
396398
]
399+
400+
def get_swift_containers(self, account_id):
401+
return [
402+
container.to_dict(original_names=True)
403+
for container in self.call_paginated_api(
404+
self.connection.swift.containers, account_id=account_id, limit=self.config.paginated_limit
405+
)
406+
]

openstack_controller/datadog_checks/openstack_controller/components/component.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class Id(str, Enum):
4949
LOAD_BALANCER = 'load-balancer'
5050
IMAGE = 'image'
5151
HEAT = 'heat'
52+
SWIFT = 'swift'
5253

5354
@unique
5455
class Types(list, Enum):
@@ -60,6 +61,7 @@ class Types(list, Enum):
6061
LOAD_BALANCER = ['load-balancer']
6162
IMAGE = ['image']
6263
HEAT = ['orchestration']
64+
SWIFT = ['object-store']
6365

6466
def http_error(report_service_check=False):
6567
def decorator_http_error(func):
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# (C) Datadog, Inc. 2024-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
5+
6+
from datadog_checks.openstack_controller.components.component import Component
7+
from datadog_checks.openstack_controller.metrics import (
8+
SWIFT_CONTAINER_COUNT,
9+
SWIFT_CONTAINER_METRICS,
10+
SWIFT_CONTAINER_PREFIX,
11+
SWIFT_CONTAINER_TAGS,
12+
SWIFT_RESPONSE_TIME,
13+
SWIFT_SERVICE_CHECK,
14+
get_metrics_and_tags,
15+
)
16+
17+
18+
class Swift(Component):
19+
ID = Component.Id.SWIFT
20+
TYPES = Component.Types.SWIFT
21+
SERVICE_CHECK = SWIFT_SERVICE_CHECK
22+
23+
def __init__(self, check):
24+
super(Swift, self).__init__(check)
25+
26+
@Component.register_global_metrics(ID)
27+
@Component.http_error(report_service_check=True)
28+
def _report_response_time(self, global_components_config, tags):
29+
self.check.log.debug("reporting `%s` response time", Swift.ID.value)
30+
response_time = self.check.api.get_response_time(Swift.TYPES.value, remove_project_id=False)
31+
self.check.log.debug("`%s` response time: %s", Swift.ID.value, response_time)
32+
self.check.gauge(SWIFT_RESPONSE_TIME, response_time, tags=tags)
33+
34+
@Component.register_project_metrics(ID)
35+
@Component.http_error()
36+
def _report_containers(self, project_id, tags, config):
37+
report_containers = config.get('containers', True)
38+
if report_containers:
39+
data = self.check.api.get_swift_containers(project_id)
40+
self.check.log.debug("containers: %s", data)
41+
for item in data:
42+
container = get_metrics_and_tags(
43+
item,
44+
tags=SWIFT_CONTAINER_TAGS,
45+
prefix=SWIFT_CONTAINER_PREFIX,
46+
metrics=SWIFT_CONTAINER_METRICS,
47+
)
48+
self.check.log.debug("container: %s", container)
49+
self.check.gauge(SWIFT_CONTAINER_COUNT, 1, tags=tags + container['tags'])
50+
for metric, value in container['metrics'].items():
51+
self.check.gauge(metric, value, tags=tags + container['tags'])

openstack_controller/datadog_checks/openstack_controller/config_models/instance.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,14 @@ class NetworkItem(BaseModel):
327327
quotas: Optional[bool] = None
328328

329329

330+
class SwiftItem(BaseModel):
331+
model_config = ConfigDict(
332+
arbitrary_types_allowed=True,
333+
frozen=True,
334+
)
335+
containers: Optional[bool] = None
336+
337+
330338
class Components(BaseModel):
331339
model_config = ConfigDict(
332340
arbitrary_types_allowed=True,
@@ -340,6 +348,7 @@ class Components(BaseModel):
340348
image: Optional[Union[bool, ImageItem]] = None
341349
load_balancer: Optional[Union[bool, LoadBalancerItem]] = Field(None, alias='load-balancer')
342350
network: Optional[Union[bool, NetworkItem]] = None
351+
swift: Optional[Union[bool, SwiftItem]] = None
343352

344353

345354
class MetricPatterns(BaseModel):

openstack_controller/datadog_checks/openstack_controller/metrics.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,18 @@
645645
'stack_status': 'stack_status',
646646
}
647647

648+
SWIFT_METRICS_PREFIX = "openstack.swift"
649+
SWIFT_SERVICE_CHECK = f"{SWIFT_METRICS_PREFIX}.api.up"
650+
SWIFT_RESPONSE_TIME = f"{SWIFT_METRICS_PREFIX}.response_time"
651+
SWIFT_CONTAINER_PREFIX = f"{SWIFT_METRICS_PREFIX}.container"
652+
SWIFT_CONTAINER_COUNT = f"{SWIFT_CONTAINER_PREFIX}.count"
653+
SWIFT_CONTAINER_METRICS = {
654+
f"{SWIFT_CONTAINER_PREFIX}.bytes": {},
655+
}
656+
SWIFT_CONTAINER_TAGS = {
657+
'name': 'container_name',
658+
}
659+
648660

649661
def is_interface_metric(label):
650662
return any(seg in label for seg in ['_rx', '_tx'])

openstack_controller/datadog_checks/openstack_controller/openstack_controller.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from datadog_checks.openstack_controller.components.image import Image
1717
from datadog_checks.openstack_controller.components.load_balancer import LoadBalancer
1818
from datadog_checks.openstack_controller.components.network import Network
19+
from datadog_checks.openstack_controller.components.swift import Swift
1920
from datadog_checks.openstack_controller.config import OpenstackConfig, normalize_discover_config_include
2021

2122
from .config_models import ConfigMixin
@@ -53,6 +54,7 @@ def init(self):
5354
LoadBalancer(self),
5455
Image(self),
5556
Heat(self),
57+
Swift(self),
5658
]
5759
self.projects_discovery = None
5860
if self.config.projects:

openstack_controller/metadata.csv

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,6 @@ openstack.octavia.quota.loadbalancer,gauge,,,,The configured load balancer quota
272272
openstack.octavia.quota.member,gauge,,,,The configured member quota limit,0,openstack_controller,octavia quota member,
273273
openstack.octavia.quota.pool,gauge,,,,The configured pool quota limit,0,openstack_controller,octavia quota pool,
274274
openstack.octavia.response_time,gauge,,millisecond,,Duration that an HTTP request takes to complete when making a request to octavia endpoint,0,openstack_controller,octavia response time,
275+
openstack.swift.container.bytes,gauge,,,,Number of bytes in a swift container,0,openstack_controller,swift container bytes,
276+
openstack.swift.container.count,gauge,,,,"Number of swift containers",0,openstack_controller,swift container ct,
277+
openstack.swift.response_time,gauge,,millisecond,,"Duration that an HTTP request takes to complete when making a request to swift endpoint",0,openstack_controller,swift response time,

openstack_controller/tests/conftest.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def method(method, url, file='response', headers=None, params=None):
188188
def mock_http_call(mock_responses):
189189
def call(method, url, file='response', headers=None, params=None):
190190
response = mock_responses(method, url, file=file, headers=headers, params=params)
191-
if response:
191+
if response is not None:
192192
return response
193193
http_response = requests.models.Response()
194194
http_response.status_code = 404
@@ -921,6 +921,26 @@ def stacks(project_id, limit=None):
921921
return mock.MagicMock(stacks=mock.MagicMock(side_effect=stacks))
922922

923923

924+
@pytest.fixture
925+
def connection_swift(request, mock_responses):
926+
param = request.param if hasattr(request, 'param') and request.param is not None else {}
927+
http_error = param.get('http_error')
928+
929+
def containers(account_id, limit=None):
930+
if http_error and 'containers' in http_error and account_id in http_error['containers']:
931+
raise requests.exceptions.HTTPError(response=http_error['containers'])
932+
return [
933+
mock.MagicMock(
934+
to_dict=mock.MagicMock(
935+
return_value=node,
936+
)
937+
)
938+
for node in mock_responses('GET', f'/v1/AUTH_{account_id}/format=json')
939+
]
940+
941+
return mock.MagicMock(containers=mock.MagicMock(side_effect=containers))
942+
943+
924944
@pytest.fixture
925945
def openstack_connection(
926946
openstack_session,
@@ -933,6 +953,7 @@ def openstack_connection(
933953
connection_load_balancer,
934954
connection_image,
935955
connection_heat,
956+
connection_swift,
936957
):
937958
def connection(cloud, session, region_name):
938959
return mock.MagicMock(
@@ -946,6 +967,7 @@ def connection(cloud, session, region_name):
946967
load_balancer=connection_load_balancer,
947968
image=connection_image,
948969
heat=connection_heat,
970+
swift=connection_swift,
949971
)
950972

951973
with mock.patch('openstack.connection.Connection', side_effect=connection) as mock_connection:

openstack_controller/tests/docker/docker-compose.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
version: "3"
21

32
services:
43
openstack-keystone:
@@ -66,3 +65,11 @@ services:
6665
- ./etc/caddy/glance:/etc/caddy/
6766
ports:
6867
- "8004:80"
68+
openstack-swift:
69+
image: caddy:2.6.2-alpine
70+
container_name: openstack-swift
71+
volumes:
72+
- ../fixtures:/usr/share/caddy
73+
- ./etc/caddy/glance:/etc/caddy/
74+
ports:
75+
- "6002:80"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
{
3+
"name": "container_2",
4+
"count": 0,
5+
"bytes": 0,
6+
"last_modified": "2024-05-22T14:40:36.172670"
7+
}
8+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
{
3+
"name": "container_1",
4+
"count": 1,
5+
"bytes": 490,
6+
"last_modified": "2024-05-22T13:23:23.011420"
7+
}
8+
]

0 commit comments

Comments
 (0)