Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions hub_adapter/autostart.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,13 @@
get_core_client,
get_flame_hub_auth_flow,
get_node_id,
get_node_metadata_for_url,
get_node_type_cache,
get_registry_metadata_for_url,
get_settings,
get_ssl_context,
)
from hub_adapter.errors import KongConflictError, KongConnectError
from hub_adapter.middleware import log_event
from hub_adapter.routers.hub import (
_parse_query_params,
list_analysis_nodes,
)
from hub_adapter.routers.hub import _parse_query_params, list_analysis_nodes
from hub_adapter.routers.kong import (
create_and_connect_analysis_to_project,
delete_analysis,
Expand Down Expand Up @@ -117,7 +112,7 @@ async def auto_start_analyses(self) -> set | None:
)
return None

formatted_query_params = _parse_query_params(sort="-updated_at")
formatted_query_params = _parse_query_params(sort="-updated_at", include="analysis")

try:
analyses = await list_analysis_nodes(
Expand Down Expand Up @@ -377,16 +372,12 @@ async def send_start_request(self, analysis_props: dict, kong_token: str) -> tup
service=ServiceTag.AUTOSTART,
)

node_metadata = get_node_metadata_for_url(analysis_props["node_id"], core_client=self.core_client)
analysis_info = get_registry_metadata_for_url(node_metadata, core_client=self.core_client)

analysis_id = analysis_props["analysis_id"]
project_id = analysis_props["project_id"]

props = compile_analysis_pod_data(
analysis_id=analysis_id,
project_id=project_id,
compiled_info=analysis_info,
kong_token=kong_token,
)

Expand Down
6 changes: 0 additions & 6 deletions hub_adapter/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@
HOST = "host"
NODE = "node"
NODE_ID = "NODE_ID"
REGISTRY = "registry"
ACCOUNT_NAME = "account_name"
EXTERNAL_NAME = "external_name"
ACCOUNT_SECRET = "account_secret"
REGISTRY_PROJECT = "registry_project"
REGISTRY_PROJECT_ID = "registry_project_id"


class ServiceTag(StrEnum):
Expand Down
113 changes: 0 additions & 113 deletions hub_adapter/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,128 +316,15 @@ async def get_node_type_cache(
return _node_type_cache


def get_node_metadata_for_url(
node_id: Annotated[uuid.UUID | str, Body(description="Node UUID")],
core_client: Annotated[flame_hub.CoreClient, Depends(get_core_client)],
) -> Node | None:
"""Get analysis metadata for a given UUID to be used in creating analysis image URL."""
node_metadata: Node | None = core_client.get_node(node_id=node_id)

if not node_metadata.registry_project_id:
err_msg = f"No registry project associated with node {node_id}"
logger.error(err_msg)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={
"message": err_msg,
"service": "Hub",
"status_code": status.HTTP_400_BAD_REQUEST,
},
headers={"WWW-Authenticate": "Bearer"},
)

return node_metadata


def get_registry_metadata_for_url(
node_metadata: Annotated[Node, Depends(get_node_metadata_for_url)],
core_client: Annotated[flame_hub.CoreClient, Depends(get_core_client)],
):
"""Get registry metadata for a given UUID to be used in creating analysis image URL."""
registry_metadata = dict()

try:
registry_metadata = core_client.get_registry_project(
node_metadata.registry_project_id,
fields=("account_id", "account_name", "account_secret"),
)

except HubAPIError as err:
if err.error_response.status_code == status.HTTP_404_NOT_FOUND:
err_msg = f"Registry Project {node_metadata.registry_project_id} not found"
logger.error(err_msg)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail={
"message": err_msg,
"service": "Hub",
"status_code": status.HTTP_404_NOT_FOUND,
},
headers={"WWW-Authenticate": "Bearer"},
) from err

else:
logger.error(err.error_response.message)
raise HTTPException(
status_code=err.error_response.status_code,
detail={
"message": err.error_response.message,
"service": "Hub",
"status_code": err.error_response.status_code,
},
headers={"WWW-Authenticate": "Bearer"},
) from err

if not registry_metadata.external_name:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={
"message": "No external name for node found",
"service": "Hub",
"status_code": status.HTTP_400_BAD_REQUEST,
},
headers={"WWW-Authenticate": "Bearer"},
)

if not registry_metadata.account_name or not registry_metadata.account_secret:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail={
"message": "Unable to retrieve node client ID or secret from the registry",
"service": "Hub",
"status_code": status.HTTP_404_NOT_FOUND,
},
headers={"WWW-Authenticate": "Bearer"},
)

registry_project_external_name = registry_metadata.external_name
registry_id = registry_metadata.registry_id

