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

Generated by: LCOV version 2.1-beta