Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AN-333] Prevent infinite DRS download retries #7679

Merged
merged 7 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,33 +57,29 @@
downloadAttempt: Int = 0
): IO[DownloadResult] = {

def maybeRetryForDownloadFailure(t: Throwable): IO[DownloadResult] =
if (downloadAttempt < downloadRetries) {
backoff foreach { b => Thread.sleep(b.backoffMillis) }
logger.warn(s"Attempting download retry $downloadAttempt of $downloadRetries for a GCS url", t)
downloadWithRetries(downloadRetries,
backoff map {
_.next
},
downloadAttempt + 1
)
} else {
IO.raiseError(new RuntimeException(s"Exhausted $downloadRetries resolution retries to download GCS file", t))
}

runDownloadCommand.redeemWith(
recover = maybeRetryForDownloadFailure,
bind = {
case s: DownloadSuccess.type =>
IO.pure(s)
case _: RecognizedRetryableDownloadFailure =>
downloadWithRetries(downloadRetries, backoff, downloadAttempt + 1)
case _: UnrecognizedRetryableDownloadFailure =>
downloadWithRetries(downloadRetries, backoff, downloadAttempt + 1)
case _ =>
downloadWithRetries(downloadRetries, backoff, downloadAttempt + 1)
}
)
// Necessary function to handle the throwable when trying to recover a failed download
def handleDownloadFailure(t: Throwable): IO[DownloadResult] =
downloadWithRetries(downloadRetries, backoff, downloadAttempt + 1)

Check warning on line 62 in cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala

View check run for this annotation

Codecov / codecov/patch

cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala#L62

Added line #L62 was not covered by tests

if (downloadAttempt < downloadRetries) {
backoff foreach { b => Thread.sleep(b.backoffMillis) }

Check warning on line 65 in cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala

View check run for this annotation

Codecov / codecov/patch

cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala#L64-L65

Added lines #L64 - L65 were not covered by tests
logger.info(s"Attempting download attempt $downloadAttempt of $downloadRetries for a GCS url")
runDownloadCommand.redeemWith(
recover = handleDownloadFailure,

Check warning on line 68 in cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala

View check run for this annotation

Codecov / codecov/patch

cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala#L67-L68

Added lines #L67 - L68 were not covered by tests
bind = {
case s: DownloadSuccess.type =>
IO.pure(s)

Check warning on line 71 in cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala

View check run for this annotation

Codecov / codecov/patch

cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala#L71

Added line #L71 was not covered by tests
case _: RecognizedRetryableDownloadFailure =>
downloadWithRetries(downloadRetries, backoff, downloadAttempt + 1)

Check warning on line 73 in cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala

View check run for this annotation

Codecov / codecov/patch

cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala#L73

Added line #L73 was not covered by tests
case _: UnrecognizedRetryableDownloadFailure =>
downloadWithRetries(downloadRetries, backoff, downloadAttempt + 1)

Check warning on line 75 in cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala

View check run for this annotation

Codecov / codecov/patch

cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala#L75

Added line #L75 was not covered by tests
case _ =>
downloadWithRetries(downloadRetries, backoff, downloadAttempt + 1)

Check warning on line 77 in cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala

View check run for this annotation

Codecov / codecov/patch

cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala#L77

Added line #L77 was not covered by tests
}
)
} else {
IO.raiseError(new RuntimeException(s"Exhausted $downloadRetries resolution retries to download GCS file"))

Check warning on line 81 in cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala

View check run for this annotation

Codecov / codecov/patch

cromwell-drs-localizer/src/main/scala/drs/localizer/downloaders/GcsUriDownloader.scala#L81

Added line #L81 was not covered by tests
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package drs.localizer.downloaders

import common.assertion.CromwellTimeoutSpec
import org.mockito.Mockito.{spy, times, verify}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

Expand Down Expand Up @@ -96,4 +97,27 @@ class GcsUriDownloaderSpec extends AnyFlatSpec with CromwellTimeoutSpec with Mat

downloader.generateDownloadScript(gcsUrl, Option(fakeSAJsonPath)) shouldBe expectedDownloadScript
}

it should "fail to download GCS URL after 5 attempts" in {
val gcsUrl = "gs://foo/bar.bam"
val downloader = spy(
new GcsUriDownloader(
gcsUrl = gcsUrl,
downloadLoc = fakeDownloadLocation,
requesterPaysProjectIdOption = Option(fakeRequesterPaysId),
serviceAccountJson = None
)
)

val result = downloader.downloadWithRetries(5, None).attempt.unsafeRunSync()

result.isLeft shouldBe true
// attempts to download the 1st time and the 5th time, but doesn't attempt a 6th
verify(downloader, times(1)).downloadWithRetries(5, None, 1)
verify(downloader, times(1)).downloadWithRetries(5, None, 5)
verify(downloader, times(0)).downloadWithRetries(5, None, 6)
// attempts the actual download command 5 times
verify(downloader, times(5)).runDownloadCommand

}
}
Loading