Skip to content

Commit 78b5536

Browse files
[PLINT-393] Add support for heat component (#17573)
* added heat component Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * adding test cases for openstack.heat.stack.up Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * updated licensing Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * sorting metadata.csv Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * addressed comments Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * reordered metadata.csv Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * added back stack_status as a tag and removed the metric openstack.heat.stack.completed Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * removing lambda_name and lambda_value from _report_stacks Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> * Update openstack_controller/tests/test_unit_heat.py Co-authored-by: Sarah Witt <sarah.witt@datadoghq.com> --------- Signed-off-by: rahulkaukuntla <rahul.kaukuntla@datadoghq.com> Co-authored-by: Sarah Witt <sarah.witt@datadoghq.com>
1 parent f8eff5f commit 78b5536

File tree

27 files changed

+741
-2
lines changed

27 files changed

+741
-2
lines changed

openstack_controller/assets/configuration/spec.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,13 @@ files:
655655
properties:
656656
- name: members
657657
type: boolean
658+
- name: heat
659+
anyOf:
660+
- type: boolean
661+
- type: object
662+
properties:
663+
- name: stacks
664+
type: boolean
658665
example:
659666
compute: false
660667
- name: projects

openstack_controller/assets/service_checks.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,21 @@
148148
"tenant_id"
149149
],
150150
"description": "Returns `UNKNOWN` if the Agent is unable to get the Network state, `CRITICAL` if the Network is down. Returns `OK` otherwise."
151+
},
152+
{
153+
"agent_version": "7.55.0",
154+
"check": "openstack.heat.api.up",
155+
"statuses": [
156+
"ok",
157+
"critical"
158+
],
159+
"integration": "OpenStack",
160+
"name": "OpenStack HEAT API Up",
161+
"groups": [
162+
"host",
163+
"keystone_server",
164+
"region_id"
165+
],
166+
"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."
151167
}
152168
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added Heat Component

openstack_controller/datadog_checks/openstack_controller/api/api.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,7 @@ def get_baremetal_conductors(self):
9797
@abstractmethod
9898
def get_baremetal_nodes(self):
9999
pass # pragma: no cover
100+
101+
@abstractmethod
102+
def get_heat_stacks(self):
103+
pass # pragma: no cover

openstack_controller/datadog_checks/openstack_controller/api/api_rest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,3 +599,11 @@ def get_glance_members(self, image_id):
599599
)
600600
response.raise_for_status()
601601
return response.json().get('members', [])
602+
603+
def get_heat_stacks(self, project_id):
604+
return self.make_paginated_request(
605+
'{}/v1/{}/stacks'.format(self._catalog.get_endpoint_by_type(Component.Types.HEAT.value), project_id),
606+
'stacks',
607+
'id',
608+
next_signifier='links',
609+
)

openstack_controller/datadog_checks/openstack_controller/api/api_sdk.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,3 +386,11 @@ def get_glance_images(self):
386386

387387
def get_glance_members(self, image_id):
388388
return [member.to_dict(original_names=True) for member in self.connection.image.members(image_id)]
389+
390+
def get_heat_stacks(self, project_id):
391+
return [
392+
stack.to_dict(original_names=True)
393+
for stack in self.call_paginated_api(
394+
self.connection.heat.stacks, project_id=project_id, limit=self.config.paginated_limit
395+
)
396+
]

openstack_controller/datadog_checks/openstack_controller/components/component.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class Id(str, Enum):
4848
BAREMETAL = 'baremetal'
4949
LOAD_BALANCER = 'load-balancer'
5050
IMAGE = 'image'
51+
HEAT = 'heat'
5152

5253
@unique
5354
class Types(list, Enum):
@@ -58,6 +59,7 @@ class Types(list, Enum):
5859
BAREMETAL = ['baremetal']
5960
LOAD_BALANCER = ['load-balancer']
6061
IMAGE = ['image']
62+
HEAT = ['orchestration']
6163

