Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
joseivanlopez committed Jul 4, 2024
1 parent 32143a7 commit daadd77
Show file tree
Hide file tree
Showing 7 changed files with 309 additions and 45 deletions.
2 changes: 1 addition & 1 deletion rust/agama-lib/src/storage/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ impl<'a> StorageClient<'a> {
Ok(Some(BlockDevice {
active: get_property(properties, "Active")?,
encrypted: get_property(properties, "Encrypted")?,
recoverable_size: get_property(properties, "RecoverableSize")?,
size: get_property(properties, "Size")?,
shrinking: get_property(properties, "Shrinking")?,
start: get_property(properties, "Start")?,
systems: get_property(properties, "Systems")?,
udev_ids: get_property(properties, "UdevIds")?,
Expand Down
33 changes: 32 additions & 1 deletion rust/agama-lib/src/storage/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,14 +504,45 @@ pub struct DeviceInfo {
pub struct BlockDevice {
pub active: bool,
pub encrypted: bool,
pub recoverable_size: DeviceSize,
pub size: DeviceSize,
pub shrinking: ShrinkingInfo,
pub start: u64,
pub systems: Vec<String>,
pub udev_ids: Vec<String>,
pub udev_paths: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub enum ShrinkingInfo {
Supported(DeviceSize),
Unsupported(Vec<String>),
}

impl TryFrom<zbus::zvariant::Value<'_>> for ShrinkingInfo {
type Error = zbus::zvariant::Error;

fn try_from(value: zbus::zvariant::Value) -> Result<Self, zbus::zvariant::Error> {
let hash: HashMap<String, OwnedValue> = value.clone().try_into()?;
let mut info: Option<Self> = None;

if let Some(size) = get_optional_property(&hash, "Supported")? {
info = Some(Self::Supported(size));
}
if let Some(reasons) = get_optional_property(&hash, "Unsupported")? {
info = Some(Self::Unsupported(reasons));
}

if let Some(info_value) = info {
Ok(info_value)
} else {
Err(Self::Error::Message(
format!("Wrong value for Shrinking: {}", value).to_string(),
))
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Component {
Expand Down
17 changes: 12 additions & 5 deletions service/lib/agama/dbus/storage/interfaces/device/block.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
# find current contact information at www.suse.com.

require "dbus"
require "agama/storage/device_shrinking"

module Agama
module DBus
Expand Down Expand Up @@ -86,11 +87,17 @@ def block_size
storage_device.size.to_i
end

# Size of the space that could be theoretically reclaimed by shrinking the device.
# Shrinking information.
#
# @return [Integer]
def block_recoverable_size
storage_device.recoverable_size.to_i
# @return [Hash]
def block_shrinking
shrinking = Agama::Storage::DeviceShrinking.new(storage_device)

if shrinking.supported?
{ "Supported" => shrinking.min_size.to_i }
else
{ "Unsupported" => shrinking.unsupported_reasons }
end
end

# Name of the currently installed systems
Expand All @@ -112,7 +119,7 @@ def self.included(base)
dbus_reader :block_udev_ids, "as", dbus_name: "UdevIds"
dbus_reader :block_udev_paths, "as", dbus_name: "UdevPaths"
dbus_reader :block_size, "t", dbus_name: "Size"
dbus_reader :block_recoverable_size, "t", dbus_name: "RecoverableSize"
dbus_reader :block_shrinking, "a{sv}", dbus_name: "Shrinking"
dbus_reader :block_systems, "as", dbus_name: "Systems"
end
end
Expand Down
187 changes: 187 additions & 0 deletions service/lib/agama/storage/device_shrinking.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# frozen_string_literal: true

# Copyright (c) [2024] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "yast/i18n"
require "y2storage"

module Agama
module Storage
# Generates information about shrinking a block device.
class DeviceShrinking
include Yast::I18n

# @param device [Y2Storage::BlkDevice]
def initialize(device)
textdomain "agama"

@device = device
end

# Whether the device can be shrunk.
#
# Conditions to allow shrinking:
# * Either the device does not exist in system yet or it has some content.
# * The device allows shriking to a minimum valid size (bigger than 0 and less than the
# device size).
# * There is no reason preventing to shrink the device.
#
# @return [Boolean]
def supported?
(!device.exists_in_probed? || content?) && min_size? && !reasons?
end

# Minimun size the device can be shrunk to.
#
# @return [DiskSize, nil] nil if the device cannot be shrunk.
def min_size
return nil unless supported?

device.resize_info.min_size
end

# Reasons because the device cannot be shrunk.
#
# @return [Array<String>, nil] nil if the device can be shrunk.
def unsupported_reasons
return nil if supported?

[
content_reason_text,
min_size_reason_text,
shrinking_reasons_text,
resize_reasons_text
].flatten.compact
end

private

# @return [Y2Storage::BlkDevice]
attr_reader :device

# Reasons preventing shrinking.
SHRINKING_REASONS = [
:RB_SHRINK_NOT_SUPPORTED_BY_FILESYSTEM,
:RB_SHRINK_NOT_SUPPORTED_BY_MULTIDEVICE_FILESYSTE,
:RB_MIN_SIZE_FOR_FILESYSTEM,
:RB_MIN_SIZE_FOR_PARTITION,
:RB_SHRINK_NOT_SUPPORTED_FOR_LVM_LV_TYPE,
:RB_MIN_SIZE_FOR_LVM_LV,
:RB_FILESYSTEM_FULL
].freeze
private_constant :SHRINKING_REASONS

# Reasons preventing both shrinking and growing.
RESIZE_REASONS = [
:RB_RESIZE_NOT_SUPPORTED_BY_DEVICE,
:RB_MIN_MAX_ERROR,
:RB_FILESYSTEM_INCONSISTENT,
:RB_EXTENDED_PARTITION,
:RB_ON_IMPLICIT_PARTITION_TABLE,
:RB_RESIZE_NOT_SUPPORTED_FOR_LVM_LV_TYPE,
:RB_RESIZE_NOT_SUPPORTED_DUE_TO_SNAPSHOTS,
:RB_PASSWORD_REQUIRED
].freeze
private_constant :RESIZE_REASONS

# Whether the device has some content.
#
# If the device only contains an encryption layer, then the device is considered as empty.
#
# @return [Boolean]
def content?
device = self.device.encryption || self.device
device.descendants.any?
end

# Whether the device can be shrunk to a minimum valid size.
#
# @return [Boolean]
def min_size?
min_size = device.resize_info.min_size

min_size > 0 && min_size != device.size
end

# Whether there is some reason preventing to shrink the device.
#
# @return [Boolean]
def reasons?
shrinking_reasons.any? || resize_reasons.any?
end

# Reasons preventing to shrink.
#
# @return [Array<Symbol>]
def shrinking_reasons
device.resize_info.reasons.select { |r| SHRINKING_REASONS.include?(r) }
end

# Reasons preventing both to shrink and to grow.
#
# @return [Array<Symbol>]
def resize_reasons
device.resize_info.reasons.select { |r| RESIZE_REASONS.include?(r) }
end

# Text of the reason preventing to shrink because there is no content.
#
# @return [String, nil] nil if there is content or there is any other reasons.
def content_reason_text
return nil if content? || reasons?

_("Neither a file system nor a storage system was detected on the device. In case the " \
"device does contain a file system or a storage system that is not supported, resizing " \
"will most likely cause data loss.")
end

# Text of the reason preventing to shrink because there is no valid minimum size.
#
# @return [String, nil] nil if there is a minimum size or there is any other reasons.
def min_size_reason_text
return nil if min_size? || reasons?

_("Shrinking is not supported by this device")
end

# Text of the reasons proventing to shrink.
#
# @return [Array<String>]
def shrinking_reasons_text
shrinking_reasons.map { |r| reason_text(r) }
end

# Text of the reasons proventing both to shrink and to grow.
#
# @return [Array<String>]
def resize_reasons_text
resize_reasons.map { |r| reason_text(r) }
end

# Text of the given reason.
#
# @param reason [Symbol]
# @return [String]
def reason_text(reason)
device.resize_info.reason_text(reason)
end
end
end
end
6 changes: 5 additions & 1 deletion web/src/client/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class DBusClient {
* @property {boolean} [encrypted] - Whether the device is encrypted (only for block devices)
* @property {boolean} [isEFI] - Whether the device is an EFI partition (only for partition)
* @property {number} [size]
* @property {number} [recoverableSize]
* @property {ShrinkingInfo} [shrinking]
* @property {string[]} [systems] - Name of the installed systems
* @property {string[]} [udevIds]
* @property {string[]} [udevPaths]
Expand Down Expand Up @@ -102,6 +102,10 @@ class DBusClient {
* @property {string} [mountPath]
* @property {string} [label]
*
* @typedef {object} ShrinkingInfo
* @property {number} [supported] - Min size the device can be shrunk to.
* @property {string[]} [unsupported] - Reasons why the device cannot be shrunk.
*
* @typedef {object} ProposalResult
* @property {ProposalSettings} settings
* @property {Action[]} actions
Expand Down
Loading

0 comments on commit daadd77

Please sign in to comment.