Skip to content

Commit 8407ea4

Browse files
S0okJuhalucinor
authored andcommitted
feat: Add get_attachment_details tool (#75)
1 parent d3fa146 commit 8407ea4

File tree

3 files changed

+126
-3
lines changed

3 files changed

+126
-3
lines changed

src/openstack_mcp_server/tools/block_storage_tools.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from .base import get_openstack_conn
44
from .response.block_storage import (
5+
Attachment,
6+
ConnectionInfo,
57
Volume,
68
VolumeAttachment,
79
)
@@ -22,6 +24,8 @@ def register_tools(self, mcp: FastMCP):
2224
mcp.tool()(self.delete_volume)
2325
mcp.tool()(self.extend_volume)
2426

27+
mcp.tool()(self.get_attachment_details)
28+
2529
def get_volumes(self) -> list[Volume]:
2630
"""
2731
Get the list of Block Storage volumes.
@@ -183,3 +187,41 @@ def extend_volume(self, volume_id: str, new_size: int) -> None:
183187
conn = get_openstack_conn()
184188

185189
conn.block_storage.extend_volume(volume_id, new_size)
190+
191+
def get_attachment_details(self, attachment_id: str) -> Attachment:
192+
"""
193+
Get detailed information about a specific attachment.
194+
195+
:param attachment_id: The ID of the attachment to get details for
196+
:return: An Attachment object with detailed information
197+
"""
198+
conn = get_openstack_conn()
199+
200+
attachment = conn.block_storage.get_attachment(attachment_id)
201+
202+
# NOTE: We exclude the auth_* fields for security reasons
203+
connection_info = attachment.connection_info
204+
filtered_connection_info = ConnectionInfo(
205+
access_mode=connection_info.get("access_mode"),
206+
cacheable=connection_info.get("cacheable"),
207+
driver_volume_type=connection_info.get("driver_volume_type"),
208+
encrypted=connection_info.get("encrypted"),
209+
qos_specs=connection_info.get("qos_specs"),
210+
target_discovered=connection_info.get("target_discovered"),
211+
target_iqn=connection_info.get("target_iqn"),
212+
target_lun=connection_info.get("target_lun"),
213+
target_portal=connection_info.get("target_portal"),
214+
)
215+
216+
params = {
217+
"id": attachment.id,
218+
"instance": attachment.instance,
219+
"volume_id": attachment.volume_id,
220+
"attached_at": attachment.attached_at,
221+
"detached_at": attachment.detached_at,
222+
"attach_mode": attachment.attach_mode,
223+
"connection_info": filtered_connection_info,
224+
"connector": attachment.connector,
225+
}
226+
227+
return Attachment(**params)

src/openstack_mcp_server/tools/response/block_storage.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,26 @@ class Volume(BaseModel):
1919
is_encrypted: bool | None = None
2020
description: str | None = None
2121
attachments: list[VolumeAttachment] = []
22+
23+
24+
class ConnectionInfo(BaseModel):
25+
access_mode: str | None = None
26+
cacheable: bool | None = None
27+
driver_volume_type: str | None = None
28+
encrypted: bool | None = None
29+
qos_specs: str | None = None
30+
target_discovered: bool | None = None
31+
target_iqn: str | None = None
32+
target_lun: int | None = None
33+
target_portal: str | None = None
34+
35+
36+
class Attachment(BaseModel):
37+
id: str
38+
instance: str
39+
volume_id: str
40+
attached_at: str | None = None
41+
detached_at: str | None = None
42+
attach_mode: str | None = None
43+
connection_info: ConnectionInfo | None = None
44+
connector: str | None = None

tests/tools/test_block_storage_tools.py

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
from openstack_mcp_server.tools.block_storage_tools import BlockStorageTools
66
from openstack_mcp_server.tools.response.block_storage import (
7+
Attachment,
8+
ConnectionInfo,
79
Volume,
810
VolumeAttachment,
911
)
@@ -613,9 +615,6 @@ def test_register_tools(self):
613615
block_storage_tools = BlockStorageTools()
614616
block_storage_tools.register_tools(mock_mcp)
615617

616-
# Verify mcp.tool() was called for each method
617-
assert mock_mcp.tool.call_count == 5
618-
619618
# Verify all methods were registered
620619
registered_methods = [
621620
call[0][0] for call in mock_tool_decorator.call_args_list
@@ -683,3 +682,62 @@ def test_all_block_storage_methods_have_docstrings(self):
683682
assert len(docstring.strip()) > 0, (
684683
f"{method_name} docstring should not be empty"
685684
)
685+
686+
def test_get_attachment_details(
687+
self, mock_get_openstack_conn_block_storage
688+
):
689+
"""Test getting attachment details."""
690+
691+
# Set up the attachment mock object
692+
mock_attachment = Mock()
693+
mock_attachment.id = "attach-123"
694+
mock_attachment.instance = "server-123"
695+
mock_attachment.volume_id = "vol-123"
696+
mock_attachment.attached_at = "2024-01-01T12:00:00Z"
697+
mock_attachment.detached_at = None
698+
mock_attachment.attach_mode = "attach"
699+
mock_attachment.connection_info = {
700+
"access_mode": "rw",
701+
"cacheable": True,
702+
"driver_volume_type": "iscsi",
703+
"encrypted": False,
704+
"qos_specs": None,
705+
"target_discovered": True,
706+
"target_iqn": "iqn.2024-01-01.com.example:volume-123",
707+
"target_lun": 0,
708+
"target_portal": "192.168.1.100:3260",
709+
}
710+
mock_attachment.connector = "connector-123"
711+
712+
# Configure the mock block_storage.get_attachment()
713+
mock_conn = mock_get_openstack_conn_block_storage
714+
mock_conn.block_storage.get_attachment.return_value = mock_attachment
715+
716+
block_storage_tools = BlockStorageTools()
717+
result = block_storage_tools.get_attachment_details("attach-123")
718+
719+
# Verify the result
720+
assert isinstance(result, Attachment)
721+
assert result.id == "attach-123"
722+
assert result.instance == "server-123"
723+
assert result.attached_at == "2024-01-01T12:00:00Z"
724+
assert result.detached_at is None
725+
assert result.attach_mode == "attach"
726+
assert result.connection_info == ConnectionInfo(
727+
access_mode="rw",
728+
cacheable=True,
729+
driver_volume_type="iscsi",
730+
encrypted=False,
731+
qos_specs=None,
732+
target_discovered=True,
733+
target_iqn="iqn.2024-01-01.com.example:volume-123",
734+
target_lun=0,
735+
target_portal="192.168.1.100:3260",
736+
)
737+
assert result.connector == "connector-123"
738+
assert result.volume_id == "vol-123"
739+
740+
# Verify the mock calls
741+
mock_conn.block_storage.get_attachment.assert_called_once_with(
742+
"attach-123"
743+
)

0 commit comments

Comments
 (0)