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

Generated by: LCOV version 2.1-beta