diff --git a/internal/hardware/validator.go b/internal/hardware/validator.go index e24c008692..6cd104a429 100644 --- a/internal/hardware/validator.go +++ b/internal/hardware/validator.go @@ -27,10 +27,11 @@ 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" - 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" + wrongISCSINetworkTemplate = "iSCSI host IP %s is the same as host IP, they must be different" + ErrsInIscsiDisableMultipathInstallation = "Installation on multipath device is not possible due to errors on at least one iSCSI disk" ) //go:generate mockgen -source=validator.go -package=hardware -destination=mock_validator.go @@ -155,6 +156,20 @@ func (v *validator) DiskIsEligible(ctx context.Context, disk *models.Disk, infra fmt.Sprintf(wrongMultipathTypeTemplate, inventoryDisk.DriveType, strings.Join([]string{string(models.DriveTypeFC), string(models.DriveTypeISCSI)}, ", "))) break } + // If errors are detected on iSCSI disks, multipath is not allowed + if !lo.Contains(notEligibleReasons, ErrsInIscsiDisableMultipathInstallation) { + if inventoryDisk.DriveType == models.DriveTypeISCSI { + // check if iSCSI boot drive is valid + if !v.IsValidStorageDeviceType(inventoryDisk, hostArchitecture, clusterVersion) { + notEligibleReasons = append(notEligibleReasons, ErrsInIscsiDisableMultipathInstallation) + } + // Check if network is configured properly to install on iSCSI boot drive + err = isISCSINetworkingValid(inventoryDisk, inventory) + if err != nil { + notEligibleReasons = append(notEligibleReasons, ErrsInIscsiDisableMultipathInstallation) + } + } + } } } } diff --git a/internal/hardware/validator_test.go b/internal/hardware/validator_test.go index abfa5076a3..fd4340aa6a 100644 --- a/internal/hardware/validator_test.go +++ b/internal/hardware/validator_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "math" "os" "testing" @@ -293,15 +294,83 @@ var _ = Describe("Disk eligibility", func() { testDisk.DriveType = models.DriveTypeMultipath allDisks := []*models.Disk{&testDisk, {Name: "sda", DriveType: models.DriveTypeISCSI, Holders: "dm-0"}, {Name: "sdb", DriveType: models.DriveTypeISCSI, Holders: "dm-0"}} inventory.Disks = allDisks + cluster.OpenshiftVersion = "4.15.0" + hostInventory, _ := common.UnmarshalInventory(host.Inventory) + + // Add a default IPv6 route + inventory.Routes = append(hostInventory.Routes, &models.Route{ + Family: int32(common.IPv6), + Interface: "eth0", + Gateway: "fe80:db8::1", + Destination: "::", + Metric: 600, + }) + inventory.Interfaces = hostInventory.Interfaces + + operatorsMock.EXPECT().GetRequirementsBreakdownForHostInCluster(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.OperatorHostRequirements{}, nil).AnyTimes() + By("Check Multipath iSCSI is not eligible when host IPv4 address isn't set") notEligibleReasons, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) + Expect(err).ToNot(HaveOccurred()) + Expect(notEligibleReasons).To(ContainElement(ErrsInIscsiDisableMultipathInstallation)) + + By("Check Multipath iSCSI is eligible when host IPv4 address is not part of default network interface") + for _, disk := range allDisks { + disk.Iscsi = &models.Iscsi{HostIPAddress: "4.5.6.7"} + } + notEligibleReasons, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) + Expect(err).ToNot(HaveOccurred()) + Expect(notEligibleReasons).To(BeEmpty(), fmt.Sprintf("Debug info: inventory: %s", host.Inventory)) + + By("Check Multipath iSCSI is not eligible when host IPv4 address is part of default network interface") + for _, disk := range allDisks { + disk.Iscsi = &models.Iscsi{HostIPAddress: "1.2.3.4"} + } + notEligibleReasons, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) + Expect(err).ToNot(HaveOccurred()) + Expect(notEligibleReasons).To(ContainElement(ErrsInIscsiDisableMultipathInstallation)) + + By("Check Multipath iSCSI is eligible when host IPv6 address is not part of default network interface") + for _, disk := range allDisks { + disk.Iscsi = &models.Iscsi{HostIPAddress: "1002:db8::10"} + } + notEligibleReasons, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) + Expect(err).ToNot(HaveOccurred()) + Expect(notEligibleReasons).To(BeEmpty()) + + By("Check Multipath iSCSI is not eligible when host IPv6 address is part of default network interface") + for _, disk := range allDisks { + disk.Iscsi = &models.Iscsi{HostIPAddress: "1001:db8::10"} + } + notEligibleReasons, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) + Expect(err).ToNot(HaveOccurred()) + Expect(notEligibleReasons).To(ContainElement(ErrsInIscsiDisableMultipathInstallation)) + + By("Check Multipath iSCSI on older version is not eligible") + for _, disk := range allDisks { + disk.Iscsi = &models.Iscsi{HostIPAddress: "4.5.6.7"} + } + cluster.OpenshiftVersion = "4.14.1" + notEligibleReasons, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) + Expect(err).ToNot(HaveOccurred()) + Expect(notEligibleReasons).To(ContainElement(ErrsInIscsiDisableMultipathInstallation)) + By("Check Multipath iSCSI is eligible on day2 cluster") + for _, disk := range allDisks { + disk.Iscsi = &models.Iscsi{HostIPAddress: "4.5.6.7"} + } + cluster.Kind = swag.String(models.ClusterKindAddHostsCluster) + cluster.OpenshiftVersion = "" + infraEnv.OpenshiftVersion = "4.16" + notEligibleReasons, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(notEligibleReasons).To(BeEmpty()) By("Check infra env Multipath iSCSI is eligible") + for _, disk := range allDisks { + disk.Iscsi = &models.Iscsi{HostIPAddress: "4.5.6.7"} + } notEligibleReasons, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) - Expect(err).ToNot(HaveOccurred()) Expect(notEligibleReasons).To(BeEmpty()) }) diff --git a/internal/host/validations_test.go b/internal/host/validations_test.go index 5a8cb80e4b..b93c39db04 100644 --- a/internal/host/validations_test.go +++ b/internal/host/validations_test.go @@ -2347,6 +2347,14 @@ var _ = Describe("Validations test", func() { cluster common.Cluster host models.Host ) + + standaloneiSCSIDisks := func(iscsi *models.Iscsi) []*models.Disk { + return []*models.Disk{{ID: "install-disk", DriveType: models.DriveTypeISCSI, Iscsi: iscsi}} + } + multipathiSCSIDisks := func(iscsi *models.Iscsi) []*models.Disk { + return []*models.Disk{{ID: "install-disk", Name: "dm-0", DriveType: models.DriveTypeMultipath}, {Name: "sda", DriveType: models.DriveTypeISCSI, Holders: "dm-0", Iscsi: iscsi}, {Name: "sdb", DriveType: models.DriveTypeISCSI, Holders: "dm-0", Iscsi: iscsi}} + } + BeforeEach(func() { cluster = hostutil.GenerateTestCluster(clusterID) hostId, infraEnvId := strfmt.UUID(uuid.New().String()), strfmt.UUID(uuid.New().String()) @@ -2379,7 +2387,7 @@ var _ = Describe("Validations test", func() { _, _, ok := getValidationResult(refreshedHost.ValidationsInfo, NoIscsiNicBelongsToMachineCidr) Expect(ok).To(BeFalse()) }) - It("Not iSCSI drive", func() { + It("Not iSCSI/ Multipath iSCSI drives", func() { var inventory models.Inventory err := json.Unmarshal([]byte(host.Inventory), &inventory) Expect(err).ShouldNot(HaveOccurred()) @@ -2401,20 +2409,13 @@ var _ = Describe("Validations test", func() { _, _, ok := getValidationResult(refreshedHost.ValidationsInfo, NoIscsiNicBelongsToMachineCidr) Expect(ok).To(BeFalse()) }) - It("iSCSI drive - no network interface set with host IP", func() { + DescribeTable("iSCSI / Multipath iSCSI drive - no network interface set with host IP", func(disks []*models.Disk) { var inventory models.Inventory err := json.Unmarshal([]byte(host.Inventory), &inventory) Expect(err).ShouldNot(HaveOccurred()) host.InstallationDiskID = "install-disk" - inventory.Disks = []*models.Disk{ - { - ID: "install-disk", - DriveType: models.DriveTypeISCSI, - Iscsi: &models.Iscsi{ - HostIPAddress: "99.99.99.99", - }}, - } + inventory.Disks = disks inventoryByte, _ := json.Marshal(inventory) host.Inventory = string(inventoryByte) @@ -2429,22 +2430,19 @@ var _ = Describe("Validations test", func() { Expect(ok).To(BeTrue()) Expect(status).To(Equal(ValidationError)) Expect(message).To(Equal("Cannot find network interface associated to iSCSI host IP address")) - }) + }, + Entry("Standalone iSCSI", standaloneiSCSIDisks(&models.Iscsi{HostIPAddress: "99.99.99.99"})), + Entry("Multipath iSCSI", multipathiSCSIDisks(&models.Iscsi{HostIPAddress: "99.99.99.99"})), + ) DescribeTable( - "iSCSI drive - malformed iSCSI properties", - func(iSCSI *models.Iscsi, expectedMessage string) { + "iSCSI / Multipath iSCSI drive - malformed iSCSI properties", + func(disks []*models.Disk, expectedMessage string) { var inventory models.Inventory err := json.Unmarshal([]byte(host.Inventory), &inventory) Expect(err).ShouldNot(HaveOccurred()) host.InstallationDiskID = "install-disk" - inventory.Disks = []*models.Disk{ - { - ID: "install-disk", - DriveType: models.DriveTypeISCSI, - Iscsi: iSCSI, - }, - } + inventory.Disks = disks inventoryByte, _ := json.Marshal(inventory) host.Inventory = string(inventoryByte) @@ -2460,27 +2458,22 @@ var _ = Describe("Validations test", func() { Expect(status).To(Equal(ValidationError)) Expect(message).To(Equal(expectedMessage)) }, - Entry("iSCSI is nil", nil, "iSCSI installation disk is missing host IP address"), - Entry("Host IP address is empty", &models.Iscsi{}, "Cannot find network interface associated to iSCSI host IP address"), - Entry("Host IP address is invalid", &models.Iscsi{HostIPAddress: "invalid"}, "Cannot find network interface associated to iSCSI host IP address"), + Entry("Standalone iSCSI - iSCSI is nil", standaloneiSCSIDisks(nil), "iSCSI disk is missing host IP address"), + Entry("Multipath iSCSI - iSCSI is nil", multipathiSCSIDisks(nil), "iSCSI disk is missing host IP address"), + Entry("Standalone iSCSI - Host IP address is empty", standaloneiSCSIDisks(&models.Iscsi{}), "Cannot find network interface associated to iSCSI host IP address"), + Entry("Multipath iSCSI - Host IP address is empty", multipathiSCSIDisks(&models.Iscsi{}), "Cannot find network interface associated to iSCSI host IP address"), + Entry("Standalone iSCSI - Host IP address is invalid", standaloneiSCSIDisks(&models.Iscsi{HostIPAddress: "invalid"}), "Cannot find network interface associated to iSCSI host IP address"), + Entry("Multipath iSCSI - Host IP address is invalid", multipathiSCSIDisks(&models.Iscsi{HostIPAddress: "invalid"}), "Cannot find network interface associated to iSCSI host IP address"), ) DescribeTable( - "iSCSI drive - machine networks", - func(iSCSIHostIPAdress string, machineNetworks []*models.MachineNetwork, interfaces []*models.Interface, expectedStatus ValidationStatus, expectedMessage string) { + "iSCSI / Multipath iSCSI drive - machine networks", + func(disks []*models.Disk, machineNetworks []*models.MachineNetwork, interfaces []*models.Interface, expectedStatus ValidationStatus, expectedMessage string) { var inventory models.Inventory err := json.Unmarshal([]byte(host.Inventory), &inventory) Expect(err).ShouldNot(HaveOccurred()) host.InstallationDiskID = "install-disk" - inventory.Disks = []*models.Disk{ - { - ID: "install-disk", - DriveType: models.DriveTypeISCSI, - Iscsi: &models.Iscsi{ - HostIPAddress: iSCSIHostIPAdress, - }, - }, - } + inventory.Disks = disks inventory.Interfaces = interfaces inventoryByte, _ := json.Marshal(inventory) @@ -2497,9 +2490,11 @@ var _ = Describe("Validations test", func() { Expect(message).To(Equal(expectedMessage)) Expect(status).To(Equal(expectedStatus)) }, - Entry("Machine networks is nil", "192.168.1.1", nil, nil, ValidationPending, "Missing inventory or machine network CIDR"), - Entry("Machine networks is empty", "192.168.1.1", []*models.MachineNetwork{}, nil, ValidationPending, "Missing inventory or machine network CIDR"), - Entry("Machine networks IPv4 overlaps with host IPv4 address", "192.168.1.1", + Entry("Standalone iSCSI - Machine networks is nil", standaloneiSCSIDisks(&models.Iscsi{HostIPAddress: "192.168.1.1"}), nil, nil, ValidationPending, "Missing inventory or machine network CIDR"), + Entry("Multipath iSCSI - Machine networks is nil", multipathiSCSIDisks(&models.Iscsi{HostIPAddress: "192.168.1.1"}), nil, nil, ValidationPending, "Missing inventory or machine network CIDR"), + Entry("Standalone iSCSI - Machine networks is empty", standaloneiSCSIDisks(&models.Iscsi{HostIPAddress: "192.168.1.1"}), []*models.MachineNetwork{}, nil, ValidationPending, "Missing inventory or machine network CIDR"), + Entry("Multipath iSCSI - Machine networks is empty", multipathiSCSIDisks(&models.Iscsi{HostIPAddress: "192.168.1.1"}), []*models.MachineNetwork{}, nil, ValidationPending, "Missing inventory or machine network CIDR"), + Entry("Standalone iSCSI - Machine networks IPv4 overlaps with host IPv4 address", standaloneiSCSIDisks(&models.Iscsi{HostIPAddress: "192.168.1.1"}), []*models.MachineNetwork{ { Cidr: "192.168.1.0/24", @@ -2512,7 +2507,20 @@ var _ = Describe("Validations test", func() { }, }, ValidationFailure, "Network interface connected to iSCSI disk cannot belong to machine network CIDRs"), - Entry("Machine networks IPv6 overlaps with host IPv6 address", "2001:0db8:85a3::8a2e:0370:7334", + Entry("Multipath iSCSI - Machine networks IPv4 overlaps with host IPv4 address", multipathiSCSIDisks(&models.Iscsi{HostIPAddress: "192.168.1.1"}), + []*models.MachineNetwork{ + { + Cidr: "192.168.1.0/24", + ClusterID: clusterID, + }, + }, + []*models.Interface{ + { + IPV4Addresses: []string{"192.168.1.1/24"}, + }, + }, + ValidationFailure, fmt.Sprintf("%s: Network interface connected to iSCSI disk (%s) cannot belong to machine network CIDRs", hardware.ErrsInIscsiDisableMultipathInstallation, "sda, sdb")), + Entry("Standalone iSCSI - Machine networks IPv6 overlaps with host IPv6 address", standaloneiSCSIDisks(&models.Iscsi{HostIPAddress: "2001:0db8:85a3::8a2e:0370:7334"}), []*models.MachineNetwork{ { Cidr: "2001:0db8:85a3::/48", @@ -2525,7 +2533,20 @@ var _ = Describe("Validations test", func() { }, }, ValidationFailure, "Network interface connected to iSCSI disk cannot belong to machine network CIDRs"), - Entry("Machine networks IPv6 uses the same interface as host IPv4 address", "192.168.1.1", + Entry("Multipath iSCSI - Machine networks IPv6 overlaps with host IPv6 address", multipathiSCSIDisks(&models.Iscsi{HostIPAddress: "2001:0db8:85a3::8a2e:0370:7334"}), + []*models.MachineNetwork{ + { + Cidr: "2001:0db8:85a3::/48", + ClusterID: clusterID, + }, + }, + []*models.Interface{ + { + IPV6Addresses: []string{"2001:0db8:85a3::8a2e:0370:7334/48"}, + }, + }, + ValidationFailure, fmt.Sprintf("%s: Network interface connected to iSCSI disk (%s) cannot belong to machine network CIDRs", hardware.ErrsInIscsiDisableMultipathInstallation, "sda, sdb")), + Entry("Standalone iSCSI - Machine networks IPv6 uses the same interface as host IPv4 address", standaloneiSCSIDisks(&models.Iscsi{HostIPAddress: "192.168.1.1"}), []*models.MachineNetwork{ { Cidr: "2001:0db8:85a3::/48", @@ -2539,7 +2560,21 @@ var _ = Describe("Validations test", func() { }, }, ValidationFailure, "Network interface connected to iSCSI disk cannot belong to machine network CIDRs"), - Entry("Machine networks IPv4 uses the same interface as host IPv6 address", "2001:0db8:85a3::8a2e:0370:7334", + Entry("Multipath iSCSI - Machine networks IPv6 uses the same interface as host IPv4 address", multipathiSCSIDisks(&models.Iscsi{HostIPAddress: "192.168.1.1"}), + []*models.MachineNetwork{ + { + Cidr: "2001:0db8:85a3::/48", + ClusterID: clusterID, + }, + }, + []*models.Interface{ + { + IPV4Addresses: []string{"192.168.1.1/24"}, + IPV6Addresses: []string{"2001:0db8:85a3::8a2e:0370:7334/48"}, + }, + }, + ValidationFailure, fmt.Sprintf("%s: Network interface connected to iSCSI disk (%s) cannot belong to machine network CIDRs", hardware.ErrsInIscsiDisableMultipathInstallation, "sda, sdb")), + Entry("Standalone iSCSI - Machine networks IPv4 uses the same interface as host IPv6 address", standaloneiSCSIDisks(&models.Iscsi{HostIPAddress: "2001:0db8:85a3::8a2e:0370:7334"}), []*models.MachineNetwork{ { Cidr: "192.168.1.1/24", @@ -2553,12 +2588,62 @@ var _ = Describe("Validations test", func() { }, }, ValidationFailure, "Network interface connected to iSCSI disk cannot belong to machine network CIDRs"), - Entry("Machine networks IPv4 and host IPv4 address use different interfaces", "192.168.1.1", + Entry("Multipath iSCSI - Machine networks IPv4 uses the same interface as host IPv6 address", multipathiSCSIDisks(&models.Iscsi{HostIPAddress: "2001:0db8:85a3::8a2e:0370:7334"}), + []*models.MachineNetwork{ + { + Cidr: "192.168.1.1/24", + ClusterID: clusterID, + }, + }, + []*models.Interface{ + { + IPV4Addresses: []string{"192.168.1.1/24"}, + IPV6Addresses: []string{"2001:0db8:85a3::8a2e:0370:7334/48"}, + }, + }, + ValidationFailure, fmt.Sprintf("%s: Network interface connected to iSCSI disk (%s) cannot belong to machine network CIDRs", hardware.ErrsInIscsiDisableMultipathInstallation, "sda, sdb")), + Entry("Standalone iSCSI - Machine networks IPv4 and host IPv4 address use different interfaces", standaloneiSCSIDisks(&models.Iscsi{HostIPAddress: "192.168.1.1"}), + []*models.MachineNetwork{ + { + Cidr: "192.168.2.1/24", + ClusterID: clusterID, + }, + }, + []*models.Interface{ + { + IPV4Addresses: []string{"192.168.1.1/24"}, + }, + { + IPV4Addresses: []string{"192.168.2.1/24"}, + }, + }, + ValidationSuccess, "Network interface connected to iSCSI disk does not belong to machine network CIDRs"), + Entry("Multipath iSCSI - Machine networks IPv4 and host IPv4 address use different interfaces", multipathiSCSIDisks(&models.Iscsi{HostIPAddress: "192.168.1.1"}), + []*models.MachineNetwork{ + { + Cidr: "192.168.2.1/24", + ClusterID: clusterID, + }, + }, + []*models.Interface{ + { + IPV4Addresses: []string{"192.168.1.1/24"}, + }, + { + IPV4Addresses: []string{"192.168.2.1/24"}, + }, + }, + ValidationSuccess, "Network interface connected to iSCSI disk does not belong to machine network CIDRs"), + Entry("Standalone iSCSI - Machine networks IPv4/IPv6 and host IPv4 address use different interfaces", standaloneiSCSIDisks(&models.Iscsi{HostIPAddress: "192.168.1.1"}), []*models.MachineNetwork{ { Cidr: "192.168.2.1/24", ClusterID: clusterID, }, + { + Cidr: "2001:0db8:85a3::/48", + ClusterID: clusterID, + }, }, []*models.Interface{ { @@ -2566,10 +2651,11 @@ var _ = Describe("Validations test", func() { }, { IPV4Addresses: []string{"192.168.2.1/24"}, + IPV6Addresses: []string{"2001:0db8:85a3::8a2e:0370:7334/48"}, }, }, ValidationSuccess, "Network interface connected to iSCSI disk does not belong to machine network CIDRs"), - Entry("Machine networks IPv4/IPv6 and host IPv4 address use different interfaces", "192.168.1.1", + Entry("Multipath iSCSI - Machine networks IPv4/IPv6 and host IPv4 address use different interfaces", multipathiSCSIDisks(&models.Iscsi{HostIPAddress: "192.168.1.1"}), []*models.MachineNetwork{ { Cidr: "192.168.2.1/24", diff --git a/internal/host/validator.go b/internal/host/validator.go index 63c7eee076..10846a6a90 100644 --- a/internal/host/validator.go +++ b/internal/host/validator.go @@ -1929,19 +1929,26 @@ func (v *validator) noIscsiNicBelongsToMachineCidr(c *validationContext) (Valida return ValidationSuccessSuppressOutput, "" } - if installationDisk.DriveType != models.DriveTypeISCSI { - // Validation is not relevant in this case - return ValidationSuccessSuppressOutput, "" - } - if c.inventory == nil || !network.IsMachineCidrAvailable(c.cluster) { return ValidationPending, "Missing inventory or machine network CIDR" } + if installationDisk.DriveType == models.DriveTypeISCSI { + return v.standaloneiSCSI(c, installationDisk) + } else if installationDisk.DriveType == models.DriveTypeMultipath { + return v.multipathiSCSI(c, installationDisk) + } else { + // Validation is not relevant in this case + return ValidationSuccessSuppressOutput, "" + } +} + +// standaloneiSCSI - Related to the noIscsiNicBelongsToMachineCidr validation. This is executed when the installation disk is an iSCSI disk. +func (v *validator) standaloneiSCSI(c *validationContext, installationDisk *models.Disk) (ValidationStatus, string) { if installationDisk.Iscsi == nil { // If this is nil, the disk shouldn't have passed the eligilibily test in the first place - v.log.Warn("iSCSI installation disk is missing host IP address") - return ValidationError, "iSCSI installation disk is missing host IP address" + v.log.Warn("iSCSI disk is missing host IP address") + return ValidationError, "iSCSI disk is missing host IP address" } nic, err := network.FindInterfaceByIPString(installationDisk.Iscsi.HostIPAddress, c.inventory.Interfaces) @@ -1957,3 +1964,24 @@ func (v *validator) noIscsiNicBelongsToMachineCidr(c *validationContext) (Valida return ValidationSuccess, "Network interface connected to iSCSI disk does not belong to machine network CIDRs" } + +// multipathiSCSI - Related to the noIscsiNicBelongsToMachineCidr validation. This is executed when the installation disk is a multipath disk serving as a holder for iSCSI drives. +func (v *validator) multipathiSCSI(c *validationContext, installationDisk *models.Disk) (ValidationStatus, string) { + var status ValidationStatus + msg := "" + var iSCSIDisksWithErrors []string + for _, disk := range c.inventory.Disks { + if disk.DriveType == models.DriveTypeISCSI && strings.Contains(disk.Holders, installationDisk.Name) { + status, msg = v.standaloneiSCSI(c, disk) + if status == ValidationError { + return ValidationError, msg + } else if status == ValidationFailure { + iSCSIDisksWithErrors = append(iSCSIDisksWithErrors, disk.Name) + } + } + } + if len(iSCSIDisksWithErrors) > 0 { + return ValidationFailure, fmt.Sprintf("%s: Network interface connected to iSCSI disk (%s) cannot belong to machine network CIDRs", hardware.ErrsInIscsiDisableMultipathInstallation, strings.Join(iSCSIDisksWithErrors, ", ")) + } + return ValidationSuccess, msg +}