6264
def http_error(report_service_check=False):
6365
def decorator_http_error(func):
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
HEAT_RESPONSE_TIME,
9+
HEAT_SERVICE_CHECK,
10+
HEAT_STACK_COUNT,
11+
HEAT_STACK_PREFIX,
12+
HEAT_STACK_TAGS,
13+
get_metrics_and_tags,
14+
)
15+
16+
17+
class Heat(Component):
18+
ID = Component.Id.HEAT
19+
TYPES = Component.Types.HEAT
20+
SERVICE_CHECK = HEAT_SERVICE_CHECK
21+
22+
def __init__(self, check):
23+
super(Heat, self).__init__(check)
24+
25+
@Component.register_global_metrics(ID)
26+
@Component.http_error(report_service_check=True)
27+
def _report_response_time(self, global_components_config, tags):
28+
self.check.log.debug("reporting `%s` response time", Heat.ID.value)
29+
response_time = self.check.api.get_response_time(Heat.TYPES.value)
30+
self.check.log.debug("`%s` response time: %s", Heat.ID.value, response_time)
31+
self.check.gauge(HEAT_RESPONSE_TIME, response_time, tags=tags)
32+
33+
@Component.register_project_metrics(ID)
34+
@Component.http_error()
35+
def _report_stacks(self, project_id, tags, config):
36+
report_stacks = config.get('stacks', True)
37+
if report_stacks:
38+
data = self.check.api.get_heat_stacks(project_id)
39+
self.check.log.debug("stacks: %s", data)
40+
for item in data:
41+
stack = get_metrics_and_tags(
42+
item,
43+
tags=HEAT_STACK_TAGS,
44+
prefix=HEAT_STACK_PREFIX,
45+
metrics={},
46+
)
47+
self.check.gauge(HEAT_STACK_COUNT, 1, tags=tags + stack['tags'])
48+
self.check.log.debug("stack: %s", stack)
49+
for metric, value in stack['metrics'].items():
50+
self.check.gauge(metric, value, tags=tags + stack['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
@@ -129,6 +129,14 @@ class ComputeItem(BaseModel):
129129
services: Optional[bool] = None
130130

131131

132+
class HeatItem(BaseModel):
133+
model_config = ConfigDict(
134+
arbitrary_types_allowed=True,
135+
frozen=True,
136+
)
137+
stacks: Optional[bool] = None
138+
139+
132140
class IdentityItem(BaseModel):
133141
model_config = ConfigDict(
134142
arbitrary_types_allowed=True,
@@ -327,6 +335,7 @@ class Components(BaseModel):
327335
baremetal: Optional[Union[bool, BaremetalItem]] = None
328336
block_storage: Optional[Union[bool, BlockStorageItem]] = Field(None, alias='block-storage')
329337
compute: Optional[Union[bool, ComputeItem]] = None
338+
heat: Optional[Union[bool, HeatItem]] = None
330339
identity: Optional[Union[bool, IdentityItem]] = None
331340
image: Optional[Union[bool, ImageItem]] = None
332341
load_balancer: Optional[Union[bool, LoadBalancerItem]] = Field(None, alias='load-balancer')

openstack_controller/datadog_checks/openstack_controller/metrics.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,17 @@
634634
'status': 'status',
635635
}
636636

637+
HEAT_METRICS_PREFIX = "openstack.heat"
638+
HEAT_SERVICE_CHECK = f"{HEAT_METRICS_PREFIX}.api.up"
639+
HEAT_RESPONSE_TIME = f"{HEAT_METRICS_PREFIX}.response_time"
640+
HEAT_STACK_PREFIX = f"{HEAT_METRICS_PREFIX}.stack"
641+
HEAT_STACK_COUNT = f"{HEAT_STACK_PREFIX}.count"
642+
HEAT_STACK_TAGS = {
643+
'id': 'stack_id',
644+
'stack_name': 'stack_name',
645+
'stack_status': 'stack_status',
646+
}
647+
637648

638649
def is_interface_metric(label):
639650
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
@@ -11,6 +11,7 @@
1111
from datadog_checks.openstack_controller.components.bare_metal import BareMetal
1212
from datadog_checks.openstack_controller.components.block_storage import BlockStorage
1313
from datadog_checks.openstack_controller.components.compute import Compute
14+
from datadog_checks.openstack_controller.components.heat import Heat
1415
from datadog_checks.openstack_controller.components.identity import Identity
1516
from datadog_checks.openstack_controller.components.image import Image
1617
from datadog_checks.openstack_controller.components.load_balancer import LoadBalancer
@@ -51,6 +52,7 @@ def init(self):
5152
BareMetal(self),
5253
LoadBalancer(self),
5354
Image(self),
55+
Heat(self),
5456
]
5557
self.projects_discovery = None
5658
if self.config.projects:

openstack_controller/metadata.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ openstack.glance.image.member.count,gauge,,,,Number of members associated with a
1717
openstack.glance.image.size,gauge,,,,Size of image file in bytes,0,openstack_controller,glance image size,
1818
openstack.glance.image.up,gauge,,,,Whether a glance image is up,0,openstack_controller,glance image up,
1919
openstack.glance.response_time,gauge,,millisecond,,Duration that an HTTP request takes to complete when making a request to glance endpoint,0,openstack_controller,glance response time,
20+
openstack.heat.response_time,gauge,,millisecond,,"Duration that an HTTP request takes to complete when making a request to heat endpoint",0,openstack_controller,heat response time,
21+
openstack.heat.stack.count,gauge,,,,"Number of public virtual machine stacks",0,openstack_controller,heat stack ct,
2022
openstack.ironic.allocation.count,gauge,,,,Number of ironic allocations,0,openstack_controller,ironic allocation ct,
2123
openstack.ironic.conductor.count,gauge,,,,Number of ironic conductors,0,openstack_controller,ironic conductor ct,
2224
openstack.ironic.conductor.up,gauge,,,,Whether an ironic conductor is up,0,openstack_controller,ironic conductor up,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
clouds:
22
test_cloud:
3-
auth: {auth_url: 'http://10.164.0.25/identity', password: password, user_domain_id: default,
3+
auth: {auth_url: 'http://10.164.0.71/identity', password: password, user_domain_id: default,
44
username: admin}

openstack_controller/tests/conftest.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,26 @@ def amphorae(project_id, limit=None):
901901
)
902902

903903

904+
@pytest.fixture
905+
def connection_heat(request, mock_responses):
906+
param = request.param if hasattr(request, 'param') and request.param is not None else {}
907+
http_error = param.get('http_error')
908+
909+
def stacks(project_id, limit=None):
910+
if http_error and 'stacks' in http_error and project_id in http_error['stacks']:
911+
raise requests.exceptions.HTTPError(response=http_error['stacks'])
912+
return [
913+
mock.MagicMock(
914+
to_dict=mock.MagicMock(
915+
return_value=node,
916+
)
917+
)
918+
for node in mock_responses('GET', f'/heat-api/v1/{project_id}/stacks')['stacks']
919+
]
920+
921+
return mock.MagicMock(stacks=mock.MagicMock(side_effect=stacks))
922+
923+
904924
@pytest.fixture
905925
def openstack_connection(
906926
openstack_session,
@@ -912,6 +932,7 @@ def openstack_connection(
912932
connection_block_storage,
913933
connection_load_balancer,
914934
connection_image,
935+
connection_heat,
915936
):
916937
def connection(cloud, session, region_name):
917938
return mock.MagicMock(
@@ -924,6 +945,7 @@ def connection(cloud, session, region_name):
924945
block_storage=connection_block_storage,
925946
load_balancer=connection_load_balancer,
926947
image=connection_image,
948+
heat=connection_heat,
927949
)
928950

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

openstack_controller/tests/docker/docker-compose.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,11 @@ services:
5858
- ./etc/caddy/glance:/etc/caddy/
5959
ports:
6060
- "9292:80"
61+
openstack-heat:
62+
image: caddy:2.6.2-alpine
63+
container_name: openstack-heat
64+
volumes:
65+
- ../fixtures:/usr/share/caddy
66+
- ./etc/caddy/glance:/etc/caddy/
67+
ports:
68+
- "8004:80"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
{
3+
debug
4+
admin :2019
5+
}
6+
:80 {
7+
root * /usr/share/caddy/
8+
@get_heat {
9+
method GET
10+
path /heat-api
11+
}
12+
route @get_image {
13+
rewrite * /GET{http.request.uri.path}/response.json
14+
file_server
15+
}
16+
file_server browse
17+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"versions": [
3+
{
4+
"id": "v1.0",
5+
"status": "CURRENT",
6+
"links": [
7+
{
8+
"rel": "self",
9+
"href": "http://openstack.example.com/heat-api//v1/"
10+
}
11+
]
12+
}
13+
]
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"versions": [
3+
{
4+
"id": "v1.0",
5+
"status": "CURRENT",
6+
"links": [
7+
{
8+
"rel": "self",
9+
"href": "http://10.164.0.71/heat-api/v1/"
10+
}
11+
]
12+
}
13+
]
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"stacks": [
3+
{
4+
"id": "36004d0b-715c-442b-a0b9-605111f381c2",
5+
"links": [
6+
{
7+
"href": "http://127.0.0.1/heat-api/v1/1e6e233e637d4d55a50a62b63398ad15/stacks/teststack/36004d0b-715c-442b-a0b9-605111f381c2",
8+
"rel": "self"
9+
}
10+
],
11+
"project": "1e6e233e637d4d55a50a62b63398ad15",
12+
"stack_name": "teststack",
13+
"description": "",
14+
"stack_status": "CREATE_FAILED",
15+
"stack_status_reason": "Resource CREATE failed: ValueError: resources.hello_world: nics are required after microversion 2.36",
16+
"creation_time": "2024-05-13T19:57:43Z",
17+
"updated_time": null,
18+
"deletion_time": null,
19+
"stack_owner": "admin",
20+
"parent": null,
21+
"stack_user_project_id": "cb2488c1dbd34aacb5a160e5217b3385",
22+
"tags": null
23+
}
24+
],
25+
"links": [
26+
{
27+
"rel": "next",
28+
"href": "http://127.0.0.1/heat-api/v1/1e6e233e637d4d55a50a62b63398ad15/stacks?limit=1&marker=36004d0b-715c-442b-a0b9-605111f381c2"
29+
}
30+
]
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"stacks": []}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"stacks": [
3+
{
4+
"id": "0a90fd9f-b411-440d-9e75-960c0bc88c54",
5+
"links": [
6+
{
7+
"href": "http://127.0.0.1/heat-api/v1/1e6e233e637d4d55a50a62b63398ad15/stacks/teststack1/0a90fd9f-b411-440d-9e75-960c0bc88c54",
8+
"rel": "self"
9+
}
10+
],
11+
"project": "1e6e233e637d4d55a50a62b63398ad15",
12+
"stack_name": "teststack1",
13+
"description": "",
14+
"stack_status": "CREATE_FAILED",
15+
"stack_status_reason": "Resource CREATE failed: ValueError: resources.hello_world: nics are required after microversion 2.36",
16+
"creation_time": "2024-05-14T14:09:41Z",
17+
"updated_time": null,
18+
"deletion_time": null,
19+
"stack_owner": "admin",
20+
"parent": null,
21+
"stack_user_project_id": "8de984c5dde144f6847ac5c44c9ca709",
22+
"tags": null
23+
}
24+
],
25+
"links": [
26+
{
27+
"rel": "next",
28+
"href": "http://127.0.0.1/heat-api/v1/1e6e233e637d4d55a50a62b63398ad15/stacks?limit=1&marker=0a90fd9f-b411-440d-9e75-960c0bc88c54"
29+
}
30+
]
31+
}

0 commit comments

Comments
 (0)