Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

server/lib/src/live_routes.rs

//! **A repository made at run time serves at once** (lane M4b). The route tables are built
//! once at boot; a paid signup's repository (`add_principal`, lane M4) was served only from
//! the next start. [`LiveRoutes`] is what the boot keeps to make one more: the doors' route
//! tables (shared [`FastRoutes`] handles, so the gRPC door, the HTTP door and the job runner
//! of a door all see the new route), every repository's declaration, the handler catalog,
//! the growth's hold and (metered) the paid limit. [`LiveRoutes::serve`] builds the new
//! repository's backend exactly as the boot does — its own backend from its declaration,
//! wrapped in the growth's hold, then in the paid limit — and swaps it into its door's table.
//! A request in flight keeps the backend (and the table) it already resolved.
//!
//! What the boot alone can serve stays "served from the next start", said by name: a template
//! with upstreams (the pull-through proxy is wired over the boot's pointers) and a template
//! a credential gates (the read gate is fixed at boot; serving the copy open would be a leak).

use std::collections::BTreeMap;
use std::sync::Arc;

use crate::exposed::fast_routes::FastRoutes;
use crate::lock::RwLockOrRecover as _;
use crate::repository::Repository;

/// What the boot keeps to serve a repository made after it.
pub struct LiveRoutes {
    /// Each exposed endpoint's route table, by the endpoint's name.
    pub(crate) doors: Vec<(String, FastRoutes)>,
    /// Every non-group repository's declaration (RON), by name.
    pub(crate) specs: BTreeMap<String, String>,
    pub(crate) catalog: Arc<crate::handlers::HandlerCatalog>,
    pub(crate) hold: Arc<crate::grow::Hold>,
    pub(crate) read_gate: Arc<crate::auth::ReadGate>,
    /// The paid limit and the data root its guard reads (metered, a primary with a key).
    #[cfg(feature = "metered")]
    pub(crate) quota: Option<(Arc<crate::metered::Quota>, std::path::PathBuf)>,
    /// **Tables that route every repository whatever its door** (lane P4): the replication
    /// door's, which a certified twin follows and a recharge pulls through. A repository made
    /// at run time is added to each, so the twin can follow it; one made before a table is
    /// registered is added at registration ([`LiveRoutes::also_route`]).
    pub(crate) every: std::sync::RwLock<Vec<FastRoutes>>,
    /// Every repository made at run time, with the backend its door routes.
    pub(crate) made: std::sync::RwLock<Vec<(String, Arc<dyn traits::RepositoryBackendTrait>)>>,
}

impl LiveRoutes {
    /// True when some door serves `name` now.
    pub fn serves(&self, name: &str) -> bool {
        self.doors.iter().any(|(_, t)| t.lookup(name).is_some())
    }

    /// **Route every repository made at run time on `table` too, from now on** — the
    /// replication door's (lane P4). Those made before are added at once.
    pub fn also_route(&self, table: FastRoutes) {
        let mut every = self.every.write_or_recover();
        for (name, b) in self.made.read_or_recover().iter() {
            table.add(name, b.clone());
        }
        every.push(table);
    }

    /// True when `name` is a repository this start declared (one a made repository may be
    /// shaped like).
    pub fn declares(&self, name: &str) -> bool {
        self.specs.contains_key(name)
    }

    /// **`name` is owner-only from now on** (lane P1, [`crate::auth::ReadGate::own`]).
    pub fn own(&self, name: &str, owner: &str) {
        self.read_gate.own(name, owner);
    }

    /// [`LiveRoutes::own`] with the record's grants too (a twin following, a recharge).
    pub fn own_entry(&self, name: &str, entry: crate::auth::SoldEntry) {
        self.read_gate.own_entry(name, entry);
    }

    /// The backend some door serves `name` with now.
    pub fn lookup(&self, name: &str) -> Option<Arc<dyn traits::RepositoryBackendTrait>> {
        self.doors.iter().find_map(|(_, t)| t.lookup(name))
    }

