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 : }
|