Skip to content

Commit c1d8145

Browse files
committed
feat: get flavors
1 parent 9fe7365 commit c1d8145

File tree

3 files changed

+106
-13
lines changed

3 files changed

+106
-13
lines changed

src/openstack_mcp_server/tools/compute_tools.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
from fastmcp import FastMCP
44

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

710
from .base import get_openstack_conn
811

@@ -16,10 +19,10 @@ def register_tools(self, mcp: FastMCP):
1619
"""
1720
Register Compute-related tools with the FastMCP instance.
1821
"""
19-
2022
mcp.tool()(self.get_servers)
2123
mcp.tool()(self.get_server)
2224
mcp.tool()(self.create_server)
25+
mcp.tool()(self.get_flavors)
2326

2427
def get_servers(self) -> list[Server]:
2528
"""
@@ -60,7 +63,7 @@ def create_server(
6063
6164
:param name: The name of the server.
6265
:param image: The ID of the image to use.
63-
:param flavor: The (integer) ID of the flavor to use.
66+
:param flavor: The ID of the flavor to use.
6467
:param network: The ID of the network to attach.
6568
:param key_name: The name of the key pair to use.
6669
:param security_groups: A list of security group names to attach.
@@ -87,3 +90,15 @@ def create_server(
8790
server = conn.compute.get_server(resp.id)
8891

8992
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

src/openstack_mcp_server/tools/response/compute.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from pydantic import BaseModel, ConfigDict, Field
22

33

4-
class Flavor(BaseModel):
4+
class ServerFlavor(BaseModel):
55
id: str | None = Field(default=None, exclude=True)
66
name: str = Field(validation_alias="original_name")
77
model_config = ConfigDict(validate_by_name=True)
88

99

10-
class Image(BaseModel):
10+
class ServerImage(BaseModel):
1111
id: str
1212

1313

@@ -27,8 +27,20 @@ class Server(BaseModel):
2727
id: str
2828
name: str
2929
status: str | None = None
30-
flavor: Flavor | None = None
31-
image: Image | None = None
30+
flavor: ServerFlavor | None = None
31+
image: ServerImage | None = None
3232
addresses: dict[str, list[ServerIp]] | None = None
3333
key_name: str | None = None
3434
security_groups: list[ServerSecurityGroup] | None = None
35+
36+
37+
class Flavor(BaseModel):
38+
id: str
39+
name: str
40+
vcpus: int
41+
ram: int
42+
disk: int
43+
swap: int | None = None
44+
is_public: bool = Field(validation_alias="os-flavor-access:is_public")
45+
46+
model_config = ConfigDict(validate_by_name=True)

tests/tools/test_compute_tools.py

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from openstack_mcp_server.tools.compute_tools import ComputeTools
44
from openstack_mcp_server.tools.response.compute import (
55
Flavor,
6-
Image,
76
Server,
7+
ServerFlavor,
8+
ServerImage,
89
ServerIp,
910
ServerSecurityGroup,
1011
)
@@ -79,8 +80,8 @@ def test_get_servers_success(self, mock_get_openstack_conn):
7980
id="434eb822-3fbd-44a1-a000-3b511ac9b516",
8081
name="web-server-01",
8182
status="ACTIVE",
82-
flavor=Flavor(id=None, name="m1.tiny"),
83-
image=Image(id="de527f30-d078-41f4-8f18-a23bf2d39366"),
83+
flavor=ServerFlavor(id=None, name="m1.tiny"),
84+
image=ServerImage(id="de527f30-d078-41f4-8f18-a23bf2d39366"),
8485
addresses={
8586
"private": [
8687
ServerIp(addr="192.168.1.10", version=4, type="fixed"),
@@ -93,8 +94,8 @@ def test_get_servers_success(self, mock_get_openstack_conn):
9394
id="ffd071fe-1334-45f6-8894-5b0bcac262a6",
9495
name="db-server-01",
9596
status="SHUTOFF",
96-
flavor=Flavor(id=None, name="m1.small"),
97-
image=Image(id="3d897e0e-4117-46bb-ae77-e734bb16a1ca"),
97+
flavor=ServerFlavor(id=None, name="m1.small"),
98+
image=ServerImage(id="3d897e0e-4117-46bb-ae77-e734bb16a1ca"),
9899
addresses={
99100
"net1": [
100101
ServerIp(addr="192.168.1.11", version=4, type="fixed"),
@@ -258,9 +259,10 @@ def test_register_tools(self):
258259
call(compute_tools.get_servers),
259260
call(compute_tools.get_server),
260261
call(compute_tools.create_server),
262+
call(compute_tools.get_flavors),
261263
],
262264
)
263-
assert mock_tool_decorator.call_count == 3
265+
assert mock_tool_decorator.call_count == 4
264266

265267
def test_compute_tools_instantiation(self):
266268
"""Test ComputeTools can be instantiated."""
@@ -279,3 +281,67 @@ def test_get_servers_docstring(self):
279281
assert docstring is not None
280282
assert "Get the list of Compute servers" in docstring
281283
assert "return" in docstring.lower() or "Return" in docstring
284+
285+
def test_get_flavors_success(self, mock_get_openstack_conn):
286+
"""Test getting flavors successfully."""
287+
mock_conn = mock_get_openstack_conn
288+
289+
# Create mock flavor objects
290+
mock_flavor1 = {
291+
"id": "1",
292+
"name": "m1.tiny",
293+
"vcpus": 1,
294+
"ram": 512,
295+
"disk": 1,
296+
"swap": 0,
297+
"os-flavor-access:is_public": True,
298+
}
299+
300+
mock_flavor2 = {
301+
"id": "2",
302+
"name": "m1.small",
303+
"vcpus": 2,
304+
"ram": 2048,
305+
"disk": 20,
306+
"swap": 0,
307+
"os-flavor-access:is_public": True,
308+
}
309+
310+
mock_conn.compute.flavors.return_value = [mock_flavor1, mock_flavor2]
311+
312+
compute_tools = ComputeTools()
313+
result = compute_tools.get_flavors()
314+
315+
expected_output = [
316+
Flavor(
317+
id="1",
318+
name="m1.tiny",
319+
vcpus=1,
320+
ram=512,
321+
disk=1,
322+
swap=0,
323+
is_public=True,
324+
),
325+
Flavor(
326+
id="2",
327+
name="m1.small",
328+
vcpus=2,
329+
ram=2048,
330+
disk=20,
331+
swap=0,
332+
is_public=True,
333+
),
334+
]
335+
assert result == expected_output
336+
mock_conn.compute.flavors.assert_called_once()
337+
338+
def test_get_flavors_empty_list(self, mock_get_openstack_conn):
339+
"""Test getting flavors when no flavors exist."""
340+
mock_conn = mock_get_openstack_conn
341+
mock_conn.compute.flavors.return_value = []
342+
343+
compute_tools = ComputeTools()
344+
result = compute_tools.get_flavors()
345+
346+
assert result == []
347+
mock_conn.compute.flavors.assert_called_once()

0 commit comments

Comments
 (0)