Skip to content
Open
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
68 changes: 68 additions & 0 deletions app/routers/status/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,53 @@ class Endpoint(str, enum.Enum):
filesystem = "filesystem"


class StorageOperation(str, enum.Enum):
"""Storage-discovery operation an adapter implements for a Resource (internal, never serialized).

RFC Resource Operation Affordances: these relations have no legacy
`supported_endpoints` category (section 3.3), and resource_type alone MUST NOT
imply support (section 4), so adapters declare them explicitly.
"""
resolve_storage_locations = "resolve-storage-locations"
get_storage_access_endpoints = "get-storage-access-endpoints"


# Storage router path segment for each storage-discovery relation.
_STORAGE_OPERATION_PATHS = {
StorageOperation.resolve_storage_locations: "locations",
StorageOperation.get_storage_access_endpoints: "access-endpoints",
}

# RFC Resource Operation Affordances section 4: Resource types eligible to advertise each
# storage-discovery relation, as (types matched with their extensions, generic types
# matched exactly). The generic parents match only exactly because some of their children
# are not eligible (e.g. compute:gpu, or storage:mount for access endpoints).
_STORAGE_OPERATION_SOURCE_TYPES = {
StorageOperation.resolve_storage_locations: (
(
"urn:doe-iri:resource:compute:system",
"urn:doe-iri:resource:compute:node",
"urn:doe-iri:resource:storage:filesystem",
"urn:doe-iri:resource:storage:mount",
"urn:doe-iri:resource:storage:system",
"urn:doe-iri:resource:storage:block",
"urn:doe-iri:resource:storage:object",
"urn:doe-iri:resource:service:dtn",
),
("urn:doe-iri:resource:compute", "urn:doe-iri:resource:storage"),
),
StorageOperation.get_storage_access_endpoints: (
(
"urn:doe-iri:resource:storage:filesystem",
"urn:doe-iri:resource:storage:system",
"urn:doe-iri:resource:storage:block",
"urn:doe-iri:resource:storage:object",
),
("urn:doe-iri:resource:storage",),
),
}


class Resource(NamedObject):
"""Represents a resource in the system."""
def _self_path(self) -> str:
Expand All @@ -70,6 +117,11 @@ def _self_path(self) -> str:
current_status: Status|None = Field(default=None, description="The current status comes from the status of the last event for this resource", example="up")
resource_type: ResourceTypeValue = Field(..., description="DOE IRI URN for the resource type", example=ResourceType.service)
supported_endpoints: list[Endpoint] = Field(default_factory=list, description="a list of endpoints where this resource can be used")
storage_operations: list[StorageOperation] = Field(
default_factory=list,
exclude=True,
description="Storage-discovery operations the adapter implements for this Resource; each is advertised as its iri:* HAL link.",
)
related_resource_ids: dict[str, list[str]] = Field(
default_factory=dict,
exclude=True,
Expand Down Expand Up @@ -100,6 +152,18 @@ def _validate_related_resource_ids(self):
)
return self

@model_validator(mode="after")
def _validate_storage_operations(self):
for operation in self.storage_operations:
prefix_types, exact_types = _STORAGE_OPERATION_SOURCE_TYPES[operation]
if self.resource_type in exact_types or any(urn_has_complete_prefix(t, self.resource_type) for t in prefix_types):
continue
raise ValueError(
f"storage_operations: 'iri:{operation.value}' is not eligible for resource_type "
f"{self.resource_type} (RFC Resource Operation Affordances section 4)"
)
return self

@computed_field(description="URI of the site where this resource is located")
@property
def site_uri(self) -> str:
Expand Down Expand Up @@ -138,6 +202,10 @@ def _extra_links(self) -> dict:
links[f"iri:{relation}"] = build_hal_link(f"{fs_base}/{op}/{self.id}", media_type=None)
links["iri:upload-file"] = build_hal_link(f"{fs_base}/upload/{self.id}{{?path}}", media_type=None, templated=True)
advertises_operations = True
for operation, path in _STORAGE_OPERATION_PATHS.items():
if operation in self.storage_operations:
links[f"iri:{operation.value}"] = build_hal_link(f"{get_url_prefix()}/storage/{path}/{self.id}", media_type=None)
advertises_operations = True
if advertises_operations:
links["service-desc"] = build_hal_link(f"{get_url_prefix()}/openapi.json", media_type=SERVICE_DESC_MEDIA_TYPE)
for relation, target_ids in self.related_resource_ids.items():
Expand Down
1 change: 1 addition & 0 deletions app/routers/storage/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class AccessEndpoint(BaseModel):
resource_id: str = Field(..., description="ID of the storage resource this endpoint belongs to")
protocol: AccessProtocol = Field(..., description="Data access protocol")
display_name: Optional[str] = Field(default=None, description="Human-readable name for this endpoint", example="Globus endpoint example")
description: Optional[str] = Field(default=None, description="Additional information about this endpoint, such as access prerequisites", example="Join the facility Globus group before use")
auth_type: str = Field(..., description="Authentication mechanism required to use this endpoint", example="globus")
capabilities: list[AccessCapability] = Field(..., description="Supported data operations")
# Globus-specific
Expand Down
Loading