LCOV - differential code coverage report
Current view: top level - libs/compute_api/src - spec.rs (source / functions) Coverage Total Hit UBC CBC
Current: cd44433dd675caa99df17a61b18949c8387e2242.info Lines: 78.6 % 84 66 18 66
Current Date: 2024-01-09 02:06:09 Functions: 33.4 % 314 105 209 105
Baseline: 66c52a629a0f4a503e193045e0df4c77139e344b.info
Baseline Date: 2024-01-08 15:34:46

           TLA  Line data    Source code
       1                 : //! `ComputeSpec` represents the contents of the spec.json file.
       2                 : //!
       3                 : //! The spec.json file is used to pass information to 'compute_ctl'. It contains
       4                 : //! all the information needed to start up the right version of PostgreSQL,
       5                 : //! and connect it to the storage nodes.
       6                 : use std::collections::HashMap;
       7                 : 
       8                 : use serde::{Deserialize, Serialize};
       9                 : use utils::id::{TenantId, TimelineId};
      10                 : use utils::lsn::Lsn;
      11                 : 
      12                 : use regex::Regex;
      13                 : use remote_storage::RemotePath;
      14                 : 
      15                 : /// String type alias representing Postgres identifier and
      16                 : /// intended to be used for DB / role names.
      17                 : pub type PgIdent = String;
      18                 : 
      19                 : /// Cluster spec or configuration represented as an optional number of
      20                 : /// delta operations + final cluster state description.
      21 CBC       28435 : #[derive(Clone, Debug, Default, Deserialize, Serialize)]
      22                 : pub struct ComputeSpec {
      23                 :     pub format_version: f32,
      24                 : 
      25                 :     // The control plane also includes a 'timestamp' field in the JSON document,
      26                 :     // but we don't use it for anything. Serde will ignore missing fields when
      27                 :     // deserializing it.
      28                 :     pub operation_uuid: Option<String>,
      29                 : 
      30                 :     /// Compute features to enable. These feature flags are provided, when we
      31                 :     /// know all the details about client's compute, so they cannot be used
      32                 :     /// to change `Empty` compute behavior.
      33                 :     #[serde(default)]
      34                 :     pub features: Vec<ComputeFeature>,
      35                 : 
      36                 :     /// Expected cluster state at the end of transition process.
      37                 :     pub cluster: Cluster,
      38                 :     pub delta_operations: Option<Vec<DeltaOp>>,
      39                 : 
      40                 :     /// An optinal hint that can be passed to speed up startup time if we know
      41                 :     /// that no pg catalog mutations (like role creation, database creation,
      42                 :     /// extension creation) need to be done on the actual database to start.
      43                 :     #[serde(default)] // Default false
      44                 :     pub skip_pg_catalog_updates: bool,
      45                 : 
      46                 :     // Information needed to connect to the storage layer.
      47                 :     //
      48                 :     // `tenant_id`, `timeline_id` and `pageserver_connstring` are always needed.
      49                 :     //
      50                 :     // Depending on `mode`, this can be a primary read-write node, a read-only
      51                 :     // replica, or a read-only node pinned at an older LSN.
      52                 :     // `safekeeper_connstrings` must be set for a primary.
      53                 :     //
      54                 :     // For backwards compatibility, the control plane may leave out all of
      55                 :     // these, and instead set the "neon.tenant_id", "neon.timeline_id",
      56                 :     // etc. GUCs in cluster.settings. TODO: Once the control plane has been
      57                 :     // updated to fill these fields, we can make these non optional.
      58                 :     pub tenant_id: Option<TenantId>,
      59                 : 
      60                 :     pub timeline_id: Option<TimelineId>,
      61                 : 
      62                 :     pub pageserver_connstring: Option<String>,
      63                 : 
      64                 :     #[serde(default)]
      65                 :     pub safekeeper_connstrings: Vec<String>,
      66                 : 
      67                 :     #[serde(default)]
      68                 :     pub mode: ComputeMode,
      69                 : 
      70                 :     /// If set, 'storage_auth_token' is used as the password to authenticate to
      71                 :     /// the pageserver and safekeepers.
      72                 :     pub storage_auth_token: Option<String>,
      73                 : 
      74                 :     // information about available remote extensions
      75                 :     pub remote_extensions: Option<RemoteExtSpec>,
      76                 : 
      77                 :     pub pgbouncer_settings: Option<HashMap<String, String>>,
      78                 : }
      79                 : 
      80                 : /// Feature flag to signal `compute_ctl` to enable certain experimental functionality.
      81               4 : #[derive(Serialize, Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
      82                 : #[serde(rename_all = "snake_case")]
      83                 : pub enum ComputeFeature {
      84                 :     // XXX: Add more feature flags here.
      85                 : 
      86                 :     // This is a special feature flag that is used to represent unknown feature flags.
      87                 :     // Basically all unknown to enum flags are represented as this one. See unit test
      88                 :     // `parse_unknown_features()` for more details.
      89                 :     #[serde(other)]
      90                 :     UnknownFeature,
      91                 : }
      92                 : 
      93              59 : #[derive(Clone, Debug, Default, Deserialize, Serialize)]
      94                 : pub struct RemoteExtSpec {
      95                 :     pub public_extensions: Option<Vec<String>>,
      96                 :     pub custom_extensions: Option<Vec<String>>,
      97                 :     pub library_index: HashMap<String, String>,
      98                 :     pub extension_data: HashMap<String, ExtensionData>,
      99                 : }
     100                 : 
     101              60 : #[derive(Clone, Debug, Serialize, Deserialize)]
     102                 : pub struct ExtensionData {
     103                 :     pub control_data: HashMap<String, String>,
     104                 :     pub archive_path: String,
     105                 : }
     106                 : 
     107                 : impl RemoteExtSpec {
     108               2 :     pub fn get_ext(
     109               2 :         &self,
     110               2 :         ext_name: &str,
     111               2 :         is_library: bool,
     112               2 :         build_tag: &str,
     113               2 :         pg_major_version: &str,
     114               2 :     ) -> anyhow::Result<(String, RemotePath)> {
     115               2 :         let mut real_ext_name = ext_name;
     116               2 :         if is_library {
     117                 :             // sometimes library names might have a suffix like
     118                 :             // library.so or library.so.3. We strip this off
     119                 :             // because library_index is based on the name without the file extension
     120 UBC           0 :             let strip_lib_suffix = Regex::new(r"\.so.*").unwrap();
     121               0 :             let lib_raw_name = strip_lib_suffix.replace(real_ext_name, "").to_string();
     122               0 : 
     123               0 :             real_ext_name = self
     124               0 :                 .library_index
     125               0 :                 .get(&lib_raw_name)
     126               0 :                 .ok_or(anyhow::anyhow!("library {} is not found", lib_raw_name))?;
     127 CBC           2 :         }
     128                 : 
     129                 :         // Check if extension is present in public or custom.
     130                 :         // If not, then it is not allowed to be used by this compute.
     131               2 :         if let Some(public_extensions) = &self.public_extensions {
     132 UBC           0 :             if !public_extensions.contains(&real_ext_name.to_string()) {
     133               0 :                 if let Some(custom_extensions) = &self.custom_extensions {
     134               0 :                     if !custom_extensions.contains(&real_ext_name.to_string()) {
     135               0 :                         return Err(anyhow::anyhow!("extension {} is not found", real_ext_name));
     136               0 :                     }
     137               0 :                 }
     138               0 :             }
     139 CBC           2 :         }
     140                 : 
     141               2 :         match self.extension_data.get(real_ext_name) {
     142               2 :             Some(_ext_data) => {
     143               2 :                 // Construct the path to the extension archive
     144               2 :                 // BUILD_TAG/PG_MAJOR_VERSION/extensions/EXTENSION_NAME.tar.zst
     145               2 :                 //
     146               2 :                 // Keep it in sync with path generation in
     147               2 :                 // https://github.com/neondatabase/build-custom-extensions/tree/main
     148               2 :                 let archive_path_str =
     149               2 :                     format!("{build_tag}/{pg_major_version}/extensions/{real_ext_name}.tar.zst");
     150               2 :                 Ok((
     151               2 :                     real_ext_name.to_string(),
     152               2 :                     RemotePath::from_string(&archive_path_str)?,
     153                 :                 ))
     154                 :             }
     155 UBC           0 :             None => Err(anyhow::anyhow!(
     156               0 :                 "real_ext_name {} is not found",
     157               0 :                 real_ext_name
     158               0 :             )),
     159                 :         }
     160 CBC           2 :     }
     161                 : }
     162                 : 
     163            9698 : #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
     164                 : pub enum ComputeMode {
     165                 :     /// A read-write node
     166                 :     #[default]
     167                 :     Primary,
     168                 :     /// A read-only node, pinned at a particular LSN
     169                 :     Static(Lsn),
     170                 :     /// A read-only node that follows the tip of the branch in hot standby mode
     171                 :     ///
     172                 :     /// Future versions may want to distinguish between replicas with hot standby
     173                 :     /// feedback and other kinds of replication configurations.
     174                 :     Replica,
     175                 : }
     176                 : 
     177           14735 : #[derive(Clone, Debug, Default, Deserialize, Serialize)]
     178                 : pub struct Cluster {
     179                 :     pub cluster_id: Option<String>,
     180                 :     pub name: Option<String>,
     181                 :     pub state: Option<String>,
     182                 :     pub roles: Vec<Role>,
     183                 :     pub databases: Vec<Database>,
     184                 : 
     185                 :     /// Desired contents of 'postgresql.conf' file. (The 'compute_ctl'
     186                 :     /// tool may add additional settings to the final file.)
     187                 :     pub postgresql_conf: Option<String>,
     188                 : 
     189                 :     /// Additional settings that will be appended to the 'postgresql.conf' file.
     190                 :     pub settings: GenericOptions,
     191                 : }
     192                 : 
     193                 : /// Single cluster state changing operation that could not be represented as
     194                 : /// a static `Cluster` structure. For example:
     195                 : /// - DROP DATABASE
     196                 : /// - DROP ROLE
     197                 : /// - ALTER ROLE name RENAME TO new_name
     198                 : /// - ALTER DATABASE name RENAME TO new_name
     199             120 : #[derive(Clone, Debug, Deserialize, Serialize)]
     200                 : pub struct DeltaOp {
     201                 :     pub action: String,
     202                 :     pub name: PgIdent,
     203                 :     pub new_name: Option<PgIdent>,
     204                 : }
     205                 : 
     206                 : /// Rust representation of Postgres role info with only those fields
     207                 : /// that matter for us.
     208             180 : #[derive(Clone, Debug, Deserialize, Serialize)]
     209                 : pub struct Role {
     210                 :     pub name: PgIdent,
     211                 :     pub encrypted_password: Option<String>,
     212                 :     pub options: GenericOptions,
     213                 : }
     214                 : 
     215                 : /// Rust representation of Postgres database info with only those fields
     216                 : /// that matter for us.
     217            2653 : #[derive(Clone, Debug, Deserialize, Serialize)]
     218                 : pub struct Database {
     219                 :     pub name: PgIdent,
     220                 :     pub owner: PgIdent,
     221                 :     pub options: GenericOptions,
     222                 :     // These are derived flags, not present in the spec file.
     223                 :     // They are never set by the control plane.
     224                 :     #[serde(skip_deserializing, default)]
     225                 :     pub restrict_conn: bool,
     226                 :     #[serde(skip_deserializing, default)]
     227                 :     pub invalid: bool,
     228                 : }
     229                 : 
     230                 : /// Common type representing both SQL statement params with or without value,
     231                 : /// like `LOGIN` or `OWNER username` in the `CREATE/ALTER ROLE`, and config
     232                 : /// options like `wal_level = logical`.
     233             910 : #[derive(Clone, Debug, Deserialize, Serialize)]
     234                 : pub struct GenericOption {
     235                 :     pub name: String,
     236                 :     pub value: Option<String>,
     237                 :     pub vartype: String,
     238                 : }
     239                 : 
     240                 : /// Optional collection of `GenericOption`'s. Type alias allows us to
     241                 : /// declare a `trait` on it.
     242                 : pub type GenericOptions = Option<Vec<GenericOption>>;
     243                 : 
     244                 : #[cfg(test)]
     245                 : mod tests {
     246                 :     use super::*;
     247                 :     use std::fs::File;
     248                 : 
     249               1 :     #[test]
     250               1 :     fn parse_spec_file() {
     251               1 :         let file = File::open("tests/cluster_spec.json").unwrap();
     252               1 :         let spec: ComputeSpec = serde_json::from_reader(file).unwrap();
     253                 : 
     254                 :         // Features list defaults to empty vector.
     255               1 :         assert!(spec.features.is_empty());
     256               1 :     }
     257                 : 
     258               1 :     #[test]
     259               1 :     fn parse_unknown_fields() {
     260               1 :         // Forward compatibility test
     261               1 :         let file = File::open("tests/cluster_spec.json").unwrap();
     262               1 :         let mut json: serde_json::Value = serde_json::from_reader(file).unwrap();
     263               1 :         let ob = json.as_object_mut().unwrap();
     264               1 :         ob.insert("unknown_field_123123123".into(), "hello".into());
     265               1 :         let _spec: ComputeSpec = serde_json::from_value(json).unwrap();
     266               1 :     }
     267                 : 
     268               1 :     #[test]
     269               1 :     fn parse_unknown_features() {
     270               1 :         // Test that unknown feature flags do not cause any errors.
     271               1 :         let file = File::open("tests/cluster_spec.json").unwrap();
     272               1 :         let mut json: serde_json::Value = serde_json::from_reader(file).unwrap();
     273               1 :         let ob = json.as_object_mut().unwrap();
     274               1 : 
     275               1 :         // Add unknown feature flags.
     276               1 :         let features = vec!["foo_bar_feature", "baz_feature"];
     277               1 :         ob.insert("features".into(), features.into());
     278               1 : 
     279               1 :         let spec: ComputeSpec = serde_json::from_value(json).unwrap();
     280                 : 
     281               1 :         assert!(spec.features.len() == 2);
     282               1 :         assert!(spec.features.contains(&ComputeFeature::UnknownFeature));
     283               1 :         assert_eq!(spec.features, vec![ComputeFeature::UnknownFeature; 2]);
     284               1 :     }
     285                 : }
        

Generated by: LCOV version 2.1-beta