Skip to content

Commit 2a06938

Browse files
authored
fix(ess_billing): Improve reliability and prevent API errors (#14744)
This commit introduces several improvements to the ess_billing integration to enhance its reliability and prevent common API errors. - To prevent repeated failing requests on non-200 HTTP status codes, the CEL program now sets want_more to false. This allows the input to retry at the next periodic interval instead of exhausting the execution budget. - A validation rule has been added to enforce a minimum 'from' date of 2021-01-01. This clamps the calculated timestamp to the API's minimum allowed value, preventing 'Bad Request' errors caused by lookbehind configurations creating excessively early dates. This uses the max() function which requires Elastic Agent 8.18 or greater so the Kibana constraint was raised as a proxy. - All instances of the now() function in the CEL program have been replaced with the now variable. This ensures a stable time reference throughout a single execution, leading to more consistent and predictable time-based calculations. A system test has been added to cover these scenarios. Fixes #14743 Fixes #14755
1 parent ebeb2a2 commit 2a06938

File tree

8 files changed

+475
-130
lines changed

8 files changed

+475
-130
lines changed

packages/ess_billing/changelog.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
# newer versions go on top
2+
- version: "1.4.3"
3+
changes:
4+
- description: Fixed minimum date validation for ESS Billing API to prevent errors with dates prior to 2021-01-01. This change requires Elastic Agent 8.18 or greater.
5+
type: bugfix
6+
link: https://github.com/elastic/integrations/pull/14744
7+
- description: Fixed excessive retries by stopping repeated API requests after non-200 HTTP errors.
8+
type: bugfix
9+
link: https://github.com/elastic/integrations/pull/14744
210
- version: "1.4.2"
311
changes:
412
- description: Add temporary processor to remove the fields added by the Agentless policy.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
services:
2+
ess_billing:
3+
image: docker.elastic.co/observability/stream:v0.19.0
4+
ports:
5+
- 8080
6+
volumes:
7+
- ./files:/files:ro
8+
environment:
9+
PORT: '8080'
10+
STREAM_ADDR: ":8080"
11+
command:
12+
- http-server
13+
- --config=/files/stream-config.yml
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
rules:
2+
# Auth error test case (unused because elastic-package cannot handle testing events with error values.)
3+
- path: /api/v2/billing/organizations/3333/costs/instances
4+
methods: ['GET']
5+
request_headers:
6+
Authorization:
7+
- "xxxx"
8+
query_params:
9+
from: "{from:.+}"
10+
to: "{to:.+}"
11+
responses:
12+
- status_code: 403
13+
headers:
14+
Content-Type:
15+
- 'text/plain; charset=utf-8'
16+
body: |-
17+
403: User does not have 'billing-costs:get:3333' permissions
18+
19+
# No instances test case (currently used because elastic-package does not support zero hit_count assertions).
20+
- path: /api/v2/billing/organizations/2222/costs/instances
21+
methods: ['GET']
22+
request_headers:
23+
Authorization:
24+
- "xxxx"
25+
query_params:
26+
from: "{from:.+}"
27+
to: "{to:.+}"
28+
responses:
29+
- status_code: 200
30+
headers:
31+
Content-Type:
32+
- 'application/json'
33+
body: |-
34+
{{ minify_json `
35+
{
36+
"total_ecu": 14.0214,
37+
"instances": []
38+
}
39+
`}}
40+
41+
# Normal test case.
42+
- path: /api/v2/billing/organizations/1111/costs/instances
43+
methods: ['GET']
44+
request_headers:
45+
Authorization:
46+
- "xxxx"
47+
query_params:
48+
from: "{from:.+}"
49+
to: "{to:.+}"
50+
responses:
51+
- status_code: 200
52+
headers:
53+
Content-Type:
54+
- 'application/json'
55+
body: |-
56+
{{ minify_json `
57+
{
58+
"total_ecu": 14.0214,
59+
"instances": [
60+
{
61+
"total_ecu": 14.0214,
62+
"id": "4679751e171d403d8603cb1639b67e57",
63+
"name": "example-deployment",
64+
"type": "deployment",
65+
"product_line_items": [
66+
{
67+
"name": "Cloud Standard, AWS us-east-1 (N. Virginia), aws.es.datafrozen.i3en.2, 4GB, 1AZ",
68+
"total_ecu": 2.8992,
69+
"type": "capacity",
70+
"sku": "aws.es.datafrozen.i3en.2_us-east-1_4096_1",
71+
"unit": "hour",
72+
"quantity": {
73+
"value": 24,
74+
"formatted_value": "24 hours"
75+
},
76+
"display_quantity": {
77+
"value": 24,
78+
"formatted_value": "24 hours",
79+
"type": "default"
80+
},
81+
"rate": {
82+
"value": 0.1208,
83+
"formatted_value": "0.1208 per hour"
84+
},
85+
"kind": "elasticsearch",
86+
"quantities": []
87+
},
88+
{
89+
"name": "Cloud Standard, AWS us-east-1 (N. Virginia), aws.es.datahot.c6gd, 4GB, 2AZ",
90+
"total_ecu": 7.3728,
91+
"type": "capacity",
92+
"sku": "aws.es.datahot.c6gd_us-east-1_4096_2",
93+
"unit": "hour",
94+
"quantity": {
95+
"value": 24,
96+
"formatted_value": "24 hours"
97+
},
98+
"display_quantity": {
99+
"value": 24,
100+
"formatted_value": "24 hours",
101+
"type": "default"
102+
},
103+
"rate": {
104+
"value": 0.3072,
105+
"formatted_value": "0.3072 per hour"
106+
},
107+
"kind": "elasticsearch",
108+
"quantities": []
109+
},
110+
{
111+
"name": "Cloud Standard, AWS us-east-1 (N. Virginia), aws.kibana.c6gd, 1GB, 1AZ",
112+
"total_ecu": 0.0,
113+
"type": "capacity",
114+
"sku": "aws.kibana.c6gd_us-east-1_1024_1",
115+
"unit": "hour",
116+
"quantity": {
117+
"value": 24,
118+
"formatted_value": "24 hours"
119+
},
120+
"display_quantity": {
121+
"value": 24,
122+
"formatted_value": "24 hours",
123+
"type": "default"
124+
},
125+
"rate": {
126+
"value": 0.0,
127+
"formatted_value": "0.0000 per hour"
128+
},
129+
"kind": "kibana",
130+
"quantities": []
131+
},
132+
{
133+
"name": "AWS Data Transfer In",
134+
"total_ecu": 0.0,
135+
"type": "data_in",
136+
"sku": "aws.data-transfer-in",
137+
"unit": "GB",
138+
"quantity": {
139+
"value": 8.326714027673006,
140+
"formatted_value": "8.3267 GBs"
141+
},
142+
"display_quantity": {
143+
"value": 8.326714027673006,
144+
"formatted_value": "8.3267 GBs",
145+
"type": "default"
146+
},
147+
"rate": {
148+
"value": 0.0,
149+
"formatted_value": "0.0000 per GB"
150+
},
151+
"kind": null,
152+
"quantities": []
153+
},
154+
{
155+
"name": "AWS Data Transfer Out",
156+
"total_ecu": 0.0236,
157+
"type": "data_out",
158+
"sku": "aws.data-transfer-out",
159+
"unit": "GB",
160+
"quantity": {
161+
"value": 0.47151940781623125,
162+
"formatted_value": "0.4715 GBs"
163+
},
164+
"display_quantity": {
165+
"value": 0.47151940781623125,
166+
"formatted_value": "0.4715 GBs",
167+
"type": "default"
168+
},
169+
"rate": {
170+
"value": 0.05,
171+
"formatted_value": "0.0500 per GB"
172+
},
173+
"kind": null,
174+
"quantities": []
175+
},
176+
{
177+
"name": "AWS Data Transfer Inter-Node",
178+
"total_ecu": 2.4238,
179+
"type": "data_internode",
180+
"sku": "aws.data-transfer-inter-node",
181+
"unit": "GB",
182+
"quantity": {
183+
"value": 151.48789499513805,
184+
"formatted_value": "151.4879 GBs"
185+
},
186+
"display_quantity": {
187+
"value": 151.48789499513805,
188+
"formatted_value": "151.4879 GBs",
189+
"type": "default"
190+
},
191+
"rate": {
192+
"value": 0.016,
193+
"formatted_value": "0.0160 per GB"
194+
},
195+
"kind": null,
196+
"quantities": []
197+
},
198+
{
199+
"name": "AWS Snapshot Storage",
200+
"total_ecu": 0.2724,
201+
"type": "storage_bytes",
202+
"sku": "aws.snapshot-storage",
203+
"unit": "GB",
204+
"quantity": {
205+
"value": 8.25573491696794,
206+
"formatted_value": "8.2557 GBs"
207+
},
208+
"display_quantity": {
209+
"value": 8.25573491696794,
210+
"formatted_value": "8.2557 GBs",
211+
"type": "default"
212+
},
213+
"rate": {
214+
"value": 0.033,
215+
"formatted_value": "0.0330 per GB"
216+
},
217+
"kind": null,
218+
"quantities": []
219+
},
220+
{
221+
"name": "AWS Snapshot Storage API",
222+
"total_ecu": 1.0296,
223+
"type": "storage_api",
224+
"sku": "aws.snapshot-api-1k",
225+
"unit": "1000 API Calls",
226+
"quantity": {
227+
"value": 572,
228+
"formatted_value": "572k requests"
229+
},
230+
"display_quantity": {
231+
"value": 572,
232+
"formatted_value": "572k requests",
233+
"type": "default"
234+
},
235+
"rate": {
236+
"value": 0.0018,
237+
"formatted_value": "0.0018 per 1000 API Calls"
238+
},
239+
"kind": null,
240+
"quantities": []
241+
},
242+
{
243+
"name": "Synthetics Browser",
244+
"total_ecu": 0.0,
245+
"type": "synthetics_browser",
246+
"sku": "global.synthetics-browser",
247+
"unit": "test",
248+
"quantity": {
249+
"value": 0.0,
250+
"formatted_value": "0 tests"
251+
},
252+
"display_quantity": {
253+
"value": 0.0,
254+
"formatted_value": "0 tests",
255+
"type": "default"
256+
},
257+
"rate": {
258+
"value": 0.0123,
259+
"formatted_value": "0.0123 per test"
260+
},
261+
"kind": null,
262+
"quantities": []
263+
},
264+
{
265+
"name": "Synthetics Lightweight",
266+
"total_ecu": 0.0,
267+
"type": "synthetics_lightweight",
268+
"sku": "global.synthetics-lightweight",
269+
"unit": "region",
270+
"quantity": {
271+
"value": 0.0,
272+
"formatted_value": "0 regions"
273+
},
274+
"display_quantity": {
275+
"value": 0.0,
276+
"formatted_value": "0 regions",
277+
"type": "default"
278+
},
279+
"rate": {
280+
"value": 28.0,
281+
"formatted_value": "28.0000 per region"
282+
},
283+
"kind": null,
284+
"quantities": []
285+
}
286+
]
287+
}
288+
]
289+
}
290+
`}}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
input: cel
2+
service: ess_billing
3+
vars:
4+
url: http://{{Hostname}}:{{Port}}
5+
organization_id: 1111
6+
api_key: xxxx
7+
data_stream:
8+
vars:
9+
lookbehind: 2
10+
hide_sensitive: false
11+
enable_request_tracer: true
12+
# Verify this merges with the built-in drop_event processor.
13+
processors: |-
14+
- add_fields:
15+
target: labels
16+
fields:
17+
test_run_id: "{{Test.RunID}}"
18+
19+
assert:
20+
# There are 10 records in the mocked response.
21+
# And with lookbehind=2 this means it will execute twice, resulting in 10 * 2 = 20 records.
22+
hit_count: 20

0 commit comments

Comments
 (0)