Skip to content

Commit 75cbb74

Browse files
author
Jesse Anttila
committed
apiserver: add --ebs-volumes and --prefer options to ephemeral-storage init command
1 parent 3cf7322 commit 75cbb74

File tree

5 files changed

+172
-33
lines changed

5 files changed

+172
-33
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# ephemeral storage links: /dev/disk/ephemeral-ebs
2+
3+
ACTION=="remove", GOTO="ephemeral_ebs_storage_end"
4+
KERNEL!="nvme*", GOTO="ephemeral_ebs_storage_end"
5+
SUBSYSTEM!="block", GOTO="ephemeral_ebs_storage_end"
6+
ENV{DEVTYPE}!="disk", GOTO="ephemeral_ebs_storage_end"
7+
ATTRS{model}!="Amazon Elastic Block Store", GOTO="ephemeral_ebs_storage_end"
8+
9+
# Create links for ephemeral EBS volumes that start with "xvdd".
10+
ENV{BOTTLEROCKET_DEVICE_TYPE}=="ephemeral", ENV{XVD_DEVICE_NAME}=="xvdd?", SYMLINK+="disk/ephemeral-ebs/$env{XVD_DEVICE_NAME}"
11+
12+
LABEL="ephemeral_ebs_storage_end"

packages/os/os.spec

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ Source205: bootstrap-commands-tmpfiles.conf
6464
# 3xx sources: udev rules
6565
Source300: ephemeral-storage.rules
6666
Source301: ebs-volumes.rules
67-
Source302: supplemental-storage.rules
67+
Source302: ephemeral-ebs-storage.rules
68+
Source303: supplemental-storage.rules
6869

6970
# 4xx sources: Bottlerocket licenses
7071
Source400: COPYRIGHT
@@ -727,7 +728,8 @@ install -p -m 0644 %{S:205} %{buildroot}%{_cross_tmpfilesdir}/bootstrap-commands
727728
install -d %{buildroot}%{_cross_udevrulesdir}
728729
install -p -m 0644 %{S:300} %{buildroot}%{_cross_udevrulesdir}/80-ephemeral-storage.rules
729730
install -p -m 0644 %{S:301} %{buildroot}%{_cross_udevrulesdir}/81-ebs-volumes.rules
730-
install -p -m 0644 %{S:302} %{buildroot}%{_cross_udevrulesdir}/82-supplemental-storage.rules
731+
install -p -m 0644 %{S:302} %{buildroot}%{_cross_udevrulesdir}/82-ephemeral-ebs-storage.rules
732+
install -p -m 0644 %{S:303} %{buildroot}%{_cross_udevrulesdir}/83-supplemental-storage.rules
731733

732734
%cross_scan_attribution --clarify %{_builddir}/sources/clarify.toml \
733735
cargo --offline --locked %{_builddir}/sources/Cargo.toml
@@ -809,7 +811,8 @@ install -p -m 0644 %{S:400} %{S:401} %{S:402} %{buildroot}%{_cross_licensedir}
809811
%{_cross_bindir}/ghostdog
810812
%{_cross_udevrulesdir}/80-ephemeral-storage.rules
811813
%{_cross_udevrulesdir}/81-ebs-volumes.rules
812-
%{_cross_udevrulesdir}/82-supplemental-storage.rules
814+
%{_cross_udevrulesdir}/82-ephemeral-ebs-storage.rules
815+
%{_cross_udevrulesdir}/83-supplemental-storage.rules
813816

814817
%files -n %{_cross_os}signpost
815818
%{_cross_bindir}/signpost

sources/api/apiserver/src/server/ephemeral_storage.rs

Lines changed: 100 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! The 'ephemeral_storage' module supports configuring and using local instance storage.
22
3-
use model::ephemeral_storage::Filesystem;
3+
use model::ephemeral_storage::{Filesystem, Preference};
44

55
use snafu::{ensure, ResultExt};
66
use std::collections::HashSet;
@@ -22,6 +22,8 @@ static EPHEMERAL_MNT: &str = ".ephemeral";
2222
/// Name of the device and its path from the MD driver
2323
static RAID_DEVICE_DIR: &str = "/dev/md/";
2424
static RAID_DEVICE_NAME: &str = "ephemeral";
25+
/// Symlink to ephemeral storage array or disk
26+
static EPHEMERAL_STORAGE_LINK: &str = "/dev/disk/ephemeral-storage";
2527