if not registry_id:
err = f"No registry is associated with node {registry_project_external_name}"
logger.error(err)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={
"message": err,
"service": "Hub",
"status_code": status.HTTP_400_BAD_REQUEST,
},
headers={"WWW-Authenticate": "Bearer"},
)

host = registry_metadata.registry.host
user = registry_metadata.account_name
pwd = registry_metadata.account_secret

return host, registry_project_external_name, user, pwd


def compile_analysis_pod_data(
analysis_id: Annotated[uuid.UUID | str, Body(description="Analysis UUID")],
project_id: Annotated[uuid.UUID | str, Body(description="Project UUID")],
compiled_info: Annotated[tuple, Depends(get_registry_metadata_for_url)],
kong_token: Annotated[str, Body(description="Analysis keyauth kong token")] = None,
):
"""Put all the data together for passing on to the PO."""
host, registry_project_external_name, registry_user, registry_sec = compiled_info
compiled_response = {
"image_url": f"{host}/{registry_project_external_name}/{analysis_id}",
"analysis_id": str(analysis_id),
"project_id": str(project_id),
"kong_token": kong_token,
"registry_url": host,
"registry_user": registry_user,
"registry_password": registry_sec,
}
return compiled_response
4 changes: 4 additions & 0 deletions hub_adapter/routers/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ def _parse_query_params(
page: Annotated[str | None, Query(description='Pagination as JSON, e.g. {"limit": 50, "offset": 0}')] = None,
sort: Annotated[str | None, Query(description="Sort field; prefix with - for descending, + for ascending")] = None,
fields: Annotated[str | None, Query(description="Comma-separated list of fields to return")] = None,
include: Annotated[str | None, Query(description="Comma-separated list of nested fields to include")] = None,
) -> dict:
"""Format query params for the hub client."""
formatted: dict = {}

if include:
formatted["include"] = include.split(",")

if fields:
formatted["fields"] = fields.split(",")

