Preserve staging I/O failure causes (T-615)
This commit is contained in:
@@ -83,6 +83,12 @@ type DiskSpaceChecker interface {
|
||||
AvailableBytes(appRoot string) (int64, error)
|
||||
}
|
||||
|
||||
// StorageFailureClassifier lets the platform identify a preserved staging I/O
|
||||
// failure as insufficient storage without importing platform APIs into core.
|
||||
type StorageFailureClassifier interface {
|
||||
IsDiskFull(err error) bool
|
||||
}
|
||||
|
||||
// TargetStateChecker reports whether the verified current entrypoint is still
|
||||
// running. It never starts, waits for, or terminates a process.
|
||||
type TargetStateChecker interface {
|
||||
@@ -106,24 +112,26 @@ type InstallResult struct {
|
||||
}
|
||||
|
||||
// InstallServiceConfig makes all external installation dependencies explicit.
|
||||
// Disk and target-state checks are mandatory so no caller can silently bypass
|
||||
// the pre-extract safety boundary.
|
||||
// Disk, storage-failure and target-state checks are mandatory so no caller can
|
||||
// silently bypass the pre-extract safety boundary or disk-full diagnosis.
|
||||
type InstallServiceConfig struct {
|
||||
Extractor installer.Extractor
|
||||
Records InstallRecordStore
|
||||
Health installer.HealthCheck
|
||||
DiskSpace DiskSpaceChecker
|
||||
TargetState TargetStateChecker
|
||||
Extractor installer.Extractor
|
||||
Records InstallRecordStore
|
||||
Health installer.HealthCheck
|
||||
DiskSpace DiskSpaceChecker
|
||||
StorageFailures StorageFailureClassifier
|
||||
TargetState TargetStateChecker
|
||||
}
|
||||
|
||||
// InstallService implements the core-only verified package installation use
|
||||
// case. The caller must supply entries produced by catalog.Client.
|
||||
type InstallService struct {
|
||||
extractor installer.Extractor
|
||||
records InstallRecordStore
|
||||
health installer.HealthCheck
|
||||
diskSpace DiskSpaceChecker
|
||||
targetState TargetStateChecker
|
||||
extractor installer.Extractor
|
||||
records InstallRecordStore
|
||||
health installer.HealthCheck
|
||||
diskSpace DiskSpaceChecker
|
||||
storageFailures StorageFailureClassifier
|
||||
targetState TargetStateChecker
|
||||
}
|
||||
|
||||
func NewInstallService(config InstallServiceConfig) (*InstallService, error) {
|
||||
@@ -136,15 +144,19 @@ func NewInstallService(config InstallServiceConfig) (*InstallService, error) {
|
||||
if config.DiskSpace == nil {
|
||||
return nil, fmt.Errorf("%w: disk space checker is required", ErrInstallServiceConfig)
|
||||
}
|
||||
if config.StorageFailures == nil {
|
||||
return nil, fmt.Errorf("%w: storage failure classifier is required", ErrInstallServiceConfig)
|
||||
}
|
||||
if config.TargetState == nil {
|
||||
return nil, fmt.Errorf("%w: target state checker is required", ErrInstallServiceConfig)
|
||||
}
|
||||
return &InstallService{
|
||||
extractor: config.Extractor,
|
||||
records: config.Records,
|
||||
health: config.Health,
|
||||
diskSpace: config.DiskSpace,
|
||||
targetState: config.TargetState,
|
||||
extractor: config.Extractor,
|
||||
records: config.Records,
|
||||
health: config.Health,
|
||||
diskSpace: config.DiskSpace,
|
||||
storageFailures: config.StorageFailures,
|
||||
targetState: config.TargetState,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -154,16 +166,16 @@ func NewInstallService(config InstallServiceConfig) (*InstallService, error) {
|
||||
func (service *InstallService) Install(request InstallRequest) (InstallResult, error) {
|
||||
expectation, record, err := resolveInstallRequest(request)
|
||||
if err != nil {
|
||||
return InstallResult{}, installError(InstallStageVerify, err)
|
||||
return InstallResult{}, service.installError(InstallStageVerify, err)
|
||||
}
|
||||
|
||||
appRoot, err := service.records.EnsureAppRoot(record.ID)
|
||||
if err != nil {
|
||||
return InstallResult{}, installError(InstallStageRecover, err)
|
||||
return InstallResult{}, service.installError(InstallStageRecover, err)
|
||||
}
|
||||
recovery, err := installer.Recover(appRoot)
|
||||
if err != nil {
|
||||
return InstallResult{}, installError(InstallStageRecover, err)
|
||||
return InstallResult{}, service.installError(InstallStageRecover, err)
|
||||
}
|
||||
|
||||
extracted, err := service.extractor.ExtractVerifiedFileWithCheck(
|
||||
@@ -173,7 +185,7 @@ func (service *InstallService) Install(request InstallRequest) (InstallResult, e
|
||||
service.preExtractCheck(appRoot, record.ID),
|
||||
)
|
||||
if err != nil {
|
||||
return InstallResult{}, installError(stageForPackageError(err), err)
|
||||
return InstallResult{}, service.installError(stageForPackageError(err), err)
|
||||
}
|
||||
record.Files = make([]storage.InstalledFile, 0, len(extracted.PayloadFiles))
|
||||
for _, file := range extracted.PayloadFiles {
|
||||
@@ -196,7 +208,7 @@ func (service *InstallService) Install(request InstallRequest) (InstallResult, e
|
||||
return nil
|
||||
})
|
||||
if err := switcher.Switch(appRoot); err != nil {
|
||||
return InstallResult{}, installError(stageForSwitchError(err, recordWriteErr), err)
|
||||
return InstallResult{}, service.installError(stageForSwitchError(err, recordWriteErr), err)
|
||||
}
|
||||
return InstallResult{
|
||||
AppID: record.ID,
|
||||
@@ -298,8 +310,15 @@ func resolveInstallRequest(request InstallRequest) (installer.PackageExpectation
|
||||
}, nil
|
||||
}
|
||||
|
||||
func installError(stage InstallStage, err error) error {
|
||||
return &InstallError{Stage: stage, Code: failureCodeFor(err), Err: err}
|
||||
func (service *InstallService) installError(stage InstallStage, err error) error {
|
||||
return &InstallError{Stage: stage, Code: service.failureCodeFor(err), Err: err}
|
||||
}
|
||||
|
||||
func (service *InstallService) failureCodeFor(err error) FailureCode {
|
||||
if errors.Is(err, installer.ErrStagingOutput) && service.storageFailures.IsDiskFull(err) {
|
||||
return FailureCodeDiskFull
|
||||
}
|
||||
return failureCodeFor(err)
|
||||
}
|
||||
|
||||
func stageForPackageError(err error) InstallStage {
|
||||
|
||||
Reference in New Issue
Block a user