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