LCOV - code coverage report
Current view: top level - compute_tools/src - config.rs (source / functions) Coverage Total Hit
Test: 62212f4d57a7ad0f69dc82a04629a0bbd5f7c824.info Lines: 12.2 % 148 18
Test Date: 2025-03-17 10:41:39 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              : 
      93              :     // tls
      94            0 :     if let Some(tls_config) = tls_config {
      95            0 :         writeln!(file, "ssl = on")?;
      96              : 
      97              :         // postgres requires the keyfile to be in a secure file,
      98              :         // currently too complicated to ensure that at the VM level,
      99              :         // so we just copy them to another file instead. :shrug:
     100            0 :         tls::update_key_path_blocking(pgdata_path, tls_config);
     101            0 : 
     102            0 :         // these are the default, but good to be explicit.
     103            0 :         writeln!(file, "ssl_cert_file = '{}'", SERVER_CRT)?;
     104            0 :         writeln!(file, "ssl_key_file = '{}'", SERVER_KEY)?;
     105            0 :     }
     106              : 
     107              :     // Locales
     108            0 :     if cfg!(target_os = "macos") {
     109            0 :         writeln!(file, "lc_messages='C'")?;
     110            0 :         writeln!(file, "lc_monetary='C'")?;
     111            0 :         writeln!(file, "lc_time='C'")?;
     112            0 :         writeln!(file, "lc_numeric='C'")?;
     113              :     } else {
     114            0 :         writeln!(file, "lc_messages='C.UTF-8'")?;
     115            0 :         writeln!(file, "lc_monetary='C.UTF-8'")?;
     116            0 :         writeln!(file, "lc_time='C.UTF-8'")?;
     117            0 :         writeln!(file, "lc_numeric='C.UTF-8'")?;
     118              :     }
     119              : 
     120            0 :     match spec.mode {
     121            0 :         ComputeMode::Primary => {}
     122            0 :         ComputeMode::Static(lsn) => {
     123            0 :             // hot_standby is 'on' by default, but let's be explicit
     124            0 :             writeln!(file, "hot_standby=on")?;
     125            0 :             writeln!(file, "recovery_target_lsn='{lsn}'")?;
     126              :         }
     127              :         ComputeMode::Replica => {
     128              :             // hot_standby is 'on' by default, but let's be explicit
     129            0 :             writeln!(file, "hot_standby=on")?;
     130              :         }
     131              :     }
     132              : 
     133            0 :     if cfg!(target_os = "linux") {
     134              :         // Check /proc/sys/vm/overcommit_memory -- if it equals 2 (i.e. linux memory overcommit is
     135              :         // disabled), then the control plane has enabled swap and we should set
     136              :         // dynamic_shared_memory_type = 'mmap'.
     137              :         //
     138              :         // This is (maybe?) temporary - for more, see https://github.com/neondatabase/cloud/issues/12047.
     139            0 :         let overcommit_memory_contents = std::fs::read_to_string("/proc/sys/vm/overcommit_memory")
     140            0 :             // ignore any errors - they may be expected to occur under certain situations (e.g. when
     141            0 :             // not running in Linux).
     142            0 :             .unwrap_or_else(|_| String::new());
     143            0 :         if overcommit_memory_contents.trim() == "2" {
     144            0 :             let opt = GenericOption {
     145            0 :                 name: "dynamic_shared_memory_type".to_owned(),
     146            0 :                 value: Some("mmap".to_owned()),
     147            0 :                 vartype: "enum".to_owned(),
     148            0 :             };
     149            0 : 
     150            0 :             writeln!(file, "{}", opt.to_pg_setting())?;
     151            0 :         }
     152            0 :     }
     153              : 
     154              :     // If there are any extra options in the 'settings' field, append those
     155            0 :     if spec.cluster.settings.is_some() {
     156            0 :         writeln!(file, "# Managed by compute_ctl: begin")?;
     157            0 :         write!(file, "{}", spec.cluster.settings.as_pg_settings())?;
     158            0 :         writeln!(file, "# Managed by compute_ctl: end")?;
     159            0 :     }
     160              : 
     161              :     // If audit logging is enabled, configure pgaudit.
     162              :     //
     163              :     // Note, that this is called after the settings from spec are written.
     164              :     // This way we always override the settings from the spec
     165              :     // and don't allow the user or the control plane admin to change them.
     166            0 :     if let ComputeAudit::Hipaa = spec.audit_log_level {
     167            0 :         writeln!(file, "# Managed by compute_ctl audit settings: begin")?;
     168              :         // This log level is very verbose
     169              :         // but this is necessary for HIPAA compliance.
     170              :         // Exclude 'misc' category, because it doesn't contain anythig relevant.
     171            0 :         writeln!(file, "pgaudit.log='all, -misc'")?;
     172            0 :         writeln!(file, "pgaudit.log_parameter=on")?;
     173              :         // Disable logging of catalog queries
     174              :         // The catalog doesn't contain sensitive data, so we don't need to audit it.
     175            0 :         writeln!(file, "pgaudit.log_catalog=off")?;
     176              :         // Set log rotation to 5 minutes
     177              :         // TODO: tune this after performance testing
     178            0 :         writeln!(file, "pgaudit.log_rotation_age=5")?;
     179              : 
     180              :         // Add audit shared_preload_libraries, if they are not present.
     181              :         //
     182              :         // The caller who sets the flag is responsible for ensuring that the necessary
     183              :         // shared_preload_libraries are present in the compute image,
     184              :         // otherwise the compute start will fail.
     185            0 :         if let Some(libs) = spec.cluster.settings.find("shared_preload_libraries") {
     186            0 :             let mut extra_shared_preload_libraries = String::new();
     187            0 :             if !libs.contains("pgaudit") {
     188            0 :                 extra_shared_preload_libraries.push_str(",pgaudit");
     189            0 :             }
     190            0 :             if !libs.contains("pgauditlogtofile") {
     191            0 :                 extra_shared_preload_libraries.push_str(",pgauditlogtofile");
     192            0 :             }
     193            0 :             writeln!(
     194            0 :                 file,
     195            0 :                 "shared_preload_libraries='{}{}'",
     196            0 :                 libs, extra_shared_preload_libraries
     197            0 :             )?;
     198              :         } else {
     199              :             // Typically, this should be unreacheable,
     200              :             // because we always set at least some shared_preload_libraries in the spec
     201              :             // but let's handle it explicitly anyway.
     202            0 :             writeln!(
     203            0 :                 file,
     204            0 :                 "shared_preload_libraries='neon,pgaudit,pgauditlogtofile'"
     205            0 :             )?;
     206              :         }
     207            0 :         writeln!(file, "# Managed by compute_ctl audit settings: end")?;
     208            0 :     }
     209              : 
     210            0 :     writeln!(file, "neon.extension_server_port={}", extension_server_port)?;
     211              : 
     212            0 :     if spec.drop_subscriptions_before_start {
     213            0 :         writeln!(file, "neon.disable_logical_replication_subscribers=true")?;
     214              :     } else {
     215              :         // be explicit about the default value
     216            0 :         writeln!(file, "neon.disable_logical_replication_subscribers=false")?;
     217              :     }
     218              : 
     219              :     // This is essential to keep this line at the end of the file,
     220              :     // because it is intended to override any settings above.
     221            0 :     writeln!(file, "include_if_exists = 'compute_ctl_temp_override.conf'")?;
     222              : 
     223            0 :     Ok(())
     224            0 : }
     225              : 
     226            0 : pub fn with_compute_ctl_tmp_override<F>(pgdata_path: &Path, options: &str, exec: F) -> Result<()>
     227            0 : where
     228            0 :     F: FnOnce() -> Result<()>,
     229            0 : {
     230            0 :     let path = pgdata_path.join("compute_ctl_temp_override.conf");
     231            0 :     let mut file = File::create(path)?;
     232            0 :     write!(file, "{}", options)?;
     233              : 
     234            0 :     let res = exec();
     235            0 : 
     236            0 :     file.set_len(0)?;
     237              : 
     238            0 :     res
     239            0 : }
        

Generated by: LCOV version 2.1-beta