    /// **The backend a made repository's bytes are written through** (lane P4: a twin that
    /// follows a sale, a main recharged after one). Served at once when it can be
    /// ([`LiveRoutes::serve`]); a repository only a start can serve (a gated or pull-through
    /// template) gets a backend over its own bytes that no door routes yet, so a follow or a
    /// recharge still holds every artifact and the next start serves them. `Err` only when no
    /// backend can be made at all (its template is gone, its declaration makes none).
    pub fn backend_for(
        &self,
        name: &str,
        template: &str,
        private: bool,
    ) -> Result<Arc<dyn traits::RepositoryBackendTrait>, String> {
        if let Some(b) = self.lookup(name) {
            return Ok(b);
        }
        let why = match self.serve_as(name, template, private) {
            Ok(()) => {
                return self
                    .lookup(name)
                    .ok_or_else(|| format!("'{name}' was served and no door routes it"))
            }
            Err(why) => why,
        };
        let spec = self.specs.get(template).ok_or_else(|| {
            format!("its template '{template}' is not a repository this start declared")
        })?;
        let tmpl: Repository = ron::from_str(spec)
            .map_err(|e| format!("its template '{template}' cannot be read again ({e})"))?;
        let mut repo = crate::sold_copy(&tmpl, name)?;
        repo.backend_from_config_with(&self.catalog)
            .map_err(|e| format!("its backend cannot be made ({e:#})"))?;
        let inner = repo
            .backend_repository
            .take()
            .ok_or_else(|| "its declaration made no backend".to_string())?;
        log::warn!(
            "repository '{name}': its bytes are kept, and it is served from the next start ({why})"
        );
        let backend = crate::held_backend(&repo, inner, &self.hold, &self.catalog);
        // No public door routes it, but the replication door does: a recharge pulls it back.
        let every = self.every.read_or_recover();
        for t in every.iter() {
            t.add(name, backend.clone());
        }
        self.made
            .write_or_recover()
            .push((name.to_owned(), backend.clone()));
        Ok(backend)
    }

    /// **Serve `name`, shaped like `template`, from now on** (idempotent). `Err` says why it
    /// is served only from the next start; nothing was changed then.
    pub fn serve(&self, name: &str, template: &str) -> Result<(), String> {
        self.serve_as(name, template, false)
    }

    /// [`LiveRoutes::serve`], and with `private` its reads need a credential of this server
    /// from the first request on (lane P4): the read gate learns the name BEFORE any door
    /// routes it, so no read ever finds it open.
    pub fn serve_as(&self, name: &str, template: &str, private: bool) -> Result<(), String> {
        if self.serves(name) {
            return Ok(());
        }
        let spec = self.specs.get(template).ok_or_else(|| {
            format!("its template '{template}' is not a repository this start declared")
        })?;
        let tmpl: Repository = ron::from_str(spec)
            .map_err(|e| format!("its template '{template}' cannot be read again ({e})"))?;
        if self.read_gate.repo_requires_auth(template) || self.read_gate.floor(template).is_some() {
            return Err(format!("its template '{template}' is gated by a credential, which the read gate learns at a start"));
        }
        if !tmpl.ron_upstreams.is_empty() {
            return Err(format!(
                "its template '{template}' pulls through upstreams, which are wired at a start"
            ));
        }
        let door = tmpl
            .ron_out
            .as_ref()
            .map(|o| o.ron_exposed_endpoint.clone())
            .ok_or_else(|| format!("its template '{template}' is exposed on no door"))?;
        let table = self
            .doors
            .iter()
            .find(|(n, _)| *n == door)
            .map(|(_, t)| t)
            .ok_or_else(|| format!("its door '{door}' has no route table"))?;
        let mut repo = crate::sold_copy(&tmpl, name)?;
        repo.ron_require_auth |= private;
        repo.backend_from_config_with(&self.catalog)
            .map_err(|e| format!("its backend cannot be made ({e:#})"))?;
        let inner = repo
            .backend_repository
            .take()
            .ok_or_else(|| "its declaration made no backend".to_string())?;
        if private {
            self.read_gate.gate_from_now(name);
        }
        #[allow(unused_mut)]
        let mut backend = crate::held_backend(&repo, inner, &self.hold, &self.catalog);
        #[cfg(feature = "metered")]
        if let Some((quota, root)) = &self.quota {
            quota.add_places(name, repo.storage_places());
            backend = crate::metered::QuotaGuard::wrap(
                backend,
                name.to_string(),
                Arc::clone(quota),
                root.clone(),
            );
        }
        {
            // Under the `every` lock, so a table registered meanwhile misses nothing.
            let every = self.every.read_or_recover();
            table.add(name, backend.clone());
            for t in every.iter() {
                t.add(name, backend.clone());
            }
            self.made
                .write_or_recover()
                .push((name.to_owned(), backend));
        }
        log::warn!("repository '{name}': made at run time, shaped like '{template}', served now on door '{door}'");
        Ok(())
    }
}