Skip to content
Draft
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
15 changes: 15 additions & 0 deletions crates/openshell-cli/tests/ensure_providers_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,21 @@ impl TestOpenShell {

#[tonic::async_trait]
impl OpenShell for TestOpenShell {
async fn get_service_route_snapshot(
&self,
_: tonic::Request<openshell_core::proto::GetServiceRouteSnapshotRequest>,
) -> Result<Response<openshell_core::proto::GetServiceRouteSnapshotResponse>, Status> {
Err(Status::unimplemented("unused"))
}
type WatchServiceRoutesStream = tokio_stream::wrappers::ReceiverStream<
Result<openshell_core::proto::WatchServiceRoutesResponse, Status>,
>;
async fn watch_service_routes(
&self,
_: tonic::Request<openshell_core::proto::WatchServiceRoutesRequest>,
) -> Result<Response<Self::WatchServiceRoutesStream>, Status> {
Err(Status::unimplemented("unused"))
}
async fn peer_notify_config_update(
&self,
_request: tonic::Request<openshell_core::proto::PeerNotifyConfigUpdateRequest>,
Expand Down
15 changes: 15 additions & 0 deletions crates/openshell-cli/tests/mtls_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@ struct TestOpenShell;

#[tonic::async_trait]
impl OpenShell for TestOpenShell {
async fn get_service_route_snapshot(
&self,
_: tonic::Request<openshell_core::proto::GetServiceRouteSnapshotRequest>,
) -> Result<Response<openshell_core::proto::GetServiceRouteSnapshotResponse>, Status> {
Err(Status::unimplemented("unused"))
}
type WatchServiceRoutesStream = tokio_stream::wrappers::ReceiverStream<
Result<openshell_core::proto::WatchServiceRoutesResponse, Status>,
>;
async fn watch_service_routes(
&self,
_: tonic::Request<openshell_core::proto::WatchServiceRoutesRequest>,
) -> Result<Response<Self::WatchServiceRoutesStream>, Status> {
Err(Status::unimplemented("unused"))
}
async fn peer_notify_config_update(
&self,
_request: tonic::Request<openshell_core::proto::PeerNotifyConfigUpdateRequest>,
Expand Down
15 changes: 15 additions & 0 deletions crates/openshell-cli/tests/provider_commands_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,21 @@ impl TestOpenShell {

#[tonic::async_trait]
impl OpenShell for TestOpenShell {
async fn get_service_route_snapshot(
&self,
_: tonic::Request<openshell_core::proto::GetServiceRouteSnapshotRequest>,
) -> Result<Response<openshell_core::proto::GetServiceRouteSnapshotResponse>, Status> {
Err(Status::unimplemented("unused"))
}
type WatchServiceRoutesStream = tokio_stream::wrappers::ReceiverStream<
Result<openshell_core::proto::WatchServiceRoutesResponse, Status>,
>;
async fn watch_service_routes(
&self,
_: tonic::Request<openshell_core::proto::WatchServiceRoutesRequest>,
) -> Result<Response<Self::WatchServiceRoutesStream>, Status> {
Err(Status::unimplemented("unused"))
}
async fn peer_notify_config_update(
&self,
_request: tonic::Request<openshell_core::proto::PeerNotifyConfigUpdateRequest>,
Expand Down
15 changes: 15 additions & 0 deletions crates/openshell-cli/tests/sandbox_create_lifecycle_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,21 @@ struct TestOpenShell {

#[tonic::async_trait]
impl OpenShell for TestOpenShell {
async fn get_service_route_snapshot(
&self,
_: tonic::Request<openshell_core::proto::GetServiceRouteSnapshotRequest>,
) -> Result<Response<openshell_core::proto::GetServiceRouteSnapshotResponse>, Status> {
Err(Status::unimplemented("unused"))
}
type WatchServiceRoutesStream = tokio_stream::wrappers::ReceiverStream<
Result<openshell_core::proto::WatchServiceRoutesResponse, Status>,
>;
async fn watch_service_routes(
&self,
_: tonic::Request<openshell_core::proto::WatchServiceRoutesRequest>,
) -> Result<Response<Self::WatchServiceRoutesStream>, Status> {
Err(Status::unimplemented("unused"))
}
async fn peer_notify_config_update(
&self,
_request: tonic::Request<openshell_core::proto::PeerNotifyConfigUpdateRequest>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ macro_rules! mock_gateway {
) => {
#[tonic::async_trait]
impl OpenShell for MockGateway {
async fn get_service_route_snapshot(&self, _: Request<openshell_core::proto::GetServiceRouteSnapshotRequest>) -> Result<Response<openshell_core::proto::GetServiceRouteSnapshotResponse>, Status> {
Err(Status::unimplemented("unused"))
}
type WatchServiceRoutesStream = tokio_stream::wrappers::ReceiverStream<Result<openshell_core::proto::WatchServiceRoutesResponse, Status>>;
async fn watch_service_routes(&self, _: Request<openshell_core::proto::WatchServiceRoutesRequest>) -> Result<Response<Self::WatchServiceRoutesStream>, Status> {
Err(Status::unimplemented("unused"))
}
$(async fn $method(&self, _: Request<$request>) -> Result<Response<$response>, Status> {
Err(Status::unimplemented("unused test RPC"))
})*
Expand Down
15 changes: 15 additions & 0 deletions crates/openshell-cli/tests/sandbox_name_fallback_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,21 @@ struct TestOpenShell {

#[tonic::async_trait]
impl OpenShell for TestOpenShell {
async fn get_service_route_snapshot(
&self,
_: tonic::Request<openshell_core::proto::GetServiceRouteSnapshotRequest>,
) -> Result<Response<openshell_core::proto::GetServiceRouteSnapshotResponse>, Status> {
Err(Status::unimplemented("unused"))
}
type WatchServiceRoutesStream = tokio_stream::wrappers::ReceiverStream<
Result<openshell_core::proto::WatchServiceRoutesResponse, Status>,
>;
async fn watch_service_routes(
&self,
_: tonic::Request<openshell_core::proto::WatchServiceRoutesRequest>,
) -> Result<Response<Self::WatchServiceRoutesStream>, Status> {
Err(Status::unimplemented("unused"))
}
async fn peer_notify_config_update(
&self,
_request: tonic::Request<openshell_core::proto::PeerNotifyConfigUpdateRequest>,
Expand Down
14 changes: 14 additions & 0 deletions crates/openshell-sdk/tests/client_mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,20 @@ fn workload_template_proto(name: &str, workspace: &str) -> proto::SandboxWorkloa

#[tonic::async_trait]
impl OpenShell for TestOpenShell {
async fn get_service_route_snapshot(
&self,
_: tonic::Request<proto::GetServiceRouteSnapshotRequest>,
) -> Result<Response<proto::GetServiceRouteSnapshotResponse>, Status> {
Err(Status::unimplemented("unused"))
}
type WatchServiceRoutesStream =
tokio_stream::wrappers::ReceiverStream<Result<proto::WatchServiceRoutesResponse, Status>>;
async fn watch_service_routes(
&self,
_: tonic::Request<proto::WatchServiceRoutesRequest>,
) -> Result<Response<Self::WatchServiceRoutesStream>, Status> {
Err(Status::unimplemented("unused"))
}
async fn peer_notify_config_update(
&self,
_request: tonic::Request<proto::PeerNotifyConfigUpdateRequest>,
Expand Down
24 changes: 24 additions & 0 deletions crates/openshell-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,27 @@ change durable sandbox protobufs. Reads revalidate owner, configuration,
generation, and expiry. Cleanup scans bounded pages and uses conditional deletes
so concurrent renewals are preserved. Discovery consumers must not treat these
records as application health or external ingress readiness.

## Workspace service discovery

`GetServiceRouteSnapshot` and `WatchServiceRoutes` expose accepted installations
for one explicit active workspace. They require `sandbox:read` and workspace
user access; omitted/all-workspaces selectors and sandbox principals are rejected.
Dedicated discovery machine grants are separate work.

Each successful response replaces the complete view. Consumers preserve the
returned hostname as TLS SNI and HTTP authority when dialing the supervisor
endpoint, remove absent routes, and expire cached entries at `expires_at` even
when discovery is unavailable. An error is not an empty replacement. The opaque
snapshot revision includes lease renewals; `installation_version` remains stable
across those renewals and changes when installation identity changes. These
records establish neither application health nor ingress readiness.

Collection checks installation resource versions across two scans, retrying once
if changed. This is a verified view, not a transaction across configuration and
owner resources. Reads are bounded to 1,000 installation records, 4,096 routes,
4 MiB, and ten seconds. Overflow, corruption, or storage failure returns an error.
Watches refresh and reauthorize each second, use a one-message buffer, and end
after one minute or a five-second blocked send. Each gateway process accepts at
most 32 watches. Reconnect and relist on stream termination; revisions are opaque
equality tokens, not resumable cursors or an ordering across gateway replicas.
17 changes: 17 additions & 0 deletions crates/openshell-server/src/grpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod supervisor_host;
pub use sandbox::mint_persisted_authentication;
mod service;
pub mod service_advertisement;
mod service_discovery;
mod validation;
pub mod workspace;

Expand Down Expand Up @@ -511,6 +512,22 @@ impl OpenShell for OpenShellService {
service::handle_list_services(&self.state, request).await
}

async fn get_service_route_snapshot(
&self,
request: Request<openshell_core::proto::GetServiceRouteSnapshotRequest>,
) -> Result<Response<openshell_core::proto::GetServiceRouteSnapshotResponse>, Status> {
service_discovery::get_snapshot(&self.state, request).await
}

type WatchServiceRoutesStream = service_discovery::RouteWatchStream;

async fn watch_service_routes(
&self,
request: Request<openshell_core::proto::WatchServiceRoutesRequest>,
) -> Result<Response<Self::WatchServiceRoutesStream>, Status> {
service_discovery::watch(&self.state, request).await
}

async fn delete_service(
&self,
request: Request<DeleteServiceRequest>,
Expand Down
3 changes: 2 additions & 1 deletion crates/openshell-server/src/grpc/service_advertisement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ pub fn spawn_cleanup(state: Arc<ServerState>, mut shutdown: tokio::sync::watch::
}

#[cfg(test)]
mod tests {
pub mod tests {
use super::*;

fn route() -> SandboxServiceRoute {
Expand Down Expand Up @@ -635,6 +635,7 @@ mod tests {
port: 9443,
domain: "services.example.com".into(),
};
Arc::get_mut(&mut state).unwrap().admin_role = "openshell-admin".into();
let sandbox = Sandbox {
metadata: Some(ObjectMeta {
id: "sandbox".into(),
Expand Down
Loading
Loading