diff --git a/internal/hardware/validator.go b/internal/hardware/validator.go index f7196f0e9c..1097410480 100644 --- a/internal/hardware/validator.go +++ b/internal/hardware/validator.go @@ -27,11 +27,12 @@ import ( ) const ( - tooSmallDiskTemplate = "Disk is too small (disk only has %s, but %s are required)" - wrongDriveTypeTemplate = "Drive type is %s, it must be one of %s." - wrongMultipathTypeTemplate = "Multipath device has path of type %s, it must be %s" - iSCSIWithMultipathHolder = "iSCSI disk with a multipath holder is not eligible" - wrongISCSINetworkTemplate = "iSCSI host IP %s is the same as host IP, they must be different" + tooSmallDiskTemplate = "Disk is too small (disk only has %s, but %s are required)" + wrongDriveTypeTemplate = "Drive type is %s, it must be one of %s." + wrongMultipathTypeTemplate = "Multipath device has path of type %s, it must be %s" + iSCSIWithMultipathHolder = "iSCSI disk with a multipath holder is not eligible" + wrongISCSINetworkTemplate = "iSCSI host IP %s is the same as host IP, they must be different" + errsInIscsiDisableMultipathInstallation = "Installation in multipath is not possible due to errors in at least one iSCSI disk." ) //go:generate mockgen -source=validator.go -package=hardware -destination=mock_validator.go @@ -147,13 +148,13 @@ func (v *validator) DiskIsEligible(ctx context.Context, disk *models.Disk, infra fmt.Sprintf(wrongDriveTypeTemplate, disk.DriveType, strings.Join(v.getValidDeviceStorageTypes(hostArchitecture, clusterVersion), ", "))) } - // We only allow multipath if all paths are FC if disk.DriveType == models.DriveTypeMultipath { for _, inventoryDisk := range inventory.Disks { - if lo.Contains(strings.Split(inventoryDisk.Holders, ","), disk.Name) { - if inventoryDisk.DriveType != models.DriveTypeFC { + if strings.Contains(inventoryDisk.Holders, disk.Name) { + // We only allow multipath if all paths are FC/ iSCSI + if inventoryDisk.DriveType != models.DriveTypeFC && inventoryDisk.DriveType != models.DriveTypeISCSI { notEligibleReasons = append(notEligibleReasons, - fmt.Sprintf(wrongMultipathTypeTemplate, inventoryDisk.DriveType, string(models.DriveTypeFC))) + fmt.Sprintf(wrongMultipathTypeTemplate, inventoryDisk.DriveType, fmt.Sprintf("%s or %s", string(models.DriveTypeFC), string(models.DriveTypeISCSI)))) break } } @@ -161,11 +162,6 @@ func (v *validator) DiskIsEligible(ctx context.Context, disk *models.Disk, infra } if disk.DriveType == models.DriveTypeISCSI { - err := areISCSIHoldersValid(disk, inventory) - if err != nil { - notEligibleReasons = append(notEligibleReasons, err.Error()) - } - // Check if network is configured properly to install on iSCSI boot drive err = isISCSINetworkingValid(disk, inventory) if err != nil { @@ -176,27 +172,6 @@ func (v *validator) DiskIsEligible(ctx context.Context, disk *models.Disk, infra return notEligibleReasons, nil } -// Validate holders of iSCSI disk. We do not allow iSCSI disk with multipath holder. -func areISCSIHoldersValid(disk *models.Disk, inventory *models.Inventory) error { - multipathDiskNamesMap := make(map[string]struct{}) - for _, inventoryDisk := range inventory.Disks { - if inventoryDisk.DriveType == models.DriveTypeMultipath { - multipathDiskNamesMap[inventoryDisk.Name] = struct{}{} - } - } - - // Check if the iSCSI disk has any holders that are multipath disks - holders := strings.Split(disk.Holders, ",") - for _, holder := range holders { - if _, exists := multipathDiskNamesMap[holder]; exists { - return fmt.Errorf(iSCSIWithMultipathHolder) - } - } - - return nil - -} - // isISCSINetworkingValid checks if the iSCSI disk is not connected through the // default network interface. The default network interface is the interface // which is used by the default gateway. diff --git a/internal/host/hostcommands/install_cmd.go b/internal/host/hostcommands/install_cmd.go index 385007e17f..010a2f565e 100644 --- a/internal/host/hostcommands/install_cmd.go +++ b/internal/host/hostcommands/install_cmd.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "k8s.io/utils/strings/slices" "net" "net/netip" "strings" @@ -300,7 +301,10 @@ func constructHostInstallerArgs(cluster *common.Cluster, host *models.Host, inve // append kargs depending on installation drive type installationDisk := hostutil.GetDiskByInstallationPath(inventory.Disks, hostutil.GetHostInstallationPath(host)) if installationDisk != nil { - installerArgs = appendMultipathArgs(installerArgs, installationDisk) + installerArgs, err = appendMultipathArgs(installerArgs, installationDisk, inventory, hasUserConfiguredIP) + if err != nil { + return "", err + } installerArgs, err = appendISCSIArgs(installerArgs, installationDisk, inventory, hasUserConfiguredIP) if err != nil { return "", err @@ -370,7 +374,9 @@ func appendISCSIArgs(installerArgs []string, installationDisk *models.Disk, inve } // enable iSCSI on boot - installerArgs = append(installerArgs, "--append-karg", "rd.iscsi.firmware=1") + if !slices.Contains(installerArgs, "rd.iscsi.firmware=1") { + installerArgs = append(installerArgs, "--append-karg", "rd.iscsi.firmware=1") + } if hasUserConfiguredIP { return installerArgs, nil @@ -391,16 +397,32 @@ func appendISCSIArgs(installerArgs []string, installationDisk *models.Disk, inve if iSCSIHostIP.Is6() { dhcp = "dhcp6" } - installerArgs = append(installerArgs, "--append-karg", fmt.Sprintf("ip=%s:%s", nic.Name, dhcp)) + + if !slices.Contains(installerArgs, fmt.Sprintf("ip=%s:%s", nic.Name, dhcp)) { + installerArgs = append(installerArgs, "--append-karg", fmt.Sprintf("ip=%s:%s", nic.Name, dhcp)) + } return installerArgs, nil } -func appendMultipathArgs(installerArgs []string, installationDisk *models.Disk) []string { +func appendMultipathArgs(installerArgs []string, installationDisk *models.Disk, inventory *models.Inventory, hasUserConfiguredIP bool) ([]string, error) { if installationDisk.DriveType != models.DriveTypeMultipath { - return installerArgs + return installerArgs, nil } - return append(installerArgs, "--append-karg", "root=/dev/disk/by-label/dm-mpath-root", "--append-karg", "rw", "--append-karg", "rd.multipath=default") + + installerArgs = append(installerArgs, "--append-karg", "root=/dev/disk/by-label/dm-mpath-root", "--append-karg", "rw", "--append-karg", "rd.multipath=default") + + for _, disk := range inventory.Disks { + if disk.DriveType == models.DriveTypeISCSI && strings.Contains(disk.Holders, installationDisk.Name) { + iSCSIInstallerArgs, err := appendISCSIArgs(installerArgs, disk, inventory, hasUserConfiguredIP) + if err != nil { + return nil, err + } + installerArgs = append(installerArgs, iSCSIInstallerArgs...) + break + } + } + return installerArgs, nil } func appends390xArgs(inventory *models.Inventory, installerArgs []string, log logrus.FieldLogger) ([]string, bool) { diff --git a/internal/network/manifests_generator.go b/internal/network/manifests_generator.go index 30003be1d5..c25415e83c 100644 --- a/internal/network/manifests_generator.go +++ b/internal/network/manifests_generator.go @@ -555,12 +555,14 @@ spec: [Service] Type=oneshot - ExecStart=-/bin/sh -c ' \ - lsblk -o NAME,TRAN,MOUNTPOINTS --json | jq -e \'.blockdevices[] | select(.tran == "iscsi") | select(.children) | .children[].mountpoints | select(.) | index("/sysroot") | select(.)\' > /dev/null; \ - if [ $? = 0 ]; \ - then \ - echo "iSCSI boot volume detected, force network reconfiguration..."; \ - nmcli -t -f DEVICE device status | xargs -l nmcli device reapply; \ + ExecStart=-/bin/sh -c ' set -x; \ + MULTIPATH=".blockdevices[] | select(.tran == \\\"iscsi\\\") | select(.children) | .children[] | select(.children) | .children[].mountpoints | select(.) | index(\\\"/sysroot\\\")"; \ + ISCSI_ALONE=".blockdevices[] | select(.tran == \\\"iscsi\\\") | select(.children) | .children[].mountpoints | select(.) | index(\\\"/sysroot\\\")"; \ + (lsblk -o NAME,TRAN,MOUNTPOINTS --json | jq -e "$MULTIPATH" > /dev/null) || (lsblk -o NAME,TRAN,MOUNTPOINTS --json | jq -e "$ISCSI_ALONE" > /dev/null); \ + if [ $? = 0 ]; \ + then \ + echo "iSCSI OR multipath iSCSI boot volume detected, force network reconfiguration..."; \ + nmcli -t -f DEVICE device status | xargs -l nmcli device reapply; \ fi' ExecStartPost=-systemctl disable iscsi-nic-reapply.service @@ -569,12 +571,23 @@ spec: ` func (m *ManifestsGenerator) AddNicReapply(ctx context.Context, log logrus.FieldLogger, c *common.Cluster) error { - // Add this manifest only is one of the host is installting on an iSCSI boot drive + // Add this manifest only if one of the host is installing on an iSCSI / multiapth + iSCSI boot drive _, isUsingISCSIBootDrive := lo.Find(c.Cluster.Hosts, func(h *models.Host) bool { - installationDisk, err := hostutil.GetHostInstallationDisk(h) + inventory, err := common.UnmarshalInventory(h.Inventory) if err != nil { return false } + installationDisk := hostutil.GetDiskByInstallationPath(inventory.Disks, hostutil.GetHostInstallationPath(h)) + if installationDisk.DriveType == models.DriveTypeMultipath { + if err != nil { + return false + } + for _, disk := range inventory.Disks { + if disk.DriveType == models.DriveTypeISCSI && strings.Contains(disk.Holders, installationDisk.Name) { + return true + } + } + } return installationDisk.DriveType == models.DriveTypeISCSI })