diff --git a/api/vendor/github.com/openshift/assisted-service/models/disk.go b/api/vendor/github.com/openshift/assisted-service/models/disk.go index 84b3a5bf4121..7574de9e72b5 100644 --- a/api/vendor/github.com/openshift/assisted-service/models/disk.go +++ b/api/vendor/github.com/openshift/assisted-service/models/disk.go @@ -51,6 +51,9 @@ type Disk struct { // Whether the disk appears to be an installation media or not IsInstallationMedia bool `json:"is_installation_media,omitempty"` + // iscsi + Iscsi *Iscsi `json:"iscsi,omitempty"` + // model Model string `json:"model,omitempty"` @@ -98,6 +101,10 @@ func (m *Disk) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateIscsi(formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -157,6 +164,25 @@ func (m *Disk) validateIoPerf(formats strfmt.Registry) error { return nil } +func (m *Disk) validateIscsi(formats strfmt.Registry) error { + if swag.IsZero(m.Iscsi) { // not required + return nil + } + + if m.Iscsi != nil { + if err := m.Iscsi.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("iscsi") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("iscsi") + } + return err + } + } + + return nil +} + // ContextValidate validate this disk based on the context it is used func (m *Disk) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error @@ -173,6 +199,10 @@ func (m *Disk) ContextValidate(ctx context.Context, formats strfmt.Registry) err res = append(res, err) } + if err := m.contextValidateIscsi(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -223,6 +253,22 @@ func (m *Disk) contextValidateIoPerf(ctx context.Context, formats strfmt.Registr return nil } +func (m *Disk) contextValidateIscsi(ctx context.Context, formats strfmt.Registry) error { + + if m.Iscsi != nil { + if err := m.Iscsi.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("iscsi") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("iscsi") + } + return err + } + } + + return nil +} + // MarshalBinary interface implementation func (m *Disk) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/api/vendor/github.com/openshift/assisted-service/models/iscsi.go b/api/vendor/github.com/openshift/assisted-service/models/iscsi.go new file mode 100644 index 000000000000..865d1ca20b31 --- /dev/null +++ b/api/vendor/github.com/openshift/assisted-service/models/iscsi.go @@ -0,0 +1,50 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// Iscsi iscsi +// +// swagger:model iscsi +type Iscsi struct { + + // Host IP address used to reach iSCSI target + HostIPAddress string `json:"host_ip_address,omitempty"` +} + +// Validate validates this iscsi +func (m *Iscsi) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this iscsi based on context it is used +func (m *Iscsi) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *Iscsi) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Iscsi) UnmarshalBinary(b []byte) error { + var res Iscsi + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/client/vendor/github.com/openshift/assisted-service/models/disk.go b/client/vendor/github.com/openshift/assisted-service/models/disk.go index 84b3a5bf4121..7574de9e72b5 100644 --- a/client/vendor/github.com/openshift/assisted-service/models/disk.go +++ b/client/vendor/github.com/openshift/assisted-service/models/disk.go @@ -51,6 +51,9 @@ type Disk struct { // Whether the disk appears to be an installation media or not IsInstallationMedia bool `json:"is_installation_media,omitempty"` + // iscsi + Iscsi *Iscsi `json:"iscsi,omitempty"` + // model Model string `json:"model,omitempty"` @@ -98,6 +101,10 @@ func (m *Disk) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateIscsi(formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -157,6 +164,25 @@ func (m *Disk) validateIoPerf(formats strfmt.Registry) error { return nil } +func (m *Disk) validateIscsi(formats strfmt.Registry) error { + if swag.IsZero(m.Iscsi) { // not required + return nil + } + + if m.Iscsi != nil { + if err := m.Iscsi.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("iscsi") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("iscsi") + } + return err + } + } + + return nil +} + // ContextValidate validate this disk based on the context it is used func (m *Disk) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error @@ -173,6 +199,10 @@ func (m *Disk) ContextValidate(ctx context.Context, formats strfmt.Registry) err res = append(res, err) } + if err := m.contextValidateIscsi(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -223,6 +253,22 @@ func (m *Disk) contextValidateIoPerf(ctx context.Context, formats strfmt.Registr return nil } +func (m *Disk) contextValidateIscsi(ctx context.Context, formats strfmt.Registry) error { + + if m.Iscsi != nil { + if err := m.Iscsi.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("iscsi") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("iscsi") + } + return err + } + } + + return nil +} + // MarshalBinary interface implementation func (m *Disk) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/client/vendor/github.com/openshift/assisted-service/models/iscsi.go b/client/vendor/github.com/openshift/assisted-service/models/iscsi.go new file mode 100644 index 000000000000..865d1ca20b31 --- /dev/null +++ b/client/vendor/github.com/openshift/assisted-service/models/iscsi.go @@ -0,0 +1,50 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// Iscsi iscsi +// +// swagger:model iscsi +type Iscsi struct { + + // Host IP address used to reach iSCSI target + HostIPAddress string `json:"host_ip_address,omitempty"` +} + +// Validate validates this iscsi +func (m *Iscsi) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this iscsi based on context it is used +func (m *Iscsi) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *Iscsi) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Iscsi) UnmarshalBinary(b []byte) error { + var res Iscsi + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/internal/common/common.go b/internal/common/common.go index 8ad08b18cae5..2d6137c0c401 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -15,6 +15,7 @@ import ( yamlpatch "github.com/krishicks/yaml-patch" "github.com/openshift/assisted-service/models" "github.com/thoas/go-funk" + "golang.org/x/sys/unix" "gorm.io/gorm" ) @@ -40,9 +41,6 @@ const ( IgnitionTokenKeyInSecret = "ignition-token" - FamilyIPv4 int32 = 4 - FamilyIPv6 int32 = 6 - AMD64CPUArchitecture = "amd64" X86CPUArchitecture = "x86_64" DefaultCPUArchitecture = X86CPUArchitecture @@ -56,6 +54,24 @@ const ( ExternalPlatformNameOci = "oci" ) +type AddressFamily int + +const ( + IPv4 AddressFamily = unix.AF_INET + IPv6 AddressFamily = unix.AF_INET6 +) + +func (a AddressFamily) String() string { + switch a { + case IPv4: + return "IPv4" + case IPv6: + return "IPv6" + default: + return fmt.Sprintf("Unexpected family value %d", a) + } +} + var ( UnlimitedEvents *int64 = swag.Int64(-1) NoOffsetEvents *int64 = swag.Int64(0) diff --git a/internal/common/test_configuration.go b/internal/common/test_configuration.go index cd12d1217876..3c5ba0876ff7 100644 --- a/internal/common/test_configuration.go +++ b/internal/common/test_configuration.go @@ -280,7 +280,7 @@ var TestDomainNameResolutionsWildcardResolved = &models.DomainResolutionResponse var TestDomainNameResolutionsWildcardResolvedWithCname = &models.DomainResolutionResponse{Resolutions: WildcardResolvedWithCname} var TestSubDomainNameResolutionsWildcardResolved = &models.DomainResolutionResponse{Resolutions: SubDomainWildcardResolved} -var TestDefaultRouteConfiguration = []*models.Route{{Family: FamilyIPv4, Interface: "eth0", Gateway: "1.2.3.10", Destination: "0.0.0.0", Metric: 600}} +var TestDefaultRouteConfiguration = []*models.Route{{Family: int32(IPv4), Interface: "eth0", Gateway: "1.2.3.10", Destination: "0.0.0.0", Metric: 600}} var TestIPv4Networking = TestNetworking{ ClusterNetworks: []*models.ClusterNetwork{{Cidr: "1.3.0.0/16", HostPrefix: 24}}, diff --git a/internal/hardware/mock_validator.go b/internal/hardware/mock_validator.go index 286294ee27a6..f925fcc758a9 100644 --- a/internal/hardware/mock_validator.go +++ b/internal/hardware/mock_validator.go @@ -37,18 +37,18 @@ func (m *MockValidator) EXPECT() *MockValidatorMockRecorder { } // DiskIsEligible mocks base method. -func (m *MockValidator) DiskIsEligible(ctx context.Context, disk *models.Disk, infraEnv *common.InfraEnv, cluster *common.Cluster, host *models.Host, hostArchitecture string, allDisks []*models.Disk) ([]string, error) { +func (m *MockValidator) DiskIsEligible(ctx context.Context, disk *models.Disk, infraEnv *common.InfraEnv, cluster *common.Cluster, host *models.Host, inventory *models.Inventory) ([]string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DiskIsEligible", ctx, disk, infraEnv, cluster, host, hostArchitecture, allDisks) + ret := m.ctrl.Call(m, "DiskIsEligible", ctx, disk, infraEnv, cluster, host, inventory) ret0, _ := ret[0].([]string) ret1, _ := ret[1].(error) return ret0, ret1 } // DiskIsEligible indicates an expected call of DiskIsEligible. -func (mr *MockValidatorMockRecorder) DiskIsEligible(ctx, disk, infraEnv, cluster, host, hostArchitecture, allDisks interface{}) *gomock.Call { +func (mr *MockValidatorMockRecorder) DiskIsEligible(ctx, disk, infraEnv, cluster, host, inventory interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DiskIsEligible", reflect.TypeOf((*MockValidator)(nil).DiskIsEligible), ctx, disk, infraEnv, cluster, host, hostArchitecture, allDisks) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DiskIsEligible", reflect.TypeOf((*MockValidator)(nil).DiskIsEligible), ctx, disk, infraEnv, cluster, host, inventory) } // GetClusterHostRequirements mocks base method. diff --git a/internal/hardware/validator.go b/internal/hardware/validator.go index 569a743dfedd..41b408a9d81d 100644 --- a/internal/hardware/validator.go +++ b/internal/hardware/validator.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "math" + "net/netip" "regexp" "sort" "strings" @@ -15,12 +16,13 @@ import ( "github.com/openshift/assisted-service/internal/common" "github.com/openshift/assisted-service/internal/feature" "github.com/openshift/assisted-service/internal/host/hostutil" + "github.com/openshift/assisted-service/internal/network" "github.com/openshift/assisted-service/internal/operators" "github.com/openshift/assisted-service/internal/provider/registry" "github.com/openshift/assisted-service/models" "github.com/openshift/assisted-service/pkg/conversions" + "github.com/samber/lo" "github.com/sirupsen/logrus" - "github.com/thoas/go-funk" "k8s.io/utils/ptr" ) @@ -29,6 +31,7 @@ const ( 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" ) //go:generate mockgen -source=validator.go -package=hardware -destination=mock_validator.go @@ -37,7 +40,7 @@ type Validator interface { GetHostInstallationPath(host *models.Host) string GetClusterHostRequirements(ctx context.Context, cluster *common.Cluster, host *models.Host) (*models.ClusterHostRequirements, error) GetInfraEnvHostRequirements(ctx context.Context, infraEnv *common.InfraEnv) (*models.ClusterHostRequirements, error) - DiskIsEligible(ctx context.Context, disk *models.Disk, infraEnv *common.InfraEnv, cluster *common.Cluster, host *models.Host, hostArchitecture string, allDisks []*models.Disk) ([]string, error) + DiskIsEligible(ctx context.Context, disk *models.Disk, infraEnv *common.InfraEnv, cluster *common.Cluster, host *models.Host, inventory *models.Inventory) ([]string, error) ListEligibleDisks(inventory *models.Inventory) []*models.Disk IsValidStorageDeviceType(disk *models.Disk, hostArchitecture string, openshiftVersion string) bool GetInstallationDiskSpeedThresholdMs(ctx context.Context, cluster *common.Cluster, host *models.Host) (int64, error) @@ -108,7 +111,7 @@ func isNvme(name string) bool { // it against a list of predicates. Returns all the reasons the disk // was found to be not eligible, or an empty slice if it was found to // be eligible -func (v *validator) DiskIsEligible(ctx context.Context, disk *models.Disk, infraEnv *common.InfraEnv, cluster *common.Cluster, host *models.Host, hostArchitecture string, allDisks []*models.Disk) ([]string, error) { +func (v *validator) DiskIsEligible(ctx context.Context, disk *models.Disk, infraEnv *common.InfraEnv, cluster *common.Cluster, host *models.Host, inventory *models.Inventory) ([]string, error) { var requirements *models.ClusterHostRequirements var err error var clusterVersion string @@ -133,6 +136,7 @@ func (v *validator) DiskIsEligible(ctx context.Context, disk *models.Disk, infra humanize.Bytes(uint64(disk.SizeBytes)), humanize.Bytes(uint64(minSizeBytes)))) } + hostArchitecture := inventory.CPU.Architecture if !v.IsValidStorageDeviceType(disk, hostArchitecture, clusterVersion) { notEligibleReasons = append(notEligibleReasons, fmt.Sprintf(wrongDriveTypeTemplate, disk.DriveType, strings.Join(v.getValidDeviceStorageTypes(hostArchitecture, clusterVersion), ", "))) @@ -140,8 +144,8 @@ func (v *validator) DiskIsEligible(ctx context.Context, disk *models.Disk, infra // We only allow multipath if all paths are FC if disk.DriveType == models.DriveTypeMultipath { - for _, inventoryDisk := range allDisks { - if funk.ContainsString(strings.Split(inventoryDisk.Holders, ","), disk.Name) { + for _, inventoryDisk := range inventory.Disks { + if lo.Contains(strings.Split(inventoryDisk.Holders, ","), disk.Name) { if inventoryDisk.DriveType != models.DriveTypeFC { notEligibleReasons = append(notEligibleReasons, fmt.Sprintf(wrongMultipathTypeTemplate, inventoryDisk.DriveType, string(models.DriveTypeFC))) @@ -151,30 +155,88 @@ func (v *validator) DiskIsEligible(ctx context.Context, disk *models.Disk, infra } } - // We are not allow iSCSI disk with multipath holder if disk.DriveType == models.DriveTypeISCSI { - multipathDiskNamesMap := make(map[string]struct{}) - for _, inventoryDisk := range allDisks { - if inventoryDisk.DriveType == models.DriveTypeMultipath { - multipathDiskNamesMap[inventoryDisk.Name] = struct{}{} - } + err := areISCSIHoldersValid(disk, inventory) + if err != nil { + notEligibleReasons = append(notEligibleReasons, err.Error()) } - // 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 { - notEligibleReasons = append(notEligibleReasons, iSCSIWithMultipathHolder) - break - } + // Check if network is configured properly to install on iSCSI boot drive + err = isISCSINetworkingValid(disk, inventory) + if err != nil { + notEligibleReasons = append(notEligibleReasons, err.Error()) } } 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. +func isISCSINetworkingValid(disk *models.Disk, inventory *models.Inventory) error { + // get the IPv4 or the IPv6 of the interface connected to the iSCSI target + if disk.Iscsi == nil { + return fmt.Errorf("Host IP address is not available") + + } + iSCSIHostIP, err := netip.ParseAddr(disk.Iscsi.HostIPAddress) + if err != nil { + return fmt.Errorf("Cannot parse iSCSI host IP %s: %w", disk.Iscsi.HostIPAddress, err) + } + + defaultRoute := network.GetDefaultRouteByFamily(inventory.Routes, iSCSIHostIP.Is6()) + if defaultRoute == nil { + return nil + } + + defaultInterface, ok := lo.Find(inventory.Interfaces, func(i *models.Interface) bool { + return i.Name == defaultRoute.Interface + }) + if !ok { + return fmt.Errorf("Cannot find the network interface behind the default route") + } + + // look if one of the IP assigned to the default interface interface + // corresponds to the IP used by host to connect on the iSCSI target + ips := defaultInterface.IPV4Addresses + if iSCSIHostIP.Is6() { + ips = defaultInterface.IPV6Addresses + } + _, ok = lo.Find(ips, func(ip string) bool { + prefix, err := netip.ParsePrefix(ip) + return err == nil && iSCSIHostIP.Compare(prefix.Addr()) == 0 + }) + + if ok { + return fmt.Errorf(wrongISCSINetworkTemplate, iSCSIHostIP.String()) + } + return nil +} + func (v *validator) IsValidStorageDeviceType(disk *models.Disk, hostArchitecture string, openshiftVersion string) bool { - return funk.ContainsString(v.getValidDeviceStorageTypes(hostArchitecture, openshiftVersion), string(disk.DriveType)) + return lo.Contains(v.getValidDeviceStorageTypes(hostArchitecture, openshiftVersion), string(disk.DriveType)) } func (v *validator) purgeServiceReasons(reasons []string) []string { @@ -195,9 +257,9 @@ func (v *validator) purgeServiceReasons(reasons []string) []string { } func (v *validator) ListEligibleDisks(inventory *models.Inventory) []*models.Disk { - eligibleDisks := funk.Filter(inventory.Disks, func(disk *models.Disk) bool { + eligibleDisks := lo.Filter(inventory.Disks, func(disk *models.Disk, index int) bool { return disk.InstallationEligibility.Eligible - }).([]*models.Disk) + }) // Sorting list by size increase sort.Slice(eligibleDisks, func(i, j int) bool { @@ -391,7 +453,7 @@ func (v *validator) isEdgeWorker(host *models.Host) bool { return false } - return funk.Contains(v.edgeWorkersProductList, strings.ToLower(strings.ReplaceAll(inventory.SystemVendor.ProductName, " ", ""))) + return lo.Contains(v.edgeWorkersProductList, strings.ToLower(strings.ReplaceAll(inventory.SystemVendor.ProductName, " ", ""))) } func (v *validator) getOCPInfraEnvHostRoleRequirementsForVersion(infraEnv *common.InfraEnv, role models.HostRole) (models.ClusterHostRequirementsDetails, error) { diff --git a/internal/hardware/validator_test.go b/internal/hardware/validator_test.go index 5297131f39d8..5cfb1df5e955 100644 --- a/internal/hardware/validator_test.go +++ b/internal/hardware/validator_test.go @@ -87,6 +87,7 @@ var _ = Describe("Disk eligibility", func() { var ( hwvalidator Validator testDisk models.Disk + inventory *models.Inventory bigEnoughSize int64 tooSmallSize int64 ctx context.Context @@ -124,6 +125,12 @@ var _ = Describe("Disk eligibility", func() { DriveType: models.DriveTypeSSD, SizeBytes: bigEnoughSize, } + inventory = &models.Inventory{ + Disks: []*models.Disk{&testDisk}, + CPU: &models.CPU{ + Architecture: models.ClusterCPUArchitectureX8664, + }, + } ctx = context.TODO() }) @@ -134,13 +141,13 @@ var _ = Describe("Disk eligibility", func() { It("Check if SSD is eligible", func() { testDisk.DriveType = models.DriveTypeSSD - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) By("Check infra env SSD is eligible") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) @@ -149,13 +156,13 @@ var _ = Describe("Disk eligibility", func() { It("Check if HDD is eligible", func() { testDisk.DriveType = models.DriveTypeHDD - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) By("Check infra env HDD is eligible") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) @@ -163,42 +170,77 @@ var _ = Describe("Disk eligibility", func() { It("Check if iSCSI is eligible", func() { testDisk.DriveType = models.DriveTypeISCSI + testDisk.Name = "iscsi0" cluster.OpenshiftVersion = "4.15.0" hostInventory, _ := common.UnmarshalInventory(host.Inventory) - hw, _ := json.Marshal(&hostInventory) - host.Inventory = string(hw) - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + // 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 iSCSI is not eligible when host IPv4 address isn't set") + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) + Expect(err).ToNot(HaveOccurred()) + Expect(eligible).To(ContainElement("Host IP address is not available")) + + By("Check iSCSI is eligible when host IPv4 address is not part of default network interface") + testDisk.Iscsi = &models.Iscsi{HostIPAddress: "4.5.6.7"} + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) + Expect(err).ToNot(HaveOccurred()) + Expect(eligible).To(BeEmpty()) + + By("Check iSCSI is not eligible when host IPv4 address is part of default network interface") + testDisk.Iscsi = &models.Iscsi{HostIPAddress: "1.2.3.4"} + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) + Expect(err).ToNot(HaveOccurred()) + Expect(eligible).To(ContainElement("iSCSI host IP 1.2.3.4 is the same as host IP, they must be different")) + + By("Check iSCSI is eligible when host IPv6 address is not part of default network interface") + testDisk.Iscsi = &models.Iscsi{HostIPAddress: "1002:db8::10"} + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) + By("Check iSCSI is not eligible when host IPv6 address is part of default network interface") + testDisk.Iscsi = &models.Iscsi{HostIPAddress: "1001:db8::10"} + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) + Expect(err).ToNot(HaveOccurred()) + Expect(eligible).To(ContainElement("iSCSI host IP 1001:db8::10 is the same as host IP, they must be different")) + By("Check iSCSI on older version is not eligible") - operatorsMock.EXPECT().GetRequirementsBreakdownForHostInCluster(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.OperatorHostRequirements{}, nil) + testDisk.Iscsi = &models.Iscsi{HostIPAddress: "4.5.6.7"} cluster.OpenshiftVersion = "4.14.1" - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) - + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) - Expect(eligible).ToNot(BeEmpty()) + Expect(eligible).To(ContainElement("Drive type is iSCSI, it must be one of HDD, SSD, Multipath.")) By("Check infra env iSCSI is not eligible") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) - + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) - Expect(eligible).ToNot(BeEmpty()) + Expect(eligible).To(ContainElement("Drive type is iSCSI, it must be one of HDD, SSD, Multipath.")) }) It("Check that FC multipath is eligible", func() { testDisk.Name = "dm-0" testDisk.DriveType = models.DriveTypeMultipath allDisks := []*models.Disk{&testDisk, {Name: "sda", DriveType: models.DriveTypeFC, Holders: "dm-0"}, {Name: "sdb", DriveType: models.DriveTypeFC, Holders: "dm-0"}} + inventory.Disks = allDisks - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, allDisks) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) By("Check infra env FC multipath is eligible") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, allDisks) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) @@ -208,14 +250,15 @@ var _ = Describe("Disk eligibility", func() { testDisk.Name = "dm-0" 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 - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, allDisks) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).ToNot(BeEmpty()) By("Check infra env iSCSI multipath is not eligible") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, allDisks) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).ToNot(BeEmpty()) @@ -227,8 +270,9 @@ var _ = Describe("Disk eligibility", func() { cluster.OpenshiftVersion = "4.15.0" testDisk.DriveType = models.DriveTypeISCSI allDisks := []*models.Disk{&testDisk, {Name: "sdc", DriveType: models.DriveTypeISCSI, Holders: "dm-0"}, {Name: "dm-0", DriveType: models.DriveTypeMultipath}} + inventory.Disks = allDisks - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, allDisks) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible[0]).To(Equal(iSCSIWithMultipathHolder)) @@ -240,8 +284,9 @@ var _ = Describe("Disk eligibility", func() { cluster.OpenshiftVersion = "4.14.1" testDisk.DriveType = models.DriveTypeISCSI allDisks := []*models.Disk{&testDisk, {Name: "sdc", DriveType: models.DriveTypeISCSI, Holders: "dm-0"}, {Name: "dm-0", DriveType: models.DriveTypeMultipath}} + inventory.Disks = allDisks - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, allDisks) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible[0]).To(ContainSubstring(fmt.Sprintf("Drive type is %s, it must be one of", models.DriveTypeISCSI))) @@ -251,13 +296,13 @@ var _ = Describe("Disk eligibility", func() { It("Check if FC is not eligible on non-s390x", func() { testDisk.DriveType = models.DriveTypeFC - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).ToNot(BeEmpty()) By("Check infra env FC is only eligible for s390x") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).ToNot(BeEmpty()) @@ -265,14 +310,15 @@ var _ = Describe("Disk eligibility", func() { It("Check if FC is eligible for s390x", func() { testDisk.DriveType = models.DriveTypeFC + inventory.CPU.Architecture = models.ClusterCPUArchitectureS390x - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureS390x, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) By("Check infra env FC is only eligible for s390x") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureS390x, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) @@ -281,13 +327,13 @@ var _ = Describe("Disk eligibility", func() { It("Check if ECKD is not eligible on non-s390x", func() { testDisk.DriveType = models.DriveTypeECKD - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).ToNot(BeEmpty()) By("Check infra env ECKD is only eligible for s390x") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).ToNot(BeEmpty()) @@ -295,14 +341,15 @@ var _ = Describe("Disk eligibility", func() { It("Check if ECKD is eligible for s390x", func() { testDisk.DriveType = models.DriveTypeECKD + inventory.CPU.Architecture = models.ClusterCPUArchitectureS390x - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureS390x, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) By("Check infra env ECKD is only eligible for s390x") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureS390x, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) @@ -311,13 +358,13 @@ var _ = Describe("Disk eligibility", func() { It("Check if FBA is not eligible on non-s390x", func() { testDisk.DriveType = models.DriveTypeFBA - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).ToNot(BeEmpty()) By("Check infra env FBA is only eligible for s390x") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).ToNot(BeEmpty()) @@ -325,14 +372,15 @@ var _ = Describe("Disk eligibility", func() { It("Check if FBA is eligible for s390x", func() { testDisk.DriveType = models.DriveTypeFBA + inventory.CPU.Architecture = models.ClusterCPUArchitectureS390x - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureS390x, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) By("Check infra env FBA is only eligible for s390x") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureS390x, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) @@ -341,13 +389,13 @@ var _ = Describe("Disk eligibility", func() { It("Check that ODD is not eligible", func() { testDisk.DriveType = models.DriveTypeODD - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).ToNot(BeEmpty()) By("Check infra-env ODD is not eligible") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).ToNot(BeEmpty()) @@ -356,13 +404,13 @@ var _ = Describe("Disk eligibility", func() { It("Check that a big enough size is eligible", func() { testDisk.SizeBytes = bigEnoughSize - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) By("Check infra-env a big enough size is eligible") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) @@ -371,7 +419,7 @@ var _ = Describe("Disk eligibility", func() { versionRequirements["default"].MasterRequirements.DiskSizeGb = minDiskSizeGb - 2 tooSmallSizeForWorker := conversions.GbToBytes(minDiskSizeGb) - 1 testDisk.SizeBytes = tooSmallSizeForWorker - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(BeEmpty()) @@ -382,13 +430,13 @@ var _ = Describe("Disk eligibility", func() { It("Check that a small size is not eligible", func() { testDisk.SizeBytes = tooSmallSize - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).ToNot(BeEmpty()) By("Check infra-env a small size is not eligible") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).ToNot(BeEmpty()) @@ -398,13 +446,13 @@ var _ = Describe("Disk eligibility", func() { existingReasons := []string{"Reason 1", "Reason 2"} testDisk.InstallationEligibility.NotEligibleReasons = existingReasons - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(ConsistOf(existingReasons)) By("Check infra-env existing non-eligibility reasons are preserved") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(ConsistOf(existingReasons)) }) @@ -415,14 +463,14 @@ var _ = Describe("Disk eligibility", func() { testDisk.SizeBytes = tooSmallSize - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(ContainElements(existingReasons)) Expect(eligible).To(HaveLen(len(existingReasons) + 1)) By("Check infra env a small size reason is added to existing reasons") - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, nil, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(ContainElements(existingReasons)) @@ -436,13 +484,13 @@ var _ = Describe("Disk eligibility", func() { testDisk.SizeBytes = tooSmallSize - eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err := hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(ContainElements(existingReasons)) Expect(eligible).To(HaveLen(len(existingReasons) + 1)) testDisk.InstallationEligibility.NotEligibleReasons = existingReasons - eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, models.ClusterCPUArchitectureX8664, []*models.Disk{&testDisk}) + eligible, err = hwvalidator.DiskIsEligible(ctx, &testDisk, infraEnv, &cluster, &host, inventory) Expect(err).ToNot(HaveOccurred()) Expect(eligible).To(ContainElements(existingReasons)) Expect(eligible).To(HaveLen(len(existingReasons) + 1)) diff --git a/internal/host/host.go b/internal/host/host.go index 02f100fbe6bb..904fc8a020d8 100644 --- a/internal/host/host.go +++ b/internal/host/host.go @@ -256,7 +256,7 @@ func (m *Manager) populateDisksEligibility(ctx context.Context, inventory *model } // Append to the existing reasons already filled in by the agent - reasons, err := m.hwValidator.DiskIsEligible(ctx, disk, infraEnv, cluster, host, inventory.CPU.Architecture, inventory.Disks) + reasons, err := m.hwValidator.DiskIsEligible(ctx, disk, infraEnv, cluster, host, inventory) if err != nil { return err } diff --git a/internal/host/host_test.go b/internal/host/host_test.go index b08a1c0a1261..503cdc24d46f 100644 --- a/internal/host/host_test.go +++ b/internal/host/host_test.go @@ -1177,7 +1177,7 @@ var _ = Describe("UpdateInventory", func() { It(test.testName, func() { host = hostutil.GenerateTestHost(hostId, infraEnvId, clusterId, models.HostStatusDiscovering) Expect(db.Create(&host).Error).ShouldNot(HaveOccurred()) - mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), models.ClusterCPUArchitectureX8664, gomock.Any()) + mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) mockValidator.EXPECT().ListEligibleDisks(gomock.Any()).Return(test.inventory.Disks) mockEvents.EXPECT().V2AddMetricsEvent(ctx, &clusterId, &hostId, gomock.Any(), gomock.Any(), models.EventSeverityInfo, "nic.virtual_interfaces", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) mockEvents.EXPECT().V2AddMetricsEvent(ctx, &clusterId, &hostId, gomock.Any(), gomock.Any(), models.EventSeverityInfo, "nic.physical_interfaces", gomock.Any(), gomock.Any(), gomock.Any()) @@ -1239,7 +1239,7 @@ var _ = Describe("UpdateInventory", func() { } { test := test It(test.testName, func() { - mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), models.ClusterCPUArchitectureX8664, gomock.Any()).Return(append(test.agentReasons, test.serviceReasons...), nil) + mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(append(test.agentReasons, test.serviceReasons...), nil) testInventory := models.Inventory{ CPU: &models.CPU{ @@ -1317,7 +1317,7 @@ var _ = Describe("UpdateInventory", func() { mockEvents.EXPECT().V2AddMetricsEvent(ctx, host.ClusterID, &hostId, gomock.Any(), gomock.Any(), models.EventSeverityInfo, "nic.virtual_interfaces", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) mockEvents.EXPECT().V2AddMetricsEvent(ctx, host.ClusterID, &hostId, gomock.Any(), gomock.Any(), models.EventSeverityInfo, "nic.physical_interfaces", gomock.Any(), gomock.Any(), gomock.Any()) - mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), models.ClusterCPUArchitectureX8664, gomock.Any()) + mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) mockValidator.EXPECT().ListEligibleDisks(gomock.Any()).Return(inventory.Disks) if test.expectMatch { mockEvents.EXPECT().SendHostEvent(gomock.Any(), eventstest.NewEventMatcher( @@ -1351,7 +1351,7 @@ var _ = Describe("UpdateInventory", func() { Expect(db.Create(&host).Error).ShouldNot(HaveOccurred()) }) It("Happy flow", func() { - mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), models.ClusterCPUArchitectureX8664, gomock.Any()) + mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) mockValidator.EXPECT().ListEligibleDisks(gomock.Any()).Return( []*models.Disk{{ID: diskId, Name: diskName}}, ) @@ -1398,7 +1398,7 @@ var _ = Describe("UpdateInventory", func() { host.Inventory = common.GenerateTestDefaultInventory() host.InstallationDiskPath = "" Expect(db.Create(&host).Error).ShouldNot(HaveOccurred()) - mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), models.ClusterCPUArchitectureX8664, gomock.Any()) + mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) }) @@ -1414,7 +1414,7 @@ var _ = Describe("UpdateInventory", func() { Expect(h.InstallationDiskID).To(Equal(diskId)) // Now make sure it gets removed if the disk is no longer in the inventory - mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), models.ClusterCPUArchitectureX8664, gomock.Any()) + mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) mockValidator.EXPECT().ListEligibleDisks(gomock.Any()).Return( []*models.Disk{}, ) @@ -1437,7 +1437,7 @@ var _ = Describe("UpdateInventory", func() { mockValidator.EXPECT().ListEligibleDisks(gomock.Any()).Return( []*models.Disk{{ID: diskId, Name: diskName}}, ) - mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), models.ClusterCPUArchitectureX8664, gomock.Any()) + mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) Expect(hapi.UpdateInventory(ctx, &host, host.Inventory)).ToNot(HaveOccurred()) h = hostutil.GetHostFromDB(hostId, infraEnvId, db) Expect(h.InstallationDiskPath).To(Equal(diskPath)) @@ -1458,7 +1458,7 @@ var _ = Describe("UpdateInventory", func() { host.Inventory = common.GenerateTestInventoryWithVirtualInterface(1, 1) host.InstallationDiskPath = "" Expect(db.Create(&host).Error).ShouldNot(HaveOccurred()) - mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), models.ClusterCPUArchitectureX8664, gomock.Any()).AnyTimes() + mockValidator.EXPECT().DiskIsEligible(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() mockValidator.EXPECT().ListEligibleDisks(gomock.Any()).Return( []*models.Disk{{ID: diskId, Name: diskName}}, ).AnyTimes() diff --git a/internal/host/transition_test.go b/internal/host/transition_test.go index da6c90cc9ed6..12b8d0ee11ef 100644 --- a/internal/host/transition_test.go +++ b/internal/host/transition_test.go @@ -5668,25 +5668,25 @@ var _ = Describe("Refresh Host", func() { Context("Default route", func() { ipv4Routes := []*models.Route{ - {Family: common.FamilyIPv4, Destination: "0.0.0.0", Gateway: "10.254.0.1"}, - {Family: common.FamilyIPv4, Destination: "192.168.122.0", Gateway: "0.0.0.0"}} + {Family: int32(common.IPv4), Destination: "0.0.0.0", Gateway: "10.254.0.1"}, + {Family: int32(common.IPv4), Destination: "192.168.122.0", Gateway: "0.0.0.0"}} ipv6Routes := []*models.Route{ - {Family: common.FamilyIPv6, Destination: net.IPv6zero.String(), Gateway: "2001:1::1"}, - {Family: common.FamilyIPv6, Destination: "2001:1::1", Gateway: net.IPv6zero.String()}, - {Family: common.FamilyIPv6, Destination: net.IPv6zero.String(), Gateway: net.IPv6zero.String()}} + {Family: int32(common.IPv6), Destination: net.IPv6zero.String(), Gateway: "2001:1::1"}, + {Family: int32(common.IPv6), Destination: "2001:1::1", Gateway: net.IPv6zero.String()}, + {Family: int32(common.IPv6), Destination: net.IPv6zero.String(), Gateway: net.IPv6zero.String()}} noDefaultRoute := []*models.Route{ - {Family: common.FamilyIPv4, Destination: "10.254.2.2", Gateway: "10.254.2.1"}, - {Family: common.FamilyIPv4, Destination: "172.17.0.15", Gateway: "172.17.0.1"}, - {Family: common.FamilyIPv6, Destination: "2001:1::10", Gateway: "2001:1::1"}, + {Family: int32(common.IPv4), Destination: "10.254.2.2", Gateway: "10.254.2.1"}, + {Family: int32(common.IPv4), Destination: "172.17.0.15", Gateway: "172.17.0.1"}, + {Family: int32(common.IPv6), Destination: "2001:1::10", Gateway: "2001:1::1"}, } invalidDestination := []*models.Route{ - {Family: common.FamilyIPv4, Destination: "invalid", Gateway: "10.254.2.1"}, + {Family: int32(common.IPv4), Destination: "invalid", Gateway: "10.254.2.1"}, } invalidGateway := []*models.Route{ - {Family: common.FamilyIPv4, Destination: "0.0.0.0", Gateway: "invalid"}, + {Family: int32(common.IPv4), Destination: "0.0.0.0", Gateway: "invalid"}, } defaultNTPSourcesInBytes, err := json.Marshal(defaultNTPSources) domainNameResolutions := common.TestDomainNameResolutionsSuccess diff --git a/internal/network/connectivity_groups.go b/internal/network/connectivity_groups.go index c2a776771256..6a63dcbeec6d 100644 --- a/internal/network/connectivity_groups.go +++ b/internal/network/connectivity_groups.go @@ -2,7 +2,6 @@ package network import ( "encoding/json" - "fmt" "net" "github.com/go-openapi/strfmt" @@ -11,14 +10,13 @@ import ( "github.com/openshift/assisted-service/models" "github.com/pkg/errors" "github.com/thoas/go-funk" - "golang.org/x/sys/unix" ) -type AddressFamily int +type AddressFamily = common.AddressFamily const ( - IPv4 AddressFamily = unix.AF_INET - IPv6 AddressFamily = unix.AF_INET6 + IPv4 AddressFamily = common.IPv4 + IPv6 AddressFamily = common.IPv6 ) type Connectivity struct { @@ -26,17 +24,6 @@ type Connectivity struct { L3ConnectedAddresses map[strfmt.UUID][]string `json:"l3_connected_addresses"` } -func (a AddressFamily) String() string { - switch a { - case IPv4: - return "IPv4" - case IPv6: - return "IPv6" - default: - return fmt.Sprintf("Unexpected family value %d", a) - } -} - type connectivityKey struct { first, second int } diff --git a/internal/network/utils.go b/internal/network/utils.go index 71e40d40a467..ab6209c6b864 100644 --- a/internal/network/utils.go +++ b/internal/network/utils.go @@ -11,14 +11,15 @@ import ( "github.com/openshift/assisted-service/internal/common" "github.com/openshift/assisted-service/models" "github.com/pkg/errors" - "github.com/thoas/go-funk" + "github.com/samber/lo" + "golang.org/x/sys/unix" "gorm.io/gorm" ) func IsDefaultRoute(r *models.Route) (bool, error) { gw := net.ParseIP(r.Gateway) if gw == nil { - return false, nil //gateway is empty for non default routes + return false, nil // gateway is empty for non default routes } dst := net.ParseIP(r.Destination) @@ -29,6 +30,36 @@ func IsDefaultRoute(r *models.Route) (bool, error) { return dst.IsUnspecified() && !gw.IsUnspecified(), nil } +// Returns the IPv4 or the IPv6 default route +func GetDefaultRouteByFamily(routes []*models.Route, ipv6 bool) *models.Route { + family := unix.AF_INET + if ipv6 { + family = unix.AF_INET6 + } + + defaultRoutes := lo.Filter(routes, func(r *models.Route, index int) bool { + if int(r.Family) == family { + isDefault, _ := IsDefaultRoute(r) + return isDefault + } + return false + }) + + // keep the route with the lowest metric + var metric *int32 + var defaultRoute *models.Route + for _, r := range defaultRoutes { + if metric != nil && *metric < r.Metric { + continue + } + + metric = &r.Metric + defaultRoute = r + } + + return defaultRoute +} + // Obtains the IP addresses used by a host func GetInventoryIPAddresses(inventory *models.Inventory) ([]string, []string) { ipV4Addresses := []string{} @@ -83,7 +114,6 @@ func GetClusterAddressStack(hosts []*models.Host) (bool, bool, error) { // Get configured address families from cluster configuration based on the CIDRs (machine-network-cidr, cluster-network-cidr, // service-network-cidr) func GetConfiguredAddressFamilies(cluster *common.Cluster) (ipv4 bool, ipv6 bool, err error) { - for _, cidr := range common.GetNetworksCidrs(cluster) { if cidr == nil || *cidr == "" { continue @@ -202,7 +232,7 @@ func GetApiVips(cluster *common.Cluster) (ret []string) { if cluster.APIVips == nil { return nil } - ret = funk.Map(cluster.APIVips, func(x *models.APIVip) string { return string(x.IP) }).([]string) + ret = lo.Map(cluster.APIVips, func(x *models.APIVip, index int) string { return string(x.IP) }) return } @@ -210,7 +240,7 @@ func GetIngressVips(cluster *common.Cluster) (ret []string) { if cluster.IngressVips == nil { return nil } - ret = funk.Map(cluster.IngressVips, func(x *models.IngressVip) string { return string(x.IP) }).([]string) + ret = lo.Map(cluster.IngressVips, func(x *models.IngressVip, index int) string { return string(x.IP) }) return } @@ -335,7 +365,7 @@ func areListsEquivalent(len1, len2 int, areItemsEquivalent func(int, int) bool) var usedIndexes []int containsEquivalentItem := func(index, length int) bool { for i := 0; i != length; i++ { - if !funk.ContainsInt(usedIndexes, i) && areItemsEquivalent(index, i) { + if !lo.Contains(usedIndexes, i) && areItemsEquivalent(index, i) { usedIndexes = append(usedIndexes, i) return true } diff --git a/models/disk.go b/models/disk.go index 84b3a5bf4121..7574de9e72b5 100644 --- a/models/disk.go +++ b/models/disk.go @@ -51,6 +51,9 @@ type Disk struct { // Whether the disk appears to be an installation media or not IsInstallationMedia bool `json:"is_installation_media,omitempty"` + // iscsi + Iscsi *Iscsi `json:"iscsi,omitempty"` + // model Model string `json:"model,omitempty"` @@ -98,6 +101,10 @@ func (m *Disk) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateIscsi(formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -157,6 +164,25 @@ func (m *Disk) validateIoPerf(formats strfmt.Registry) error { return nil } +func (m *Disk) validateIscsi(formats strfmt.Registry) error { + if swag.IsZero(m.Iscsi) { // not required + return nil + } + + if m.Iscsi != nil { + if err := m.Iscsi.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("iscsi") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("iscsi") + } + return err + } + } + + return nil +} + // ContextValidate validate this disk based on the context it is used func (m *Disk) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error @@ -173,6 +199,10 @@ func (m *Disk) ContextValidate(ctx context.Context, formats strfmt.Registry) err res = append(res, err) } + if err := m.contextValidateIscsi(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -223,6 +253,22 @@ func (m *Disk) contextValidateIoPerf(ctx context.Context, formats strfmt.Registr return nil } +func (m *Disk) contextValidateIscsi(ctx context.Context, formats strfmt.Registry) error { + + if m.Iscsi != nil { + if err := m.Iscsi.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("iscsi") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("iscsi") + } + return err + } + } + + return nil +} + // MarshalBinary interface implementation func (m *Disk) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/models/iscsi.go b/models/iscsi.go new file mode 100644 index 000000000000..865d1ca20b31 --- /dev/null +++ b/models/iscsi.go @@ -0,0 +1,50 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// Iscsi iscsi +// +// swagger:model iscsi +type Iscsi struct { + + // Host IP address used to reach iSCSI target + HostIPAddress string `json:"host_ip_address,omitempty"` +} + +// Validate validates this iscsi +func (m *Iscsi) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this iscsi based on context it is used +func (m *Iscsi) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *Iscsi) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Iscsi) UnmarshalBinary(b []byte) error { + var res Iscsi + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index 30a68010bc9b..a78eb05702f6 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -7320,6 +7320,9 @@ func init() { "description": "Whether the disk appears to be an installation media or not", "type": "boolean" }, + "iscsi": { + "$ref": "#/definitions/iscsi" + }, "model": { "type": "string" }, @@ -9125,6 +9128,15 @@ func init() { "pattern": "^(?:(?:(?:[0-9]{1,3}\\.){3}[0-9]{1,3})|(?:(?:[0-9a-fA-F]*:[0-9a-fA-F]*){2,}))?$", "x-go-custom-tag": "gorm:\"primaryKey\"" }, + "iscsi": { + "type": "object", + "properties": { + "host_ip_address": { + "description": "Host IP address used to reach iSCSI target", + "type": "string" + } + } + }, "kernel_argument": { "description": "pair of [operation, argument] specifying the argument and what operation should be applied on it.", "type": "object", @@ -18127,6 +18139,9 @@ func init() { "description": "Whether the disk appears to be an installation media or not", "type": "boolean" }, + "iscsi": { + "$ref": "#/definitions/iscsi" + }, "model": { "type": "string" }, @@ -19901,6 +19916,15 @@ func init() { "pattern": "^(?:(?:(?:[0-9]{1,3}\\.){3}[0-9]{1,3})|(?:(?:[0-9a-fA-F]*:[0-9a-fA-F]*){2,}))?$", "x-go-custom-tag": "gorm:\"primaryKey\"" }, + "iscsi": { + "type": "object", + "properties": { + "host_ip_address": { + "description": "Host IP address used to reach iSCSI target", + "type": "string" + } + } + }, "kernel_argument": { "description": "pair of [operation, argument] specifying the argument and what operation should be applied on it.", "type": "object", diff --git a/swagger.yaml b/swagger.yaml index a84b92fdfe78..a24ea58e8ab1 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -5933,6 +5933,8 @@ definitions: holders: type: string description: A comma-separated list of disk names that this disk belongs to + iscsi: + $ref: '#/definitions/iscsi' drive_type: type: string @@ -5959,6 +5961,13 @@ definitions: type: integer description: 99th percentile of fsync duration in milliseconds + iscsi: + type: object + properties: + host_ip_address: + type: string + description: Host IP address used to reach iSCSI target + boot: type: object properties: diff --git a/vendor/github.com/openshift/assisted-service/models/disk.go b/vendor/github.com/openshift/assisted-service/models/disk.go index 84b3a5bf4121..7574de9e72b5 100644 --- a/vendor/github.com/openshift/assisted-service/models/disk.go +++ b/vendor/github.com/openshift/assisted-service/models/disk.go @@ -51,6 +51,9 @@ type Disk struct { // Whether the disk appears to be an installation media or not IsInstallationMedia bool `json:"is_installation_media,omitempty"` + // iscsi + Iscsi *Iscsi `json:"iscsi,omitempty"` + // model Model string `json:"model,omitempty"` @@ -98,6 +101,10 @@ func (m *Disk) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateIscsi(formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -157,6 +164,25 @@ func (m *Disk) validateIoPerf(formats strfmt.Registry) error { return nil } +func (m *Disk) validateIscsi(formats strfmt.Registry) error { + if swag.IsZero(m.Iscsi) { // not required + return nil + } + + if m.Iscsi != nil { + if err := m.Iscsi.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("iscsi") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("iscsi") + } + return err + } + } + + return nil +} + // ContextValidate validate this disk based on the context it is used func (m *Disk) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error @@ -173,6 +199,10 @@ func (m *Disk) ContextValidate(ctx context.Context, formats strfmt.Registry) err res = append(res, err) } + if err := m.contextValidateIscsi(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -223,6 +253,22 @@ func (m *Disk) contextValidateIoPerf(ctx context.Context, formats strfmt.Registr return nil } +func (m *Disk) contextValidateIscsi(ctx context.Context, formats strfmt.Registry) error { + + if m.Iscsi != nil { + if err := m.Iscsi.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("iscsi") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("iscsi") + } + return err + } + } + + return nil +} + // MarshalBinary interface implementation func (m *Disk) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/vendor/github.com/openshift/assisted-service/models/iscsi.go b/vendor/github.com/openshift/assisted-service/models/iscsi.go new file mode 100644 index 000000000000..865d1ca20b31 --- /dev/null +++ b/vendor/github.com/openshift/assisted-service/models/iscsi.go @@ -0,0 +1,50 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// Iscsi iscsi +// +// swagger:model iscsi +type Iscsi struct { + + // Host IP address used to reach iSCSI target + HostIPAddress string `json:"host_ip_address,omitempty"` +} + +// Validate validates this iscsi +func (m *Iscsi) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this iscsi based on context it is used +func (m *Iscsi) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *Iscsi) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Iscsi) UnmarshalBinary(b []byte) error { + var res Iscsi + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +}