yast/yast-storage-ng

View on GitHub
src/lib/y2storage/proposal/autoinst_nfs_planner.rb

Summary

Maintainability
A
0 mins
Test Coverage
# Copyright (c) [2019] 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 "y2storage/proposal/autoinst_drive_planner"

module Y2Storage
  module Proposal
    # This class converts an AutoYaST specification into a Planned::Nfs in order
    # to set up a NFS filesystem.
    class AutoinstNfsPlanner < AutoinstDrivePlanner
      # Returns an array of planned NFS filesystems according to an AutoYaST specification
      #
      # @param drive [AutoinstProfile::DriveSection] drive section describing NFS filesystems
      # @return [Array<Planned::Nfs>] Planned NFS filesystems
      def planned_devices(drive)
        return planned_devices_old_format(drive) if old_format?(drive)

        planned_devices_new_format(drive)
      end

      private

      # This hash defines mandatory profile values for a NFS share using the old AutoYaST style.
      #
      # Keys are the name of the section, and values are the mandatory values for such section.
      #
      # With old format, the partition section must contain a device and a mount value, e.g.:
      #
      # <drive>
      #   <device>/dev/nfs</device>
      #   <partitions>
      #     <partition>
      #       <device>192.168.56.1:/root_fs</device>
      #       <mount>/</mount>
      #     </partition>
      #   </partitions>
      # </drive>
      OLD_FORMAT_MANDATORY_VALUES = { drive: [], partition: [:device, :mount] }.freeze

      # Similar to {OLD_FORMAT_MANDATORY_VALUES}, but for the new AutoYaST style.
      #
      # With new format, drive section must contain a device and partition section must contain
      # a mount value, e.g.:
      #
      # <drive>
      #   <device>192.168.56.1:/root_fs</device>
      #   <partitions>
      #     <partition>
      #       <mount>/</mount>
      #     </partition>
      #   </partitions>
      # </drive>
      NEW_FORMAT_MANDATORY_VALUES = { drive: [:device], partition: [:mount] }.freeze

      private_constant :OLD_FORMAT_MANDATORY_VALUES, :NEW_FORMAT_MANDATORY_VALUES

      def old_format?(drive)
        drive.device == "/dev/nfs" || drive.partitions.any?(&:device)
      end

      # Returns a list of planned NFS filesystems from the old-style AutoYaST profile
      #
      # Using `/dev/nfs` as device name means that the whole drive section should be treated as an
      # old-style AutoYaST NFS description. Each partition represents an NFS filesystem and the `device`
      # is used to indicate the NFS share (e.g., `192.168.56.1:/root_fs`).
      #
      # @param drive [AutoinstProfile::DriveSection] drive section describing the list of NFS
      #   filesystems (old-style AutoYaST)
      # @return [Array<Planned::Nfs>] List of planned NFS filesystems
      def planned_devices_old_format(drive)
        drive.partitions.map { |p| planned_device_old_format(drive, p) }.compact
      end

      # Creates a planned NFS filesystem from the old-style AutoYaST profile
      #
      # @param drive [AutoinstProfile::DriveSection] drive section describing the list of NFS
      #   filesystems
      # @param partition_section [AutoinstProfile::PartitionSection] partition section describing
      #   a NFS filesystem
      #
      # @return [Planned::Nfs]
      def planned_device_old_format(drive, partition_section)
        return nil unless valid_drive?(drive, partition: partition_section, profile_format: :old)

        issues_list.add(Y2Storage::AutoinstIssues::NoPartitionable, drive) if drive.wanted_partitions?

        share = partition_section.device

        create_planned_nfs(share, partition_section)
      end

      # Returns a list of planned NFS filesystems from the new-style AutoYaST profile
      #
      # @param drive [AutoinstProfile::DriveSection] drive section describing a NFS share
      # @return [Array<Planned::Nfs>] List containing the planned NFS filesystem
      def planned_devices_new_format(drive)
        [planned_device_new_format(drive)].compact
      end

      # Creates a planned NFS filesystem from the new-style AutoYaST profile
      #
      # @param drive [AutoinstProfile::DriveSection] drive section describing a NFS share
      # @return [Planned::Nfs]
      def planned_device_new_format(drive)
        return nil unless valid_drive?(drive, profile_format: :new)

        issues_list.add(Y2Storage::AutoinstIssues::NoPartitionable, drive) if drive.wanted_partitions?
        issues_list.add(Y2Storage::AutoinstIssues::SurplusPartitions, drive) if drive.partitions.size > 1

        share = drive.device
        master_partition = drive.partitions.first

        create_planned_nfs(share, master_partition)
      end

      # Creates a planned NFS filesystem
      #
      # @param share [String] NFS share name with the format server:path
      # @param partition_section [AutoinstProfile::PartitionSection] partition section describing
      #   a NFS filesystem
      #
      # @return [Planned::Nfs]
      def create_planned_nfs(share, partition_section)
        planned_nfs = Planned::Nfs.new(server(share), path(share))

        planned_nfs.mount_point = partition_section.mount
        planned_nfs.fstab_options = partition_section.fstab_options || []

        planned_nfs
      end

      # Whether the drive section is valid
      #
      # Errors are registered when the section is not valid.
      #
      # @param drive [AutoinstProfile::DriveSection]
      # @param partition [AutoinstProfile::PartitionSection]
      # @param profile_format [:new, :old] whether the section is using the new or old AutoYaST style
      #
      # @return [Boolean]
      def valid_drive?(drive, partition: nil, profile_format: :new)
        partition_section = partition || drive.partitions.first

        !missing_drive_values?(drive, profile_format) &&
          !missing_partition_values?(partition_section, profile_format)
      end

      # Whether any value is missing for the drive section
      #
      # Errors are registered when values are missing.
      #
      # @param drive [AutoinstProfile::DriveSection]
      # @param profile_format [:new, :old] whether the section is using the new or old AutoYaST style
      #
      # @return [Boolean]
      def missing_drive_values?(drive, profile_format)
        missing_any_value?(drive, mandatory_drive_values(profile_format))
      end

      # Whether any value is missing for the partition section
      #
      # Errors are registered when values are missing.
      #
      # @param partition_section [AutoinstProfile::PartitionSection]
      # @param profile_format [:new, :old] whether the section is using the new or old AutoYaST style
      #
      # @return [Boolean]
      def missing_partition_values?(partition_section, profile_format)
        missing_any_value?(partition_section, mandatory_partition_values(profile_format))
      end

      # Whether any of the given values is missing in the given section
      #
      # Note: finding the first missing value is faster, but all values are checked to
      # register all possible issues.
      #
      # @param section [::Installation::AutoinstProfile::SectionWithAttributes]
      # @param values [Array<Symbol>]
      #
      # @return [Boolean]
      def missing_any_value?(section, values)
        values.map { |v| missing_value?(section, v) }.any?
      end

      # Whether the given value is missing in the given section
      #
      # An error is registered when the value is missing.
      #
      # @param section [::Installation::AutoinstProfile::SectionWithAttributes]
      # @param value [Symbol]
      #
      # @return [Boolean]
      def missing_value?(section, value)
        return false if section.send(value)

        issues_list.add(Y2Storage::AutoinstIssues::MissingValue, section, value)

        true
      end

      # Mandatory values for the drive section
      #
      # @param profile_format [:new, :old] whether the section is using the new or old AutoYaST style
      # @return [Array<Symbol>]
      def mandatory_drive_values(profile_format)
        mandatory_values(profile_format)[:drive]
      end

      # Mandatory values for the partition section
      #
      # @param profile_format [:new, :old] whether the section is using the new or old AutoYaST style
      # @return [Array<Symbol>]
      def mandatory_partition_values(profile_format)
        mandatory_values(profile_format)[:partition]
      end

      # Mandatory values depending on the AutoYaST style
      #
      # @param profile_format [:new, :old] new or old AutoYaST style
      # @return [Hash<Symbol, Array<Symbol>>] see {OLD_FORMAT_MANDATORY_VALUES} and
      #   {NEW_FORMAT_MANDATORY_VALUES}
      def mandatory_values(profile_format)
        if profile_format == :new
          NEW_FORMAT_MANDATORY_VALUES
        else
          OLD_FORMAT_MANDATORY_VALUES
        end
      end

      # Name of the server from a NFS share
      #
      # @param share [String] e.g., "192.168.56.1:/root_fs"
      # @return [String] e.g., "192.168.56.1"
      def server(share)
        server_and_path(share).first || ""
      end

      # Name of the shared directory from a NFS share
      #
      # @param share [String] e.g., "192.168.56.1:/root_fs"
      # @return [String] e.g., "/root_fs"
      def path(share)
        server_and_path(share).last || ""
      end

      # Name of the server and the shared directory from a NFS share
      #
      # Note that the directory can be omitted (e.g., "192.168.56.1"). For more details,
      # see {https://tools.ietf.org/html/rfc2224}.
      #
      # @param share [String] e.g., "192.168.56.1:/root_fs"
      # @return [Array<String, nil>]
      def server_and_path(share)
        server, path = share.split(":")
        [server, path]
      end
    end
  end
end