Skip to content

Commit

Permalink
Extend storage D-Bus API (#1071)
Browse files Browse the repository at this point in the history
## Problem

The current result of the storage proposal is presented as a list of
actions, which could make a bit complex to understand the overall
picture of the resulting devices.

That list of actions is going to be replaced by a more visual
representation of the devices. To make this possible, more information
about the devices has to be exported on D-Bus.

* Part of https://trello.com/c/7TTVVquM.

## Solution

Add the following information to D-Bus:

* The list of actions includes the SID of the affected device.
* The partition table exports the unused slots.
* The LVM devices (volume groups, physical volumes and logical volumes)
are exported.
* The block devices includes their start block and also indicates
whether the device is encrypted.
* The staging devices are exported. 

NOTE: This PR goes to a feature branch instead of master.

## Testing

* Added unit tests
* Tested manually
  • Loading branch information
joseivanlopez authored Mar 6, 2024
2 parents 7d5e54c + 8128fd7 commit e35d470
Show file tree
Hide file tree
Showing 18 changed files with 374 additions and 73 deletions.
2 changes: 1 addition & 1 deletion service/.rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ AllCops:
Exclude:
- vendor/**/*
- lib/agama/dbus/y2dir/**/*
- agama.gemspec
- agama-yast.gemspec
- package/*.spec

# a D-Bus method definition may take up more line lenght than usual
Expand Down
9 changes: 7 additions & 2 deletions service/lib/agama/dbus/base_tree.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

# Copyright (c) [2023] SUSE LLC
# Copyright (c) [2023-2024] SUSE LLC
#
# All Rights Reserved.
#
Expand Down Expand Up @@ -47,11 +47,16 @@ def initialize(service, root_path, logger: nil)
#
# @param objects [Array]
def objects=(objects)
try_add_objects(objects)
try_update_objects(objects)
try_add_objects(objects)
try_delete_objects(objects)
end

# Unexports the current D-Bus objects of this tree.
def clean
dbus_objects.each { |o| service.unexport(o) }
end

private

# @return [::DBus::ObjectServer]
Expand Down
20 changes: 16 additions & 4 deletions service/lib/agama/dbus/storage/device.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,15 @@ module Storage
#
# The D-Bus object includes the required interfaces for the storage object that it represents.
class Device < BaseObject
# @return [Y2Storage::Device]
attr_reader :storage_device
# sid of the Y2Storage device.
#
# @note A Y2Storage device is a wrapper over a libstorage-ng object. If the source
# devicegraph does not exist anymore (e.g., after reprobing), then the Y2Storage device
# object cannot be used (memory error). The device sid is stored to avoid accessing to
# the old Y2Storage device when updating the represented device, see {#storage_device=}.
#
# @return [Integer]
attr_reader :sid

# Constructor
#
Expand All @@ -43,6 +50,7 @@ def initialize(storage_device, path, tree, logger: nil)
super(path, logger: logger)

@storage_device = storage_device
@sid = storage_device.sid
@tree = tree
add_interfaces
end
Expand All @@ -54,12 +62,13 @@ def initialize(storage_device, path, tree, logger: nil)
#
# @param value [Y2Storage::Device]
def storage_device=(value)
if value.sid != storage_device.sid
if value.sid != sid
raise "Cannot update the D-Bus object because the given device has a different sid: " \
"#{value} instead of #{storage_device.sid}"
"#{value} instead of #{sid}"
end

@storage_device = value
@sid = value.sid

interfaces_and_properties.each do |interface, properties|
dbus_properties_changed(interface, properties, [])
Expand All @@ -71,6 +80,9 @@ def storage_device=(value)
# @return [DevicesTree]
attr_reader :tree

# @return [Y2Storage::Device]
attr_reader :storage_device

# Adds the required interfaces according to the storage object.
def add_interfaces
interfaces = Interfaces::Device.constants
Expand Down
30 changes: 21 additions & 9 deletions service/lib/agama/dbus/storage/devices_tree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,19 @@ def path_for(device)
::DBus::ObjectPath.new(File.join(root_path, device.sid.to_s))
end

# Updates the D-Bus tree according to the given devicegraph
# Updates the D-Bus tree according to the given devicegraph.
#
# @note In the devices tree it is important to avoid updating D-Bus nodes. Note that an
# already exported D-Bus object could require to add or remove interfaces (e.g., an
# existing partition needs to add the Filesystem interface after formatting the
# partition). Dynamically adding or removing interfaces is not possible with ruby-dbus
# once the object is exported on D-Bus.
#
# Updating the currently exported D-Bus objects is avoided by calling to {#clean} first.
#
# @param devicegraph [Y2Storage::Devicegraph]
def update(devicegraph)
clean
self.objects = devices(devicegraph)
end

Expand All @@ -52,31 +61,34 @@ def create_dbus_object(device)
end

# @see BaseTree
# @param dbus_object [Device]
# @param device [Y2Storage::Device]
def update_dbus_object(dbus_object, device)
dbus_object.storage_device = device
#
# @note D-Bus objects representing devices cannot be safely updated, see {#update}.
def update_dbus_object(_dbus_object, _device)
nil
end

# @see BaseTree
# @param dbus_object [Device]
# @param device [Y2Storage::Device]
def dbus_object?(dbus_object, device)
dbus_object.storage_device.sid == device.sid
dbus_object.sid == device.sid
end

# Devices to be exported.
#
# Right now, only the required information for calculating a proposal is exported, that is:
# * Potential candidate devices (i.e., disk devices, MDs).
# * Partitions of the candidate devices in order to indicate how to find free space.
#
# TODO: export LVM VGs and file systems of directly formatted devices.
# * LVM volume groups and logical volumes.
#
# @param devicegraph [Y2Storage::Devicegraph]
# @return [Array<Y2Storage::Device>]
def devices(devicegraph)
devices = devicegraph.disk_devices + devicegraph.software_raids
devices = devicegraph.disk_devices +
devicegraph.software_raids +
devicegraph.lvm_vgs +
devicegraph.lvm_lvs

devices + partitions_from(devices)
end

Expand Down
1 change: 1 addition & 0 deletions service/lib/agama/dbus/storage/interfaces/device.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module Device
require "agama/dbus/storage/interfaces/device/component"
require "agama/dbus/storage/interfaces/device/drive"
require "agama/dbus/storage/interfaces/device/filesystem"
require "agama/dbus/storage/interfaces/device/lvm_vg"
require "agama/dbus/storage/interfaces/device/md"
require "agama/dbus/storage/interfaces/device/multipath"
require "agama/dbus/storage/interfaces/device/partition_table"
Expand Down
16 changes: 16 additions & 0 deletions service/lib/agama/dbus/storage/interfaces/device/block.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,27 @@ def block_name
storage_device.name
end

# Position of the first block of the region.
#
# @return [Integer]
def block_start
storage_device.start
end

# Whether the block device is currently active
#
# @return [Boolean]
def block_active
storage_device.active?
end

# Whether the block device is encrypted.
#
# @return [Boolean]
def block_encrypted
storage_device.encrypted?
end

# Name of the udev by-id links
#
# @return [Array<String>]
Expand Down Expand Up @@ -100,7 +114,9 @@ def self.included(base)
base.class_eval do
dbus_interface BLOCK_INTERFACE do
dbus_reader :block_name, "s", dbus_name: "Name"
dbus_reader :block_start, "t", dbus_name: "Start"
dbus_reader :block_active, "b", dbus_name: "Active"
dbus_reader :block_encrypted, "b", dbus_name: "Encrypted"
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"
Expand Down
2 changes: 2 additions & 0 deletions service/lib/agama/dbus/storage/interfaces/device/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ def self.included(base)
base.class_eval do
dbus_interface COMPONENT_INTERFACE do
dbus_reader :component_type, "s", dbus_name: "Type"
# The names are provided just in case the device is component of a device that
# is not exported yet (e.g., Bcache devices).
dbus_reader :component_device_names, "as", dbus_name: "DeviceNames"
dbus_reader :component_devices, "ao", dbus_name: "Devices"
end
Expand Down
90 changes: 90 additions & 0 deletions service/lib/agama/dbus/storage/interfaces/device/lvm_vg.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# 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 "dbus"

module Agama
module DBus
module Storage
module Interfaces
module Device
# Interface for a LVM Volume Group.
#
# @note This interface is intended to be included by {Agama::DBus::Storage::Device} if
# needed.
module LvmVg
# Whether this interface should be implemented for the given device.
#
# @note LVM Volume Groups implement this interface.
#
# @param storage_device [Y2Storage::Device]
# @return [Boolean]
def self.apply?(storage_device)
storage_device.is?(:lvm_vg)
end

VOLUME_GROUP_INTERFACE = "org.opensuse.Agama.Storage1.LVM.VolumeGroup"
private_constant :VOLUME_GROUP_INTERFACE

# Name of the volume group
#
# @return [String] e.g., "/dev/mapper/vg0"
def lvm_vg_name
storage_device.name
end

# Size of the volume group in bytes
#
# @return [Integer]
def lvm_vg_size
storage_device.size.to_i
end

# D-Bus paths of the objects representing the physical volumes.
#
# @return [Array<String>]
def lvm_vg_pvs
storage_device.lvm_pvs.map { |p| tree.path_for(p.plain_blk_device) }
end

# D-Bus paths of the objects representing the logical volumes.
#
# @return [Array<String>]
def lvm_vg_lvs
storage_device.lvm_lvs.map { |l| tree.path_for(l) }
end

def self.included(base)
base.class_eval do
dbus_interface VOLUME_GROUP_INTERFACE do
dbus_reader :lvm_vg_name, "s", dbus_name: "Name"
dbus_reader :lvm_vg_size, "t", dbus_name: "Size"
dbus_reader :lvm_vg_pvs, "ao", dbus_name: "PhysicalVolumes"
dbus_reader :lvm_vg_lvs, "ao", dbus_name: "LogicalVolumes"
end
end
end
end
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,22 @@ def partition_table_partitions
storage_device.partition_table.partitions.map { |p| tree.path_for(p) }
end

# Available slots within a partition table, that is, the spaces that can be used to
# create a new partition.
#
# @return [Array<Array(Integer, Integer)>] The first block and the size of each slot.
def partition_table_unused_slots
storage_device.partition_table.unused_partition_slots.map do |slot|
[slot.region.start, slot.region.size.to_i]
end
end

def self.included(base)
base.class_eval do
dbus_interface PARTITION_TABLE_INTERFACE do
dbus_reader :partition_table_type, "s", dbus_name: "Type"
dbus_reader :partition_table_partitions, "ao", dbus_name: "Partitions"
dbus_reader :partition_table_unused_slots, "a(tt)", dbus_name: "UnusedSlots"
end
end
end
Expand Down
12 changes: 11 additions & 1 deletion service/lib/agama/dbus/storage/manager.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

# Copyright (c) [2022-2023] SUSE LLC
# Copyright (c) [2022-2024] SUSE LLC
#
# All Rights Reserved.
#
Expand Down Expand Up @@ -289,6 +289,7 @@ def register_proposal_callbacks
proposal.on_calculate do
export_proposal
proposal_properties_changed
refresh_staging_devices
end
end

Expand Down Expand Up @@ -332,6 +333,11 @@ def refresh_system_devices
system_devices_tree.update(devicegraph)
end

def refresh_staging_devices
devicegraph = Y2Storage::StorageManager.instance.staging
staging_devices_tree.update(devicegraph)
end

def refresh_iscsi_nodes
nodes = backend.iscsi.nodes
iscsi_nodes_tree.update(nodes)
Expand All @@ -348,6 +354,10 @@ def system_devices_tree
@system_devices_tree ||= DevicesTree.new(@service, tree_path("system"), logger: logger)
end

def staging_devices_tree
@staging_devices_tree ||= DevicesTree.new(@service, tree_path("staging"), logger: logger)
end

def tree_path(tree_root)
File.join(PATH, tree_root)
end
Expand Down
1 change: 1 addition & 0 deletions service/lib/agama/dbus/storage/proposal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def dbus_settings
# @return [Hash]
def to_dbus_action(action)
{
"Device" => action.target_device.sid,
"Text" => action.sentence,
"Subvol" => action.device_is?(:btrfs_subvolume),
"Delete" => action.delete?
Expand Down
Loading

0 comments on commit e35d470

Please sign in to comment.