LCOV - differential code coverage report
Current view: top level - proxy/src/console - provider.rs (source / functions) Coverage Total Hit UBC CBC
Current: f6946e90941b557c917ac98cd5a7e9506d180f3e.info Lines: 7.4 % 54 4 50 4
Current Date: 2023-10-19 02:04:12 Functions: 3.8 % 26 1 25 1
Baseline: c8637f37369098875162f194f92736355783b050.info
Baseline Date: 2023-10-18 20:25:20

           TLA  Line data    Source code
       1                 : pub mod mock;
       2                 : pub mod neon;
       3                 : 
       4                 : use super::messages::MetricsAuxInfo;
       5                 : use crate::{
       6                 :     auth::ClientCredentials,
       7                 :     cache::{timed_lru, TimedLru},
       8                 :     compute, scram,
       9                 : };
      10                 : use async_trait::async_trait;
      11                 : use std::sync::Arc;
      12                 : 
      13                 : pub mod errors {
      14                 :     use crate::{
      15                 :         error::{io_error, UserFacingError},
      16                 :         http,
      17                 :         proxy::ShouldRetry,
      18                 :     };
      19                 :     use thiserror::Error;
      20                 : 
      21                 :     /// A go-to error message which doesn't leak any detail.
      22                 :     const REQUEST_FAILED: &str = "Console request failed";
      23                 : 
      24                 :     /// Common console API error.
      25 UBC           0 :     #[derive(Debug, Error)]
      26                 :     pub enum ApiError {
      27                 :         /// Error returned by the console itself.
      28                 :         #[error("{REQUEST_FAILED} with {}: {}", .status, .text)]
      29                 :         Console {
      30                 :             status: http::StatusCode,
      31                 :             text: Box<str>,
      32                 :         },
      33                 : 
      34                 :         /// Various IO errors like broken pipe or malformed payload.
      35                 :         #[error("{REQUEST_FAILED}: {0}")]
      36                 :         Transport(#[from] std::io::Error),
      37                 :     }
      38                 : 
      39                 :     impl ApiError {
      40                 :         /// Returns HTTP status code if it's the reason for failure.
      41               0 :         pub fn http_status_code(&self) -> Option<http::StatusCode> {
      42               0 :             use ApiError::*;
      43               0 :             match self {
      44               0 :                 Console { status, .. } => Some(*status),
      45               0 :                 _ => None,
      46                 :             }
      47               0 :         }
      48                 :     }
      49                 : 
      50                 :     impl UserFacingError for ApiError {
      51               0 :         fn to_string_client(&self) -> String {
      52               0 :             use ApiError::*;
      53               0 :             match self {
      54                 :                 // To minimize risks, only select errors are forwarded to users.
      55                 :                 // Ask @neondatabase/control-plane for review before adding more.
      56               0 :                 Console { status, .. } => match *status {
      57                 :                     http::StatusCode::NOT_FOUND => {
      58                 :                         // Status 404: failed to get a project-related resource.
      59               0 :                         format!("{REQUEST_FAILED}: endpoint cannot be found")
      60                 :                     }
      61                 :                     http::StatusCode::NOT_ACCEPTABLE => {
      62                 :                         // Status 406: endpoint is disabled (we don't allow connections).
      63               0 :                         format!("{REQUEST_FAILED}: endpoint is disabled")
      64                 :                     }
      65                 :                     http::StatusCode::LOCKED => {
      66                 :                         // Status 423: project might be in maintenance mode (or bad state), or quotas exceeded.
      67               0 :                         format!("{REQUEST_FAILED}: endpoint is temporary unavailable. check your quotas and/or contact our support")
      68                 :                     }
      69               0 :                     _ => REQUEST_FAILED.to_owned(),
      70                 :                 },
      71               0 :                 _ => REQUEST_FAILED.to_owned(),
      72                 :             }
      73               0 :         }
      74                 :     }
      75                 : 
      76                 :     impl ShouldRetry for ApiError {
      77                 :         fn could_retry(&self) -> bool {
      78 CBC           4 :             match self {
      79                 :                 // retry some transport errors
      80 UBC           0 :                 Self::Transport(io) => io.could_retry(),
      81                 :                 // retry some temporary failures because the compute was in a bad state
      82                 :                 // (bad request can be returned when the endpoint was in transition)
      83                 :                 Self::Console {
      84                 :                     status: http::StatusCode::BAD_REQUEST,
      85                 :                     ..
      86               0 :                 } => true,
      87                 :                 // locked can be returned when the endpoint was in transition
      88                 :                 // or when quotas are exceeded. don't retry when quotas are exceeded
      89                 :                 Self::Console {
      90                 :                     status: http::StatusCode::LOCKED,
      91               0 :                     ref text,
      92               0 :                 } => {
      93               0 :                     !text.contains("written data quota exceeded")
      94               0 :                         && !text.contains("the limit for current plan reached")
      95                 :                 }
      96                 :                 // retry server errors
      97 CBC           4 :                 Self::Console { status, .. } if status.is_server_error() => true,
      98               2 :                 _ => false,
      99                 :             }
     100               4 :         }
     101                 :     }
     102                 : 
     103                 :     impl From<reqwest::Error> for ApiError {
     104 UBC           0 :         fn from(e: reqwest::Error) -> Self {
     105               0 :             io_error(e).into()
     106               0 :         }
     107                 :     }
     108                 : 
     109                 :     impl From<reqwest_middleware::Error> for ApiError {
     110               0 :         fn from(e: reqwest_middleware::Error) -> Self {
     111               0 :             io_error(e).into()
     112               0 :         }
     113                 :     }
     114                 : 
     115               0 :     #[derive(Debug, Error)]
     116                 :     pub enum GetAuthInfoError {
     117                 :         // We shouldn't include the actual secret here.
     118                 :         #[error("Console responded with a malformed auth secret")]
     119                 :         BadSecret,
     120                 : 
     121                 :         #[error(transparent)]
     122                 :         ApiError(ApiError),
     123                 :     }
     124                 : 
     125                 :     // This allows more useful interactions than `#[from]`.
     126                 :     impl<E: Into<ApiError>> From<E> for GetAuthInfoError {
     127               0 :         fn from(e: E) -> Self {
     128               0 :             Self::ApiError(e.into())
     129               0 :         }
     130                 :     }
     131                 : 
     132                 :     impl UserFacingError for GetAuthInfoError {
     133               0 :         fn to_string_client(&self) -> String {
     134               0 :             use GetAuthInfoError::*;
     135               0 :             match self {
     136                 :                 // We absolutely should not leak any secrets!
     137               0 :                 BadSecret => REQUEST_FAILED.to_owned(),
     138                 :                 // However, API might return a meaningful error.
     139               0 :                 ApiError(e) => e.to_string_client(),
     140                 :             }
     141               0 :         }
     142                 :     }
     143               0 :     #[derive(Debug, Error)]
     144                 :     pub enum WakeComputeError {
     145                 :         #[error("Console responded with a malformed compute address: {0}")]
     146                 :         BadComputeAddress(Box<str>),
     147                 : 
     148                 :         #[error(transparent)]
     149                 :         ApiError(ApiError),
     150                 :     }
     151                 : 
     152                 :     // This allows more useful interactions than `#[from]`.
     153                 :     impl<E: Into<ApiError>> From<E> for WakeComputeError {
     154               0 :         fn from(e: E) -> Self {
     155               0 :             Self::ApiError(e.into())
     156               0 :         }
     157                 :     }
     158                 : 
     159                 :     impl UserFacingError for WakeComputeError {
     160               0 :         fn to_string_client(&self) -> String {
     161               0 :             use WakeComputeError::*;
     162               0 :             match self {
     163                 :                 // We shouldn't show user the address even if it's broken.
     164                 :                 // Besides, user is unlikely to care about this detail.
     165               0 :                 BadComputeAddress(_) => REQUEST_FAILED.to_owned(),
     166                 :                 // However, API might return a meaningful error.
     167               0 :                 ApiError(e) => e.to_string_client(),
     168                 :             }
     169               0 :         }
     170                 :     }
     171                 : }
     172                 : 
     173                 : /// Extra query params we'd like to pass to the console.
     174                 : pub struct ConsoleReqExtra<'a> {
     175                 :     /// A unique identifier for a connection.
     176                 :     pub session_id: uuid::Uuid,
     177                 :     /// Name of client application, if set.
     178                 :     pub application_name: Option<&'a str>,
     179                 : }
     180                 : 
     181                 : /// Auth secret which is managed by the cloud.
     182                 : pub enum AuthInfo {
     183                 :     /// Md5 hash of user's password.
     184                 :     Md5([u8; 16]),
     185                 : 
     186                 :     /// [SCRAM](crate::scram) authentication info.
     187                 :     Scram(scram::ServerSecret),
     188                 : }
     189                 : 
     190                 : /// Info for establishing a connection to a compute node.
     191                 : /// This is what we get after auth succeeded, but not before!
     192               0 : #[derive(Clone)]
     193                 : pub struct NodeInfo {
     194                 :     /// Compute node connection params.
     195                 :     /// It's sad that we have to clone this, but this will improve
     196                 :     /// once we migrate to a bespoke connection logic.
     197                 :     pub config: compute::ConnCfg,
     198                 : 
     199                 :     /// Labels for proxy's metrics.
     200                 :     pub aux: Arc<MetricsAuxInfo>,
     201                 : 
     202                 :     /// Whether we should accept self-signed certificates (for testing)
     203                 :     pub allow_self_signed_compute: bool,
     204                 : }
     205                 : 
     206                 : pub type NodeInfoCache = TimedLru<Arc<str>, NodeInfo>;
     207                 : pub type CachedNodeInfo = timed_lru::Cached<&'static NodeInfoCache>;
     208                 : 
     209                 : /// This will allocate per each call, but the http requests alone
     210                 : /// already require a few allocations, so it should be fine.
     211                 : #[async_trait]
     212                 : pub trait Api {
     213                 :     /// Get the client's auth secret for authentication.
     214                 :     async fn get_auth_info(
     215                 :         &self,
     216                 :         extra: &ConsoleReqExtra<'_>,
     217                 :         creds: &ClientCredentials,
     218                 :     ) -> Result<Option<AuthInfo>, errors::GetAuthInfoError>;
     219                 : 
     220                 :     /// Wake up the compute node and return the corresponding connection info.
     221                 :     async fn wake_compute(
     222                 :         &self,
     223                 :         extra: &ConsoleReqExtra<'_>,
     224                 :         creds: &ClientCredentials,
     225                 :     ) -> Result<CachedNodeInfo, errors::WakeComputeError>;
     226                 : }
     227                 : 
     228                 : /// Various caches for [`console`](super).
     229                 : pub struct ApiCaches {
     230                 :     /// Cache for the `wake_compute` API method.
     231                 :     pub node_info: NodeInfoCache,
     232                 : }
        

Generated by: LCOV version 2.1-beta