Skip to content

Commit

Permalink
feat(api): Add key/value store with namespace emulation
Browse files Browse the repository at this point in the history
For storing pipeline settings and variable and make them
a bit more stateful we need reliable key/value store.

Signed-off-by: Denys Fedoryshchenko <[email protected]>
  • Loading branch information
nuclearcat committed Oct 30, 2024
1 parent 7a99386 commit bc00ed7
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 2 deletions.
38 changes: 37 additions & 1 deletion api/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
from beanie import init_beanie
from fastapi_pagination.ext.motor import paginate
from motor import motor_asyncio
from kernelci.api.models import Hierarchy, Node, parse_node_obj, EventHistory
from redis import asyncio as aioredis
from kernelci.api.models import Hierarchy, Node, parse_node_obj

Check failure on line 14 in api/db.py

View workflow job for this annotation

GitHub Actions / Lint

Unable to import 'kernelci.api.models'
from .models import User, UserGroup



class Database:

Check warning on line 19 in api/db.py

View workflow job for this annotation

GitHub Actions / Lint

too many blank lines (3)
"""Database abstraction class
Expand Down Expand Up @@ -45,6 +47,8 @@ class Database:

def __init__(self, service='mongodb://db:27017', db_name='kernelci'):
self._motor = motor_asyncio.AsyncIOMotorClient(service)
# TBD: Make redis host configurable
self._redis = aioredis.from_url('redis://redis:6379')
self._db = self._motor[db_name]

async def initialize_beanie(self):
Expand All @@ -60,6 +64,38 @@ def _get_collection(self, model):
col = self.COLLECTIONS[model]
return self._db[col]

async def get_kv(self, namespace, key):
"""
Get value from redis key-value store
Create a keyname by concatenating namespace and key
"""
keyname = f"{namespace}:{key}"
return await self._redis.get(keyname)

async def set_kv(self, namespace, key, value):
"""
Set value in redis key-value store
Create a keyname by concatenating namespace and key
"""
keyname = f"{namespace}:{key}"
return await self._redis.set(keyname, value)

async def del_kv(self, namespace, key):
"""
Delete key from redis key-value store
Create a keyname by concatenating namespace and key
"""
keyname = f"{namespace}:{key}"
return await self._redis.delete(keyname)

async def exists_kv(self, namespace, key):
"""
Check if key exists in redis key-value store
Create a keyname by concatenating namespace and key
"""
keyname = f"{namespace}:{key}"
return await self._redis.exists(keyname)

async def create_indexes(self):
"""Create indexes for models"""
for model in self.COLLECTIONS:
Expand Down
44 changes: 43 additions & 1 deletion api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
Request,
Form,
Header,
Query
Query,
Body,
)
from fastapi.responses import JSONResponse, PlainTextResponse, FileResponse
from fastapi.security import OAuth2PasswordRequestForm
Expand Down Expand Up @@ -780,6 +781,47 @@ async def put_nodes(
return obj_list


# -----------------------------------------------------------------------------
# Key/Value namespace enabled store
@app.get('/kv/{namespace}/{key}', response_model=Union[str, None])
async def get_kv(namespace: str, key: str,
user: User = Depends(get_current_user)):

"""Get a key value pair from the store"""
metrics.add('http_requests_total', 1)
return await db.get_kv(namespace, key)


@app.post('/kv/{namespace}/{key}', response_model=Optional[str])
async def post_kv(namespace: str, key: str,
value: Optional[str] = Body(default=None),
user: User = Depends(get_current_user)):
"""Set a key-value pair in the store
namespace and key are part of the URL
value is part of the request body.
If value is not provided, we need to call delete_kv to remove the key.
"""
metrics.add('http_requests_total', 1)
if not value:
await db.del_kv(namespace, key)
return "OK"
ret = await db.set_kv(namespace, key, value)
if ret:
return "OK"
raise HTTPException(status_code=500, detail="Failed to set key-value pair")


# Delete a key-value pair from the store
@app.delete('/kv/{namespace}/{key}', response_model=Optional[str])
async def delete_kv(namespace: str, key: str,
user: User = Depends(get_current_user)):
"""Delete a key-value pair from the store"""
metrics.add('http_requests_total', 1)
await db.del_kv(namespace, key)
response = "Key-value pair deleted successfully"
return response


# -----------------------------------------------------------------------------
# Pub/Sub

Expand Down

0 comments on commit bc00ed7

Please sign in to comment.