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 6 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,30 @@
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

logger.info(s"Attempting download attempt $downloadAttempt of $downloadRetries for a GCS url")
lucymcnatt marked this conversation as resolved.
Show resolved Hide resolved

if (downloadAttempt < downloadRetries) {
backoff foreach { b => Thread.sleep(b.backoffMillis) }
runDownloadCommand.redeemWith(
recover = handleDownloadFailure,

Check warning on line 69 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#L66-L69

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

Check warning on line 72 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#L72

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

Check warning on line 74 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#L74

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

Check warning on line 76 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#L76

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

Check warning on line 78 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#L78

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

Check warning on line 82 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#L82

Added line #L82 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