Expand Down
2 changes: 1 addition & 1 deletion hub_adapter/routers/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ async def initialize_analysis(

# The Hub client is synchronous and this handler has to stay async, so the call is offloaded
analysis = await run_in_threadpool(
core_client.find_analysis_nodes, filter={"analysis_id": analysis_params.analysis_id}
core_client.find_analysis_nodes, filter={"analysis_id": analysis_params.analysis_id}, include="analysis"
)
if not analysis:
raise HTTPException(
Expand Down
11 changes: 1 addition & 10 deletions hub_adapter/routers/podorc.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,7 @@
from starlette.requests import Request
from starlette.responses import Response

from hub_adapter.auth import (
_add_internal_token_if_missing,
jwtbearer,
require_researcher_role,
verify_idp_token,
)
from hub_adapter.auth import _add_internal_token_if_missing, jwtbearer, require_researcher_role, verify_idp_token
from hub_adapter.constants import ServiceTag
from hub_adapter.core import route
from hub_adapter.dependencies import compile_analysis_pod_data, get_settings
Expand Down Expand Up @@ -52,10 +47,6 @@
body_params=[
"analysis_id",
"project_id",
"registry_url",
"registry_user",
"registry_password",
"image_url",
"kong_token",
],
name="podorc.pods.create",
Expand Down
19 changes: 1 addition & 18 deletions hub_adapter/schemas/hub.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,8 @@
"""Models for Hub endpoints."""

from flame_hub.models import Analysis, MasterImage, Project, Registry, NodeType
from flame_hub.models import NodeType
from pydantic import BaseModel


class DetailedAnalysis(Analysis):
"""Model representing a single detailed analysis."""

registry: Registry | None = None
project: Project | None = None
master_image: MasterImage | None = None


class AnalysisImageUrl(BaseModel):
image_url: str
project_id: str | None = None
kong_token: str | None = None
analysis_id: str
registry_url: str
registry_user: str | None = None


class NodeTypeResponse(BaseModel):
type: NodeType
4 changes: 0 additions & 4 deletions hub_adapter/schemas/podorc.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,7 @@ class CreateAnalysis(BaseModel):

analysis_id: str
project_id: str
registry_url: str
registry_user: str
registry_password: str
kong_token: str
image_url: str


class LogReport(RootModel[dict[uuid.UUID, list[str | None]]]):
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ requires-python = ">=3.12"
dependencies = [
"uvicorn>=0.38.0",
"fastapi>=0.138.0",
"flame-hub-client>=1.1.0",
"flame-hub-client>=2.0.0",
"click>=8.1.7",
"python-multipart>=0.0.31",
"httpx2>=2.9.1",
Expand Down
1 change: 0 additions & 1 deletion tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
realm_id=None,
registry_id=None,
type="default",
robot_id=None, # deprecated
)

MOCK_ANALYSIS = {
Expand Down
32 changes: 2 additions & 30 deletions tests/test_autostart.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,16 +267,10 @@ async def test_fetch_token_header(self, mock_fetch_token, mock_log_event):
@patch("hub_adapter.autostart.make_request")
@patch("hub_adapter.autostart.GoGoAnalysis.fetch_token_header")
@patch("hub_adapter.autostart.compile_analysis_pod_data")
@patch("hub_adapter.autostart.get_node_metadata_for_url")
@patch("hub_adapter.autostart.get_registry_metadata_for_url")
@pytest.mark.asyncio
async def test_send_start_request(
self, mock_registry_metadata, mock_node_metadata, mock_pod_data, mock_token_header, mock_request, mock_log_event
):
async def test_send_start_request(self, mock_pod_data, mock_token_header, mock_request, mock_log_event):
"""Test starting an analysis pod."""
# These first methods are for feeding into one another
mock_registry_metadata.return_value = {} # Not needed
mock_node_metadata.return_value = {} # Not needed
mock_pod_data.return_value = {} # Not needed
mock_token_header.return_value = {"foo"} # Just need something

Expand Down Expand Up @@ -429,21 +423,15 @@ async def test_describe_node_success(self, mock_node_type_cache, mock_get_node_i
@patch("hub_adapter.autostart.make_request")
@patch("hub_adapter.autostart.GoGoAnalysis.fetch_token_header")
@patch("hub_adapter.autostart.compile_analysis_pod_data")
@patch("hub_adapter.autostart.get_node_metadata_for_url")
@patch("hub_adapter.autostart.get_registry_metadata_for_url")
@pytest.mark.asyncio
async def test_send_start_request_http_status_error(
self,
mock_registry_metadata,
mock_node_metadata,
mock_pod_data,
mock_token_header,
mock_request,
mock_log_event,
):
"""Test send_start_request with HTTPStatusError."""
mock_registry_metadata.return_value = {}
mock_node_metadata.return_value = {}
mock_pod_data.return_value = {}
mock_token_header.return_value = {"Authorization": "Bearer token"}

Expand All @@ -469,21 +457,15 @@ async def test_send_start_request_http_status_error(
@patch("hub_adapter.autostart.make_request")
@patch("hub_adapter.autostart.GoGoAnalysis.fetch_token_header")
@patch("hub_adapter.autostart.compile_analysis_pod_data")
@patch("hub_adapter.autostart.get_node_metadata_for_url")
@patch("hub_adapter.autostart.get_registry_metadata_for_url")
@pytest.mark.asyncio
async def test_send_start_request_connect_error(
self,
mock_registry_metadata,
mock_node_metadata,
mock_pod_data,
mock_token_header,
mock_request,
mock_log_event,
):
"""Test send_start_request with ConnectError."""
mock_registry_metadata.return_value = {}
mock_node_metadata.return_value = {}
mock_pod_data.return_value = {}
mock_token_header.return_value = {"Authorization": "Bearer token"}

Expand All @@ -503,12 +485,8 @@ async def test_send_start_request_connect_error(

@patch("hub_adapter.autostart.GoGoAnalysis.fetch_token_header")
@patch("hub_adapter.autostart.compile_analysis_pod_data")
@patch("hub_adapter.autostart.get_node_metadata_for_url")
@patch("hub_adapter.autostart.get_registry_metadata_for_url")
@pytest.mark.asyncio
async def test_send_start_request_no_token(
self, mock_registry_metadata, mock_node_metadata, mock_pod_data, mock_token_header
):
async def test_send_start_request_no_token(self, mock_pod_data, mock_token_header):
"""Test send_start_request when token header is None."""
mock_token_header.return_value = None

Expand Down Expand Up @@ -896,21 +874,15 @@ def teardown_method(self):
@patch("hub_adapter.autostart.make_request")
@patch("hub_adapter.autostart.GoGoAnalysis.fetch_token_header")
@patch("hub_adapter.autostart.compile_analysis_pod_data")
@patch("hub_adapter.autostart.get_node_metadata_for_url")
@patch("hub_adapter.autostart.get_registry_metadata_for_url")
@pytest.mark.asyncio
async def test_send_start_request_remote_protocol_error(
self,
mock_registry_metadata,
mock_node_metadata,
mock_pod_data,
mock_token_header,
mock_request,
mock_log_event,
):
"""Test send_start_request with RemoteProtocolError."""
mock_registry_metadata.return_value = {}
mock_node_metadata.return_value = {}
mock_pod_data.return_value = {}
mock_token_header.return_value = {"Authorization": "Bearer token"}

Expand Down
Loading
Loading