cloud_detect/providers/
openstack.rs1use std::path::Path;
4use std::time::Duration;
5
6use async_trait::async_trait;
7use tokio::fs;
8use tokio::sync::mpsc::Sender;
9use tracing::{debug, error, info, instrument};
10
11use crate::{Provider, ProviderId};
12
13const METADATA_URI: &str = "http://169.254.169.254";
14const METADATA_PATH: &str = "/openstack/";
15const PRODUCT_NAME_FILE: &str = "/sys/class/dmi/id/product_name";
16const PRODUCT_NAMES: [&str; 2] = ["Openstack Nova", "OpenStack Compute"];
17const CHASSIS_ASSET_TAG_FILE: &str = "/sys/class/dmi/id/chassis_asset_tag";
18const CHASSIS_ASSET_TAGS: [&str; 5] = [
19 "HUAWEICLOUD",
20 "OpenTelekomCloud",
21 "SAP CCloud VM",
22 "OpenStack Nova",
23 "OpenStack Compute",
24];
25pub(crate) const IDENTIFIER: ProviderId = ProviderId::OpenStack;
26
27pub(crate) struct OpenStack;
28
29#[async_trait]
30impl Provider for OpenStack {
31 fn identifier(&self) -> ProviderId {
32 IDENTIFIER
33 }
34
35 #[instrument(skip_all)]
37 async fn identify(&self, tx: Sender<ProviderId>, timeout: Duration) {
38 info!("Checking OpenStack");
39 if self
40 .check_vendor_files(PRODUCT_NAME_FILE, CHASSIS_ASSET_TAG_FILE)
41 .await
42 || self.check_metadata_server(METADATA_URI, timeout).await
43 {
44 info!("Identified OpenStack");
45 let res = tx.send(IDENTIFIER).await;
46
47 if let Err(err) = res {
48 error!("Error sending message: {:?}", err);
49 }
50 }
51 }
52}
53
54impl OpenStack {
55 #[instrument(skip_all)]
57 async fn check_metadata_server(&self, metadata_uri: &str, timeout: Duration) -> bool {
58 let url = format!("{metadata_uri}{METADATA_PATH}");
59 debug!("Checking {} metadata using url: {}", IDENTIFIER, url);
60
61 let client = if let Ok(client) = reqwest::Client::builder().timeout(timeout).build() {
62 client
63 } else {
64 error!("Error creating client");
65 return false;
66 };
67
68 match client.get(url).send().await {
69 Ok(resp) => resp.status().is_success(),
70 Err(err) => {
71 error!("Error making request: {:?}", err);
72 false
73 }
74 }
75 }
76
77 #[instrument(skip_all)]
79 async fn check_vendor_files<P: AsRef<Path>>(
80 &self,
81 product_name_file: P,
82 chassis_asset_tag_file: P,
83 ) -> bool {
84 debug!(
85 "Checking {} vendor file: {}",
86 IDENTIFIER,
87 product_name_file.as_ref().display()
88 );
89
90 if product_name_file.as_ref().is_file() {
91 match fs::read_to_string(product_name_file).await {
92 Ok(content) => {
93 if PRODUCT_NAMES.iter().any(|&name| content.contains(name)) {
94 return true;
95 }
96 }
97 Err(err) => {
98 error!("Error reading file: {:?}", err);
99 }
100 }
101 }
102
103 debug!(
104 "Checking {} vendor file: {}",
105 IDENTIFIER,
106 chassis_asset_tag_file.as_ref().display(),
107 );
108
109 if chassis_asset_tag_file.as_ref().is_file() {
110 match fs::read_to_string(chassis_asset_tag_file).await {
111 Ok(content) => {
112 if CHASSIS_ASSET_TAGS
113 .iter()
114 .any(|&name| content.contains(name))
115 {
116 return true;
117 }
118 }
119 Err(err) => {
120 error!("Error reading file: {:?}", err);
121 }
122 }
123 }
124
125 false
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use std::io::Write;
132
133 use anyhow::Result;
134 use tempfile::NamedTempFile;
135 use wiremock::matchers::path;
136 use wiremock::{Mock, MockServer, ResponseTemplate};
137
138 use super::*;
139
140 #[tokio::test]
141 async fn test_check_metadata_server_success() {
142 let mock_server = MockServer::start().await;
143 Mock::given(path(METADATA_PATH))
144 .respond_with(ResponseTemplate::new(200))
145 .expect(1)
146 .mount(&mock_server)
147 .await;
148
149 let provider = OpenStack;
150 let metadata_uri = mock_server.uri();
151 let result = provider
152 .check_metadata_server(&metadata_uri, Duration::from_secs(1))
153 .await;
154
155 assert!(result);
156 }
157
158 #[tokio::test]
159 async fn test_check_metadata_server_failure() {
160 let mock_server = MockServer::start().await;
161 Mock::given(path(METADATA_PATH))
162 .respond_with(ResponseTemplate::new(500))
163 .expect(1)
164 .mount(&mock_server)
165 .await;
166
167 let provider = OpenStack;
168 let metadata_uri = mock_server.uri();
169 let result = provider
170 .check_metadata_server(&metadata_uri, Duration::from_secs(1))
171 .await;
172
173 assert!(!result);
174 }
175
176 #[tokio::test]
177 async fn test_check_vendor_file_success() -> Result<()> {
178 let mut product_name_file = NamedTempFile::new()?;
179 let mut chassis_asset_tag_file = NamedTempFile::new()?;
180
181 product_name_file.write_all(PRODUCT_NAMES[0].as_bytes())?;
182 chassis_asset_tag_file.write_all(CHASSIS_ASSET_TAGS[0].as_bytes())?;
183
184 let provider = OpenStack;
185 let result = provider
186 .check_vendor_files(product_name_file.path(), chassis_asset_tag_file.path())
187 .await;
188
189 assert!(result);
190
191 Ok(())
192 }
193
194 #[tokio::test]
195 async fn test_check_vendor_file_failure() -> Result<()> {
196 let product_name_file = NamedTempFile::new()?;
197 let chassis_asset_tag_file = NamedTempFile::new()?;
198
199 let provider = OpenStack;
200 let result = provider
201 .check_vendor_files(product_name_file.path(), chassis_asset_tag_file.path())
202 .await;
203
204 assert!(!result);
205
206 Ok(())
207 }
208}