georgs11 #48

Merged
Ghost merged 5 commits from georgs11 into main 2024-01-11 15:12:06 +00:00
20 changed files with 240 additions and 216 deletions

View File

@@ -73,12 +73,11 @@ sudo echo "experimental-features = nix-command flakes" > '/etc/nix/nix.conf'
- To start the backend server, execute:
```bash
clan webui --reload --no-open --log-level debug --populate
clan webui --reload --no-open --log-level debug --populate --emulate
```
- The server will automatically restart if any Python files change.
- The `--populate` flag will automatically populate the database with dummy data
- To emulate some distributed system behavior run `python3 tests/emulate_fastapi.py`
- The `--emulate` flag will automatically run servers the database with dummy data for the fronted to communicate with (ap, dlg, c1 and c2)
8. **Build the Frontend**:

View File

@@ -28,6 +28,12 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
help="Populate the database with dummy data",
default=False,
)
parser.add_argument(
"--emulate",
action="store_true",
help="Emulate two entities c1 and c2 + dlg and ap",
default=False,
)
parser.add_argument(
"--no-open", action="store_true", help="Don't open the browser", default=False
)

View File

@@ -11,6 +11,12 @@ class Status(Enum):
UNKNOWN = "unknown"
class Roles(Enum):
PROSUMER = "service_prosumer"
AP = "AP"
DLG = "DLG"
class Machine(BaseModel):
name: str
status: Status
@@ -25,6 +31,10 @@ class EntityBase(BaseModel):
did: str = Field(..., example="did:sov:test:1234")
name: str = Field(..., example="C1")
ip: str = Field(..., example="127.0.0.1")
network: str = Field(..., example="255.255.0.0")
role: Roles = Field(
..., example=Roles("service_prosumer")
) # roles are needed for UI to show the correct view
visible: bool = Field(..., example=True)
other: dict = Field(
...,

View File

@@ -126,6 +126,35 @@ def start_server(args: argparse.Namespace) -> None:
cmd = ["pytest", "-s", str(test_db_api)]
subprocess.run(cmd, check=True)
if args.emulate:
import multiprocessing as mp
from config import host, port_ap, port_client_base, port_dlg
from emulate_fastapi import app_ap, app_c1, app_c2, app_dlg, get_health
app_ports = [
(app_dlg, port_dlg),
(app_ap, port_ap),
(app_c1, port_client_base),
(app_c2, port_client_base + 1),
]
urls = list()
# start servers as processes (dlg, ap, c1 and c2 for tests)
for app, port in app_ports:
proc = mp.Process(
target=uvicorn.run,
args=(app,),
kwargs={"host": host, "port": port, "log_level": "info"},
daemon=True,
)
proc.start()
urls.append(f"http://{host}:{port}")
# check server health
for url in urls:
res = get_health(url=url + "/health")
if res is None:
raise Exception(f"Couldn't reach {url} after starting server")
uvicorn.run(
"clan_cli.webui.app:app",
host=args.host,

View File

@@ -1,6 +1,7 @@
from sqlalchemy import JSON, Boolean, Column, ForeignKey, Integer, String, Text
from sqlalchemy import JSON, Boolean, Column, Enum, ForeignKey, Integer, String, Text
from sqlalchemy.orm import relationship
from .schemas import Roles
from .sql_db import Base
# Relationsship example
@@ -14,12 +15,15 @@ class Entity(Base):
did = Column(String, primary_key=True, index=True)
name = Column(String, index=True, unique=True)
ip = Column(String, index=True)
network = Column(String, index=True)
role = Column(Enum(Roles), index=True, nullable=False) # type: ignore
# role = Column(String, index=True, nullable=False)
attached = Column(Boolean, index=True)
visible = Column(Boolean, index=True)
stop_health_task = Column(Boolean)
## Non queryable body ##
# In here we deposit: Network, Roles, Visible, etc.
# In here we deposit: Not yet defined stuff
other = Column(JSON)
## Relations ##

4
pkgs/clan-cli/config.py Normal file
View File

@@ -0,0 +1,4 @@
host = "127.0.0.1"
port_dlg = 6000
port_ap = 6600
port_client_base = 7000

View File

@@ -1,21 +1,60 @@
import sys
import time
import urllib
import uvicorn
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
app_dlg = FastAPI()
app_ap = FastAPI()
app_c1 = FastAPI()
app_c2 = FastAPI()
# bash tests: curl localhost:8000/ap_list_of_services
# bash tests: curl localhost:6600/ap_list_of_services
# curl localhost:7001/consume_service_from_other_entity
@app.get("/health")
async def healthcheck() -> str:
#### HEALTH
@app_c1.get("/health")
async def healthcheck_c1() -> str:
return "200 OK"
@app.get("/consume_service_from_other_entity", response_class=HTMLResponse)
async def consume_service_from_other_entity() -> HTMLResponse:
@app_c2.get("/health")
async def healthcheck_c2() -> str:
return "200 OK"
@app_dlg.get("/health")
async def healthcheck_dlg() -> str:
return "200 OK"
@app_ap.get("/health")
async def healthcheck_ap() -> str:
return "200 OK"
def get_health(*, url: str, max_retries: int = 20, delay: float = 0.2) -> str | None:
for attempt in range(max_retries):
try:
with urllib.request.urlopen(url) as response:
return response.read()
except urllib.error.URLError as e:
print(f"Attempt {attempt + 1} failed: {e.reason}", file=sys.stderr)
time.sleep(delay)
return None
#### CONSUME SERVICE
# TODO send_msg???
@app_c1.get("/consume_service_from_other_entity", response_class=HTMLResponse)
async def consume_service_from_other_entity_c1() -> HTMLResponse:
html_content = """
<html>
<body>
@@ -27,7 +66,23 @@ async def consume_service_from_other_entity() -> HTMLResponse:
return HTMLResponse(content=html_content, status_code=200)
@app.get("/ap_list_of_services", response_class=HTMLResponse)
@app_c2.get("/consume_service_from_other_entity", response_class=HTMLResponse)
async def consume_service_from_other_entity_c2() -> HTMLResponse:
html_content = """
<html>
<body>
<div style="width:480px"><iframe allow="fullscreen" frameBorder="0" height="270" src="https://giphy.com/embed/IOWD3uknMxYyh7CsgN/video" width="480"></iframe></div>
</body>
</html>
"""
time.sleep(3)
return HTMLResponse(content=html_content, status_code=200)
#### ap_list_of_services
@app_ap.get("/ap_list_of_services", response_class=HTMLResponse)
async def ap_list_of_services() -> HTMLResponse:
html_content = b"""HTTP/1.1 200 OK\r\n\r\n[[
{
@@ -114,7 +169,7 @@ async def ap_list_of_services() -> HTMLResponse:
return HTMLResponse(content=html_content, status_code=200)
@app.get("/dlg_list_of_did_resolutions", response_class=HTMLResponse)
@app_dlg.get("/dlg_list_of_did_resolutions", response_class=HTMLResponse)
async def dlg_list_of_did_resolutions() -> HTMLResponse:
html_content = b"""HTTP/1.1 200 OK\r\n\r\n
[
@@ -148,6 +203,3 @@ async def dlg_list_of_did_resolutions() -> HTMLResponse:
}
]"""
return HTMLResponse(content=html_content, status_code=200)
uvicorn.run(app, host="localhost", port=8000)

View File

@@ -11,6 +11,7 @@ from fastapi.testclient import TestClient
from openapi_client import ApiClient, Configuration
from ports import PortFunction
import config
from clan_cli.webui.app import app
@@ -31,10 +32,11 @@ def get_health(*, url: str, max_retries: int = 20, delay: float = 0.2) -> str |
# Pytest fixture to run the server in a separate process
# server
@pytest.fixture(scope="session")
def server_url(unused_tcp_port: PortFunction) -> Generator[str, None, None]:
port = unused_tcp_port()
host = "127.0.0.1"
host = config.host
proc = Process(
target=uvicorn.run,
args=(app,),

View File

@@ -43,6 +43,7 @@ from openapi_client.models.eventmessage_create import EventmessageCreate
from openapi_client.models.http_validation_error import HTTPValidationError
from openapi_client.models.machine import Machine
from openapi_client.models.resolution import Resolution
from openapi_client.models.roles import Roles
from openapi_client.models.service import Service
from openapi_client.models.service_create import ServiceCreate
from openapi_client.models.status import Status

View File

@@ -3,10 +3,12 @@
## Properties
| Name | Type | Description | Notes |
| -------------------- | ---------- | ----------- | ----- |
| -------------------- | --------------------- | ----------- | ----- |
| **did** | **str** | |
| **name** | **str** | |
| **ip** | **str** | |
| **network** | **str** | |
| **role** | [**Roles**](Roles.md) | |
| **visible** | **bool** | |
| **other** | **object** | |
| **attached** | **bool** | |

View File

@@ -3,10 +3,12 @@
## Properties
| Name | Type | Description | Notes |
| ----------- | ---------- | ----------- | ----- |
| ----------- | --------------------- | ----------- | ----- |
| **did** | **str** | |
| **name** | **str** | |
| **ip** | **str** | |
| **network** | **str** | |
| **role** | [**Roles**](Roles.md) | |
| **visible** | **bool** | |
| **other** | **object** | |

View File

@@ -0,0 +1,10 @@
# Roles
An enumeration.
## Properties
| Name | Type | Description | Notes |
| ---- | ---- | ----------- | ----- |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -21,6 +21,7 @@ from openapi_client.models.eventmessage_create import EventmessageCreate
from openapi_client.models.http_validation_error import HTTPValidationError
from openapi_client.models.machine import Machine
from openapi_client.models.resolution import Resolution
from openapi_client.models.roles import Roles
from openapi_client.models.service import Service
from openapi_client.models.service_create import ServiceCreate
from openapi_client.models.status import Status

View File

@@ -20,6 +20,7 @@ import json
from typing import Any, Dict
from pydantic import BaseModel, Field, StrictBool, StrictStr
from openapi_client.models.roles import Roles
class Entity(BaseModel):
"""
@@ -28,11 +29,13 @@ class Entity(BaseModel):
did: StrictStr = Field(...)
name: StrictStr = Field(...)
ip: StrictStr = Field(...)
network: StrictStr = Field(...)
role: Roles = Field(...)
visible: StrictBool = Field(...)
other: Dict[str, Any] = Field(...)
attached: StrictBool = Field(...)
stop_health_task: StrictBool = Field(...)
__properties = ["did", "name", "ip", "visible", "other", "attached", "stop_health_task"]
__properties = ["did", "name", "ip", "network", "role", "visible", "other", "attached", "stop_health_task"]
class Config:
"""Pydantic configuration"""
@@ -73,6 +76,8 @@ class Entity(BaseModel):
"did": obj.get("did"),
"name": obj.get("name"),
"ip": obj.get("ip"),
"network": obj.get("network"),
"role": obj.get("role"),
"visible": obj.get("visible"),
"other": obj.get("other"),
"attached": obj.get("attached"),

View File

@@ -20,6 +20,7 @@ import json
from typing import Any, Dict
from pydantic import BaseModel, Field, StrictBool, StrictStr
from openapi_client.models.roles import Roles
class EntityCreate(BaseModel):
"""
@@ -28,9 +29,11 @@ class EntityCreate(BaseModel):
did: StrictStr = Field(...)
name: StrictStr = Field(...)
ip: StrictStr = Field(...)
network: StrictStr = Field(...)
role: Roles = Field(...)
visible: StrictBool = Field(...)
other: Dict[str, Any] = Field(...)
__properties = ["did", "name", "ip", "visible", "other"]
__properties = ["did", "name", "ip", "network", "role", "visible", "other"]
class Config:
"""Pydantic configuration"""
@@ -71,6 +74,8 @@ class EntityCreate(BaseModel):
"did": obj.get("did"),
"name": obj.get("name"),
"ip": obj.get("ip"),
"network": obj.get("network"),
"role": obj.get("role"),
"visible": obj.get("visible"),
"other": obj.get("other")
})

View File

@@ -0,0 +1,41 @@
# coding: utf-8
"""
FastAPI
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
The version of the OpenAPI document: 0.1.0
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
import json
import pprint
import re # noqa: F401
from aenum import Enum, no_arg
class Roles(str, Enum):
"""
An enumeration.
"""
"""
allowed enum values
"""
SERVICE_PROSUMER = 'service_prosumer'
AP = 'AP'
DLG = 'DLG'
@classmethod
def from_json(cls, json_str: str) -> Roles:
"""Create an instance of Roles from a JSON string"""
return Roles(json.loads(json_str))

View File

@@ -1,67 +0,0 @@
# coding: utf-8
"""
FastAPI
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
The version of the OpenAPI document: 0.1.0
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
import unittest
import datetime
from openapi_client.models.eventmessage import Eventmessage # noqa: E501
class TestEventmessage(unittest.TestCase):
"""Eventmessage unit test stubs"""
def setUp(self):
pass
def tearDown(self):
pass
def make_instance(self, include_optional) -> Eventmessage:
"""Test Eventmessage
include_option is a boolean, when False only required
params are included, when True both required and
optional params are included """
# uncomment below to create an instance of `Eventmessage`
"""
model = Eventmessage() # noqa: E501
if include_optional:
return Eventmessage(
id = 123456,
timestamp = 1234123413,
group = 1,
group_id = 12345,
msg_type = 1,
src_did = 'did:sov:test:2234',
des_did = 'did:sov:test:1234',
msg = {optinal=values}
)
else:
return Eventmessage(
id = 123456,
timestamp = 1234123413,
group = 1,
group_id = 12345,
msg_type = 1,
src_did = 'did:sov:test:2234',
des_did = 'did:sov:test:1234',
msg = {optinal=values},
)
"""
def testEventmessage(self):
"""Test Eventmessage"""
# inst_req_only = self.make_instance(include_optional=False)
# inst_req_and_optional = self.make_instance(include_optional=True)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,67 +0,0 @@
# coding: utf-8
"""
FastAPI
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
The version of the OpenAPI document: 0.1.0
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
import unittest
import datetime
from openapi_client.models.eventmessage_create import EventmessageCreate # noqa: E501
class TestEventmessageCreate(unittest.TestCase):
"""EventmessageCreate unit test stubs"""
def setUp(self):
pass
def tearDown(self):
pass
def make_instance(self, include_optional) -> EventmessageCreate:
"""Test EventmessageCreate
include_option is a boolean, when False only required
params are included, when True both required and
optional params are included """
# uncomment below to create an instance of `EventmessageCreate`
"""
model = EventmessageCreate() # noqa: E501
if include_optional:
return EventmessageCreate(
id = 123456,
timestamp = 1234123413,
group = 1,
group_id = 12345,
msg_type = 1,
src_did = 'did:sov:test:2234',
des_did = 'did:sov:test:1234',
msg = {optinal=values}
)
else:
return EventmessageCreate(
id = 123456,
timestamp = 1234123413,
group = 1,
group_id = 12345,
msg_type = 1,
src_did = 'did:sov:test:2234',
des_did = 'did:sov:test:1234',
msg = {optinal=values},
)
"""
def testEventmessageCreate(self):
"""Test EventmessageCreate"""
# inst_req_only = self.make_instance(include_optional=False)
# inst_req_and_optional = self.make_instance(include_optional=True)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,45 +0,0 @@
# coding: utf-8
"""
FastAPI
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
The version of the OpenAPI document: 0.1.0
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
import unittest
from openapi_client.api.eventmessages_api import EventmessagesApi # noqa: E501
class TestEventmessagesApi(unittest.TestCase):
"""EventmessagesApi unit test stubs"""
def setUp(self) -> None:
self.api = EventmessagesApi() # noqa: E501
def tearDown(self) -> None:
pass
def test_create_eventmessage(self) -> None:
"""Test case for create_eventmessage
Create Eventmessage # noqa: E501
"""
pass
def test_get_all_eventmessages(self) -> None:
"""Test case for get_all_eventmessages
Get All Eventmessages # noqa: E501
"""
pass
if __name__ == '__main__':
unittest.main()

View File

@@ -13,13 +13,21 @@ from openapi_client.models import (
Eventmessage,
EventmessageCreate,
Machine,
Roles,
ServiceCreate,
Status,
)
import config
random.seed(42)
host = config.host
port_dlg = config.port_dlg
port_ap = config.port_ap
port_client_base = config.port_client_base
num_uuids = 100
uuids = [str(uuid.UUID(int=random.getrandbits(128))) for i in range(num_uuids)]
@@ -36,11 +44,33 @@ def create_entities(num: int = 10) -> list[EntityCreate]:
en = EntityCreate(
did=f"did:sov:test:12{i}",
name=f"C{i}",
ip=f"127.0.0.1:{7000+i}",
ip=f"{host}:{port_client_base+i}",
network="255.255.0.0",
role=Roles("service_prosumer"),
visible=True,
other={},
)
res.append(en)
dlg = EntityCreate(
did=f"did:sov:test:{port_dlg}",
name="DLG",
ip=f"{host}:{port_dlg}/health",
network="255.255.0.0",
role=Roles("DLG"),
visible=True,
other={},
)
res.append(dlg)
ap = EntityCreate(
did=f"did:sov:test:{port_ap}",
name="AP",
ip=f"{host}:{port_ap}/health",
network="255.255.0.0",
role=Roles("AP"),
visible=True,
other={},
)
res.append(ap)
return res