Skip to content

Commit ba4259e

Browse files
authored
feat: add instance id and run id (#67)
1 parent ccfa056 commit ba4259e

3 files changed

Lines changed: 157 additions & 1 deletion

File tree

src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use clap::Parser;
1212
use log::{error, info};
1313
use tokio::{select, sync::oneshot};
1414

15-
use crate::utils::observability::init_observability;
15+
use crate::utils::{instance, observability::init_observability};
1616

1717
/// Git hash of the aisix core at build time.
1818
pub const GIT_HASH: &str = env!("VERGEN_GIT_SHA");
@@ -37,6 +37,9 @@ pub struct Args {
3737
pub async fn run(config_file: Option<String>) -> Result<()> {
3838
let (ob_shutdown_signal, ob_shutdown_task) =
3939
init_observability().context("failed to initialize observability")?;
40+
41+
instance::init().context("failed to initialize instance")?;
42+
4043
let config = match config::load(config_file).context("failed to load configuration") {
4144
Ok(c) => Arc::new(c),
4245
Err(e) => {

src/utils/instance.rs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
use std::{path::Path, sync::OnceLock};
2+
3+
use anyhow::{Result, anyhow};
4+
use log::{debug, info, warn};
5+
use uuid::Uuid;
6+
7+
const ID_FILE: &str = "/tmp/aisix_instance_id";
8+
9+
static RUN_ID: OnceLock<String> = OnceLock::new();
10+
static INSTANCE_ID: OnceLock<String> = OnceLock::new();
11+
12+
/// Initialize the instance ID and run ID.
13+
pub fn init() -> Result<()> {
14+
RUN_ID.get_or_init(|| Uuid::new_v4().to_string());
15+
16+
let instance_id = resolve_instance_id_from_path(Path::new(ID_FILE))?;
17+
INSTANCE_ID.get_or_init(|| instance_id);
18+
19+
Ok(())
20+
}
21+
22+
/// Get the run ID.
23+
pub fn run_id() -> String {
24+
RUN_ID.get().cloned().expect("run id has been initialized")
25+
}
26+
27+
/// Get the instance ID.
28+
pub fn instance_id() -> String {
29+
INSTANCE_ID
30+
.get()
31+
.cloned()
32+
.expect("instance id has been initialized")
33+
}
34+
35+
fn resolve_instance_id_from_path(path: &Path) -> Result<String> {
36+
match read_id_file(path) {
37+
Ok(Some(id)) => {
38+
debug!("agent: loaded instance_id from {:?}", path);
39+
return Ok(id);
40+
}
41+
Err(e) => return Err(e),
42+
Ok(None) => {}
43+
};
44+
45+
let id = Uuid::new_v4().to_string();
46+
47+
if let Ok(()) = write_id_file(path, &id) {
48+
info!(
49+
"agent: generated and persisted instance_id={id} to {:?}",
50+
path
51+
);
52+
return Ok(id);
53+
}
54+
55+
warn!(
56+
"agent: instance_id={id} is in-memory only (could not write to {:?}) — identifier will rotate on restart",
57+
path
58+
);
59+
Ok(id)
60+
}
61+
62+
fn read_id_file(path: &Path) -> Result<Option<String>> {
63+
match std::fs::read_to_string(path) {
64+
Ok(content) => {
65+
let trimmed = content.trim();
66+
if !trimmed.is_empty() {
67+
return Ok(Some(trimmed.to_string()));
68+
}
69+
warn!("agent: instance_id file {:?} is empty, ignoring", path);
70+
Ok(None)
71+
}
72+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
73+
Err(e) => Err(anyhow!("failed to read instance_id file {path:?}: {e}")),
74+
}
75+
}
76+
77+
fn write_id_file(path: &Path, id: &str) -> Result<()> {
78+
if let Some(parent) = path.parent() {
79+
if let Err(e) = std::fs::create_dir_all(parent) {
80+
debug!(
81+
"agent: cannot create instance_id parent dir {:?}: {e}",
82+
parent
83+
);
84+
return Err(anyhow!(
85+
"failed to create instance_id parent dir {parent:?}: {e}"
86+
));
87+
}
88+
}
89+
90+
if let Err(e) = std::fs::write(path, id) {
91+
debug!("agent: cannot write instance_id to {:?}: {e}", path);
92+
return Err(anyhow!("failed to write instance_id file {path:?}: {e}"));
93+
}
94+
95+
Ok(())
96+
}
97+
98+
#[cfg(test)]
99+
mod tests {
100+
use std::path::PathBuf;
101+
102+
use super::*;
103+
104+
fn tmp_dir() -> PathBuf {
105+
let dir = std::env::temp_dir().join(format!("aisix_instance_test_{}", Uuid::new_v4()));
106+
std::fs::create_dir_all(&dir).unwrap();
107+
dir
108+
}
109+
110+
#[test]
111+
fn reuses_existing_file() {
112+
let dir = tmp_dir();
113+
let path = dir.join("instance_id");
114+
std::fs::write(&path, "existing-id\n").unwrap();
115+
116+
assert_eq!(resolve_instance_id_from_path(&path).unwrap(), "existing-id");
117+
}
118+
119+
#[test]
120+
fn returns_error_when_existing_path_is_not_readable_as_file() {
121+
let dir = tmp_dir();
122+
let path = dir.join("instance_id");
123+
std::fs::create_dir_all(&path).unwrap();
124+
125+
assert!(resolve_instance_id_from_path(&path).is_err());
126+
}
127+
128+
#[test]
129+
fn writes_new_id_when_file_absent() {
130+
let dir = tmp_dir();
131+
let path = dir.join("sub/instance_id");
132+
133+
let id = resolve_instance_id_from_path(&path).unwrap();
134+
assert!(!id.is_empty());
135+
assert_eq!(std::fs::read_to_string(&path).unwrap(), id);
136+
}
137+
138+
#[test]
139+
fn falls_back_to_memory_when_unwritable() {
140+
use std::os::unix::fs::PermissionsExt;
141+
142+
let dir = tmp_dir();
143+
let blocked = dir.join("blocked");
144+
std::fs::create_dir_all(&blocked).unwrap();
145+
std::fs::set_permissions(&blocked, std::fs::Permissions::from_mode(0o555)).unwrap();
146+
147+
let target = blocked.join("instance_id");
148+
let id = resolve_instance_id_from_path(&target).unwrap();
149+
assert!(!id.is_empty());
150+
assert!(!target.exists());
151+
}
152+
}

src/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod future;
2+
pub mod instance;
23
pub mod jsonschema;
34
pub mod metrics;
45
pub mod observability;

0 commit comments

Comments
 (0)