LCOV - code coverage report
Current view: top level - compute_tools/src - config.rs (source / functions) Coverage Total Hit
Test: 1e20c4f2b28aa592527961bb32170ebbd2c9172f.info Lines: 9.9 % 181 18
Test Date: 2025-07-16 12:29:03 Functions: 20.0 % 5 1

            Line data    Source code
       1              : use anyhow::Result;
       2              : use std::fmt::Write as FmtWrite;
       3              : use std::fs::{File, OpenOptions};
       4              : use std::io;
       5              : use std::io::Write;
       6              : use std::io::prelude::*;
       7              : use std::path::Path;
       8              : 
       9              : use compute_api::responses::TlsConfig;
      10              : use compute_api::spec::{ComputeAudit, ComputeMode, ComputeSpec, GenericOption};
      11              : 
      12              : use crate::compute::ComputeNodeParams;
      13              : use crate::pg_helpers::{
      14              :     GenericOptionExt, GenericOptionsSearch, PgOptionsSerialize, escape_conf_value,
      15              : };
      16              : use crate::tls::{self, SERVER_CRT, SERVER_KEY};
      17              : 
      18              : /// Check that `line` is inside a text file and put it there if it is not.
      19              : /// Create file if it doesn't exist.
      20            3 : pub fn line_in_file(path: &Path, line: &str) -> Result<bool> {
      21            3 :     let mut file = OpenOptions::new()
      22            3 :         .read(true)
      23            3 :         .write(true)
      24            3 :         .create(true)
      25            3 :         .append(false)
      26            3 :         .truncate(false)
      27            3 :         .open(path)?;
      28            3 :     let buf = io::BufReader::new(&file);
      29            3 :     let mut count: usize = 0;
      30              : 
      31            5 :     for l in buf.lines() {
      32            5 :         if l? == line {
      33            1 :             return Ok(false);
      34            4 :         }
      35            4 :         count = 1;
      36              :     }
      37              : 
      38            2 :     write!(file, "{}{}", "\n".repeat(count), line)?;
      39            2 :     Ok(true)
      40            3 : }
      41              : 
      42              : /// Create or completely rewrite configuration file specified by `path`
      43            0 : pub fn write_postgres_conf(
      44            0 :     pgdata_path: &Path,
      45            0 :     params: &ComputeNodeParams,
      46            0 :     spec: &ComputeSpec,
      47            0 :     extension_server_port: u16,
      48            0 :     tls_config: &Option<TlsConfig>,
      49            0 : ) -> Result<()> {
      50            0 :     let path = pgdata_path.join("postgresql.conf");
      51              :     // File::create() destroys the file content if it exists.
      52            0 :     let mut file = File::create(path)?;
      53              : 
      54              :     // Write the postgresql.conf content from the spec file as is.
      55            0 :     if let Some(conf) = &spec.cluster.postgresql_conf {
      56            0 :         writeln!(file, "{conf}")?;
      57            0 :     }
      58              : 
      59              :     // Add options for connecting to storage
      60            0 :     writeln!(file, "# Neon storage settings")?;
      61            0 :     if let Some(s) = &spec.pageserver_connstring {
      62            0 :         writeln!(file, "neon.pageserver_connstring={}", escape_conf_value(s))?;
      63            0 :     }
      64            0 :     if let Some(stripe_size) = spec.shard_stripe_size {
      65            0 :         writeln!(file, "neon.stripe_size={stripe_size}")?;
      66            0 :     }
      67            0 :     if !spec.safekeeper_connstrings.is_empty() {
      68            0 :         let mut neon_safekeepers_value = String::new();
      69            0 :         tracing::info!(
      70            0 :             "safekeepers_connstrings is not zero, gen: {:?}",
      71              :             spec.safekeepers_generation
      72              :         );
      73              :         // If generation is given, prepend sk list with g#number:
      74            0 :         if let Some(generation) = spec.safekeepers_generation {
      75            0 :             write!(neon_safekeepers_value, "g#{generation}:")?;
      76            0 :         }
      77            0 :         neon_safekeepers_value.push_str(&spec.safekeeper_connstrings.join(","));
      78            0 :         writeln!(
      79            0 :             file,
      80            0 :             "neon.safekeepers={}",
      81            0 :             escape_conf_value(&neon_safekeepers_value)
      82            0 :         )?;
      83            0 :     }
      84            0 :     if let Some(s) = &spec.tenant_id {
      85            0 :         writeln!(file, "neon.tenant_id={}", escape_conf_value(&s.to_string()))?;
      86            0 :     }
      87            0 :     if let Some(s) = &spec.timeline_id {
      88            0 :         writeln!(
      89            0 :             file,
      90            0 :             "neon.timeline_id={}",
      91            0 :             escape_conf_value(&s.to_string())
      92            0 :         )?;
      93            0 :     }
      94            0 :     if let Some(s) = &spec.project_id {
      95            0 :         writeln!(file, "neon.project_id={}", escape_conf_value(s))?;
      96            0 :     }
      97            0 :     if let Some(s) = &spec.branch_id {
      98            0 :         writeln!(file, "neon.branch_id={}", escape_conf_value(s))?;
      99            0 :     }
     100            0 :     if let Some(s) = &spec.endpoint_id {
     101            0 :         writeln!(file, "neon.endpoint_id={}", escape_conf_value(s))?;
     102            0 :     }
     103              : 
     104              :     // tls
     105            0 :     if let Some(tls_config) = tls_config {
     106            0 :         writeln!(file, "ssl = on")?;
     107              : 
     108              :         // postgres requires the keyfile to be in a secure file,
     109              :         // currently too complicated to ensure that at the VM level,
     110              :         // so we just copy them to another file instead. :shrug:
     111            0 :         tls::update_key_path_blocking(pgdata_path, tls_config);
     112              : 
     113              :         // these are the default, but good to be explicit.
     114            0 :         writeln!(file, "ssl_cert_file = '{SERVER_CRT}'")?;
     115            0 :         writeln!(file, "ssl_key_file = '{SERVER_KEY}'")?;
     116            0 :     }
     117              : 
     118              :     // Locales
     119            0 :     if cfg!(target_os = "macos") {
     120            0 :         writeln!(file, "lc_messages='C'")?;
     121            0 :         writeln!(file, "lc_monetary='C'")?;
     122            0 :         writeln!(file, "lc_time='C'")?;
     123            0 :         writeln!(file, "lc_numeric='C'")?;
     124              :     } else {
     125            0 :         writeln!(file, "lc_messages='C.UTF-8'")?;
     126            0 :         writeln!(file, "lc_monetary='C.UTF-8'")?;
     127            0 :         writeln!(file, "lc_time='C.UTF-8'")?;
     128            0 :         writeln!(file, "lc_numeric='C.UTF-8'")?;
     129              :     }
     130              : 
     131            0 :     writeln!(file, "neon.compute_mode={}", spec.mode.to_type_str())?;
     132            0 :     match spec.mode {
     133            0 :         ComputeMode::Primary => {}
     134            0 :         ComputeMode::Static(lsn) => {
     135              :             // hot_standby is 'on' by default, but let's be explicit
     136            0 :             writeln!(file, "hot_standby=on")?;
     137            0 :             writeln!(file, "recovery_target_lsn='{lsn}'")?;
     138              :         }
     139              :         ComputeMode::Replica => {
     140              :             // hot_standby is 'on' by default, but let's be explicit
     141            0 :             writeln!(file, "hot_standby=on")?;
     142              :         }
     143              :     }
     144              : 
     145            0 :     if cfg!(target_os = "linux") {
     146              :         // Check /proc/sys/vm/overcommit_memory -- if it equals 2 (i.e. linux memory overcommit is
     147              :         // disabled), then the control plane has enabled swap and we should set
     148              :         // dynamic_shared_memory_type = 'mmap'.
     149              :         //
     150              :         // This is (maybe?) temporary - for more, see https://github.com/neondatabase/cloud/issues/12047.
     151            0 :         let overcommit_memory_contents = std::fs::read_to_string("/proc/sys/vm/overcommit_memory")
     152              :             // ignore any errors - they may be expected to occur under certain situations (e.g. when
     153              :             // not running in Linux).
     154            0 :             .unwrap_or_else(|_| String::new());
     155            0 :         if overcommit_memory_contents.trim() == "2" {
     156            0 :             let opt = GenericOption {
     157            0 :                 name: "dynamic_shared_memory_type".to_owned(),
     158            0 :                 value: Some("mmap".to_owned()),
     159            0 :                 vartype: "enum".to_owned(),
     160            0 :             };
     161              : 
     162            0 :             writeln!(file, "{}", opt.to_pg_setting())?;
     163            0 :         }
     164            0 :     }
     165              : 
     166            0 :     writeln!(
     167            0 :         file,
     168            0 :         "neon.privileged_role_name={}",
     169            0 :         escape_conf_value(params.privileged_role_name.as_str())
     170            0 :     )?;
     171              : 
     172              :     // If there are any extra options in the 'settings' field, append those
     173            0 :     if spec.cluster.settings.is_some() {
     174            0 :         writeln!(file, "# Managed by compute_ctl: begin")?;
     175            0 :         write!(file, "{}", spec.cluster.settings.as_pg_settings())?;
     176            0 :         writeln!(file, "# Managed by compute_ctl: end")?;
     177            0 :     }
     178              : 
     179              :     // If base audit logging is enabled, configure it.
     180              :     // In this setup, the audit log will be written to the standard postgresql log.
     181              :     //
     182              :     // If compliance audit logging is enabled, configure pgaudit.
     183              :     //
     184              :     // Note, that this is called after the settings from spec are written.
     185              :     // This way we always override the settings from the spec
     186              :     // and don't allow the user or the control plane admin to change them.
     187            0 :     match spec.audit_log_level {
     188            0 :         ComputeAudit::Disabled => {}
     189              :         ComputeAudit::Log | ComputeAudit::Base => {
     190            0 :             writeln!(file, "# Managed by compute_ctl base audit settings: start")?;
     191            0 :             writeln!(file, "pgaudit.log='ddl,role'")?;
     192              :             // Disable logging of catalog queries to reduce the noise
     193            0 :             writeln!(file, "pgaudit.log_catalog=off")?;
     194              : 
     195            0 :             if let Some(libs) = spec.cluster.settings.find("shared_preload_libraries") {
     196            0 :                 let mut extra_shared_preload_libraries = String::new();
     197            0 :                 if !libs.contains("pgaudit") {
     198            0 :                     extra_shared_preload_libraries.push_str(",pgaudit");
     199            0 :                 }
     200            0 :                 writeln!(
     201            0 :                     file,
     202            0 :                     "shared_preload_libraries='{libs}{extra_shared_preload_libraries}'"
     203            0 :                 )?;
     204              :             } else {
     205              :                 // Typically, this should be unreacheable,
     206              :                 // because we always set at least some shared_preload_libraries in the spec
     207              :                 // but let's handle it explicitly anyway.
     208            0 :                 writeln!(file, "shared_preload_libraries='neon,pgaudit'")?;
     209              :             }
     210            0 :             writeln!(file, "# Managed by compute_ctl base audit settings: end")?;
     211              :         }
     212              :         ComputeAudit::Hipaa | ComputeAudit::Extended | ComputeAudit::Full => {
     213            0 :             writeln!(
     214            0 :                 file,
     215            0 :                 "# Managed by compute_ctl compliance audit settings: begin"
     216            0 :             )?;
     217              :             // Enable logging of parameters.
     218              :             // This is very verbose and may contain sensitive data.
     219            0 :             if spec.audit_log_level == ComputeAudit::Full {
     220            0 :                 writeln!(file, "pgaudit.log_parameter=on")?;
     221            0 :                 writeln!(file, "pgaudit.log='all'")?;
     222              :             } else {
     223            0 :                 writeln!(file, "pgaudit.log_parameter=off")?;
     224            0 :                 writeln!(file, "pgaudit.log='all, -misc'")?;
     225              :             }
     226              :             // Disable logging of catalog queries
     227              :             // The catalog doesn't contain sensitive data, so we don't need to audit it.
     228            0 :             writeln!(file, "pgaudit.log_catalog=off")?;
     229              :             // Set log rotation to 5 minutes
     230              :             // TODO: tune this after performance testing
     231            0 :             writeln!(file, "pgaudit.log_rotation_age=5")?;
     232              : 
     233              :             // Enable audit logs for pg_session_jwt extension
     234              :             // TODO: Consider a good approach for shipping pg_session_jwt logs to the same sink as
     235              :             // pgAudit - additional context in https://github.com/neondatabase/cloud/issues/28863
     236              :             //
     237              :             // writeln!(file, "pg_session_jwt.audit_log=on")?;
     238              : 
     239              :             // Add audit shared_preload_libraries, if they are not present.
     240              :             //
     241              :             // The caller who sets the flag is responsible for ensuring that the necessary
     242              :             // shared_preload_libraries are present in the compute image,
     243              :             // otherwise the compute start will fail.
     244            0 :             if let Some(libs) = spec.cluster.settings.find("shared_preload_libraries") {
     245            0 :                 let mut extra_shared_preload_libraries = String::new();
     246            0 :                 if !libs.contains("pgaudit") {
     247            0 :                     extra_shared_preload_libraries.push_str(",pgaudit");
     248            0 :                 }
     249            0 :                 if !libs.contains("pgauditlogtofile") {
     250            0 :                     extra_shared_preload_libraries.push_str(",pgauditlogtofile");
     251            0 :                 }
     252            0 :                 writeln!(
     253            0 :                     file,
     254            0 :                     "shared_preload_libraries='{libs}{extra_shared_preload_libraries}'"
     255            0 :                 )?;
     256              :             } else {
     257              :                 // Typically, this should be unreacheable,
     258              :                 // because we always set at least some shared_preload_libraries in the spec
     259              :                 // but let's handle it explicitly anyway.
     260            0 :                 writeln!(
     261            0 :                     file,
     262            0 :                     "shared_preload_libraries='neon,pgaudit,pgauditlogtofile'"
     263            0 :                 )?;
     264              :             }
     265            0 :             writeln!(
     266            0 :                 file,
     267            0 :                 "# Managed by compute_ctl compliance audit settings: end"
     268            0 :             )?;
     269              :         }
     270              :     }
     271              : 
     272            0 :     writeln!(file, "neon.extension_server_port={extension_server_port}")?;
     273              : 
     274            0 :     if spec.drop_subscriptions_before_start {
     275            0 :         writeln!(file, "neon.disable_logical_replication_subscribers=true")?;
     276              :     } else {
     277              :         // be explicit about the default value
     278            0 :         writeln!(file, "neon.disable_logical_replication_subscribers=false")?;
     279              :     }
     280              : 
     281              :     // We need Postgres to send logs to rsyslog so that we can forward them
     282              :     // further to customers' log aggregation systems.
     283            0 :     if spec.logs_export_host.is_some() {
     284            0 :         writeln!(file, "log_destination='stderr,syslog'")?;
     285            0 :     }
     286              : 
     287              :     // This is essential to keep this line at the end of the file,
     288              :     // because it is intended to override any settings above.
     289            0 :     writeln!(file, "include_if_exists = 'compute_ctl_temp_override.conf'")?;
     290              : 
     291            0 :     Ok(())
     292            0 : }
     293              : 
     294            0 : pub fn with_compute_ctl_tmp_override<F>(pgdata_path: &Path, options: &str, exec: F) -> Result<()>
     295            0 : where
     296            0 :     F: FnOnce() -> Result<()>,
     297              : {
     298            0 :     let path = pgdata_path.join("compute_ctl_temp_override.conf");
     299            0 :     let mut file = File::create(path)?;
     300            0 :     write!(file, "{options}")?;
     301              : 
     302            0 :     let res = exec();
     303              : 
     304            0 :     file.set_len(0)?;
     305              : 
     306            0 :     res
     307            0 : }
        

Generated by: LCOV version 2.1-beta