Skip to content

Commit 4d3aa1a

Browse files
committed
chore(identity): Remove unnecessary variable(#31)
- Delete unnecessary variable in test code.
2 parents 8a5cf10 + efd7095 commit 4d3aa1a

File tree

11 files changed

+1330
-131
lines changed

11 files changed

+1330
-131
lines changed

.github/workflows/ci.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches: [ develop ]
6+
push:
7+
branches: [ develop ]
8+
9+
jobs:
10+
lint-and-test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Install uv
18+
uses: astral-sh/setup-uv@v3
19+
20+
- name: Set up Python
21+
run: uv python install
22+
23+
- name: Install dependencies
24+
run: uv sync --all-groups
25+
26+
- name: Run ruff check
27+
run: uv run ruff check
28+
29+
- name: Run ruff format check
30+
run: uv run ruff format --check .
31+
32+
- name: Run tests with pytest
33+
run: uv run pytest
34+
35+
36+
test-matrix:
37+
runs-on: ubuntu-latest
38+
strategy:
39+
matrix:
40+
python-version: ["3.10", "3.11", "3.12", "3.13"]
41+
42+
steps:
43+
- name: Checkout code
44+
uses: actions/checkout@v4
45+
46+
- name: Install uv
47+
uses: astral-sh/setup-uv@v3
48+
49+
- name: Set up Python ${{ matrix.python-version }}
50+
run: uv python install ${{ matrix.python-version }}
51+
52+
- name: Install dependencies
53+
run: uv sync --all-groups
54+
55+
- name: Run tests
56+
run: uv run pytest

src/openstack_mcp_server/tools/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ def register_tool(mcp: FastMCP):
66
Register Openstack MCP tools.
77
"""
88

9+
from .block_storage_tools import BlockStorageTools
910
from .compute_tools import ComputeTools
1011
from .identity_tools import IdentityTools
1112
from .image_tools import ImageTools
@@ -15,3 +16,4 @@ def register_tool(mcp: FastMCP):
1516
ImageTools().register_tools(mcp)
1617
IdentityTools().register_tools(mcp)
1718
NetworkTools().register_tools(mcp)
19+
BlockStorageTools().register_tools(mcp)
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
from fastmcp import FastMCP
2+
3+
from .base import get_openstack_conn
4+
from .response.block_storage import (
5+
Volume,
6+
VolumeAttachment,
7+
)
8+
9+
10+
class BlockStorageTools:
11+
"""
12+
A class to encapsulate Block Storage-related tools and utilities.
13+
"""
14+
15+
def register_tools(self, mcp: FastMCP):
16+
"""
17+
Register Block Storage-related tools with the FastMCP instance.
18+
"""
19+
mcp.tool()(self.get_volumes)
20+
mcp.tool()(self.get_volume_details)
21+
mcp.tool()(self.create_volume)
22+
mcp.tool()(self.delete_volume)
23+
mcp.tool()(self.extend_volume)
24+
25+
def get_volumes(self) -> list[Volume]:
26+
"""
27+
Get the list of Block Storage volumes.
28+
29+
:return: A list of Volume objects representing the volumes.
30+
"""
31+
conn = get_openstack_conn()
32+
33+
# List the volumes
34+
volume_list = []
35+
for volume in conn.block_storage.volumes():
36+
attachments = []
37+
for attachment in volume.attachments or []:
38+
attachments.append(
39+
VolumeAttachment(
40+
server_id=attachment.get("server_id"),
41+
device=attachment.get("device"),
42+
attachment_id=attachment.get("id"),
43+
),
44+
)
45+
46+
volume_list.append(
47+
Volume(
48+
id=volume.id,
49+
name=volume.name,
50+
status=volume.status,
51+
size=volume.size,
52+
volume_type=volume.volume_type,
53+
availability_zone=volume.availability_zone,
54+
created_at=str(volume.created_at)
55+
if volume.created_at
56+
else None,
57+
is_bootable=volume.is_bootable,
58+
is_encrypted=volume.is_encrypted,
59+
description=volume.description,
60+
attachments=attachments,
61+
),
62+
)
63+
64+
return volume_list
65+
66+
def get_volume_details(self, volume_id: str) -> Volume:
67+
"""
68+
Get detailed information about a specific volume.
69+
70+
:param volume_id: The ID of the volume to get details for
71+
:return: A Volume object with detailed information
72+
"""
73+
conn = get_openstack_conn()
74+
75+
volume = conn.block_storage.get_volume(volume_id)
76+
77+
attachments = []
78+
for attachment in volume.attachments or []:
79+
attachments.append(
80+
VolumeAttachment(
81+
server_id=attachment.get("server_id"),
82+
device=attachment.get("device"),
83+
attachment_id=attachment.get("id"),
84+
),
85+
)
86+
87+
return Volume(
88+
id=volume.id,
89+
name=volume.name,
90+
status=volume.status,
91+
size=volume.size,
92+
volume_type=volume.volume_type,
93+
availability_zone=volume.availability_zone,
94+
created_at=str(volume.created_at),
95+
is_bootable=volume.is_bootable,
96+
is_encrypted=volume.is_encrypted,
97+
description=volume.description,
98+
attachments=attachments,
99+
)
100+
101+
def create_volume(
102+
self,
103+
name: str,
104+
size: int,
105+
description: str | None = None,
106+
volume_type: str | None = None,
107+
availability_zone: str | None = None,
108+
bootable: bool | None = None,
109+
image: str | None = None,
110+
) -> Volume:
111+
"""
112+
Create a new volume.
113+
114+
:param name: Name for the new volume
115+
:param size: Size of the volume in GB
116+
:param description: Optional description for the volume
117+
:param volume_type: Optional volume type
118+
:param availability_zone: Optional availability zone
119+
:param bootable: Optional flag to make the volume bootable
120+
:param image: Optional Image name, ID or object from which to create
121+
:return: The created Volume object
122+
"""
123+
conn = get_openstack_conn()
124+
125+
volume_kwargs = {
126+
"name": name,
127+
}
128+
129+
if description is not None:
130+
volume_kwargs["description"] = description
131+
if volume_type is not None:
132+
volume_kwargs["volume_type"] = volume_type
133+
if availability_zone is not None:
134+
volume_kwargs["availability_zone"] = availability_zone
135+
136+
volume = conn.block_storage.create_volume(
137+
size=size,
138+
image=image,
139+
bootable=bootable,
140+
**volume_kwargs,
141+
)
142+
143+
volume_obj = Volume(
144+
id=volume.id,
145+
name=volume.name,
146+
status=volume.status,
147+
size=volume.size,
148+
volume_type=volume.volume_type,
149+
availability_zone=volume.availability_zone,
150+
created_at=str(volume.created_at),
151+
is_bootable=volume.is_bootable,
152+
is_encrypted=volume.is_encrypted,
153+
description=volume.description,
154+
attachments=[],
155+
)
156+
157+
return volume_obj
158+
159+
def delete_volume(self, volume_id: str, force: bool = False) -> None:
160+
"""
161+
Delete a volume.
162+
163+
:param volume_id: The ID of the volume to delete
164+
:param force: Whether to force delete the volume
165+
:return: None
166+
"""
167+
conn = get_openstack_conn()
168+
169+
conn.block_storage.delete_volume(
170+
volume_id,
171+
force=force,
172+
ignore_missing=False,
173+
)
174+
175+
def extend_volume(self, volume_id: str, new_size: int) -> None:
176+
"""
177+
Extend a volume to a new size.
178+
179+
:param volume_id: The ID of the volume to extend
180+
:param new_size: The new size in GB (must be larger than current size)
181+
:return: None
182+
"""
183+
conn = get_openstack_conn()
184+
185+
conn.block_storage.extend_volume(volume_id, new_size)
Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
from typing import Any
2+
13
from fastmcp import FastMCP
24

3-
from openstack_mcp_server.tools.response.compute import Server
5+
from openstack_mcp_server.tools.response.compute import (
6+
Flavor,
7+
Server,
8+
)
49

510
from .base import get_openstack_conn
611

@@ -14,38 +19,86 @@ def register_tools(self, mcp: FastMCP):
1419
"""
1520
Register Compute-related tools with the FastMCP instance.
1621
"""
22+
mcp.tool()(self.get_servers)
23+
mcp.tool()(self.get_server)
24+
mcp.tool()(self.create_server)
25+
mcp.tool()(self.get_flavors)
1726

18-
mcp.tool()(self.get_compute_servers)
19-
mcp.tool()(self.get_compute_server)
20-
21-
def get_compute_servers(self) -> list[Server]:
27+
def get_servers(self) -> list[Server]:
2228
"""
23-
Get the list of Compute servers by invoking the registered tool.
29+
Get the list of Compute servers.
2430
25-
:return: A list of Server objects representing the Compute servers.
31+
:return: A list of Server objects.
2632
"""
27-
# Initialize connection
2833
conn = get_openstack_conn()
29-
30-
# List the servers
3134
server_list = []
3235
for server in conn.compute.servers():
33-
server_list.append(
34-
Server(name=server.name, id=server.id, status=server.status),
35-
)
36+
server_list.append(Server(**server))
3637

3738
return server_list
3839

39-
def get_compute_server(self, id: str) -> Server:
40+
def get_server(self, id: str) -> Server:
4041
"""
41-
Get a specific Compute server by invoking the registered tool.
42+
Get a specific Compute server.
4243
4344
:param id: The ID of the server to retrieve.
44-
:return: A Server object representing the Compute server.
45+
:return: A Server object.
4546
"""
46-
# Initialize connection
4747
conn = get_openstack_conn()
48-
49-
# Get a specific server (for example, the first one)
5048
server = conn.compute.get_server(id)
51-
return Server(name=server.name, id=server.id, status=server.status)
49+
return Server(**server)
50+
51+
def create_server(
52+
self,
53+
name: str,
54+
image: str,
55+
flavor: int,
56+
network: str,
57+
key_name: str | None = None,
58+
security_groups: list[str] | None = None,
59+
user_data: str | None = None,
60+
) -> Server:
61+
"""
62+
Create a new Compute server.
63+
64+
:param name: The name of the server.
65+
:param image: The ID of the image to use.
66+
:param flavor: The ID of the flavor to use.
67+
:param network: The ID of the network to attach.
68+
:param key_name: The name of the key pair to use.
69+
:param security_groups: A list of security group names to attach.
70+
:param user_data: User data to pass to the server.
71+
:return: A Server object
72+
"""
73+
conn = get_openstack_conn()
74+
server_params: dict[str, Any] = {
75+
"name": name,
76+
"flavorRef": flavor,
77+
"imageRef": image,
78+
"networks": [{"uuid": network}],
79+
"key_name": key_name,
80+
"security_groups": security_groups,
81+
"user_data": user_data,
82+
}
83+
server_params = {
84+
k: v for k, v in server_params.items() if v is not None
85+
}
86+
87+
resp = conn.compute.create_server(**server_params)
88+
# NOTE: The create_server method returns a server object with minimal information.
89+
# To get the full server details, we need to fetch it again.
90+
server = conn.compute.get_server(resp.id)
91+
92+
return Server(**server)
93+
94+
def get_flavors(self) -> list[Flavor]:
95+
"""
96+
Get flavors (server hardware configurations).
97+
98+
:return: A list of Flavor objects.
99+
"""
100+
conn = get_openstack_conn()
101+
flavor_list = []
102+
for flavor in conn.compute.flavors():
103+
flavor_list.append(Flavor(**flavor))
104+
return flavor_list

0 commit comments

Comments
 (0)