LCOV - code coverage report
Current view: top level - proxy/src/console - messages.rs (source / functions) Coverage Total Hit
Test: 691a4c28fe7169edd60b367c52d448a0a6605f1f.info Lines: 82.8 % 122 101
Test Date: 2024-05-10 13:18:37 Functions: 34.7 % 101 35

            Line data    Source code
       1              : use measured::FixedCardinalityLabel;
       2              : use serde::{Deserialize, Serialize};
       3              : use std::fmt;
       4              : 
       5              : use crate::auth::IpPattern;
       6              : 
       7              : use crate::intern::{BranchIdInt, EndpointIdInt, ProjectIdInt};
       8              : 
       9              : /// Generic error response with human-readable description.
      10              : /// Note that we can't always present it to user as is.
      11            0 : #[derive(Debug, Deserialize)]
      12              : pub struct ConsoleError {
      13              :     pub error: Box<str>,
      14              : }
      15              : 
      16              : /// Response which holds client's auth secret, e.g. [`crate::scram::ServerSecret`].
      17              : /// Returned by the `/proxy_get_role_secret` API method.
      18           18 : #[derive(Deserialize)]
      19              : pub struct GetRoleSecret {
      20              :     pub role_secret: Box<str>,
      21              :     pub allowed_ips: Option<Vec<IpPattern>>,
      22              :     pub project_id: Option<ProjectIdInt>,
      23              : }
      24              : 
      25              : // Manually implement debug to omit sensitive info.
      26              : impl fmt::Debug for GetRoleSecret {
      27            0 :     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
      28            0 :         f.debug_struct("GetRoleSecret").finish_non_exhaustive()
      29            0 :     }
      30              : }
      31              : 
      32              : /// Response which holds compute node's `host:port` pair.
      33              : /// Returned by the `/proxy_wake_compute` API method.
      34            6 : #[derive(Debug, Deserialize)]
      35              : pub struct WakeCompute {
      36              :     pub address: Box<str>,
      37              :     pub aux: MetricsAuxInfo,
      38              : }
      39              : 
      40              : /// Async response which concludes the link auth flow.
      41              : /// Also known as `kickResponse` in the console.
      42            8 : #[derive(Debug, Deserialize)]
      43              : pub struct KickSession<'a> {
      44              :     /// Session ID is assigned by the proxy.
      45              :     pub session_id: &'a str,
      46              : 
      47              :     /// Compute node connection params.
      48              :     #[serde(deserialize_with = "KickSession::parse_db_info")]
      49              :     pub result: DatabaseInfo,
      50              : }
      51              : 
      52              : impl KickSession<'_> {
      53            2 :     fn parse_db_info<'de, D>(des: D) -> Result<DatabaseInfo, D::Error>
      54            2 :     where
      55            2 :         D: serde::Deserializer<'de>,
      56            2 :     {
      57            4 :         #[derive(Deserialize)]
      58            2 :         enum Wrapper {
      59            2 :             // Currently, console only reports `Success`.
      60            2 :             // `Failure(String)` used to be here... RIP.
      61            2 :             Success(DatabaseInfo),
      62            2 :         }
      63            2 : 
      64            2 :         Wrapper::deserialize(des).map(|x| match x {
      65            2 :             Wrapper::Success(info) => info,
      66            2 :         })
      67            2 :     }
      68              : }
      69              : 
      70              : /// Compute node connection params.
      71           56 : #[derive(Deserialize)]
      72              : pub struct DatabaseInfo {
      73              :     pub host: Box<str>,
      74              :     pub port: u16,
      75              :     pub dbname: Box<str>,
      76              :     pub user: Box<str>,
      77              :     /// Console always provides a password, but it might
      78              :     /// be inconvenient for debug with local PG instance.
      79              :     pub password: Option<Box<str>>,
      80              :     pub aux: MetricsAuxInfo,
      81              : }
      82              : 
      83              : // Manually implement debug to omit sensitive info.
      84              : impl fmt::Debug for DatabaseInfo {
      85            0 :     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
      86            0 :         f.debug_struct("DatabaseInfo")
      87            0 :             .field("host", &self.host)
      88            0 :             .field("port", &self.port)
      89            0 :             .field("dbname", &self.dbname)
      90            0 :             .field("user", &self.user)
      91            0 :             .finish_non_exhaustive()
      92            0 :     }
      93              : }
      94              : 
      95              : /// Various labels for prometheus metrics.
      96              : /// Also known as `ProxyMetricsAuxInfo` in the console.
      97           50 : #[derive(Debug, Deserialize, Clone)]
      98              : pub struct MetricsAuxInfo {
      99              :     pub endpoint_id: EndpointIdInt,
     100              :     pub project_id: ProjectIdInt,
     101              :     pub branch_id: BranchIdInt,
     102              :     #[serde(default)]
     103              :     pub cold_start_info: ColdStartInfo,
     104              : }
     105              : 
     106           20 : #[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, FixedCardinalityLabel)]
     107              : #[serde(rename_all = "snake_case")]
     108              : pub enum ColdStartInfo {
     109              :     #[default]
     110              :     Unknown,
     111              :     /// Compute was already running
     112              :     Warm,
     113              :     #[serde(rename = "pool_hit")]
     114              :     #[label(rename = "pool_hit")]
     115              :     /// Compute was not running but there was an available VM
     116              :     VmPoolHit,
     117              :     #[serde(rename = "pool_miss")]
     118              :     #[label(rename = "pool_miss")]
     119              :     /// Compute was not running and there were no VMs available
     120              :     VmPoolMiss,
     121              : 
     122              :     // not provided by control plane
     123              :     /// Connection available from HTTP pool
     124              :     HttpPoolHit,
     125              :     /// Cached connection info
     126              :     WarmCached,
     127              : }
     128              : 
     129              : impl ColdStartInfo {
     130            0 :     pub fn as_str(&self) -> &'static str {
     131            0 :         match self {
     132            0 :             ColdStartInfo::Unknown => "unknown",
     133            0 :             ColdStartInfo::Warm => "warm",
     134            0 :             ColdStartInfo::VmPoolHit => "pool_hit",
     135            0 :             ColdStartInfo::VmPoolMiss => "pool_miss",
     136            0 :             ColdStartInfo::HttpPoolHit => "http_pool_hit",
     137            0 :             ColdStartInfo::WarmCached => "warm_cached",
     138              :         }
     139            0 :     }
     140              : }
     141              : 
     142              : #[cfg(test)]
     143              : mod tests {
     144              :     use super::*;
     145              :     use serde_json::json;
     146              : 
     147           10 :     fn dummy_aux() -> serde_json::Value {
     148           10 :         json!({
     149           10 :             "endpoint_id": "endpoint",
     150           10 :             "project_id": "project",
     151           10 :             "branch_id": "branch",
     152           10 :             "cold_start_info": "unknown",
     153           10 :         })
     154           10 :     }
     155              : 
     156              :     #[test]
     157            2 :     fn parse_kick_session() -> anyhow::Result<()> {
     158            2 :         // This is what the console's kickResponse looks like.
     159            2 :         let json = json!({
     160            2 :             "session_id": "deadbeef",
     161            2 :             "result": {
     162            2 :                 "Success": {
     163            2 :                     "host": "localhost",
     164            2 :                     "port": 5432,
     165            2 :                     "dbname": "postgres",
     166            2 :                     "user": "john_doe",
     167            2 :                     "password": "password",
     168            2 :                     "aux": dummy_aux(),
     169            2 :                 }
     170            2 :             }
     171            2 :         });
     172            2 :         let _: KickSession = serde_json::from_str(&json.to_string())?;
     173              : 
     174            2 :         Ok(())
     175            2 :     }
     176              : 
     177              :     #[test]
     178            2 :     fn parse_db_info() -> anyhow::Result<()> {
     179              :         // with password
     180            2 :         let _: DatabaseInfo = serde_json::from_value(json!({
     181            2 :             "host": "localhost",
     182            2 :             "port": 5432,
     183            2 :             "dbname": "postgres",
     184            2 :             "user": "john_doe",
     185            2 :             "password": "password",
     186            2 :             "aux": dummy_aux(),
     187            2 :         }))?;
     188              : 
     189              :         // without password
     190            2 :         let _: DatabaseInfo = serde_json::from_value(json!({
     191            2 :             "host": "localhost",
     192            2 :             "port": 5432,
     193            2 :             "dbname": "postgres",
     194            2 :             "user": "john_doe",
     195            2 :             "aux": dummy_aux(),
     196            2 :         }))?;
     197              : 
     198              :         // new field (forward compatibility)
     199            2 :         let _: DatabaseInfo = serde_json::from_value(json!({
     200            2 :             "host": "localhost",
     201            2 :             "port": 5432,
     202            2 :             "dbname": "postgres",
     203            2 :             "user": "john_doe",
     204            2 :             "project": "hello_world",
     205            2 :             "N.E.W": "forward compatibility check",
     206            2 :             "aux": dummy_aux(),
     207            2 :         }))?;
     208              : 
     209            2 :         Ok(())
     210            2 :     }
     211              : 
     212              :     #[test]
     213            2 :     fn parse_wake_compute() -> anyhow::Result<()> {
     214            2 :         let json = json!({
     215            2 :             "address": "0.0.0.0",
     216            2 :             "aux": dummy_aux(),
     217            2 :         });
     218            2 :         let _: WakeCompute = serde_json::from_str(&json.to_string())?;
     219            2 :         Ok(())
     220            2 :     }
     221              : 
     222              :     #[test]
     223            2 :     fn parse_get_role_secret() -> anyhow::Result<()> {
     224            2 :         // Empty `allowed_ips` field.
     225            2 :         let json = json!({
     226            2 :             "role_secret": "secret",
     227            2 :         });
     228            2 :         let _: GetRoleSecret = serde_json::from_str(&json.to_string())?;
     229            2 :         let json = json!({
     230            2 :             "role_secret": "secret",
     231            2 :             "allowed_ips": ["8.8.8.8"],
     232            2 :         });
     233            2 :         let _: GetRoleSecret = serde_json::from_str(&json.to_string())?;
     234            2 :         let json = json!({
     235            2 :             "role_secret": "secret",
     236            2 :             "allowed_ips": ["8.8.8.8"],
     237            2 :             "project_id": "project",
     238            2 :         });
     239            2 :         let _: GetRoleSecret = serde_json::from_str(&json.to_string())?;
     240              : 
     241            2 :         Ok(())
     242            2 :     }
     243              : }
        

Generated by: LCOV version 2.1-beta