From cde25bb70530f6103dd440858647a8181811404d Mon Sep 17 00:00:00 2001 From: Daniel Erez Date: Mon, 14 Dec 2020 16:01:17 +0200 Subject: [PATCH] MGMT-3026 - invoke fio from installcmd - Invoke fio_perf_check agent command as part of installcmd flow. - Handled error in inventory (handleReplyError) by looking for a specific fio exit code (FioPerfCheckCmdExitCode). --- internal/bminventory/inventory.go | 4 + internal/host/fioperfcheckcmd.go | 80 ++++++++++++++++ internal/host/host.go | 15 +++ internal/host/installcmd.go | 4 +- internal/host/installcmd_test.go | 8 ++ internal/host/instructionmanager.go | 1 + internal/host/mock_host_api.go | 17 +++- models/disk.go | 25 +++++ models/fio_perf_check_request.go | 98 ++++++++++++++++++++ models/fio_perf_check_response.go | 43 +++++++++ models/io_perf.go | 43 +++++++++ models/step_type.go | 5 +- openshift/template.yaml | 3 + restapi/embedded_spec.go | 92 +++++++++++++++++- subsystem/host_test.go | 26 ++++++ swagger.yaml | 34 +++++++ tools/deploy_assisted_installer_configmap.py | 1 + 17 files changed, 492 insertions(+), 7 deletions(-) create mode 100644 internal/host/fioperfcheckcmd.go create mode 100644 models/fio_perf_check_request.go create mode 100644 models/fio_perf_check_response.go create mode 100644 models/io_perf.go diff --git a/internal/bminventory/inventory.go b/internal/bminventory/inventory.go index edbf13e2ac6..1e48c99eced 100644 --- a/internal/bminventory/inventory.go +++ b/internal/bminventory/inventory.go @@ -2433,6 +2433,10 @@ func (b *bareMetalInventory) PostStepReply(ctx context.Context, params installer func (b *bareMetalInventory) handleReplyError(params installer.PostStepReplyParams, ctx context.Context, log logrus.FieldLogger, h *models.Host) error { if params.Reply.StepType == models.StepTypeInstall { + if params.Reply.ExitCode == host.FioPerfCheckCmdExitCode { + log.Warnf("FIO performance check: %s", params.Reply.Error) + return b.hostApi.HandlePrepareInstallationFailure(ctx, h, params.Reply.Error) + } // Handle case of installation error due to an already running assisted-installer. if params.Reply.ExitCode == ContainerAlreadyRunningExitCode && strings.Contains(params.Reply.Error, "the container name \"assisted-installer\" is already in use") { log.Warnf("Install command failed due to an already running installation: %s", params.Reply.Error) diff --git a/internal/host/fioperfcheckcmd.go b/internal/host/fioperfcheckcmd.go new file mode 100644 index 00000000000..75c2e0365a9 --- /dev/null +++ b/internal/host/fioperfcheckcmd.go @@ -0,0 +1,80 @@ +package host + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/openshift/assisted-service/models" + "github.com/sirupsen/logrus" +) + +const ( + FioPerfCheckCmdExitCode int64 = 222 + FioDurationThreshold int64 = 20 +) + +type fioPerfCheckCmd struct { + baseCmd + fioPerfCheckImage string + path string + durationThreshold int64 +} + +func NewFioPerfCheckCmd(log logrus.FieldLogger, fioPerfCheckImage string, path string, durationThreshold int64) *fioPerfCheckCmd { + return &fioPerfCheckCmd{ + baseCmd: baseCmd{log: log}, + fioPerfCheckImage: fioPerfCheckImage, + path: path, + durationThreshold: durationThreshold, + } +} + +func (c *fioPerfCheckCmd) GetSteps(ctx context.Context, host *models.Host) ([]*models.Step, error) { + args, err := c.GetArgs() + if err != nil { + return nil, err + } + + step := &models.Step{ + StepType: models.StepTypeFioPerfCheck, + Command: "podman", + Args: args, + } + return []*models.Step{step}, nil +} + +func (c *fioPerfCheckCmd) GetArgs() ([]string, error) { + exitCode := FioPerfCheckCmdExitCode + request := models.FioPerfCheckRequest{ + Path: &c.path, + DurationThreshold: &c.durationThreshold, + ExitCode: &exitCode, + } + requestBytes, err := json.Marshal(request) + if err != nil { + c.log.WithError(err).Errorf("failed to marshal FioPerfCheckRequest") + return nil, err + } + + return []string{ + "run", "--privileged", "--net=host", "--rm", "--quiet", + "-v", "/dev:/dev:rw", + "-v", "/var/log:/var/log", + "-v", "/run/systemd/journal/socket:/run/systemd/journal/socket", + c.fioPerfCheckImage, + "fio_perf_check", + strconv.Quote(string(requestBytes)), + }, nil +} + +func (c *fioPerfCheckCmd) GetCommandString() string { + args, err := c.GetArgs() + if err != nil { + return "" + } + + return fmt.Sprintf("podman %s && ", strings.Join(args, " ")) +} diff --git a/internal/host/host.go b/internal/host/host.go index da60e3649f0..80d128e70eb 100644 --- a/internal/host/host.go +++ b/internal/host/host.go @@ -91,6 +91,7 @@ type API interface { RegisterHost(ctx context.Context, h *models.Host, db *gorm.DB) error RegisterInstalledOCPHost(ctx context.Context, h *models.Host, db *gorm.DB) error HandleInstallationFailure(ctx context.Context, h *models.Host) error + HandlePrepareInstallationFailure(ctx context.Context, h *models.Host, reason string) error UpdateInstallProgress(ctx context.Context, h *models.Host, progress *models.HostProgress) error RefreshStatus(ctx context.Context, h *models.Host, db *gorm.DB) error SetBootstrap(ctx context.Context, h *models.Host, isbootstrap bool, db *gorm.DB) error @@ -240,6 +241,20 @@ func (m *Manager) populateDisksEligibility(inventoryString string) (string, erro return string(result), nil } +func (m *Manager) HandlePrepareInstallationFailure(ctx context.Context, h *models.Host, reason string) error { + + lastStatusUpdateTime := h.StatusUpdatedAt + err := m.sm.Run(TransitionTypeHostInstallationFailed, newStateHost(h), &TransitionArgsHostInstallationFailed{ + ctx: ctx, + reason: reason, + }) + if err == nil { + m.reportInstallationMetrics(ctx, h, &models.HostProgressInfo{CurrentStage: "installation command failed", + StageStartedAt: lastStatusUpdateTime}, models.HostStageFailed) + } + return err +} + func (m *Manager) UpdateInventory(ctx context.Context, h *models.Host, inventory string) error { hostStatus := swag.StringValue(h.Status) allowedStatuses := append(hostStatusesBeforeInstallation[:], models.HostStatusInstallingInProgress) diff --git a/internal/host/installcmd.go b/internal/host/installcmd.go index b7cc262b5fc..cb64442e804 100644 --- a/internal/host/installcmd.go +++ b/internal/host/installcmd.go @@ -147,14 +147,14 @@ func (i *installCmd) GetSteps(ctx context.Context, host *models.Host) ([]*models if err = t.Execute(buf, data); err != nil { return nil, err } - step.Args = []string{"-c", buf.String()} unbootableCmd, err := i.getDiskUnbootableCmd(ctx, *host) if err != nil { return nil, err } - step.Args = []string{"-c", unbootableCmd + buf.String()} + fioPerfCheckCmd := NewFioPerfCheckCmd(i.log, i.instructionConfig.FioPerfCheckImage, bootdevice, FioDurationThreshold) + step.Args = []string{"-c", unbootableCmd + fioPerfCheckCmd.GetCommandString() + buf.String()} if _, err := UpdateHost(i.log, i.db, host.ClusterID, *host.ID, *host.Status, "installer_version", i.instructionConfig.InstallerImage, "installation_disk_path", bootdevice); err != nil { diff --git a/internal/host/installcmd_test.go b/internal/host/installcmd_test.go index b0d30ddcdc1..fd0c3ae9ff1 100644 --- a/internal/host/installcmd_test.go +++ b/internal/host/installcmd_test.go @@ -30,6 +30,7 @@ var DefaultInstructionConfig = InstructionConfig{ InstallerImage: "quay.io/ocpmetal/assisted-installer:latest", ControllerImage: "quay.io/ocpmetal/assisted-installer-controller:latest", InventoryImage: "quay.io/ocpmetal/assisted-installer-agent:latest", + FioPerfCheckImage: "quay.io/ocpmetal/assisted-installer-agent:latest", InstallationTimeout: 120, ReleaseImageMirror: "local.registry:5000/ocp@sha256:eab93b4591699a5a4ff50ad3517892653f04fb840127895bb3609b3cc68f98f3", } @@ -365,6 +366,13 @@ func validateInstallCommand(reply *models.Step, role models.HostRole, clusterId "--boot-device /dev/sdb --host-id %s --openshift-version %s --mco-image mcoImage " + "--controller-image %s --url %s --insecure=false --agent-image %s --installation-timeout %s" + fioPerfCheckCmd := "podman run --privileged --net=host --rm --quiet -v /dev:/dev:rw -v /var/log:/var/log " + + "-v /run/systemd/journal/socket:/run/systemd/journal/socket " + + "quay.io/ocpmetal/assisted-installer-agent:latest fio_perf_check " + + "\"{\\\"duration_threshold\\\":20,\\\"exit_code\\\":222,\\\"path\\\":\\\"/dev/sdb\\\"}\" && " + + installCommand = fioPerfCheckCmd + installCommand + if proxy != "" { installCommand += fmt.Sprintf(" %s", proxy) } else if bootableDisks != nil { diff --git a/internal/host/instructionmanager.go b/internal/host/instructionmanager.go index 12d36dd8de9..04f7f34c494 100644 --- a/internal/host/instructionmanager.go +++ b/internal/host/instructionmanager.go @@ -57,6 +57,7 @@ type InstructionConfig struct { DhcpLeaseAllocatorImage string `envconfig:"DHCP_LEASE_ALLOCATOR_IMAGE" default:"quay.io/ocpmetal/assisted-installer-agent:latest"` APIVIPConnectivityCheckImage string `envconfig:"API_VIP_CONNECTIVITY_CHECK_IMAGE" default:"quay.io/ocpmetal/assisted-installer-agent:latest"` NtpSynchronizerImage string `envconfig:"NTP_SYNCHRONIZER_IMAGE" default:"quay.io/ocpmetal/assisted-installer-agent:latest"` + FioPerfCheckImage string `envconfig:"FIO_PERF_CHECK_IMAGE" default:"quay.io/ocpmetal/assisted-installer-agent:latest"` SkipCertVerification bool `envconfig:"SKIP_CERT_VERIFICATION" default:"false"` SupportL2 bool `envconfig:"SUPPORT_L2" default:"true"` InstallationTimeout uint `envconfig:"INSTALLATION_TIMEOUT" default:"0"` diff --git a/internal/host/mock_host_api.go b/internal/host/mock_host_api.go index 34a6fc5d088..3320d7806b0 100644 --- a/internal/host/mock_host_api.go +++ b/internal/host/mock_host_api.go @@ -6,14 +6,13 @@ package host import ( context "context" - reflect "reflect" - strfmt "github.com/go-openapi/strfmt" gomock "github.com/golang/mock/gomock" gorm "github.com/jinzhu/gorm" common "github.com/openshift/assisted-service/internal/common" models "github.com/openshift/assisted-service/models" logrus "github.com/sirupsen/logrus" + reflect "reflect" ) // MockAPI is a mock of API interface @@ -96,6 +95,20 @@ func (mr *MockAPIMockRecorder) HandleInstallationFailure(ctx, h interface{}) *go return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleInstallationFailure", reflect.TypeOf((*MockAPI)(nil).HandleInstallationFailure), ctx, h) } +// HandlePrepareInstallationFailure mocks base method +func (m *MockAPI) HandlePrepareInstallationFailure(ctx context.Context, h *models.Host, reason string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HandlePrepareInstallationFailure", ctx, h, reason) + ret0, _ := ret[0].(error) + return ret0 +} + +// HandlePrepareInstallationFailure indicates an expected call of HandlePrepareInstallationFailure +func (mr *MockAPIMockRecorder) HandlePrepareInstallationFailure(ctx, h, reason interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePrepareInstallationFailure", reflect.TypeOf((*MockAPI)(nil).HandlePrepareInstallationFailure), ctx, h, reason) +} + // UpdateInstallProgress mocks base method func (m *MockAPI) UpdateInstallProgress(ctx context.Context, h *models.Host, progress *models.HostProgress) error { m.ctrl.T.Helper() diff --git a/models/disk.go b/models/disk.go index cdd149c3776..06a6badf19a 100644 --- a/models/disk.go +++ b/models/disk.go @@ -31,6 +31,9 @@ type Disk struct { // installation eligibility InstallationEligibility DiskInstallationEligibility `json:"installation_eligibility,omitempty"` + // io perf + IoPerf *IoPerf `json:"io_perf,omitempty"` + // model Model string `json:"model,omitempty"` @@ -64,6 +67,10 @@ func (m *Disk) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateIoPerf(formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -86,6 +93,24 @@ func (m *Disk) validateInstallationEligibility(formats strfmt.Registry) error { return nil } +func (m *Disk) validateIoPerf(formats strfmt.Registry) error { + + if swag.IsZero(m.IoPerf) { // not required + return nil + } + + if m.IoPerf != nil { + if err := m.IoPerf.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("io_perf") + } + return err + } + } + + return nil +} + // MarshalBinary interface implementation func (m *Disk) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/models/fio_perf_check_request.go b/models/fio_perf_check_request.go new file mode 100644 index 00000000000..a26bdd60746 --- /dev/null +++ b/models/fio_perf_check_request.go @@ -0,0 +1,98 @@ +// 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 ( + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// FioPerfCheckRequest fio perf check request +// +// swagger:model fio_perf_check_request +type FioPerfCheckRequest struct { + + // The maximal fdatasync duration that is considered acceptable. + // Required: true + DurationThreshold *int64 `json:"duration_threshold"` + + // Exit code to return in case of an error. + // Required: true + ExitCode *int64 `json:"exit_code"` + + // --filename argument for fio (expects a file or a block device path). + // Required: true + Path *string `json:"path"` +} + +// Validate validates this fio perf check request +func (m *FioPerfCheckRequest) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateDurationThreshold(formats); err != nil { + res = append(res, err) + } + + if err := m.validateExitCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePath(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *FioPerfCheckRequest) validateDurationThreshold(formats strfmt.Registry) error { + + if err := validate.Required("duration_threshold", "body", m.DurationThreshold); err != nil { + return err + } + + return nil +} + +func (m *FioPerfCheckRequest) validateExitCode(formats strfmt.Registry) error { + + if err := validate.Required("exit_code", "body", m.ExitCode); err != nil { + return err + } + + return nil +} + +func (m *FioPerfCheckRequest) validatePath(formats strfmt.Registry) error { + + if err := validate.Required("path", "body", m.Path); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *FioPerfCheckRequest) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *FioPerfCheckRequest) UnmarshalBinary(b []byte) error { + var res FioPerfCheckRequest + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/fio_perf_check_response.go b/models/fio_perf_check_response.go new file mode 100644 index 00000000000..cd9d4e6d3c9 --- /dev/null +++ b/models/fio_perf_check_response.go @@ -0,0 +1,43 @@ +// 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 ( + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// FioPerfCheckResponse fio perf check response +// +// swagger:model fio_perf_check_response +type FioPerfCheckResponse struct { + + // The 99th percentile of fdatasync durations in milliseconds. + IoSyncDuration int64 `json:"io_sync_duration,omitempty"` +} + +// Validate validates this fio perf check response +func (m *FioPerfCheckResponse) Validate(formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *FioPerfCheckResponse) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *FioPerfCheckResponse) UnmarshalBinary(b []byte) error { + var res FioPerfCheckResponse + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/io_perf.go b/models/io_perf.go new file mode 100644 index 00000000000..c00c154a90d --- /dev/null +++ b/models/io_perf.go @@ -0,0 +1,43 @@ +// 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 ( + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// IoPerf io perf +// +// swagger:model io_perf +type IoPerf struct { + + // 99th percentile of fsync duration in milliseconds + SyncDuration int64 `json:"sync_duration,omitempty"` +} + +// Validate validates this io perf +func (m *IoPerf) Validate(formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *IoPerf) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *IoPerf) UnmarshalBinary(b []byte) error { + var res IoPerf + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/models/step_type.go b/models/step_type.go index 81219ab40dc..9c949e0feb2 100644 --- a/models/step_type.go +++ b/models/step_type.go @@ -46,6 +46,9 @@ const ( // StepTypeNtpSynchronizer captures enum value "ntp-synchronizer" StepTypeNtpSynchronizer StepType = "ntp-synchronizer" + + // StepTypeFioPerfCheck captures enum value "fio-perf-check" + StepTypeFioPerfCheck StepType = "fio-perf-check" ) // for schema @@ -53,7 +56,7 @@ var stepTypeEnum []interface{} func init() { var res []StepType - if err := json.Unmarshal([]byte(`["connectivity-check","execute","inventory","install","free-network-addresses","reset-installation","dhcp-lease-allocate","api-vip-connectivity-check","ntp-synchronizer"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["connectivity-check","execute","inventory","install","free-network-addresses","reset-installation","dhcp-lease-allocate","api-vip-connectivity-check","ntp-synchronizer","fio-perf-check"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/openshift/template.yaml b/openshift/template.yaml index 546a78481c0..1d90e2605c5 100644 --- a/openshift/template.yaml +++ b/openshift/template.yaml @@ -84,6 +84,9 @@ parameters: - name: NTP_SYNCHRONIZER_IMAGE value: '' required: true +- name: FIO_PERF_CHECK_IMAGE + value: '' + required: true - name: INSTALL_RH_CA value: "false" required: true diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index cc19aa12582..ef1b1ad4c8b 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -4797,6 +4797,9 @@ func init() { }, "x-nullable": false }, + "io_perf": { + "$ref": "#/definitions/io_perf" + }, "model": { "type": "string" }, @@ -4933,6 +4936,37 @@ func init() { "$ref": "#/definitions/event" } }, + "fio_perf_check_request": { + "type": "object", + "required": [ + "path", + "duration_threshold", + "exit_code" + ], + "properties": { + "duration_threshold": { + "description": "The maximal fdatasync duration that is considered acceptable.", + "type": "integer" + }, + "exit_code": { + "description": "Exit code to return in case of an error.", + "type": "integer" + }, + "path": { + "description": "--filename argument for fio (expects a file or a block device path).", + "type": "string" + } + } + }, + "fio_perf_check_response": { + "type": "object", + "properties": { + "io_sync_duration": { + "description": "The 99th percentile of fdatasync durations in milliseconds.", + "type": "integer" + } + } + }, "free-addresses-list": { "type": "array", "items": { @@ -5526,6 +5560,15 @@ func init() { } } }, + "io_perf": { + "type": "object", + "properties": { + "sync_duration": { + "description": "99th percentile of fsync duration in milliseconds", + "type": "integer" + } + } + }, "l2-connectivity": { "type": "object", "properties": { @@ -5799,7 +5842,8 @@ func init() { "reset-installation", "dhcp-lease-allocate", "api-vip-connectivity-check", - "ntp-synchronizer" + "ntp-synchronizer", + "fio-perf-check" ] }, "steps": { @@ -10711,6 +10755,9 @@ func init() { }, "x-nullable": false }, + "io_perf": { + "$ref": "#/definitions/io_perf" + }, "model": { "type": "string" }, @@ -10847,6 +10894,37 @@ func init() { "$ref": "#/definitions/event" } }, + "fio_perf_check_request": { + "type": "object", + "required": [ + "path", + "duration_threshold", + "exit_code" + ], + "properties": { + "duration_threshold": { + "description": "The maximal fdatasync duration that is considered acceptable.", + "type": "integer" + }, + "exit_code": { + "description": "Exit code to return in case of an error.", + "type": "integer" + }, + "path": { + "description": "--filename argument for fio (expects a file or a block device path).", + "type": "string" + } + } + }, + "fio_perf_check_response": { + "type": "object", + "properties": { + "io_sync_duration": { + "description": "The 99th percentile of fdatasync durations in milliseconds.", + "type": "integer" + } + } + }, "free-addresses-list": { "type": "array", "items": { @@ -11441,6 +11519,15 @@ func init() { } } }, + "io_perf": { + "type": "object", + "properties": { + "sync_duration": { + "description": "99th percentile of fsync duration in milliseconds", + "type": "integer" + } + } + }, "l2-connectivity": { "type": "object", "properties": { @@ -11714,7 +11801,8 @@ func init() { "reset-installation", "dhcp-lease-allocate", "api-vip-connectivity-check", - "ntp-synchronizer" + "ntp-synchronizer", + "fio-perf-check" ] }, "steps": { diff --git a/subsystem/host_test.go b/subsystem/host_test.go index 6d23424e0e1..0a92d6d4434 100644 --- a/subsystem/host_test.go +++ b/subsystem/host_test.go @@ -15,6 +15,7 @@ import ( . "github.com/onsi/gomega" "github.com/openshift/assisted-service/client/installer" "github.com/openshift/assisted-service/internal/common" + internalhost "github.com/openshift/assisted-service/internal/host" "github.com/openshift/assisted-service/models" ) @@ -272,6 +273,31 @@ var _ = Describe("Host tests", func() { }) + It("installation_fio_error_reply", func() { + host := ®isterHost(clusterID).Host + Expect(db.Model(host).Update("status", "installing").Error).NotTo(HaveOccurred()) + Expect(db.Model(host).UpdateColumn("inventory", defaultInventory()).Error).NotTo(HaveOccurred()) + Expect(db.Model(host).Update("role", "worker").Error).NotTo(HaveOccurred()) + reason := "Disk /dev/vda is not fast enough for installation." + + _, err := agentBMClient.Installer.PostStepReply(ctx, &installer.PostStepReplyParams{ + ClusterID: clusterID, + HostID: *host.ID, + Reply: &models.StepReply{ + ExitCode: internalhost.FioPerfCheckCmdExitCode, + Output: "Failed to install", + StepType: models.StepTypeInstall, + StepID: "installCmd-" + string(models.StepTypeExecute), + Error: reason, + }, + }) + Expect(err).ShouldNot(HaveOccurred()) + host = getHost(clusterID, *host.ID) + Expect(swag.StringValue(host.Status)).Should(Equal("error")) + Expect(swag.StringValue(host.StatusInfo)).Should(Equal(reason)) + + }) + It("connectivity_report_store_only_relevant_reply", func() { host := ®isterHost(clusterID).Host diff --git a/swagger.yaml b/swagger.yaml index 5ed34c9b962..e8447f801e1 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -2905,6 +2905,7 @@ definitions: - dhcp-lease-allocate - api-vip-connectivity-check - ntp-synchronizer + - fio-perf-check step: type: object @@ -3705,6 +3706,15 @@ definitions: type: string smart: type: string + io_perf: + $ref: '#/definitions/io_perf' + + io_perf: + type: object + properties: + sync_duration: + type: integer + description: 99th percentile of fsync duration in milliseconds boot: type: object @@ -3822,6 +3832,30 @@ definitions: type: boolean description: API VIP connecitivty check result. + fio_perf_check_request: + type: object + required: + - path + - duration_threshold + - exit_code + properties: + path: + type: string + description: --filename argument for fio (expects a file or a block device path). + duration_threshold: + type: integer + description: The maximal fdatasync duration that is considered acceptable. + exit_code: + type: integer + description: Exit code to return in case of an error. + + fio_perf_check_response: + type: object + properties: + io_sync_duration: + type: integer + description: The 99th percentile of fdatasync durations in milliseconds. + credentials: type: object properties: diff --git a/tools/deploy_assisted_installer_configmap.py b/tools/deploy_assisted_installer_configmap.py index 416e6cdfe79..4a05805b7c5 100644 --- a/tools/deploy_assisted_installer_configmap.py +++ b/tools/deploy_assisted_installer_configmap.py @@ -65,6 +65,7 @@ def main(): "FREE_ADDRESSES_IMAGE": "assisted-installer-agent", "DHCP_LEASE_ALLOCATOR_IMAGE": "assisted-installer-agent", "API_VIP_CONNECTIVITY_CHECK_IMAGE": "assisted-installer-agent", + "FIO_PERF_CHECK_IMAGE": "assisted-installer-agent", "NTP_SYNCHRONIZER_IMAGE": "assisted-installer-agent"} for env_var_name, image_short_name in versions.items(): versions[env_var_name] = deployment_options.get_image_override(deploy_options, image_short_name, env_var_name)