Skip to content

Commit 4e591a6

Browse files
authored
fix(billing-cost-management-mcp-server): surface EBS attachment_state on COH get_recommendation (#4966)
1 parent 6471ab9 commit 4e591a6

3 files changed

Lines changed: 68 additions & 1 deletion

File tree

‎src/billing-cost-management-mcp-server/awslabs/billing_cost_management_mcp_server/tools/cost_optimization_hub_helpers.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,13 @@ async def get_recommendation(
270270
'recommendedResourceDetails'
271271
)
272272

273+
# An EBS volume's attachmentState ("Attached"/"Unattached") tells an idle volume from an
274+
# unattached one under action type Delete, so surface it at the top level.
275+
current_details = recommendation.get('currentResourceDetails') or {}
276+
ebs_config = (current_details.get('ebsVolume') or {}).get('configuration') or {}
277+
if ebs_config.get('attachmentState') is not None:
278+
formatted_response['attachment_state'] = ebs_config['attachmentState']
279+
273280
if 'tags' in recommendation:
274281
formatted_response['tags'] = recommendation.get('tags')
275282

‎src/billing-cost-management-mcp-server/awslabs/billing_cost_management_mcp_server/tools/cost_optimization_hub_tools.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,10 @@ def _validate_order_by(
603603
604604
Cost Optimization Hub provides recommendations across multiple AWS services, including:
605605
- EC2 instances (right-sizing, Graviton migration)
606-
- EBS volumes (unused volumes, IOPS optimization)
606+
- EBS volumes (Delete covers both idle attached volumes and unattached ones: a
607+
recommended_resource_summary starting "Detach volume from instance" marks an attached
608+
volume, and get_recommendation returns attachment_state "Attached" or "Unattached" as of
609+
last_refresh_timestamp), IOPS/type optimization
607610
- RDS instances (right-sizing, engine optimization)
608611
- Lambda functions (memory size optimization)
609612
- SP/RI

‎src/billing-cost-management-mcp-server/tests/tools/test_cost_optimization_hub_helpers.py‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,63 @@ async def test_includes_current_and_recommended_resource_details(
364364
rec = result['data']
365365
assert rec['current_resource_details'] == current_details
366366
assert rec['recommended_resource_details'] == recommended_details
367+
# Not an EBS volume, so there is no attachment state to surface.
368+
assert 'attachment_state' not in rec
369+
370+
@pytest.mark.parametrize(
371+
'action_type, state',
372+
[('Delete', 'Unattached'), ('Rightsize', 'Attached'), ('Upgrade', 'Attached')],
373+
)
374+
async def test_surfaces_ebs_attachment_state(
375+
self, mock_context, mock_coh_client, action_type, state
376+
):
377+
"""Test get_recommendation surfaces an EBS volume's attachmentState on any action type."""
378+
current_details = {
379+
'ebsVolume': {
380+
'configuration': {
381+
'storage': {'type': 'gp3', 'sizeInGb': 100.0},
382+
'attachmentState': state,
383+
}
384+
}
385+
}
386+
mock_coh_client.get_recommendation.return_value = {
387+
'recommendationId': 'rec-ebs',
388+
'resourceId': 'vol-0123456789abcdef0',
389+
'currentResourceType': 'EbsVolume',
390+
'actionType': action_type,
391+
'currentResourceDetails': current_details,
392+
}
393+
394+
result = await get_recommendation(mock_context, mock_coh_client, 'rec-ebs')
395+
396+
rec = result['data']
397+
assert rec['attachment_state'] == state
398+
# The nested details are passed through unchanged.
399+
assert rec['current_resource_details'] == current_details
400+
401+
@pytest.mark.parametrize(
402+
'current_details',
403+
[
404+
{'ebsVolume': {'configuration': {'storage': {'type': 'gp2'}}}},
405+
{'ebsVolume': {}},
406+
{'ebsVolume': {'configuration': None}},
407+
{'ebsVolume': None},
408+
],
409+
)
410+
async def test_no_attachment_state_when_absent(
411+
self, mock_context, mock_coh_client, current_details
412+
):
413+
"""Test get_recommendation omits attachment_state when the API returns none."""
414+
mock_coh_client.get_recommendation.return_value = {
415+
'recommendationId': 'rec-ebs',
416+
'currentResourceType': 'EbsVolume',
417+
'currentResourceDetails': current_details,
418+
}
419+
420+
result = await get_recommendation(mock_context, mock_coh_client, 'rec-ebs')
421+
422+
assert result['status'] == 'success'
423+
assert 'attachment_state' not in result['data']
367424

368425

369426
def _account_groups(count):

0 commit comments

Comments
 (0)