-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(clone): enable option fs id change to clone uuid
Signed-off-by: Abhinandan Purkait <[email protected]>
- Loading branch information
1 parent
e759393
commit 19132ef
Showing
12 changed files
with
535 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
300 changes: 300 additions & 0 deletions
300
control-plane/csi-driver/src/bin/node/filesystem_ops.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,300 @@ | ||
//! This module consists of the various filesystem specific operations utility. Including creation | ||
//! of filesystem, changing the parameter of filesystem like uuid, default mount options for | ||
//! specific filesystem, repairing of the filesystem, retrieving specific properties of the | ||
//! filesystem. | ||
use crate::mount; | ||
use csi_driver::filesystem::FileSystem as Fs; | ||
use devinfo::{blkid::probe::Probe, DevInfoError}; | ||
use std::process::Output; | ||
use tokio::process::Command; | ||
use tonic::async_trait; | ||
use tracing::trace; | ||
use uuid::Uuid; | ||
|
||
/// `nouuid` mount flag, to allow duplicate fs uuid. | ||
const XFS_NO_UUID_FLAG: &str = "nouuid"; | ||
|
||
/// Error type filesystem operations. | ||
type Error = String; | ||
|
||
/// Ext4 filesystem type. | ||
pub(crate) struct Ext4Fs; | ||
/// XFS filesystem type. | ||
pub(crate) struct XFs; | ||
|
||
/// Filesystem type for csi node ops, wrapper over the parent Filesystem enum. | ||
#[derive(Clone, Debug, Eq, PartialEq)] | ||
pub(crate) struct FileSystem(Fs); | ||
|
||
impl From<Fs> for FileSystem { | ||
fn from(value: Fs) -> Self { | ||
Self(value) | ||
} | ||
} | ||
|
||
// Implement as ref for the FileSystem. | ||
impl AsRef<str> for FileSystem { | ||
fn as_ref(&self) -> &str { | ||
self.0.as_ref() | ||
} | ||
} | ||
|
||
// Implement Display for the filesystem | ||
impl std::fmt::Display for FileSystem { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "{}", self.0.as_ref()) | ||
} | ||
} | ||
|
||
impl FileSystem { | ||
/// Convert the top level enumeration to specific filesystem types to access the supported | ||
/// operations by them. | ||
pub(crate) fn fs_ops(&self) -> Result<&dyn FileSystemOps, Error> { | ||
static EXT4FS: Ext4Fs = Ext4Fs {}; | ||
static XFS: XFs = XFs {}; | ||
match self.0 { | ||
Fs::Ext4 => Ok(&EXT4FS), | ||
Fs::Xfs => Ok(&XFS), | ||
_ => Err(format!("Unsupported filesystem {self}")), | ||
} | ||
} | ||
/// Get a specific filesystem property by specifying the device path. | ||
pub(crate) fn property(device: &str, property: &str) -> Result<String, DevInfoError> { | ||
let probe = Probe::new_from_filename(device)?; | ||
probe.do_probe()?; | ||
probe.lookup_value(property) | ||
} | ||
} | ||
|
||
#[async_trait] | ||
pub(crate) trait FileSystemOps: Send + Sync { | ||
/// Create the filesystem using its fs util. | ||
async fn create(&self, device: &str) -> Result<(), Error>; | ||
/// Get the default mount options along with the user passed options for specific filesystems. | ||
fn mount_flags(&self, mount_flags: Vec<String>) -> Vec<String>; | ||
/// Unmount the filesystem if the filesystem uuid and the provided uuid differ. | ||
fn unmount_on_fs_id_diff( | ||
&self, | ||
device_path: &str, | ||
fs_staging_path: &str, | ||
volume_uuid: &Uuid, | ||
) -> Result<(), Error>; | ||
/// Repair the filesystem with specific filesystem utility. | ||
async fn repair( | ||
&self, | ||
device: &str, | ||
staging_path: &str, | ||
options: &[String], | ||
volume_uuid: &Uuid, | ||
) -> Result<(), Error>; | ||
/// Set the filesystem uuid after repair. | ||
async fn set_uuid( | ||
&self, | ||
device: &str, | ||
staging_path: &str, | ||
options: &[String], | ||
volume_uuid: &Uuid, | ||
) -> Result<(), Error>; | ||
} | ||
|
||
#[async_trait] | ||
impl FileSystemOps for Ext4Fs { | ||
async fn create(&self, device: &str) -> Result<(), Error> { | ||
let binary = format!("mkfs.{}", "ext4"); | ||
let output = Command::new(&binary) | ||
.arg(device) | ||
.output() | ||
.await | ||
.map_err(|error| format!("failed to execute {binary}: {error}"))?; | ||
ack_command_output(output, binary) | ||
} | ||
|
||
fn mount_flags(&self, mount_flags: Vec<String>) -> Vec<String> { | ||
mount_flags | ||
} | ||
|
||
fn unmount_on_fs_id_diff( | ||
&self, | ||
_device_path: &str, | ||
_fs_staging_path: &str, | ||
_volume_uuid: &Uuid, | ||
) -> Result<(), Error> { | ||
Ok(()) | ||
} | ||
|
||
async fn repair( | ||
&self, | ||
device: &str, | ||
_staging_path: &str, | ||
_options: &[String], | ||
_volume_uuid: &Uuid, | ||
) -> Result<(), Error> { | ||
let binary = "e2fsck".to_string(); | ||
let output = Command::new(&binary) | ||
.arg("-y") | ||
.arg("-f") | ||
.arg(device) | ||
.output() | ||
.await | ||
.map_err(|error| format!("failed to execute {binary}: {error}"))?; | ||
|
||
trace!( | ||
"Output from {} command: {}, status code: {:?}", | ||
binary, | ||
String::from_utf8(output.stdout.clone()).unwrap(), | ||
output.status.code() | ||
); | ||
|
||
// For ext4 fs repair using e2fsck the success condition is when status code is 0 or 1. | ||
if !output.status.success() && output.status.code() != Some(1) { | ||
return Err(format!( | ||
"{} command failed: {}", | ||
binary, | ||
String::from_utf8(output.stderr).unwrap() | ||
)); | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Change the uuid of the ext4 filesystem after repair. | ||
async fn set_uuid( | ||
&self, | ||
device: &str, | ||
staging_path: &str, | ||
options: &[String], | ||
volume_uuid: &Uuid, | ||
) -> Result<(), Error> { | ||
if let Ok(probed_uuid) = FileSystem::property(device, "UUID") { | ||
if probed_uuid == volume_uuid.to_string() { | ||
return Ok(()); | ||
} | ||
} | ||
self.repair(device, staging_path, options, volume_uuid) | ||
.await?; | ||
let binary = "tune2fs".to_string(); | ||
let output = Command::new(&binary) | ||
.arg("-U") | ||
.arg(volume_uuid.to_string()) | ||
.arg(device) | ||
.output() | ||
.await | ||
.map_err(|error| format!("failed to execute {binary}: {error}"))?; | ||
ack_command_output(output, binary) | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl FileSystemOps for XFs { | ||
async fn create(&self, device: &str) -> Result<(), Error> { | ||
let binary = format!("mkfs.{}", "xfs"); | ||
let output = Command::new(&binary) | ||
.arg(device) | ||
.output() | ||
.await | ||
.map_err(|error| format!("failed to execute {binary}: {error}"))?; | ||
ack_command_output(output, binary) | ||
} | ||
|
||
fn mount_flags(&self, mount_flags: Vec<String>) -> Vec<String> { | ||
let mut mount_flags = mount_flags; | ||
if !mount_flags.contains(&XFS_NO_UUID_FLAG.to_string()) { | ||
mount_flags.push(XFS_NO_UUID_FLAG.to_string()) | ||
} | ||
mount_flags | ||
} | ||
|
||
fn unmount_on_fs_id_diff( | ||
&self, | ||
device_path: &str, | ||
fs_staging_path: &str, | ||
volume_uuid: &Uuid, | ||
) -> Result<(), Error> { | ||
mount::unmount_on_fs_id_diff(device_path, fs_staging_path, volume_uuid) | ||
} | ||
|
||
/// Xfs filesystem needs an unmount to clear the log, so that the parameters can be changed. | ||
/// Mount the filesystem to a defined path and then unmount it. | ||
async fn repair( | ||
&self, | ||
device: &str, | ||
staging_path: &str, | ||
options: &[String], | ||
volume_uuid: &Uuid, | ||
) -> Result<(), Error> { | ||
mount::filesystem_mount(device, staging_path, &FileSystem(Fs::Xfs), options).map_err(|error| { | ||
format!( | ||
"(xfs repairing) Failed to mount device {device} onto {staging_path} for {volume_uuid} : {error}", | ||
) | ||
})?; | ||
mount::filesystem_unmount(staging_path).map_err(|error| { | ||
format!( | ||
"(xfs repairing) Failed to unmount device {device} from {staging_path} for {volume_uuid} : {error}", | ||
) | ||
}) | ||
} | ||
|
||
/// Change the uuid of the xfs filesystem after repair. | ||
async fn set_uuid( | ||
&self, | ||
device: &str, | ||
staging_path: &str, | ||
options: &[String], | ||
volume_uuid: &Uuid, | ||
) -> Result<(), Error> { | ||
if let Ok(probed_uuid) = FileSystem::property(device, "UUID") { | ||
if probed_uuid == volume_uuid.to_string() { | ||
return Ok(()); | ||
} | ||
} | ||
self.repair(device, staging_path, options, volume_uuid) | ||
.await?; | ||
let binary = "xfs_admin".to_string(); | ||
let output = Command::new(&binary) | ||
.arg("-U") | ||
.arg(volume_uuid.to_string()) | ||
.arg(device) | ||
.output() | ||
.await | ||
.map_err(|error| format!("failed to execute {binary}: {error}"))?; | ||
|
||
if !output.status.success() { | ||
return Err(format!( | ||
"{} command failed: {}", | ||
binary, | ||
String::from_utf8(output.stderr).unwrap() | ||
)); | ||
} | ||
|
||
let probe_uuid = FileSystem::property(device, "UUID") | ||
.map_err(|error| format!("Failed to get UUID of device {device}: {error}"))?; | ||
|
||
if volume_uuid.to_string() != probe_uuid { | ||
return Err(format!( | ||
"failed to set filesystem uuid using : {binary}, {}", | ||
String::from_utf8(output.stdout).unwrap_or_default() | ||
)); | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
// Acknowledge the output from Command. | ||
fn ack_command_output(output: Output, binary: String) -> Result<(), Error> { | ||
trace!( | ||
"Output from {} command: {}, status code: {:?}", | ||
binary, | ||
String::from_utf8(output.stdout.clone()).unwrap(), | ||
output.status.code() | ||
); | ||
|
||
if output.status.success() { | ||
return Ok(()); | ||
} | ||
|
||
Err(format!( | ||
"{} command failed: {}", | ||
binary, | ||
String::from_utf8(output.stderr).unwrap() | ||
)) | ||
} |
Oops, something went wrong.