2628
pub struct BindDirs {
2729
pub allowed_exact: HashSet<&'static str>,
@@ -32,14 +34,25 @@ pub struct BindDirs {
3234
/// initialize prepares the ephemeral storage for formatting and formats it. For multiple disks
3335
/// preparation is the creation of a RAID0 array, for a single disk this is a no-op. The array or disk
3436
/// is then formatted with the specified filesystem (default=xfs) if not formatted already.
35-
pub fn initialize(fs: Option<Filesystem>, disks: Option<Vec<String>>) -> Result<()> {
37+
pub fn initialize(
38+
fs: Option<Filesystem>,
39+
disks: Option<Vec<String>>,
40+
ebs_volumes: Option<Vec<String>>,
41+
prefer: Option<Vec<Preference>>,
42+
) -> Result<()> {
3643
let known_disks = ephemeral_devices()?;
3744
let known_disks_hash = HashSet::<_>::from_iter(known_disks.iter());
45+
let known_ebs_volumes = ephemeral_ebs_volumes()?;
46+
let known_ebs_volumes_hash = HashSet::<_>::from_iter(known_ebs_volumes.iter());
3847

39-
let disks = match disks {
40-
Some(disks) => {
41-
// we have disks provided, so match them against the list of valid disks
42-
for disk in &disks {
48+
let any_specified = disks.as_ref().is_some_and(|x| !x.is_empty())
49+
|| ebs_volumes.as_ref().is_some_and(|x| !x.is_empty());
50+
51+
let disks = if any_specified {
52+
// use all specified ephemeral disks and ebs volumes, if they're all valid
53+
let mut selected_disks = vec![];
54+
if let Some(d) = disks {
55+
for disk in &d {
4356
ensure!(
4457
known_disks_hash.contains(disk),
4558
error::InvalidParameterSnafu {
@@ -48,26 +61,57 @@ pub fn initialize(fs: Option<Filesystem>, disks: Option<Vec<String>>) -> Result<
4861
}
4962
)
5063
}
51-
disks
64+
selected_disks.extend(d);
5265
}
53-
None => {
54-
// if there are no disks specified, and none are available we treat the init as a
55-
// no-op to allow "ephemeral-storage init"/"ephemeral-storage bind" to work on instances
56-
// with and without ephemeral storage
57-
if known_disks.is_empty() {
58-
info!("no ephemeral disks found, skipping ephemeral storage initialization");
59-
return Ok(());
66+
67+
if let Some(e) = ebs_volumes {
68+
for ebs_volume in &e {
69+
ensure!(
70+
known_ebs_volumes_hash.contains(ebs_volume),
71+
error::InvalidParameterSnafu {
72+
parameter: "ebs_volumes",
73+
reason: format!("unknown ebs volume {ebs_volume:?}"),
74+
}
75+
)
76+
}
77+
selected_disks.extend(e);
78+
}
79+
selected_disks
80+
} else {
81+
// if there are no specified disks, use preference list to find a non-empty set of disks
82+
let preferences = prefer.unwrap_or_else(|| {
83+
vec![Preference {
84+
ephemeral_disk: true,
85+
ebs_volume: false,
86+
}]
87+
});
88+
89+
let mut disks = vec![];
90+
for preference in preferences {
91+
if preference.ephemeral_disk {
92+
disks.extend(&known_disks);
93+
}
94+
if preference.ebs_volume {
95+
disks.extend(&known_ebs_volumes);
96+
}
97+
if !disks.is_empty() {
98+
break;
6099
}
61-
// no disks specified, so use the default
62-
known_disks
63100
}
101+
if disks.is_empty() {
102+
// no disks were specified and none of the preferences produced any disks
103+
// this is special-cased as a no-op
104+
info!("no ephemeral disks found, skipping ephemeral storage initialization");
105+
return Ok(());
106+
}
107+
disks.into_iter().cloned().collect()
64108
};
65109

66110
ensure!(
67111
!disks.is_empty(),
68112
error::InvalidParameterSnafu {
69113
parameter: "disks",
70-
reason: "no local ephemeral disks specified",
114+
reason: "no valid local ephemeral disks or ebs volumes specified",
71115
}
72116
);
73117

@@ -101,22 +145,21 @@ pub fn initialize(fs: Option<Filesystem>, disks: Option<Vec<String>>) -> Result<
101145
);
102146
}
103147

148+
// Create link to formatted device for use in `bind`
149+
std::os::unix::fs::symlink(&device_name, EPHEMERAL_STORAGE_LINK)
150+
.context(error::DiskSymlinkFailureSnafu {})?;
151+
104152
Ok(())
105153
}
106154

107155
/// binds the specified directories to the pre-configured array, creating those directories if
108156
/// they do not exist.
109157
pub fn bind(variant: &str, dirs: Vec<String>) -> Result<()> {
110-
let device_name = match ephemeral_devices()?.len() {
111-
// handle the no local instance storage case
112-
0 => {
113-
info!("no ephemeral disks found, skipping ephemeral storage binding");
114-
return Ok(());
115-
}
116-
// If there is only one device, use that
117-
1 => ephemeral_devices()?.first().expect("non-empty").clone(),
118-
_ => format!("{RAID_DEVICE_DIR}{RAID_DEVICE_NAME}"),
119-
};
158+
let device_name = EPHEMERAL_STORAGE_LINK;
159+
if !std::fs::exists(device_name).is_ok_and(|x| x) {
160+
info!("ephemeral storage not initialized, skipping binding");
161+
return Ok(());
162+
}
120163

121164
// Normalize input by trimming trailing "/"
122165
let dirs: Vec<String> = dirs
@@ -150,7 +193,7 @@ pub fn bind(variant: &str, dirs: Vec<String>) -> Result<()> {
150193
info!("mounting {:?} as {:?}", device_name, mount_point);
151194
let output = Command::new(MOUNT)
152195
.args([
153-
OsString::from(device_name.clone()),
196+
OsString::from(device_name),
154197
OsString::from(mount_point.as_os_str()),
155198
])
156199
.output()
@@ -289,6 +332,29 @@ pub fn ephemeral_devices() -> Result<Vec<String>> {
289332
Ok(filenames)
290333
}
291334

335+
/// ephemeral_ebs_volumes returns the full path name to the ebs volumes in /dev/disk/ephemeral-ebs
336+
pub fn ephemeral_ebs_volumes() -> Result<Vec<String>> {
337+
const EPHEMERAL_EBS_PATH: &str = "/dev/disk/ephemeral-ebs";
338+
let mut filenames = Vec::new();
339+
// for instances without ebs volumes attached, we don't error and just return an empty vector so
340+
// it can be handled gracefully
341+
if fs::metadata(EPHEMERAL_EBS_PATH).is_err() {
342+
return Ok(filenames);
343+
}
344+
345+
let entries =
346+
std::fs::read_dir(EPHEMERAL_EBS_PATH).context(error::DiscoverEbsVolumesSnafu {
347+
path: String::from(EPHEMERAL_EBS_PATH),
348+
})?;
349+
for entry in entries {
350+
let entry = entry.context(error::DiscoverEbsVolumesSnafu {
351+
path: String::from(EPHEMERAL_EBS_PATH),
352+
})?;
353+
filenames.push(entry.path().into_os_string().to_string_lossy().to_string());
354+
}
355+
Ok(filenames)
356+
}
357+
292358
/// allowed_bind_dirs returns a set of the directories that can be bound to ephemeral storage, which
293359
/// varies based on the variant, a set of the prefixes of directories that are allowed to be bound.
294360
/// and a set of substrings that are disallowed in the directory name.
@@ -385,6 +451,12 @@ pub mod error {
385451
path: String,
386452
},
387453

454+
#[snafu(display("Failed to discover ebs volumes from {}: {}", path, source))]
455+
DiscoverEbsVolumes {
456+
source: std::io::Error,
457+
path: String,
458+
},
459+
388460
#[snafu(display("Failed to mount {} to {}: {}", what, dest, String::from_utf8_lossy(output.stderr.as_slice())))]
389461
MountArrayFailure {
390462
what: String,

sources/api/apiserver/src/server/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -726,8 +726,13 @@ async fn get_fips_report(query: web::Query<HashMap<String, String>>) -> Result<H
726726

727727
/// Configure ephemeral storage (raid & format, or just format for single disk)
728728
async fn initialize_ephemeral_storage(cfg: web::Json<Init>) -> Result<HttpResponse> {
729-
ephemeral_storage::initialize(cfg.0.filesystem, cfg.0.disks)
730-
.context(error::EphemeralInitializeSnafu {})?;
729+
ephemeral_storage::initialize(
730+
cfg.0.filesystem,
731+
cfg.0.disks,
732+
cfg.0.ebs_volumes,
733+
cfg.0.prefer,
734+
)
735+
.context(error::EphemeralInitializeSnafu {})?;
731736
Ok(HttpResponse::NoContent().finish()) // 204
732737
}
733738
/// Bind directories to ephemeral storage (mount array, bind, and unmount)

sources/models/src/ephemeral_storage.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,58 @@ impl Display for Filesystem {
1818
}
1919
}
2020

21+
/// Storage type preferences for ephemeral storage
22+
#[derive(Debug, Clone, Serialize, Deserialize)]
23+
pub struct Preference {
24+
pub ephemeral_disk: bool,
25+
pub ebs_volume: bool,
26+
}
27+
impl Display for Preference {
28+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
29+
let mut first = false;
30+
let mut write = |s| {
31+
if first {
32+
first = false;
33+
} else {
34+
f.write_str("+")?;
35+
}
36+
f.write_str(s)
37+
};
38+
if self.ephemeral_disk {
39+
write("ephemeral-disk")?;
40+
}
41+
if self.ebs_volume {
42+
write("ebs-volume")?;
43+
}
44+
Ok(())
45+
}
46+
}
47+
impl<'a> TryFrom<&'a str> for Preference {
48+
type Error = &'a str;
49+
50+
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
51+
let mut preference = Self {
52+
ephemeral_disk: false,
53+
ebs_volume: false,
54+
};
55+
for i in value.split("+") {
56+
match i {
57+
"ephemeral-disk" => preference.ephemeral_disk = true,
58+
"ebs-volume" => preference.ebs_volume = true,
59+
x => return Err(x),
60+
}
61+
}
62+
Ok(preference)
63+
}
64+
}
65+
2166
/// Initialize ephemeral storage
2267
#[derive(Debug, Clone, Serialize, Deserialize)]
2368
pub struct Init {
2469
pub filesystem: Option<Filesystem>,
2570
pub disks: Option<Vec<String>>,
71+
pub ebs_volumes: Option<Vec<String>>,
72+
pub prefer: Option<Vec<Preference>>,
2673
}
2774

2875
/// Bind directories to configured ephemeral storage

0 commit comments

Comments
 (0)