Skip to content

Commit e75c872

Browse files
committed
🚧 Adding API Server (#281)
* 🚧 Adding API Server Signed-off-by: Muhammed Hussain Karimi <info@karimi.dev> * Merge branch 'develop' into add-api-server Signed-off-by: Muhammed Hussain Karimi <info@karimi.dev> * 📦 Fix Lock and init API Server Signed-off-by: Muhammed Hussain Karimi <info@karimi.dev>
1 parent 27e2128 commit e75c872

16 files changed

Lines changed: 1585 additions & 251 deletions

File tree

.pre-commit.sh

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ CHART_DIR="$ROOT_DIR/deploy/helm/rawfile-localpv"
77
CHART="$CHART_DIR/Chart.yaml"
88

99
help() {
10-
cat <<EOF
10+
cat <<EOF
1111
Usage: $(basename "$0") [OPTIONS]
1212
1313
Options:
@@ -21,24 +21,27 @@ EOF
2121

2222
DONE=
2323
while [ "$#" -gt 0 ]; do
24-
case $1 in
25-
-h|--help)
26-
break
27-
shift;;
28-
--version-sync)
29-
PY_VERSION=$(grep -Po '(?<=version = ")[^"]*' "$ROOT_DIR/pyproject.toml")
30-
sed -i'' -e "s/^version: .*/version: $PY_VERSION/" "$CHART"
31-
sed -i'' -e "s/^appVersion: .*/appVersion: $PY_VERSION/" "$CHART"
32-
DONE="yes"
33-
shift;;
34-
*)
35-
echo -e "Unknown parameter $1" >&2
36-
help
37-
exit 1
38-
esac
24+
case $1 in
25+
-h | --help)
26+
break
27+
shift
28+
;;
29+
--version-sync)
30+
PY_VERSION=$(grep -Pom1 '(?<=version = ")[^"]*' "$ROOT_DIR/pyproject.toml")
31+
sed -i'' -e "s/^version: .*/version: $PY_VERSION/" "$CHART"
32+
sed -i'' -e "s/^appVersion: .*/appVersion: $PY_VERSION/" "$CHART"
33+
DONE="yes"
34+
shift
35+
;;
36+
*)
37+
echo -e "Unknown parameter $1" >&2
38+
help
39+
exit 1
40+
;;
41+
esac
3942
done
4043

4144
if [ -z "${DONE:-}" ]; then
42-
help
43-
exit 0
45+
help
46+
exit 0
4447
fi

poetry.lock

Lines changed: 1362 additions & 225 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ tldextract = "^5.3.1"
2424
humanize = "^4.15.0"
2525
setproctitle = "^1.3.7"
2626
ipaddress = "^1.0.23"
27+
fastapi = {extras = ["standard"], version = "^0.128.0"}
28+
uvicorn = "^0.40.0"
2729

2830
[tool.poetry.scripts]
2931
rawfile = "rawfile.rawfile:cli"

rawfile/api_server/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from fastapi import FastAPI
2+
from api_server.v1.routes import router as v1_router
3+
4+
app = FastAPI(debug=False, title="RawFile LocalPV API Server")
5+
app.include_router(v1_router, prefix="/v1", tags=["v1"])
6+
7+
8+
def start_server(host, workers, port):
9+
import uvicorn
10+
11+
uvicorn.run("api_server:app", host=host, port=port, workers=workers)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .nodes import Node, NodeList, PoolStat, PoolsStat
2+
3+
__all__ = ["Node", "NodeList", "PoolStat", "PoolsStat"]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from typing import List, TypeAlias
2+
from pydantic import BaseModel, IPvAnyAddress
3+
4+
5+
class PoolStat(BaseModel):
6+
reserved_capacity: str
7+
path: str
8+
reserved_capacity_mode: str
9+
capacity: int
10+
11+
12+
PoolsStat: TypeAlias = dict[str, PoolStat]
13+
14+
15+
class Node(BaseModel):
16+
name: str
17+
ip: IPvAnyAddress
18+
pools_stat: PoolsStat
19+
20+
21+
NodeList: TypeAlias = List[Node]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from fastapi import APIRouter
2+
3+
router = APIRouter()
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from fastapi import APIRouter
2+
from internal_svc import SIGNATURE_METADATA
3+
from orchestrator.k8s import node_ip_mapping
4+
from api_server.v1.models import NodeList, Node, PoolStat, PoolsStat
5+
import grpc
6+
from internal import internal_pb2, internal_pb2_grpc
7+
from config import config
8+
import ipaddress
9+
import asyncio
10+
11+
router = APIRouter()
12+
13+
14+
def get_node_pool_stat(node_ip):
15+
node_ip_str = node_ip
16+
metadata = [(SIGNATURE_METADATA, config.csi_driver.internal_signature)]
17+
node_ip = ipaddress.ip_address(node_ip_str)
18+
if node_ip.version == 6:
19+
node_ip_str = f"[{node_ip_str}]"
20+
channel = grpc.insecure_channel(f"{node_ip_str}:{config.csi_driver.internal_port}")
21+
stub = internal_pb2_grpc.InternalStub(channel)
22+
return stub.GetPoolsStats(
23+
internal_pb2.GetPoolsStatsRequest(),
24+
metadata=metadata,
25+
timeout=15,
26+
)
27+
28+
29+
async def build_node(node_name: str, node_ip: str) -> Node:
30+
pool_stats = await get_node_pool_stat(node_ip)
31+
32+
return Node(
33+
name=node_name,
34+
ip=node_ip,
35+
pools_stat=PoolsStat(
36+
{
37+
name: PoolStat(
38+
reserved_capacity=pool.reserved_capacity,
39+
path=pool.path,
40+
reserved_capacity_mode=pool.reserved_capacity_mode,
41+
capacity=pool.capacity,
42+
)
43+
for name, pool in pool_stats.stats.items()
44+
}
45+
),
46+
)
47+
48+
49+
@router.get("/")
50+
async def get_nodes() -> NodeList:
51+
node_list = []
52+
node_ips = node_ip_mapping.get_all_nodes()
53+
tasks = [build_node(node_name, node_ip) for node_name, node_ip in node_ips.items()]
54+
node_list = await asyncio.gather(*tasks)
55+
return node_list

rawfile/config/model.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,22 @@ class GCCmd(BaseModel):
201201
)
202202

203203

204+
class APICmd(BaseModel):
205+
host: str = Field(
206+
default="0.0.0.0",
207+
description="Host address to bind the API server",
208+
)
209+
port: int = Field(
210+
default=8080,
211+
description="Port number to bind the API server",
212+
gt=0,
213+
le=65535,
214+
)
215+
workers: int = Field(
216+
default=4, description="Number of uvicorn (ASGI) workers for API server"
217+
)
218+
219+
204220
class RawFileCmd(
205221
BaseSettings, cli_parse_args=True, cli_exit_on_error=False, cli_kebab_case=True
206222
):
@@ -253,6 +269,9 @@ def settings_customise_sources(
253269
csi_driver: CliSubCommand[CSIDriverCmd] = Field(
254270
description="Starts CSI Driver",
255271
)
272+
api: CliSubCommand[APICmd] = Field(
273+
description="Starts API server",
274+
)
256275

257276
@model_validator(mode="after")
258277
def validate_ga(self):

rawfile/internal/internal_pb2.py

Lines changed: 15 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)