From d5693c93d7e7aa35b01fe9890f9ebacbf2d3d954 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Thu, 18 Apr 2019 11:37:46 -0400 Subject: [PATCH 01/46] Fix AWS continueOnReturnCode (#4813) --- src/ci/bin/testCentaurAws.sh | 4 ---- .../main/scala/cromwell/backend/impl/aws/AwsBatchJob.scala | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ci/bin/testCentaurAws.sh b/src/ci/bin/testCentaurAws.sh index 876461de02d..dc085bda78e 100755 --- a/src/ci/bin/testCentaurAws.sh +++ b/src/ci/bin/testCentaurAws.sh @@ -33,16 +33,13 @@ cromwell::build::run_centaur \ -e cnv_somatic_panel \ -e localdockertest \ -e inline_file \ - -e exit \ -e iwdr_input_string_function \ -e globbingbehavior \ -e non_root_default_user \ -e draft3_glob_access \ -e bad_output_task \ -e cwl_interpolated_strings \ - -e failures.terminal_status \ -e bad_file_string \ - -e default_runtime_attributes \ -e non_root_specified_user \ -e space \ -e draft3_optional_input_from_scatter \ @@ -52,7 +49,6 @@ cromwell::build::run_centaur \ -e cwl_cache_between_workflows \ -e abort.scheduled_abort \ -e cwl_cache_within_workflow \ - -e continue_on_return_code \ -e globbingscatter \ -e inline_file_custom_entryname \ -e relative_output_paths \ diff --git a/supportedBackends/aws/src/main/scala/cromwell/backend/impl/aws/AwsBatchJob.scala b/supportedBackends/aws/src/main/scala/cromwell/backend/impl/aws/AwsBatchJob.scala index 854f2d3ed7b..6bb85059ae9 100644 --- a/supportedBackends/aws/src/main/scala/cromwell/backend/impl/aws/AwsBatchJob.scala +++ b/supportedBackends/aws/src/main/scala/cromwell/backend/impl/aws/AwsBatchJob.scala @@ -122,7 +122,7 @@ final case class AwsBatchJob(jobDescriptor: BackendJobDescriptor, // WDL/CWL |" |cat $dockerStderr |echo "--$boundary--" - |exit $$(cat $dockerRc) + |exit 0 """).stripMargin } def submitJob[F[_]]()( From 4cc32c112931c60211e55cccde35fd7593b054b0 Mon Sep 17 00:00:00 2001 From: Miguel Covarrubias Date: Fri, 19 Apr 2019 13:05:08 -0400 Subject: [PATCH 02/46] Update cromwell version from 40 to 41 --- project/Version.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Version.scala b/project/Version.scala index ec94f285f4f..a2543575639 100644 --- a/project/Version.scala +++ b/project/Version.scala @@ -5,7 +5,7 @@ import sbt._ object Version { // Upcoming release, or current if we're on a master / hotfix branch - val cromwellVersion = "40" + val cromwellVersion = "41" /** * Returns true if this project should be considered a snapshot. From 54c5c480aa54f82b6ef787f5698b7eabc6c69942 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Fri, 19 Apr 2019 16:10:06 -0400 Subject: [PATCH 03/46] Custom google labels (#4825) --- CHANGELOG.md | 6 ++ .../google_labels/bad_options.json | 5 ++ .../google_labels/good_options.json | 6 ++ .../google_labels/google_labels.wdl | 57 +++++++++++++++++ .../google_labels/wrapper.wdl | 10 +++ .../standardTestCases/google_labels_bad.test | 17 +++++ .../standardTestCases/google_labels_good.test | 17 +++++ .../google_labels_subworkflows.test | 16 +++++ .../scala/cromwell/core/WorkflowOptions.scala | 3 +- docs/backends/Google.md | 5 +- docs/wf_options/Google.md | 6 +- .../pipelines/common/GoogleLabels.scala | 64 ++++++++++++++----- ...inesApiAsyncBackendJobExecutionActor.scala | 20 ++++-- .../PipelinesApiInitializationActor.scala | 2 + .../PipelinesApiJobCachingActorHelper.scala | 9 +-- .../api/PipelinesApiRequestFactory.scala | 3 +- .../pipelines/common/GoogleLabelsSpec.scala | 4 +- ...ApiAsyncBackendJobExecutionActorSpec.scala | 2 - .../pipelines/v1alpha2/GenomicsFactory.scala | 2 +- .../pipelines/v2alpha1/GenomicsFactory.scala | 4 +- 20 files changed, 218 insertions(+), 40 deletions(-) create mode 100644 centaur/src/main/resources/standardTestCases/google_labels/bad_options.json create mode 100644 centaur/src/main/resources/standardTestCases/google_labels/good_options.json create mode 100644 centaur/src/main/resources/standardTestCases/google_labels/google_labels.wdl create mode 100644 centaur/src/main/resources/standardTestCases/google_labels/wrapper.wdl create mode 100644 centaur/src/main/resources/standardTestCases/google_labels_bad.test create mode 100644 centaur/src/main/resources/standardTestCases/google_labels_good.test create mode 100644 centaur/src/main/resources/standardTestCases/google_labels_subworkflows.test diff --git a/CHANGELOG.md b/CHANGELOG.md index d37fae8b3d2..f5c0d6d3a3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Cromwell Change Log +## 41 Release Notes + +### Workflow Options + +* It is now possible to supply custom `google-labels` in [workflow options](https://cromwell.readthedocs.io/en/stable/wf_options/Google/). + ## 40 Release Notes ### Config Changes diff --git a/centaur/src/main/resources/standardTestCases/google_labels/bad_options.json b/centaur/src/main/resources/standardTestCases/google_labels/bad_options.json new file mode 100644 index 00000000000..6d7e010d5a3 --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/google_labels/bad_options.json @@ -0,0 +1,5 @@ +{ + "google_labels": { + "custom-label-label-label-label-label-label-label-label-label-label": "0custom-value" + } +} diff --git a/centaur/src/main/resources/standardTestCases/google_labels/good_options.json b/centaur/src/main/resources/standardTestCases/google_labels/good_options.json new file mode 100644 index 00000000000..f8da61c898e --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/google_labels/good_options.json @@ -0,0 +1,6 @@ +{ + "read_from_cache": false, + "google_labels": { + "custom-label": "custom-value" + } +} diff --git a/centaur/src/main/resources/standardTestCases/google_labels/google_labels.wdl b/centaur/src/main/resources/standardTestCases/google_labels/google_labels.wdl new file mode 100644 index 00000000000..b85c2eab3ef --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/google_labels/google_labels.wdl @@ -0,0 +1,57 @@ +version 1.0 + +workflow google_labels { + + meta { + description: "Confirms that the google_labels workflow option is propagated correctly to tasks" + } + + input { + Array[Pair[String, String]] expected_kvps = [ ("wdl-task-name", "check-labels") ] + Boolean check_aliases = true + } + + # Build an array of checker lines to confirm the existence of each KVP: + scatter (p in expected_kvps) { + String checker_lines = "echo $INSTANCE_INFO | grep -q '\"~{p.left}\": \"~{p.right}\"' || echo \"Couldn't find correct '~{p.left}' label in instance info: $INSTANCE_INFO\" 1>&2" + } + + call check_labels { input: label_checker_lines = checker_lines } + + if (check_aliases) { + call check_labels as check_label_alias { input: label_checker_lines = [ + "echo $INSTANCE_INFO | grep -q '\"wdl-task-name\": \"check-labels\"' || echo \"Couldn't find correct 'wdl-task-name' label in instance info: $INSTANCE_INFO\" 1>&2", + "echo $INSTANCE_INFO | grep -q '\"wdl-call-alias\": \"check-label-alias\"' || echo \"Couldn't find correct 'wdl-call-alias' label in instance info: $INSTANCE_INFO\" 1>&2" + ] } + } + + output { + Boolean done = true + } + +} + +task check_labels { + + input { + Array[String] label_checker_lines + } + + command { + # Check that the task metadata is correct: + INSTANCE=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/name" -H "Metadata-Flavor: Google") + TOKEN=$(gcloud auth application-default print-access-token) + INSTANCE_INFO=$(curl -s "https://www.googleapis.com/compute/v1/projects/broad-dsde-cromwell-dev/zones/us-central1-c/instances/$INSTANCE" \ + -H "Authorization: Bearer $TOKEN" \ + -H 'Accept: application/json') + + # Check for the custom label in the instance info: + ~{sep="\n" label_checker_lines} + } + runtime { + docker: "google/cloud-sdk:slim" + # Specify this zone because it's used in the curl commands above. We probably *could* work this out ad-hoc but it's easier to hard-code it here: + zones: ["us-central1-c"] + failOnStderr: true + } +} diff --git a/centaur/src/main/resources/standardTestCases/google_labels/wrapper.wdl b/centaur/src/main/resources/standardTestCases/google_labels/wrapper.wdl new file mode 100644 index 00000000000..33dfe9eefb9 --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/google_labels/wrapper.wdl @@ -0,0 +1,10 @@ +version 1.0 + +import "google_labels.wdl" + +workflow google_labels_wrapper { + call google_labels.google_labels { input: + expected_kvps = [ ("cromwell-sub-workflow-name", "google-labels"), ("wdl-task-name", "check-labels") ], + check_aliases = false + } +} diff --git a/centaur/src/main/resources/standardTestCases/google_labels_bad.test b/centaur/src/main/resources/standardTestCases/google_labels_bad.test new file mode 100644 index 00000000000..720f8a18050 --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/google_labels_bad.test @@ -0,0 +1,17 @@ +name: google_labels_bad +testFormat: workflowfailure +backends: [Papiv2] + +files { + workflow: google_labels/google_labels.wdl + options: google_labels/bad_options.json +} + +metadata { + workflowName: google_labels + status: Failed + + "failures.0.message": "Invalid 'google_labels' in workflow options" + "failures.0.causedBy.0.message": "Invalid label field: `custom-label-label-label-label-label-label-label-label-label-label` is 66 characters. The maximum is 63." + "failures.0.causedBy.1.message": "Invalid label field: `0custom-value` did not match the regex '[a-z]([-a-z0-9]*[a-z0-9])?'" +} diff --git a/centaur/src/main/resources/standardTestCases/google_labels_good.test b/centaur/src/main/resources/standardTestCases/google_labels_good.test new file mode 100644 index 00000000000..76cf5ec5b16 --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/google_labels_good.test @@ -0,0 +1,17 @@ +name: google_labels_good +testFormat: workflowsuccess +backends: [Papiv2] + +files { + workflow: google_labels/google_labels.wdl + options: google_labels/good_options.json +} + +metadata { + workflowName: google_labels + status: Succeeded + + "calls.google_labels.check_labels.labels.wdl-task-name": "check_labels" + + "calls.google_labels.check_labels.backendLabels.custom-label": "custom-value" +} diff --git a/centaur/src/main/resources/standardTestCases/google_labels_subworkflows.test b/centaur/src/main/resources/standardTestCases/google_labels_subworkflows.test new file mode 100644 index 00000000000..21b734c94fc --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/google_labels_subworkflows.test @@ -0,0 +1,16 @@ +name: google_labels_sub +testFormat: workflowsuccess +backends: [Papiv2] + +files { + workflow: google_labels/wrapper.wdl + options: google_labels/good_options.json + imports: [ + google_labels/google_labels.wdl + ] +} + +metadata { + workflowName: google_labels_wrapper + status: Succeeded +} diff --git a/core/src/main/scala/cromwell/core/WorkflowOptions.scala b/core/src/main/scala/cromwell/core/WorkflowOptions.scala index 855b2e0c977..51a132b9e4f 100644 --- a/core/src/main/scala/cromwell/core/WorkflowOptions.scala +++ b/core/src/main/scala/cromwell/core/WorkflowOptions.scala @@ -65,6 +65,7 @@ object WorkflowOptions { private lazy val EncryptedFields: Seq[String] = WorkflowOptionsConf.getStringList("encrypted-fields").asScala private lazy val EncryptionKey: String = WorkflowOptionsConf.getString("base64-encryption-key") private lazy val defaultRuntimeOptionKey: String = DefaultRuntimeOptions.name + private lazy val validObjectKeys: Set[String] = Set(DefaultRuntimeOptions.name, "google_labels") def encryptField(value: JsString): Try[JsObject] = { Aes256Cbc.encrypt(value.value.getBytes("utf-8"), SecretKey(EncryptionKey)) match { @@ -94,7 +95,7 @@ object WorkflowOptions { case (k, v: JsString) if EncryptedFields.contains(k) => k -> encryptField(v) case (k, v: JsString) => k -> Success(v) case (k, v: JsBoolean) => k -> Success(v) - case (k, v: JsObject) if defaultRuntimeOptionKey.equals(k) => k -> Success(v) + case (k, v: JsObject) if validObjectKeys.contains(k) => k -> Success(v) case (k, v: JsNumber) => k -> Success(v) case (k, v) if isEncryptedField(v) => k -> Success(v) case (k, v: JsArray) => k -> Success(v) diff --git a/docs/backends/Google.md b/docs/backends/Google.md index 1a7ce38e130..e8177948e37 100644 --- a/docs/backends/Google.md +++ b/docs/backends/Google.md @@ -224,7 +224,8 @@ workflows using the Google backend. #### Google Labels -Every call run on the Pipelines API backend is given certain labels by default, so that Google resources can be queried by these labels later. The current default label set automatically applied is: +Every call run on the Pipelines API backend is given certain labels by default, so that Google resources can be queried by these labels later. +The current default label set automatically applied is: | Key | Value | Example | Notes | |-----|-------|---------|-------| @@ -233,7 +234,7 @@ Every call run on the Pipelines API backend is given certain labels by default, | wdl-task-name | The name of the WDL task | my-task | | | wdl-call-alias | The alias of the WDL call that created this job | my-task-1 | Only present if the task was called with an alias. | -Any custom labels provided upon workflow submission are also applied to Google resources by the Pipelines API. +Any custom labels provided as '`google_labels`' in the [workflow options](../wf_options/Google) are also applied to Google resources by the Pipelines API. ## Using NCBI Sequence Read Archive (SRA) Data diff --git a/docs/wf_options/Google.md b/docs/wf_options/Google.md index 14e2400ab14..87c504155a1 100644 --- a/docs/wf_options/Google.md +++ b/docs/wf_options/Google.md @@ -11,6 +11,7 @@ Keys | Possible Values | Description `auth_bucket` |`string` | A GCS URL that only Cromwell can write to. The Cromwell account is determined by the `google.authScheme` (and the corresponding `google.userAuth` and `google.serviceAuth`). Defaults to the the value in [jes_gcs_root](#jes_gcs_root). `monitoring_script` |`string` | Specifies a GCS URL to a script that will be invoked prior to the user command being run. For example, if the value for monitoring_script is `"gs://bucket/script.sh"`, it will be invoked as `./script.sh > monitoring.log &`. The value `monitoring.log` file will be automatically de-localized. `monitoring_image` |`string` | Specifies a Docker image to monitor the task. This image will run concurrently with the task container, and provides an alternative mechanism to `monitoring_script` (the latter runs *inside* the task container). For example, one can use `quay.io/broadinstitute/cromwell-monitor`, which reports cpu/memory/disk utilization metrics to [Stackdriver](https://cloud.google.com/monitoring/). +`google_labels` | `object` | An object containing only string values. Represent custom labels to send with PAPI job requests. Per the PAPI specification, each key and value must conform to the regex `[a-z]([-a-z0-9]*[a-z0-9])?`. # Example ```json @@ -21,6 +22,9 @@ Keys | Possible Values | Description "google_compute_service_account": " my-new-svcacct@my-google-project.iam.gserviceaccount.com" "auth_bucket": "gs://my-auth-bucket/private", "monitoring_script": "gs://bucket/script.sh", - "monitoring_image": "quay.io/broadinstitute/cromwell-monitor" + "monitoring_image": "quay.io/broadinstitute/cromwell-monitor", + "google_labels": { + "custom-label": "custom-value" + } } ``` diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/GoogleLabels.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/GoogleLabels.scala index 814cda54251..8ad01e42c8a 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/GoogleLabels.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/GoogleLabels.scala @@ -1,21 +1,31 @@ package cromwell.backend.google.pipelines.common -import cats.data.Validated.Valid +import cats.data.Validated.{Invalid, Valid} import common.validation.ErrorOr.ErrorOr -import cromwell.core.labels.{Label, Labels} import cats.syntax.validated._ -import scala.util.matching.Regex +import cats.syntax.traverse._ +import cats.instances.list._ +import cats.syntax.apply._ +import common.exception.AggregatedMessageException +import cromwell.core.{CromwellFatalExceptionMarker, WorkflowOptions} +import spray.json.{JsObject, JsString} + +import scala.util.{Failure, Success, Try} +import scala.util.control.NoStackTrace + +final case class GoogleLabel(key: String, value: String) object GoogleLabels { val MaxLabelLength = 63 - val GoogleLabelsRegexPattern = "[a-z]([-a-z0-9]*[a-z0-9])?" + val GoogleLabelRegexPattern = "[a-z]([-a-z0-9]*[a-z0-9])?" + val GoogleLabelRegex = GoogleLabelRegexPattern.r - // This function is used to coerce a string into one that meets the requirements for a label submission to JES. + // This function is used to coerce a string into one that meets the requirements for a label submission to Google Pipelines API. // See 'labels' in https://cloud.google.com/genomics/reference/rpc/google.genomics.v1alpha2#google.genomics.v1alpha2.RunPipelineArgs def safeGoogleName(mainText: String, emptyAllowed: Boolean = false): String = { - validateLabelRegex(mainText, GoogleLabelsRegexPattern.r) match { + validateLabelRegex(mainText) match { case Valid(labelText) => labelText case invalid @ _ if mainText.equals("") && emptyAllowed => mainText case invalid @ _ => @@ -47,22 +57,46 @@ object GoogleLabels { } } - def validateLabelRegex(s: String, regexAllowed: Regex): ErrorOr[String] = { - (regexAllowed.pattern.matcher(s).matches, s.length <= MaxLabelLength) match { + def validateLabelRegex(s: String): ErrorOr[String] = { + (GoogleLabelRegex.pattern.matcher(s).matches, s.length <= MaxLabelLength) match { case (true, true) => s.validNel - case (false, false) => s"Invalid label: `$s` did not match regex $regexAllowed and it is ${s.length} characters. The maximum is $MaxLabelLength.".invalidNel - case (false, _) => s"Invalid label: `$s` did not match the regex $regexAllowed.".invalidNel - case (_, false) => s"Invalid label: `$s` is ${s.length} characters. The maximum is $MaxLabelLength.".invalidNel + case (false, false) => s"Invalid label field: `$s` did not match regex '$GoogleLabelRegexPattern' and it is ${s.length} characters. The maximum is $MaxLabelLength.".invalidNel + case (false, _) => s"Invalid label field: `$s` did not match the regex '$GoogleLabelRegexPattern'".invalidNel + case (_, false) => s"Invalid label field: `$s` is ${s.length} characters. The maximum is $MaxLabelLength.".invalidNel + } + } + + + def safeLabels(values: (String, String)*): Seq[GoogleLabel] = { + def safeGoogleLabel(kvp: (String, String)): GoogleLabel = { + GoogleLabel(safeGoogleName(kvp._1), safeGoogleName(kvp._2, emptyAllowed = true)) } + values.map(safeGoogleLabel) + } + + def validateLabel(key: String, value: String): ErrorOr[GoogleLabel] = { + (validateLabelRegex(key), validateLabelRegex(value)).mapN { (validKey, validValue) => GoogleLabel(validKey, validValue) } } + def fromWorkflowOptions(workflowOptions: WorkflowOptions): Try[Seq[GoogleLabel]] = { - def toLabels(values: (String, String)*): Labels = { + def extractGoogleLabelsFromJsObject(jsObject: JsObject): Try[Seq[GoogleLabel]] = { + val asErrorOr = jsObject.fields.toList.traverse { + case (key: String, value: JsString) => GoogleLabels.validateLabel(key, value.value) + case (key, other) => s"Bad label value type for '$key'. Expected simple string but got $other".invalidNel : ErrorOr[GoogleLabel] + } - def safeGoogleLabel(key: String, value: String): Label = { - Label(safeGoogleName(key), safeGoogleName(value, emptyAllowed = true)) + asErrorOr match { + case Valid(value) => Success(value) + case Invalid(errors) => Failure(new AggregatedMessageException("Invalid 'google_labels' in workflow options", errors.toList) with CromwellFatalExceptionMarker with NoStackTrace) + } } - Labels(values.toVector map (safeGoogleLabel _ ).tupled) + workflowOptions.toMap.get("google_labels") match { + case Some(obj: JsObject) => extractGoogleLabelsFromJsObject(obj) + case Some(other) => Failure(new Exception(s"Invalid 'google_labels' in workflow options. Must be a simple JSON object mapping string keys to string values. Got $other") with NoStackTrace with CromwellFatalExceptionMarker) + case None => Success(Seq.empty) + } } + } diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiAsyncBackendJobExecutionActor.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiAsyncBackendJobExecutionActor.scala index 888f7f9e7b1..7b56488b9f3 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiAsyncBackendJobExecutionActor.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiAsyncBackendJobExecutionActor.scala @@ -34,6 +34,7 @@ import cromwell.filesystems.sra.SraPath import cromwell.google.pipelines.common.PreviousRetryReasons import cromwell.services.keyvalue.KeyValueServiceActor._ import cromwell.services.keyvalue.KvClient +import cromwell.services.metadata.CallMetadataKeys import shapeless.Coproduct import wdl4s.parser.MemoryUnit import wom.CommandSetupSideEffectFile @@ -393,7 +394,7 @@ class PipelinesApiAsyncBackendJobExecutionActor(override val standardParams: Sta } } - private def createPipelineParameters(inputOutputParameters: InputOutputParameters): CreatePipelineParameters = { + private def createPipelineParameters(inputOutputParameters: InputOutputParameters, customLabels: Seq[GoogleLabel]): CreatePipelineParameters = { standardParams.backendInitializationDataOption match { case Some(data: PipelinesApiBackendInitializationData) => val dockerKeyAndToken: Option[CreatePipelineDockerKeyAndToken] = for { @@ -432,7 +433,7 @@ class PipelinesApiAsyncBackendJobExecutionActor(override val standardParams: Sta inputOutputParameters, googleProject(jobDescriptor.workflowDescriptor), computeServiceAccount(jobDescriptor.workflowDescriptor), - backendLabels, + backendLabels ++ customLabels, preemptible, pipelinesConfiguration.jobShell, dockerKeyAndToken, @@ -501,15 +502,23 @@ class PipelinesApiAsyncBackendJobExecutionActor(override val standardParams: Sta def uploadScriptFile = commandScriptContents.fold( errors => Future.failed(new RuntimeException(errors.toList.mkString(", "))), - asyncIo.writeAsync(jobPaths.script, _, Seq(CloudStorageOptions.withMimeType("text/plain")))) + asyncIo.writeAsync(jobPaths.script, _, Seq(CloudStorageOptions.withMimeType("text/plain"))) + ) + + def sendGoogleLabelsToMetadata(customLabels: Seq[GoogleLabel]): Unit = { + lazy val backendLabelEvents: Map[String, String] = ((backendLabels ++ customLabels) map { l => s"${CallMetadataKeys.BackendLabels}:${l.key}" -> l.value }).toMap + tellMetadata(backendLabelEvents) + } val runPipelineResponse = for { _ <- evaluateRuntimeAttributes _ <- uploadScriptFile + customLabels <- Future.fromTry(GoogleLabels.fromWorkflowOptions(workflowDescriptor.workflowOptions)) jesParameters <- generateInputOutputParameters - createParameters = createPipelineParameters(jesParameters) + createParameters = createPipelineParameters(jesParameters, customLabels) _ = this.hasDockerCredentials = createParameters.privateDockerKeyAndEncryptedToken.isDefined runId <- runPipeline(workflowId, createParameters, jobLogger) + _ = sendGoogleLabelsToMetadata(customLabels) } yield runId runPipelineResponse map { runId => @@ -613,11 +622,10 @@ class PipelinesApiAsyncBackendJobExecutionActor(override val standardParams: Sta val message = unable + details + prettyError Future.successful(FailedNonRetryableExecutionHandle(StandardException( runStatus.errorCode, message, jobTag, returnCode, standardPaths.error), returnCode)) - case _ => { + case _ => val finalPrettyPrintedError = generateBetterErrorMsg(runStatus, prettyError) Future.successful(FailedNonRetryableExecutionHandle(StandardException( runStatus.errorCode, finalPrettyPrintedError, jobTag, returnCode, standardPaths.error), returnCode)) - } } } diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiInitializationActor.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiInitializationActor.scala index 636c26e9cd4..94c1d4362b5 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiInitializationActor.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiInitializationActor.scala @@ -125,6 +125,8 @@ class PipelinesApiInitializationActor(pipelinesParams: PipelinesApiInitializatio for { paths <- workflowPaths _ = publishWorkflowRoot(paths.workflowRoot.pathAsString) + // Validate the google-labels workflow options, and only succeed initialization if they're good: + _ <- Future.fromTry(GoogleLabels.fromWorkflowOptions(workflowOptions)) data <- initializationData } yield Option(data) } diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiJobCachingActorHelper.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiJobCachingActorHelper.scala index 0ca13c40d8c..f9739c6c5f6 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiJobCachingActorHelper.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiJobCachingActorHelper.scala @@ -1,6 +1,5 @@ package cromwell.backend.google.pipelines.common -import akka.actor.Actor import cromwell.backend.google.pipelines.common.io.{PipelinesApiAttachedDisk, PipelinesApiWorkingDisk} import cromwell.backend.standard.StandardCachingActorHelper import cromwell.core.labels.Labels @@ -11,7 +10,7 @@ import cromwell.services.metadata.CallMetadataKeys import scala.language.postfixOps trait PipelinesApiJobCachingActorHelper extends StandardCachingActorHelper { - this: Actor with JobLogging => + this: PipelinesApiAsyncBackendJobExecutionActor with JobLogging => lazy val initializationData: PipelinesApiBackendInitializationData = { backendInitializationDataAs[PipelinesApiBackendInitializationData] @@ -58,12 +57,10 @@ trait PipelinesApiJobCachingActorHelper extends StandardCachingActorHelper { lazy val originalLabels: Labels = defaultLabels - lazy val backendLabels: Labels = GoogleLabels.toLabels(originalLabels.asTuple :_*) + lazy val backendLabels: Seq[GoogleLabel] = GoogleLabels.safeLabels(originalLabels.asTuple :_*) lazy val originalLabelEvents: Map[String, String] = originalLabels.value map { l => s"${CallMetadataKeys.Labels}:${l.key}" -> l.value } toMap - lazy val backendLabelEvents: Map[String, String] = backendLabels.value map { l => s"${CallMetadataKeys.BackendLabels}:${l.key}" -> l.value } toMap - override protected def nonStandardMetadata: Map[String, Any] = { val googleProject = initializationData .workflowPaths @@ -77,6 +74,6 @@ trait PipelinesApiJobCachingActorHelper extends StandardCachingActorHelper { PipelinesApiMetadataKeys.ExecutionBucket -> initializationData.workflowPaths.executionRootString, PipelinesApiMetadataKeys.EndpointUrl -> jesAttributes.endpointUrl, "preemptible" -> preemptible - ) ++ backendLabelEvents ++ originalLabelEvents + ) ++ originalLabelEvents } } diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/PipelinesApiRequestFactory.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/PipelinesApiRequestFactory.scala index 145b9b42a65..14b4f44439d 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/PipelinesApiRequestFactory.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/PipelinesApiRequestFactory.scala @@ -6,7 +6,6 @@ import cromwell.backend.google.pipelines.common._ import cromwell.backend.google.pipelines.common.api.PipelinesApiRequestFactory.CreatePipelineParameters import cromwell.backend.google.pipelines.common.io.PipelinesApiAttachedDisk import cromwell.backend.standard.StandardAsyncJob -import cromwell.core.labels.Labels import cromwell.core.logging.JobLogger import cromwell.core.path.Path import wom.runtime.WomOutputRuntimeExtractor @@ -70,7 +69,7 @@ object PipelinesApiRequestFactory { inputOutputParameters: InputOutputParameters, projectId: String, computeServiceAccount: String, - labels: Labels, + googleLabels: Seq[GoogleLabel], preemptible: Boolean, jobShell: String, privateDockerKeyAndEncryptedToken: Option[CreatePipelineDockerKeyAndToken], diff --git a/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/GoogleLabelsSpec.scala b/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/GoogleLabelsSpec.scala index 3811fe8c7ac..def0c3ecc6f 100644 --- a/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/GoogleLabelsSpec.scala +++ b/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/GoogleLabelsSpec.scala @@ -22,8 +22,8 @@ class GoogleLabelsSpec extends FlatSpec with Matchers { ) googleLabelConversions foreach { case (label: String, conversion: String) => - it should s"not validate the bad label string '$label'" in { - GoogleLabels.validateLabelRegex(label, GoogleLabels.GoogleLabelsRegexPattern.r) match { + it should s"not validate the bad label key '$label'" in { + GoogleLabels.validateLabelRegex(label) match { case Invalid(_) => // Good! case Valid(_) => fail(s"Label validation succeeded but should have failed.") } diff --git a/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/PipelinesApiAsyncBackendJobExecutionActorSpec.scala b/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/PipelinesApiAsyncBackendJobExecutionActorSpec.scala index bf7a8a3cb2b..a201bd8821d 100644 --- a/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/PipelinesApiAsyncBackendJobExecutionActorSpec.scala +++ b/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/PipelinesApiAsyncBackendJobExecutionActorSpec.scala @@ -885,8 +885,6 @@ class PipelinesApiAsyncBackendJobExecutionActorSpec extends TestKitSuite("JesAsy val actual = jesBackend.startMetadataKeyValues.safeMapValues(_.toString) actual should be( Map( - "backendLabels:cromwell-workflow-id" -> s"cromwell-$workflowId", - "backendLabels:wdl-task-name" -> "goodbye", "backendLogs:log" -> s"$jesGcsRoot/wf_hello/$workflowId/call-goodbye/goodbye.log", "callRoot" -> s"$jesGcsRoot/wf_hello/$workflowId/call-goodbye", "jes:endpointUrl" -> "https://genomics.googleapis.com/", diff --git a/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/GenomicsFactory.scala b/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/GenomicsFactory.scala index b5f857fcd55..b0e952533d5 100644 --- a/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/GenomicsFactory.scala +++ b/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/GenomicsFactory.scala @@ -70,7 +70,7 @@ case class GenomicsFactory(applicationName: String, authMode: GoogleAuthMode, en rpargs.setInputs((inputParameters.safeMapValues(_.toGoogleRunParameter) ++ literalInputRunParameters).asJava) rpargs.setOutputs(outputParameters.safeMapValues(_.toGoogleRunParameter).asJava) - rpargs.setLabels(createPipelineParameters.labels.asJavaMap) + rpargs.setLabels(createPipelineParameters.googleLabels.map(label => label.key -> label.value).toMap.asJava) val rpr = new RunPipelineRequest().setEphemeralPipeline(pipeline).setPipelineArgs(rpargs) diff --git a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/GenomicsFactory.scala b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/GenomicsFactory.scala index d0881746821..81f7d4bcb7e 100644 --- a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/GenomicsFactory.scala +++ b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/GenomicsFactory.scala @@ -117,7 +117,7 @@ case class GenomicsFactory(applicationName: String, authMode: GoogleAuthMode, en .setServiceAccount(serviceAccount) .setMachineType(createPipelineParameters.runtimeAttributes |> toMachineType(jobLogger)) .setBootDiskSizeGb(adjustedBootDiskSize) - .setLabels(createPipelineParameters.labels.asJavaMap) + .setLabels(createPipelineParameters.googleLabels.map(label => label.key -> label.value).toMap.asJava) .setNetwork(network) .setAccelerators(accelerators) @@ -139,7 +139,7 @@ case class GenomicsFactory(applicationName: String, authMode: GoogleAuthMode, en val pipelineRequest = new RunPipelineRequest() .setPipeline(pipeline) - .setLabels(createPipelineParameters.labels.asJavaMap) + .setLabels(createPipelineParameters.googleLabels.map(label => label.key -> label.value).toMap.asJava) genomics.pipelines().run(pipelineRequest).buildHttpRequest() } From b07a0b744f778d8954a306e744fc8bbc3e221cd3 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Mon, 22 Apr 2019 12:18:55 -0400 Subject: [PATCH 04/46] Add a test asserting that empty filenames are handled (#4845) --- .../standardTestCases/empty_filename.test | 14 +++++++++++++ .../empty_filename/empty_filename.wdl | 21 +++++++++++++++++++ .../empty_filename/oops.inputs.json | 3 +++ 3 files changed, 38 insertions(+) create mode 100644 centaur/src/main/resources/standardTestCases/empty_filename.test create mode 100644 centaur/src/main/resources/standardTestCases/empty_filename/empty_filename.wdl create mode 100644 centaur/src/main/resources/standardTestCases/empty_filename/oops.inputs.json diff --git a/centaur/src/main/resources/standardTestCases/empty_filename.test b/centaur/src/main/resources/standardTestCases/empty_filename.test new file mode 100644 index 00000000000..842f4c0061c --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/empty_filename.test @@ -0,0 +1,14 @@ +name: empty_filename +testFormat: workflowfailure + + +files { + workflow: empty_filename/empty_filename.wdl + inputs: empty_filename/oops.inputs.json +} + +metadata { + status: Failed + "failures.0.message": "Workflow input processing failed" + "failures.0.causedBy.0.message": "Invalid value for File input 'file': empty value" +} diff --git a/centaur/src/main/resources/standardTestCases/empty_filename/empty_filename.wdl b/centaur/src/main/resources/standardTestCases/empty_filename/empty_filename.wdl new file mode 100644 index 00000000000..15f2ea0515c --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/empty_filename/empty_filename.wdl @@ -0,0 +1,21 @@ +version 1.0 + +workflow empty_filename { + input { + File file + } + + call use_file { input: file = file } +} + +task use_file { + input { + File file + } + command <<< + cat ~{file} + >>> + output { + String content = read_string(stdout()) + } +} diff --git a/centaur/src/main/resources/standardTestCases/empty_filename/oops.inputs.json b/centaur/src/main/resources/standardTestCases/empty_filename/oops.inputs.json new file mode 100644 index 00000000000..7f075d61962 --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/empty_filename/oops.inputs.json @@ -0,0 +1,3 @@ +{ + "empty_filename.file": "" +} From ac52ba999eb5f8c72b3eed26c2de0de1b5de5944 Mon Sep 17 00:00:00 2001 From: Adam Nichols Date: Mon, 22 Apr 2019 14:20:42 -0400 Subject: [PATCH 05/46] [develop] Fix AWS "filename too long" (#4858) --- .../src/main/java/org/lerch/s3fs/S3Channel.java | 17 +++++++++++++++++ .../main/java/org/lerch/s3fs/S3FileChannel.java | 4 ++-- .../org/lerch/s3fs/S3SeekableByteChannel.java | 4 ++-- src/ci/bin/testCentaurAws.sh | 1 - 4 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 filesystems/s3/src/main/java/org/lerch/s3fs/S3Channel.java diff --git a/filesystems/s3/src/main/java/org/lerch/s3fs/S3Channel.java b/filesystems/s3/src/main/java/org/lerch/s3fs/S3Channel.java new file mode 100644 index 00000000000..7276c5dacd7 --- /dev/null +++ b/filesystems/s3/src/main/java/org/lerch/s3fs/S3Channel.java @@ -0,0 +1,17 @@ +package org.lerch.s3fs; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public interface S3Channel { + /** + * Create a temporary place for the S3 file on the local filesystem, with the s3 filename mixed in + * e.g. "/tmp/script-tempSuffix" + * @param path S3 path to create a file for + * @return Path on the local filesystem to the temporary file that was created + */ + default Path createTempFile(S3Path path) throws IOException { + return Files.createTempFile(path.getFileName().toString(), ""); + } +} diff --git a/filesystems/s3/src/main/java/org/lerch/s3fs/S3FileChannel.java b/filesystems/s3/src/main/java/org/lerch/s3fs/S3FileChannel.java index 7c74aeebaa9..c9c39999bb9 100644 --- a/filesystems/s3/src/main/java/org/lerch/s3fs/S3FileChannel.java +++ b/filesystems/s3/src/main/java/org/lerch/s3fs/S3FileChannel.java @@ -23,7 +23,7 @@ import static java.lang.String.format; -public class S3FileChannel extends FileChannel { +public class S3FileChannel extends FileChannel implements S3Channel { private S3Path path; private Set options; @@ -42,7 +42,7 @@ else if (!exists && !this.options.contains(StandardOpenOption.CREATE_NEW) && !this.options.contains(StandardOpenOption.CREATE)) throw new NoSuchFileException(format("target not exists: %s", path)); - tempFile = Files.createTempFile("temp-s3-", key.replaceAll("/", "_")); + tempFile = createTempFile(path); boolean removeTempFile = true; try { if (exists) { diff --git a/filesystems/s3/src/main/java/org/lerch/s3fs/S3SeekableByteChannel.java b/filesystems/s3/src/main/java/org/lerch/s3fs/S3SeekableByteChannel.java index 952de8e494b..f5de0aba192 100644 --- a/filesystems/s3/src/main/java/org/lerch/s3fs/S3SeekableByteChannel.java +++ b/filesystems/s3/src/main/java/org/lerch/s3fs/S3SeekableByteChannel.java @@ -23,7 +23,7 @@ import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.S3Object; -public class S3SeekableByteChannel implements SeekableByteChannel { +public class S3SeekableByteChannel implements SeekableByteChannel, S3Channel { private S3Path path; private Set options; @@ -49,7 +49,7 @@ else if (!exists && !this.options.contains(StandardOpenOption.CREATE_NEW) && !this.options.contains(StandardOpenOption.CREATE)) throw new NoSuchFileException(format("target not exists: %s", path)); - tempFile = Files.createTempFile("temp-s3-", key.replaceAll("/", "_")); + tempFile = createTempFile(path); boolean removeTempFile = true; try { if (exists) { diff --git a/src/ci/bin/testCentaurAws.sh b/src/ci/bin/testCentaurAws.sh index 876461de02d..e29dac21c7c 100755 --- a/src/ci/bin/testCentaurAws.sh +++ b/src/ci/bin/testCentaurAws.sh @@ -48,7 +48,6 @@ cromwell::build::run_centaur \ -e draft3_optional_input_from_scatter \ -e iwdr_input_string \ -e globbingindex \ - -e file_name_too_long \ -e cwl_cache_between_workflows \ -e abort.scheduled_abort \ -e cwl_cache_within_workflow \ From 4db113ca8dc5449c804cf9a6792f09ca2667c82a Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Mon, 22 Apr 2019 14:22:09 -0400 Subject: [PATCH 06/46] Fix (#4853) --- .../main/scala/cromwell/backend/impl/aws/AwsBatchJob.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/supportedBackends/aws/src/main/scala/cromwell/backend/impl/aws/AwsBatchJob.scala b/supportedBackends/aws/src/main/scala/cromwell/backend/impl/aws/AwsBatchJob.scala index 854f2d3ed7b..66dae71feaa 100644 --- a/supportedBackends/aws/src/main/scala/cromwell/backend/impl/aws/AwsBatchJob.scala +++ b/supportedBackends/aws/src/main/scala/cromwell/backend/impl/aws/AwsBatchJob.scala @@ -37,6 +37,7 @@ import cats.data.Kleisli._ import cats.data.ReaderT._ import cats.implicits._ +import cromwell.core.ExecutionIndex._ import scala.language.higherKinds import cats.effect.{Async, Timer} import software.amazon.awssdk.services.batch.BatchClient @@ -131,9 +132,9 @@ final case class AwsBatchJob(jobDescriptor: BackendJobDescriptor, // WDL/CWL val taskId = jobDescriptor.key.call.fullyQualifiedName + "-" + jobDescriptor.key.index + "-" + jobDescriptor.key.attempt val workflow = jobDescriptor.workflowDescriptor val uniquePath = workflow.callable.name + "/" + - jobDescriptor.taskCall.callable.name + "/" + + jobDescriptor.taskCall.localName + "/" + workflow.id + "/" + - jobDescriptor.key.index + "/" + + jobDescriptor.key.index.fromIndex + "/" + jobDescriptor.key.attempt Log.info(s"""Submitting job to AWS Batch""") Log.info(s"""dockerImage: ${runtimeAttributes.dockerImage}""") @@ -158,7 +159,7 @@ final case class AwsBatchJob(jobDescriptor: BackendJobDescriptor, // WDL/CWL }).compile.last.map(_.get)) //if successful there is guaranteed to be a value emitted, hence we can .get this option } - (createDefinition[F](s"""${workflow.callable.name}-${jobDescriptor.taskCall.callable.name}""", uniquePath) product Kleisli.ask[F, AwsBatchAttributes]). + (createDefinition[F](s"""${workflow.callable.name}-${jobDescriptor.taskCall.localName}""", uniquePath) product Kleisli.ask[F, AwsBatchAttributes]). flatMap((callClient _).tupled) } From bfc47bbaa72df22ee5e831642c39520c8ae2504e Mon Sep 17 00:00:00 2001 From: Dan Tenenbaum Date: Mon, 22 Apr 2019 11:22:50 -0700 Subject: [PATCH 07/46] Fix syntax of docker build command (#4868) --- docs/developers/Building.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developers/Building.md b/docs/developers/Building.md index 6a7bb2d6530..6143f9e18b7 100644 --- a/docs/developers/Building.md +++ b/docs/developers/Building.md @@ -11,7 +11,7 @@ features or fixes, the following are required to build Cromwell from source: You can also use the [development image](https://github.com/broadinstitute/cromwell/tree/develop/scripts/docker-develop), and build a development container to work inside: ```bash -$ docker build -t cromwell-dev Dockerfile +$ docker build -t cromwell-dev . $ docker run -it cromwell-dev bash ``` From 0ce4f55bd9cccb33c23e38f4a93289bffd0522aa Mon Sep 17 00:00:00 2001 From: Ruchi Date: Mon, 22 Apr 2019 21:54:17 -0400 Subject: [PATCH 08/46] docker compatibility for AWS Batch (#4865) --- docs/cromwell_features/CallCaching.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/cromwell_features/CallCaching.md b/docs/cromwell_features/CallCaching.md index 8f348ff8d63..d65f1ffec41 100644 --- a/docs/cromwell_features/CallCaching.md +++ b/docs/cromwell_features/CallCaching.md @@ -123,11 +123,12 @@ Cromwell provides two methods to lookup a Docker hash from a Docker tag: Docker registry and access levels supported by Cromwell for docker digest lookup in "remote" mode: - | | DockerHub || GCR || - |:-----:|:---------:|:-------:|:------:|:-------:| - | | Public | Private | Public | Private | - | Pipelines API | X | X | X | X | - | Other | X | | X | | + | | DockerHub || GCR || ECR || + |:-----:|:---------:|:-------:|:------:|:-------:|:------:|:-------:| + | | Public | Private | Public | Private | Public | Private | + | Pipelines API | X | X | X | X | | | + | AWS Batch | X | | | | | | + | Other | X | | X | | | | **Runtime Attributes** From 6a0fb75fb709754d01bc56d859593ba91294d940 Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Tue, 23 Apr 2019 17:15:27 +0200 Subject: [PATCH 09/46] More explanation in the centaur docs (#4827) --- docs/developers/Building.md | 3 +++ docs/developers/Centaur.md | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/docs/developers/Building.md b/docs/developers/Building.md index 6143f9e18b7..b9b44e10840 100644 --- a/docs/developers/Building.md +++ b/docs/developers/Building.md @@ -35,6 +35,9 @@ Finally build the Cromwell jar: $ sbt assembly ``` +NOTE: This command will run for a long time the first time. +NOTE: Compiling will not succeed on directories encrypted with ecryptfs (ubuntu encrypted home dirs for example), due to long file paths. + `sbt assembly` will build the runnable Cromwell JAR in `server/target/scala-2.12/` with a name like `cromwell-.jar`. To build a [Docker](https://www.docker.com/) image, run: diff --git a/docs/developers/Centaur.md b/docs/developers/Centaur.md index 5d7a3539d34..422b2bf96e2 100644 --- a/docs/developers/Centaur.md +++ b/docs/developers/Centaur.md @@ -2,7 +2,13 @@ Centaur is an integration testing suite for the [Cromwell](http://github.com/bro ## Prerequisites -Centaur expects to find a Cromwell server properly configured and running in server mode, listening on port 8000. This can be configured by modifying the `cromwellUrl` parameter in `application.conf`. +Centaur expects to find a Cromwell server properly configured and running in server mode, listening on port 8000. +This can be configured by modifying the `cromwellUrl` parameter in `application.conf`. + +You can get a build of your current cromwell code with [these instructions](Building.md). +The server can be run with `java -jar server`, checkout [this page](../CommandLine.md) +for more detailed instructions. +You can now run the tests from another terminal. ## Running @@ -46,7 +52,7 @@ centaur { Each test case file is a HOCON file with the following structure: ``` name: NAME // Required: Name of the test -testFormat: TESTFORMAT // Required: One of WorkflowSuccessTest, WorkflowFailureTest +testFormat: TESTFORMAT // Required: One of WorkflowSuccessTest, WorkflowFailureTest, runtwiceexpectingcallcaching backends: [BACKENDNAME1, BACKENDNAME2, ...] // Optional list of backends. If supplied, this test will be ignored if these backends are not supported by the Cromwell server basePath: /an/optional/field // Optional, location for the files {} entries to be found relative to tags: [ "any", "custom", "tags" ] // Optional, a set of custom tags to apply to this test @@ -62,6 +68,17 @@ files { metadata { fully.qualified.key.name1: VALUE1 fully.qualified.key.name2: VALUE2 + // Examples: + // failures is a list, the first entry (0) might be the error you are looking for. If multiple errors are expected the entire list can be checked. + // It has a "message" and a "causedBy" field. + "failures.0.message": "Cromwell senses you did not use WomTool validate." + "failures.0.causedBy": "BetweenKeyboardAndChairException" +} + +filesystemcheck: "local" // possible values: "local", "gcs". Used in conjunction with outputExpectations to define files we expect to exist after running this workflow. +outputExpectations: { + "/path/to/my/output/file1": 1 + "/path/to/file/that/should/not/exist": 0 } ``` @@ -74,5 +91,16 @@ The `testFormat` field can be one of the following, case insensitive: * `workflowfailure`: The workflow being supplied is expected to fail The `metadata` is optional. If supplied, Centaur will retrieve the metadata from the successfully completed workflow and compare the values retrieved to those supplied. At the moment the only fields supported are strings, numbers and booleans. -For any metadata values which require workflow ID (i.e, file paths), use <\> as a placeholder instead. For example: -* "calls.hello.hello.stdout": "gs://google-project/jes/root/wdl/<\>/call-task/task-stdout.log" \ No newline at end of file + +You can find which metadata is recorded by running a workflow ```java -jar run -m metadata.json my_workflow.wdl```. +This will save the metadata in `metadata.json`. + +For any metadata values or outputExpectations which require workflow ID (i.e, file paths), use `<>` as a placeholder instead. For example: +* `"calls.hello.hello.stdout": "gs://google-project/jes/root/wdl/<>/call-task/task-stdout.log"` + +In case the absolute path the cromwell root is used (for example: `/home/my_user/projects/cromwell/cromwell-executions`) + you can use `<>` as a replacement. +* `"calls.hello.hello.exit_code": "<>/call-hello/execution/exit_code"` + +In case testing of the caching is required `<>` can be used. +The testFormat should be `runtwiceexpectingcallcaching`. From ae3d37d0b5857a4a0d1c96edcbe840aa7b246eb8 Mon Sep 17 00:00:00 2001 From: mcovarr Date: Tue, 23 Apr 2019 18:24:07 -0400 Subject: [PATCH 10/46] Mlc fix horicromtal engine upgrade (#4883) Fix horizontal engine upgrade test. --- .../docker-compose-horicromtal.yml | 2 ++ .../bin/testCentaurHoricromtalEngineUpgradePapiV2.sh | 9 +++++++-- src/ci/bin/testCentaurHoricromtalPapiV2.sh | 2 +- src/ci/resources/horicromtal_application.conf | 10 ---------- 4 files changed, 10 insertions(+), 13 deletions(-) delete mode 100644 src/ci/resources/horicromtal_application.conf diff --git a/scripts/docker-compose-mysql/docker-compose-horicromtal.yml b/scripts/docker-compose-mysql/docker-compose-horicromtal.yml index c526ce76d25..8a2992e57ef 100644 --- a/scripts/docker-compose-mysql/docker-compose-horicromtal.yml +++ b/scripts/docker-compose-mysql/docker-compose-horicromtal.yml @@ -24,6 +24,7 @@ services: environment: - JAVA_OPTS=-Dconfig.file=${CROMWELL_BUILD_RESOURCES_DIRECTORY}/${CROMWELL_CONFIG} -Dwebservice.port=8000 -Dsystem.max-workflow-launch-count=1 -Dsystem.new-workflow-poll-rate=10 -Dsystem.max-concurrent-workflows=30 - CROMWELL_BUILD_RESOURCES_DIRECTORY=${CROMWELL_BUILD_ROOT_DIRECTORY}/target/ci/resources + - CROMWELL_BUILD_MYSQL_USERNAME=${CROMWELL_BUILD_MYSQL_USERNAME} healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000"] interval: 2s @@ -43,3 +44,4 @@ services: environment: - JAVA_OPTS=-Dconfig.file=${CROMWELL_BUILD_RESOURCES_DIRECTORY}/${CROMWELL_CONFIG} -Dwebservice.port=8008 -Dsystem.max-workflow-launch-count=1 -Dsystem.new-workflow-poll-rate=10 -Dsystem.max-concurrent-workflows=30 -Dservices.MetadataService.config.metadata-summary-refresh-interval=Inf - CROMWELL_BUILD_RESOURCES_DIRECTORY=${CROMWELL_BUILD_ROOT_DIRECTORY}/target/ci/resources + - CROMWELL_BUILD_MYSQL_USERNAME=${CROMWELL_BUILD_MYSQL_USERNAME} diff --git a/src/ci/bin/testCentaurHoricromtalEngineUpgradePapiV2.sh b/src/ci/bin/testCentaurHoricromtalEngineUpgradePapiV2.sh index 48c23653bdd..d21d2c2c992 100755 --- a/src/ci/bin/testCentaurHoricromtalEngineUpgradePapiV2.sh +++ b/src/ci/bin/testCentaurHoricromtalEngineUpgradePapiV2.sh @@ -11,11 +11,16 @@ if [ "${CROMWELL_BUILD_PROVIDER}" = "${CROMWELL_BUILD_PROVIDER_TRAVIS}" ] && [ - prior_version=$(cromwell::private::calculate_prior_version_tag) export TEST_CROMWELL_PRIOR_VERSION_TAG="${prior_version}" - export TEST_CROMWELL_PRIOR_VERSION_CONF="papi_v2_${prior_version}_application.conf" + WOULD_BE_PRIOR_VERSION_CONF="papi_v2_${prior_version}_application.conf" + if [[ -f "$CROMWELL_BUILD_RESOURCES_DIRECTORY/$WOULD_BE_PRIOR_VERSION_CONF" ]]; then + export TEST_CROMWELL_PRIOR_VERSION_CONF="$WOULD_BE_PRIOR_VERSION_CONF" + else + export TEST_CROMWELL_PRIOR_VERSION_CONF="papi_v2_application.conf" + fi # This is the Docker tag that will be applied to the Docker image that is created for the code being built. This image # will *not* be pushed to Docker Hub or any other repo, it only lives local to the build. export TEST_CROMWELL_TAG=just-testing-horicromtal - export TEST_CROMWELL_CONF=horicromtal_application.conf + export TEST_CROMWELL_CONF="papi_v2_application.conf" export CROMWELL_BUILD_MYSQL_USERNAME=travis cromwell::build::setup_centaur_environment diff --git a/src/ci/bin/testCentaurHoricromtalPapiV2.sh b/src/ci/bin/testCentaurHoricromtalPapiV2.sh index e8f53b322d0..256a1711dc7 100755 --- a/src/ci/bin/testCentaurHoricromtalPapiV2.sh +++ b/src/ci/bin/testCentaurHoricromtalPapiV2.sh @@ -9,7 +9,7 @@ source "${BASH_SOURCE%/*}/test.inc.sh" || source test.inc.sh # Setting these variables should cause the associated config values to be rendered into centaur_application_horicromtal.conf # There should probably be more indirections in CI scripts but that can wait. export TEST_CROMWELL_TAG=just-testing-horicromtal -export TEST_CROMWELL_CONF=horicromtal_application.conf +export TEST_CROMWELL_CONF=papi_v2_application.conf export CROMWELL_BUILD_MYSQL_USERNAME=travis cromwell::build::setup_common_environment diff --git a/src/ci/resources/horicromtal_application.conf b/src/ci/resources/horicromtal_application.conf deleted file mode 100644 index c8c0c880589..00000000000 --- a/src/ci/resources/horicromtal_application.conf +++ /dev/null @@ -1,10 +0,0 @@ -include required("papi_v2_application.conf") - -database { - db.url = "jdbc:mysql://localhost:3306/cromwell_test?useSSL=false&rewriteBatchedStatements=true" - db.user = "travis" - db.password = "" - db.driver = "com.mysql.cj.jdbc.Driver" - profile = "slick.jdbc.MySQLProfile$" - db.connectionTimeout = 15000 -} From edf5edcbfef19485f5f703d7710871f9b2eecf6a Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Tue, 23 Apr 2019 21:05:33 -0400 Subject: [PATCH 11/46] Don't pass on Sam Conflict --- .../main/scala/cromiam/sam/SamClient.scala | 10 +- .../scala/cromiam/sam/SamClientSpec.scala | 145 +++++++++++++----- .../cromiam/webservice/MockClients.scala | 86 ++++++----- .../cromiam/webservice/QuerySupportSpec.scala | 5 +- .../webservice/SubmissionSupportSpec.scala | 15 +- 5 files changed, 175 insertions(+), 86 deletions(-) diff --git a/CromIAM/src/main/scala/cromiam/sam/SamClient.scala b/CromIAM/src/main/scala/cromiam/sam/SamClient.scala index 1317fafd385..7593a82012d 100644 --- a/CromIAM/src/main/scala/cromiam/sam/SamClient.scala +++ b/CromIAM/src/main/scala/cromiam/sam/SamClient.scala @@ -136,15 +136,17 @@ class SamClient(scheme: String, val createCollection = registerCreation(user, collection, cromIamRequest) createCollection flatMap { - case r if r.status == StatusCodes.Conflict => requestAuth(CollectionAuthorizationRequest(user, collection, "add"), cromIamRequest) case r if r.status == StatusCodes.NoContent => Monad[FailureResponseOrT].unit case r => FailureResponseOrT[IO, HttpResponse, Unit](IO.raiseError(SamRegisterCollectionException(r.status))) + } recoverWith { + case r if r.status == StatusCodes.Conflict => requestAuth(CollectionAuthorizationRequest(user, collection, "add"), cromIamRequest) + case r => FailureResponseOrT[IO, HttpResponse, Unit](IO.raiseError(SamRegisterCollectionException(r.status))) } } - private def registerCreation(user: User, - collection: Collection, - cromIamRequest: HttpRequest): FailureResponseOrT[HttpResponse] = { + protected def registerCreation(user: User, + collection: Collection, + cromIamRequest: HttpRequest): FailureResponseOrT[HttpResponse] = { val request = HttpRequest(method = HttpMethods.POST, uri = samRegisterUri(collection), headers = List[HttpHeader](user.authorization)) instrumentRequest( diff --git a/CromIAM/src/test/scala/cromiam/sam/SamClientSpec.scala b/CromIAM/src/test/scala/cromiam/sam/SamClientSpec.scala index d8ea828a98a..50c6963d4c2 100644 --- a/CromIAM/src/test/scala/cromiam/sam/SamClientSpec.scala +++ b/CromIAM/src/test/scala/cromiam/sam/SamClientSpec.scala @@ -4,37 +4,40 @@ import akka.actor.ActorSystem import akka.http.scaladsl.model.headers.{Authorization, OAuth2BearerToken} import akka.http.scaladsl.model.{HttpEntity, HttpRequest, HttpResponse, StatusCodes} import akka.stream.ActorMaterializer +import cats.Monad import cromiam.auth.{Collection, User} import cromiam.sam.SamClient.{CollectionAuthorizationRequest, SamDenialException, SamRegisterCollectionException} -import cromiam.webservice.MockSamClient +import cromiam.webservice.MockSamClient._ +import cromiam.webservice._ +import cromwell.api.CromwellClient.UnsuccessfulRequestException import cromwell.api.model._ import org.broadinstitute.dsde.workbench.model.WorkbenchUserId -import org.scalatest.EitherValues._ import org.scalatest.{AsyncFlatSpec, BeforeAndAfterAll, Matchers} +import org.specs2.mock.Mockito import scala.concurrent.ExecutionContextExecutor -class SamClientSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll { +class SamClientSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll with Mockito { implicit val actorSystem: ActorSystem = ActorSystem("SamClientSpec") implicit val ece: ExecutionContextExecutor = actorSystem.dispatcher implicit val materializer: ActorMaterializer = ActorMaterializer() - val samClient = new MockSamClient() - val samClientNoWhitelist = new MockSamClient(checkSubmitWhitelist = false) - val expectedErrorMessage = "expected error" - val expectedErrorResponse = HttpResponse(StatusCodes.InternalServerError, entity = HttpEntity(expectedErrorMessage)) - val samClientWithError = new MockSamClient(samResponseOption = Option(expectedErrorResponse)) + private val expectedErrorResponse = + HttpResponse(StatusCodes.InternalServerError, entity = HttpEntity("expected error")) val authorization = Authorization(OAuth2BearerToken("my-token")) - val authorizedUserWithCollection: User = User(WorkbenchUserId(samClient.authorizedUserCollectionStr), authorization) - val unauthorizedUserWithNoCollection: User = User(WorkbenchUserId(samClient.unauthorizedUserCollectionStr), authorization) - val notWhitelistedUser: User = User(WorkbenchUserId(samClient.notWhitelistedUser), authorization) - - val authorizedCollection: Collection = Collection(samClient.authorizedUserCollectionStr) - val unauthorizedCollection: Collection = Collection(samClient.unauthorizedUserCollectionStr) - val authorizedCollectionRequest: CollectionAuthorizationRequest = CollectionAuthorizationRequest(authorizedUserWithCollection, authorizedCollection, "add") - val unauthorizedCollectionRequest: CollectionAuthorizationRequest = CollectionAuthorizationRequest(unauthorizedUserWithNoCollection, unauthorizedCollection, "add") + val authorizedUserWithCollection = User(WorkbenchUserId(MockSamClient.AuthorizedUserCollectionStr), authorization) + val unauthorizedUserWithNoCollection = + User(WorkbenchUserId(MockSamClient.UnauthorizedUserCollectionStr), authorization) + val notWhitelistedUser = User(WorkbenchUserId(MockSamClient.NotWhitelistedUser), authorization) + + val authorizedCollection = Collection(MockSamClient.AuthorizedUserCollectionStr) + val unauthorizedCollection = Collection(MockSamClient.UnauthorizedUserCollectionStr) + val authorizedCollectionRequest = + CollectionAuthorizationRequest(authorizedUserWithCollection, authorizedCollection, "add") + val unauthorizedCollectionRequest = + CollectionAuthorizationRequest(unauthorizedUserWithNoCollection, unauthorizedCollection, "add") val emptyHttpRequest: HttpRequest = HttpRequest() @@ -46,28 +49,37 @@ class SamClientSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll behavior of "SamClient" it should "return true if user is whitelisted" in { + val samClient = new MockSamClient() samClient.isSubmitWhitelisted(authorizedUserWithCollection, emptyHttpRequest).map(v => assert(v)) .asIo.unsafeToFuture() } it should "return false if user is not whitelisted" in { + val samClient = new MockSamClient() samClient.isSubmitWhitelisted(notWhitelistedUser, emptyHttpRequest).map(v => assert(!v)) .asIo.unsafeToFuture() } it should "return sam errors while checking is whitelisted" in { - samClientWithError.isSubmitWhitelisted(notWhitelistedUser, emptyHttpRequest).value.unsafeToFuture() map { - _.left.value should be(expectedErrorResponse) + val samClient = new MockSamClient() { + override def isSubmitWhitelistedSam(user: User, cromiamRequest: HttpRequest): FailureResponseOrT[Boolean] = { + MockSamClient.returnResponse(expectedErrorResponse) + } + } + samClient.isSubmitWhitelisted(notWhitelistedUser, emptyHttpRequest).value.unsafeToFuture() map { + _ should be(Left(expectedErrorResponse)) } } it should "eventually return the collection(s) of user" in { + val samClient = new MockSamClient() samClient.collectionsForUser(authorizedUserWithCollection, emptyHttpRequest).map(collectionList => - assert(collectionList == samClient.userCollectionList) + assert(collectionList == MockSamClient.UserCollectionList) ).asIo.unsafeToFuture() } it should "fail if user doesn't have any collections" in { + val samClient = new MockSamClient() recoverToExceptionIf[Exception] { samClient.collectionsForUser(unauthorizedUserWithNoCollection, emptyHttpRequest) .asIo.unsafeToFuture() @@ -76,19 +88,14 @@ class SamClientSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll ) } - it should "return sam errors while checking collections" in { - samClientWithError.collectionsForUser(unauthorizedUserWithNoCollection, emptyHttpRequest) - .value.unsafeToFuture() map { - _.left.value should be(expectedErrorResponse) - } - } - it should "return true if user is authorized to perform action on collection" in { + val samClient = new MockSamClient() samClient.requestAuth(authorizedCollectionRequest, emptyHttpRequest).map(_ => succeed) .asIo.unsafeToFuture() } it should "throw SamDenialException if user is not authorized to perform action on collection" in { + val samClient = new MockSamClient() recoverToExceptionIf[SamDenialException] { samClient.requestAuth(unauthorizedCollectionRequest, emptyHttpRequest) .asIo.unsafeToFuture() @@ -97,18 +104,14 @@ class SamClientSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll } } - it should "return sam errors while checking if user is authorized" in { - samClientWithError.requestAuth(authorizedCollectionRequest, emptyHttpRequest).value.unsafeToFuture() map { - _.left.value should be(expectedErrorResponse) - } - } - it should "register collection to Sam if user has authorization to create/add to collection" in { + val samClient = new MockSamClient() samClient.requestSubmission(authorizedUserWithCollection, authorizedCollection, emptyHttpRequest).map(_ => succeed) .asIo.unsafeToFuture() } it should "throw SamRegisterCollectionException if user doesn't have authorization to create/add to collection" in { + val samClient = new MockSamClient() recoverToExceptionIf[SamRegisterCollectionException] { samClient.requestSubmission(unauthorizedUserWithNoCollection, unauthorizedCollection, emptyHttpRequest).map(_ => succeed) .asIo.unsafeToFuture() @@ -117,10 +120,80 @@ class SamClientSpec extends AsyncFlatSpec with Matchers with BeforeAndAfterAll } } - it should "return sam errors while checking user authorization" in { - samClientWithError.requestSubmission(unauthorizedUserWithNoCollection, unauthorizedCollection, emptyHttpRequest) - .value.unsafeToFuture() map { - _.left.value should be(expectedErrorResponse) + it should "add the user when create returns a conflict" in { + val samClient = new BaseMockSamClient() { + override protected def registerCreation(user: User, + collection: Collection, + cromiamRequest: HttpRequest): FailureResponseOrT[HttpResponse] = { + val conflictResponse = HttpResponse(StatusCodes.Conflict, entity = HttpEntity("expected conflict")) + returnResponse(conflictResponse) + } + + override def requestAuth(authorizationRequest: CollectionAuthorizationRequest, + cromiamRequest: HttpRequest): FailureResponseOrT[Unit] = { + Monad[FailureResponseOrT].unit + } + } + samClient + .requestSubmission(unauthorizedUserWithNoCollection, unauthorizedCollection, emptyHttpRequest) + .map(_ => succeed) + .asIo + .unsafeToFuture() + } + + it should "fail to add the user when create returns a conflict but then adding returns an error" in { + val samClient = new BaseMockSamClient() { + override protected def registerCreation(user: User, + collection: Collection, + cromiamRequest: HttpRequest): FailureResponseOrT[HttpResponse] = { + val conflictResponse = HttpResponse(StatusCodes.Conflict, entity = HttpEntity("expected conflict")) + returnResponse(conflictResponse) + } + + override def requestAuth(authorizationRequest: CollectionAuthorizationRequest, + cromiamRequest: HttpRequest): FailureResponseOrT[Unit] = { + returnResponse(expectedErrorResponse) + } + } + recoverToExceptionIf[UnsuccessfulRequestException] { + samClient.requestSubmission(unauthorizedUserWithNoCollection, unauthorizedCollection, emptyHttpRequest) + .asIo.unsafeToFuture() + } map { exception => + assert(exception.getMessage == "expected error") + } + } + + it should "fail to add the user when create returns an unexpected successful response" in { + val samClient = new BaseMockSamClient() { + override protected def registerCreation(user: User, + collection: Collection, + cromiamRequest: HttpRequest): FailureResponseOrT[HttpResponse] = { + val unexpectedOkResponse = HttpResponse(StatusCodes.OK, entity = HttpEntity("elided ok message")) + returnResponse(unexpectedOkResponse) + } + } + recoverToExceptionIf[SamRegisterCollectionException] { + samClient.requestSubmission(unauthorizedUserWithNoCollection, unauthorizedCollection, emptyHttpRequest) + .asIo.unsafeToFuture() + } map { exception => + exception.getMessage should be("Can't register collection with Sam. Status code: 200 OK") + } + } + + it should "fail to add the user when create returns an unexpected failure response" in { + val samClient = new BaseMockSamClient() { + override protected def registerCreation(user: User, + collection: Collection, + cromiamRequest: HttpRequest): FailureResponseOrT[HttpResponse] = { + val unexpectedFailureResponse = HttpResponse(StatusCodes.ImATeapot, entity = HttpEntity("elided error message")) + returnResponse(unexpectedFailureResponse) + } + } + recoverToExceptionIf[SamRegisterCollectionException] { + samClient.requestSubmission(unauthorizedUserWithNoCollection, unauthorizedCollection, emptyHttpRequest) + .asIo.unsafeToFuture() + } map { exception => + exception.getMessage should be("Can't register collection with Sam. Status code: 418 I'm a teapot") } } } diff --git a/CromIAM/src/test/scala/cromiam/webservice/MockClients.scala b/CromIAM/src/test/scala/cromiam/webservice/MockClients.scala index 6bb5f65a247..3fb9689e5cc 100644 --- a/CromIAM/src/test/scala/cromiam/webservice/MockClients.scala +++ b/CromIAM/src/test/scala/cromiam/webservice/MockClients.scala @@ -5,11 +5,13 @@ import akka.event.NoLogging import akka.http.scaladsl.model.StatusCodes._ import akka.http.scaladsl.model.{HttpRequest, HttpResponse, StatusCodes} import akka.stream.ActorMaterializer +import cats.Monad import cats.effect.IO import cromiam.auth.{Collection, User} import cromiam.cromwell.CromwellClient import cromiam.sam.SamClient import cromiam.sam.SamClient.{CollectionAuthorizationRequest, SamDenialException, SamRegisterCollectionException} +import cromiam.webservice.MockSamClient._ import cromwell.api.model._ import scala.concurrent.ExecutionContextExecutor @@ -88,11 +90,13 @@ class MockCromwellClient()(implicit system: ActorSystem, } } -class MockSamClient(checkSubmitWhitelist: Boolean = true, - samResponseOption: Option[HttpResponse] = None) +/** + * Overrides some values, but doesn't override methods. + */ +class BaseMockSamClient(checkSubmitWhitelist: Boolean = true) (implicit system: ActorSystem, - ece: ExecutionContextExecutor, - materializer: ActorMaterializer) + ece: ExecutionContextExecutor, + materializer: ActorMaterializer) extends SamClient( "http", "bar", @@ -100,61 +104,59 @@ class MockSamClient(checkSubmitWhitelist: Boolean = true, checkSubmitWhitelist, NoLogging, ActorRef.noSender - )(system, ece, materializer) { - - val authorizedUserCollectionStr: String = "123456789" - val unauthorizedUserCollectionStr: String = "987654321" - - val notWhitelistedUser: String = "ABC123" + )(system, ece, materializer) - val userCollectionList: List[Collection] = List(Collection("col1"), Collection("col2")) +/** + * Extends the base mock client with overriden methods. + */ +class MockSamClient(checkSubmitWhitelist: Boolean = true) + (implicit system: ActorSystem, + ece: ExecutionContextExecutor, + materializer: ActorMaterializer) + extends BaseMockSamClient(checkSubmitWhitelist) { override def collectionsForUser(user: User, httpRequest: HttpRequest): FailureResponseOrT[List[Collection]] = { - samResponseOption match { - case Some(samResponse) => FailureResponseOrT.left(IO.pure(samResponse)) - case None => - val userId = user.userId.value - if (userId.equalsIgnoreCase(unauthorizedUserCollectionStr)) { - val exception = new Exception(s"Unable to look up collections for user $userId!") - FailureResponseOrT.left(IO.raiseError[HttpResponse](exception)) - } else { - FailureResponseOrT.pure(userCollectionList) - } + val userId = user.userId.value + if (userId.equalsIgnoreCase(UnauthorizedUserCollectionStr)) { + val exception = new Exception(s"Unable to look up collections for user $userId!") + FailureResponseOrT.left(IO.raiseError[HttpResponse](exception)) + } else { + FailureResponseOrT.pure(UserCollectionList) } } override def requestSubmission(user: User, collection: Collection, cromIamRequest: HttpRequest): FailureResponseOrT[Unit] = { - samResponseOption match { - case Some(samResponse) => FailureResponseOrT.left(IO.pure(samResponse)) - case None => - collection match { - case c if c.name.equalsIgnoreCase(unauthorizedUserCollectionStr) => - val exception = SamRegisterCollectionException(StatusCodes.BadRequest) - FailureResponseOrT.left(IO.raiseError[HttpResponse](exception)) - case c if c.name.equalsIgnoreCase(authorizedUserCollectionStr) => FailureResponseOrT.pure(()) - case _ => FailureResponseOrT.pure(()) - } + collection match { + case c if c.name.equalsIgnoreCase(UnauthorizedUserCollectionStr) => + val exception = SamRegisterCollectionException(StatusCodes.BadRequest) + FailureResponseOrT.left(IO.raiseError[HttpResponse](exception)) + case c if c.name.equalsIgnoreCase(AuthorizedUserCollectionStr) => Monad[FailureResponseOrT].unit + case _ => Monad[FailureResponseOrT].unit } } override def isSubmitWhitelistedSam(user: User, cromIamRequest: HttpRequest): FailureResponseOrT[Boolean] = { - samResponseOption match { - case Some(samResponse) => FailureResponseOrT.left(IO.pure(samResponse)) - case None => FailureResponseOrT.pure(!user.userId.value.equalsIgnoreCase(notWhitelistedUser)) - } + FailureResponseOrT.pure(!user.userId.value.equalsIgnoreCase(NotWhitelistedUser)) } override def requestAuth(authorizationRequest: CollectionAuthorizationRequest, cromIamRequest: HttpRequest): FailureResponseOrT[Unit] = { - samResponseOption match { - case Some(samResponse) => FailureResponseOrT.left(IO(samResponse)) - case None => - authorizationRequest.user.userId.value match { - case `authorizedUserCollectionStr` => FailureResponseOrT.pure(()) - case _ => FailureResponseOrT.left(IO.raiseError[HttpResponse](new SamDenialException)) - } + authorizationRequest.user.userId.value match { + case AuthorizedUserCollectionStr => Monad[FailureResponseOrT].unit + case _ => FailureResponseOrT.left(IO.raiseError[HttpResponse](new SamDenialException)) } } } + +object MockSamClient { + val AuthorizedUserCollectionStr: String = "123456789" + val UnauthorizedUserCollectionStr: String = "987654321" + val NotWhitelistedUser: String = "ABC123" + val UserCollectionList: List[Collection] = List(Collection("col1"), Collection("col2")) + + def returnResponse[T](response: HttpResponse): FailureResponseOrT[T] = { + FailureResponseOrT.left(IO.pure(response)) + } +} diff --git a/CromIAM/src/test/scala/cromiam/webservice/QuerySupportSpec.scala b/CromIAM/src/test/scala/cromiam/webservice/QuerySupportSpec.scala index 6239081bd17..0d3362f5911 100644 --- a/CromIAM/src/test/scala/cromiam/webservice/QuerySupportSpec.scala +++ b/CromIAM/src/test/scala/cromiam/webservice/QuerySupportSpec.scala @@ -15,7 +15,10 @@ class QuerySupportSpec extends FlatSpec with Matchers with ScalatestRouteTest wi override val log: LoggingAdapter = NoLogging val authorization = Authorization(OAuth2BearerToken("my-token")) - val authHeaders: List[HttpHeader] = List(authorization, RawHeader("OIDC_CLAIM_user_id", samClient.authorizedUserCollectionStr)) + val authHeaders: List[HttpHeader] = List( + authorization, + RawHeader("OIDC_CLAIM_user_id", MockSamClient.AuthorizedUserCollectionStr) + ) val queryPath = "/api/workflows/v1/query" val getQuery = s"$queryPath?status=Submitted&label=foo:bar&label=foo:baz" diff --git a/CromIAM/src/test/scala/cromiam/webservice/SubmissionSupportSpec.scala b/CromIAM/src/test/scala/cromiam/webservice/SubmissionSupportSpec.scala index 4346cb905ff..6de4c4687e2 100644 --- a/CromIAM/src/test/scala/cromiam/webservice/SubmissionSupportSpec.scala +++ b/CromIAM/src/test/scala/cromiam/webservice/SubmissionSupportSpec.scala @@ -18,9 +18,18 @@ class SubmissionSupportSpec extends FlatSpec with Matchers with ScalatestRouteTe implicit val routeTestTimeout = new RouteTestTimeout(10.seconds.dilated) val authorization = Authorization(OAuth2BearerToken("my-token")) - val badAuthHeaders: List[HttpHeader] = List(authorization, RawHeader("OIDC_CLAIM_user_id", samClient.unauthorizedUserCollectionStr)) - val goodAuthHeaders: List[HttpHeader] = List(authorization, RawHeader("OIDC_CLAIM_user_id", samClient.authorizedUserCollectionStr)) - val notWhitelistedUserHeader: List[HttpHeader] = List(authorization, RawHeader("OIDC_CLAIM_user_id", samClient.notWhitelistedUser)) + val badAuthHeaders: List[HttpHeader] = List( + authorization, + RawHeader("OIDC_CLAIM_user_id", MockSamClient.UnauthorizedUserCollectionStr) + ) + val goodAuthHeaders: List[HttpHeader] = List( + authorization, + RawHeader("OIDC_CLAIM_user_id", MockSamClient.AuthorizedUserCollectionStr) + ) + val notWhitelistedUserHeader: List[HttpHeader] = List( + authorization, + RawHeader("OIDC_CLAIM_user_id", MockSamClient.NotWhitelistedUser) + ) val submitPath: String = "/api/workflows/v1" From 821eb7f58455b225a90eebf2b48c28875927aeef Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Tue, 23 Apr 2019 21:38:13 -0400 Subject: [PATCH 12/46] Add config option to shutdown cromwell when unable to write heartbeats. - Additional wiring for the cromwell terminator. - Reduced duplicate calls to ConfigFactory.load(). - Increased ability to pass around test configs. - Provide names for more actors. - Instrument failures in batch actors. - Use the same heartbeat written to the database for internal success/failure tracking. - Removed code defaults that were not being used, as they were being overridden by the reference.conf. --- CHANGELOG.md | 16 ++ core/src/main/resources/reference.conf | 1 + .../scala/cromwell/core/TestKitSuite.scala | 8 +- .../slick/WorkflowStoreSlickDatabase.scala | 4 +- .../sql/WorkflowStoreSqlDatabase.scala | 2 +- docs/Configuring.md | 105 ++++++++- .../cromwell/engine/CromwellTerminator.scala | 10 + .../workflow/SingleWorkflowRunnerActor.scala | 25 ++- .../workflowstore/InMemoryWorkflowStore.scala | 5 +- .../workflowstore/SqlWorkflowStore.scala | 6 +- .../WorkflowHeartbeatConfig.scala | 72 ++++-- .../workflowstore/WorkflowStore.scala | 4 +- .../workflowstore/WorkflowStoreAccess.scala | 13 +- .../workflowstore/WorkflowStoreActor.scala | 5 + .../WorkflowStoreCoordinatedAccessActor.scala | 7 +- .../WorkflowStoreHeartbeatWriteActor.scala | 71 +++++- .../cromwell/server/CromwellRootActor.scala | 31 ++- .../cromwell/server/CromwellServer.scala | 10 +- .../cromwell/server/CromwellSystem.scala | 18 +- .../engine/MockCromwellTerminator.scala | 12 + .../workflowstore/SqlWorkflowStoreSpec.scala | 4 +- .../WorkflowHeartbeatConfigSpec.scala | 163 ++++++++++++++ ...kflowStoreCoordinatedAccessActorSpec.scala | 10 +- ...WorkflowStoreHeartbeatWriteActorSpec.scala | 75 +++++++ .../scala/cromwell/CromwellEntryPoint.scala | 13 +- .../scala/cromwell/CromwellTestKitSpec.scala | 17 +- .../engine/WorkflowManagerActorSpec.scala | 2 +- .../engine/WorkflowStoreActorSpec.scala | 207 +++++++++++++----- .../SingleWorkflowRunnerActorSpec.scala | 57 +++-- .../SubWorkflowStoreSpec.scala | 18 +- .../InstrumentedBatchActor.scala | 7 +- 31 files changed, 847 insertions(+), 151 deletions(-) create mode 100644 engine/src/main/scala/cromwell/engine/CromwellTerminator.scala create mode 100644 engine/src/test/scala/cromwell/engine/MockCromwellTerminator.scala create mode 100644 engine/src/test/scala/cromwell/engine/workflow/workflowstore/WorkflowHeartbeatConfigSpec.scala create mode 100644 engine/src/test/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreHeartbeatWriteActorSpec.scala diff --git a/CHANGELOG.md b/CHANGELOG.md index f5c0d6d3a3c..a031de085e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,22 @@ * It is now possible to supply custom `google-labels` in [workflow options](https://cromwell.readthedocs.io/en/stable/wf_options/Google/). +### Heartbeat failure shutdown + +When a Cromwell instance is unable to write heartbeats for some period of time it will automatically shut down. For more +information see the docs on [configuring Workflow Hearbeats](https://cromwell.readthedocs.io/en/stable/Configuring/). + +NOTE: In the remote chance that the `system.workflow-heartbeats.ttl` has been configured to be less than `5 minutes` +then the new configuration value `system.workflow-heartbeats.write-failure-shutdown-duration` must also be explicitly +set less than the `ttl`. + +### Bug fixes + +#### Better validation of workflow heartbeats + +An error will be thrown on startup when the `system.workflow-heartbeats.heartbeat-interval` is not less than the +`system.workflow-heartbeats.ttl`. + ## 40 Release Notes ### Config Changes diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 3c1b95f6c53..0eabd4eb146 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -226,6 +226,7 @@ system { workflow-heartbeats { heartbeat-interval: 2 minutes ttl: 10 minutes + write-failure-shutdown-duration: 5 minutes write-batch-size: 10000 write-threshold: 10000 } diff --git a/core/src/test/scala/cromwell/core/TestKitSuite.scala b/core/src/test/scala/cromwell/core/TestKitSuite.scala index cb6621810ef..e8443a4d5f7 100644 --- a/core/src/test/scala/cromwell/core/TestKitSuite.scala +++ b/core/src/test/scala/cromwell/core/TestKitSuite.scala @@ -21,10 +21,10 @@ abstract class TestKitSuite(actorSystemName: String = TestKitSuite.randomName, shutdown() } - val emptyActor = system.actorOf(Props.empty) - val mockIoActor = system.actorOf(MockIoActor.props()) - val simpleIoActor = system.actorOf(SimpleIoActor.props) - val failIoActor = system.actorOf(FailIoActor.props()) + val emptyActor = system.actorOf(Props.empty, "TestKitSuiteEmptyActor") + val mockIoActor = system.actorOf(MockIoActor.props(), "TestKitSuiteMockIoActor") + val simpleIoActor = system.actorOf(SimpleIoActor.props, "TestKitSuiteSimpleIoActor") + val failIoActor = system.actorOf(FailIoActor.props(), "TestKitSuiteFailIoActor") } object TestKitSuite { diff --git a/database/sql/src/main/scala/cromwell/database/slick/WorkflowStoreSlickDatabase.scala b/database/sql/src/main/scala/cromwell/database/slick/WorkflowStoreSlickDatabase.scala index c02b3e0a28d..6406ae6b289 100644 --- a/database/sql/src/main/scala/cromwell/database/slick/WorkflowStoreSlickDatabase.scala +++ b/database/sql/src/main/scala/cromwell/database/slick/WorkflowStoreSlickDatabase.scala @@ -107,13 +107,13 @@ trait WorkflowStoreSlickDatabase extends WorkflowStoreSqlDatabase { } override def writeWorkflowHeartbeats(workflowExecutionUuids: Seq[String], - heartbeatTimestampOption: Option[Timestamp]) + heartbeatTimestamp: Timestamp) (implicit ec: ExecutionContext): Future[Int] = { // Return the count of heartbeats written. This could legitimately be less than the size of the `workflowExecutionUuids` // List if any of those workflows completed and their workflow store entries were removed. val action = for { counts <- DBIO.sequence(workflowExecutionUuids map { workflowExecutionUuid => - dataAccess.heartbeatForWorkflowStoreEntry(workflowExecutionUuid).update(heartbeatTimestampOption) + dataAccess.heartbeatForWorkflowStoreEntry(workflowExecutionUuid).update(Option(heartbeatTimestamp)) }) } yield counts.sum // Auto-commit mode, so each statement is individually committed to avoid deadlocks diff --git a/database/sql/src/main/scala/cromwell/database/sql/WorkflowStoreSqlDatabase.scala b/database/sql/src/main/scala/cromwell/database/sql/WorkflowStoreSqlDatabase.scala index 68703af9de2..1a9ec6dda22 100644 --- a/database/sql/src/main/scala/cromwell/database/sql/WorkflowStoreSqlDatabase.scala +++ b/database/sql/src/main/scala/cromwell/database/sql/WorkflowStoreSqlDatabase.scala @@ -78,7 +78,7 @@ ____ __ ____ ______ .______ __ ___ _______ __ ______ (implicit ec: ExecutionContext): Future[Seq[WorkflowStoreEntry]] def writeWorkflowHeartbeats(workflowExecutionUuids: Seq[String], - heartbeatTimestampOption: Option[Timestamp]) + heartbeatTimestamp: Timestamp) (implicit ec: ExecutionContext): Future[Int] /** diff --git a/docs/Configuring.md b/docs/Configuring.md index 39279fe304e..49eb9adaf3d 100644 --- a/docs/Configuring.md +++ b/docs/Configuring.md @@ -242,7 +242,8 @@ For more information about docker compose: [Docker compose doc](https://docs.doc **Insert Batch Size** -Cromwell queues up and then inserts batches of records into the database for increased performance. You can adjust the number of database rows batch inserted by cromwell as follows: +Cromwell queues up and then inserts batches of records into the database for increased performance. You can adjust the +number of database rows batch inserted by Cromwell as follows: ```hocon database { @@ -420,3 +421,105 @@ of how this is used for the default configuration of the `Local` backend. [cromwell-examples-conf]: https://www.github.com/broadinstitute/cromwell/tree/develop/cromwell.examples.conf [cromwell-examples-folder]: https://www.github.com/broadinstitute/cromwell/tree/develop/cromwell.example.backends + +### Workflow Heartbeats + +**Cromwell ID** + +Each Cromwell instance is given a `cromwell_id` that is either randomly generated or configured. + +By default, the Cromwell ID is `cromid-<7_digit_random_hex>`. + +A custom identifier may replace the "cromid" portion of the string. For example: + +```hocon +system { + cromwell_id = "main" +} +``` + +This would generates a `cromwell_id` of `main-<7_digit_random_hex>`. Each time Cromwell restarts the random part of the +ID will change, however the `main` prefix would remain the same. + +If the random part of the Cromwell ID should not be generated, set the configuration value: + +```hocon +system { + cromwell_id_random_suffix = false +} +``` + +**Heartbeat TTL** + +When a Cromwell instance begins running or resuming a workflow it stores the above `cromwell_id` within the database row +for the workflow, along with a timestamp called the "heartbeat". As the workflow continues to run the Cromwell instance +will intermittently update the heartbeat for the running workflow. If the Cromwell dies, after some time-to-live (TTL), +the workflow has been abandoned, and will be resumed by another available Cromwell instance. + +Adjust the heartbeat TTL via the configuration value: + +```hocon +system.workflow-heartbeats { + ttl = 10 minutes +} +``` + +The default TTL is 10 minutes. The shortest the TTL option is 10 seconds. + +**Heartbeat Interval** + +The interval for writing heartbeats may be adjusted via: + +```hocon +system.workflow-heartbeats { + heartbeat-interval = 2 minutes +} +``` + +The default interval is 2 minutes. The shortest interval option is 3.333 seconds. The interval may not be greater than +the TTL. + +**Heartbeat Failure Shutdown** + +Cromwell will automatically shutdown when unable to write heartbeats for a period of time. This period of time may be +adjusted via: + +```hocon +system.workflow-heartbeats { + write-failure-shutdown-duration = 5 minutes +} +``` + +The default shutdown duration is 5 minutes. The maximum allowed shutdown duration is the TTL. + +**Heartbeat Batch Size** + +Workflow heartbeats are internally queued by Cromwell and written in batches. When the configurable batch size is +reached, all of the heartbeats within the batch will be written at the same time, even if the heartbeat interval has not +elapsed. + +This batch threshold may be adjusted via: + +```hocon +system.workflow-heartbeats { + write-batch-size = 100 +} +``` + +The default batch size is 100. + +**Heartbeat Threshold** + +Cromwell writes one batch of workflow heartbeats at a time. While the internal queue of heartbeats-to-write passes above +a configurable threshold then [instrumentation](developers/Instrumentation.md) may send a metric signal that the +heartbeat load is above normal. + +This threshold may be configured the configuration value: + +```hocon +system.workflow-heartbeats { + write-threshold = 100 +} +``` + +The default threshold value is 100, just like the default for the heartbeat batch size. diff --git a/engine/src/main/scala/cromwell/engine/CromwellTerminator.scala b/engine/src/main/scala/cromwell/engine/CromwellTerminator.scala new file mode 100644 index 00000000000..462369c6988 --- /dev/null +++ b/engine/src/main/scala/cromwell/engine/CromwellTerminator.scala @@ -0,0 +1,10 @@ +package cromwell.engine + +import akka.Done +import akka.actor.CoordinatedShutdown + +import scala.concurrent.Future + +trait CromwellTerminator { + def beginCromwellShutdown(reason: CoordinatedShutdown.Reason): Future[Done] +} diff --git a/engine/src/main/scala/cromwell/engine/workflow/SingleWorkflowRunnerActor.scala b/engine/src/main/scala/cromwell/engine/workflow/SingleWorkflowRunnerActor.scala index d8a49ce3473..1ee8e8cbb76 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/SingleWorkflowRunnerActor.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/SingleWorkflowRunnerActor.scala @@ -7,6 +7,7 @@ import akka.actor._ import akka.stream.ActorMaterializer import cats.instances.try_._ import cats.syntax.functor._ +import com.typesafe.config.Config import common.util.VersionUtil import cromwell.core.Dispatcher.EngineDispatcher import cromwell.core._ @@ -14,6 +15,7 @@ import cromwell.core.abort.WorkflowAbortFailureResponse import cromwell.core.actor.BatchActor.QueueWeight import cromwell.core.path.Path import cromwell.core.retry.SimpleExponentialBackoff +import cromwell.engine.CromwellTerminator import cromwell.engine.workflow.SingleWorkflowRunnerActor._ import cromwell.engine.workflow.WorkflowManagerActor.{PreventNewWorkflowsFromStarting, RetrieveNewWorkflows} import cromwell.engine.workflow.workflowstore.WorkflowStoreActor.SubmitWorkflow @@ -37,10 +39,13 @@ import scala.util.{Failure, Try} */ class SingleWorkflowRunnerActor(source: WorkflowSourceFilesCollection, metadataOutputPath: Option[Path], + terminator: CromwellTerminator, gracefulShutdown: Boolean, - abortJobsOnTerminate: Boolean + abortJobsOnTerminate: Boolean, + config: Config )(implicit materializer: ActorMaterializer) - extends CromwellRootActor(gracefulShutdown, abortJobsOnTerminate, false) with LoggingFSM[RunnerState, SwraData] { + extends CromwellRootActor(terminator, gracefulShutdown, abortJobsOnTerminate, false, config) + with LoggingFSM[RunnerState, SwraData] { import SingleWorkflowRunnerActor._ private val backoff = SimpleExponentialBackoff(1 second, 1 minute, 1.2) @@ -220,9 +225,21 @@ class SingleWorkflowRunnerActor(source: WorkflowSourceFilesCollection, object SingleWorkflowRunnerActor { def props(source: WorkflowSourceFilesCollection, metadataOutputFile: Option[Path], + terminator: CromwellTerminator, gracefulShutdown: Boolean, - abortJobsOnTerminate: Boolean)(implicit materializer: ActorMaterializer): Props = { - Props(new SingleWorkflowRunnerActor(source, metadataOutputFile, gracefulShutdown, abortJobsOnTerminate)).withDispatcher(EngineDispatcher) + abortJobsOnTerminate: Boolean, + config: Config) + (implicit materializer: ActorMaterializer): Props = { + Props( + new SingleWorkflowRunnerActor( + source = source, + metadataOutputPath = metadataOutputFile, + terminator = terminator, + gracefulShutdown = gracefulShutdown, + abortJobsOnTerminate = abortJobsOnTerminate, + config = config + ) + ).withDispatcher(EngineDispatcher) } sealed trait RunnerMessage diff --git a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/InMemoryWorkflowStore.scala b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/InMemoryWorkflowStore.scala index da87e3a2d82..ebe2ef0b002 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/InMemoryWorkflowStore.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/InMemoryWorkflowStore.scala @@ -74,8 +74,11 @@ class InMemoryWorkflowStore extends WorkflowStore { } } - override def writeWorkflowHeartbeats(workflowIds: Set[(WorkflowId, OffsetDateTime)])(implicit ec: ExecutionContext): Future[Int] = + override def writeWorkflowHeartbeats(workflowIds: Set[(WorkflowId, OffsetDateTime)], + heartbeatDateTime: OffsetDateTime) + (implicit ec: ExecutionContext): Future[Int] = { Future.successful(workflowIds.size) + } override def switchOnHoldToSubmitted(id: WorkflowId)(implicit ec: ExecutionContext): Future[Unit] = Future.successful(()) diff --git a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/SqlWorkflowStore.scala b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/SqlWorkflowStore.scala index 98248319d29..ed889299464 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/SqlWorkflowStore.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/SqlWorkflowStore.scala @@ -101,9 +101,11 @@ case class SqlWorkflowStore(sqlDatabase: WorkflowStoreSqlDatabase) extends Workf } } - override def writeWorkflowHeartbeats(workflowIds: Set[(WorkflowId, OffsetDateTime)])(implicit ec: ExecutionContext): Future[Int] = { + override def writeWorkflowHeartbeats(workflowIds: Set[(WorkflowId, OffsetDateTime)], + heartbeatDateTime: OffsetDateTime) + (implicit ec: ExecutionContext): Future[Int] = { val sortedWorkflowIds = workflowIds.toList sortBy(_._2) map (_._1.toString) - sqlDatabase.writeWorkflowHeartbeats(sortedWorkflowIds, Option(OffsetDateTime.now.toSystemTimestamp)) + sqlDatabase.writeWorkflowHeartbeats(sortedWorkflowIds, heartbeatDateTime.toSystemTimestamp) } /** diff --git a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowHeartbeatConfig.scala b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowHeartbeatConfig.scala index 4aade10bca5..d27d6d5b1f6 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowHeartbeatConfig.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowHeartbeatConfig.scala @@ -2,22 +2,26 @@ package cromwell.engine.workflow.workflowstore import java.util.UUID +import cats.syntax.apply._ +import cats.syntax.validated._ import com.typesafe.config.{Config, ConfigFactory} +import common.validation.ErrorOr._ +import common.validation.Validation._ import cromwell.engine.workflow.workflowstore.WorkflowHeartbeatConfig._ import io.circe._ import io.circe.generic.semiauto._ import io.circe.syntax._ +import mouse.all._ import net.ceedubs.ficus.Ficus._ import scala.concurrent.duration._ -import scala.language.postfixOps -import mouse.all._ /** * * @param cromwellId Identifier for this Cromwell for horizontaling purposes. * @param heartbeatInterval How long between `WorkflowActor` heartbeats and `WorkflowStoreHeartbeatWriteActor` database flushes. * @param ttl Entries in the workflow store with heartbeats older than `ttl` ago are presumed abandoned and up for grabs. + * @param failureShutdownDuration How long writing heartbeats are allow to fail before cromwell shuts down. * @param writeBatchSize The maximum size of a write batch. * @param writeThreshold The threshold of heartbeat writes above which load is considered high. */ @@ -25,9 +29,9 @@ case class WorkflowHeartbeatConfig( cromwellId: String, heartbeatInterval: FiniteDuration, ttl: FiniteDuration, + failureShutdownDuration: FiniteDuration, writeBatchSize: Int, - writeThreshold: Int) -{ + writeThreshold: Int) { override def toString: String = this.asInstanceOf[WorkflowHeartbeatConfig].asJson.spaces2 } @@ -43,28 +47,60 @@ object WorkflowHeartbeatConfig { private[engine] implicit lazy val encodeWorkflowHeartbeatConfig: Encoder[WorkflowHeartbeatConfig] = deriveEncoder def apply(config: Config): WorkflowHeartbeatConfig = { + validate(config).toTry("Errors parsing WorkflowHeartbeatConfig").get + } + + private def validate(config: Config): ErrorOr[WorkflowHeartbeatConfig] = { val randomSuffix = config .getOrElse("system.cromwell_id_random_suffix", true) .fold("-" + UUID.randomUUID().toString.take(7), "") val cromwellId: String = config.getOrElse("system.cromwell_id", "cromid") + randomSuffix val heartbeats: Config = config.getOrElse("system.workflow-heartbeats", ConfigFactory.empty()) - // Default to 10 mins and don't allow values less than 10 secs except for testing purposes. + // Use defaults from reference.conf but don't allow values less than 10 secs except for testing purposes. val minHeartbeatTtl = config.getOrElse("danger.debug.only.minimum-heartbeat-ttl", 10.seconds) - val ttl: FiniteDuration = heartbeats.getOrElse("ttl", 10 minutes).max(minHeartbeatTtl) - // Default to one third the TTL and don't allow values less than one third of the minimum heartbeat TTL. - val heartbeatInterval: FiniteDuration = heartbeats.getOrElse("heartbeat-interval", ttl / 3).max(minHeartbeatTtl / 3) + val ttl: FiniteDuration = heartbeats.as[FiniteDuration]("ttl").max(minHeartbeatTtl) + // Use the defaults in reference.conf but don't allow values less than one third of the minimum TTL. + val heartbeatIntervalValidation: ErrorOr[FiniteDuration] = { + val minHeartbeatInterval = minHeartbeatTtl / 3 + val heartbeatInterval = heartbeats.as[FiniteDuration]("heartbeat-interval") max minHeartbeatInterval + if (heartbeatInterval >= ttl) { + val errorMessage = + s"The system.workflow-heartbeats.heartbeat-interval ($heartbeatInterval)" + + s" is not less than the system.workflow-heartbeats.ttl ($ttl)." + errorMessage.invalidNel + } else { + heartbeatInterval.valid + } + } + + val failureShutdownDurationValidation: ErrorOr[FiniteDuration] = { + // Don't allow shutdown duration to be more than the heartbeat TTL. + val failureShutdownDuration = heartbeats.as[FiniteDuration]("write-failure-shutdown-duration") max 0.seconds + if (failureShutdownDuration > ttl) { + val errorMessage = + s"The system.workflow-heartbeats.write-failure-shutdown-duration ($failureShutdownDuration)" + + s" is greater than the system.workflow-heartbeats.ttl ($ttl)." + errorMessage.invalidNel + } else { + failureShutdownDuration.valid + } + } - // Default and sanity bound all other parameters as well. - val writeBatchSize: Int = Math.max(heartbeats.getOrElse("write-batch-size", 100), 1) - val writeThreshold: Int = Math.max(heartbeats.getOrElse("write-threshold", 100), 1) + // Sanity bound all other parameters as well. + val writeBatchSize: Int = heartbeats.getInt("write-batch-size") max 1 + val writeThreshold: Int = heartbeats.getInt("write-threshold") max 1 - WorkflowHeartbeatConfig( - cromwellId = cromwellId, - heartbeatInterval = heartbeatInterval, - ttl = ttl, - writeBatchSize = writeBatchSize, - writeThreshold = writeThreshold - ) + (heartbeatIntervalValidation, failureShutdownDurationValidation) mapN { + (heartbeatInterval, failureShutdownDuration) => + WorkflowHeartbeatConfig( + cromwellId = cromwellId, + heartbeatInterval = heartbeatInterval, + ttl = ttl, + failureShutdownDuration = failureShutdownDuration, + writeBatchSize = writeBatchSize, + writeThreshold = writeThreshold + ) + } } } diff --git a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStore.scala b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStore.scala index 3a42d5821af..a0fa8c28b74 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStore.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStore.scala @@ -38,7 +38,9 @@ trait WorkflowStore { */ def fetchStartableWorkflows(n: Int, cromwellId: String, heartbeatTtl: FiniteDuration)(implicit ec: ExecutionContext): Future[List[WorkflowToStart]] - def writeWorkflowHeartbeats(workflowIds: Set[(WorkflowId, OffsetDateTime)])(implicit ec: ExecutionContext): Future[Int] + def writeWorkflowHeartbeats(workflowIds: Set[(WorkflowId, OffsetDateTime)], + heartbeatDateTime: OffsetDateTime) + (implicit ec: ExecutionContext): Future[Int] def switchOnHoldToSubmitted(id: WorkflowId)(implicit ec: ExecutionContext): Future[Unit] } diff --git a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreAccess.scala b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreAccess.scala index bf3dc765796..6c7b7e9583d 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreAccess.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreAccess.scala @@ -15,7 +15,8 @@ import scala.concurrent.{ExecutionContext, Future} * Interface for workflow store operations that read or write multiple rows in a single transaction. */ sealed trait WorkflowStoreAccess { - def writeWorkflowHeartbeats(workflowIds: NonEmptyVector[(WorkflowId, OffsetDateTime)]) + def writeWorkflowHeartbeats(workflowIds: NonEmptyVector[(WorkflowId, OffsetDateTime)], + heartbeatDateTime: OffsetDateTime) (implicit ec: ExecutionContext): Future[Int] def fetchStartableWorkflows(maxWorkflows: Int, cromwellId: String, heartbeatTtl: FiniteDuration) @@ -28,9 +29,10 @@ sealed trait WorkflowStoreAccess { */ case class UncoordinatedWorkflowStoreAccess(store: WorkflowStore) extends WorkflowStoreAccess { - override def writeWorkflowHeartbeats(workflowIds: NonEmptyVector[(WorkflowId, OffsetDateTime)]) + override def writeWorkflowHeartbeats(workflowIds: NonEmptyVector[(WorkflowId, OffsetDateTime)], + heartbeatDateTime: OffsetDateTime) (implicit ec: ExecutionContext): Future[Int] = { - store.writeWorkflowHeartbeats(workflowIds.toVector.toSet) + store.writeWorkflowHeartbeats(workflowIds.toVector.toSet, heartbeatDateTime) } override def fetchStartableWorkflows(maxWorkflows: Int, cromwellId: String, heartbeatTtl: FiniteDuration) @@ -44,10 +46,11 @@ case class UncoordinatedWorkflowStoreAccess(store: WorkflowStore) extends Workfl * that runs its operations sequentially. */ case class CoordinatedWorkflowStoreAccess(actor: ActorRef) extends WorkflowStoreAccess { - override def writeWorkflowHeartbeats(workflowIds: NonEmptyVector[(WorkflowId, OffsetDateTime)]) + override def writeWorkflowHeartbeats(workflowIds: NonEmptyVector[(WorkflowId, OffsetDateTime)], + heartbeatDateTime: OffsetDateTime) (implicit ec: ExecutionContext): Future[Int] = { implicit val timeout = Timeout(WorkflowStoreCoordinatedAccessActor.Timeout) - actor.ask(WorkflowStoreCoordinatedAccessActor.WriteHeartbeats(workflowIds)).mapTo[Int] + actor.ask(WorkflowStoreCoordinatedAccessActor.WriteHeartbeats(workflowIds, heartbeatDateTime)).mapTo[Int] } override def fetchStartableWorkflows(maxWorkflows: Int, cromwellId: String, heartbeatTtl: FiniteDuration) diff --git a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreActor.scala b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreActor.scala index 9d5659e75e3..9310cb36ad0 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreActor.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreActor.scala @@ -7,6 +7,7 @@ import akka.pattern.pipe import cats.data.NonEmptyList import cromwell.core.Dispatcher.EngineDispatcher import cromwell.core._ +import cromwell.engine.CromwellTerminator import cromwell.util.GracefulShutdownHelper import cromwell.util.GracefulShutdownHelper.ShutdownCommand @@ -14,6 +15,7 @@ final case class WorkflowStoreActor private( workflowStore: WorkflowStore, workflowStoreAccess: WorkflowStoreAccess, serviceRegistryActor: ActorRef, + terminator: CromwellTerminator, abortAllJobsOnTerminate: Boolean, workflowHeartbeatConfig: WorkflowHeartbeatConfig) extends Actor with ActorLogging with GracefulShutdownHelper { @@ -40,6 +42,7 @@ final case class WorkflowStoreActor private( WorkflowStoreHeartbeatWriteActor.props( workflowStoreAccess = workflowStoreAccess, workflowHeartbeatConfig = workflowHeartbeatConfig, + terminator = terminator, serviceRegistryActor = serviceRegistryActor), "WorkflowStoreHeartbeatWriteActor") @@ -82,6 +85,7 @@ object WorkflowStoreActor { workflowStoreDatabase: WorkflowStore, workflowStoreAccess: WorkflowStoreAccess, serviceRegistryActor: ActorRef, + terminator: CromwellTerminator, abortAllJobsOnTerminate: Boolean, workflowHeartbeatConfig: WorkflowHeartbeatConfig ) = { @@ -89,6 +93,7 @@ object WorkflowStoreActor { workflowStore = workflowStoreDatabase, workflowStoreAccess = workflowStoreAccess, serviceRegistryActor = serviceRegistryActor, + terminator = terminator, abortAllJobsOnTerminate = abortAllJobsOnTerminate, workflowHeartbeatConfig = workflowHeartbeatConfig)).withDispatcher(EngineDispatcher) } diff --git a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreCoordinatedAccessActor.scala b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreCoordinatedAccessActor.scala index 0deaec130bd..f9e1f2ddb44 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreCoordinatedAccessActor.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreCoordinatedAccessActor.scala @@ -30,15 +30,16 @@ class WorkflowStoreCoordinatedAccessActor(workflowStore: WorkflowStore) extends } override def receive: Receive = { - case WriteHeartbeats(ids) => - workflowStore.writeWorkflowHeartbeats(ids.toVector.toSet) |> run + case WriteHeartbeats(ids, heartbeatDateTime) => + workflowStore.writeWorkflowHeartbeats(ids.toVector.toSet, heartbeatDateTime) |> run case FetchStartableWorkflows(count, cromwellId, heartbeatTtl) => workflowStore.fetchStartableWorkflows(count, cromwellId, heartbeatTtl) |> run } } object WorkflowStoreCoordinatedAccessActor { - final case class WriteHeartbeats(workflowIds: NonEmptyVector[(WorkflowId, OffsetDateTime)]) + final case class WriteHeartbeats(workflowIds: NonEmptyVector[(WorkflowId, OffsetDateTime)], + heartbeatDateTime: OffsetDateTime) final case class FetchStartableWorkflows(count: Int, cromwellId: String, heartbeatTtl: FiniteDuration) val Timeout = 1 minute diff --git a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreHeartbeatWriteActor.scala b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreHeartbeatWriteActor.scala index 7e8ee90b805..1dbc0fb780a 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreHeartbeatWriteActor.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreHeartbeatWriteActor.scala @@ -1,19 +1,25 @@ package cromwell.engine.workflow.workflowstore -import java.time.OffsetDateTime +import java.time.{OffsetDateTime, Duration => JDuration} +import java.util.concurrent.TimeUnit -import akka.actor.{ActorRef, Props} +import akka.actor.{ActorRef, CoordinatedShutdown, Props} import cats.data.{NonEmptyList, NonEmptyVector} import cromwell.core.Dispatcher.EngineDispatcher import cromwell.core.WorkflowId import cromwell.core.instrumentation.InstrumentationPrefixes +import cromwell.engine.CromwellTerminator import cromwell.engine.workflow.workflowstore.WorkflowStoreActor.WorkflowStoreWriteHeartbeatCommand import cromwell.services.EnhancedBatchActor +import mouse.all._ import scala.concurrent.Future +import scala.concurrent.duration.FiniteDuration +import scala.util.{Failure, Success, Try} case class WorkflowStoreHeartbeatWriteActor(workflowStoreAccess: WorkflowStoreAccess, workflowHeartbeatConfig: WorkflowHeartbeatConfig, + terminator: CromwellTerminator, override val serviceRegistryActor: ActorRef) extends EnhancedBatchActor[(WorkflowId, OffsetDateTime)]( @@ -22,13 +28,23 @@ case class WorkflowStoreHeartbeatWriteActor(workflowStoreAccess: WorkflowStoreAc override val threshold = workflowHeartbeatConfig.writeThreshold + private val failureShutdownDuration = workflowHeartbeatConfig.failureShutdownDuration + + //noinspection ActorMutableStateInspection + private var lastSuccessOption: Option[OffsetDateTime] = None + /** * Process the data asynchronously * * @return the number of elements processed */ override protected def process(data: NonEmptyVector[(WorkflowId, OffsetDateTime)]): Future[Int] = instrumentedProcess { - workflowStoreAccess.writeWorkflowHeartbeats(data) + val heartbeatDateTime = OffsetDateTime.now() + val processFuture = workflowStoreAccess.writeWorkflowHeartbeats(data, heartbeatDateTime) + processFuture transform { + // Track the `Try`, and then return the original `Try`. Similar to `andThen` but doesn't swallow exceptions. + _ <| trackRepeatedFailures(heartbeatDateTime, data.length) + } } override def receive = enhancedReceive.orElse(super.receive) @@ -38,18 +54,67 @@ case class WorkflowStoreHeartbeatWriteActor(workflowStoreAccess: WorkflowStoreAc override def commandToData(snd: ActorRef): PartialFunction[Any, (WorkflowId, OffsetDateTime)] = { case command: WorkflowStoreWriteHeartbeatCommand => (command.workflowId, command.submissionTime) } + + /* + WARNING: Even though this is in an actor, the logic deals with instances of Future that could complete in _any_ order, + and even call this method at the same time from different threads. + + We are expecting the underlying FSM to ensure that the call to this method does NOT occur in parallel, waiting for + the call to `process` to complete. + */ + private def trackRepeatedFailures(heartbeatDateTime: OffsetDateTime, workflowCount: Int)(processTry: Try[Int]): Unit = { + processTry match { + case Success(_) => + lastSuccessOption = Option(heartbeatDateTime) + case Failure(_: Exception) => + /* + If this is the first time processing heartbeats, then initialize the "last success" to when this heartbeat check + began. This allows configuring the failure shutdown as low as zero seconds. + */ + val lastSuccess = lastSuccessOption getOrElse { + lastSuccessOption = Option(heartbeatDateTime) + heartbeatDateTime + } + val now = OffsetDateTime.now() + val failureJDuration = JDuration.between(lastSuccess, now) + if (failureJDuration.toNanos >= failureShutdownDuration.toNanos) { + val failureUnits = failureShutdownDuration.unit + val failureLength = FiniteDuration(failureJDuration.toNanos, TimeUnit.NANOSECONDS).toUnit(failureUnits) + log.error(String.format( + "Shutting down %s as at least %s of heartbeat write errors have occurred between %s and %s (%s %s)", + workflowHeartbeatConfig.cromwellId, + failureShutdownDuration, + lastSuccess, + now, + failureLength.toString, + failureUnits.toString.toLowerCase + )) + terminator.beginCromwellShutdown(WorkflowStoreHeartbeatWriteActor.Shutdown) + } + () + case Failure(throwable) => + log.error(throwable, s"Shutting down due to ${throwable.getMessage}") + terminator.beginCromwellShutdown(WorkflowStoreHeartbeatWriteActor.Shutdown) + () + } + } + } object WorkflowStoreHeartbeatWriteActor { + object Shutdown extends CoordinatedShutdown.Reason + def props( workflowStoreAccess: WorkflowStoreAccess, workflowHeartbeatConfig: WorkflowHeartbeatConfig, + terminator: CromwellTerminator, serviceRegistryActor: ActorRef ): Props = Props( WorkflowStoreHeartbeatWriteActor( workflowStoreAccess = workflowStoreAccess, workflowHeartbeatConfig = workflowHeartbeatConfig, + terminator = terminator, serviceRegistryActor = serviceRegistryActor )).withDispatcher(EngineDispatcher) } diff --git a/engine/src/main/scala/cromwell/server/CromwellRootActor.scala b/engine/src/main/scala/cromwell/server/CromwellRootActor.scala index 81c59f9b7b0..bb407c00d33 100644 --- a/engine/src/main/scala/cromwell/server/CromwellRootActor.scala +++ b/engine/src/main/scala/cromwell/server/CromwellRootActor.scala @@ -6,7 +6,7 @@ import akka.event.Logging import akka.pattern.GracefulStopSupport import akka.routing.RoundRobinPool import akka.stream.ActorMaterializer -import com.typesafe.config.{Config, ConfigFactory} +import com.typesafe.config.Config import cromwell.core._ import cromwell.core.actor.StreamActorHelper.ActorRestartException import cromwell.core.filesystem.CromwellFileSystems @@ -14,6 +14,7 @@ import cromwell.core.io.Throttle import cromwell.core.io.Throttle._ import cromwell.docker.DockerInfoActor import cromwell.docker.local.DockerCliFlow +import cromwell.engine.CromwellTerminator import cromwell.engine.backend.{BackendSingletonCollection, CromwellBackends} import cromwell.engine.io.{IoActor, IoActorProxy} import cromwell.engine.workflow.WorkflowManagerActor @@ -48,14 +49,20 @@ import scala.util.{Failure, Success, Try} * READ THIS: If you add a "system-level" actor here, make sure to consider what should be its * position in the shutdown process and modify CromwellShutdown accordingly. */ -abstract class CromwellRootActor(gracefulShutdown: Boolean, abortJobsOnTerminate: Boolean, val serverMode: Boolean)(implicit materializer: ActorMaterializer) extends Actor with ActorLogging with GracefulShutdownHelper { +abstract class CromwellRootActor(terminator: CromwellTerminator, + gracefulShutdown: Boolean, + abortJobsOnTerminate: Boolean, + val serverMode: Boolean, + protected val config: Config) + (implicit materializer: ActorMaterializer) + extends Actor with ActorLogging with GracefulShutdownHelper { + import CromwellRootActor._ // Make sure the filesystems are initialized at startup val _ = CromwellFileSystems.instance private val logger = Logging(context.system, this) - protected val config = ConfigFactory.load() private val workflowHeartbeatConfig = WorkflowHeartbeatConfig(config) logger.info("Workflow heartbeat configuration:\n{}", workflowHeartbeatConfig) @@ -71,7 +78,10 @@ abstract class CromwellRootActor(gracefulShutdown: Boolean, abortJobsOnTerminate coordinatedWorkflowStoreAccess match { case Some(false) => UncoordinatedWorkflowStoreAccess(workflowStore) case _ => - val coordinatedWorkflowStoreAccessActor: ActorRef = context.actorOf(WorkflowStoreCoordinatedAccessActor.props(workflowStore)) + val coordinatedWorkflowStoreAccessActor: ActorRef = context.actorOf( + WorkflowStoreCoordinatedAccessActor.props(workflowStore), + "WorkflowStoreCoordinatedAccessActor" + ) CoordinatedWorkflowStoreAccess(coordinatedWorkflowStoreAccessActor) } } @@ -81,6 +91,7 @@ abstract class CromwellRootActor(gracefulShutdown: Boolean, abortJobsOnTerminate workflowStoreDatabase = workflowStore, workflowStoreAccess = workflowStoreAccess, serviceRegistryActor = serviceRegistryActor, + terminator = terminator, abortAllJobsOnTerminate = abortJobsOnTerminate, workflowHeartbeatConfig = workflowHeartbeatConfig), "WorkflowStoreActor") @@ -174,7 +185,8 @@ abstract class CromwellRootActor(gracefulShutdown: Boolean, abortJobsOnTerminate workflowStoreActor = workflowStoreActor, workflowManagerActor = workflowManagerActor, workflowHeartbeatConfig = workflowHeartbeatConfig - ) + ), + "AbortRequestScanningActor" ) } @@ -182,7 +194,7 @@ abstract class CromwellRootActor(gracefulShutdown: Boolean, abortJobsOnTerminate // If abortJobsOnTerminate is true, aborting all workflows will be handled by the graceful shutdown process CromwellShutdown.registerShutdownTasks( cromwellId = workflowHeartbeatConfig.cromwellId, - abortJobsOnTerminate, + abortJobsOnTerminate = abortJobsOnTerminate, actorSystem = context.system, workflowManagerActor = workflowManagerActor, logCopyRouter = workflowLogCopyRouter, @@ -198,6 +210,7 @@ abstract class CromwellRootActor(gracefulShutdown: Boolean, abortJobsOnTerminate ) } else if (abortJobsOnTerminate) { // If gracefulShutdown is false but abortJobsOnTerminate is true, set up a classic JVM shutdown hook + val abortTimeout = config.as[FiniteDuration]("akka.coordinated-shutdown.phases.abort-all-workflows.timeout") sys.addShutdownHook { implicit val ec = context.system.dispatcher @@ -205,10 +218,10 @@ abstract class CromwellRootActor(gracefulShutdown: Boolean, abortJobsOnTerminate // Give 30 seconds to the workflow store to switch all running workflows to aborting and shutdown. Should be more than enough _ <- gracefulStop(workflowStoreActor, 30.seconds, ShutdownCommand) // Once all workflows are "Aborting" in the workflow store, ask the WMA to effectively abort all of them - _ <- gracefulStop(workflowManagerActor, AbortTimeout, AbortAllWorkflowsCommand) + _ <- gracefulStop(workflowManagerActor, abortTimeout, AbortAllWorkflowsCommand) } yield () - Try(Await.result(abortFuture, AbortTimeout)) match { + Try(Await.result(abortFuture, abortTimeout)) match { case Success(_) => logger.info("All workflows aborted") case Failure(f) => logger.error("Failed to abort workflows", f) } @@ -231,8 +244,6 @@ abstract class CromwellRootActor(gracefulShutdown: Boolean, abortJobsOnTerminate } object CromwellRootActor extends GracefulStopSupport { - import net.ceedubs.ficus.Ficus._ - val AbortTimeout = ConfigFactory.load().as[FiniteDuration]("akka.coordinated-shutdown.phases.abort-all-workflows.timeout") val DefaultNumberOfWorkflowLogCopyWorkers = 10 val DefaultCacheTTL = 20 minutes val DefaultNumberOfCacheReadWorkers = 25 diff --git a/engine/src/main/scala/cromwell/server/CromwellServer.scala b/engine/src/main/scala/cromwell/server/CromwellServer.scala index eb88698ac7b..455cf6c035b 100644 --- a/engine/src/main/scala/cromwell/server/CromwellServer.scala +++ b/engine/src/main/scala/cromwell/server/CromwellServer.scala @@ -26,7 +26,13 @@ object CromwellServer { } class CromwellServerActor(cromwellSystem: CromwellSystem, gracefulShutdown: Boolean, abortJobsOnTerminate: Boolean)(override implicit val materializer: ActorMaterializer) - extends CromwellRootActor(gracefulShutdown, abortJobsOnTerminate, serverMode = true) + extends CromwellRootActor( + terminator = cromwellSystem, + gracefulShutdown = gracefulShutdown, + abortJobsOnTerminate = abortJobsOnTerminate, + serverMode = true, + config = cromwellSystem.config + ) with CromwellApiService with CromwellInstrumentationActor with WesRouteSupport @@ -36,7 +42,7 @@ class CromwellServerActor(cromwellSystem: CromwellSystem, gracefulShutdown: Bool override implicit val ec = context.dispatcher override def actorRefFactory: ActorContext = context - val webserviceConf = cromwellSystem.conf.getConfig("webservice") + val webserviceConf = cromwellSystem.config.getConfig("webservice") val interface = webserviceConf.getString("interface") val port = webserviceConf.getInt("port") diff --git a/engine/src/main/scala/cromwell/server/CromwellSystem.scala b/engine/src/main/scala/cromwell/server/CromwellSystem.scala index 6eccb3c6fdb..1a6a709c8f3 100644 --- a/engine/src/main/scala/cromwell/server/CromwellSystem.scala +++ b/engine/src/main/scala/cromwell/server/CromwellSystem.scala @@ -1,16 +1,18 @@ package cromwell.server -import akka.actor.{ActorSystem, DeadLetter, Props, Terminated} +import akka.Done +import akka.actor.{ActorSystem, CoordinatedShutdown, DeadLetter, Props, Terminated} import akka.http.scaladsl.Http import akka.stream.ActorMaterializer -import com.typesafe.config.ConfigFactory +import com.typesafe.config.Config +import cromwell.engine.CromwellTerminator import cromwell.engine.backend.{BackendConfiguration, CromwellBackends} import cromwell.languages.config.{CromwellLanguages, LanguageConfiguration} import cromwell.services.{EngineServicesStore, MetadataServicesStore} import scala.concurrent.Future -trait CromwellSystem { +trait CromwellSystem extends CromwellTerminator { /* Initialize the service stores and their respective data access objects, if they haven't been already, before starting any services such as Metadata refresh and especially HTTP server binding. Some services like HTTP binding terminate @@ -26,10 +28,10 @@ trait CromwellSystem { MetadataServicesStore.metadataDatabaseInterface protected def systemName = "cromwell-system" - val conf = ConfigFactory.load() - + def config: Config + protected def newActorSystem(): ActorSystem = { - val system = ActorSystem(systemName, conf) + val system = ActorSystem(systemName, config) // This sets up our own dead letter listener that we can shut off during shutdown lazy val deadLetterListener = system.actorOf(Props(classOf[CromwellDeadLetterListener]), "DeadLetterListenerActor") system.eventStream.subscribe(deadLetterListener, classOf[DeadLetter]) @@ -40,6 +42,10 @@ trait CromwellSystem { implicit final lazy val materializer = ActorMaterializer() implicit private final lazy val ec = actorSystem.dispatcher + override def beginCromwellShutdown(reason: CoordinatedShutdown.Reason): Future[Done] = { + CromwellShutdown.instance(actorSystem).run(reason) + } + def shutdownActorSystem(): Future[Terminated] = { // If the actor system is already terminated it's already too late for a clean shutdown // Note: This does not protect again starting 2 shutdowns concurrently diff --git a/engine/src/test/scala/cromwell/engine/MockCromwellTerminator.scala b/engine/src/test/scala/cromwell/engine/MockCromwellTerminator.scala new file mode 100644 index 00000000000..1ef165ce14b --- /dev/null +++ b/engine/src/test/scala/cromwell/engine/MockCromwellTerminator.scala @@ -0,0 +1,12 @@ +package cromwell.engine + +import akka.Done +import akka.actor.CoordinatedShutdown + +import scala.concurrent.Future + +object MockCromwellTerminator extends CromwellTerminator { + override def beginCromwellShutdown(notUsed: CoordinatedShutdown.Reason): Future[Done] = { + Future.successful(Done) + } +} diff --git a/engine/src/test/scala/cromwell/engine/workflow/workflowstore/SqlWorkflowStoreSpec.scala b/engine/src/test/scala/cromwell/engine/workflow/workflowstore/SqlWorkflowStoreSpec.scala index 0b45722fe8a..69a3b901f30 100644 --- a/engine/src/test/scala/cromwell/engine/workflow/workflowstore/SqlWorkflowStoreSpec.scala +++ b/engine/src/test/scala/cromwell/engine/workflow/workflowstore/SqlWorkflowStoreSpec.scala @@ -80,7 +80,7 @@ class SqlWorkflowStoreSpec extends FlatSpec with Matchers with ScalaFutures with _ = startableWorkflows.map(_.id).intersect(submissionResponses.map(_.id).toList) should be(empty) abortWorkflowId = submissionResponses.head.id _ <- workflowStore.switchOnHoldToSubmitted(abortWorkflowId) - _ <- workflowStore.writeWorkflowHeartbeats(Set((abortWorkflowId, OffsetDateTime.now))) + _ <- workflowStore.writeWorkflowHeartbeats(Set((abortWorkflowId, OffsetDateTime.now)), OffsetDateTime.now) workflowStoreAbortResponse <- workflowStore.aborting(abortWorkflowId) _ = workflowStoreAbortResponse should be(WorkflowStoreAbortResponse.AbortedOnHoldOrSubmitted) } yield ()).futureValue @@ -121,7 +121,7 @@ class SqlWorkflowStoreSpec extends FlatSpec with Matchers with ScalaFutures with WorkflowStoreState.Submitted.toString, WorkflowStoreState.Running.toString ) - _ <- workflowStore.writeWorkflowHeartbeats(Set((abortWorkflowId, OffsetDateTime.now))) + _ <- workflowStore.writeWorkflowHeartbeats(Set((abortWorkflowId, OffsetDateTime.now)), OffsetDateTime.now) workflowStoreAbortResponse <- workflowStore.aborting(abortWorkflowId) _ = workflowStoreAbortResponse should be(WorkflowStoreAbortResponse.AbortRequested) } yield ()).futureValue diff --git a/engine/src/test/scala/cromwell/engine/workflow/workflowstore/WorkflowHeartbeatConfigSpec.scala b/engine/src/test/scala/cromwell/engine/workflow/workflowstore/WorkflowHeartbeatConfigSpec.scala new file mode 100644 index 00000000000..cf5aa23694a --- /dev/null +++ b/engine/src/test/scala/cromwell/engine/workflow/workflowstore/WorkflowHeartbeatConfigSpec.scala @@ -0,0 +1,163 @@ +package cromwell.engine.workflow.workflowstore + +import com.typesafe.config.ConfigFactory +import common.exception.AggregatedMessageException +import org.scalatest.prop.TableDrivenPropertyChecks +import org.scalatest.{FlatSpec, Matchers} + +import scala.concurrent.duration._ + +class WorkflowHeartbeatConfigSpec extends FlatSpec with Matchers with TableDrivenPropertyChecks { + + behavior of "WorkflowHeartbeatConfig" + + it should "generate a default config" in { + val workflowHeartbeatConfig = WorkflowHeartbeatConfig(WorkflowHeartbeatConfigSpec.DefaultConfig) + workflowHeartbeatConfig.cromwellId should startWith("cromid-") + workflowHeartbeatConfig.cromwellId should have length 14 + workflowHeartbeatConfig.heartbeatInterval should be (2.minutes) + workflowHeartbeatConfig.ttl should be(10.minutes) + workflowHeartbeatConfig.failureShutdownDuration should be(5.minutes) + workflowHeartbeatConfig.writeBatchSize should be(10000) + workflowHeartbeatConfig.writeThreshold should be(10000) + } + + private val validConfigTests = Table( + ("description", "config", "expected"), + ( + "from an empty config", + "system.cromwell_id_random_suffix = false", + WorkflowHeartbeatConfig("cromid", 2.minutes, 10.minutes, 5.minutes, 10000, 10000), + ), + ( + "with a specified cromid", + """system.cromwell_id = "new_crom_name"""", + WorkflowHeartbeatConfig("new_crom_name", 2.minutes, 10.minutes, 5.minutes, 10000, 10000), + ), + ( + "with a specified heartbeat interval", + "system.workflow-heartbeats.heartbeat-interval = 3 minutes", + WorkflowHeartbeatConfig("cromid", 3.minutes, 10.minutes, 5.minutes, 10000, 10000), + ), + ( + "with a specified ttl", + "system.workflow-heartbeats.ttl = 5 minutes", + WorkflowHeartbeatConfig("cromid", 2.minutes, 5.minutes, 5.minutes, 10000, 10000), + ), + ( + "with a ttl less than the default heartbeat interval", + """|system.workflow-heartbeats.ttl = 1 minutes + |system.workflow-heartbeats.heartbeat-interval = 59 seconds + |system.workflow-heartbeats.write-failure-shutdown-duration = 0 minutes + |""".stripMargin, + WorkflowHeartbeatConfig("cromid", 59.seconds, 1.minutes, 0.minutes, 10000, 10000), + ), + ( + "with a specified shutdown duration", + "system.workflow-heartbeats.write-failure-shutdown-duration = 1 minute", + WorkflowHeartbeatConfig("cromid", 2.minutes, 10.minutes, 1.minute, 10000, 10000), + ), + ( + "with a specified batch size", + "system.workflow-heartbeats.write-batch-size = 2000", + WorkflowHeartbeatConfig("cromid", 2.minutes, 10.minutes, 5.minutes, 2000, 10000), + ), + ( + "with a specified threshold", + "system.workflow-heartbeats.write-threshold = 5000", + WorkflowHeartbeatConfig("cromid", 2.minutes, 10.minutes, 5.minutes, 10000, 5000), + ), + ( + "when trying to set the ttl below the minimum", + """|system.workflow-heartbeats.ttl = 9 seconds + |system.workflow-heartbeats.heartbeat-interval = 8 seconds + |system.workflow-heartbeats.write-failure-shutdown-duration = 0 minutes + |""".stripMargin, + WorkflowHeartbeatConfig("cromid", 8.seconds, 10.seconds, 0.minutes, 10000, 10000), + ), + ( + "when trying to set the interval below the minimum", + "system.workflow-heartbeats.heartbeat-interval = 3 seconds", + WorkflowHeartbeatConfig("cromid", 10.seconds / 3, 10.minutes, 5.minutes, 10000, 10000), + ), + ( + "when trying to set a negative shutdown duration", + "system.workflow-heartbeats.write-failure-shutdown-duration = -1 seconds", + WorkflowHeartbeatConfig("cromid", 2.minutes, 10.minutes, 0.minutes, 10000, 10000), + ), + ) + + forAll(validConfigTests) { (description, configString, expected) => + it should s"create an instance $description" in { + val config = ConfigFactory.parseString( + // Remove the randomness from the cromid + s"""|system.cromwell_id_random_suffix = false + |$configString + |""".stripMargin + ).withFallback(WorkflowHeartbeatConfigSpec.DefaultConfig) + WorkflowHeartbeatConfig(config) should be(expected) + } + } + + private val invalidConfigTests = Table( + ("description", "config", "expected"), + ( + "when the heartbeat interval is equal to the ttl", + """|system.workflow-heartbeats { + | ttl = 2 minutes + | write-failure-shutdown-duration = 0 seconds + |} + |""".stripMargin, + AggregatedMessageException( + "Errors parsing WorkflowHeartbeatConfig", + List( + "The system.workflow-heartbeats.heartbeat-interval (2 minutes)" + + " is not less than the system.workflow-heartbeats.ttl (2 minutes).", + ) + ) + ), + ( + "when the heartbeat interval is not less than the ttl", + """|system.workflow-heartbeats { + | ttl = 1 minute + | write-failure-shutdown-duration = 0 seconds + |} + |""".stripMargin, + AggregatedMessageException( + "Errors parsing WorkflowHeartbeatConfig", + List( + "The system.workflow-heartbeats.heartbeat-interval (2 minutes)" + + " is not less than the system.workflow-heartbeats.ttl (1 minute).", + ) + ) + ), + ( + "when the failure duration is greater than the ttl", + """|system.workflow-heartbeats { + | ttl = 5 minutes + | write-failure-shutdown-duration = 301 seconds + |} + |""".stripMargin, + AggregatedMessageException( + "Errors parsing WorkflowHeartbeatConfig", + List( + "The system.workflow-heartbeats.write-failure-shutdown-duration (301 seconds)" + + " is greater than the system.workflow-heartbeats.ttl (5 minutes).", + ) + ) + ), + ) + + forAll(invalidConfigTests) { (description, configString, expected: AggregatedMessageException) => + it should s"fail to create an instance $description" in { + val config = ConfigFactory.parseString(configString).withFallback(WorkflowHeartbeatConfigSpec.DefaultConfig) + val exception = the[AggregatedMessageException] thrownBy WorkflowHeartbeatConfig(config) + exception.getMessage should be(expected.getMessage) + exception.errorMessages should contain theSameElementsAs expected.errorMessages + } + } +} + +object WorkflowHeartbeatConfigSpec { + val DefaultConfig = ConfigFactory.load() +} diff --git a/engine/src/test/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreCoordinatedAccessActorSpec.scala b/engine/src/test/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreCoordinatedAccessActorSpec.scala index 9052395acf7..dae90032f3a 100644 --- a/engine/src/test/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreCoordinatedAccessActorSpec.scala +++ b/engine/src/test/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreCoordinatedAccessActorSpec.scala @@ -31,13 +31,14 @@ class WorkflowStoreCoordinatedAccessActorSpec extends TestKitSuite("WorkflowStor it should "writeHeartBeats" in { val expected = 12345 val workflowStore = new InMemoryWorkflowStore { - override def writeWorkflowHeartbeats(workflowIds: Set[(WorkflowId, OffsetDateTime)]) + override def writeWorkflowHeartbeats(workflowIds: Set[(WorkflowId, OffsetDateTime)], + heartbeatDateTime: OffsetDateTime) (implicit ec: ExecutionContext): Future[Int] = { Future.successful(expected) } } val actor = TestActorRef(new WorkflowStoreCoordinatedAccessActor(workflowStore)) - val request = WriteHeartbeats(NonEmptyVector.of((WorkflowId.randomId(), OffsetDateTime.now))) + val request = WriteHeartbeats(NonEmptyVector.of((WorkflowId.randomId(), OffsetDateTime.now)), OffsetDateTime.now) implicit val timeout: Timeout = Timeout(2.seconds.dilated) actor.ask(request).mapTo[Int] map { actual => actual should be(expected) @@ -113,13 +114,14 @@ class WorkflowStoreCoordinatedAccessActorSpec extends TestKitSuite("WorkflowStor forAll(failureResponses) { (description, result, expectedException, expectedMessagePrefix) => it should s"fail to writeHeartBeats due to $description" in { val workflowStore = new InMemoryWorkflowStore { - override def writeWorkflowHeartbeats(workflowIds: Set[(WorkflowId, OffsetDateTime)]) + override def writeWorkflowHeartbeats(workflowIds: Set[(WorkflowId, OffsetDateTime)], + heartbeatDateTime: OffsetDateTime) (implicit ec: ExecutionContext): Future[Nothing] = { result() } } val actor = TestActorRef(new WorkflowStoreCoordinatedAccessActor(workflowStore)) - val request = WriteHeartbeats(NonEmptyVector.of((WorkflowId.randomId(), OffsetDateTime.now))) + val request = WriteHeartbeats(NonEmptyVector.of((WorkflowId.randomId(), OffsetDateTime.now)), OffsetDateTime.now) implicit val timeout: Timeout = Timeout(2.seconds.dilated) actor.ask(request).failed map { actual => actual.getMessage should startWith(expectedMessagePrefix) diff --git a/engine/src/test/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreHeartbeatWriteActorSpec.scala b/engine/src/test/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreHeartbeatWriteActorSpec.scala new file mode 100644 index 00000000000..a83782bc140 --- /dev/null +++ b/engine/src/test/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreHeartbeatWriteActorSpec.scala @@ -0,0 +1,75 @@ +package cromwell.engine.workflow.workflowstore + +import java.time.OffsetDateTime + +import akka.Done +import akka.actor.CoordinatedShutdown +import com.typesafe.config.ConfigFactory +import cromwell.core.{TestKitSuite, WorkflowId} +import cromwell.engine.CromwellTerminator +import cromwell.engine.workflow.workflowstore.WorkflowStoreActor.WorkflowStoreWriteHeartbeatCommand +import org.scalatest.concurrent.Eventually +import org.scalatest.{FlatSpecLike, Matchers} + +import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContext, Future} +import scala.util.control.NoStackTrace + +class WorkflowStoreHeartbeatWriteActorSpec extends TestKitSuite("WorkflowStoreHeartbeatWriteActorSpec") + with FlatSpecLike with Matchers with Eventually { + + behavior of "WorkflowStoreHeartbeatWriteActor" + + it should "send a shutdown signal when unable to write workflow heartbeats" in { + + var shutdownCalled = false + + val workflowStore = new InMemoryWorkflowStore { + override def writeWorkflowHeartbeats(workflowIds: Set[(WorkflowId, OffsetDateTime)], + heartbeatDateTime: OffsetDateTime) + (implicit ec: ExecutionContext): Future[Int] = { + Future.failed(new RuntimeException("this is expected") with NoStackTrace) + } + } + + val workflowStoreAccess = UncoordinatedWorkflowStoreAccess(workflowStore) + val workflowHeartbeatTypesafeConfig = ConfigFactory.parseString( + """|danger.debug.only.minimum-heartbeat-ttl = 10 ms + |system.workflow-heartbeats { + | heartbeat-interval = 500 ms + | write-batch-size = 1 + | write-failure-shutdown-duration = 1 s + |} + |""".stripMargin + ).withFallback(ConfigFactory.load()) + val workflowHeartbeatConfig = WorkflowHeartbeatConfig(workflowHeartbeatTypesafeConfig) + val terminator = new CromwellTerminator { + override def beginCromwellShutdown(reason: CoordinatedShutdown.Reason): Future[Done] = { + shutdownCalled = true + Future.successful(Done) + } + } + val serviceRegistryActor = emptyActor + val workflowStoreHeartbeatWriteActor = system.actorOf( + WorkflowStoreHeartbeatWriteActor.props( + workflowStoreAccess, + workflowHeartbeatConfig, + terminator, + serviceRegistryActor + ), + "WorkflowStoreHeartbeatWriteActor" + ) + + val workflowId = WorkflowId.randomId() + val submissionTime = OffsetDateTime.now() + + implicit val patienceConfig = PatienceConfig(timeout = 30.seconds, interval = 200.milliseconds) + eventually { + if (!shutdownCalled) { + // Workflow heartbeats are not automatically retried. Instead, write the heartbeat each time we retry. + workflowStoreHeartbeatWriteActor ! WorkflowStoreWriteHeartbeatCommand(workflowId, submissionTime) + fail("shutdown was not called") + } + } + } +} diff --git a/server/src/main/scala/cromwell/CromwellEntryPoint.scala b/server/src/main/scala/cromwell/CromwellEntryPoint.scala index ac0b4c0b88f..5a9b41d758a 100644 --- a/server/src/main/scala/cromwell/CromwellEntryPoint.scala +++ b/server/src/main/scala/cromwell/CromwellEntryPoint.scala @@ -60,7 +60,14 @@ object CromwellEntryPoint extends GracefulStopSupport { implicit val actorSystem = cromwellSystem.actorSystem val sources = validateRunArguments(args) - val runnerProps = SingleWorkflowRunnerActor.props(sources, args.metadataOutput, gracefulShutdown, abortJobsOnTerminate.getOrElse(true))(cromwellSystem.materializer) + val runnerProps = SingleWorkflowRunnerActor.props( + source = sources, + metadataOutputFile = args.metadataOutput, + terminator = cromwellSystem, + gracefulShutdown = gracefulShutdown, + abortJobsOnTerminate = abortJobsOnTerminate.getOrElse(true), + config = cromwellSystem.config + )(cromwellSystem.materializer) val runner = cromwellSystem.actorSystem.actorOf(runnerProps, "SingleWorkflowRunnerActor") @@ -107,7 +114,9 @@ object CromwellEntryPoint extends GracefulStopSupport { initLogging(command) lazy val Log = LoggerFactory.getLogger("cromwell") Try { - new CromwellSystem {} + new CromwellSystem { + override lazy val config = CromwellEntryPoint.config + } } recoverWith { case t: Throwable => Log.error(s"Failed to instantiate Cromwell System. Shutting down Cromwell.", t) diff --git a/server/src/test/scala/cromwell/CromwellTestKitSpec.scala b/server/src/test/scala/cromwell/CromwellTestKitSpec.scala index c0175d64bcd..68bb814faa2 100644 --- a/server/src/test/scala/cromwell/CromwellTestKitSpec.scala +++ b/server/src/test/scala/cromwell/CromwellTestKitSpec.scala @@ -14,6 +14,7 @@ import cromwell.core.path.BetterFileMethods.Cmds import cromwell.core.path.DefaultPathBuilder import cromwell.docker.DockerInfoActor.{DockerInfoSuccessResponse, DockerInformation} import cromwell.docker.{DockerHashResult, DockerInfoRequest} +import cromwell.engine.MockCromwellTerminator import cromwell.engine.workflow.WorkflowManagerActor.RetrieveNewWorkflows import cromwell.engine.workflow.lifecycle.execution.callcaching.CallCacheReadActor.{CacheLookupNoHit, CacheLookupRequest} import cromwell.engine.workflow.lifecycle.execution.callcaching.CallCacheWriteActor.SaveCallCacheHashes @@ -106,6 +107,8 @@ object CromwellTestKitSpec { private val testWorkflowManagerSystemCount = new AtomicInteger() class TestWorkflowManagerSystem extends CromwellSystem { + override val config = CromwellTestKitSpec.DefaultConfig + override protected def systemName: String = "test-system-" + testWorkflowManagerSystemCount.incrementAndGet() override protected def newActorSystem() = ActorSystem(systemName, ConfigFactory.parseString(CromwellTestKitSpec.ConfigText)) /** @@ -175,7 +178,9 @@ object CromwellTestKitSpec { } } - lazy val DefaultConfig = ConfigFactory.load.withValue( + lazy val DefaultConfig = ConfigFactory.load + + lazy val NooPServiceActorConfig = DefaultConfig.withValue( "services.LoadController.class", ConfigValueFactory.fromAnyRef("cromwell.services.NooPServiceActor") ) @@ -229,10 +234,12 @@ object CromwellTestKitSpec { private val ServiceRegistryActorSystem = akka.actor.ActorSystem("cromwell-service-registry-system") val ServiceRegistryActorInstance = { - ServiceRegistryActorSystem.actorOf(ServiceRegistryActor.props(ConfigFactory.load()), "ServiceRegistryActor") + ServiceRegistryActorSystem.actorOf(ServiceRegistryActor.props(CromwellTestKitSpec.DefaultConfig), "ServiceRegistryActor") } - class TestCromwellRootActor(override val config: Config)(implicit materializer: ActorMaterializer) extends CromwellRootActor(false, false, serverMode = true) { + class TestCromwellRootActor(config: Config)(implicit materializer: ActorMaterializer) + extends CromwellRootActor(MockCromwellTerminator, false, false, serverMode = true, config = config) { + override lazy val serviceRegistryActor = ServiceRegistryActorInstance override lazy val workflowStore = new InMemoryWorkflowStore override lazy val subWorkflowStore = new InMemorySubWorkflowStore(workflowStore) @@ -308,7 +315,7 @@ abstract class CromwellTestKitSpec(val twms: TestWorkflowManagerSystem = default workflowOptions: String = "{}", customLabels: String = "{}", terminalState: WorkflowState = WorkflowSucceeded, - config: Config = DefaultConfig, + config: Config = NooPServiceActorConfig, patienceConfig: PatienceConfig = defaultPatience)(implicit ec: ExecutionContext): Map[FullyQualifiedName, WomValue] = { val rootActor = buildCromwellRootActor(config) @@ -334,7 +341,7 @@ abstract class CromwellTestKitSpec(val twms: TestWorkflowManagerSystem = default workflowOptions: String = "{}", allowOtherOutputs: Boolean = true, terminalState: WorkflowState = WorkflowSucceeded, - config: Config = DefaultConfig, + config: Config = NooPServiceActorConfig, patienceConfig: PatienceConfig = defaultPatience) (implicit ec: ExecutionContext): WorkflowId = { val rootActor = buildCromwellRootActor(config) diff --git a/server/src/test/scala/cromwell/engine/WorkflowManagerActorSpec.scala b/server/src/test/scala/cromwell/engine/WorkflowManagerActorSpec.scala index 8955e8909e5..3c428f04371 100644 --- a/server/src/test/scala/cromwell/engine/WorkflowManagerActorSpec.scala +++ b/server/src/test/scala/cromwell/engine/WorkflowManagerActorSpec.scala @@ -78,7 +78,7 @@ class WorkflowManagerActorSpec extends CromwellTestKitWordSpec with WorkflowDesc ) } - val config = CromwellTestKitSpec.DefaultConfig. + val config = CromwellTestKitSpec.NooPServiceActorConfig. withValue("system.max-concurrent-workflows", ConfigValueFactory.fromAnyRef(2)). withValue("system.new-workflow-poll-rate", ConfigValueFactory.fromAnyRef(1)) diff --git a/server/src/test/scala/cromwell/engine/WorkflowStoreActorSpec.scala b/server/src/test/scala/cromwell/engine/WorkflowStoreActorSpec.scala index 800dc5bb650..dc7f3779fa3 100644 --- a/server/src/test/scala/cromwell/engine/WorkflowStoreActorSpec.scala +++ b/server/src/test/scala/cromwell/engine/WorkflowStoreActorSpec.scala @@ -4,7 +4,7 @@ import java.time.OffsetDateTime import akka.testkit._ import cats.data.{NonEmptyList, NonEmptyVector} -import com.typesafe.config.{Config, ConfigFactory} +import com.typesafe.config.Config import cromwell.core._ import cromwell.core.abort.{AbortResponse, WorkflowAbortFailureResponse, WorkflowAbortRequestedResponse, WorkflowAbortedResponse} import cromwell.database.slick.EngineSlickDatabase @@ -41,7 +41,7 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor val helloCwlWorldSourceFiles = HelloWorld.asWorkflowSources(workflowType = Option("CWL"), workflowTypeVersion = Option("v1.0")) val cromwellId = "f00ba4" val heartbeatTtl = 1 hour - val rootConfig = ConfigFactory.load() + val rootConfig = CromwellTestKitSpec.DefaultConfig val databaseConfig = rootConfig.getConfig("database") /** @@ -71,14 +71,34 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor "The WorkflowStoreActor" should { "return an ID for a submitted workflow" in { val store = new InMemoryWorkflowStore - val storeActor = system.actorOf(WorkflowStoreActor.props(store, store |> access, CromwellTestKitSpec.ServiceRegistryActorInstance, abortAllJobsOnTerminate = false, workflowHeartbeatConfig)) + val storeActor = system.actorOf( + WorkflowStoreActor.props( + store, + store |> access, + CromwellTestKitSpec.ServiceRegistryActorInstance, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-ReturnIdForSubmitted" + ) storeActor ! SubmitWorkflow(helloWorldSourceFiles) expectMsgType[WorkflowSubmittedToStore](10 seconds) } "check if workflow state is 'On Hold' when workflowOnHold = true" in { val store = new InMemoryWorkflowStore - val storeActor = system.actorOf(WorkflowStoreActor.props(store, store |> access, CromwellTestKitSpec.ServiceRegistryActorInstance, abortAllJobsOnTerminate = false, workflowHeartbeatConfig)) + val storeActor = system.actorOf( + WorkflowStoreActor.props( + store, + store |> access, + CromwellTestKitSpec.ServiceRegistryActorInstance, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-CheckOnHold" + ) storeActor ! SubmitWorkflow(helloWorldSourceFilesOnHold) expectMsgPF(10 seconds) { case submit: WorkflowSubmittedToStore => submit.state shouldBe WorkflowOnHold @@ -87,7 +107,17 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor "return 3 IDs for a batch submission of 3" in { val store = new InMemoryWorkflowStore - val storeActor = system.actorOf(WorkflowStoreActor.props(store, store |> access, CromwellTestKitSpec.ServiceRegistryActorInstance, abortAllJobsOnTerminate = false, workflowHeartbeatConfig)) + val storeActor = system.actorOf( + WorkflowStoreActor.props( + store, + store |> access, + CromwellTestKitSpec.ServiceRegistryActorInstance, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-ReturnIdsForBatch" + ) storeActor ! BatchSubmitWorkflows(NonEmptyList.of(helloWorldSourceFiles, helloWorldSourceFiles, helloWorldSourceFiles)) expectMsgPF(10 seconds) { case WorkflowsBatchSubmittedToStore(ids, WorkflowSubmitted) => ids.toList.size shouldBe 3 @@ -96,7 +126,17 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor "fetch exactly N workflows" in { val store = new InMemoryWorkflowStore - val storeActor = system.actorOf(WorkflowStoreActor.props(store, store |> access, CromwellTestKitSpec.ServiceRegistryActorInstance, abortAllJobsOnTerminate = false, workflowHeartbeatConfig)) + val storeActor = system.actorOf( + WorkflowStoreActor.props( + store, + store |> access, + CromwellTestKitSpec.ServiceRegistryActorInstance, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-FetchExactlyN" + ) storeActor ! BatchSubmitWorkflows(NonEmptyList.of(helloWorldSourceFiles, helloWorldSourceFiles, helloCwlWorldSourceFiles)) val insertedIds = expectMsgType[WorkflowsBatchSubmittedToStore](10 seconds).workflowIds.toList @@ -139,8 +179,21 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor val store = new InMemoryWorkflowStore - val storeActor = system.actorOf(WorkflowStoreActor.props(store, store |> access, CromwellTestKitSpec.ServiceRegistryActorInstance, abortAllJobsOnTerminate = false, workflowHeartbeatConfig)) - val readMetadataActor = system.actorOf(ReadMetadataActor.props()) + val storeActor = system.actorOf( + WorkflowStoreActor.props( + store, + store |> access, + CromwellTestKitSpec.ServiceRegistryActorInstance, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-FetchEncryptedWorkflowOptions" + ) + val readMetadataActor = system.actorOf( + ReadMetadataActor.props(), + "ReadMetadataActor-FetchEncryptedOptions" + ) storeActor ! BatchSubmitWorkflows(NonEmptyList.of(optionedSourceFiles)) val insertedIds = expectMsgType[WorkflowsBatchSubmittedToStore](10 seconds).workflowIds.toList @@ -182,7 +235,17 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor "return only the remaining workflows if N is larger than size" in { val store = new InMemoryWorkflowStore - val storeActor = system.actorOf(WorkflowStoreActor.props(store, store |> access, CromwellTestKitSpec.ServiceRegistryActorInstance, abortAllJobsOnTerminate = false, workflowHeartbeatConfig)) + val storeActor = system.actorOf( + WorkflowStoreActor.props( + store, + store |> access, + CromwellTestKitSpec.ServiceRegistryActorInstance, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-ReturnOnlyRemaining" + ) storeActor ! BatchSubmitWorkflows(NonEmptyList.of(helloWorldSourceFiles, helloWorldSourceFiles, helloWorldSourceFiles)) val insertedIds = expectMsgType[WorkflowsBatchSubmittedToStore](10 seconds).workflowIds.toList @@ -202,7 +265,17 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor "remain responsive if you ask to remove a workflow it doesn't have" in { val store = new InMemoryWorkflowStore - val storeActor = system.actorOf(WorkflowStoreActor.props(store, store |> access, CromwellTestKitSpec.ServiceRegistryActorInstance, abortAllJobsOnTerminate = false, workflowHeartbeatConfig)) + val storeActor = system.actorOf( + WorkflowStoreActor.props( + store, + store |> access, + CromwellTestKitSpec.ServiceRegistryActorInstance, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-RemainResponsiveForUnknown" + ) storeActor ! FetchRunnableWorkflows(100) expectMsgPF(10 seconds) { @@ -213,13 +286,17 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor "abort an on hold workflow" in { val store = new InMemoryWorkflowStore - val storeActor = system.actorOf(WorkflowStoreActor.props( - store, - store |> access, - CromwellTestKitSpec.ServiceRegistryActorInstance, - abortAllJobsOnTerminate = false, - workflowHeartbeatConfig - )) + val storeActor = system.actorOf( + WorkflowStoreActor.props( + store, + store |> access, + CromwellTestKitSpec.ServiceRegistryActorInstance, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-AbortOnHold" + ) storeActor ! SubmitWorkflow(helloWorldSourceFiles.copy(workflowOnHold = true)) val workflowId = expectMsgType[WorkflowSubmittedToStore](10.seconds).workflowId storeActor ! AbortWorkflowCommand(workflowId) @@ -230,13 +307,17 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor "abort a submitted workflow with an empty heartbeat" in { runWithDatabase(databaseConfig) { store => - val storeActor = system.actorOf(WorkflowStoreActor.props( - store, - store |> access, - CromwellTestKitSpec.ServiceRegistryActorInstance, - abortAllJobsOnTerminate = false, - workflowHeartbeatConfig - )) + val storeActor = system.actorOf( + WorkflowStoreActor.props( + store, + store |> access, + CromwellTestKitSpec.ServiceRegistryActorInstance, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-AbortSubmittedEmptyHeartbeat" + ) storeActor ! SubmitWorkflow(helloWorldSourceFiles) val workflowId = expectMsgType[WorkflowSubmittedToStore](10.seconds).workflowId storeActor ! AbortWorkflowCommand(workflowId) @@ -250,16 +331,21 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor runWithDatabase(databaseConfig) { store => val coordinatedAccess = store |> access - val storeActor = system.actorOf(WorkflowStoreActor.props( - store, - coordinatedAccess, - CromwellTestKitSpec.ServiceRegistryActorInstance, - abortAllJobsOnTerminate = false, - workflowHeartbeatConfig - )) + val storeActor = system.actorOf( + WorkflowStoreActor.props( + store, + coordinatedAccess, + CromwellTestKitSpec.ServiceRegistryActorInstance, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-AbortSubmittedNonEmptyHeartbeat" + ) storeActor ! SubmitWorkflow(helloWorldSourceFiles) val workflowId = expectMsgType[WorkflowSubmittedToStore](10.seconds).workflowId - coordinatedAccess.actor ! WriteHeartbeats(NonEmptyVector.of((workflowId, OffsetDateTime.now()))) + coordinatedAccess.actor ! + WriteHeartbeats(NonEmptyVector.of((workflowId, OffsetDateTime.now())), OffsetDateTime.now()) expectMsg(10.seconds, 1) storeActor ! AbortWorkflowCommand(workflowId) val abortResponse = expectMsgType[AbortResponse](10.seconds) @@ -270,13 +356,17 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor "abort a running workflow with an empty heartbeat" in { runWithDatabase(databaseConfig) { store => - val storeActor = system.actorOf(WorkflowStoreActor.props( - store, - store |> access, - CromwellTestKitSpec.ServiceRegistryActorInstance, - abortAllJobsOnTerminate = false, - workflowHeartbeatConfig - )) + val storeActor = system.actorOf( + WorkflowStoreActor.props( + store, + store |> access, + CromwellTestKitSpec.ServiceRegistryActorInstance, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-AbortRunningEmptyHeartbeat" + ) storeActor ! SubmitWorkflow(helloWorldSourceFiles) val workflowId = expectMsgType[WorkflowSubmittedToStore](10.seconds).workflowId @@ -299,13 +389,17 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor "abort a running workflow with a non-empty heartbeat" in { runWithDatabase(databaseConfig) { store => val coordinatedAccess = store |> access - val storeActor = system.actorOf(WorkflowStoreActor.props( - store, - coordinatedAccess, - CromwellTestKitSpec.ServiceRegistryActorInstance, - abortAllJobsOnTerminate = false, - workflowHeartbeatConfig - )) + val storeActor = system.actorOf( + WorkflowStoreActor.props( + store, + coordinatedAccess, + CromwellTestKitSpec.ServiceRegistryActorInstance, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-AbortRunningNonEmptyHeartbeat" + ) storeActor ! SubmitWorkflow(helloWorldSourceFiles) val workflowId = expectMsgType[WorkflowSubmittedToStore](10.seconds).workflowId @@ -318,7 +412,8 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor Await.result(futureUpdate, 10.seconds.dilated) should be(1) - coordinatedAccess.actor ! WriteHeartbeats(NonEmptyVector.of((workflowId, OffsetDateTime.now()))) + coordinatedAccess.actor ! + WriteHeartbeats(NonEmptyVector.of((workflowId, OffsetDateTime.now())), OffsetDateTime.now()) expectMsg(10.seconds, 1) storeActor ! AbortWorkflowCommand(workflowId) @@ -330,13 +425,17 @@ class WorkflowStoreActorSpec extends CromwellTestKitWordSpec with CoordinatedWor "not abort a not found workflow" in { val store = new InMemoryWorkflowStore - val storeActor = system.actorOf(WorkflowStoreActor.props( - store, - store |> access, - CromwellTestKitSpec.ServiceRegistryActorInstance, - abortAllJobsOnTerminate = false, - workflowHeartbeatConfig - )) + val storeActor = system.actorOf( + WorkflowStoreActor.props( + store, + store |> access, + CromwellTestKitSpec.ServiceRegistryActorInstance, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-AbortNotFound" + ) val notFoundWorkflowId = WorkflowId.fromString("7ff8dff3-bc80-4500-af3b-57dbe7a6ecbb") storeActor ! AbortWorkflowCommand(notFoundWorkflowId) val abortResponse = expectMsgType[AbortResponse](10 seconds) diff --git a/server/src/test/scala/cromwell/engine/workflow/SingleWorkflowRunnerActorSpec.scala b/server/src/test/scala/cromwell/engine/workflow/SingleWorkflowRunnerActorSpec.scala index 7da00ad2602..29ddac905a0 100644 --- a/server/src/test/scala/cromwell/engine/workflow/SingleWorkflowRunnerActorSpec.scala +++ b/server/src/test/scala/cromwell/engine/workflow/SingleWorkflowRunnerActorSpec.scala @@ -7,16 +7,16 @@ import akka.pattern.ask import akka.stream.ActorMaterializer import akka.testkit._ import akka.util.Timeout -import com.typesafe.config.ConfigFactory import cromwell.CromwellTestKitSpec._ import cromwell._ import cromwell.core.path.{DefaultPathBuilder, Path} import cromwell.core.{SimpleIoActor, WorkflowSourceFilesCollection} +import cromwell.engine.MockCromwellTerminator import cromwell.engine.backend.BackendSingletonCollection import cromwell.engine.workflow.SingleWorkflowRunnerActor.RunWorkflow import cromwell.engine.workflow.SingleWorkflowRunnerActorSpec._ -import cromwell.engine.workflow.tokens.JobExecutionTokenDispenserActor import cromwell.engine.workflow.tokens.DynamicRateLimiter.Rate +import cromwell.engine.workflow.tokens.JobExecutionTokenDispenserActor import cromwell.engine.workflow.workflowstore._ import cromwell.util.SampleWdl import cromwell.util.SampleWdl.{ExpressionsInInputs, GoodbyeWorld, ThreeStep} @@ -52,29 +52,53 @@ object SingleWorkflowRunnerActorSpec { class TestSingleWorkflowRunnerActor(source: WorkflowSourceFilesCollection, metadataOutputPath: Option[Path])(implicit materializer: ActorMaterializer) - extends SingleWorkflowRunnerActor(source, metadataOutputPath, false, false) { + extends SingleWorkflowRunnerActor( + source = source, + metadataOutputPath = metadataOutputPath, + terminator = MockCromwellTerminator, + gracefulShutdown = false, + abortJobsOnTerminate = false, + config = CromwellTestKitSpec.DefaultConfig + ) { override lazy val serviceRegistryActor = CromwellTestKitSpec.ServiceRegistryActorInstance override private [workflow] def done() = context.stop(self) } } abstract class SingleWorkflowRunnerActorSpec extends CromwellTestKitWordSpec with CoordinatedWorkflowStoreBuilder with Mockito { - private val workflowHeartbeatConfig = WorkflowHeartbeatConfig(ConfigFactory.load()) + private val workflowHeartbeatConfig = WorkflowHeartbeatConfig(CromwellTestKitSpec.DefaultConfig) val store = new InMemoryWorkflowStore private val workflowStore = - system.actorOf(WorkflowStoreActor.props(store, store |> access, dummyServiceRegistryActor, abortAllJobsOnTerminate = false, workflowHeartbeatConfig)) - private val serviceRegistry = TestProbe().ref - private val jobStore = system.actorOf(AlwaysHappyJobStoreActor.props) - private val ioActor = system.actorOf(SimpleIoActor.props) - private val subWorkflowStore = system.actorOf(AlwaysHappySubWorkflowStoreActor.props) - private val callCacheReadActor = system.actorOf(EmptyCallCacheReadActor.props) - private val callCacheWriteActor = system.actorOf(EmptyCallCacheWriteActor.props) - private val dockerHashActor = system.actorOf(EmptyDockerHashActor.props) - private val jobTokenDispenserActor = system.actorOf(JobExecutionTokenDispenserActor.props(serviceRegistry, Rate(100, 1.second), None)) + system.actorOf( + WorkflowStoreActor.props( + store, + store |> access, + dummyServiceRegistryActor, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor" + ) + private val serviceRegistry = TestProbe("ServiceRegistryProbe").ref + private val jobStore = system.actorOf(AlwaysHappyJobStoreActor.props, "AlwaysHappyJobStoreActor") + private val ioActor = system.actorOf(SimpleIoActor.props, "SimpleIoActor") + private val subWorkflowStore = system.actorOf( + AlwaysHappySubWorkflowStoreActor.props, + "AlwaysHappySubWorkflowStoreActor" + ) + private val callCacheReadActor = system.actorOf(EmptyCallCacheReadActor.props, "EmptyCallCacheReadActor") + private val callCacheWriteActor = system.actorOf(EmptyCallCacheWriteActor.props, "EmptyCallCacheWriteActor") + private val dockerHashActor = system.actorOf(EmptyDockerHashActor.props, "EmptyDockerHashActor") + private val jobTokenDispenserActor = system.actorOf( + JobExecutionTokenDispenserActor.props(serviceRegistry, Rate(100, 1.second), None), + "JobExecutionTokenDispenserActor" + ) def workflowManagerActor(): ActorRef = { - val params = WorkflowManagerActorParams(ConfigFactory.load(), + val params = WorkflowManagerActorParams( + CromwellTestKitSpec.DefaultConfig, workflowStore = workflowStore, ioActor = ioActor, serviceRegistryActor = dummyServiceRegistryActor, @@ -93,7 +117,10 @@ abstract class SingleWorkflowRunnerActorSpec extends CromwellTestKitWordSpec wit def createRunnerActor(sampleWdl: SampleWdl = ThreeStep, managerActor: => ActorRef = workflowManagerActor(), outputFile: => Option[Path] = None): ActorRef = { - system.actorOf(Props(new TestSingleWorkflowRunnerActor(sampleWdl.asWorkflowSources(), outputFile))) + system.actorOf( + Props(new TestSingleWorkflowRunnerActor(sampleWdl.asWorkflowSources(), outputFile)), + "TestSingleWorkflowRunnerActor" + ) } def singleWorkflowActor(sampleWdl: SampleWdl = ThreeStep, managerActor: => ActorRef = workflowManagerActor(), diff --git a/server/src/test/scala/cromwell/subworkflowstore/SubWorkflowStoreSpec.scala b/server/src/test/scala/cromwell/subworkflowstore/SubWorkflowStoreSpec.scala index 92a4e0f30d5..1091f405fee 100644 --- a/server/src/test/scala/cromwell/subworkflowstore/SubWorkflowStoreSpec.scala +++ b/server/src/test/scala/cromwell/subworkflowstore/SubWorkflowStoreSpec.scala @@ -1,11 +1,10 @@ package cromwell.subworkflowstore import akka.testkit.TestProbe -import com.typesafe.config.ConfigFactory -import cromwell.CromwellTestKitWordSpec import cromwell.core.ExecutionIndex._ import cromwell.core.{JobKey, WorkflowId, WorkflowSourceFilesWithoutImports} import cromwell.database.sql.tables.SubWorkflowStoreEntry +import cromwell.engine.MockCromwellTerminator import cromwell.engine.workflow.CoordinatedWorkflowStoreBuilder import cromwell.engine.workflow.workflowstore.WorkflowStoreActor.SubmitWorkflow import cromwell.engine.workflow.workflowstore.WorkflowStoreSubmitActor.WorkflowSubmittedToStore @@ -14,6 +13,7 @@ import cromwell.services.EngineServicesStore import cromwell.subworkflowstore.SubWorkflowStoreActor._ import cromwell.subworkflowstore.SubWorkflowStoreSpec._ import cromwell.util.WomMocks +import cromwell.{CromwellTestKitSpec, CromwellTestKitWordSpec} import mouse.all._ import org.scalatest.Matchers import org.specs2.mock.Mockito @@ -35,8 +35,18 @@ class SubWorkflowStoreSpec extends CromwellTestKitWordSpec with CoordinatedWorkf val subWorkflowStoreService = system.actorOf(SubWorkflowStoreActor.props(subWorkflowStore)) lazy val workflowStore = SqlWorkflowStore(EngineServicesStore.engineDatabaseInterface) - val workflowHeartbeatConfig = WorkflowHeartbeatConfig(ConfigFactory.load()) - val workflowStoreService = system.actorOf(WorkflowStoreActor.props(workflowStore, workflowStore |> access, TestProbe().ref, abortAllJobsOnTerminate = false, workflowHeartbeatConfig)) + val workflowHeartbeatConfig = WorkflowHeartbeatConfig(CromwellTestKitSpec.DefaultConfig) + val workflowStoreService = system.actorOf( + WorkflowStoreActor.props( + workflowStore, + workflowStore |> access, + TestProbe("ServiceRegistryProbe-Work").ref, + MockCromwellTerminator, + abortAllJobsOnTerminate = false, + workflowHeartbeatConfig + ), + "WorkflowStoreActor-Work" + ) val parentWorkflowId = WorkflowId.randomId() val subWorkflowId = WorkflowId.randomId() diff --git a/services/src/main/scala/cromwell/services/instrumentation/InstrumentedBatchActor.scala b/services/src/main/scala/cromwell/services/instrumentation/InstrumentedBatchActor.scala index 9bea0a1a5d6..09ee8e735c3 100644 --- a/services/src/main/scala/cromwell/services/instrumentation/InstrumentedBatchActor.scala +++ b/services/src/main/scala/cromwell/services/instrumentation/InstrumentedBatchActor.scala @@ -5,6 +5,7 @@ import cromwell.core.actor.BatchActor import cromwell.services.instrumentation.InstrumentedBatchActor.{QueueSizeTimerAction, QueueSizeTimerKey} import scala.concurrent.Future +import scala.util.{Failure, Success} object InstrumentedBatchActor { case object QueueSizeTimerKey @@ -26,6 +27,7 @@ trait InstrumentedBatchActor[C] { this: BatchActor[C] with CromwellInstrumentati instrumentationPath.concatNel(NonEmptyList.one(name)) private val processedPath = makePath("processed") + private val failurePath = makePath("failure") private val queueSizePath = makePath("queue") timers.startSingleTimer(QueueSizeTimerKey, QueueSizeTimerAction, CromwellInstrumentation.InstrumentationRate) @@ -49,7 +51,10 @@ trait InstrumentedBatchActor[C] { this: BatchActor[C] with CromwellInstrumentati */ protected def instrumentedProcess(f: => Future[Int]) = { val action = f - action foreach { n => count(processedPath, n.toLong, instrumentationPrefix) } + action onComplete { + case Success(numProcessed) => count(processedPath, numProcessed.toLong, instrumentationPrefix) + case Failure(_) => count(failurePath, 1L, instrumentationPrefix) + } action } } From c1fe3fa6516c40dfe2c08697fd4b0544ee2006c5 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Wed, 24 Apr 2019 08:27:54 -0400 Subject: [PATCH 13/46] Change "push" tests to be opt-in (#4839) --- src/ci/bin/test.inc.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ci/bin/test.inc.sh b/src/ci/bin/test.inc.sh index 7bcdbf81906..fd1292f666c 100644 --- a/src/ci/bin/test.inc.sh +++ b/src/ci/bin/test.inc.sh @@ -79,6 +79,8 @@ cromwell::private::create_build_variables() { CROMWELL_BUILD_IS_VIRTUAL_ENV=false fi + CROMWELL_BUILD_RUN_TESTS=true + case "${CROMWELL_BUILD_PROVIDER}" in "${CROMWELL_BUILD_PROVIDER_TRAVIS}") CROMWELL_BUILD_IS_CI=true @@ -98,6 +100,14 @@ cromwell::private::create_build_variables() { CROMWELL_BUILD_MYSQL_PASSWORD="" CROMWELL_BUILD_MYSQL_SCHEMA="cromwell_test" CROMWELL_BUILD_GENERATE_COVERAGE=true + + # Always run on sbt, even for 'push'. + # This allows quick sanity checks before starting PRs *and* publishing after merges into develop. + if [[ "${CROMWELL_BUILD_TYPE}" == "sbt" ]]; then + CROMWELL_BUILD_RUN_TESTS=true + elif [[ "${TRAVIS_COMMIT_MESSAGE}" != *"[force ci]"* ]] && [[ "${TRAVIS_EVENT_TYPE}" == "push" ]]; then + CROMWELL_BUILD_RUN_TESTS=false + fi ;; "${CROMWELL_BUILD_PROVIDER_JENKINS}") # External variables must be passed through in the ENVIRONMENT of src/ci/docker-compose/docker-compose.yml @@ -747,6 +757,10 @@ cromwell::private::kill_tree() { cromwell::build::exec_test_script() { cromwell::private::create_build_variables + if [[ "${CROMWELL_BUILD_RUN_TESTS}" == "false" ]]; then + echo "Use '[force ci]' in commit message to run tests on 'push'" + exit 0 + fi cromwell::private::exec_test_script } From e53e4c9eb6910943ffde7f9cc39a88b7f69c4b31 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Wed, 24 Apr 2019 08:29:58 -0400 Subject: [PATCH 14/46] Log workflow IDs and counts, and retry, if metadata fails to be written (#4838) --- .../collections/EnhancedCollections.scala | 4 +- .../scala/cromwell/MetadataWatchActor.scala | 4 +- .../routes/CromwellApiServiceSpec.scala | 2 +- .../services/metadata/MetadataService.scala | 7 +- .../metadata/impl/WriteMetadataActor.scala | 30 +++- .../impl/WriteMetadataActorSpec.scala | 141 +++++++++++++++--- 6 files changed, 160 insertions(+), 28 deletions(-) diff --git a/common/src/main/scala/common/collections/EnhancedCollections.scala b/common/src/main/scala/common/collections/EnhancedCollections.scala index f9311d4f13a..f573506c953 100644 --- a/common/src/main/scala/common/collections/EnhancedCollections.scala +++ b/common/src/main/scala/common/collections/EnhancedCollections.scala @@ -71,9 +71,9 @@ object EnhancedCollections { case (element, dequeued) => (element, weightFunction(element), dequeued) }) match { // If the element's weight is > maxWeight and strict is true, drop the element - case Some((_, elementWeight, dequeued)) if c.gt(elementWeight, maxWeight) && strict => takeWhileWeightedRec(dequeued, head, weight) + case Some((_, elementWeight, dequeued)) if c.gteq(elementWeight, maxWeight) && strict => takeWhileWeightedRec(dequeued, head, weight) // If we're under the max weight, add the element to the head and recurse - case Some((element, elementWeight, dequeued)) if c.lt(elementWeight + weight, maxWeight) => takeWhileWeightedRec(dequeued, head :+ element, weight + elementWeight) + case Some((element, elementWeight, dequeued)) if c.lteq(elementWeight + weight, maxWeight) => takeWhileWeightedRec(dequeued, head :+ element, weight + elementWeight) // Otherwise stop here (make sure to return the original queue so we don't lose the last dequeued element) case _ => head -> tail } diff --git a/engine/src/test/scala/cromwell/MetadataWatchActor.scala b/engine/src/test/scala/cromwell/MetadataWatchActor.scala index f8a14818713..6d276b61bbc 100644 --- a/engine/src/test/scala/cromwell/MetadataWatchActor.scala +++ b/engine/src/test/scala/cromwell/MetadataWatchActor.scala @@ -17,13 +17,13 @@ final case class MetadataWatchActor(promise: Promise[Unit], matchers: Matcher*) var unsatisfiedMatchers = matchers override def receive = { - case PutMetadataAction(events) if unsatisfiedMatchers.nonEmpty => + case PutMetadataAction(events, _) if unsatisfiedMatchers.nonEmpty => unsatisfiedMatchers = unsatisfiedMatchers.filterNot { m => m.matches(events) } if (unsatisfiedMatchers.isEmpty) { promise.trySuccess(()) () } - case PutMetadataAction(_) => // Superfluous message. Ignore + case PutMetadataAction(_, _) => // Superfluous message. Ignore // Because the MetadataWatchActor is sometimes used in place of the ServiceRegistryActor, this allows WFs to continue: case kvGet: KvGet => sender ! KvKeyLookupFailed(kvGet) case kvPut: KvPut => sender ! KvPutSuccess(kvPut) diff --git a/engine/src/test/scala/cromwell/webservice/routes/CromwellApiServiceSpec.scala b/engine/src/test/scala/cromwell/webservice/routes/CromwellApiServiceSpec.scala index 173847fae31..f32a87857ef 100644 --- a/engine/src/test/scala/cromwell/webservice/routes/CromwellApiServiceSpec.scala +++ b/engine/src/test/scala/cromwell/webservice/routes/CromwellApiServiceSpec.scala @@ -592,7 +592,7 @@ object CromwellApiServiceSpec { case GetSingleWorkflowMetadataAction(id, None, None, _) => sender ! MetadataLookupResponse(metadataQuery(id), fullMetadataResponse(id)) case GetSingleWorkflowMetadataAction(id, Some(_), None, _) => sender ! MetadataLookupResponse(metadataQuery(id), filteredMetadataResponse(id)) case GetSingleWorkflowMetadataAction(id, None, Some(_), _) => sender ! MetadataLookupResponse(metadataQuery(id), filteredMetadataResponse(id)) - case PutMetadataActionAndRespond(events, _) => + case PutMetadataActionAndRespond(events, _, _) => events.head.key.workflowId match { case CromwellApiServiceSpec.ExistingWorkflowId => sender ! MetadataWriteSuccess(events) case CromwellApiServiceSpec.SummarizedWorkflowId => sender ! MetadataWriteSuccess(events) diff --git a/services/src/main/scala/cromwell/services/metadata/MetadataService.scala b/services/src/main/scala/cromwell/services/metadata/MetadataService.scala index 81a1291fa41..ce9ae225de8 100644 --- a/services/src/main/scala/cromwell/services/metadata/MetadataService.scala +++ b/services/src/main/scala/cromwell/services/metadata/MetadataService.scala @@ -71,11 +71,14 @@ object MetadataService { } sealed trait MetadataWriteAction extends MetadataServiceAction { + def maxAttempts: Int def events: Iterable[MetadataEvent] def size: Int = events.size } - final case class PutMetadataAction(events: Iterable[MetadataEvent]) extends MetadataWriteAction - final case class PutMetadataActionAndRespond(events: Iterable[MetadataEvent], replyTo: ActorRef) extends MetadataWriteAction + + val MaximumMetadataWriteAttempts = 10 + final case class PutMetadataAction(events: Iterable[MetadataEvent], maxAttempts: Int = MaximumMetadataWriteAttempts) extends MetadataWriteAction + final case class PutMetadataActionAndRespond(events: Iterable[MetadataEvent], replyTo: ActorRef, maxAttempts: Int = MaximumMetadataWriteAttempts) extends MetadataWriteAction final case object ListenToMetadataWriteActor extends MetadataServiceAction with ListenToMessage diff --git a/services/src/main/scala/cromwell/services/metadata/impl/WriteMetadataActor.scala b/services/src/main/scala/cromwell/services/metadata/impl/WriteMetadataActor.scala index c755a8728a9..ace3ba520ee 100644 --- a/services/src/main/scala/cromwell/services/metadata/impl/WriteMetadataActor.scala +++ b/services/src/main/scala/cromwell/services/metadata/impl/WriteMetadataActor.scala @@ -37,16 +37,38 @@ class WriteMetadataActor(override val batchSize: Int, dbAction onComplete { case Success(_) => putWithResponse foreach { case (ev, replyTo) => replyTo ! MetadataWriteSuccess(ev) } - case Failure(regerts) => - val workflowMetadataFailureCounts = e.toVector.flatMap(_.events).groupBy(x => x.key.workflowId).map { case (wfid, list) => s"$wfid: ${list.size}" } - log.error("Metadata events permanently dropped for the following workflows: " + workflowMetadataFailureCounts.mkString(",")) + case Failure(cause) => - putWithResponse foreach { case (ev, replyTo) => replyTo ! MetadataWriteFailure(regerts, ev) } + val (outOfTries, stillGood) = e.toVector.partition(_.maxAttempts <= 1) + + handleOutOfTries(outOfTries, cause) + handleEventsToReconsider(stillGood) } dbAction.map(_ => allPutEvents.size) } + private def enumerateWorkflowWriteFailures(writeActions: Vector[MetadataWriteAction]): String = + writeActions.flatMap(_.events).groupBy(_.key.workflowId).map { case (wfid, list) => s"$wfid: ${list.size}" }.mkString(", ") + + private def handleOutOfTries(writeActions: Vector[MetadataWriteAction], reason: Throwable): Unit = if (writeActions.nonEmpty) { + log.error(reason, "Metadata event writes have failed irretrievably for the following workflows. They will be lost: " + enumerateWorkflowWriteFailures(writeActions)) + + writeActions foreach { + case PutMetadataActionAndRespond(ev, replyTo, _) => replyTo ! MetadataWriteFailure(reason, ev) + case _: PutMetadataAction => () // We need to satisfy the exhaustive match but there's nothing special to do here + } + } + + private def handleEventsToReconsider(writeActions: Vector[MetadataWriteAction]): Unit = if (writeActions.nonEmpty) { + log.warning("Metadata event writes have failed for the following workflows. They will be retried: " + enumerateWorkflowWriteFailures(writeActions)) + + writeActions foreach { + case action: PutMetadataAction => self ! action.copy(maxAttempts = action.maxAttempts - 1) + case action: PutMetadataActionAndRespond => self ! action.copy(maxAttempts = action.maxAttempts - 1) + } + } + // EnhancedBatchActor overrides override def receive = enhancedReceive.orElse(super.receive) override protected def weightFunction(command: MetadataWriteAction) = command.size diff --git a/services/src/test/scala/cromwell/services/metadata/impl/WriteMetadataActorSpec.scala b/services/src/test/scala/cromwell/services/metadata/impl/WriteMetadataActorSpec.scala index 23bdaed6eef..3d1e4c734b7 100644 --- a/services/src/test/scala/cromwell/services/metadata/impl/WriteMetadataActorSpec.scala +++ b/services/src/test/scala/cromwell/services/metadata/impl/WriteMetadataActorSpec.scala @@ -2,44 +2,116 @@ package cromwell.services.metadata.impl import java.sql.{Connection, Timestamp} +import akka.actor.ActorRef import akka.testkit.{TestFSMRef, TestProbe} -import cats.data.NonEmptyList +import cats.data.{NonEmptyList, NonEmptyVector} import com.typesafe.config.ConfigFactory import cromwell.core.{TestKitSuite, WorkflowId} import cromwell.database.sql.joins.MetadataJobQueryValue import cromwell.database.sql.tables.{MetadataEntry, WorkflowMetadataSummaryEntry} import cromwell.database.sql.{MetadataSqlDatabase, SqlDatabase} -import cromwell.services.metadata.MetadataService.{MetadataWriteSuccess, PutMetadataActionAndRespond} +import cromwell.services.metadata.MetadataService.{MetadataWriteAction, MetadataWriteFailure, MetadataWriteSuccess, PutMetadataAction, PutMetadataActionAndRespond} +import cromwell.services.metadata.impl.WriteMetadataActorSpec.BatchSizeCountingWriteMetadataActor import cromwell.services.metadata.{MetadataEvent, MetadataKey, MetadataValue} import org.scalatest.{FlatSpecLike, Matchers} import scala.concurrent.duration._ import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} +import scala.util.control.NoStackTrace +import org.scalatest.concurrent.Eventually +import org.scalatest.time.{Millis, Seconds, Span} -class WriteMetadataActorSpec extends TestKitSuite with FlatSpecLike with Matchers { +class WriteMetadataActorSpec extends TestKitSuite with FlatSpecLike with Matchers with Eventually { behavior of "WriteMetadataActor" - - it should "respond to all senders" in { + + implicit val defaultPatience = PatienceConfig(timeout = scaled(Span(10, Seconds)), interval = Span(500, Millis)) + + it should "process jobs in the correct batch sizes" in { val registry = TestProbe().ref - val writeActor = TestFSMRef(new WriteMetadataActor(10, 1.second, registry, Int.MaxValue) { - override val metadataDatabaseInterface = mockDatabaseInterface + val writeActor = TestFSMRef(new BatchSizeCountingWriteMetadataActor(10, 2.seconds, registry, Int.MaxValue) { + override val metadataDatabaseInterface = mockDatabaseInterface(0) }) - - val metadataKey = MetadataKey(WorkflowId.randomId(), None, "metadata_key") - val metadataEvent = MetadataEvent(metadataKey, MetadataValue("hello")) - - val probes = (1 until 20).map({ _ => + + def metadataEvent(index: Int) = PutMetadataAction(MetadataEvent(MetadataKey(WorkflowId.randomId(), None, s"metadata_key_$index"), MetadataValue(s"hello_$index"))) + + val probes = (0 until 27).map({ _ => val probe = TestProbe() - probe.send(writeActor, PutMetadataActionAndRespond(List(metadataEvent), probe.ref)) probe + }).zipWithIndex.map { + case (probe, index) => probe -> metadataEvent(index) + } + + probes foreach { + case (probe, msg) => probe.send(writeActor, msg) + } + + eventually { + writeActor.underlyingActor.batchSizes should be(Vector(10, 10, 7)) + } + } + + val failuresBetweenSuccessValues = List(0, 5, 9) + failuresBetweenSuccessValues foreach { failureRate => + it should s"succeed metadata writes and respond to all senders even with $failureRate failures between each success" in { + val registry = TestProbe().ref + val writeActor = TestFSMRef(new BatchSizeCountingWriteMetadataActor(10, 100.millis, registry, Int.MaxValue) { + override val metadataDatabaseInterface = mockDatabaseInterface(failureRate) + }) + + def metadataEvent(index: Int, probe: ActorRef) = PutMetadataActionAndRespond(List(MetadataEvent(MetadataKey(WorkflowId.randomId(), None, s"metadata_key_$index"), MetadataValue(s"hello_$index"))), probe) + + val probes = (0 until 43).map({ _ => + val probe = TestProbe() + probe + }).zipWithIndex.map { + case (probe, index) => probe -> metadataEvent(index, probe.ref) + } + + probes foreach { + case (probe, msg) => probe.send(writeActor, msg) + } + + probes.foreach { + case (probe, msg) => probe.expectMsg(MetadataWriteSuccess(msg.events)) + } + eventually { + writeActor.underlyingActor.failureCount should be (5 * failureRate) + } + } + } + + it should s"fail metadata writes and respond to all senders with failures" in { + val registry = TestProbe().ref + val writeActor = TestFSMRef(new BatchSizeCountingWriteMetadataActor(10, 100.millis, registry, Int.MaxValue) { + override val metadataDatabaseInterface = mockDatabaseInterface(100) }) - - probes.foreach(_.expectMsg(MetadataWriteSuccess(List(metadataEvent)))) + + def metadataEvent(index: Int, probe: ActorRef) = PutMetadataActionAndRespond(List(MetadataEvent(MetadataKey(WorkflowId.randomId(), None, s"metadata_key_$index"), MetadataValue(s"hello_$index"))), probe) + + val probes = (0 until 43).map({ _ => + val probe = TestProbe() + probe + }).zipWithIndex.map { + case (probe, index) => probe -> metadataEvent(index, probe.ref) + } + + probes foreach { + case (probe, msg) => probe.send(writeActor, msg) + } + + probes.foreach { + case (probe, msg) => probe.expectMsg(MetadataWriteFailure(WriteMetadataActorSpec.IntermittentException, msg.events)) + } + eventually { + writeActor.underlyingActor.failureCount should be(5 * 10) + } } // Mock database interface. - val mockDatabaseInterface = new MetadataSqlDatabase with SqlDatabase { + // A customizable number of failures occur between each success + def mockDatabaseInterface(failuresBetweenEachSuccess: Int) = new MetadataSqlDatabase with SqlDatabase { private def notImplemented() = throw new UnsupportedOperationException override protected val urlKey = "mock_database_url" @@ -50,9 +122,18 @@ class WriteMetadataActorSpec extends TestKitSuite with FlatSpecLike with Matcher override def existsMetadataEntries()( implicit ec: ExecutionContext): Nothing = notImplemented() + var requestsSinceLastSuccess = 0 // Return successful override def addMetadataEntries(metadataEntries: Iterable[MetadataEntry]) - (implicit ec: ExecutionContext): Future[Unit] = Future.successful(()) + (implicit ec: ExecutionContext): Future[Unit] = { + if (requestsSinceLastSuccess == failuresBetweenEachSuccess) { + requestsSinceLastSuccess = 0 + Future.successful(()) + } else { + requestsSinceLastSuccess += 1 + Future.failed(WriteMetadataActorSpec.IntermittentException) + } + } override def metadataEntryExists(workflowExecutionUuid: String) (implicit ec: ExecutionContext): Nothing = notImplemented() @@ -176,3 +257,29 @@ class WriteMetadataActorSpec extends TestKitSuite with FlatSpecLike with Matcher override def close(): Nothing = notImplemented() } } + +object WriteMetadataActorSpec { + + val IntermittentException = new Exception("Simulated Database Flakiness Exception") with NoStackTrace + + class BatchSizeCountingWriteMetadataActor(override val batchSize: Int, + override val flushRate: FiniteDuration, + override val serviceRegistryActor: ActorRef, + override val threshold: Int) extends WriteMetadataActor(batchSize, flushRate, serviceRegistryActor, threshold) { + + var batchSizes: Vector[Int] = Vector.empty + var failureCount: Int = 0 + + override def process(e: NonEmptyVector[MetadataWriteAction]) = { + batchSizes = batchSizes :+ e.length + val result = super.process(e) + result.onComplete { + case Success(_) => // Don't increment failure count + case Failure(_) => failureCount += 1 + } + + result + } + } + +} From 852a330a6a3ad3b06e839112b752bdbfd0efe2a8 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Wed, 24 Apr 2019 08:39:45 -0400 Subject: [PATCH 15/46] [develop] Ensure HealthMonitorServiceActor can initialize without google config (#4876) --- .../WorkbenchHealthMonitorServiceActor.scala | 14 ++---- .../HealthMonitorServiceActorSpec.scala | 47 +++++++++++++++++++ 2 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 services/src/test/scala/cromwell/services/healthmonitor/HealthMonitorServiceActorSpec.scala diff --git a/services/src/main/scala/cromwell/services/healthmonitor/impl/workbench/WorkbenchHealthMonitorServiceActor.scala b/services/src/main/scala/cromwell/services/healthmonitor/impl/workbench/WorkbenchHealthMonitorServiceActor.scala index 0a4b0908450..c974ec32261 100644 --- a/services/src/main/scala/cromwell/services/healthmonitor/impl/workbench/WorkbenchHealthMonitorServiceActor.scala +++ b/services/src/main/scala/cromwell/services/healthmonitor/impl/workbench/WorkbenchHealthMonitorServiceActor.scala @@ -42,11 +42,11 @@ abstract class WorkbenchHealthMonitorServiceActor(val serviceConfig: Config, glo protected lazy val Gcs = MonitoredSubsystem("GCS", () => checkGcs()) protected lazy val PapiSubsystems = papiBackendConfigurations map papiMonitoredSubsystem - val googleConfig = GoogleConfiguration(globalConfig) + lazy val googleConfig = GoogleConfiguration(globalConfig) - val googleAuthName = serviceConfig.as[Option[String]]("google-auth-name").getOrElse("application-default") + lazy val googleAuthName = serviceConfig.as[Option[String]]("google-auth-name").getOrElse("application-default") - val googleAuth = googleConfig.auth(googleAuthName) match { + lazy val googleAuth = googleConfig.auth(googleAuthName) match { case Valid(a) => a case Invalid(e) => throw new IllegalArgumentException("Unable to configure WorkbenchHealthMonitor: " + e.toList.mkString(", ")) } @@ -64,13 +64,9 @@ abstract class WorkbenchHealthMonitorServiceActor(val serviceConfig: Config, glo } private def checkPapi(papiConfiguration: PapiConfiguration): Future[SubsystemStatus] = { - checkPapi(papiConfiguration.papiConfig, papiConfiguration.papiProviderConfig) - } + val papiConfig = papiConfiguration.papiConfig + val papiProviderConfig = papiConfiguration.papiProviderConfig - /** - * Demonstrates connectivity to Google Pipelines API (PAPI) by making sure it can access an authenticated endpoint - */ - private def checkPapi(papiConfig: Config, papiProviderConfig: Config): Future[SubsystemStatus] = { val endpointUrl = new URL(papiConfig.as[String]("genomics.endpoint-url")) val papiProjectId = papiConfig.as[String]("project") diff --git a/services/src/test/scala/cromwell/services/healthmonitor/HealthMonitorServiceActorSpec.scala b/services/src/test/scala/cromwell/services/healthmonitor/HealthMonitorServiceActorSpec.scala new file mode 100644 index 00000000000..994d685bbfb --- /dev/null +++ b/services/src/test/scala/cromwell/services/healthmonitor/HealthMonitorServiceActorSpec.scala @@ -0,0 +1,47 @@ +package cromwell.services.healthmonitor + +import akka.actor.Props +import akka.pattern.AskSupport +import akka.testkit.TestProbe +import com.typesafe.config.ConfigFactory +import cromwell.core.TestKitSuite +import cromwell.services.healthmonitor.ProtoHealthMonitorServiceActor.{GetCurrentStatus, StatusCheckResponse} +import cromwell.services.healthmonitor.impl.HealthMonitorServiceActor +import org.scalatest.concurrent.Eventually +import org.scalatest.time.{Millis, Seconds, Span} +import org.scalatest.{FlatSpecLike, Matchers} + +import scala.concurrent.duration._ + +class HealthMonitorServiceActorSpec extends TestKitSuite with FlatSpecLike with Matchers with Eventually with AskSupport { + + override implicit def patienceConfig = PatienceConfig(timeout = scaled(Span(15, Seconds)), interval = Span(500, Millis)) + + behavior of "HealthMonitorServiceActor" + + it should "be able to load an instance and answer questions even with only basic configuration" in { + + val serviceConfigString = + """check-dockerhub: false + |check-engine-database: false + |check-gcs: false + |check-papi-backends: [] + |""".stripMargin + + + val globalConfigString = + s"""services.HealthMonitor.config: { + |$serviceConfigString + |} + |""".stripMargin + + val actor = system.actorOf(Props(new HealthMonitorServiceActor(ConfigFactory.parseString(serviceConfigString), ConfigFactory.parseString(globalConfigString), null))) + + val testProbe = TestProbe() + + eventually { + testProbe.send(actor, GetCurrentStatus) + testProbe.expectMsg(5.seconds, StatusCheckResponse(ok = true, systems = Map.empty)) + } + } +} From 15ec080590340a8e1c6b74698e1734837d6f874c Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Wed, 24 Apr 2019 13:37:16 -0400 Subject: [PATCH 16/46] Log warnings for long running jobs (#4872) --- CHANGELOG.md | 22 ++++++++- .../cromwell/backend/SlowJobWarning.scala | 46 +++++++++++++++++++ .../async/AsyncBackendJobExecutionActor.scala | 6 +-- .../main/scala/cromwell/backend/backend.scala | 4 ++ .../StandardAsyncExecutionActor.scala | 10 +++- cromwell.example.backends/AWS.conf | 3 ++ cromwell.example.backends/LSF.conf | 1 + cromwell.example.backends/PAPIv2.conf | 5 +- ...inesApiAsyncBackendJobExecutionActor.scala | 3 +- .../common/PipelinesApiConfiguration.scala | 1 - 10 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 backend/src/main/scala/cromwell/backend/SlowJobWarning.scala diff --git a/CHANGELOG.md b/CHANGELOG.md index a031de085e0..2cc13c3ee44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ * It is now possible to supply custom `google-labels` in [workflow options](https://cromwell.readthedocs.io/en/stable/wf_options/Google/). -### Heartbeat failure shutdown +### Config Changes + +#### Heartbeat failure shutdown When a Cromwell instance is unable to write heartbeats for some period of time it will automatically shut down. For more information see the docs on [configuring Workflow Hearbeats](https://cromwell.readthedocs.io/en/stable/Configuring/). @@ -15,6 +17,23 @@ NOTE: In the remote chance that the `system.workflow-heartbeats.ttl` has been co then the new configuration value `system.workflow-heartbeats.write-failure-shutdown-duration` must also be explicitly set less than the `ttl`. +#### Logging long running jobs + +All backends can now emit slow job warnings after a configurable time running. +NB This example shows how to configure this setting for the PAPIv2 backend: +```conf +# Emit a warning if jobs last longer than this amount of time. This might indicate that something got stuck. +backend { + providers { + PAPIv2 { + config { + slow-job-warning-time: 24 hours + } + } + } +} +``` + ### Bug fixes #### Better validation of workflow heartbeats @@ -22,6 +41,7 @@ set less than the `ttl`. An error will be thrown on startup when the `system.workflow-heartbeats.heartbeat-interval` is not less than the `system.workflow-heartbeats.ttl`. + ## 40 Release Notes ### Config Changes diff --git a/backend/src/main/scala/cromwell/backend/SlowJobWarning.scala b/backend/src/main/scala/cromwell/backend/SlowJobWarning.scala new file mode 100644 index 00000000000..074895e366a --- /dev/null +++ b/backend/src/main/scala/cromwell/backend/SlowJobWarning.scala @@ -0,0 +1,46 @@ +package cromwell.backend + +import java.time.OffsetDateTime + +import akka.actor.{Actor, ActorLogging} +import cromwell.backend.SlowJobWarning._ + +import scala.concurrent.duration.FiniteDuration + +trait SlowJobWarning { this: Actor with ActorLogging => + + def slowJobWarningReceive: Actor.Receive = { + case WarnAboutSlownessAfter(jobId, duration) => + alreadyWarned = false + warningDetails = Option(WarningDetails(jobId, OffsetDateTime.now(), OffsetDateTime.now().plusSeconds(duration.toSeconds))) + case WarnAboutSlownessIfNecessary => handleWarnMessage() + } + + var warningDetails: Option[WarningDetails] = None + var alreadyWarned: Boolean = false + + def warnAboutSlowJobIfNecessary(jobId: String) = { + // Don't do anything here because we might need to update state. + // Instead, send a message and handle this in the receive block. + self ! WarnAboutSlownessIfNecessary + } + + private def handleWarnMessage(): Unit = { + if (!alreadyWarned) { + warningDetails match { + case Some(WarningDetails(jobId, startTime, warningTime)) if OffsetDateTime.now().isAfter(warningTime) => + log.warning(s"Job with ID '{}' has been running since {} and might not be making progress", jobId, startTime) + alreadyWarned = true + case _ => // Nothing to do + } + } + } + +} + +object SlowJobWarning { + final case class WarnAboutSlownessAfter(jobId: String, duration: FiniteDuration) + case object WarnAboutSlownessIfNecessary + + final case class WarningDetails(jobId: String, startTime: OffsetDateTime, warningTime: OffsetDateTime) +} diff --git a/backend/src/main/scala/cromwell/backend/async/AsyncBackendJobExecutionActor.scala b/backend/src/main/scala/cromwell/backend/async/AsyncBackendJobExecutionActor.scala index d75e3f85cad..df8d0d6e9b1 100644 --- a/backend/src/main/scala/cromwell/backend/async/AsyncBackendJobExecutionActor.scala +++ b/backend/src/main/scala/cromwell/backend/async/AsyncBackendJobExecutionActor.scala @@ -4,7 +4,7 @@ package cromwell.backend.async import java.util.concurrent.ExecutionException import akka.actor.{Actor, ActorLogging, ActorRef} -import cromwell.backend.BackendJobDescriptor +import cromwell.backend.{BackendJobDescriptor, SlowJobWarning} import cromwell.backend.BackendJobExecutionActor._ import cromwell.backend.async.AsyncBackendJobExecutionActor._ import cromwell.core.CromwellFatalExceptionMarker @@ -40,7 +40,7 @@ object AsyncBackendJobExecutionActor { } } -trait AsyncBackendJobExecutionActor { this: Actor with ActorLogging => +trait AsyncBackendJobExecutionActor { this: Actor with ActorLogging with SlowJobWarning => def dockerImageUsed: Option[String] @@ -84,7 +84,7 @@ trait AsyncBackendJobExecutionActor { this: Actor with ActorLogging => context.stop(self) } - override def receive: Receive = { + override def receive: Receive = slowJobWarningReceive orElse { case mode: ExecutionMode => robustExecuteOrRecover(mode) case IssuePollRequest(handle) => robustPoll(handle) case PollResponseReceived(handle) if handle.isDone => self ! Finish(handle) diff --git a/backend/src/main/scala/cromwell/backend/backend.scala b/backend/src/main/scala/cromwell/backend/backend.scala index d6ad9c3f41c..427f302d929 100644 --- a/backend/src/main/scala/cromwell/backend/backend.scala +++ b/backend/src/main/scala/cromwell/backend/backend.scala @@ -12,6 +12,7 @@ import cromwell.core.path.{DefaultPathBuilderFactory, PathBuilderFactory} import cromwell.core.{CallKey, WorkflowId, WorkflowOptions} import cromwell.docker.DockerInfoActor.DockerSize import cromwell.services.keyvalue.KeyValueServiceActor.KvResponse +import net.ceedubs.ficus.Ficus._ import wom.callable.{ExecutableCallable, MetaValueElement} import wom.graph.CommandCallNode import wom.graph.GraphNodePort.OutputPort @@ -19,6 +20,7 @@ import wom.runtime.WomOutputRuntimeExtractor import wom.values.WomArray.WomArrayLike import wom.values._ +import scala.concurrent.duration.FiniteDuration import scala.util.{Failure, Success, Try} /** @@ -142,6 +144,8 @@ case class BackendConfigurationDescriptor(backendConfig: Config, globalConfig: C def pathBuildersWithDefault(workflowOptions: WorkflowOptions)(implicit as: ActorSystem) = { PathBuilderFactory.instantiatePathBuilders(configuredFactoriesWithDefault.values.toList, workflowOptions) } + + lazy val slowJobWarningAfter = backendConfig.as[Option[FiniteDuration]](path="slow-job-warning-time") } final case class AttemptedLookupResult(name: String, value: Try[WomValue]) { diff --git a/backend/src/main/scala/cromwell/backend/standard/StandardAsyncExecutionActor.scala b/backend/src/main/scala/cromwell/backend/standard/StandardAsyncExecutionActor.scala index 38d495ec470..1f5c5270660 100644 --- a/backend/src/main/scala/cromwell/backend/standard/StandardAsyncExecutionActor.scala +++ b/backend/src/main/scala/cromwell/backend/standard/StandardAsyncExecutionActor.scala @@ -20,6 +20,7 @@ import common.validation.Validation._ import cromwell.backend.BackendJobExecutionActor.{BackendJobExecutionResponse, JobAbortedResponse, JobReconnectionNotSupportedException} import cromwell.backend.BackendLifecycleActor.AbortJobCommand import cromwell.backend.OutputEvaluator._ +import cromwell.backend.SlowJobWarning.{WarnAboutSlownessAfter, WarnAboutSlownessIfNecessary} import cromwell.backend._ import cromwell.backend.async.AsyncBackendJobExecutionActor._ import cromwell.backend.async._ @@ -73,7 +74,8 @@ case class DefaultStandardAsyncExecutionActorParams * NOTE: Unlike the parent trait `AsyncBackendJobExecutionActor`, this trait is subject to even more frequent updates * as the common behavior among the backends adjusts in unison. */ -trait StandardAsyncExecutionActor extends AsyncBackendJobExecutionActor with StandardCachingActorHelper with AsyncIoActorClient with KvClient { +trait StandardAsyncExecutionActor + extends AsyncBackendJobExecutionActor with StandardCachingActorHelper with AsyncIoActorClient with KvClient with SlowJobWarning { this: Actor with ActorLogging with BackendJobLifecycleActor => override lazy val ioCommandBuilder: IoCommandBuilder = DefaultIoCommandBuilder @@ -922,7 +924,7 @@ trait StandardAsyncExecutionActor extends AsyncBackendJobExecutionActor with Sta private var missedAbort = false private case class CheckMissedAbort(jobId: StandardAsyncJob) - context.become(kvClientReceive orElse standardReceiveBehavior(None) orElse receive) + context.become(kvClientReceive orElse standardReceiveBehavior(None) orElse slowJobWarningReceive orElse receive) def standardReceiveBehavior(jobIdOption: Option[StandardAsyncJob]): Receive = LoggingReceive { case AbortJobCommand => @@ -964,6 +966,9 @@ trait StandardAsyncExecutionActor extends AsyncBackendJobExecutionActor with Sta private def executeOrRecoverSuccess(executionHandle: ExecutionHandle): Future[ExecutionHandle] = { executionHandle match { case handle: PendingExecutionHandle[StandardAsyncJob@unchecked, StandardAsyncRunInfo@unchecked, StandardAsyncRunState@unchecked] => + + configurationDescriptor.slowJobWarningAfter foreach { duration => self ! WarnAboutSlownessAfter(handle.pendingJob.jobId, duration) } + tellKvJobId(handle.pendingJob) map { _ => jobLogger.info(s"job id: ${handle.pendingJob.jobId}") tellMetadata(Map(CallMetadataKeys.JobId -> handle.pendingJob.jobId)) @@ -988,6 +993,7 @@ trait StandardAsyncExecutionActor extends AsyncBackendJobExecutionActor with Sta jobLogger.debug(s"$tag Polling Job ${handle.pendingJob}") pollStatusAsync(handle) flatMap { backendRunStatus => + self ! WarnAboutSlownessIfNecessary handlePollSuccess(handle, backendRunStatus) } recover { case throwable => diff --git a/cromwell.example.backends/AWS.conf b/cromwell.example.backends/AWS.conf index 0e4c25ba5be..441415cd00f 100644 --- a/cromwell.example.backends/AWS.conf +++ b/cromwell.example.backends/AWS.conf @@ -58,6 +58,9 @@ backend { auth = "default" } } + + # Emit a warning if jobs last longer than this amount of time. This might indicate that something got stuck in the cloud. + slow-job-warning-time: 24 hours } } } diff --git a/cromwell.example.backends/LSF.conf b/cromwell.example.backends/LSF.conf index 1afff6c47d5..d3e8fb56d6f 100644 --- a/cromwell.example.backends/LSF.conf +++ b/cromwell.example.backends/LSF.conf @@ -22,4 +22,5 @@ backend { job-id-regex = "Job <(\\d+)>.*" } } + } } diff --git a/cromwell.example.backends/PAPIv2.conf b/cromwell.example.backends/PAPIv2.conf index 49f2a136ba9..008414a4b55 100644 --- a/cromwell.example.backends/PAPIv2.conf +++ b/cromwell.example.backends/PAPIv2.conf @@ -24,7 +24,10 @@ backend { # Make the name of the backend used for call caching purposes insensitive to the PAPI version. name-for-call-caching-purposes: PAPI - + + # Emit a warning if jobs last longer than this amount of time. This might indicate that something got stuck in PAPI. + slow-job-warning-time: 24 hours + # Set this to the lower of the two values "Queries per 100 seconds" and "Queries per 100 seconds per user" for # your project. # diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiAsyncBackendJobExecutionActor.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiAsyncBackendJobExecutionActor.scala index 7b56488b9f3..4373da18495 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiAsyncBackendJobExecutionActor.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiAsyncBackendJobExecutionActor.scala @@ -87,7 +87,8 @@ object PipelinesApiAsyncBackendJobExecutionActor { class PipelinesApiAsyncBackendJobExecutionActor(override val standardParams: StandardAsyncExecutionActorParams) extends BackendJobLifecycleActor with StandardAsyncExecutionActor with PipelinesApiJobCachingActorHelper - with PipelinesApiStatusRequestClient with PipelinesApiRunCreationClient with PipelinesApiAbortClient with KvClient with PapiInstrumentation { + with PipelinesApiStatusRequestClient with PipelinesApiRunCreationClient with PipelinesApiAbortClient with KvClient + with PapiInstrumentation { override lazy val ioCommandBuilder = GcsBatchCommandBuilder diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiConfiguration.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiConfiguration.scala index bbc6c5fd7d1..0ac927a13c1 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiConfiguration.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiConfiguration.scala @@ -8,7 +8,6 @@ import cromwell.core.BackendDockerConfiguration import net.ceedubs.ficus.Ficus._ import spray.json._ - class PipelinesApiConfiguration(val configurationDescriptor: BackendConfigurationDescriptor, val genomicsFactory: PipelinesApiFactoryInterface, val googleConfig: GoogleConfiguration, From 6e55eaf4b331395dd79024a411e641ced6e1ae47 Mon Sep 17 00:00:00 2001 From: Ruchi Date: Wed, 24 Apr 2019 13:38:40 -0400 Subject: [PATCH 17/46] Add note about bucket permissions in PAPI v2 (#4633) --- docs/tutorials/PipelinesApi101.md | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/tutorials/PipelinesApi101.md b/docs/tutorials/PipelinesApi101.md index aae0ee4dcd3..2324a7f7760 100644 --- a/docs/tutorials/PipelinesApi101.md +++ b/docs/tutorials/PipelinesApi101.md @@ -1,5 +1,38 @@ ## Getting started on Google Pipelines API +## Pipelines API v2 + +### Basic Information + +Initial support for Google [Pipelines API version 2](https://cloud.google.com/genomics/reference/rest/) was added in Cromwell 32. +Expect feature parity with v1 except: + +* PAPI v2 private Docker support is equivalent to that in PAPI v1 but the configuration differs, please see [Docker configuration](http://cromwell.readthedocs.io/en/develop/filesystems/Google#Docker) for more details. +* The "refresh token" authentication mode is **NOT** supported on PAPI V2. + +In addition, the following changes are to be expected: + +* Error messages for failed jobs might differ from V1 +* The Pipelines API log file content might differ from V1 + +### Setting up PAPIv2 + +For now the easiest way to try PAPIv2 is to migrate an existing set up from PAPIv1 (see below). After that, copy the PAPIv2 sample configuration in [cromwell.examples.conf](https://github.com/broadinstitute/cromwell/blob/develop/cromwell.examples.conf) in place of the PAPIv1 backend. + +#### Permissions: + +With Pipelines API v2, the mode of authentication (ie, Application Default, User Service Account, default compute service account, etc) will need to have these permissions on the bucket holding the Cromwell directory (root directory): + +* storage.objects.list +* storage.objects.create +* storage.objects.delete + +## Pipelines API v1 + +### Deprecation warning + +Please note that Google intends to deprecate PAPIv1 in the near future (circa mid 2019 or perhaps earlier). + ### Prerequisites This tutorial page relies on completing the previous tutorial: From 2d9f98e42e0b30bcff09a76d45326a27d8b7e056 Mon Sep 17 00:00:00 2001 From: Adam Nichols Date: Wed, 24 Apr 2019 13:45:10 -0400 Subject: [PATCH 18/46] Pin CWL conformance to commit 1f501e3 --- .../cwl_output_json/cwl_output_json.inputs | 8 +++--- .../scala/centaur/cwl/OutputManipulator.scala | 5 ++-- .../src/test/resources/application.conf | 2 +- .../test/scala/CloudPreprocessorSpec.scala | 12 ++++---- src/ci/bin/test.inc.sh | 6 ++-- src/ci/bin/testConformanceLocal.sh | 2 +- src/ci/bin/testConformancePapiV2.sh | 4 +-- .../local_conformance_expected_failures.txt | 18 ++++++++++++ .../papi_v2_conformance_expected_failures.txt | 23 +++++++++++++++ .../tesk_conformance_expected_failures.txt | 28 +++++++++++++++++++ 10 files changed, 89 insertions(+), 19 deletions(-) diff --git a/centaur/src/main/resources/standardTestCases/cwl_output_json/cwl_output_json.inputs b/centaur/src/main/resources/standardTestCases/cwl_output_json/cwl_output_json.inputs index 99ca6fea8ac..d83a30872a9 100644 --- a/centaur/src/main/resources/standardTestCases/cwl_output_json/cwl_output_json.inputs +++ b/centaur/src/main/resources/standardTestCases/cwl_output_json/cwl_output_json.inputs @@ -1,22 +1,22 @@ { "args.py": { "class": "File", - "location": "gs://centaur-cwl-conformance/cwl-inputs/args.py" + "location": "gs://centaur-cwl-conformance-1f501e3/cwl-inputs/args.py" }, "reference": { "class": "File", - "location": "gs://centaur-cwl-conformance/cwl-inputs/chr20.fa", + "location": "gs://centaur-cwl-conformance-1f501e3/cwl-inputs/chr20.fa", "size": 123, "checksum": "sha1$hash" }, "reads": [ { "class": "File", - "location": "gs://centaur-cwl-conformance/cwl-inputs/example_human_Illumina.pe_1.fastq" + "location": "gs://centaur-cwl-conformance-1f501e3/cwl-inputs/example_human_Illumina.pe_1.fastq" }, { "class": "File", - "location": "gs://centaur-cwl-conformance/cwl-inputs/example_human_Illumina.pe_2.fastq" + "location": "gs://centaur-cwl-conformance-1f501e3/cwl-inputs/example_human_Illumina.pe_2.fastq" } ] } diff --git a/centaurCwlRunner/src/main/scala/centaur/cwl/OutputManipulator.scala b/centaurCwlRunner/src/main/scala/centaur/cwl/OutputManipulator.scala index 7dab4920535..dcbe3a70ec8 100644 --- a/centaurCwlRunner/src/main/scala/centaur/cwl/OutputManipulator.scala +++ b/centaurCwlRunner/src/main/scala/centaur/cwl/OutputManipulator.scala @@ -93,7 +93,8 @@ object OutputManipulator extends Poly1 { // We need this because some task return a "File" object that actually does not exist on disk but has its content provided directly instead def sizeContent: Option[Long] = obj.kleisli("contents").flatMap(_.asString.map(_.length.toLong)) - // The cwl test runner expects only the name, not the full path + // "as a special case, cwltest only matches the trailing part of location in the output sections so that it + // can do something reasonable regardless of URL scheme or prefix" - Peter Amstutz, 2019-04-16, CWL gitter val updatedLocation = obj.add("location", Json.fromString(path.pathAsString)) // Get the format @@ -128,7 +129,7 @@ object OutputManipulator extends Poly1 { val size = valueOrNull("size", defaultSize) val basename: Option[Json] = - Option(valueOrNull("basename", path.exists.option(path.nameWithoutExtension).map(Json.fromString).getOrElse(Json.Null))) + Option(valueOrNull("basename", path.exists.option(path.name).map(Json.fromString).getOrElse(Json.Null))) /* In order of priority use: diff --git a/centaurCwlRunner/src/test/resources/application.conf b/centaurCwlRunner/src/test/resources/application.conf index 91d07ee185c..27c24fa07bf 100644 --- a/centaurCwlRunner/src/test/resources/application.conf +++ b/centaurCwlRunner/src/test/resources/application.conf @@ -1 +1 @@ -papi.default-input-gcs-prefix="gs://centaur-cwl-conformance/cwl-inputs/" \ No newline at end of file +papi.default-input-gcs-prefix="gs://centaur-cwl-conformance-1f501e3/cwl-inputs/" diff --git a/centaurCwlRunner/src/test/scala/CloudPreprocessorSpec.scala b/centaurCwlRunner/src/test/scala/CloudPreprocessorSpec.scala index 20e5ff99d2b..b8eb3d47818 100644 --- a/centaurCwlRunner/src/test/scala/CloudPreprocessorSpec.scala +++ b/centaurCwlRunner/src/test/scala/CloudPreprocessorSpec.scala @@ -52,26 +52,26 @@ class CloudPreprocessorSpec extends FlatSpec with Matchers { | "input": { | "null": null, | "file": { - | "location": "gs://centaur-cwl-conformance/cwl-inputs/whale.txt", + | "location": "gs://centaur-cwl-conformance-1f501e3/cwl-inputs/whale.txt", | "class": "File", | "secondaryFiles": [ | { | "class": File, - | "location": "gs://centaur-cwl-conformance/cwl-inputs/hello.txt" + | "location": "gs://centaur-cwl-conformance-1f501e3/cwl-inputs/hello.txt" | } | ], | "default": { - | "location": "gs://centaur-cwl-conformance/cwl-inputs/default_whale.txt", + | "location": "gs://centaur-cwl-conformance-1f501e3/cwl-inputs/default_whale.txt", | "class": "File", | } | }, | "directory": { - | "location": "gs://centaur-cwl-conformance/cwl-inputs/ref.fasta", + | "location": "gs://centaur-cwl-conformance-1f501e3/cwl-inputs/ref.fasta", | "class": "Directory", | "listing": [ | { | "class": File, - | "location": "gs://centaur-cwl-conformance/cwl-inputs/hello.txt" + | "location": "gs://centaur-cwl-conformance-1f501e3/cwl-inputs/hello.txt" | } | ] | } @@ -114,7 +114,7 @@ class CloudPreprocessorSpec extends FlatSpec with Matchers { | inputBinding: { position: 2 } | default: | class: File - | location: gs://centaur-cwl-conformance/cwl-inputs/args.py + | location: gs://centaur-cwl-conformance-1f501e3/cwl-inputs/args.py | |outputs: | args: string[] diff --git a/src/ci/bin/test.inc.sh b/src/ci/bin/test.inc.sh index fd1292f666c..684ead12ff0 100644 --- a/src/ci/bin/test.inc.sh +++ b/src/ci/bin/test.inc.sh @@ -342,9 +342,9 @@ cromwell::private::verify_secure_build() { } cromwell::private::export_conformance_variables() { - CROMWELL_BUILD_CWL_TOOL_VERSION="1.0.20180809224403" - CROMWELL_BUILD_CWL_TEST_VERSION="1.0.20180601100346" - CROMWELL_BUILD_CWL_TEST_COMMIT="eb73b5e70e65ab9303a814bd1c230b927018da8f" # use known git hash to avoid changes + CROMWELL_BUILD_CWL_TOOL_VERSION="1.0.20190228155703" + CROMWELL_BUILD_CWL_TEST_VERSION="1.0.20190228134645" + CROMWELL_BUILD_CWL_TEST_COMMIT="1f501e38ff692a408e16b246ac7d64d32f0822c2" # use known git hash to avoid changes CROMWELL_BUILD_CWL_TEST_RUNNER="${CROMWELL_BUILD_ROOT_DIRECTORY}/centaurCwlRunner/src/bin/centaur-cwl-runner.bash" CROMWELL_BUILD_CWL_TEST_DIRECTORY="${CROMWELL_BUILD_ROOT_DIRECTORY}/common-workflow-language" CROMWELL_BUILD_CWL_TEST_RESOURCES="${CROMWELL_BUILD_CWL_TEST_DIRECTORY}/v1.0/v1.0" diff --git a/src/ci/bin/testConformanceLocal.sh b/src/ci/bin/testConformanceLocal.sh index 8fdfb682f28..def67ff2c0d 100755 --- a/src/ci/bin/testConformanceLocal.sh +++ b/src/ci/bin/testConformanceLocal.sh @@ -45,7 +45,7 @@ CROMWELL_PID=$! sleep 30 java \ - -Xmx2g \ + -Xmx6g \ -Dbackend.providers.Local.config.concurrent-job-limit="${CROMWELL_BUILD_CWL_TEST_PARALLELISM}" \ -jar "${CROMWELL_BUILD_CROMWELL_JAR}" \ run "${CROMWELL_BUILD_CWL_TEST_WDL}" \ diff --git a/src/ci/bin/testConformancePapiV2.sh b/src/ci/bin/testConformancePapiV2.sh index 3d27b1fd2e1..09a4beb209d 100755 --- a/src/ci/bin/testConformancePapiV2.sh +++ b/src/ci/bin/testConformancePapiV2.sh @@ -17,7 +17,7 @@ cromwell::build::assemble_jars CENTAUR_CWL_RUNNER_MODE="papi" GOOGLE_AUTH_MODE="service-account" -PAPI_INPUT_GCS_PREFIX=gs://centaur-cwl-conformance/cwl-inputs/ +PAPI_INPUT_GCS_PREFIX=gs://centaur-cwl-conformance-1f501e3/cwl-inputs/ # Export variables used in conf files and commands export CENTAUR_CWL_RUNNER_MODE @@ -50,7 +50,7 @@ CROMWELL_PID=$! sleep 30 java \ - -Xmx2g \ + -Xmx6g \ -Dbackend.providers.Local.config.concurrent-job-limit="${CROMWELL_BUILD_CWL_TEST_PARALLELISM}" \ -jar "${CROMWELL_BUILD_CROMWELL_JAR}" \ run "${CROMWELL_BUILD_CWL_TEST_WDL}" \ diff --git a/src/ci/resources/local_conformance_expected_failures.txt b/src/ci/resources/local_conformance_expected_failures.txt index e69de29bb2d..102d3f8c94e 100644 --- a/src/ci/resources/local_conformance_expected_failures.txt +++ b/src/ci/resources/local_conformance_expected_failures.txt @@ -0,0 +1,18 @@ +136 +140 +142 +156 +158 +159 +160 +161 +163 +164 +165 +166 +170 +171 +173 +190 +191 +196 diff --git a/src/ci/resources/papi_v2_conformance_expected_failures.txt b/src/ci/resources/papi_v2_conformance_expected_failures.txt index 28c1eacd8b5..405b0c970ae 100644 --- a/src/ci/resources/papi_v2_conformance_expected_failures.txt +++ b/src/ci/resources/papi_v2_conformance_expected_failures.txt @@ -3,3 +3,26 @@ 101 102 107 +136 +137 +140 +142 +152 +153 +154 +156 +158 +159 +160 +161 +163 +164 +165 +166 +170 +171 +173 +189 +190 +191 +196 diff --git a/src/ci/resources/tesk_conformance_expected_failures.txt b/src/ci/resources/tesk_conformance_expected_failures.txt index a6a44996c8f..bae7cc06c00 100644 --- a/src/ci/resources/tesk_conformance_expected_failures.txt +++ b/src/ci/resources/tesk_conformance_expected_failures.txt @@ -16,3 +16,31 @@ 118 121 129 +133 +136 +140 +142 +150 +151 +152 +153 +154 +155 +156 +158 +159 +160 +161 +163 +164 +165 +166 +170 +171 +172 +173 +174 +175 +190 +191 +196 From de53626a0e27390769abb65733d8ca92f44a5354 Mon Sep 17 00:00:00 2001 From: "Michael R. Crusoe" <1330696+mr-c@users.noreply.github.com> Date: Wed, 24 Apr 2019 20:42:25 +0200 Subject: [PATCH 19/46] Create CITATION.md (#4890) As per https://github.com/broadinstitute/cromwell/issues/4213#issuecomment-427305068 --- CITATION.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 CITATION.md diff --git a/CITATION.md b/CITATION.md new file mode 100644 index 00000000000..1e88d822a4f --- /dev/null +++ b/CITATION.md @@ -0,0 +1,26 @@ +# Citing `Cromwell` + +If you use `Cromwell` in your work we would prefer it if you would use the following reference in your work. + +## BibTeX + +```bibtex +@misc{https://doi.org/10.7490/f1000research.1114634.1, + doi = {10.7490/f1000research.1114634.1}, + url = {https://f1000research.com/slides/6-1381}, + author = {Voss, Kate and Auwera, Geraldine Van Der and Gentry, Jeff}, + title = {Full-stack genomics pipelining with GATK4 + WDL + Cromwell [version 1; not peer reviewed]}, + journal = {ISCB Comm J}, + publisher = {F1000Research}, + type = {slides} + volume = {6}, + number = {1381}, + year = {2017} +} +``` + +## Textual + +Voss K, Van der Auwera G and Gentry J. Full-stack genomics pipelining with GATK4 + WDL + Cromwell +[version 1; not peer reviewed]. _F1000Research_ 2017, **6**(ISCB Comm J):1381 (slides) +(https://doi.org/10.7490/f1000research.1114634.1) From 89439ea7ae916e4d2d10c0d39efb00f394bd2f50 Mon Sep 17 00:00:00 2001 From: mcovarr Date: Thu, 25 Apr 2019 17:36:36 -0400 Subject: [PATCH 20/46] CI fixes / band-aids (#4902) --- src/ci/bin/testCentaurBcs.sh | 3 +++ src/ci/resources/aws_application.conf | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ci/bin/testCentaurBcs.sh b/src/ci/bin/testCentaurBcs.sh index 94d0c2f4f91..b52d9d575f5 100755 --- a/src/ci/bin/testCentaurBcs.sh +++ b/src/ci/bin/testCentaurBcs.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash +# Temporary nerfing until the current zero-node cluster issue is resolved. +exit 0 + set -o errexit -o nounset -o pipefail export CROMWELL_BUILD_REQUIRES_SECURE=true # import in shellcheck / CI / IntelliJ compatible ways diff --git a/src/ci/resources/aws_application.conf b/src/ci/resources/aws_application.conf index 800d9f2b627..b75be11df05 100644 --- a/src/ci/resources/aws_application.conf +++ b/src/ci/resources/aws_application.conf @@ -47,7 +47,7 @@ backend { concurrent-job-limit = 16 default-runtime-attributes { - queueArn: "arn:aws:batch:us-east-1:952500931424:job-queue/GenomicsHighPriorityQue-c1ed17c72de5fcb" + queueArn: "arn:aws:batch:us-east-1:952500931424:job-queue/highpriority-1e2507f0-66c3-11e9-9a2f-1204ddd846a2" } filesystems { From 8e71dfe9ff95e9f281a7849213f0f036b8b426e9 Mon Sep 17 00:00:00 2001 From: Jeff Gentry Date: Fri, 26 Apr 2019 09:46:13 -0400 Subject: [PATCH 21/46] fail fast if output is never going to be there, plus shellcheck (#4899) --- src/ci/bin/testCentaurAws.sh | 2 -- .../aws/src/main/resources/ecs-proxy/proxy | 29 ++++++++++++------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/ci/bin/testCentaurAws.sh b/src/ci/bin/testCentaurAws.sh index 3abbde3f169..95d8ae8da0c 100755 --- a/src/ci/bin/testCentaurAws.sh +++ b/src/ci/bin/testCentaurAws.sh @@ -37,9 +37,7 @@ cromwell::build::run_centaur \ -e globbingbehavior \ -e non_root_default_user \ -e draft3_glob_access \ - -e bad_output_task \ -e cwl_interpolated_strings \ - -e bad_file_string \ -e non_root_specified_user \ -e space \ -e draft3_optional_input_from_scatter \ diff --git a/supportedBackends/aws/src/main/resources/ecs-proxy/proxy b/supportedBackends/aws/src/main/resources/ecs-proxy/proxy index 9286b609343..9e83a37d232 100755 --- a/supportedBackends/aws/src/main/resources/ecs-proxy/proxy +++ b/supportedBackends/aws/src/main/resources/ecs-proxy/proxy @@ -47,7 +47,7 @@ awscp() { while [ $initExitCode -ne 0 ] do echo "aws s3 $1 --sse AES256 --no-progress $2 $3" - aws s3 $1 --sse AES256 --no-progress "$2" "$3" + aws s3 "$1" --sse AES256 --no-progress "$2" "$3" initExitCode=$? if [ $initExitCode -ne 0 ]; then if [ $retryTimes -gt 4 ]; then @@ -94,7 +94,13 @@ copyfile() { if [[ "$localname" == glob-*/"*" ]]; then awscp sync "${localdirectory}/${name}" "${s3location}" else - awscp cp "${localdirectory}/${localname}" "${s3location}" + localfilepath="${localdirectory}/${localname}" + if [ -e "$localfilepath" ] ; then + awscp cp "${localfilepath}" "${s3location}" + else + echo "No such local file: ${localfilepath}" + exit 1 + fi fi else awscp cp "${s3location}" "${localdirectory}/${localname}" @@ -110,7 +116,7 @@ copyfiles() { } copyToS3() { - if [ ! -z "${1}" ]; then + if [ -n "${1}" ]; then awscp cp "${1}" "${2}" else echo "Variable defining file to be copied does not exist" @@ -129,12 +135,12 @@ decompVal() { # copy outputs as necessary # ########################################################################### -if [ ! -z "${AWS_CROMWELL_INPUTS}" ]; then +if [ -n "${AWS_CROMWELL_INPUTS}" ]; then echo "Copying input files from s3" copyfiles "${AWS_CROMWELL_INPUTS}" in fi -if [ ! -z "${AWS_CROMWELL_INPUTS_GZ}" ]; then +if [ -n "${AWS_CROMWELL_INPUTS_GZ}" ]; then echo "Copying input files from s3 (gzipped input)" inputs=$(decompVal "${AWS_CROMWELL_INPUTS_GZ}") copyfiles "${inputs}" in @@ -145,12 +151,6 @@ fi docker start -i "${AWS_ECS_PROXY_TARGET_CONTAINER_ID}" > /dev/null rc=$? -# If there are no outputs, there's no reason to go through the dance below. -# We can just exit now -if [ ! -z "${AWS_CROMWELL_OUTPUTS}" ]; then - echo "Copying output files to S3" - copyfiles "${AWS_CROMWELL_OUTPUTS}" out -fi echo "Copying rc file" copyToS3 "${AWS_CROMWELL_RC_FILE}" "${AWS_CROMWELL_CALL_ROOT}/" echo "Copying stdout file" @@ -158,6 +158,13 @@ copyToS3 "${AWS_CROMWELL_STDOUT_FILE}" "${AWS_CROMWELL_CALL_ROOT}/" echo "Copying stderr file" copyToS3 "${AWS_CROMWELL_STDERR_FILE}" "${AWS_CROMWELL_CALL_ROOT}/" +# Wait until copying stdout/stderr/rc before copying outputs to ensure they're always there in case of +# transfer error. Check first that there are outputs prior to trying to copy the outputs +if [ -n "${AWS_CROMWELL_OUTPUTS}" ]; then + echo "Copying output files to S3" + copyfiles "${AWS_CROMWELL_OUTPUTS}" out +fi + echo "Removing target container" docker rm "${AWS_ECS_PROXY_TARGET_CONTAINER_ID}" From 30748e4f7b0ed0fdaf20b616622ff7f404ce4639 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Fri, 26 Apr 2019 17:49:36 -0400 Subject: [PATCH 22/46] Don't let awkward scheduled actions wreck test cases (#4898) --- .../cromwell/core/actor/BatchActor.scala | 20 +++++++++++++++++-- .../impl/WriteMetadataActorSpec.scala | 16 +++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/cromwell/core/actor/BatchActor.scala b/core/src/main/scala/cromwell/core/actor/BatchActor.scala index 18582f76998..e61c2c2bb3d 100644 --- a/core/src/main/scala/cromwell/core/actor/BatchActor.scala +++ b/core/src/main/scala/cromwell/core/actor/BatchActor.scala @@ -1,5 +1,7 @@ package cromwell.core.actor +import java.time.OffsetDateTime + import akka.actor.{ActorRef, FSM, Timers} import akka.dispatch.ControlMessage import cats.data.NonEmptyVector @@ -72,14 +74,28 @@ abstract class BatchActor[C](val flushRate: FiniteDuration, def commandToData(snd: ActorRef): PartialFunction[Any, C] + // If work has arrived 'recently' - ie within this duration - ignore any periodic 'ScheduledProcessAction' messages + val recentArrivalThreshold: Option[FiniteDuration] = None + var mostRecentArrival: Option[OffsetDateTime] = None + def suitableIntervalSinceLastArrival(): Boolean = (for { + threshold <- recentArrivalThreshold + mostRecent <- mostRecentArrival + now = OffsetDateTime.now + } yield mostRecent.plusNanos(threshold.toNanos).isBefore(now)).getOrElse(true) + when(WaitingToProcess) { // On a regular event, only process if the batch size has been reached. case Event(command, data) if commandToData(sender).isDefinedAt(command) => + recentArrivalThreshold foreach { _ => mostRecentArrival = Option(OffsetDateTime.now()) } processIfBatchSizeReached(data.enqueue(commandToData(sender)(command))) // On a scheduled process, always process case Event(ScheduledProcessAction, data) => - gossip(QueueWeight(data.weight)) - processHead(data) + if (suitableIntervalSinceLastArrival()) { + gossip(QueueWeight(data.weight)) + processHead(data) + } else { + stay() + } case Event(ShutdownCommand, data) => logger.info(s"{} Shutting down: ${data.weight} queued messages to process", self.path.name) shuttingDown = true diff --git a/services/src/test/scala/cromwell/services/metadata/impl/WriteMetadataActorSpec.scala b/services/src/test/scala/cromwell/services/metadata/impl/WriteMetadataActorSpec.scala index 3d1e4c734b7..ad43eb9f0c6 100644 --- a/services/src/test/scala/cromwell/services/metadata/impl/WriteMetadataActorSpec.scala +++ b/services/src/test/scala/cromwell/services/metadata/impl/WriteMetadataActorSpec.scala @@ -30,7 +30,7 @@ class WriteMetadataActorSpec extends TestKitSuite with FlatSpecLike with Matcher it should "process jobs in the correct batch sizes" in { val registry = TestProbe().ref - val writeActor = TestFSMRef(new BatchSizeCountingWriteMetadataActor(10, 2.seconds, registry, Int.MaxValue) { + val writeActor = TestFSMRef(new BatchSizeCountingWriteMetadataActor(10, 10.millis, registry, Int.MaxValue) { override val metadataDatabaseInterface = mockDatabaseInterface(0) }) @@ -50,13 +50,15 @@ class WriteMetadataActorSpec extends TestKitSuite with FlatSpecLike with Matcher eventually { writeActor.underlyingActor.batchSizes should be(Vector(10, 10, 7)) } + + writeActor.stop() } val failuresBetweenSuccessValues = List(0, 5, 9) failuresBetweenSuccessValues foreach { failureRate => it should s"succeed metadata writes and respond to all senders even with $failureRate failures between each success" in { val registry = TestProbe().ref - val writeActor = TestFSMRef(new BatchSizeCountingWriteMetadataActor(10, 100.millis, registry, Int.MaxValue) { + val writeActor = TestFSMRef(new BatchSizeCountingWriteMetadataActor(10, 10.millis, registry, Int.MaxValue) { override val metadataDatabaseInterface = mockDatabaseInterface(failureRate) }) @@ -79,12 +81,14 @@ class WriteMetadataActorSpec extends TestKitSuite with FlatSpecLike with Matcher eventually { writeActor.underlyingActor.failureCount should be (5 * failureRate) } + + writeActor.stop() } } it should s"fail metadata writes and respond to all senders with failures" in { val registry = TestProbe().ref - val writeActor = TestFSMRef(new BatchSizeCountingWriteMetadataActor(10, 100.millis, registry, Int.MaxValue) { + val writeActor = TestFSMRef(new BatchSizeCountingWriteMetadataActor(10, 10.millis, registry, Int.MaxValue) { override val metadataDatabaseInterface = mockDatabaseInterface(100) }) @@ -105,8 +109,10 @@ class WriteMetadataActorSpec extends TestKitSuite with FlatSpecLike with Matcher case (probe, msg) => probe.expectMsg(MetadataWriteFailure(WriteMetadataActorSpec.IntermittentException, msg.events)) } eventually { - writeActor.underlyingActor.failureCount should be(5 * 10) + writeActor.underlyingActor.failureCount should be (5 * 10) } + + writeActor.stop() } // Mock database interface. @@ -270,6 +276,8 @@ object WriteMetadataActorSpec { var batchSizes: Vector[Int] = Vector.empty var failureCount: Int = 0 + override val recentArrivalThreshold = Some(100.millis) + override def process(e: NonEmptyVector[MetadataWriteAction]) = { batchSizes = batchSizes :+ e.length val result = super.process(e) From 8a1596a2047488e6ae08c56b47c10d124859e087 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Sat, 27 Apr 2019 03:53:48 -0400 Subject: [PATCH 23/46] Switch BCS to a non phased-out instance type --- src/ci/bin/testCentaurBcs.sh | 3 --- src/ci/bin/test_bcs.inc.sh | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ci/bin/testCentaurBcs.sh b/src/ci/bin/testCentaurBcs.sh index b52d9d575f5..94d0c2f4f91 100755 --- a/src/ci/bin/testCentaurBcs.sh +++ b/src/ci/bin/testCentaurBcs.sh @@ -1,8 +1,5 @@ #!/usr/bin/env bash -# Temporary nerfing until the current zero-node cluster issue is resolved. -exit 0 - set -o errexit -o nounset -o pipefail export CROMWELL_BUILD_REQUIRES_SECURE=true # import in shellcheck / CI / IntelliJ compatible ways diff --git a/src/ci/bin/test_bcs.inc.sh b/src/ci/bin/test_bcs.inc.sh index 2a5d776f17b..4fd7692972b 100644 --- a/src/ci/bin/test_bcs.inc.sh +++ b/src/ci/bin/test_bcs.inc.sh @@ -61,7 +61,7 @@ cromwell::private::bcs::try_bcs_create_cluster() { create_cluster \ "${cluster_name}" \ --image img-ubuntu \ - --type ecs.sn1.medium \ + --type ecs.sn1ne.large \ --nodes 8 \ --vpc_cidr_block 192.168.1.0/24 \ --no_cache_support \ From 1c6b55114f90b802ce095d6386b6a848d20cffd3 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Tue, 30 Apr 2019 17:45:02 -0400 Subject: [PATCH 24/46] Codify our release processes (#4884) --- scripts/release_processes/README.MD | 21 +++++ scripts/release_processes/caas-prod.dot | 18 +++++ scripts/release_processes/caas-prod.dot.png | Bin 0 -> 38319 bytes .../release_processes/firecloud-develop.dot | 73 ++++++++++++++++++ .../firecloud-develop.dot.png | Bin 0 -> 253609 bytes .../release-cromwell-version.dot | 10 +++ .../release-cromwell-version.dot.png | Bin 0 -> 5384 bytes 7 files changed, 122 insertions(+) create mode 100644 scripts/release_processes/README.MD create mode 100644 scripts/release_processes/caas-prod.dot create mode 100644 scripts/release_processes/caas-prod.dot.png create mode 100644 scripts/release_processes/firecloud-develop.dot create mode 100644 scripts/release_processes/firecloud-develop.dot.png create mode 100644 scripts/release_processes/release-cromwell-version.dot create mode 100644 scripts/release_processes/release-cromwell-version.dot.png diff --git a/scripts/release_processes/README.MD b/scripts/release_processes/README.MD new file mode 100644 index 00000000000..24b4c8319b2 --- /dev/null +++ b/scripts/release_processes/README.MD @@ -0,0 +1,21 @@ +# Release Processes + +## How to Release Cromwell + +![release-cromwell-version](release-cromwell-version.dot.png) + +## How to Deploy Cromwell in Firecloud + +![firecloud-develop](firecloud-develop.dot.png) + +## How to Deploy Cromwell in CAAS prod + +![caas-prod](caas-prod.dot.png) + +## How to update these processes + +Have a better idea about how the deployment process should go? Make a PR and it'll be reviewed, just like a code change! + + * Modify the appropriate `.dot` file in this directory. + * Run `for i in $(ls *.dot); do dot -Tpng -o $i.png $i; done` to update all pngs. + * Add and commit the changed `.dot` and `.png` files using git. diff --git a/scripts/release_processes/caas-prod.dot b/scripts/release_processes/caas-prod.dot new file mode 100644 index 00000000000..00be2bf5263 --- /dev/null +++ b/scripts/release_processes/caas-prod.dot @@ -0,0 +1,18 @@ +digraph { + + # Nodes + + release_cromwell [shape=oval label="Release new Cromwell version! Woohoo!"]; + get_permission [shape=oval label="Get permission to deploy"]; + update_cromwell_dev [shape=oval label="Update cromwell_dev repo"]; + jenkins_deploy_dev [shape=oval label="Deploy to dev using Jenkins"]; + jenkins_deploy_prod [shape=oval label="Deploy to prod using Jenkins"]; + + # Edges + + release_cromwell -> get_permission + get_permission -> update_cromwell_dev + update_cromwell_dev -> jenkins_deploy_dev + jenkins_deploy_dev -> jenkins_deploy_prod + +} diff --git a/scripts/release_processes/caas-prod.dot.png b/scripts/release_processes/caas-prod.dot.png new file mode 100644 index 0000000000000000000000000000000000000000..e0c6ac51f9d7485eac6cdaea6bf7ab6a08cafc18 GIT binary patch literal 38319 zcmeFZWm{DL_x??Xbcgg%l0$chba&U#skDG}HkbbT04gV_#8440O`$}Z9>jV`T8_UKrc6({l20lmT$TsE}8ygPK z$bj^Yjg2Ya9#%i;1iGoMSbq3LwxV?@+F<4R)$l>+b&dvlHmO(Du=Rs1o(@{-=Mz(Kr zzV*vc2Id+p1=iH_)^im&kfw08D#j&P4$#DIrRFE|mlzS2`lOMHj{QjZL_n&nUiM`i z7Rvw9=VuZkWokYdSQaF1I-upi@>I7Sq!8JS2rX~cw6%b>8wF;?z_$#M!$#4 z#m#Rgofzu@ch1B!Pw9*i@Wo>>yg6nH#JLP$q=qcx642G zYp>yUUxb2+3atqgXGE1q(7`vNk0j`FsTv8mROT)NQ#0OiTmxuf({j`%+kTl(mI*>7 zD7IlcxNmFyY|Yu{Oy$?W`M?x$`(4iKh1YYviiPMG#ftlq!cluqqpFzR{z>6DB-M=Q zkLn-Bv*SmI1IO2YmzUtQBYKdt^AJeGf4GS^XznPXXR1 z9ap?GugUU3dom55qDYw2(%IsLyNCbnPG4N}1kT#w%|KIc)xDMjcg8HIhGcL*p;p*1 zQ=T#6gKi*K&>=HQMB3cSyDrK#zw`0a1*3P?Ts}x1o4E0#Ls6fCWYsKFJU)i`J(lIF z&D6RATV56vbc&_*=4IGaJg%zbHKUDUZ=4R)z0fSnZRAz2ww6jU^DmtVz-HIFuPM`x)(=ZxDJNA}Y4FeK0A8IMaWj z?-mdC5hP2{8%o5A&DjAvrDEi|03r+9gJL%*KAVa$z>EQ?v0Qe!Kedh>t3$N7;v!$`_34{B|P8-DY5H~(9K{}Dl{Ci3w7_aVWxQn)>x;# zY;INXi>ed`}C1ru8I?@n;uue!>$-2MKR=FZb*(eM$~$fX;(7g#B!hmy9s<4%U0 zEB}@n5M4aC3lqi3seFFoQI>%(6A!pMDZO)nDg29p`j6CjTzmVF999W)n$TS=i_?a! z7}f|KJrf;NF0pFcADD6TKR+3}DYX1*7)?FjvbJd5Nyi99!Fn;u@vVyE_Sk;s-uJ)B z`YQ()x&8i!r(VlWx>3)#z{^c$~gm^hP@esWAc|9{qj;?#R%b32onRKJK<(Sp!d~G^hVB%pD zV0bp8Kndz6#H_XLZcA1vDjrSqsu=52lcv50#U_S*W72zhfdPDrCvH;VnC%~o-N*SV z#5*4Tu6Rn+yePfPM?k7m?l>D^^|XL`2I8WKk=~f2_hWR8^REyZfA2JqIZ@FtWfiAA z?)F0okbKv6P*S3u^kHg{rugYR@9CEt&aR%_T z*EQi7ZDN^m+$-Qim?^Cm7@O0UZ*56B1Y~@wuj%QgWWRIM$H+x8#}&QONe%z9{CA7E z-07yBY3oXDGUetkbB`)vX#Mq^mYU4>dD_uWmqi9-a}U4{pR)Y+yRFhRh4R2vE@0&*-I_*^OB249UTM@68~xkDdn^0WnXsRi z`!_Iv8h@)Rv{j8L3CdMp=1k1U`$bz*_X4Tv-FKbA2I7K6v$YD=#R8@g@)UVg2G9nx ztfYy9f*ifW;dNr*ec@=m>M*nKyO=Y>V;^fD1_CwP_}yxLXsJQoavHx;NjE9KJ-+&%OHytbb3p$(><^E!urkQ_DQTu(!K zjC)aDI(^JBEjK3Hs`O!F+SZMDkIV)g4AF-%SCH}LiT&v#@VxURn?^AmNySY-Q=g`Y z%!w7JaMVMSD<13DB!26hAoh0&MuD;?>7h+x;*r$Wm%nZK7Lk<+Eh5^0bMf32lZ=}l zs3Ni2p753NZ70X9@y%&c6-3D|qM(x`sIq=fQ)G5=?Z^ksF9b>zze ztQHb?ZKpH4K521P+iyqEn;LJgUG)i}JqrIP=1{*if;FYHjP(fp(ie!9YyD-KImZ-V zc#@`bJ1s>ePN>g$=eX2}!MGIZm74f4Kw z>wp7=>Lx$K!%Y}79c6<05c;%r^Cpi=hjs+D>QHmepnwH z1re!OFi|I@z z&5rbb(b)3XBAzA|S(lH_-UZ{WbLVU7&}J=Nm)BC5C{3z#bEy>8rg95QRz7KmG=;eA zh|1vxawBx(?`g+O_dKA&lEom|mw%v^3DGk`axS=xwRPz_ zw!|4m_e8}#>bOVV3wwOba{iJ*nZTVn?+EpXFc0uE_rR2$Q_`(DG82aUrk%;6OlW`u zE4>GHuSGJh+MFRHHPnB~Pkl9V0==Td7?Z6fBH9sjs9MSh(jLd>^RS{zkN!@Vj2Ds) z&r_Zrr_mF1pv}chcWO-METbU{Uj<=7clbkP;J+Hk&L7U(}NvKdCuAi5ji zBYs2(J<8uEvKJ7Mlg0PNMv;^S#{#=b?rGs}&BgW6q~YwG>je8Ub=t~Hey--WCP_nu z5|^79F&H6&{ZWCB__BuF0lbJD&Ds)$Yfit6&@n-(-|TGeU3!A8OufmjhUR{E(iJHF zZ8Q++#d5^n$LT_Mb25(n?0XP>iMCeHPo84q~P>exZ9)Ju4JP4 z;Nl*M5Zn-gx&1qhlCp)Sz#hTkl;|r$P_3wE2BM|>7>k2@JtwEkAbttV>34)7Zlm)8 z{pWWo=xKzE-)8D<172#d)?3GVI31K5kl(AzIQEeXB`b42|4IQI)Oor4Bf&$aYwPt-bDrHh81npuo5o zmcNqElR9Wa;(T8nN{J#y*A@Dd4bq-{zew_1#;+)un1NI@qU;XMwnceKchPnl7~STF zkKjxU#h+ryOLTvokGreaLZ#K&UhWWKko8YoUR|5NXl(<77c`>g@V(Z2_Ac5Jlz9eU zM=CK5EMw`0A?hlQ;Y|l;%qm)Zp}@6Q+|!(KXCLAIGqkP3D)wcoo;{(majNl=C;mOHli<7_QlMU<&ff`3yBU(RWz z{Yn2Owz4f*5HaSJ%>KS+C+lRaX@a5<`y=I|#96N&h1Fznpqs=3vHchJAzOs+XrTo1 z>B@Fvnet?>4ve{ur9N*rsMh3O$zs%OM*6@nhcw4~B+tdSauzxqnnQQEtbc#JQJF>x z>v}kTC!9C1swQ-(@bc%0^VP-=0a5l^4(5`Y zpZks^(yPFkO1-O~vQ&K$;7WxAavFHRrPth4dba?P;I`1kxn9EkS&o6&>xO!zuKb0r z<7aV!AN=uS-ms~xVjP;c4OCSzZt5fU0p^+Xo{{tyYk`&ipV4@zAaP+o#=Hz^+k)f) z_z$225b`!5LF}tTm{7Kn%8jlnyDR(7O#ZpXNG3U-X!plpRisKzOqJ`pkGrFIW8sM} z8M}*o)rRh!N_6c~BduQW_28mIBAr2HtigDsCs);xYc(N) z7{b7XxpTqOygg`RcobanFu`I9zCtY0SD+T1H-hoaNlb--Jc=RkTk^y`g8i}%_q8Px zPMM=0M#?5+e;Q-V-v;m<1$e(X)Rt10#t3(bs7o*9z>Fi<_a=21MS?&bYg!e@w9b*6 zwgX>U8;se(=z*-S&Oi>aM;%^`@LEbwEQ(~%$ICahk`6~E0dazU%P~!QA(b_ooBA0y zz-g)_4L|*&0?Sq1kQV2c^k~K%X}rip4|-jeR7F$p1hvRtOA_NC^*u_sX49CbB^^B}}Em7L}C!O-uPJIq;@Kv~7qhu*Q_Ta|U!?I9~`C;e}Qm#KuQ z_uXQYC;H7+6|XzR*5p7Lu?zGF7MW%$IjsXOY`$IU(aWyi%vxIvt}Ii(+xL~Fcj95F zhB1d-OC{s9utEGm8~aElP;Q%a2Ww8d6vP6h;>8$LDsOXz_>7NCX7r0rbQG*?RnQ^Z zWLrJ{ykzKt6_7AW|95vIM(%F>p@5&iQ()F>IOXDL!<^fBmTE%|wyCt3vq zq>Ga37?_R0_ICO5vCzX#crx$wcsKKSf3Ol(G>bL09EtQ9| zlH|QU)6f%3#GWKd&BGp+Jk9JowZ|Kbq#{ zf`F-&c9^Uo*e&uY52r<~=dhBZ!jRA66=1Ck#TI7%mMh(Hg0;B2&Tz((#BRf zQ`meCu4G1N#hP5JgskXe@HEzdL;=~xpS5w;_@GdLri|}6G*Voz?+X{x_q3@@7+JrXkqHMTMRTp zkTIWAIFtL{ty2Rpz8C^vD^mx@}%h#9vgAHbG(J~RTFBpH|N=b>SE00TA?Vn1~d!|Ak$QM8OA_*tP zPaKXM<>*aR0$in3LM#j2$dDR)_jO_hpg*luaxyOHa2wQKkG5Ow8iu#&vY5R~zCX2q zMxy@iZVZkzcsI>uSz@Tv3f2>Lui*;88}enoTXAM992vq#$*5J&b{PbLUiT}>5;_pc z(L&=beX-IDgku3or!CWC1*4#O( zjJX|9SS?jii*q7#DzS4y)8^lDQ4(<$EBDzb7iHSw&OyFPo#uqZ;}Zx9>Ko-##!dO*T|(hV+Dv|H;uW~WYTv< zm{}iwa><_H7W zR`v%VOSQjfnztj3`EBVF-$p%o`*-D?QN1%!qZML2;Sm^efp&2))Sekh7Ly`)i>LQG`st17## z@{m8@q&?g;+;{!c?Ghv%X=vQv9ZtbV^!w^%TQ;sr?ZQ0$DUU?Sn(7Ec!WXy)=Tl<@ z(t>v!`mWx=<|XYTAn0}Eo* zgp7d>p8k#WJ{f|yN4~^B7B)O}A`-9d((UH=@J{G!lw5l>O#WNV4-2K3RTQhF5wJh( zaE!1_L>*(q{D{6tTKHOQ7ys<_wr+VLZLxJg?z{`I!dM(0aJ|{Y-v(&A5*(;#vUVz~efv%QzP{u&GyiZHee(LUzJwR?%8dk~(| zpKy3UR*uk%M?OLx%TDN2{<1<&^f;CY%i6oa_qh=^%&c+~+UkRM^ zXPnXsbS*CF)WX~P;~C?E6G~Uo<@o*(4Mn#cPhDhYNFIblzC@l&8d*HB@lhE3hknUa ztM6~#gWXa=zDZGvV#_rbpeNd~Bh{gsm%ebFCI)-=B6fyfNVx0BlD5(M5hx4F9Z7FU zd~^2T_7p`ts`*2zdY(+vy)iS8MD%wET4g&Bv_KkgL4k0Ef&?iJ_^^KIt4cCA3Fhr~aaDOx4d>#p+916N8+1b`+9@?8 zB%#9UUQ5ggz7*dT>jY7onL0G#q(E`zrKShtGMt6BEO{ePLP2rdBg_WM%OR9+aMUDs zG+wEEuc2d)^t1h0Xz|8+KqRIVWy??aVe*@N&?^JsCu&)1f~t8I%B$+3k&%J~8M;F=~o{IWj}+uKFIy6ip88b~8ktAh(Q!sj(^_nVCF_ZU(Z?3f=A|TLJ_0f4lIWwqwd0d~z#fc6dAZEHRmU@ILKEwWa(Lzfgs!_LQP3=fl6OWciW- zUyr9jnT&WxPzWI>EGVnjL(}oLkr@Gu@KWc3f!+xew(JQY#gkEo>ki7`1R0o2LxQtm zAo!nj4D7R!!pIX9qijM28SjYgGE!+F0^XC&$ zko=Hz5%mKqKGdo5jq%(Ku>w3H$ zosSGh^CJC2pyGyAAF?QwaM5z=gsKf>KO>zlLdxQ#Y2s5eMIf)JaLF z$!DXm5u;b(jSiMk1K|BSS#s^!=b;Z)dG>ZF&VUj{-DsXO0Qn3rCShQFAJT}I8`3f* z5L%AcLMXKZp=gC^|7UF=pSciN_ak8Q1KcMD@Vl z%*Zpedrrj_uH5iuGeE@G3(CMV5122~VtjGl^aPB7BT0$l7zv5)t3uVUUc;8XJUvqb zq?U2uU8toEmwk`EWqk45`T+oeurZF43!DO++7KSNoa(6;l*XvgQD9|M>3ZvL6>pwT zO5oUUtcr5QeBP#?DmO%Rq^$Mq+sy0rmBnf3nc6sk}w77f{q4z6K^%tn4zyQC$T9f<^}3zukX}r0?fXH0@Y8kUMEc{6_r(FMKp9qg~i34?s&%px-@aRY+_A z3sI2mQ+a7_Th(6Rw}tKhlEu5>*=dOA)BU-DHz1|=0C%XF$7aX`1@3hdA=teLm}CDu zL|}QJ^Kk(ksMKff{FtkG(QI?O-n-#P2Y`et5t}=a&Io#nKj*Q!_C1~*waC(3 zU&UU)HIP;A42^{X-?0b4{8h1bC3b-EJ&U&+M!3=z^()t$DNMf}0+=_H@56F=CMF1KTsXdj9qf{sTzD$__wu zT?DT~t27%E_=b}Q!4>j1y_gicGWOohu;ry`;nEwmi@yp4G|~AS_$A7q@Lkp8 zcwo8kJ4uMo01}R5FJNvdbJwpr$^htGuju(0!EWw{xA7X)b-KgAAk?CP2V%D~@Kdo5T1Q*}igTYw^Ns=L+zVv~6Xas%bwPaT(`ZL?R9^ zsNF*}eA~LC&9I-YgN5=V)*WetCMfd}U`7~U*L*7jKG_NA8rm0wy-&>_7)@3*P+CkU zyigWKgZAD{g&G5|S@HMsEo;|BDyq7v{9WVGzB7<~+xA|%+o^UuS3RbEF9$1Zvt4Bc zZ}$rk6LbXtyG`A9o2>Hp!KPIa>m$#pA_&gm5r{<+*#Wl)hpFVUUw}|vnK+3;cp{_y z97k9r4-Z~lM*@zW*&e`-QZ_*POx+co{0|s9N+*9V{<~wKh28TV_5%ht#l>9UL{&&_ z$KRP_2a3Yo=hCe8x+2B}*f@*f{iqZ!l1p}l@qW&_DO^`ul);@+8-URQ{q9#|q9GUH zp!oyI@mx5lthk816Gn8@60%j2#@F*Bd(yatOJ$QayW&BZ7Vqt*j4)Cpt%2)TG zEiNjia&M+*yGRdhBt-$$jw4HxikeM6>rntHR-;pX>D>OAuVG%U@nqvFV0Unc2OJ}d z<)M4B>_1@R-vQT0^|=Bm`@@mh)1iWn(V?NN6|ik90sx=Gvq?gn+))#wb#RmsZq%4$ z_XKdJL}zYLx%T0)ceh{HVN2Y*Tw=Z7ie{=9#Fh;YyjxkHdzgE+VoZpfw8O;Bn(=c? zMDU@oDOWM+w&yFUs8-`S-7`2Po)qa8E|=xv%tcotz2-u?vYG2JT+MW)8a4Uy!^_B@ z|1=9QM$Q|l^8B7Z8BP5(SRRZvL}jQ!f6M$fyWp4fDlGYfX=)kgCbtHu4&m$u2Xf5e zo5a|a9F9R2>mfUi7$lQ})O7K`my2d{kY%h&%fiv48Vl~_L?yO*>D1PR->TI|E#4oZ zw<@Y?99d21L8kue(oq#2hDOu`mHo^;PK1vA=|?!rkhLAzVZ?=}SsZz}RAyDX*ltycGqOoj}!XJV))fnEZ;) zz1$C({K*jM={T8-UThJk_lXOC2Mtc+ED=NxoB&IK^OI=xU$~qnluNp;mb`9!^mzy? z7blIR4q7#kdD|Qt9)}+`aG{CuLiMS2b#&(ZW5h%se$~sFk7L~NMgOeDs1uqHS223m z(d>(*P|Gx)je72wIvxQ9d}QSn#iwuUfD5pRV0Y4*{3%eu0A-Wq9Odk4CywM}!cYVf zpHq&JG*(O<;IXi+sE9>=wqkzs{b}2M09Zw-y_O)Tp6GqBE1&MANF7vTmxSDt{o zsgi`yeRpJa!@xGDnCelE(sK^Bo72RueW2fZw)Eq+C`EO~1W^!ma1@Az{t=fz4N`di zdJN;w{l+j)UGF6%l2Z4te~3?hL4|3;kkh z(Vo*^PHe+o^k#m4WJoR{#a7{Bm=%?47)e^Y<(0tWedo+)CbrJ@#suT_Nt8xuN zXSB;?DP7x3O%#5?qwYDLBu8bS#RXW3-VvgfYEb0LLI^d!6h}2+$)r}ZZZYNg;TOBl zyUDc0ale>qzsD+i6W4QnA(|vz=yZZ#JR4oK1B&UnxWGRMD{zt}wH|(*ioB+AFMj9> z(f2i$^}Wq=WLAU5Jd%`9gy%bCHuN*oj8I6YYP?CsHqjB>`cAJnn7=nATlT|halK_T z;-xIaA@d1uA(y z4(lSand!rCbg>@I@zw{CrUGX}Alp*UMO8XpX)>RE&n-1@41+0QBGGLiVDF7K#)+i0J;A&DCP?L?#LApSMqpcjs|I;_eR|)-ippZ z65l)w?1i%CiiD$%E-yJdU&LvF%I}_ zy_V=vr~Up>T}1ddZ6Ud}D0j?I3|{1b@3=I9O6pFx@+&#nS0iE2+EB9NhEa~pz+d%) zHhVowf>&V#szYZ^RO;G!2D#VSbLVlv)0^%&`j$3(QE&8A^n`7g_C`~z2{kZjEPT~8 z4i3evu8x>vmoGIdYe`k~H8fq)#LYEj=TMEmuDU6WvsjqVn=|A(X@g&%%5#~R2P4y+J5(!z zy~6NI2WU6k&Cg``Vq6dzG`$OB9QB39PN2vJ)Az)O3|WnD7zV{HxX&~%3EYjKLXCr@ z!9tv0oFPD|H_~kk_B{_PLYpbi|GB%g-m6*Ujr1*=H3HN3c+`vZ#sc=;XX0 zSa3Y1Sd9!G1`vOZgZh~>D04VQ*rxQz*rPaWToYsKGWJq}Ev`jd-SglxQ@`#ntwwF2MRu!srUZJoe z){SM6(D%E)o$Si@xOuUmV`2;n+pS>vQ8;8Nm_topSl#gHjIF?T)`w{oX_)y{9rlX~ zkE$|BBywT@%C9-S#uP`tX>w~Rbk4Jlv$k+-)E{%5*3iqhWuogjd zq-k*CcvV5bM`6z0nuZolEhWrp%VkHI>#1A7KQjR@o6f{|t<~T-)9x?>mX4Yx?VD)z zv8eRMl=qE=MNVVf)j=Q%sL@>L>Dg%sMDsXDJWwJO@q@BNcT`N$&!2{}ji-zGc8aeX zzrX5}jeq6&ULsrhjUbdmuAM+#>=@~M`t8M_&&KPyE(4 z-uWHb|I$Hd&Zz7~y|Lv_Yw5^Axyg_z!&lYr?2A3wfApe>K$IZe#5u%WvjhEUu{uc} z3c$+!n5;!=Ur^Av&4B87{pKZ1U!(jre*dRaz8 zwwKbx8rAv`NX#vX(%7?^zNlic;OveiQA4y--CzKLz}TdwD9W}@a}aeQag}=MVMv^^_U-sf z7S$dqE6+&MgwQC6)?Fu))7{RX6KpU}!1J3;ExXDg*NGx?2ht>!!JSy(Q(zO%t(U|u z+P|E^d?CsPr%;*Z#Zt+c?c*AzG@v<$d>7e~a#nQ^1k4&Rzb0bZeUOoHa zj&;W2(&5BhZ@YW&L6~Tk_!D@Xzi;#M9O@e|q|?f*p&Cg$U!~ZT&Em7dxa16^W?twV zwg|TnS)^p9CSbp#2%~)y!F#~bOoE}+V~9V>t-K|3yO@xcfMxTV$(jb10 zy1TvZGfz*5D*byg4p2EWNK`}{|0NdxW^V}&w%B6cqJ3nf7^ zNMG}!snL;gY2;;YmClTkL8rOR1+>yKnvtWiLG(BJc`z5S2{~CtnJbw1^!CJmMlXX$ z%H&1fDzBCj5}Y1?Sy3H7vChJ5&Mh(-r6eGNKzy_{7i45Kj$f^%B3t(OW$%SpvFoKy zLl+QHhD0oLcsvQ_J&KNFkujF2})e-4knv~Bx#VU_>I;0w)h6R7VjZ8t4Jbr zhgcMJ>A;YjvS`)2hA+w=fg6V@PazNA zohVC>Rj4gVjvUk2GSyLB>mILw%zYMTgI(TWh^8Sz8laD~$tD$?#P5ZRvVc>d2K`U-!U+c_= ztB6?__`b|6vn;1EFtdr6w+3_la+^H35qkrO7n7%zhYrH;7B)^b8$UPAlN#vYO!uZ} zqAc_l+@JAF!btUnb{A9RB%|nP%%k*3bWE;_+GUL~cx=N)+Qek5;^=ZR6N6s5Mqw9z z?=xi30PXwIFfMR0rQe`k!ma=M@Ky0<3-Y>>F4@0Qd-!Nt8Yy#rjT8eSA5zS4G>hhE z<}btDptI=Ea*BV#bU1+V=2gbzuS%B7+B^O{xm(IJv{evCpcQS+1Q!^!e--m>lyI6= zN|%#q_|1z|RlYaeC))SR2d(N4Tze7A#=e?4B1&Sh7Z(PkK3tz4bL%+Bw?N~}g z_c)z)>4Y>*mwE>Bj!f*xhqH@ct+s38zM87di!p7Q0)P8x`Bc*laF}GdgOTUs5OQu& zfE#oF&=12VGB(rwg;hd5Dv8S8sDH@HUaB+<|AYl2DotET0DA9~9}+J34^^VAzZZCL zG;-|tV9Tph>HKi!KJ%J#gci6>q~P{XGN%X1RmLT@X4;{_xZrum zQ+L;Q|O&K^}q&g_x94oCSo(SKezat2BMjFM3MLzOyt84 zgwI;e1v$;c)T)2iWFB(+WSV1i{`cU28md{IDZ8%Pp#W|(Dj;)DxsbA^YXnzIUxWWN z+IDBR|FtiE*iDd3!ILB!nivPOA%Ix#Q|RE%u%c{nJr~AAd8G1$nBTfJw_h_pD`V)N z|5_(`JKMfv{{zUP9MrYd9Tr6cUv?W@i&19EK+c@iomF4gSMbV+;QxNHEJgbBP=+~T z@rS2;qV)R*#{{9>sUTg10fyh*4ju7%>pg(j;J7(puRT9?35srj%^(oWeQDhR>cq0# z_CLGhL-EHPeR_ETzr4`^?eF#ld|c*IG@K`Ou>ZEXVV~X0x((}ZuJ6r2XI_SMZyLB0P7PMI>u1vq>UHvuQ7%b@r zg0`TPIo)UUpx1a)gaEgC1;}-pyrMtY5xnOlJwuC>QBzTb8Fp7RYhUX7a9zpnSq3-r z6-7f_h?i2QyuyEfp$m}8qlHDGH#Z^gnxJp_n1mKX^h-)g8vb%@2;c@s2AQcV@l;%4 zQQuV=teg!@z?K`n8-AN9%9yXR(YIuKK062d#!za*WqXL@; zB@9Gs2z_{2Jmk|>3QXnEtB?TyA824r0;Yis`gi#Lo!d>Ssj9@D1n2L43M|8-X}e3| z1AUN`i)WhziFAp|`aODzz8B=)q!z%kY2am*${1BTtZ=xhc`j=hu|i=#vs&|bKcd*)d-p>gnxf{=>GKPj{(|@CyAQ>nJ>k4^%M7cg^MF8Vzaq+*v=jXB}6!bpbkR>|U zdG1aN2|5|d4%$@szkZ@;ar>?*ZE+6J#sCya^L&gD!Sv5);exb``ivH4bxpPYJ0A%+ zMkmen_+K}g9W_u=Hme!=sQh<6K5)!n?mhFrH~Z|53vf$u=N-QPdTQl?fRBgH1-Ma)aQ^b0@gC^$F+dKyJz#o}07NWnpEo#S9!r2Fu}A#w zSIUl04=`&0k0aCfZ@JM)kVHpV_rEeB1=OEjpsF`bHoLGL`bY}_lyr9CpFYn-6A{3C zRq$*Q^LWgWFhHWGLct;@i~!mj-Ae5q0UhXYtddSDU<$8{3RpGsmOHk>2t;^*@{6UY z9d(lu3Gll*fR^qw{u7Glq5+0?gfzkFjszenpc;o{yAGg_`KJ{ES~u62;C9>&_J>YD zQJxD5N`yJUjjDMfvCx8n#^;FcP}E33R{C<<1o-5H&sn4gcqoH!fwiVf{Wc&CpnWg^ zK2if6vWXmn<_T?8bzpzeri^*2~VyIJqVZB0?40QXDi^J=G|=^&MZ z;p*Gwcq%T0)_eN^qIXj-6`&eFzfNQk8gyYn5lk2ikBAbMD1Iy7Xl|b8cNSeiJnJrc z4CvcH!6s)e);n42h4casW{${5H|99Z@X2FWCn^~jzyIpF^GZs9y(-6F1AVBiw{^I* zlO9OE3BUk;NXY-R0RT)e(ES(re$9u;4^ZrAJ@<{g8-V{#kQ@hMW+fSS*JmAEbB}X? zYCQLlzwW(S53J?N1)psKEqx23?bf&f50`rJvrkVL~T~#MFhN>O=6Vfbio&zmDmS7p$jHEWzNwUvu$CD+ct#eu4;uQlFBvcGgJg;Ut zJ@vIEEB7r zTyv0ffk4lLe}2qQXBs z=phFhvSc!3BynZ6=(5O`M@klbXj+Za74h1$NvuBQLFV=rNEl)0TXdYDjd5LWFpmX7 zY%4%V@4?+j1a?mLkG40lNKdyaATO8vwA42LCE3fyJ!Mkzf=$BV}(BDT9y@LcA;-A5&ah zk~r5113YBJV(v3;$n-OdEdg{PEG;!LPpH?jeJ_ACOW|op%g>(;F%aiC!n+wIPV48^ zW!(}Prl4uKFssqQiNQpA#~e7WLBF{<2&W9H7`B50Qtj9E03*v8q_9R{txJ3uoW_KW zIbpo~YYzH&GAHz06wZE;E=G0t1=^Gq5e~_sqA#EOv=OPfFh@g_N}ZSYO^;*HK3hvR z21Nv)0+4oJKq}6YweNoAU+;5{r!E%2qKu$EyBU`s%Jo~QofMvPqg7+7?u(;N+dO~% zXKrZ#w0e_xn7)YRASI+`2u@6TDuef@*NG{7I_g3Lqej98-=E)~PdX}-vnsKf8F+{< z8oOIV08^4j$pT7kEKeP=6FuV>YQSCc4~fcA`Ou334H#|v#D!5nmv$ljXU0uL55O}R zyo@K={0IQqPe{8PV7k{d*#KJMV|#{?nf4{+4=4dLTF+ys?On;5jk62HJ8WQFx=`k* z*|25kvVq9a2%|`QerNzM3H;n?qS#-31@oeT`|ky@92JmghIDlmId{jc;`h&9kl(lt zJ+70$knKwX#De3AzUj2BBnDea? z;X^ssJ%55E4xTq)w$gFz`Rez~#c))SCe!pxg{s;PD{9{UqV1eF{f${=Sac|eGg=CC zFmoFN-Nf+aSVVm+l~wr9iAw$TM&9J}^97b)!c)M5_51;1R~aJSLeI^YhO#(}16(99bG zft3+~!O;anL%SJ7}th$ZXU9Ei(0Ud1~ZDK_E!qI3Fl^j1qW5JzNEsy-4 zKzuzJt3-v~76T6~pbL`u1Gs7kr~oAG0&LR=;6#nP1fV=<>eKLI692%HH!NrF`8l>w zV=ffBK;3jqe}#2upt=znI+2AH8qE@Rr6`n#<+6KJSqfMF=~p;~w#B$)y3D?s*I zfF|2H=fp~d%81%s0t36z>=D4&KngVRHlNl8J}8l_?VdYuTnCcWVJ9`9>(4v@9l}*# znY(IGkifS~)}Kt~EU~4)5!67J^#-VJ`Bc+UsvYu~u5Z~cmi^1V`))sILn_ASD-iw7 zbTaj2@=MGe2@3fZjkP3*wR6GQ0Pp;PJx^#^7V_R@_kJm$Sa&z=H<|8i{N2H@lW6oy z`Y^)Y8s=J4Z@vD`z&ub0$ZHIU;axxNE)~dRYnkP}ywc$4IiJG*gL!c6CB(YpzK?Ua!^)=GsvOo&S%$w~UG^Z2L#)kOqOFQ$TX)mL5PFq(iz>x}Li1IwbVY^?0;-`vOi;xj-tM;STvInMiTc&7F@VOGe1Bsp zhB9Lvtcl7s8uX~Cj}VxQ96lXhZ2$JEh`!nt%iGnhf(4w;$Y@Ma+~qjua_}VS`sw8G z-zj8-j;6#et8W(El&^iE?+Oc_k)k*XJ-EgVP+~#Sm$I1Sic-#VNau$YC`%1+Vi+r(PNao*#gXjVB77k##N?q6Kb`o3a7JGZMj`i=d2;jfh#>GuILeR(e0&!dRMK6CIlw&8LflsL$y^`j(i zW8H!f41+I@Afm-ai54UWNSbqm43is1gNgz+F_)WEz6xfU49|Gu0Lg_CrFq%8l>^pZ ze-OfeZA&y)YZO7xk{V*#`eOCA*8Q)?wDBz&VEZq1B;Ih)h5n|zJRxWP{El-6l-0c5 zEk4{qcR*tZA4n^K;{BCbFtSnF_1UO+<4&zQjz<;KZ)>L#M^9luD-`d7(lYUdk(FyqxfnDM=;spxH#pcHfu)w@63#p<2`RRM>Gh#c1Ar6a? zQf}Vl=14<*~(+C)~SD;qQ$=qOu52rsm*ppZ5-Z3I)2#Gr$}VAuAmkZ zF0fq{Xw6+3D8;^r8^sfdR%i$*+`BqsAiv=7)>&@BL7%KAiK60B&QTcdKCq05 zhDLIvl$7E;*2(eN%pJ}%7{8wi`Zn!`k%f`YV`CDa~9;zO3`1vk|kcwF#vmLr84#NtE2C_ zDJqRsU!BF5tSW|#0nJrL#w3#Sa?;Xd_sN6X!8Rz=8INouie$fqV}$AkB}CEZA(UbH zm&lk3*O;Sl zg)Y0SRsm@TS2cl9YO1$R^y-PHKv7BdcPiBiB}$ld6v2t|c}KT}BMf}fB!O}|`5)zH zq%vGmIuSJ`i>rRLxf><`g^-K7&ZiCBN7|FlxE;%D{q z3F|=Y$bos>YtCE8*DNkHQtzV}oXs0$&%eF(`-U^49J@}P zdA{mKA0ywO-hO<3V!N-}F%g$NU;A=vy_5pCF_G)$@Zs0U81Z;!>%*v&rLDfiNv5d_ z`i&^-PT9Q5u+6y1NeQWw_oCZ^4NBNifl!yElS~ViQWW!7N1!6!)Z)4=x<>s3NhnZ> z^RS)Dp8Uywk2UydsxD#_8E3OL{5<$~Tl2nG;@y%LkEA_K{|{*DR;S%O1qTtAK_QcKXz``UTwc4FMSJh#TB;76)|hQpF1sI`{;Z< z!@$n}rOWn*K1aci=!58KYTD}Pi78J->QD2gn|71GCUsAqpw%=h5k9%HEbyd!ZTi~q zY{H2xx7oca9-_1o-@Nnw*YbIZ!MGG=qd^tv&p-y%>;y*%f!qxasZ|M8!u9l`&w&yM zsq&=yIh&EP^!C$d?(-voP$>u@p8uQ<*(k(KZnsN(qG|(skX+|wG)UWGp#_Sh2!CuF zVvGnN#NzD3p>mUJXF+9Il=@jwJ5%~nkb^|H;pxqF@XEdOS4`B1?ecmlK2t1^&k|&y z{>4knGyF0C-iCQwI$p4U`TVB~jd3^IJpU6E{_uV=HjsA_n5xM&PG$6(XUq`|H+Q&K zsX*iPu+U#2Jh;-9=tkudGNA;ltp&F#7|1v z-YJ;y_Bn5Cf*_}*A{x59bfOY{D-ff&#Ca^{RTGa(aLXt6kynhKB*RGeBt$H1SquG6 zS|8c|b`pCw$BV;lne>a}yK3hn+Si4p>|&qt^C*(3bJK;M&41iXiXhY(Cy^&-@&EY! z#h=h|{VsA&R)%st0-X2m0#8Wx%i0m6iuhQJwP?fU$<##g*MSC1 zaGFi&XEidlJWLe&uN=reXYvD7#ih`vPiHH!1aZ6*HkqLlWAbpUJ8H`+l!NXCfN-eJ zWhk5kOcEhjo=+$|zfr2G;wv_~vp!es9!K5}sFYTTe+dtdGOAAZpO(Yk?KD!?WvC@jhMF*GNr07f9$g{3JBEop zM+YOz6IMc@J4jitrur?^Y57MVnvZ8c=_~&UoDK9tKJSn$@}!gIC~{KkCk=KH{8;2h zHoBr?+aI}?8rsZl=gSu>{}^;~#(S1*TjS>G3X9=n%(^rg#PsP(gtj?m&XjK*(;ssS z6jj_`_fNxwh;mUi{faS3jb^Qn$6Aj6O6aGe=r-sjK+}BSb05KGm34B=SFmi1sxIdc4coV zd_})`M&+g3Q1F=6Dc6z=Ps{aWPrn^he1~pl*_GnUt{4}qM174RVZe>fm0l}4n=)Si zmVWay4seZucnLx(c6Ru^g2U|FTt0e&L2)Qu00OBGBZ5CobmB|Q+pGw zCkro(%1+>ZVapN4LkA40>_40eILV1Ol1Z^_U$4{F5_d8HiA^V;I@v(;t4HXxgWrl$ zK5|Tr?ek{M7$_2_>ttjlc%q?4M$FF8%r%soi&5?m9^xDXdIV6cvCKFs=4Q{0cMV2l zfIFt|f8VDB_iTPdl|ML(vWD8dHO-szbxLSXeZ`*EgIpe3Mt5aFY!+~z?~DW@-2&_! zSlU?52R{W|Upbm2UojeOL?yKN#$HQMquWemBN*N}`IZx@(o#8WeJ`OOr>vwC*?$Wk z>U+DnmN^krbSJIoTh1bh_@Zj4=JOJsTc>F>f{y~aI zmYL3_Tg~huJ1A%|+)%^renvOZ4@x}0Uxit4l`gy|B^&|zo+?R};TJ>vIx!CWgJT7Y zU$1{m=E?9A>Y+B~>9}|-Hklu8uk9#3Jiz^V91%#V-l*hTQL~fl;7DnGh+QM6-my$N z@-CF5HED_RMFA~qK*SjwgX(F=byjDxVwI)X6T;ZSg88)TvNT8sXgAy*#pDYM|a_5t`J}0 zwO_?qs2e2~n+c(>%}Cbc5_&^51$-YwUVFjU(y9G5UWH-5ckHK65Dq^cqiH>O|3yw? zG0|t7r^EJ?1VQ;@{hbQWQljsr#5o^^_O(FKBNoa68JkJLbH7<99dxbCv`^DlzLMriO1( z`GnnFjcTH)mARLECg4}E@ZPh&K!O|>^5JwC#G1L-ly0$;ZACFe@~(^8+N#k=57Ud9##RH~^==>rF4)T37#Pbky^Yh?cm!j~O5XFs@6Ck_ zAN_8|p+YD7HYt8ik2Hi3?GH#Elk|PEUCjuB$!bJ?rJ08Z5cIX6Sdf?~>FZt*14E1t z1g#mNaJgwcCB952+pA$_a7?$(0U2Fz)H%wZ*^4!yTM&Abx`AJdk@E3x6b-|1VzIkt z+S!%o(I#(3-`*tdIR=gD7)HDMj~(?okFVo@)~Im$YM0{43@rlY9Gr0LmyZ5$1b=)y zD4&zbW1MN#d2YGi`a!U9!6|7VuhE7HcDySC@v0 zRRp-eGaC$TbZbjF+1rRd73%M4Vh^*Dg}+=yrjxVq&|*lo@q#-w>2MocfdBqxWxu|r zDkE#{eUqq3KN8CxV67%NHKH;JwD3L{)3(K(mT~BQ`i#&4Yf%&kBwC+QvlGa-t+39|m>e2b;^%yR(ZID&=Js8r6%Wc~eJoe4oNnBCzxXS%he-Mz=k?21qh{1_eB7*XAvI;Ih(J^pzl33lSD4EbEyCRa zG&A9fYDc*43WyD63`>dHB-`tFF{=d#4=Un;zdc0~%i6(!dCNTKs5_h_JGA=@}4Am)TwOD7L*xQ{e zfVJr+*h~@)Az!TFi!Moph5Vqz7Zwh9@zqeZ}%(AZ|K!}0jg7U9qV9O{hr`^}ka0nSgO zMwR>~tdo`3M1s5zp3%w#Dn+C)yr#y&9dgQ(T9#3day?smBqEd5+>v?H(t1AxPaMZw zc2PUV(3{=GksuN<%AF1m?~fPfb1Y+V29PZtxGGU3xkAHnD;*S#LKplp+L8|H^@O0* z_FsJ!Sx3s6#LIO*wj8%Ys=Q|4KE^LIU68_NPw)KlPtWKtJ{W@7a}$d64*U*2LCpzyFfGo>*Id{(G4z|9`nb zBu#2J(KDMOi^uf;rb+7n>GQud>HpHCVL8fm)-4S4-X^#DA}&0DLn?)ES!h zsbHy$7@h-5VRt%=T6GZM`T4_H^mds#10Xpw5A&b*N3i0!BPV7D#eVqB0OaG{iJCdr zIS708N%%bY;TF&?mFkslZ_OM$@ENBV;l_O`Cx~GPzL)sc8KC?3 zR~i8L%5LKxXrJv6|G1RZOLj0(3 z0N8Ah4WTrd#Nc8&7|lZ}TEp|M+0l0WJ&VzT;;ZUmqEGLLZp`|6XIRpay zPv{YtYfgv@!Mbpbf8uvxw@3ywbftRH<-r^>GgoZ@X(46%nG<6w`zV`~&UnRmM#s2P zfs9uvrGFJQlti(kluG(DacqLI7~3HbRq)fJ_}4@lSL&PbVz{7^cH7 zKSNe3i(cm8%Ch04kFJQXNy8FhPzeZa@VlRhTm*mw4a@fYm9!lSu-5pOL%ZX+ON!Y;~@mPc2EB+W9HX-@*B{u=`N* z=6Ifhl$`(>E8FOI6?vTM(&zI?!VY&%Q?f2)>u%Mp3CmsEH$Orc=hH_{talV_=RheD zXJ`?qv;pvM4JwKPlxTjmSDXyO3|9~J)YFH{sKf!i`a2edQ@s@jBU&JdWEKa~EL+tY zcv%Uk4(K4hAVzx|6B{&cZ~h~4&}FpS%+kWn0Sg%i?3qkS*V^JEw5W*qHf0P}Emmzb z1Rj?l*pGV|!~G)C-#9T``B?zbH36V_D@^<&F;`-99SV&H0V!I-Pm^MuyElJ>JZry* z^|SMD-cvEaXvQf8#0k`vW-6Ho?7n;uam_{$5~A{I^ICZ^GI>|uSsa7-3exnp$uiCr zL*Dw|SJ4p;Hz4EtGyoi;yQi)^i?5i#86#kFzb^mB7pjhQoYs&s2mJ*?DQSerS65^F zmVx2{B)dnQgrY_ODUb%pc7#Wo>vbEiPK`_nY*bt`aWhimDMU314u(C;pI2))cPVT_e9Dtq$QM+q31(` z?{3V*N<#nzHyD3Z@-!{(cs>L9!l?Xs+xp#g=spTU7iC;@9 z;Y&aGn+U!KIDpx36SRF9i@pEYNIzb!JFl2J90Rle51dvA8uu2g<^>S)!~hmVZM)H! zp>yB?S2Vey89#UL-HH40QnZLSP_1YmYm9R216mt@(cHh_=A8oR#;`dcWamGX$aS1p zgBqlGn555!;BUWM$0`N*1;uZo9c%-jw{INdtf~6iN)Z;_UR5aANG~!6W&{<|3+M@S zD^*d(7L&-NYpU$vvK^-PFFRSLQE`gXt!!g0gTLq-?mipLxygV`^T81sk8h+B`@p-eOKDW`CGKsr zFK*$7CcmMRCaP%vG+@1!K&FUWwS6(byT4iN&GUL=!9|Ap3oz-^xc*y@JtgP&CZ7W? z{03wEFUZ^!5~dL;zdhCe$j-WB=k1G8O=$?x?JVjgAjz26RehNSS+U4>!x>4zz?L+8#rQ@a6jSmKkvKnpt^ zt|c16)cj}|XnN}DJCCVR(A|ZasF(wOl=n%kh~Ciu8gm)>+n3H`2eI2AZvT=?xztFQ zQ0EyR9F_BR6*)_H^;yFt6dU+!SOE5Rf*wUZ@}AB0p_Gx z{!ChrAqL}(ipwWI!qKU9%=GQP0iH<*7OY=jI;GlNC6)N5K8W{shD&tB`AsO&>geY0 zRWpASoTOc52Ue=B)vQ&@sWrE9>X67AnG#m7!j!h~I(Rz%Qz2hpYRlq0WJoH|xv6C$8sa<2w{dN+cz|L#A3(1urKW3;u<- zJz>}@<7r=#9|Znro&8@tzW;!iu0$PDStbyKdw(@5)B5ttPZGeb`k3)DqVwccyv2V$ z-tt06!*9;Ou06A#%U~_K1+0*KKl)lp`3+ypsJsbO5+DYQEYsT6#)H@9hGWhpxwZm2O}bkV0_rlt7b< zUx0Q1kG)4v#Sl*#o@~Cvc-B&taS0?-vh11#xn!?q>MdG8VnG~|cHd_#)m@#S=F%Vh z!Wn-17OV%xRQ!JyfAmNBp3wVp4|P)<4l|0lm%S7VwExZ-VngpcuM>nnwF_hiO^2GU zzsK?v7pS}ri{+vQK-fn8*Eqj>~AUYXtba5c~oA<`Rr`c|`#Px$>ZRza4ePPt( zL3vtUg&p`u>`yF>J5GkWKtlDWG2`R)pychJZ=#|3)|wpm2W^>JbO9ZmvB`YK9SPO- z`nbw#AXK3CBW1S@GNPo$pv`0Ni+I%M5vc?p=jxHP&a{z-OGCQ0csO+~7BM+%x;agS zEOa^cYNIrEucXFagma`sm%eYC-KdIX+?6dw_&XLtA{2LpN_?nqo>SN>*T)SxKRwkY zU;mFwQ>*+vpx$9t20HoV%DcoH;HABDs+R;2$>$Qg(Catp&{2*N9FlW~Lfc3gfEHV@ zdxzL)=0If~%GdeI+vD9P;*m4jgzym+{AZcVx*eJD zpNZs#c?IRWeU3IcEhOsd^VTzv9Q86~ond=A_U(kSdfy*({3T>%79gyrsg|R(6Q}+c zj{T7ENWK^mOudjFn!|rjB)*we3psw5T|R8Bl}dxYffC@Ed_Yb)JPn&_dr?PmP{`PzP(d>Ka3kvh zh%=}T8q6b*SNcS2C|sTYY&ecHQ3AZQI|G7b4Y#@#s#N@h) zb@?jI4lS147lW%_cm-&Rg%Y|5t!rqXSzJr(_EW#?OS2>X!AsXrt@W-$XQrJ_3O$;0 zdL_-Aka4~tt%v9!%`&=;^9;aD#s(T*!bZlcOM2t+U2mIV<+-GsdhL(pR&o9XR-YZb z9;?*QCM)HIX{2liP!M&X|1_QX5@&8YMrA}urW1ylKHz)E?q9`oKl@f5fZs9!SJ>nbp!DH(?E3+4oeh70#bi$LhJpMyWg@?Mfw?!!FBVgZ_5Va z^}tfVze4SgAzMmHm~^&2H2*F-dUx+wPh0CW1-ceH)wlNZp8_n#ATdsJI%!fm-nrsu zR&b}O@L#?p1%A))r3KUx{3ibAg%B_6D(w_YEI9X$N|Fj9^otYR;waXFGcPa<=2IudwRTsyBvfH4 zKSo;^mp3R|Du;GbNzk#xjGpUK!rh>*JGlG;HvLeSYek-N1jWy&^gtei@ZHWY*7|Ne z{1W|+*kPq2Pa7^)o^D%t4z6296ReSR4|jCErI5!$V>q`BP$ zE<#6VEJRQB>*o0OwOgyp=N6;q$7_$Oz;)=) zN7O=ks^_Xb%(fFk2H_7$>>1#{txF4Nynd9LGn_6F_T?ef2^mln$@vQjch_$v<}3!A`bsTm9h5& z&5E&K9OU05|J#m>$sTZIrie8Plq;#9K{`!U?ki>N1GA}>%0}meV$r^7l}&T7sMLv$ z+GS`@P$&vuvj62%D0Aj{QAtE&7>)%)t^*)d+%7xFOF71g=N#ER91 zcoMmbI)YAKw`6t>OT2C2s8%s5>?6KVS_#qGBRD+bvx<@oV=Wf{uE?CP@n~6Av{`Dd zi24l9+zSbtboUMLeM7rj*4WOq#|quZPB+dB6#mfd3ddwQ|5=!`1=h;bAYKYP9O}## z;Ba_n$Hedizlt=$S(8fO*Nm#Q6;4!En}xH9MgjE02y1~i4z%Hg{Z@XG@bk8zW)z(s zrw|b}?V#cCB7cb_T=}v*hz`>km9#$alK+?5TCpP0}E<_%t}6nUA$2 zIT*HPcNp=AIVU47jctGRK~YcMi4|H&qJDSd(C~}bzc4K4pGG%+dPO>;srb=qEx7Uo ze9LpCYWQI|_VXMUW#*>jpqts6;50OZ=G;EowN=AFC?~~CH7unr%ljc(hI@jKG z@J&AWAe69g3eoem)EYnAuTen3`s}!}a8hnK!&dc_>MfE9bQCt)kCUKs!>o3V#;n{U z_B7NjDXPj^YL5)Z6>xcGG#cqbV|EQ)b~bqVC2O&IJ3*PCE>ES}S#i25Xe^!?pH@tp zXAfHHuuASyn;(RYN;Qnx-Z3go=7Jb9jpH@%<9zK;t=4gfuN_obQ4mUr@7G5&NSg)w zQG)NU1If1RTYrX*_T^YyrUnOf{wYERsx?rBPbcXuTT#u?Rq^^X?Jh^zno!{O2NME zA?cLx7s%h%6Yq21)ewbo>YUI{y>!>~Dx<0B4Bav_m@|Crh*xLQ*s1g6MnDGmWrrtT zQ;03<87{Op(tm#Uhd3=oxhJQwyZ@ayjwKeKH@r)P*kOeGtWu`$al_TveEFk^PVf82 zU#N_tzM5=Nos|zKliJe z$3m0TxcwUJNqY4*kSvfG_lL~r%%nfE#>y3@3mfE7QtkCdu1&*eys1amv6TM-2^{$_ zjy?|DG0~eF5o@T^9ly}rG2DEf>kZ)OUAB$3LI zRW2c{-lA=O7}9!F%7sSJrWuOVnt$x?IWRxYBw$exj_8#~`?zxSo_Aj{vx({0xUaK5 zbzy8^7n?Ss8K-zM%rr!E9d9<0yU%cI*i0lAhVv??b3=ua0QZUHHAR65I)FgxW(dh$ zA~MWbWogrzD=8Rq101y3i=ccE8fO)}5L;+_P)BTo5tYmiPAe6f5&AkjrvMYjl|cbP zh*x#)^^k|R#>bpCj}R?5Rs$qyP1c%p*8yv40Q&11`lLu5Z=)ZjtOPb25Y~Txe9&PK zT2zm-gWDly0}<)7&%a*m1ih35FT8U(bmEq%8WVb*I-7exxcu$eNC51DJ<#xOE=lMHq*U5vc*Mh+Hnx8>g%#iydG z7EjMJ2gp|w5$s?9!^N+#)_I3QN(*_a~e%OrFD2~F`a~ESIFKyIy^Bkt>Streg^H7JBzJ?7y7G2cD28l0>O0kx`my-&=<@c}Q#8)nF`$~RdMrW3BCQ=+pm`~9zZ&*}LYXrQq*@#eCk^8PYEZ|`uozW&D0 zceOg$V|BgDUM-?*-GY%3YXR@s>#ZC{*-9;C8o_W&=b2p!N$W$N{l?u-xWg%hnnnK4 z3M|`|VrkVS*QHqEm`Eabw{7e@u8sM&t>yJSn4;c<@i+F@nZB+iz_4Z`6U>i4Z7EpC ze-~gO@?o9&R@#cghK3?*ax!Oi)8WOijeXE?u4O)%U=%AGT{>|U3);$bb4pvifGBY| zDshmUSb$xYnE&B73)Tjq7}?(>Xa$-mkACIDwB3F#!#R0 zcMObhVq!G;;yj%A2>q6|Ocy3_EcioW^qNpIVib8<9GnPeW=1T7i6FiD804{S%Ch*i z7aFoC;3SL~)d}I?BXnP*lL8GKJ5P_w-}t}(@c-*0?F-7)5pJnB!;APQ_XD8RT54I` zsz}J1CfDSHUtN(0NO(zh0U=8-O}c!XOxj(IH}VL<8-~zGq(aJ$cuvpl?yXbz{ zG{Cj9?I-dcZIOp_P@vgFheMVisPAl}Wf`F!Yuy#+mfRK=t&Tc(bw=Q#`Q>GqhxpNS zKjTD5q3Km>YSb9iov1+@kp!Sp(si&8H<&X1IF48gbT5NUa59<0`?0JQa~8deZq4`` zbTfT)aD3vxH1WUMbz;&^l#M4rwy9JJF-lD1eGqX-l7`b1jQ={4m_x}Dc)c`x!QIaN z3A?||^xrWJYTY|{?UNmA3;OI8%UWtt`^nM zbzhtB{zB&rW=g|OGrl)PFGOjqrrT{lU8w@HORT9-4o_oXKE_hLrT z-DVbkO7lZ1n?frr1sWq30m_A(`XFNL`wdk|wi_>?vt;Gsy67I^Ig%?(5Wy6Rh&8F;2SnT?@j$ zkapDdEq97BmU{W~_>+tAS}c1ibsqbM;T$=%sJLcSjPuKFnfp%uqaSMmCGM8CS23(? zjbHHbx>+7hnRb5XRC~S%Mgt=jg$jos2BiEuVDQ}0Qm6u=ftq?sqn{<#iQJrxfB$V% z;$#9L_xFTfl5LhoL)1-%Eyp_dfw3dukL2;qtcewZ6Hp1^P%UV{3W9~<`qNpJvUD|x z%*{-yW@;e9CJ)V8Go2U*EX8OE)I;&rFF_<`m(jjx@3lsvC!o`=j{zwAyFi`(fxV&+ zvk#jO)Q$=E1re?&Z^?>59Ev4qBm0cPNObfEE-a_0ZI0g9X7r=;a3e@S>gpi#2`>pk zJ~qp9_0glG=XcD1@3v(wAzWeu4KL5R94z=<1_DqjT0xFmpfWLkfdZ7<+FqZT395tB zcOcI!i(pKWGs6EUwqi<-*R$&PwH#n~Ya4WaOH9%iO{p3HW-aab{gPx?^I=8Q znFW92g9Wy(IAoCR-vb93HNcC#83iE#ae~~662w7_u&R*m5CmNFlpp>yDDHv5$7U%E zRC`Cn5~S)URGi`dB)j*_8^RQwi2}n?u*p@3HuqZ|_VraDU5xn%1G`RY4K{2TD}K1I ziYyY9e=J|bEiVpYcM>ts*jPe_8_~rQe*EA&K7zZ93?Btog|l%fdV278*M7am&*Rjv z?tQQQ6sz8>=lHc+&ap%X*fLjgCY>+rx|{*G!Y02Z{TSF~3u40AJ?n?*l*T}BLXz-SDQP6*wW{C?ag zORn%LPUi0C?()=2KVU@dnLRDM1Q9hU-V0d&e61~Qw1KeNMbaq=p= z4Cq9jv)z@tvVo-%B~FQvxgVsgi-93z=Z+fChl*WB*|VcG`JR>Z07YH@sVfT#3b~G| zqTI7pP7LQn6w)^yq5Gy!j{bhzezNVdgxm~~I}g%^_g%lZV%o>dmA_GhaWMY^$Q!hf zy?a0O;3B&EusM&mc zfY)-foqNGIyIJ(x_pVZE)mfqMQpDEd0opFvDNNGEyWrZhr{6$&%Gs}$4a$HWdeky; zC=Y)B%d=93Ym*k^7W;B!d|YU*-+gSqnw(&xe1Qe%a*b@adm>B=dbmgO>2iM)V5*e) zcbpn(YY-wHZ2Oa`5OW%merD}LYGz_p`vTCtWi|C(W1?l;TMF9~IE>WtPN@U`@Sb1E z4;b1tP?VnXB3h46{Xv4Wh(npOLo4=Ke_#nZ%zDO6{4vo&Fg|VX-7h}LSflf-8O6*V zZib$&Sh7R^?hQIz^JUZTHw$L~x?ZKDKidm^t+q%V{)xCT`L6&Ok$t;^zCDaOG%kUs z7@=-ZxV-WTL2cZQjHhK=K_J`J9+dNiI@$A<3w-4f4W0^K6Wj_E_4uBfAS_^$_3w~B ztRIS-r)ilJc6-UUhlg;~+OjIIr21vPHOeQLt(@zYJ2&UAwC=K4KY?s#?LpM4ENDOG z+W+!yRo^u!vd2x2epW=(g=g+fd~*jvo#?*NP9pbOVxsV(wUQu7nW$wtTXn;w;llRo zSBLhQvBYb5v#z@RyyBi$n&dT(P$9d)CPtKB*V3o!uMdFRi<1g>lb1 zvlB?zj486~>z9}>*S_Cg7H#skmn1p6lOCpAT4D1bvyaxktJ(J!%sgq0v$5>9Eg4($ zMU_F# zFXa73f>sv{w(Et8mY1e?y!4<`Y(1-e@Fm~N9{AGF5jkCe?-tMOQyuc zv4r%6f`RI*XG!7jErZC|ehacip8PbyD^ZXDtpY-nS3z?O>9>n-qyk?^01Nuv{T&ek zkB&9km>)gqvgc3e%arBgN7-iU!Ea;y(X)hGOZ>(^S?l=}ZzjA*YY4quT_gxn`A~3K zLOI|}zKUrvzHI3uRxtf$?TqbvT6(Z!(!ue?p?|MrQ?SU_;BYtMAmY8xtXLn(Q1L9? zV24RNGohjDWJO1E_?mA!u?rqegv%RmIdHwOwKcXGGANFShXxv&u# z<9~%8a8r4F%CX)vmeCU?m?0MK-7-$LdUgdCu z;Xc5g&fOlq^anNDU|y4|;p5F7_mI^>f$iSuE{Lh~=n*m6uNx_nlc)BC zd8T#~NfUBry0_%QBMMlgIPuq&Cyls0FSs`opbhuVMI-c_sX z065UcK7umZVJdLa`>qhAxKI6Y67^uCI^LF#c7#ipa&I}zf7LQxo|thjEhtf72^(qc z)`)z^UjutG@w0wEt^{vf2T>-Pb60x<-QW2R?v53 z#e#`jGWy6vN|)$u+!ZZbieV%QsrD>-y%cnCV}$UbIAHLllMQ=sz9hSFq^XMT0GZCX z>sHz_;t{Xi*5$;XrbjqypO3H$WTuuSx<*2~@d{QvZB6w9ZoG(KXi(FWtV+xGY!VaC z-6h;B;n1fis-xH$;pxOHfqd}cA*UxO@sxtHjk)q7wCEca-bde}xfbb<n7ujN)mjy%hqy#gE`0@$d^dx4SW!pYh_Cs1Z&ATb2#xOzswr8 z!#^12d+BB0vNiG>;CdOApJmyq3c4}TO9>%WIs4@Z`?+*Q7T&Can-Y4rjyU`tl80`@ z6-4}3KMOBEUiQ44oB|uhz=&5bL(c1p?IKi{f_E1Z(C#5*dA*Q@9YwrrbwQ{jqUaW1 zTC3-D@I1N-ZvIAN_s?Yv9=FJEya6*xd5mYUiT{Y{DjsQ!FB6q^4>;eLd%dSP?SGht?x*KAI0W#4>P{*6IUEFleU=z7{ z?B7uZiEsO|r1xP8J9g7^i~~u3n=Niv7K4Hn%9Q`g{odqR^AJSX3W`&bXC+)G*`>CU z@G1A`xyMU?WNd$8CF;E*Zo&6nlg=P!r-hjO>p>5v@uBJym#My;rCs&KI@3k6@KH$} zksk`rWh)bS(F-2jpovVv`|4Xno@#gmj+>g;6NRFjTuh5U^w^B5Y*{aqdb@3X{Ah@` zgP1Nc;kf%${xYT?KS@}_Z1Oq01=U}9D9Htjb(A{n8?>3(%5gez$MtA@ zB+eXGM$jVgH#&G_^1Gn#-ZL4y2;b>fkJLhhTuW#fc4xh;rA0`8Nru^HrtO~O(Q^eq zpCjPWgmKzPEldX6pbV+Mr5q~8*w+o&(y^e(d?VJmZ)n9m$e5CoOZpuh>j_)MZw|K= zw1+g!>zx%U_-0V%y-anCJ#=*`-BxOC-#oS!P5U=ohV|;;gM0ihP5*Rsoz_hORLbg> zjV$aU*dVyDcSR7&eF>FO?v9C}MbsAIR?H?gY6|qkCy~q3l3L4!RR0?Z(@iwSU87FUga7R+sQqIP`vZ%Dah%cl>B}@ zX*@=|a{%j#EB?~tSS-~8`|+Y?VH(wQs^-0drd z-QU`s{Q5YI!Nu+JuzsE`145It>}fAZ+o3pEG9j@A`^V3VLqxqmVry?wM1}sjcF?JI zM>$XM`zzNaOq0v7&)xHH;{6ygd5$Yix|9CqTcO+P+BtfpR(mm z7#X>vKaknZ%XM%V2~YfdBW~nHge74FwdQwRS05!Qeb6{i+>gbSyv<+VufpUBo{7-F zNhXck8;0I2(aHWd=$AjuF`FZzY5x8IDJ=B(PZ~MOR$H0Zl7=}KVnYPmja>$Dq8(gC zw*M5b_UxPgd68sXOY{A1OH}lgE4zo4t74CyCS2^3Y)8oQyLmV-v1u5c;POZM!xyTEUHQ|5eq!Y~YJ*I`@xbzqAd~;LWhU1YrtHVnN zhvDK9w6lGCM6pD}?Ff5}Wze5s_s~5>_|o8-kZnytom~-ok#M<-?az=^{8o`b zi%$2OppBDp`+(epkZ*gbgkOBL2ie4RsU9oi6RJOdmHWQBze*Ld-H$j3-kIbqmdVQzgg|{xgDP zY7SOK^F~P=W>y;2(PrU}IVO%;qn`6HD(Zb!s*~Y+K28=g!JFI0t|2q}*mQO*X(OhA zXw{7FezC@}XxJFduZnK_{GU^TIVC*;(0HkVc((mD0&-nlA6sw35{(bY!?&HWgYhf?iU#q@NmYM-2q z851ep#>q8*tR1si4{Fpg^t|5VFIWM)PPj=9) z)@3vN_jXC|Azav)ZA}f?=2~E;nda#alLw2=Rygy|JL$W>LGV-{`c}oLz-4Z^6Frl` z-_*}_WR&G-eeKY;=3dcHMNhKCj4n;ph4XN2f#g{YabtU&V5}Tydk?CU5MH9)wATs6Ha-%mdIyl ztF&F_tWqKWCvydi`XhMXxAl+-KH;x(ChL)ok4Y1~ijrQ-@$;z(X0=|OFoPDQ9q!HFukmpR+p752G%8j#m8|e1S2z#+3qod zMr>x@2_JzlGgki>-3=Af(76y00@XtRFC%QZgEtm*y(?C%@Iv6yjm6lw=i)eAmQ}BW z{g-1gupZWl>0c5qb*?q97Un(N8#m%CTob{_@bEBW#th$|3&pKS2nc~{Ab{5tJdH4` zVm~nqU+`0j_}$iJ`d}5u&Ny8T_gd8`S+#O01~!iMWxCB|UIpCZ$hMepXQwz%MTdso zjEyF75rm5v0&aOUS{ug-(yN3(2?F^0#C;b5--$3SoD=gsBTd2UJ_Ax*neh$VK5`|g z=u$5b5CiK4svV?>h!If({sS4&v3&_9%;p8eM{c&?l8YeCT~A_52!buLma@GAjCHFJ zXa@mXoSjeszxY)73kR(pIzjP}?|3e zBS8p9xznai8xqXDj&DOh>KE%lAu!+wAo^qPGj<}#2XT#zK@8_tA!UG(LCRS`KnATvL|qighTr1>a-3qu;yUhLX1jDjFBl;lIFeM z0}PF0Vm7FZVB2Ukwrg)I6S_$Vv_JrnIVR{4*!PS8GeR##OyC-py+OEHVgMFln{Bpn zNrQQq_n0=Th1r4DV8&u#gITW~mC76!`-qlNZFCm+MT}!Q5jqcc)XmeBDN|hB0>g`d z{veJ~aI=vmA&zCCl}B*!j*$l7ae|FD3HYESFi0||!HqbMVOpVk3N+X(m4Q+Wo)|!} z3v~>tIEHB0Sx&Ou$H* zI079L!8SznIDZV&q=OTUJp$AhRrbvo;ZeS5x9n^FHg+ssqYxr z6yyA?o@4G634wMH5CdxmCEYVB1nBf&WckrJ$8>bj@fBk_KswIwlwS5?W^phwm|;F< z!6-&55$BItV2aKoVvge+hDadYV&FPDL3}y33W%v%1@cda-YPI-X%%277$vj{h~6TA zR~!>_&di!wb3Tq^7!`p*NyI$YiohiX7BSE$#xS1ZjGJ@r(|X2M$m)7G2#A4ogPxx4 z83J_LG3t*Q0G(~V!;OwNI%7KL=%9CF|a|XMU_Da2mv8bLj?X0zP^E>xvws&00000NkvXX Hu0mjf@V8`h literal 0 HcmV?d00001 diff --git a/scripts/release_processes/firecloud-develop.dot b/scripts/release_processes/firecloud-develop.dot new file mode 100644 index 00000000000..48a077a8c79 --- /dev/null +++ b/scripts/release_processes/firecloud-develop.dot @@ -0,0 +1,73 @@ +digraph { + + # Nodes + + release_cromwell [shape=oval label="Release new Cromwell version! Woohoo!"]; + + agora_branch [shape=oval label="Make agora PR with new Cromwell version"]; + agora_PR [shape=oval label="Wait for PR to go green (jenkins must finish!)"]; + agora_merge [shape=oval label="Merge agora PR"]; + + rawls_branch [shape=oval label="Make rawls PR with new Cromwell version"]; + rawls_PR [shape=oval label="Wait for PR to go green (jenkins must finish!)"]; + rawls_merge [shape=oval label="Merge rawls PR"]; + + fcdev_branch [shape=oval label="Make firecloud-develop PR for new Cromwell version"]; + fcdev_submodules [shape=oval label="Set rawls/agora submodule targets in firecloud-develop PR"]; + fcdev_test [shape=oval label="Retest firecloud-develop PR. Confirm Cromwell version in Jenkins console logs"]; + fcdev_wait_for_agrawal [shape=oval label="Wait for post-merge agora/rawls builds to finish"]; + fcdev_update_dev [shape=oval label="Checkout 'dev' branch of firecloud-develop and 'git pull' the latest changes"]; + fcdev_undo_submodules [shape=oval label="Rebase firecloud-develop PR and remove submodule changes"]; + fcdev_final_swatomation [shape=oval label="Retest firecloud-develop PR. Confirm Cromwell version in Jenkins console logs"]; + fcdev_merge [shape=oval label="Merge firecloud-develop PR"]; + + dspjenkins_PR [shape=oval label="Make dsp-jenkins PR setting new Cromwell version"]; + dspjenkins_merge [shape=oval label="Merge dsp-jenkins PR"]; + + jenkins_set [shape=oval label="[Jenkins] Use dsl-seed to make our dsp-jenkins branch the default"]; + jenkins_reset [shape=oval label="[Jenkins] Use dsl-seed to make 'master' the default again"]; + + fiab_smoke [shape=oval label="FIAB smoke test"]; + dev_smoke [shape=oval label="Dev (pre-merge) smoke test"]; + qa_perf [shape=oval label="QA performance testing"]; + + # Edges + + release_cromwell -> agora_branch + release_cromwell -> rawls_branch + release_cromwell -> fcdev_branch + release_cromwell -> dspjenkins_PR + + agora_branch -> agora_PR + rawls_branch -> rawls_PR + + fcdev_branch -> fcdev_submodules + agora_PR -> fcdev_submodules + rawls_PR -> fcdev_submodules + + dspjenkins_PR -> jenkins_set + + jenkins_set -> fcdev_test + fcdev_submodules -> fcdev_test + fcdev_test -> jenkins_reset + + fcdev_submodules -> fiab_smoke + fcdev_test -> qa_perf + fiab_smoke -> qa_perf + + qa_perf -> dev_smoke + + dev_smoke -> agora_merge + dev_smoke -> rawls_merge + dev_smoke -> dspjenkins_merge + + agora_merge -> fcdev_wait_for_agrawal + rawls_merge -> fcdev_wait_for_agrawal + + fcdev_wait_for_agrawal -> fcdev_update_dev + fcdev_update_dev -> fcdev_undo_submodules + + dspjenkins_merge -> fcdev_final_swatomation + fcdev_undo_submodules -> fcdev_final_swatomation + fcdev_final_swatomation -> fcdev_merge +} diff --git a/scripts/release_processes/firecloud-develop.dot.png b/scripts/release_processes/firecloud-develop.dot.png new file mode 100644 index 0000000000000000000000000000000000000000..ccc6138b70c211b412b2bdae1d5183aa07f5fcec GIT binary patch literal 253609 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 XQf8G$LV=^4a;yz5HGHSYZ@snP z;lN(lckko434HgsE%T%CE3ingkzWe8DL=Kzcy-fa+~5ckQcRsn#vWEds%Sdq`0v ztZmec_S+u8X_pjG4DWU=W;vT9A(?tJexYfa`+flTpRUe7j0T)R7Dod5pVWp5CJcua z2O+Do(a%MGcrAzY2bVXNgIAs(JvpI4>OC4AAw&P*1MGPjJ}SN?3~R}dP2>@+2x%>6 zGE15vG{#h%`B7);nh}gXOp}m>m8D^s*5=1+ISKmc4*6WHH`a@7na_HDeIQI4-ylN7 zz@6Xnz4DZ(t66-bfRy#!s;HjO2eh;(0kHgXz#{Xs-jXU2z|ws?B%gdo&u-kqL8PR7 zxP3PSX9};7w~+%sq!x5d;c;=h#zyVlEmz1!ysRq2OAQ}ws&Q(nuqM@XQP}2i(nRx> zNs8m8yq2^O+5ssb-nxU}J+l4|(*d(MKWxmAv?B$%0 zRN89tkQt;dwsG&Up>!SH@gMWbHd{qOLi*$wz2WLE%sufx&G@Ju(klH$*ziyQnnJN% zU*<1nxebP+ZO?oJ+L|Cxe+8cfzLJ-D8P5A6SJSh4X%OHhHK)kc72$v(hSz>aym2!?;4lsCRn;w*^}-_Gi*@OOb*2G`{+mCx z(-0Au=e}#833#9oliVep*p!ucrD)MZOS-bnW!?J8RgUDWu5f&V1XbNevkd1 z+XZ&sos)FN-h8ebn)uEoLi;KSXKj@QilpsN@4Y=Dn>l_cuubH{s-4c+@{)fq&(hxn?9hU273&P_L$@4nGzeUjt~{hv|iS5I-JeCx4z zPeT5hnajh<8yfDg#$XXs%6NGC@X^lb7T_Ul+uMbzNRf`Hub4M^g#>!%<_0n@oq1D} zfBYdtuw+PV{QB^BrKjm_g=MKENQJX>b-rQxM%Opb8oHpx>+tcp?=fSB7k4Pqt2C4q zJa&gSqg5Fbssx>H1UQn|qc>qO7R+XyS-g7vfR3AVRV|jwvXa*!BwLITh}X2$BH)j) z7>g?|vKWhzLi`f{_2v~FI+L8UfyIRMeY#N0;|h_QVTCMHgI$PRc)yUS{=Ld!;SCc9 zo$9Q@58*A5g*)xH9~skD*cc34+T;xO?5_^QFcD~LG2yS=#6H9hXOg1t3oB}K%ymtY zLS!T1v3O;(u$L{|OoyH!Y>HoBhSfT0&Rdw*uJh5+u zk~4Br>%()pD9-o^UxJZ0Odsg`=9xnaC1aSH7)tuYLpg+O(wJ&S1sx0fhKwD1M33mi z&&GNj_p)m1>B4g52u$BF?T$HQ8OBZmWL<6YDm2R$Z^~eKS@n{=a*{igM#nbY{JG~$ zYYD>Mv_eDU1LpQIhjxlR4i5F3o8s=$3F)qdeFx9*^d2rB;<6{dVX9;3qhS|Z&oAB3 zwr`Yr%M!{#{Oh!dyT+#cEDh7D=4N00TfI9K;R_e^_zaJVuV4eMOh4#V*~+=4Dof;? z^OC{-FoxQyIW2u+F1@-uW5fomu~6QQK?H}`NRokClJ||x#-IJ%p~KE-S1mtZ+zWl+ zg(UN!%(?4g#BIuDgibqzg#YU5UbN@ChNzXLn%Nf^5fn!@IX8(PI*3BeOtWu_C&~0* zdBIeAwx5uIv7zd(JPmm0SVVtto4KSMzUn?SUJ6{gp&_IBEF^cR8I1Tj&5y2L$tbqB)kKyoeZGog+Dy)28{sfu z%IP{M0)(rJ(+sJ!3)vC(OoM#VQ-k&eLI1b~0KpXmt+fhXc53H`lyJ9wB1kCIWH9-j z|L3hzalIBiHwr`1|LB?ktiE!(ElX=v_MUs>bya}y?l)TY{!wkBl83s#Q|qUMrcY1u ztjVP&-KIgbFz`HFAGGga&J7q)67H5dTod(7OZu14QWpUg@wP9T%fF2oc#9{>2MA;5-f4lb+0D?4WA)EE`}ixvPD>z^*uBKx4KM;6i}BLqBEr?=yWMMH>#{g|8nS-@ewb^Cm;Cm2x00YK>s2JH&VM%6;pG6yE2 z9i&!?dhCDrx%Xra+VISNG&X||}WR73f>pjAJ* z`|R5*d2!P?gFiOkP!B9+*fdM<&*jmmh{B5d6Gh;k(cD7Zrg7??ea932F6Mn2U{f*H zM)M+Frg|_x-BZGx1}KG;Frb{=@T6=6!lM82T3%7I3cpF)EmkTGM>5+gqn`lu5Q8Kt z8ch<7MU()k=+cu06&8J2B1*D3f1QdlSjt+=`$PJ_wk7U?A!zG|RU)3CylyiIsE=cP zI?lYmHpDus;qzn3IL0*()am+29i9Q=PEIwWVe(Mp)?q}VlBVs?hONs%U7I=@Fu z>IK)Y7%;LoNmEJ?sA=Zm4LX&!kxvaN>jo$7h$` zc#VbA3i5uxka+C)4kD|iR4p_5n9=~K>0VO|2uSp>sezKb;;}{-k^6%R+aXZdg@z^Uq8QvA^n1^57J0NhZM>pf%1EQt{n`L@DsEJF5I)Dt&DK7sY*Kovc`|<>oH~_I=P@HRgpo`%%j%m z1~hC}nlL}`A21RP3|O+3Q;K?MqQHd6-OXY7VWE!zOskH{u{`ZAhIOHPh!hzueq8aM zRO93wcDKAAy`zG={~5RMagS5(d-Z>>Nf@+Bf@s2jRk!O*mCuhz8fZyue*WJ1h9KOI zE?0H5U|)c0fukIllpt-`Kgf`1x6XfbU~{Ur)7Y-Q=#?uKQk)_JhG9CjufdorG8yO>^Nt z*5TAwyUQ#u{t+muDWH)Kb zCer3>EKKUpsF?f=^9ytbG{3uBOx3uIT4YB$n((|n3ds}NU5&jt2L$QUWyo#N5rNPH zgH^dr!x^`Id)|w2viI)X+L`Uafl)u4Y1a{t62m z|A+KVRmu>rJ}uyRXpO(lgu}6T6O%NSf8IFi;lu;P^+a%i-6*U`?dp!&ydKkFUak6R z2IGX7V$SO|5R|<7=I*uX{1TH-c1s=(t||>o?DS{0ADMLPiL-pVIr7zl7g$r4NO04I zv9`NRNc#{?b7ZcqODA`zpfty$B=&g^+UcAE>OIBWjHQszlh~HHob0XKGX>e(`C`26>sp$J*WK%DI^pf1E5czq?X1FB;s zPm1|Ca?QSgy^oIw8BQ~9CkEQGn79oEYsn-U-^^ga;p8tf?O9rJyJ->p3-{sNvz!6u z7UVkXun)l)>TJhhE(DehgD%Ve1W)86(3#QR7@7Nv(g&5AN?S2QOcbamw2WO`k+SWH zPPQj6G@r=6BD-rSVKMMtQ7gk-F;LR_n>KPa&i%D!X^mogY7^kRL&o+$o<*X3K?%}9 z6yX4{;PEV2;5y7bW$S5J+AXCz{iNwf?2jS4sVa4=-x$6rO+%BqQaTXg$kAVtuQGc% z--`_0 zSE7^=&?aLuNJxK#8BK3K>Ro*mE>=MpBa((;iuGr?`>I&1#DUE)9KL~gxxn2JI!kK$ zG&hGpIL9}`3w1RKHtA7GU1ow3!q%(+;75d~e=hQRy|j8V$tlJ5q2E%dhdY|bVi#CG{EmR^GuMy+f!K&#BlWNwxj$s&j8F))du7s>fW-puuse zLqh7+UR`d!nwlTc5ds(c`7(Zx9*sr>Cnj+2&Zz!dPW^>&K*~a~7?=7ti-EK3NJfai5jd$~Ouv$ay-9(g z_1&gs^*t%dr&~qb#+dU{0LnB2@q}!%ZcoWcxeB1eK-KJrfuI68QuwzAQWE*<63}!r z66dVE?m<_6RGVW;Li4v|AXeu9ioaD=tqv9-$d!Efu8n54Qq3gO;X`_FHvIrtCQ+a2 zI!L>puIBy{oAsG(n7vei00=pfbF2d*r-8gV$t0VnM3pjzJI7*^CNnzy9%!o41&lI=BjB9~|H|Ez!q216N6XbzSQa^?WB+JGse1|C1S# z^@z1_gyB$Z(fwCb;=n`jP)!MpN1xF39xs;Jw3nQ8Qt z9TabTeay+an3L;yxS@50JVg1QNC4vE3*bWt%KYU0S4~EUw5+#o{3ioh4m;V^bhbl+$lto^1uE*w%b!NJM=Ko` zJQ!5_)i4gQj#aI20Ve<#H(~ZN5t+e$D<8w*=?%dCRs9ASk*6n37HEVG7DZn=aNhoB zhmupl$`~49EPr(~U-6pI3tz>N?>YENTZ|Oy5ewKG-d8OI4wc)T637PPNRzacGd{Iw zEZAblnbjO3tM6jENS}kIW4+SJh8>9+2>?TWuH#R6h3Izm2O6`N&Cg@bFh^>j#(7B6 zujcDlk;^Sq_`6cG?$6)K8nf>MA((`YOs%HyGWPyO3_zsY1KD}b~+PIo`p z|C8{+H;VEItb)2~w?DsWdGX`M!Z(7?Z`LO&i}DxAV;|E(hP7mOM_`YD1 z>|YM6D@&9Kem7n3xh|S}YDbdx_WC+Yv;aIkvK`?9`=e<3J2Dg-e+_%_ViF*_8z$Yj z_C)1ClwZ2Nl&EY1UY~$g;w96tVpUx#pN;mYZSAkFH=D6)N?hwf#uLwzPTk-l*8ZxTX`sgX2nCNisy_fkNx*WMz-j!Y(yW_Qn5=EWbW)^M zcsf*e;B}OO=I*~PcmjG*OPDCx3I2fmAKb574MXeV2O5r07m-H05CFE(B#AV|@k1>0 zYlIiuz|Qsj#W&h$5j>B>4H6}>lV|vAz||Ctca$|Gl{tsjdmr(BQpnk(P2~V#n8<%z zC$jj*K+EgK@DGQJNQP9w7{C5nhDb|%m}vfPKTSG@XPw(zj>JDa9F=VGfa0G;PUQp! zkei0etL(mSWD!EnKUD_h3xSh34qMW^cM)bUcnp}qB>Jgqw)(LEpbZqy1%SvenF4x) zx(E=#1hm^=2@@Bd3z7WEaa@Jm*LEX7mVPs};PZI zZ9=Drk}N7DOliNHa+7+-Z>4;4(_Fb~J`^uLsS!k^kft@bk6ECLl09Re9Ai_2#JAEMLH`?0ZeoGcl8HfIUobn`-tzsPo;w zkw-V_aYAeeL0^;GlI^(uHyI^zLxJ6ETxu0Gok38;3|m!G7UlhLPwM5u2%=Qx7;a_@ z!Z4@>)rrBK`pYG$y}-7iyTV4ar`tQ=X;fNk{P%-1P3 zi%c^CUUUrY@0jEQDwqwoeHqnP(15v-jN!4NY3oHDaOxz>a(V1qINjDvGcPE`^KXV} z6zY-yhXPjVUGG_clm`F@BVD1i3SZv2rV4urVDX#=>G(VE6;lXB9!K|pp6~83n=vIt zYyShv=Jaq+yuIZ4j@3kHF})iAH&`83`XT&lgq2`NrK*}se?!@IG7oqr8#~SKec6Im z6e|M}(pjKMlR$V{z(4JtjfS_-9$nd14-sX$h_=;B@p1O(EZL|)c7^XP2XdLTC4RUV zw<-!WY6%*<3b?{Bd32pEBm{9Xvk^7iEpdDHsxH5VQF2AE3YT0^-xBwfC*@=B2; z>uwZfE(5rkC0JI}y86?AFV=Vk;9AlUKwA_9IPajEgpThHz`@3q6X0Tzg&fJ2SXl8p;Qhsta(wxvv9np&-l@ zLInUUYW+j$A4szw%?nt|qze{{nP|$M53yK({>}ix6ZGA&J}L#wNVg?sfs?zZy6&Bk z?c%rB4BV;q&1goTlH~i{Q^A$atV7M$uNRsGzFf1n9)P9_gvMnqmCaKf9zybWM_w^Y zB(zKdYQGdkn8-Y^G7ig=bjCE*A~jPGwxz#m|78Z4pYRR6@c-1{Ca}tZ$UhR{knAiF z@xHHr#$y$9J8FTufx)6oDt^Nzgmr1%NH1q4npqgJ+s^5F5&wOo`PNV)U^~U!+{Gi( zgo5or5&$vexn!$CE>*AocXG>SoBIRG*ix5n*P*EL5w_fuafd-U-THyP(ARs&8fUg~ z!K^mW%^?}l`fql=6bGbg&A%`~DiaTF_H*v2rsMQL!c8R72c;aW-Zg*~igQ0@DMigE zLY%BI{`Io66EeHz%R%y9>3d*Qyja|ItpEAk1ouNy69#9U;Z-BxL-~5Y1YAuenWvKg zTXhxiQv@fwW%5GXk%@!~O=+2h1|y3wp&>ip??0s~xlSCDIX zYZVId{}Dky5F>$F#%4heH5}_Do!^~-$1Ia7STop40o;6l9DVrG?b3HG9Vk{A^@rn~ zx_cDB{qQb6BJKSy^uDUr&u^ro?91~dEu`PTG9C(`~VSV{sOY3-QkI9- z_&ce#&H-ZhkdNEXbFj(DXy9nO7ay$i{FAAZLz=)@>8ZR*;kVPDZAt9~bmEH_x>uJM zrt~9HBg(-AkC1H3C0nkh+-7VvgflMpbGw()^Ici?l0OZ0hc|U{Hd;4NK_|~=W&)+e@^??W}#q*5+NWM z`fKU0^cJo5SFnBareGqlks)I8qBbj5w1g11S0->*0Ub}L;S~LAbOMmE2S?5*AHYN zwx5&owg7l=Fnv2%AK?VG?z66v!V(xUqV_w$H0jrK_#c>BNX~ouT+5rX5dIDL8ylRhw z=T2$gN8Q2oF&PEzA)9p9I=Dxof~@)7+P`h7q`in#r>OFpV(85Jay zQU~YHCf~TakA)luo*?tTRC-H;+B+Lij){461&o*luUCQn^KJs%bE8B?I`t(wxBx0{ zNJWj8H5XS!4X0~Q({$ryzUyFTTkLhR`y1}JK$4X-=|M>l z>oa;rx4SjjQlMDOzHtRD{V-LuFk_qkmp3<`x>qItR(DVh>7fQCacSy!XW`cghWKYpWsSmq9RWALJHgP zw$IhYQ7|xX2LP?Ck969S?lBZc{>CuKpyuhL>oWAj=x}4qXhhGge5D<9=#s#y8fNp! z^!HwnAZgAkrto#Q3d8DvzLY3UNkQWv*>%sgH3ZiPl+k$CU*o)`D2l(^-&k3I9YQn9 zlK%@GBxrHm!2&SQ9ryR2O|l?q*q^X1Ci2_QN>sO51z6=s0tO8SqAa@KV6i4a<2904 zg01?%c0n%#7bquFUHC;90=Ie_C@~dj{nJ zs7iIA^{PBnKH4C56zg_J1HAw-Cr1WTC78Jj2CPKfNzx~GurE1yO^$~^V&bz(WLA40 z0Gg_CHY0n}VCnqpd^d=KD1$XOLl%N1SOt5 zhNd&LP3}-=KvBdEXA?Ani_moIGNz0<;28kckV4pEits%Gru4;mFfrB@xvcN`>o%D0 z+A0PrNMM@lxEzwcDA$NB*fy4Q(7A*)1_=E(BUwDVp87$x)8hJ(dxA=P-5r3;SrkVe z?*ybb2igSPV4zl6JV)Xg)dktpY-#rk%AEi(xid-n9hjfL^;XzSyyr-d0*mSGLcPl| z01%j2PY$RSf_EwLd$RBKh}us1 zmR(yAfY>PH_I_|3i>k*KJy2k)3^a#$qJFU#|Uu)*MkG4VGdi@oweW+ z!MZvqHxA*ghW~vQfH04fkdXIv;Q1n>FoIrU2{A$J zq=!Mi?>@vZN}NEdPs7MV`v2&A{z0^UIvr(kbTGvj%>=Pz5@#d9%Jq0Tf2dylJNF(>ZQt2Fk z5qb&%k|C0bCi#Tb>7jx(~&S?m?-jAM;F3z%?|;Kj?lF$zM3e6w~- zEZnPy`v+6c9K-^_>Oh`TU--D53W6b-69Pj=MS97kVCT#zj3?0tMCL0g^KeP=^l7T}ehOX>Jv58-OkTPWmLd*g~ibN@{~-{-D{v z3z=ff1S6JYK(a*Y5fq}obw{jjIx3sucimfLybI_(dysT=-rYksd4F$n3XBeYvpu~6S`L67S5Vw*!DG>zS?2&cfjCnI zr#iUv51L*-h)y9a=KHd=v=A*$37Aup!uevMF~Ag(ZGRgtzwV|)5vRYjMUa@pyQ#f@ zRI)#qWq`dffyDloZQqfpFand<3`cb3&IW(wL$|4@Y~eomTLU-VI%6 z@9z*K-&M#&g3uL!V50s&r6wqQlOpV9YdFb>ba{+{=2%rWI6MBtHxIZ(GTG1`r3nqO ztrj+%J}0|!1faFF4U|9`6c<9KPKEamAkcw`C}cDRO6g%8Iha@D#o0@Jr`8gJOPic2 zCG_rkr^S&%+(1iVSGAmM)rEvznN-zvzF{s-24=k)p*CX~McN#ahUFk4rGM!0e`aGl zVi4{igA5EfYwztZC>q-!=RvAB0l^ZFp(|Y9AWj|zqE^F!htLf;3>MY2YMd=07j^#J z2{=l0$Sn;BsbpRefM@d;FD#^?)M60@wSjh#Up46(PuKE{z;7O&FA9!>DY!SUdz-w# zpils4YU1l6HGY$a$9E8?%}Z*|z_nVSw9~zCGNRnpKe0;^$oMb!ASxbH4nG@m;=RY?+2)< zWU-|H02Qk7)@X*vPru1Wo!l~3{F?$d^^9Uk1fjb}4H^tm{IyFkubH@A0nRqqVWDja zc^eQcfTjfPP&2T$H&c3U7)5@EuFI<>JN;+L*R5nZLos;%1$6Z%_6QNN^32mqox?`K zXB!!tK_=40`RV-S_-&PRJ3H6B+O zHJj_#J%DJLi;}@~=rde{Tta&usYZZI;IxF!l;Ay|0G5#cb^c;%>|>B+9qIg#QR2w} zV6NibfBZ0i$?Qie3Ez~n8td--*YF#|l+ZLM#L0B&71h>}i@4hhrpI?&ySbi78qPHb z&Vdih+gY2S@4;-{aI(xWnFa>04@EA);A=CW^vUg4;=z>M9QF-Wle;+|nXX}j3$28C zg~8S72;cA&7)-_U0q)d5K-1^j3J+*p=MHgj7DL!FzkWm7OmftG%@Ud-;$T@BN#6QZ zRLH}2Qq;3jgV%USA?(T)JfKQ)eQ3k5_m7f{MFXJug5uA6z2K=F+n}47nY_IFz8}g7 zAZiuA+=*@hT`VQI0vX$R1{W+;v%kqSe4u8IzA1%FaV(9hKcJeSa^a z9nBV?2&fe;+^m%vF9D%Wf_Dsjw9ib2Re5v~h|u|`oMX^j0^|Slv=xZ~yrzyqx2pl2fkXW%;+ zf<%-E5pZ(r!&UbRuD5`umkI{WJllhTZNl3d0`_?t1=hMtd4)ABU@*Pren(asR7@qD z=%J_71VTfbbK+Mg(Jo;o`NGpVzd03us8AR-jn`l2Z)X4RF^s`?&Sz2PAmOOaejclU z3#r|BvdU`*V0&~R*u1{|vShyajbyBom0-`kZcV3a%8d%?1Pyw=Oav(}OT-=VhPEKR znHxQNLJxnQtV#V2KRE6vfC?>uiPs*~7Sb_pW4h>z_MuDI(z<*72pRtaI=^y?O|B9% zvxr&%U-DNd%d4SP{a4|!R!7pD0*`0tXw%cGc3jdv+2`5Z7Wv$;A_rUvNc$p;-EI3` zyAjwbOp2V@sgGiY38v_8TPng0O;i^bp5N}Qv>xmCQ1e);XZ03Jo975zlIkIn2Rx9RKrxlS()b4Sp5T+2OC0kq20L)d1oQ$vBi@gDWEI=Mi&UxK0Eas-=F%vPsmySmb@Fd z*6HQ)ydu5psoSa0SPi%EuY>0$LIgx#z!))`Fuu8}ng?L&f&f7K_;JKUgK(wSPfAOAxM2wat#�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⪚F0{=-&Kb4v+PRMZPO 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⁢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 z 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

4iA<=FOxS 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 kYybcIpFc@>)?mBroIMQ{lta;2i`(iQvd(} literal 0 HcmV?d00001 diff --git a/scripts/release_processes/release-cromwell-version.dot b/scripts/release_processes/release-cromwell-version.dot new file mode 100644 index 00000000000..1753d1fc261 --- /dev/null +++ b/scripts/release_processes/release-cromwell-version.dot @@ -0,0 +1,10 @@ +digraph { + + # Nodes + + release_cromwell [shape=oval label="Run the release script"]; + + # Edges + + # TBD...? +} diff --git a/scripts/release_processes/release-cromwell-version.dot.png b/scripts/release_processes/release-cromwell-version.dot.png new file mode 100644 index 0000000000000000000000000000000000000000..a2d247a1cc69c5d292fb2aacdd5059c63391b924 GIT binary patch literal 5384 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 mV;~Z7Zj{9bA_j_ufqwy{9gIGbg*QL|0000 Date: Wed, 1 May 2019 16:28:12 -0400 Subject: [PATCH 25/46] Fix local-disk patterns for AWS (#4863) --- CHANGELOG.md | 11 ++++ .../scala/cromwell/backend/DiskPatterns.scala | 19 ++++++ .../resources/standardTestCases/aws_disk.test | 25 +++++++ .../draft3_custom_mount_point.test | 16 +++-- .../custom_mount_point/custom_mount_point.wdl | 66 +++++++++++++++++-- docs/backends/AWS.md | 27 ++++++++ docs/backends/Backends.md | 2 + mkdocs.yml | 1 + .../backend/impl/aws/io/AwsBatchVolume.scala | 23 +++++-- .../common/io/PipelinesApiAttachedDisk.scala | 9 +-- 10 files changed, 174 insertions(+), 25 deletions(-) create mode 100644 backend/src/main/scala/cromwell/backend/DiskPatterns.scala create mode 100644 centaur/src/main/resources/standardTestCases/aws_disk.test create mode 100644 docs/backends/AWS.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cc13c3ee44..b26033c373e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ * It is now possible to supply custom `google-labels` in [workflow options](https://cromwell.readthedocs.io/en/stable/wf_options/Google/). +### AWS backend + +It is now possible to use WDL disk attributes with the following formats on AWS. +``` +disks: "local-disk 20 SSD" +``` +``` +disks: "/some/mnt 20 SSD" +``` +Because Cromwell's AWS backend auto-sizes disks, the size specification is simply discarded. + ### Config Changes #### Heartbeat failure shutdown diff --git a/backend/src/main/scala/cromwell/backend/DiskPatterns.scala b/backend/src/main/scala/cromwell/backend/DiskPatterns.scala new file mode 100644 index 00000000000..1db9e1e1a99 --- /dev/null +++ b/backend/src/main/scala/cromwell/backend/DiskPatterns.scala @@ -0,0 +1,19 @@ +package cromwell.backend + +import scala.util.matching.Regex + +/** + * These patterns define the two disk patterns that should be supported by all backends + * (because they're used by the best practice workflows) + */ +object DiskPatterns { + val Identifier = "[a-zA-Z0-9-_]+" + val Directory = """/[^\s]+""" + val Integer = "[1-9][0-9]*" + + // e.g. local-disk 20 SSD + val WorkingDiskPattern: Regex = s"""local-disk\\s+($Integer)\\s+($Identifier)""".r + + // e.g. /some/mnt 20 SSD + val MountedDiskPattern: Regex = s"""($Directory)\\s+($Integer)\\s+($Identifier)""".r +} diff --git a/centaur/src/main/resources/standardTestCases/aws_disk.test b/centaur/src/main/resources/standardTestCases/aws_disk.test new file mode 100644 index 00000000000..9ee29bb5a81 --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/aws_disk.test @@ -0,0 +1,25 @@ +name: aws_disk +testFormat: workflowsuccess +backends: [ AWSBATCH ] + +files { + workflow: wdl_draft3/custom_mount_point/custom_mount_point.wdl + # https://github.com/broadinstitute/cromwell/issues/3998 + options: wdl_draft3/custom_mount_point/custom_mount_point.options +} + +metadata { + "calls.custom_mount_point.local_and_custom.backend": "AWSBATCH" + "calls.custom_mount_point.local_and_custom.backendStatus": "Succeeded" + + "calls.custom_mount_point.local_only.backend": "AWSBATCH" + "calls.custom_mount_point.local_only.backendStatus": "Succeeded" + + "calls.custom_mount_point.custom_only.backend": "AWSBATCH" + "calls.custom_mount_point.custom_only.backendStatus": "Succeeded" + + "outputs.custom_mount_point.o1": "local disk contents" + "outputs.custom_mount_point.o2": "custom mount contents" + "outputs.custom_mount_point.o3": "local disk contents" + "outputs.custom_mount_point.o4": "custom mount contents" +} diff --git a/centaur/src/main/resources/standardTestCases/draft3_custom_mount_point.test b/centaur/src/main/resources/standardTestCases/draft3_custom_mount_point.test index b107dfcd651..ca26105560f 100644 --- a/centaur/src/main/resources/standardTestCases/draft3_custom_mount_point.test +++ b/centaur/src/main/resources/standardTestCases/draft3_custom_mount_point.test @@ -9,9 +9,17 @@ files { } metadata { - "calls.custom_mount_point.t.backend": "Papi" - "calls.custom_mount_point.t.backendStatus": "Success" + "calls.custom_mount_point.local_and_custom.backend": "Papi" + "calls.custom_mount_point.local_and_custom.backendStatus": "Success" - "outputs.custom_mount_point.o1": "bazqux" - "outputs.custom_mount_point.o2": "foobar" + "calls.custom_mount_point.local_only.backend": "Papi" + "calls.custom_mount_point.local_only.backendStatus": "Success" + + "calls.custom_mount_point.custom_only.backend": "Papi" + "calls.custom_mount_point.custom_only.backendStatus": "Success" + + "outputs.custom_mount_point.o1": "local disk contents" + "outputs.custom_mount_point.o2": "custom mount contents" + "outputs.custom_mount_point.o3": "local disk contents" + "outputs.custom_mount_point.o4": "custom mount contents" } diff --git a/centaur/src/main/resources/standardTestCases/wdl_draft3/custom_mount_point/custom_mount_point.wdl b/centaur/src/main/resources/standardTestCases/wdl_draft3/custom_mount_point/custom_mount_point.wdl index e618548125d..f3b614ce1ca 100644 --- a/centaur/src/main/resources/standardTestCases/wdl_draft3/custom_mount_point/custom_mount_point.wdl +++ b/centaur/src/main/resources/standardTestCases/wdl_draft3/custom_mount_point/custom_mount_point.wdl @@ -1,6 +1,6 @@ version 1.0 -task t { +task local_and_custom { input { String v } @@ -12,10 +12,10 @@ task t { } command <<< - echo "bazqux" > some_file + echo "local disk contents" > some_file cd /some/mnt - echo "foobar" > some_file + echo "custom mount contents" > some_file >>> runtime { @@ -24,15 +24,69 @@ task t { } } + +task local_only { + + input { + String v + } + + command { + echo "local disk contents" > some_file + } + + runtime { + docker: "ubuntu:" + v + disks: "local-disk 20 SSD" + } + + output { + String out = read_string("some_file") + } +} + +task custom_only { + + input { + String v + } + + command { + cd /some/mnt + echo "custom mount contents" > some_file + } + + runtime { + docker: "ubuntu:" + v + disks: "/some/mnt 20 SSD" + } + + output { + String out = read_string("/some/mnt/some_file") + } +} + workflow custom_mount_point { - call t { + call local_and_custom { + input: + v = "latest" + } + + call local_only { + input: + v = "latest" + } + + call custom_only { input: v = "latest" } output { - String o2 = t.out2 - String o1 = t.out1 + String o1 = local_and_custom.out1 + String o2 = local_and_custom.out2 + String o3 = local_only.out + String o4 = custom_only.out } } diff --git a/docs/backends/AWS.md b/docs/backends/AWS.md new file mode 100644 index 00000000000..5bc68ffb28f --- /dev/null +++ b/docs/backends/AWS.md @@ -0,0 +1,27 @@ +# AWS Batch backend (beta) + +Check out the [getting started guide](../tutorials/AwsBatch101.md) for the bulk of our documentation. + +AWS support is fairly new to Cromwell and this reference section will expand as features are added and documented. + +### Disks + +Cromwell performs automatic disk sizing on your behalf when running with the AWS backend, so attributes like +``` +disks: "local-disk" +``` +or +``` +disks: "/some/mnt" +``` +are adequate to specify a disk that will suffice to complete your task. + +To facilitate the running of workflows originally authored for Pipelines API on Google Cloud Platform, Cromwell's AWS backend can also interpret attributes like +``` +disks: "local-disk 20 SSD" +``` +and +``` +disks: "/some/mnt 20 SSD" +``` +The size information and HDD/SSD have no effect on this backend and Cromwell simply drops them. diff --git a/docs/backends/Backends.md b/docs/backends/Backends.md index 8a74cc9dc3f..58b7fee3a48 100644 --- a/docs/backends/Backends.md +++ b/docs/backends/Backends.md @@ -17,6 +17,8 @@ Cromwell distribution: * Supports execution of Spark jobs. * **[Alibaba Cloud](BCS)** * Launch jobs on Alibaba Cloud BatchCompute service. +* **[AWS Batch (beta)](AWS.md)** + * Use Job Queues on AWS Batch HPC backends are put under the same umbrella because they all use the same generic configuration that can be specialized to fit the need of a particular technology. diff --git a/mkdocs.yml b/mkdocs.yml index 44746ac5534..885ac85d5b8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -52,6 +52,7 @@ pages: - Local: backends/Local.md - Google Cloud: backends/Google.md - Alibaba Cloud: backends/BCS.md + - AWS Batch (beta): backends/AWS.md - GA4GH TES: backends/TES.md - Spark: backends/Spark.md - HPC: backends/HPC.md diff --git a/supportedBackends/aws/src/main/scala/cromwell/backend/impl/aws/io/AwsBatchVolume.scala b/supportedBackends/aws/src/main/scala/cromwell/backend/impl/aws/io/AwsBatchVolume.scala index 5724c936bc1..d66d3bc3dc5 100644 --- a/supportedBackends/aws/src/main/scala/cromwell/backend/impl/aws/io/AwsBatchVolume.scala +++ b/supportedBackends/aws/src/main/scala/cromwell/backend/impl/aws/io/AwsBatchVolume.scala @@ -32,10 +32,11 @@ package cromwell.backend.impl.aws.io import cats.data.Validated._ import cats.syntax.validated._ -import software.amazon.awssdk.services.batch.model.{MountPoint, Volume, Host} +import software.amazon.awssdk.services.batch.model.{Host, MountPoint, Volume} import cromwell.core.path.{DefaultPathBuilder, Path} import common.exception.MessageAggregation import common.validation.ErrorOr._ +import cromwell.backend.DiskPatterns import wom.values._ import scala.util.Try @@ -50,16 +51,24 @@ import scala.util.matching.Regex */ object AwsBatchVolume { - val Directory = "^\\/.+" - val MountedDiskPattern: Regex = s"""($Directory)""".r - val LocalDiskPattern: Regex = s"""local-disk""".r + // In AWS, disks are auto-sized so these patterns match simply "local-disk" or "/some/mnt" + val MountedDiskPattern: Regex = raw"""^\s*(${DiskPatterns.Directory})\s*$$""".r + val LocalDiskPattern: Regex = raw"""^\s*local-disk\s*$$""".r def parse(s: String): Try[AwsBatchVolume] = { val validation: ErrorOr[AwsBatchVolume] = s match { - case LocalDiskPattern() => Valid(AwsBatchWorkingDisk()) - case MountedDiskPattern(mountPoint) => Valid(AwsBatchEmptyMountedDisk(DefaultPathBuilder.get(mountPoint))) - case _ => s"Disk strings should be of the format 'local-disk' or '/mount/point' but got: '$s'".invalidNel + case LocalDiskPattern() => + Valid(AwsBatchWorkingDisk()) + case MountedDiskPattern(mountPoint) => + Valid(AwsBatchEmptyMountedDisk(DefaultPathBuilder.get(mountPoint))) + // In addition to the AWS-specific patterns above, we can also fall back to PAPI-style patterns and ignore the size + case DiskPatterns.WorkingDiskPattern(_, _) => + Valid(AwsBatchWorkingDisk()) + case DiskPatterns.MountedDiskPattern(mountPoint, _, _) => + Valid(AwsBatchEmptyMountedDisk(DefaultPathBuilder.get(mountPoint))) + case _ => + s"Disk strings should be of the format 'local-disk' or '/mount/point' but got: '$s'".invalidNel } Try(validation match { diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/io/PipelinesApiAttachedDisk.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/io/PipelinesApiAttachedDisk.scala index 105f08609f8..3ad1a967399 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/io/PipelinesApiAttachedDisk.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/io/PipelinesApiAttachedDisk.scala @@ -5,22 +5,15 @@ import cats.syntax.apply._ import cats.syntax.validated._ import common.exception.MessageAggregation import common.validation.ErrorOr._ +import cromwell.backend.DiskPatterns._ import cromwell.core.path.{DefaultPathBuilder, Path} import wdl4s.parser.MemoryUnit import wom.format.MemorySize import wom.values._ import scala.util.Try -import scala.util.matching.Regex - object PipelinesApiAttachedDisk { - val Identifier = "[a-zA-Z0-9-_]+" - val Directory = """/[^\s]+""" - val Integer = "[1-9][0-9]*" - val WorkingDiskPattern: Regex = s"""${PipelinesApiWorkingDisk.Name}\\s+($Integer)\\s+($Identifier)""".r - val MountedDiskPattern: Regex = s"""($Directory)\\s+($Integer)\\s+($Identifier)""".r - def parse(s: String): Try[PipelinesApiAttachedDisk] = { def sizeGbValidation(sizeGbString: String): ErrorOr[Int] = validateLong(sizeGbString).map(_.toInt) From e04b1f86999d5f7bf246063b99ba7c164deb3073 Mon Sep 17 00:00:00 2001 From: Ruben Vorderman Date: Wed, 1 May 2019 23:00:29 +0200 Subject: [PATCH 26/46] add relative output paths change to changelog. (#4878) --- CHANGELOG.md | 6 ++++++ docs/wf_options/Overview.md | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b26033c373e..281ed72ba17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -129,7 +129,13 @@ services { } } ``` +### Workflow options changes +A new workflow option is added. If the `final_workflow_outputs_dir` is set +`use_relative_output_paths` can be used. When set to `true` this will copy +all the outputs relative to their execution directory. +my_final_workflow_outputs_dir/~~MyWorkflow/af76876d8-6e8768fa/call-MyTask/execution/~~output_of_interest. +More information can be found in [the workflow options documentation](https://cromwell.readthedocs.io/en/stable/wf_options/Overview/#output-copying). ### Bug fixes diff --git a/docs/wf_options/Overview.md b/docs/wf_options/Overview.md index fc396c5e331..7313b6a8d98 100644 --- a/docs/wf_options/Overview.md +++ b/docs/wf_options/Overview.md @@ -94,6 +94,22 @@ Example `options.json`: } ``` +With `"use_relative_output_paths": false` (the default) the outputs will look like this + +``` +final_workflow_outputs_dir/my_workflow/ade68a6d876e8d-8a98d7e9-ad98e9ae8d/call-my_one_task/execution/my_output_picture.jpg +final_workflow_outputs_dir/my_workflow/ade68a6d876e8d-8a98d7e9-ad98e9ae8d/call-my_other_task/execution/created_subdir/submarine.txt +``` + +The above result will look like this when `"use_relative_output_paths": true`: +``` +final_workflow_outputs_dir/my_output_picture.jpg +final_workflow_outputs_dir/created_subdir/submarine.txt +``` + +This will create file collisions in `final_workflow_outputs_dir` when a workflow is run twice. When cromwell +detects file collisions it will throw an error and report the workflow as failed. + ## Call Caching Options These options can override Cromwell's configured call caching behavior for a single workflow. See the [Call Caching](../CallCaching) section for more details and how to set defaults. The call caching section will also explain how these options interact when, for example, one is set `true` and the other is `false`. From 01f5e19aeeb520d4d626d56a0311a2f144c8b043 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Thu, 2 May 2019 16:41:04 -0400 Subject: [PATCH 27/46] Make invalidate_bad_caches_jes_no_copy more deterministic (#4893) --- .../invalidate_bad_caches_no_copy.wdl | 21 ++++++++++++++++--- .../invalidate_bad_caches_jes_no_copy.test | 16 +++++++++----- .../job/EngineJobExecutionActor.scala | 3 ++- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/centaur/src/main/resources/standardTestCases/invalidate_bad_caches/invalidate_bad_caches_no_copy.wdl b/centaur/src/main/resources/standardTestCases/invalidate_bad_caches/invalidate_bad_caches_no_copy.wdl index e9196b362e6..767439e0abb 100644 --- a/centaur/src/main/resources/standardTestCases/invalidate_bad_caches/invalidate_bad_caches_no_copy.wdl +++ b/centaur/src/main/resources/standardTestCases/invalidate_bad_caches/invalidate_bad_caches_no_copy.wdl @@ -1,6 +1,7 @@ task make_file { Boolean ready = true command { + # This comment adds noise to this command to stop it from call caching to other test cases echo woohoo > out.txt } runtime { @@ -8,6 +9,7 @@ task make_file { backend: "Papi-Caching-No-Copy" } output { + Boolean done = true File out = "out.txt" } } @@ -15,6 +17,7 @@ task make_file { task read_file { File input_file command { + # This comment adds noise to this command to stop it from call caching to other test cases cat ${input_file} } runtime { @@ -22,13 +25,14 @@ task read_file { backend: "Papi-Caching-No-Copy" } output { - File out = stdout() + String out = read_string(stdout()) } } task delete_file_in_gcs { String file_path command { + # This comment adds noise to this command to stop it from call caching to other test cases gsutil rm ${file_path} } runtime { @@ -40,11 +44,22 @@ task delete_file_in_gcs { } } -workflow invalidate_bad_caches { +workflow invalidate_bad_caches_no_copy { + # Make a file the first time: call make_file - call delete_file_in_gcs { input: file_path = make_file.out } + # Because it will call cache, we'll get the same file here. + call make_file as make_file_again { input: ready = make_file.done } + + # Delete both because we referenced (rather than copied) the file + call delete_file_in_gcs { input: file_path = make_file_again.out } + # Re-make the file: call make_file as invalidate_cache_and_remake_file { input: ready = delete_file_in_gcs.done } + call read_file { input: input_file = invalidate_cache_and_remake_file.out } + + output { + String woohoo = read_file.out + } } diff --git a/centaur/src/main/resources/standardTestCases/invalidate_bad_caches_jes_no_copy.test b/centaur/src/main/resources/standardTestCases/invalidate_bad_caches_jes_no_copy.test index 002e44353c8..2b22b9096af 100644 --- a/centaur/src/main/resources/standardTestCases/invalidate_bad_caches_jes_no_copy.test +++ b/centaur/src/main/resources/standardTestCases/invalidate_bad_caches_jes_no_copy.test @@ -7,9 +7,15 @@ files { } metadata { - "calls.invalidate_bad_caches.make_file.callCaching.result": "Cache Miss" - "calls.invalidate_bad_caches.make_file.callCaching.allowResultReuse": false - "calls.invalidate_bad_caches.invalidate_cache_and_remake_file.callCaching.allowResultReuse": true - "calls.invalidate_bad_caches.delete_file_in_gcs.callCaching.result": "Cache Miss" - "calls.invalidate_bad_caches.invalidate_cache_and_remake_file.callCaching.result": "Cache Miss" + "calls.invalidate_bad_caches_no_copy.make_file.callCaching.result": "Cache Miss" + "calls.invalidate_bad_caches_no_copy.make_file.callCaching.allowResultReuse": false + + "calls.invalidate_bad_caches_no_copy.make_file_again.callCaching.result": "Cache Hit: <>:invalidate_bad_caches_no_copy.make_file:-1" + + "calls.invalidate_bad_caches_no_copy.delete_file_in_gcs.callCaching.result": "Cache Miss" + + "calls.invalidate_bad_caches_no_copy.invalidate_cache_and_remake_file.callCaching.result": "Cache Miss" + "calls.invalidate_bad_caches_no_copy.invalidate_cache_and_remake_file.callCaching.allowResultReuse": true + + "outputs.invalidate_bad_caches_no_copy.woohoo": "woohoo" } diff --git a/engine/src/main/scala/cromwell/engine/workflow/lifecycle/execution/job/EngineJobExecutionActor.scala b/engine/src/main/scala/cromwell/engine/workflow/lifecycle/execution/job/EngineJobExecutionActor.scala index 1bcfa333b69..31d6fb35372 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/lifecycle/execution/job/EngineJobExecutionActor.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/lifecycle/execution/job/EngineJobExecutionActor.scala @@ -670,7 +670,8 @@ class EngineJobExecutionActor(replyTo: ActorRef, case activity: CallCachingActivity => activity.options.invalidateBadCacheResults } if (invalidationRequired) { - log.error(reason, "Failed copying cache results for job {}, invalidating cache entry.", jobDescriptorKey) + val problemSummary = s"${reason.getClass.getSimpleName}: ${reason.getMessage}" + log.warning("Failed copying cache results for job {} ({}), invalidating cache entry.", jobDescriptorKey, problemSummary) invalidateCacheHit(ejeaCacheHit.hit.cacheResultId) goto(InvalidatingCacheEntry) } else { From 787943c0eda793fcc407a3e748b56805f4a2795b Mon Sep 17 00:00:00 2001 From: Evan Benn Date: Fri, 3 May 2019 08:41:36 +1000 Subject: [PATCH 28/46] Clarify exit-code-timeout-seconds docs (#4905) --- cromwell.example.backends/HtCondor.conf | 16 ++++++++-------- cromwell.example.backends/SGE.conf | 18 +++++++++--------- cromwell.example.backends/slurm.conf | 12 ++++++------ docs/backends/HPC.md | 10 ++++++---- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/cromwell.example.backends/HtCondor.conf b/cromwell.example.backends/HtCondor.conf index aad020e385c..c4e65f29bb0 100644 --- a/cromwell.example.backends/HtCondor.conf +++ b/cromwell.example.backends/HtCondor.conf @@ -23,15 +23,15 @@ backend { String? nativeSpecs String? docker """ - + # If an 'exit-code-timeout-seconds' value is specified: - # - When a job has not been alive for longer than this timeout - # - And has still not produced an RC file + # - check-alive will be run at this interval for every job + # - if a job is found to be not alive, and no RC file appears after this interval # - Then it will be marked as Failed. - # Warning: If set, Cromwell has to run 'check-alive' for every job at regular intervals (unrelated to this timeout) - + # Warning: If set, Cromwell will run 'check-alive' for every job at this interval + # exit-code-timeout-seconds = 120 - + submit = """ chmod 755 ${script} cat > ${cwd}/execution/submitFile < ${cwd}/execution/dockerScript < Date: Fri, 3 May 2019 08:30:50 -0400 Subject: [PATCH 29/46] Take 2: Modify DRS Parsing (#4927) Cromwell now accepts host-free dos URIs --- .../drs/DrsCloudNioFileSystemProvider.scala | 13 ++++---- .../filesystems/drs/DrsPathBuilder.scala | 4 --- .../filesystems/drs/DrsPathBuilderSpec.scala | 32 +++++++++++++++++-- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/cloud-nio/cloud-nio-impl-drs/src/main/scala/cloud/nio/impl/drs/DrsCloudNioFileSystemProvider.scala b/cloud-nio/cloud-nio-impl-drs/src/main/scala/cloud/nio/impl/drs/DrsCloudNioFileSystemProvider.scala index 359a207d479..1daf2da9893 100644 --- a/cloud-nio/cloud-nio-impl-drs/src/main/scala/cloud/nio/impl/drs/DrsCloudNioFileSystemProvider.scala +++ b/cloud-nio/cloud-nio-impl-drs/src/main/scala/cloud/nio/impl/drs/DrsCloudNioFileSystemProvider.scala @@ -36,18 +36,19 @@ class DrsCloudNioFileSystemProvider(rootConfig: Config, override def getScheme: String = "dos" override def getHost(uriAsString: String): String = { - require(uriAsString.startsWith(getScheme + "://"), s"Scheme does not match $getScheme") + require(uriAsString.startsWith(s"$getScheme://"), s"Scheme does not match $getScheme") /* * In some cases for a URI, the host name is null. For example, for DRS urls like 'dos://dg.123/123-123-123', * even though 'dg.123' is a valid host, somehow since it does not conform to URI's standards, uri.getHost returns null. In such - * cases, authority is used instead of host. + * cases, authority is used instead of host. If there is no authority, use an empty string. */ val uri = new URI(uriAsString) - val host = uri.getHost - val hostOrAuthority = if (host == null) uri.getAuthority else host - require(!hostOrAuthority.isEmpty, s"Bucket/Host is empty") + val hostOrAuthorityOrEmpty = + Option(uri.getHost).getOrElse( + Option(uri.getAuthority).getOrElse("") + ) - hostOrAuthority + hostOrAuthorityOrEmpty } } diff --git a/filesystems/drs/src/main/scala/cromwell/filesystems/drs/DrsPathBuilder.scala b/filesystems/drs/src/main/scala/cromwell/filesystems/drs/DrsPathBuilder.scala index 0993a59aa53..d9cd2566be6 100644 --- a/filesystems/drs/src/main/scala/cromwell/filesystems/drs/DrsPathBuilder.scala +++ b/filesystems/drs/src/main/scala/cromwell/filesystems/drs/DrsPathBuilder.scala @@ -20,10 +20,6 @@ case class DrsPathBuilder(fileSystemProvider: DrsCloudNioFileSystemProvider) ext Try(URI.create(UrlEscapers.urlFragmentEscaper().escape(pathAsString))) flatMap { uri => if (!Option(uri.getScheme).exists(_.equalsIgnoreCase(fileSystemProvider.getScheme))) { Failure(new IllegalArgumentException(s"$pathAsString does not have a $drsScheme scheme.")) - } else if (uri.getHost == null && uri.getAuthority == null) { - Failure(new IllegalArgumentException(s"$pathAsString does not have a valid host.")) - } else if (uri.getPath == null || uri.getPath.isEmpty || uri.getPath.equalsIgnoreCase("/")) { - Failure(new IllegalArgumentException(s"$pathAsString does not have a valid path. DRS doesn't support a host only path.")) } else { Try(DrsPath(fileSystemProvider.getPath(uri))) } diff --git a/filesystems/drs/src/test/scala/cromwell/filesystems/drs/DrsPathBuilderSpec.scala b/filesystems/drs/src/test/scala/cromwell/filesystems/drs/DrsPathBuilderSpec.scala index 6deb2d697a2..713f7e19ac6 100644 --- a/filesystems/drs/src/test/scala/cromwell/filesystems/drs/DrsPathBuilderSpec.scala +++ b/filesystems/drs/src/test/scala/cromwell/filesystems/drs/DrsPathBuilderSpec.scala @@ -265,7 +265,35 @@ class DrsPathBuilderSpec extends TestKitSuite with FlatSpecLike with Matchers wi name = "world", getFileName = s"dos://nonasciibucket£€/world", getNameCount = 2, - isAbsolute = true) + isAbsolute = true), + + GoodPath( + description = "an non-absolute path without a host", + path = s"dos://blah/", + normalize = false, + pathAsString = s"dos://blah/", + pathWithoutScheme = s"blah/", + parent = null, + getParent = null, + root = s"dos://blah/", + name = "", + getFileName = null, + getNameCount = 0, + isAbsolute = true), + + GoodPath( + description = "an absolute path without a host", + path = s"dos://blah", + normalize = false, + pathAsString = s"dos://blah/", + pathWithoutScheme = s"blah/", + parent = null, + getParent = null, + root = s"dos://blah/", + name = "", + getFileName = null, + getNameCount = 1, + isAbsolute = false) ) private def badPaths = Seq( @@ -276,8 +304,6 @@ class DrsPathBuilderSpec extends TestKitSuite with FlatSpecLike with Matchers wi BadPath("a file uri path", "file:///hello/world", "file:///hello/world does not have a dos scheme."), BadPath("a relative file path", "hello/world", "hello/world does not have a dos scheme."), BadPath("an absolute file path", "/hello/world", "/hello/world does not have a dos scheme."), - BadPath("a bucket only path ending in a /", s"dos://$bucket/", s"dos://$bucket/ does not have a valid path. DRS doesn't support a host only path."), - BadPath("a bucket only path", s"dos://$bucket", s"dos://$bucket does not have a valid path. DRS doesn't support a host only path.") ) private def drsReadInterpreter(marthaResponse: MarthaResponse): IO[ReadableByteChannel] = From b99615fe2f00c9ffc89c69a21fc79fb4eb8ecc7a Mon Sep 17 00:00:00 2001 From: Adam Nichols Date: Fri, 3 May 2019 12:10:45 -0400 Subject: [PATCH 30/46] Contributing guide (#4922) --- CONTRIBUTING.md | 12 ++++++++++++ README.md | 2 ++ 2 files changed, 14 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..64071110f29 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,12 @@ +###Contributing to Cromwell + +Thank you for contributing to Cromwell. The project is sponsored by Broad Institute and has participants all over the world, who have collectively added tremendous value over the years. This page describes how we handle external contributions to maximize the impact of your work and reduce delays in getting your PRs accepted. + +####Run your idea by us first +If you're thinking of writing a non-trivial amount of code to solve a problem, we encourage you to reach out via an [issue](https://github.com/broadinstitute/cromwell/issues/new) to get feedback. It is likely we will have suggestions about how to proceed. It's also possible, though hopefully rare, that there is a hidden impediment that would prevent your solution from working. If we spot it at the idea stage, we can give you feedback much earlier than if we have to reject your pull request! + +####Maintenance considerations +The Cromwell team at Broad maintains and enhances the application to serve the needs of both Broad Institute and external users. Sometimes, we may identify a feature idea or pull request that works and is a good idea, but we may be unable to commit to maintaining it indefinitely. This may be because it does not align with the strategic direction of the project, or simply due to time constraints on the maintainers. Once again, it always helps to solicit early feedback. + +####Reviewing pull requests +Because pull requests require a substantial amount of time to review carefully, we prioritize and schedule them into our sprints alongside all of our other work. At present, the team operates on three-week sprints so if you happen to submit a PR early on in the sprint it may be a while before a team member has a chance to look at it. We realize this may be frustrating and strive to provide timely updates about PR status. diff --git a/README.md b/README.md index 79890e2ff6d..ce822ae6b41 100644 --- a/README.md +++ b/README.md @@ -9,4 +9,6 @@ The Cromwell documentation has a new home, [click here to check it out](http://c First time to Cromwell? Get started with [Tutorials](http://cromwell.readthedocs.io/en/develop/tutorials/FiveMinuteIntro/)! +Thinking about contributing to Cromwell? Get started by reading our [Contributor Guide](CONTRIBUTING.md). + ![Jamie, the Cromwell pig](docs/jamie_the_cromwell_pig.png) From aa81565a57e342c614ccddc4bb4b5974e80982e1 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Fri, 3 May 2019 14:01:53 -0400 Subject: [PATCH 31/46] Disable parts of dockerScripts test --- src/ci/bin/testDockerScripts.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ci/bin/testDockerScripts.sh b/src/ci/bin/testDockerScripts.sh index 31544c90ffa..6fca75869e9 100755 --- a/src/ci/bin/testDockerScripts.sh +++ b/src/ci/bin/testDockerScripts.sh @@ -18,6 +18,9 @@ echo "What tests would you like, my dear McMuffins?" echo "1. Testing for install of sbt" docker run --rm "${docker_tag}" which sbt +echo "Goodbye. Skipping test of sbt assembly and cloudwell https://github.com/broadinstitute/cromwell/issues/4933" +exit 0 + echo "2. Testing sbt assembly" docker run --rm -v "${PWD}:${PWD}" -w "${PWD}" "${docker_tag}" sbt assembly From 7102f424b452d85b814bcc466042e95a09b193cb Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Mon, 6 May 2019 11:26:55 -0400 Subject: [PATCH 32/46] Handle null fields from Papi V2 operations responses --- .../v2alpha1/api/Deserialization.scala | 50 ++++++-- .../v2alpha1/api/request/ErrorReporter.scala | 14 ++- .../api/request/GetRequestHandler.scala | 106 +++++++++++----- .../v2alpha1/api/DeserializationSpec.scala | 36 +++++- .../api/request/GetRequestHandlerSpec.scala | 115 ++++++++++++++++++ 5 files changed, 274 insertions(+), 47 deletions(-) create mode 100644 supportedBackends/google/pipelines/v2alpha1/src/test/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/GetRequestHandlerSpec.scala diff --git a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/Deserialization.scala b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/Deserialization.scala index c4862d6b360..545af3dd195 100644 --- a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/Deserialization.scala +++ b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/Deserialization.scala @@ -5,6 +5,7 @@ import java.util.{ArrayList => JArrayList, Map => JMap} import cats.instances.list._ import cats.syntax.traverse._ +import cats.syntax.validated._ import com.google.api.client.json.GenericJson import com.google.api.services.genomics.v2alpha1.model._ import common.validation.ErrorOr._ @@ -15,6 +16,7 @@ import mouse.all._ import scala.collection.JavaConverters._ import scala.reflect.ClassTag import scala.util.{Failure, Success, Try} + /** * This bundles up some code to work around the fact that Operation is not deserialized * completely from the JSON HTTP response. @@ -26,7 +28,8 @@ import scala.util.{Failure, Success, Try} */ private [api] object Deserialization { def findEvent[T <: GenericJson](events: List[Event], - filter: T => Boolean = Function.const(true) _)(implicit tag: ClassTag[T]): Option[RequestContextReader[Option[T]]] = + filter: T => Boolean = Function.const(true)(_: T)) + (implicit tag: ClassTag[T]): Option[RequestContextReader[Option[T]]] = events.toStream .map(_.details(tag)) .collectFirst({ @@ -39,7 +42,9 @@ private [api] object Deserialization { * Returns None if the details are not of type T */ def details[T <: GenericJson](implicit tag: ClassTag[T]): Option[Try[T]] = { - val detailsMap = event.getDetails + val detailsMap: JMap[String, Object] = Option(event) + .flatMap(event => Option(event.getDetails)) + .getOrElse(Map.empty.asJava) if (hasDetailsClass(tag.runtimeClass.getSimpleName)) { Option(deserializeTo(detailsMap)(tag)) } else None @@ -47,7 +52,13 @@ private [api] object Deserialization { def hasDetailsClass(className: String): Boolean = { // The @type field contains the type of the attribute - event.getDetails.asScala.get("@type").exists(_.asInstanceOf[String].endsWith(className)) + val classTypeOption = for { + event <- Option(event) + detailsJava <- Option(event.getDetails) + detailsScala <- Option(detailsJava.asScala) + classType <- detailsScala.get("@type") + } yield classType + classTypeOption.exists(_.asInstanceOf[String].endsWith(className)) } } @@ -55,20 +66,39 @@ private [api] object Deserialization { /** * Deserializes the events to com.google.api.services.genomics.v2alpha1.model.Event */ - def events: ErrorOr[List[Event]] = operation - .getMetadata.asScala("events").asInstanceOf[JArrayList[JMap[String, Object]]] - .asScala.toList - .traverse[ErrorOr, Event](deserializeTo[Event](_).toErrorOr) + def events: ErrorOr[List[Event]] = { + val eventsErrorOrOption = for { + eventsMap <- metadata.get("events") + eventsErrorOr <- Option(eventsMap + .asInstanceOf[JArrayList[JMap[String, Object]]] + .asScala + .toList + .traverse[ErrorOr, Event](deserializeTo[Event](_).toErrorOr) + ) + } yield eventsErrorOr + eventsErrorOrOption.getOrElse(Nil.validNel) + } /** * Deserializes the pipeline to com.google.api.services.genomics.v2alpha1.model.Pipeline */ - def pipeline: Try[Pipeline] = operation - .getMetadata.asScala("pipeline").asInstanceOf[JMap[String, Object]] |> deserializeTo[Pipeline] + def pipeline: Option[Try[Pipeline]] = { + metadata + .get("pipeline") + .map(_.asInstanceOf[JMap[String, Object]] |> deserializeTo[Pipeline]) + } // If there's a WorkerAssignedEvent it means a VM was created - which we consider as the job started // Note that the VM might still be booting def hasStarted = events.toOption.exists(_.exists(_.hasDetailsClass(classOf[WorkerAssignedEvent].getSimpleName))) + + def metadata: Map[String, AnyRef] = { + val metadataOption = for { + operationValue <- Option(operation) + metadataJava <- Option(operationValue.getMetadata) + } yield metadataJava.asScala.toMap + metadataOption.getOrElse(Map.empty) + } } /** @@ -125,7 +155,7 @@ private [api] object Deserialization { } // Go over the map entries and use the "set" method of GenericJson to set the attributes. - attributes.asScala.foreach((handleMap _).tupled) + Option(attributes).map(_.asScala).getOrElse(Map.empty).foreach((handleMap _).tupled) newT } } diff --git a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/ErrorReporter.scala b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/ErrorReporter.scala index 8a82bc3d1c0..4bd25447360 100644 --- a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/ErrorReporter.scala +++ b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/ErrorReporter.scala @@ -58,7 +58,12 @@ class ErrorReporter(machineType: Option[String], import ErrorReporter._ def toUnsuccessfulRunStatus(error: Status, events: List[Event]) = { - val status = GStatus.fromCodeValue(error.getCode) + // If for some reason the status is null, set it as UNAVAILABLE + val statusOption = for { + errorValue <- Option(error) + code <- Option(errorValue.getCode) + } yield GStatus.fromCodeValue(code.toInt) + val status = statusOption.getOrElse(GStatus.UNAVAILABLE) val builder = status match { case GStatus.UNAVAILABLE if wasPreemptible => Preempted.apply _ case GStatus.CANCELLED => Cancelled.apply _ @@ -89,7 +94,7 @@ class ErrorReporter(machineType: Option[String], private def unexpectedStatusErrorString(event: Event, stderr: Option[String], labelTag: Option[String], inputNameTag: Option[String]) = { labelTag.map("[" + _ + "] ").getOrElse("") + inputNameTag.map("Input name: " + _ + " - ").getOrElse("") + - event.getDescription + + Option(event).flatMap(eventValue => Option(eventValue.getDescription)).getOrElse("") + stderr.map(": " + _).getOrElse("") } @@ -107,7 +112,10 @@ class ErrorReporter(machineType: Option[String], .map(_.getStderr) } - private def actionLabelValue(action: Action, k: String) = action.getLabels.asScala.get(k) + private def actionLabelValue(action: Action, k: String): Option[String] = { + Option(action).flatMap(actionValue => Option(actionValue.getLabels)).map(_.asScala).flatMap(_.get(k)) + } + private def actionLabelTag(action: Action) = actionLabelValue(action, Key.Tag) private def actionLabelInputName(action: Action) = actionLabelValue(action, Key.InputName) } diff --git a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/GetRequestHandler.scala b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/GetRequestHandler.scala index 337463a1aec..6fa092cdff7 100644 --- a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/GetRequestHandler.scala +++ b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/GetRequestHandler.scala @@ -9,13 +9,14 @@ import com.google.api.services.genomics.v2alpha1.model._ import common.validation.Validation._ import cromwell.backend.google.pipelines.common.api.PipelinesApiRequestManager._ import cromwell.backend.google.pipelines.common.api.RunStatus -import cromwell.backend.google.pipelines.common.api.RunStatus.{Initializing, Running, Success} +import cromwell.backend.google.pipelines.common.api.RunStatus.{Initializing, Running, Success, UnsuccessfulRunStatus} import cromwell.backend.google.pipelines.v2alpha1.PipelinesConversions._ import cromwell.backend.google.pipelines.v2alpha1.api.ActionBuilder.Labels.Key import cromwell.backend.google.pipelines.v2alpha1.api.Deserialization._ import cromwell.backend.google.pipelines.v2alpha1.api.request.ErrorReporter._ import cromwell.cloudsupport.gcp.auth.GoogleAuthMode import cromwell.core.ExecutionEvent +import io.grpc.Status import org.apache.commons.lang3.exception.ExceptionUtils import scala.collection.JavaConverters._ @@ -45,41 +46,80 @@ trait GetRequestHandler { this: RequestHandler => } private [request] def interpretOperationStatus(operation: Operation, pollingRequest: PAPIStatusPollRequest): RunStatus = { - require(operation != null, "Operation must not be null.") + if (Option(operation).isEmpty) { + // It is possible to receive a null via an HTTP 200 with no response. If that happens, handle it and don't crash. + // https://github.com/googleapis/google-http-java-client/blob/v1.28.0/google-http-client/src/main/java/com/google/api/client/http/HttpResponse.java#L456-L458 + val errorMessage = "Operation returned as empty" + UnsuccessfulRunStatus(Status.UNKNOWN, Option(errorMessage), Nil, None, None, None, wasPreemptible = false) + } else { + try { + if (operation.getDone) { + val metadata = operation.metadata + // Deserialize the response + val events: List[Event] = operation.events.fallBackTo(List.empty)(pollingRequest.workflowId -> operation) + val pipeline: Option[Pipeline] = operation.pipeline.flatMap( + _.toErrorOr.fallBack(pollingRequest.workflowId -> operation) + ) + val actions: List[Action] = pipeline + .flatMap(pipelineValue => Option(pipelineValue.getActions)) + .map(_.asScala) + .toList + .flatten + val workerEvent: Option[WorkerAssignedEvent] = + findEvent[WorkerAssignedEvent](events).flatMap(_ (pollingRequest.workflowId -> operation)) + val executionEvents = getEventList(metadata, events, actions) + val virtualMachineOption = for { + pipelineValue <- pipeline + resources <- Option(pipelineValue.getResources) + virtualMachine <- Option(resources.getVirtualMachine) + } yield virtualMachine + // Correlate `executionEvents` to `actions` to potentially assign a grouping into the appropriate events. + val machineType = virtualMachineOption.flatMap(virtualMachine => Option(virtualMachine.getMachineType)) + /* + preemptible is only used if the job fails, as a heuristic to guess if the VM was preempted. + If we can't get the value of preempted we still need to return something, returning false will not make the + failure count as a preemption which seems better than saying that it was preemptible when we really don't know + */ + val preemptibleOption = for { + pipelineValue <- pipeline + resources <- Option(pipelineValue.getResources) + virtualMachine <- Option(resources.getVirtualMachine) + preemptible <- Option(virtualMachine.getPreemptible) + } yield preemptible + val preemptible = preemptibleOption.exists(_.booleanValue) + val instanceName = workerEvent.flatMap(workerAssignedEvent => Option(workerAssignedEvent.getInstance())) + val zone = workerEvent.flatMap(workerAssignedEvent => Option(workerAssignedEvent.getZone)) - try { - if (operation.getDone) { - val metadata = operation.getMetadata.asScala.toMap - // Deserialize the response - val events: List[Event] = operation.events.fallBackTo(List.empty)(pollingRequest.workflowId -> operation) - val pipeline: Option[Pipeline] = operation.pipeline.toErrorOr.fallBack(pollingRequest.workflowId -> operation) - val actions: List[Action] = pipeline.map( _.getActions.asScala ).toList.flatten - val workerEvent: Option[WorkerAssignedEvent] = findEvent[WorkerAssignedEvent](events).flatMap(_(pollingRequest.workflowId -> operation)) - val executionEvents = getEventList(metadata, events, actions).toList - // Correlate `executionEvents` to `actions` to potentially assign a grouping into the appropriate events. - val machineType = pipeline.map(_.getResources.getVirtualMachine.getMachineType) - // preemptible is only used if the job fails, as a heuristic to guess if the VM was preempted. - // If we can't get the value of preempted we still need to return something, returning false will not make the failure count - // as a preemption which seems better than saying that it was preemptible when we really don't know - val preemptible = pipeline.flatMap(pipeline => Option(pipeline.getResources.getVirtualMachine.getPreemptible)).exists(_.booleanValue()) - val instanceName = workerEvent.map(_.getInstance()) - val zone = workerEvent.map(_.getZone) - - // If there's an error, generate an unsuccessful status. Otherwise, we were successful! - Option(operation.getError) match { - case Some(error) => - val errorReporter = new ErrorReporter(machineType, preemptible, executionEvents, zone, instanceName, actions, operation, pollingRequest.workflowId) - errorReporter.toUnsuccessfulRunStatus(error, events) - case None => Success(executionEvents, machineType, zone, instanceName) + // If there's an error, generate an unsuccessful status. Otherwise, we were successful! + Option(operation.getError) match { + case Some(error) => + val errorReporter = new ErrorReporter( + machineType, + preemptible, + executionEvents, + zone, + instanceName, + actions, + operation, + pollingRequest.workflowId + ) + errorReporter.toUnsuccessfulRunStatus(error, events) + case None => Success(executionEvents, machineType, zone, instanceName) + } + } else if (operation.hasStarted) { + Running + } else { + Initializing } - } else if (operation.hasStarted) { - Running - } else { - Initializing + } catch { + case nullPointerException: NullPointerException => + throw new RuntimeException( + s"Caught NPE while interpreting operation ${operation.getName}: " + + s"${ExceptionUtils.getStackTrace(nullPointerException)}. " + + s"JSON was $operation", + nullPointerException + ) } - } catch { - case npe: NullPointerException => - throw new RuntimeException(s"Caught NPE while interpreting operation ${operation.getName}: ${ExceptionUtils.getStackTrace(npe)}. JSON was $operation") } } diff --git a/supportedBackends/google/pipelines/v2alpha1/src/test/scala/cromwell/backend/google/pipelines/v2alpha1/api/DeserializationSpec.scala b/supportedBackends/google/pipelines/v2alpha1/src/test/scala/cromwell/backend/google/pipelines/v2alpha1/api/DeserializationSpec.scala index 72ab81edd5c..2cc1b0b2648 100644 --- a/supportedBackends/google/pipelines/v2alpha1/src/test/scala/cromwell/backend/google/pipelines/v2alpha1/api/DeserializationSpec.scala +++ b/supportedBackends/google/pipelines/v2alpha1/src/test/scala/cromwell/backend/google/pipelines/v2alpha1/api/DeserializationSpec.scala @@ -91,7 +91,7 @@ class DeserializationSpec extends FlatSpec with Matchers { ).asJava operation.setMetadata(metadataMap) - val deserializedPipeline = operation.pipeline.get + val deserializedPipeline = operation.pipeline.get.get val action = deserializedPipeline.getActions.get(0) action.getCommands.asScala shouldBe List("echo", "hello") action.getImageUri shouldBe "ubuntu:latest" @@ -102,6 +102,40 @@ class DeserializationSpec extends FlatSpec with Matchers { virtualMachine.getPreemptible shouldBe false } + // https://github.com/broadinstitute/cromwell/issues/4772 + it should "deserialize pipeline from operation metadata without preemptible" in { + val operation = new Operation() + + val metadataMap = Map[String, AnyRef]( + "pipeline" -> Map[String, AnyRef]( + "actions" -> List[java.util.Map[String, Object]]( + Map[String, Object]( + "name" -> "actionName", + "imageUri" -> "ubuntu:latest", + "commands" -> List[String]("echo", "hello").asJava + ).asJava + ).asJava, + "resources" -> Map( + "projectId" -> "project", + "virtualMachine" -> Map( + "machineType" -> "custom-1-1024", + ).asJava + ).asJava + ).asJava + ).asJava + + operation.setMetadata(metadataMap) + val deserializedPipeline = operation.pipeline.get.get + val action = deserializedPipeline.getActions.get(0) + action.getCommands.asScala shouldBe List("echo", "hello") + action.getImageUri shouldBe "ubuntu:latest" + action.getName shouldBe "actionName" + deserializedPipeline.getResources.getProjectId shouldBe "project" + val virtualMachine = deserializedPipeline.getResources.getVirtualMachine + virtualMachine.getMachineType shouldBe "custom-1-1024" + virtualMachine.getPreemptible shouldBe null + } + it should "be able to say if the operation has started" in { val operation = new Operation() diff --git a/supportedBackends/google/pipelines/v2alpha1/src/test/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/GetRequestHandlerSpec.scala b/supportedBackends/google/pipelines/v2alpha1/src/test/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/GetRequestHandlerSpec.scala new file mode 100644 index 00000000000..54360492b53 --- /dev/null +++ b/supportedBackends/google/pipelines/v2alpha1/src/test/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/GetRequestHandlerSpec.scala @@ -0,0 +1,115 @@ +package cromwell.backend.google.pipelines.v2alpha1.api.request + +import java.net.URL + +import akka.actor.ActorRef +import com.google.api.client.http.GenericUrl +import com.google.api.client.testing.http.MockHttpTransport +import com.google.api.services.genomics.v2alpha1.model.Operation +import cromwell.backend.google.pipelines.common.api.PipelinesApiRequestManager.PAPIStatusPollRequest +import cromwell.backend.google.pipelines.common.api.RunStatus._ +import cromwell.backend.standard.StandardAsyncJob +import cromwell.cloudsupport.gcp.auth.GoogleAuthMode +import cromwell.core.WorkflowId +import io.grpc.Status +import org.scalatest.prop.TableDrivenPropertyChecks +import org.scalatest.{FlatSpec, Matchers} + +class GetRequestHandlerSpec extends FlatSpec with Matchers with TableDrivenPropertyChecks { + + behavior of "GetRequestHandler" + + private val requestHandler: GetRequestHandler = new RequestHandler( + "GetRequestHandlerSpec", + new URL("file:///getrequesthandlerspec") + ) + + private val workflowId = WorkflowId.randomId() + private val httpRequest = + new MockHttpTransport.Builder().build().createRequestFactory().buildGetRequest(new GenericUrl()) + private val actorRef = ActorRef.noSender + private val jobId = StandardAsyncJob("test_job") + private val pollingRequest = PAPIStatusPollRequest(workflowId, actorRef, httpRequest, jobId) + + private val interpretedStatus = Table( + ("description", "json", "status"), + ("parse null operation json", null, UnsuccessfulRunStatus( + Status.UNKNOWN, + Option("Operation returned as empty"), + Nil, + None, + None, + None, + wasPreemptible = false + )), + ("parse empty operation json", "{}", Initializing), + ("parse error operation json without resources", + """|{ + | "done": true, + | "error": {} + |} + |""".stripMargin, + Failed(Status.UNAVAILABLE, None, Nil, Nil, None, None, None) + ), + ("parse error operation json without virtualMachine", + """|{ + | "done": true, + | "resources": { + | }, + | "error": {} + |} + |""".stripMargin, + Failed(Status.UNAVAILABLE, None, Nil, Nil, None, None, None) + ), + ("parse error operation json without preemptible", + """|{ + | "done": true, + | "resources": { + | "virtualMachine": { + | } + | }, + | "error": {} + |} + |""".stripMargin, + Failed(Status.UNAVAILABLE, None, Nil, Nil, None, None, None) + ), + ("parse error operation json with preemptible true", + """|{ + | "done": true, + | "resources": { + | "virtualMachine": { + | "preemptible": true + | } + | }, + | "error": {} + |} + |""".stripMargin, + Failed(Status.UNAVAILABLE, None, Nil, Nil, None, None, None) + ), + ("parse error operation json with preemptible false", + """|{ + | "done": true, + | "resources": { + | "virtualMachine": { + | "preemptible": false + | } + | }, + | "error": {} + |} + |""".stripMargin, + Failed(Status.UNAVAILABLE, None, Nil, Nil, None, None, None) + ), + ) + + forAll(interpretedStatus) { (description, json, expectedStatus) => + it should description in { + // Operation responses could come back as null. Handle it and don't crash. + // https://github.com/googleapis/google-http-java-client/blob/v1.28.0/google-http-client/src/main/java/com/google/api/client/http/HttpResponse.java#L456-L458 + val operation = + Option(json).map(GoogleAuthMode.jsonFactory.createJsonParser).map(_.parse(classOf[Operation])).orNull + val runStatus = requestHandler.interpretOperationStatus(operation, pollingRequest) + runStatus should be(expectedStatus) + } + } + +} From e04a0ddbdb98f208d67552b3a762c0726b28ede4 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Mon, 6 May 2019 15:24:09 -0400 Subject: [PATCH 33/46] [develop] Job execution token dispenser actor fixes (#4924) --- .../JobExecutionTokenDispenserActor.scala | 30 ++++---- .../engine/workflow/tokens/TokenQueue.scala | 70 +++++++++++++------ .../workflow/tokens/UnhoggableTokenPool.scala | 8 +-- .../JobExecutionTokenDispenserActorSpec.scala | 39 ++++++++++- 4 files changed, 102 insertions(+), 45 deletions(-) diff --git a/engine/src/main/scala/cromwell/engine/workflow/tokens/JobExecutionTokenDispenserActor.scala b/engine/src/main/scala/cromwell/engine/workflow/tokens/JobExecutionTokenDispenserActor.scala index 8a09378780d..28aaec5648d 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/tokens/JobExecutionTokenDispenserActor.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/tokens/JobExecutionTokenDispenserActor.scala @@ -82,25 +82,17 @@ class JobExecutionTokenDispenserActor(override val serviceRegistryActor: ActorRe if (tokenAssignments.contains(sndr)) { sndr ! JobExecutionTokenDispensed } else { + val queue = tokenQueues.getOrElse(tokenType, TokenQueue(tokenType, tokenEventLogger)) + tokenQueues += tokenType -> queue.enqueue(TokenQueuePlaceholder(sndr, hogGroup)) context.watch(sndr) - val updatedTokenQueue = getTokenQueue(tokenType).enqueue(TokenQueuePlaceholder(sndr, hogGroup)) - tokenQueues += tokenType -> updatedTokenQueue + () } } - private def getTokenQueue(tokenType: JobExecutionTokenType): TokenQueue = { - tokenQueues.getOrElse(tokenType, createNewQueue(tokenType)) - } - - private def createNewQueue(tokenType: JobExecutionTokenType): TokenQueue = { - val newQueue = TokenQueue(tokenType, tokenEventLogger) - tokenQueues += tokenType -> newQueue - newQueue - } - private def distribute(n: Int) = if (tokenQueues.nonEmpty) { - val iterator = new RoundRobinQueueIterator(tokenQueues.values.toList, currentTokenQueuePointer) + // Sort by backend name to avoid re-ordering across iterations: + val iterator = new RoundRobinQueueIterator(tokenQueues.toList.sortBy(_._1.backend).map(_._2), currentTokenQueuePointer) // In rare cases, an abort might empty an inner queue between "available" and "dequeue", which could cause an // exception. @@ -124,7 +116,10 @@ class JobExecutionTokenDispenserActor(override val serviceRegistryActor: ActorRe queuePlaceholder.actor ! JobExecutionTokenDispensed // Only one token per actor, so if you've already got one, we don't need to use this new one: case LeasedActor(queuePlaceholder, lease) => - log.error(s"Actor ${queuePlaceholder.actor.path} requested a job execution token more than once. This situation should have been impossible.") + log.error(s"Programmer Error: Actor ${queuePlaceholder.actor.path} requested a job execution token more than once.") + // Because this actor already has a lease assigned to it: + // a) tell the actor that it has a lease + // b) don't hold onto this new lease - release it and let some other actor take it instead queuePlaceholder.actor ! JobExecutionTokenDispensed lease.release() }) @@ -140,7 +135,8 @@ class JobExecutionTokenDispenserActor(override val serviceRegistryActor: ActorRe leasedToken.release() context.unwatch(actor) () - case None => log.error("Job execution token returned from incorrect actor: {}", actor.path.name) + case None => + log.error("Job execution token returned from incorrect actor: {}", actor.path.name) } } @@ -150,10 +146,10 @@ class JobExecutionTokenDispenserActor(override val serviceRegistryActor: ActorRe log.debug("Actor {} stopped without returning its Job Execution Token. Reclaiming it!", terminee) self.tell(msg = JobExecutionTokenReturn, sender = terminee) case None => - log.debug("Actor {} stopped while we were still watching it... but it doesn't have a token. Removing it from any queues if necessary", terminee) + log.debug("Actor {} stopped before receiving a token, removing it from any queues if necessary", terminee) // This is a very inefficient way to remove the actor from the queue and can lead to very poor performance for a large queue and a large number of actors to remove tokenQueues = tokenQueues map { - case (tokenType, tokenQueue) => tokenType -> tokenQueue.removeLostActor(terminee) + case (tokenType, tokenQueue) => tokenType -> tokenQueue.removeTokenlessActor(terminee) } } context.unwatch(terminee) diff --git a/engine/src/main/scala/cromwell/engine/workflow/tokens/TokenQueue.scala b/engine/src/main/scala/cromwell/engine/workflow/tokens/TokenQueue.scala index e0d1a2a21ec..cedaa151b75 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/tokens/TokenQueue.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/tokens/TokenQueue.scala @@ -1,6 +1,7 @@ package cromwell.engine.workflow.tokens import akka.actor.ActorRef +import com.typesafe.scalalogging.StrictLogging import cromwell.core.JobExecutionToken import cromwell.core.JobExecutionToken.JobExecutionTokenType import cromwell.engine.workflow.tokens.TokenQueue._ @@ -17,7 +18,7 @@ import scala.collection.immutable.Queue final case class TokenQueue(queues: Map[String, Queue[TokenQueuePlaceholder]], queueOrder: Vector[String], eventLogger: TokenEventLogger, - private [tokens] val pool: UnhoggableTokenPool) { + private [tokens] val pool: UnhoggableTokenPool) extends StrictLogging { val tokenType = pool.tokenType /** @@ -47,7 +48,14 @@ final case class TokenQueue(queues: Map[String, Queue[TokenQueuePlaceholder]], * Returns a dequeue'd actor if one exists and there's a token available for it * Returns an updated token queue based on this request (successful queuees get removed, hogs get sent to the back) */ - def dequeue: DequeueResult = recursingDequeue(queues, Vector.empty, queueOrder) + def dequeue: DequeueResult = { + val guaranteedNonEmptyQueues = queues.filterNot { case (hogGroup, q: Queue[_]) => + val empty = q.isEmpty + if (empty) logger.warn(s"Programmer error: Empty token queue value still present in TokenQueue: $hogGroup") + empty + } + recursingDequeue(guaranteedNonEmptyQueues, Vector.empty, queueOrder) + } private def recursingDequeue(queues: Map[String, Queue[TokenQueuePlaceholder]], queuesTried: Vector[String], queuesRemaining: Vector[String]): DequeueResult = { if (queuesRemaining.isEmpty) { @@ -57,32 +65,48 @@ final case class TokenQueue(queues: Map[String, Queue[TokenQueuePlaceholder]], val remainingHogGroups = queuesRemaining.tail val leaseTry = pool.tryAcquire(hogGroup) - leaseTry match { - case thl: TokenHoggingLease => - val oldQueue = queues(hogGroup) - val (placeholder, newQueue) = oldQueue.dequeue - val (newQueues, newQueueOrder) = if (newQueue.isEmpty) { - (queues - hogGroup, remainingHogGroups ++ queuesTried) - } else { - (queues + (hogGroup -> newQueue), remainingHogGroups ++ queuesTried :+ hogGroup) - } - DequeueResult(Some(LeasedActor(placeholder, thl)), TokenQueue(newQueues, newQueueOrder, eventLogger, pool)) - case TokenTypeExhausted => - // The pool is completely full right now, so there's no benefit trying the other hog groups: - eventLogger.outOfTokens(tokenType.backend) - DequeueResult(None, this) - case HogLimitExceeded => - eventLogger.flagTokenHog(hogGroup) - recursingDequeue(queues, queuesTried :+ hogGroup, remainingHogGroups) + val oldQueue = queues(hogGroup) + + if (oldQueue.isEmpty) { + // We should have caught this above. But just in case: + logger.warn(s"Programmer error: Empty token queue value still present in TokenQueue: $hogGroup *and* made it through into recursiveDequeue(!): $hogGroup") + recursingDequeue(queues, queuesTried :+ hogGroup, remainingHogGroups) + } else { + leaseTry match { + case thl: TokenHoggingLease => + val (placeholder, newQueue) = oldQueue.dequeue + val (newQueues, newQueueOrder) = if (newQueue.isEmpty) { + (queues - hogGroup, remainingHogGroups ++ queuesTried) + } else { + (queues + (hogGroup -> newQueue), remainingHogGroups ++ queuesTried :+ hogGroup) + } + DequeueResult(Option(LeasedActor(placeholder, thl)), TokenQueue(newQueues, newQueueOrder, eventLogger, pool)) + case TokenTypeExhausted => + // The pool is completely full right now, so there's no benefit trying the other hog groups: + eventLogger.outOfTokens(tokenType.backend) + DequeueResult(None, this) + case HogLimitExceeded => + eventLogger.flagTokenHog(hogGroup) + recursingDequeue(queues, queuesTried :+ hogGroup, remainingHogGroups) + } } } } - def removeLostActor(lostActor: ActorRef): TokenQueue = this.copy( - queues = queues.map { case (hogGroup, queue) => - hogGroup -> queue.filterNot(_.actor == lostActor) + def removeTokenlessActor(actor: ActorRef): TokenQueue = { + val actorRemovedQueues = queues.map { case (hogGroup, queue) => + hogGroup -> queue.filterNot(_.actor == actor) } - ) + + val (emptyQueues, nonEmptyQueues) = actorRemovedQueues partition { case (_, q) => q.isEmpty } + val emptyHogGroups = emptyQueues.keys.toSet + + this.copy( + // Filter out hog group mappings with empty queues + queues = nonEmptyQueues, + queueOrder = queueOrder filterNot emptyHogGroups.contains + ) + } /** * Returns true if there's at least on element that can be dequeued, false otherwise. diff --git a/engine/src/main/scala/cromwell/engine/workflow/tokens/UnhoggableTokenPool.scala b/engine/src/main/scala/cromwell/engine/workflow/tokens/UnhoggableTokenPool.scala index 6c7b476e1e5..0c4d419b345 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/tokens/UnhoggableTokenPool.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/tokens/UnhoggableTokenPool.scala @@ -21,9 +21,9 @@ final class UnhoggableTokenPool(val tokenType: JobExecutionTokenType) extends Si _healthCheck = Function.const(true)) { lazy val hogLimitOption: Option[Int] = tokenType match { - case JobExecutionTokenType(_, None, _) => None - case JobExecutionTokenType(_, Some(limit), hogFactor) if hogFactor > 1 => Option(math.max(1, math.round(limit.floatValue() / hogFactor.floatValue()))) - case JobExecutionTokenType(_, _, _) => None + case JobExecutionTokenType(_, Some(limit), hogFactor) if hogFactor > 1 => + Option(math.max(1, math.round(limit.floatValue() / hogFactor.floatValue()))) + case _ => None } private[this] val hogGroupAssignments: mutable.Map[String, HashSet[JobExecutionToken]] = mutable.Map.empty @@ -55,7 +55,7 @@ final class UnhoggableTokenPool(val tokenType: JobExecutionTokenType) extends Si synchronized { val thisHogSet = hogGroupAssignments.getOrElse(hogGroup, HashSet.empty) - if (thisHogSet.size + 1 <= hogLimit) { + if (thisHogSet.size < hogLimit) { super.tryAcquire() match { case Some(lease) => val hoggingLease = new TokenHoggingLease(lease, hogGroup, this) diff --git a/engine/src/test/scala/cromwell/engine/workflow/tokens/JobExecutionTokenDispenserActorSpec.scala b/engine/src/test/scala/cromwell/engine/workflow/tokens/JobExecutionTokenDispenserActorSpec.scala index 494fc5608e4..b7d911509fd 100644 --- a/engine/src/test/scala/cromwell/engine/workflow/tokens/JobExecutionTokenDispenserActorSpec.scala +++ b/engine/src/test/scala/cromwell/engine/workflow/tokens/JobExecutionTokenDispenserActorSpec.scala @@ -46,6 +46,10 @@ class JobExecutionTokenDispenserActorSpec extends TestKit(ActorSystem("JETDASpec } it should "dispense the correct amount at the specified rate, not more and not faster" in { + + // Override with a slower distribution rate for this one test: + actorRefUnderTest = TestActorRef(new JobExecutionTokenDispenserActor(TestProbe().ref, Rate(10, 4.seconds), None)) + val senders = (1 to 20).map(_ => TestProbe()) senders.foreach(sender => actorRefUnderTest.tell(msg = JobExecutionTokenRequest("hogGroupA", TestInfiniteTokenType), sender = sender.ref)) // The first 10 should get their token @@ -286,10 +290,43 @@ class JobExecutionTokenDispenserActorSpec extends TestKit(ActorSystem("JETDASpec actorRefUnderTest.underlyingActor.tokenQueues.map(x => x._2.size).sum should be(0) } + it should "be resilient if the last request for a hog group is removed from the queue before a token is dispensed" in { + val tokenType = JobExecutionTokenType(s"mini", maxPoolSize = Option(6), hogFactor = 2) + + val groupARequesters = (0 until 4) map { i => TestProbe(name = s"group_a_$i") } + val groupBRequesters = (0 until 4) map { i => TestProbe(name = s"group_b_$i") } + + // Group A and B fill up the token queue: + groupARequesters foreach { probe => probe.send(actorRefUnderTest, JobExecutionTokenRequest("hogGroupA", tokenType)) } + groupBRequesters foreach { probe => probe.send(actorRefUnderTest, JobExecutionTokenRequest("hogGroupB", tokenType)) } + + // Pretty soon, the tokens should all be gone, 3 to each group, and each group should have one queued item: + eventually { + (groupARequesters.take(3) ++ groupBRequesters.take(3)).foreach { probe => + actorRefUnderTest.underlyingActor.tokenAssignments.keys should contain(probe.ref) + } + // And both groups should have one item in the queue: + actorRefUnderTest.underlyingActor.tokenQueues(tokenType).queues.values.foreach { queue => queue.size should be(1) } + } + + // Group B gets bored and aborts all jobs (in reverse order to make sure ): + groupBRequesters.reverse.foreach { probe => + probe.ref ! PoisonPill + // TODO: validate that the probe has left the queue + } + + // Group A's jobs are able to complete successfully: + groupARequesters foreach { probe => + probe.expectMsg(JobExecutionTokenDispensed) + probe.ref ! PoisonPill + } + + } + var actorRefUnderTest: TestActorRef[JobExecutionTokenDispenserActor] = _ before { - actorRefUnderTest = TestActorRef(new JobExecutionTokenDispenserActor(TestProbe().ref, Rate(10, 4.second), None)) + actorRefUnderTest = TestActorRef(new JobExecutionTokenDispenserActor(TestProbe().ref, Rate(10, 100.millis), None)) } after { actorRefUnderTest = null From 17c0b3db4adc73d7ebd81f5337c5e9a89abbe078 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Wed, 8 May 2019 00:30:59 -0400 Subject: [PATCH 34/46] Build updates - Workaround Travis Docker issue - Added heartbeats to docker tests - DRYed conformance tests - Fixed centaur tests where an empty CBCTAP early terminated getopts - Introduced non-Travis-specific variable for GitHub PR branch names - Switched from cd to pushd/popd --- .../src/bin/centaur-cwl-runner.bash | 22 +- .../src/main/resources/reference.conf | 2 +- .../centaur/cwl/CentaurCwlRunnerRunMode.scala | 2 +- project/build.properties | 2 +- project/project/build.properties | 2 +- scripts/docker-develop/Dockerfile | 2 +- src/ci/bin/test.inc.sh | 189 ++++++++++++++---- src/ci/bin/testCentaurHoricromtalPapiV2.sh | 2 - ...estCentaurPapiUpgradeNewWorkflowsPapiV1.sh | 1 - src/ci/bin/testCentaurPapiV1.sh | 1 - src/ci/bin/testCentaurPapiV2.sh | 2 - src/ci/bin/testConformanceLocal.sh | 42 +--- src/ci/bin/testConformancePapiV2.sh | 40 +--- src/ci/bin/testConformanceTesk.sh | 42 +--- src/ci/bin/testDockerDeadlock.sh | 12 +- src/ci/bin/testDockerScripts.sh | 11 +- src/ci/bin/test_bcs.inc.sh | 2 +- 17 files changed, 183 insertions(+), 193 deletions(-) diff --git a/centaurCwlRunner/src/bin/centaur-cwl-runner.bash b/centaurCwlRunner/src/bin/centaur-cwl-runner.bash index be6326c14e8..078c615e4f8 100755 --- a/centaurCwlRunner/src/bin/centaur-cwl-runner.bash +++ b/centaurCwlRunner/src/bin/centaur-cwl-runner.bash @@ -1,8 +1,22 @@ #!/usr/bin/env bash # `sbt assembly` must have already been run. -BUILD_ROOT="$( dirname "${BASH_SOURCE[0]}" )/../../.." -CENTAUR_CWL_JAR="${CENTAUR_CWL_JAR:-"$( find "${BUILD_ROOT}/centaurCwlRunner/target/scala-2.12" -name 'centaur-cwl-runner-*.jar' | head -n 1 )"}" -CENTAUR_CWL_SKIP_FILE="${BUILD_ROOT}/centaurCwlRunner/src/main/resources/skipped_tests.csv" +build_root="$( dirname "${BASH_SOURCE[0]}" )/../../.." +centaur_cwl_jar="${CENTAUR_CWL_JAR:-"$( find "${build_root}/centaurCwlRunner/target/scala-2.12" -name 'centaur-cwl-runner-*.jar' | head -n 1 )"}" +centaur_cwl_skip_file="${build_root}/centaurCwlRunner/src/main/resources/skipped_tests.csv" -java ${CENTAUR_CWL_JAVA_ARGS-"-Xmx1g"} -jar "${CENTAUR_CWL_JAR}" --skip-file "${CENTAUR_CWL_SKIP_FILE}" "$@" +centaur_cwl_java_args=("-Xmx1g") +if [[ -n "${CENTAUR_CWL_JAVA_ARGS-}" ]]; then + # Allow splitting on space to simulate an exported array + # https://stackoverflow.com/questions/5564418/exporting-an-array-in-bash-script#answer-5564589 + # shellcheck disable=SC2206 + centaur_cwl_java_args+=(${CENTAUR_CWL_JAVA_ARGS}) +fi + +# Handle empty arrays in older versions of bash +# https://stackoverflow.com/questions/7577052/bash-empty-array-expansion-with-set-u#answer-7577209 +java \ + ${centaur_cwl_java_args[@]+"${centaur_cwl_java_args[@]}"} \ + -jar "${centaur_cwl_jar}" \ + --skip-file "${centaur_cwl_skip_file}" \ + "$@" diff --git a/centaurCwlRunner/src/main/resources/reference.conf b/centaurCwlRunner/src/main/resources/reference.conf index eee7fc884fd..86e25a96304 100644 --- a/centaurCwlRunner/src/main/resources/reference.conf +++ b/centaurCwlRunner/src/main/resources/reference.conf @@ -1,7 +1,7 @@ centaur { cwl-runner { mode=local - mode=${?CENTAUR_CWL_RUNNER_MODE} + mode=${?CROMWELL_BUILD_CWL_RUNNER_MODE} papi { default-input-gcs-prefix = ${?PAPI_INPUT_GCS_PREFIX} diff --git a/centaurCwlRunner/src/main/scala/centaur/cwl/CentaurCwlRunnerRunMode.scala b/centaurCwlRunner/src/main/scala/centaur/cwl/CentaurCwlRunnerRunMode.scala index 69b8792289d..01444ce709c 100644 --- a/centaurCwlRunner/src/main/scala/centaur/cwl/CentaurCwlRunnerRunMode.scala +++ b/centaurCwlRunner/src/main/scala/centaur/cwl/CentaurCwlRunnerRunMode.scala @@ -40,7 +40,7 @@ object CentaurCwlRunnerRunMode { def fromConfig(conf: Config): CentaurCwlRunnerRunMode = { conf.getString("mode") match { case "local" => LocalRunMode - case "papi" => PapiRunMode(conf) + case "papi_v2" => PapiRunMode(conf) case "tesk" => TeskRunMode(conf) case unknown => throw new UnsupportedOperationException(s"mode not recognized: $unknown") } diff --git a/project/build.properties b/project/build.properties index 5620cc502b4..c0bab04941d 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.2.1 +sbt.version=1.2.8 diff --git a/project/project/build.properties b/project/project/build.properties index 8b697bbb94f..c0bab04941d 100644 --- a/project/project/build.properties +++ b/project/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.0 +sbt.version=1.2.8 diff --git a/scripts/docker-develop/Dockerfile b/scripts/docker-develop/Dockerfile index f07de423504..1e9adebf648 100644 --- a/scripts/docker-develop/Dockerfile +++ b/scripts/docker-develop/Dockerfile @@ -11,7 +11,7 @@ FROM openjdk:8u171 # Env variables ENV SCALA_VERSION 2.12.6 -ENV SBT_VERSION 1.2.1 +ENV SBT_VERSION 1.2.8 # Scala expects this file RUN touch /usr/lib/jvm/java-8-openjdk-amd64/release diff --git a/src/ci/bin/test.inc.sh b/src/ci/bin/test.inc.sh index 684ead12ff0..120ecc2ab8a 100644 --- a/src/ci/bin/test.inc.sh +++ b/src/ci/bin/test.inc.sh @@ -87,13 +87,14 @@ cromwell::private::create_build_variables() { CROMWELL_BUILD_IS_SECURE="${TRAVIS_SECURE_ENV_VARS}" CROMWELL_BUILD_TYPE="${BUILD_TYPE}" CROMWELL_BUILD_BRANCH="${TRAVIS_PULL_REQUEST_BRANCH:-${TRAVIS_BRANCH}}" + CROMWELL_BUILD_BRANCH_PULL_REQUEST="${TRAVIS_PULL_REQUEST_BRANCH:-""}" CROMWELL_BUILD_EVENT="${TRAVIS_EVENT_TYPE}" CROMWELL_BUILD_TAG="${TRAVIS_TAG}" CROMWELL_BUILD_NUMBER="${TRAVIS_JOB_NUMBER}" CROMWELL_BUILD_URL="https://travis-ci.com/${TRAVIS_REPO_SLUG}/jobs/${TRAVIS_JOB_ID}" CROMWELL_BUILD_GIT_USER_EMAIL="travis@travis-ci.com" CROMWELL_BUILD_GIT_USER_NAME="Travis CI" - CROMWELL_BUILD_HEARTBEAT_MESSAGE="…" + CROMWELL_BUILD_HEARTBEAT_PATTERN="…" CROMWELL_BUILD_MYSQL_HOSTNAME="localhost" CROMWELL_BUILD_MYSQL_PORT="3306" CROMWELL_BUILD_MYSQL_USERNAME="travis" @@ -116,13 +117,14 @@ cromwell::private::create_build_variables() { CROMWELL_BUILD_TYPE="${JENKINS_BUILD_TYPE}" CROMWELL_BUILD_CENTAUR_TEST_ADDITIONAL_PARAMETERS="${CENTAUR_TEST_ADDITIONAL_PARAMETERS:-""}" CROMWELL_BUILD_BRANCH="${GIT_BRANCH#origin/}" + CROMWELL_BUILD_BRANCH_PULL_REQUEST="" CROMWELL_BUILD_EVENT="" CROMWELL_BUILD_TAG="" CROMWELL_BUILD_NUMBER="${BUILD_NUMBER}" CROMWELL_BUILD_URL="${BUILD_URL}" CROMWELL_BUILD_GIT_USER_EMAIL="jenkins@jenkins.io" CROMWELL_BUILD_GIT_USER_NAME="Jenkins CI" - CROMWELL_BUILD_HEARTBEAT_MESSAGE="…\n" + CROMWELL_BUILD_HEARTBEAT_PATTERN="…\n" CROMWELL_BUILD_MYSQL_HOSTNAME="mysql-db" CROMWELL_BUILD_MYSQL_PORT="3306" CROMWELL_BUILD_MYSQL_USERNAME="root" @@ -135,13 +137,14 @@ cromwell::private::create_build_variables() { CROMWELL_BUILD_IS_SECURE=true CROMWELL_BUILD_TYPE="unknown" CROMWELL_BUILD_BRANCH="unknown" + CROMWELL_BUILD_BRANCH_PULL_REQUEST="" CROMWELL_BUILD_EVENT="unknown" CROMWELL_BUILD_TAG="" CROMWELL_BUILD_NUMBER="" CROMWELL_BUILD_URL="" CROMWELL_BUILD_GIT_USER_EMAIL="unknown.git.user@example.org" CROMWELL_BUILD_GIT_USER_NAME="Unknown Git User" - CROMWELL_BUILD_HEARTBEAT_MESSAGE="…" + CROMWELL_BUILD_HEARTBEAT_PATTERN="…" CROMWELL_BUILD_MYSQL_HOSTNAME="${CROMWELL_BUILD_MYSQL_HOSTNAME-localhost}" CROMWELL_BUILD_MYSQL_PORT="${CROMWELL_BUILD_MYSQL_PORT-3306}" CROMWELL_BUILD_MYSQL_USERNAME="${CROMWELL_BUILD_MYSQL_USERNAME-root}" @@ -185,18 +188,24 @@ cromwell::private::create_build_variables() { CROMWELL_BUILD_REQUIRES_SECURE=false fi + if [[ -z "${CROMWELL_BUILD_SBT_ASSEMBLY_COMMAND-}" ]]; then + CROMWELL_BUILD_SBT_ASSEMBLY_COMMAND="assembly" + fi + if [[ -z "${VAULT_TOKEN-}" ]]; then VAULT_TOKEN="vault token is not set as an environment variable" fi - CROMWELL_BUILD_RANDOM_256_BITS_BASE64="$(dd bs=1 count=32 if=/dev/urandom 2>/dev/null | base64 | tr -d '\n')" + CROMWELL_BUILD_RANDOM_256_BITS_BASE64="$(dd bs=1 count=32 if=/dev/urandom 2> /dev/null | base64 | tr -d '\n')" local hours_to_minutes hours_to_minutes=60 - CROMWELL_BUILD_HEARTBEAT_MINUTES=$((20 * ${hours_to_minutes})) + CROMWELL_BUILD_HEARTBEAT_MINUTES=$((20 * hours_to_minutes)) export CROMWELL_BUILD_BACKEND_TYPE export CROMWELL_BUILD_BRANCH + export CROMWELL_BUILD_BRANCH_PULL_REQUEST + export CROMWELL_BUILD_CENTAUR_TEST_ADDITIONAL_PARAMETERS export CROMWELL_BUILD_CROMWELL_CONFIG export CROMWELL_BUILD_CROMWELL_LOG export CROMWELL_BUILD_EVENT @@ -204,7 +213,7 @@ cromwell::private::create_build_variables() { export CROMWELL_BUILD_GENERATE_COVERAGE export CROMWELL_BUILD_GIT_USER_EMAIL export CROMWELL_BUILD_GIT_USER_NAME - export CROMWELL_BUILD_HEARTBEAT_MESSAGE + export CROMWELL_BUILD_HEARTBEAT_PATTERN export CROMWELL_BUILD_HEARTBEAT_MINUTES export CROMWELL_BUILD_HOME_DIRECTORY export CROMWELL_BUILD_IS_CI @@ -217,19 +226,20 @@ cromwell::private::create_build_variables() { export CROMWELL_BUILD_MYSQL_SCHEMA export CROMWELL_BUILD_MYSQL_USERNAME export CROMWELL_BUILD_NUMBER + export CROMWELL_BUILD_OPTIONAL_SECURE export CROMWELL_BUILD_OS export CROMWELL_BUILD_OS_DARWIN export CROMWELL_BUILD_OS_LINUX export CROMWELL_BUILD_PROVIDER - export CROMWELL_BUILD_PROVIDER_TRAVIS export CROMWELL_BUILD_PROVIDER_JENKINS + export CROMWELL_BUILD_PROVIDER_TRAVIS export CROMWELL_BUILD_PROVIDER_UNKNOWN export CROMWELL_BUILD_RANDOM_256_BITS_BASE64 export CROMWELL_BUILD_REQUIRES_SECURE - export CROMWELL_BUILD_OPTIONAL_SECURE - export CROMWELL_BUILD_RESOURCES_SOURCES export CROMWELL_BUILD_RESOURCES_DIRECTORY + export CROMWELL_BUILD_RESOURCES_SOURCES export CROMWELL_BUILD_ROOT_DIRECTORY + export CROMWELL_BUILD_SBT_ASSEMBLY_COMMAND export CROMWELL_BUILD_SCRIPTS_DIRECTORY export CROMWELL_BUILD_TAG export CROMWELL_BUILD_TYPE @@ -238,7 +248,6 @@ cromwell::private::create_build_variables() { export CROMWELL_BUILD_WAIT_FOR_IT_FILENAME export CROMWELL_BUILD_WAIT_FOR_IT_SCRIPT export CROMWELL_BUILD_WAIT_FOR_IT_URL - export CROMWELL_BUILD_CENTAUR_TEST_ADDITIONAL_PARAMETERS } cromwell::private::echo_build_variables() { @@ -248,6 +257,7 @@ cromwell::private::echo_build_variables() { echo "CROMWELL_BUILD_OPTIONAL_SECURE='${CROMWELL_BUILD_OPTIONAL_SECURE}'" echo "CROMWELL_BUILD_TYPE='${CROMWELL_BUILD_TYPE}'" echo "CROMWELL_BUILD_BRANCH='${CROMWELL_BUILD_BRANCH}'" + echo "CROMWELL_BUILD_BRANCH_PULL_REQUEST='${CROMWELL_BUILD_BRANCH_PULL_REQUEST}'" echo "CROMWELL_BUILD_EVENT='${CROMWELL_BUILD_EVENT}'" echo "CROMWELL_BUILD_TAG='${CROMWELL_BUILD_TAG}'" echo "CROMWELL_BUILD_NUMBER='${CROMWELL_BUILD_NUMBER}'" @@ -442,8 +452,9 @@ cromwell::private::checkout_pinned_cwl() { https://github.com/common-workflow-language/common-workflow-language.git \ "${CROMWELL_BUILD_CWL_TEST_DIRECTORY}" ( - cd "${CROMWELL_BUILD_CWL_TEST_DIRECTORY}" || exit 1 + pushd "${CROMWELL_BUILD_CWL_TEST_DIRECTORY}" > /dev/null git checkout "${CROMWELL_BUILD_CWL_TEST_COMMIT}" + popd > /dev/null ) fi } @@ -483,6 +494,8 @@ cromwell::private::vault_login() { # Login to vault to access secrets local vault_token vault_token="${VAULT_TOKEN}" + # Don't fail here if vault login fails + # shellcheck disable=SC2015 docker run --rm \ -v "${CROMWELL_BUILD_HOME_DIRECTORY}:/root:rw" \ broadinstitute/dsde-toolbox \ @@ -562,14 +575,14 @@ cromwell::private::calculate_prior_version_tag() { | awk -F \" '{print $2}' \ )" - # This function should only ever run on Travis PR builds where TRAVIS_PULL_REQUEST_BRANCH is set. - if [ -z "${TRAVIS_PULL_REQUEST_BRANCH}" ]; then - echo "Error: the TRAVIS_PULL_REQUEST_BRANCH variable is not set. calculate_prior_version_tag expects to only run on Travis Pull Request builds in which this variable is set." >&2 + # This function should only ever run on PR builds. + if [[ -z "${CROMWELL_BUILD_BRANCH_PULL_REQUEST}" ]]; then + echo "Error: the CROMWELL_BUILD_BRANCH_PULL_REQUEST variable is not set. calculate_prior_version_tag expects to only run on Travis Pull Request builds in which this variable is set." >&2 exit 1 fi # If this PR targets a hotfix branch, the previous version should be the same major version as this version. # Otherwise this PR targets a non-hotfix branch so the previous version should be one less than this version. - if $( echo "${TRAVIS_PULL_REQUEST_BRANCH}" | grep -q -E '^[0-9]+_hotfix$' ); then + if [[ "${CROMWELL_BUILD_BRANCH_PULL_REQUEST}" =~ ^[0-9\.]+_hotfix$ ]]; then prior_version="$current_version" else prior_version=$((current_version - 1)) @@ -578,19 +591,21 @@ cromwell::private::calculate_prior_version_tag() { } cromwell::private::get_prior_version_config() { - local prior_version=$1 + local prior_version + prior_version="${1:?get_prior_version_config called without a version}"; shift prior_config="${CROMWELL_BUILD_RESOURCES_DIRECTORY}/${CROMWELL_BUILD_BACKEND_TYPE}_${prior_version}_application.conf" echo "${prior_config}" } cromwell::private::setup_prior_version_resources() { local prior_config - local prior_version="$(cromwell::private::calculate_prior_version_tag)" + local prior_version + prior_version="$(cromwell::private::calculate_prior_version_tag)" CROMWELL_BUILD_CROMWELL_PRIOR_VERSION_JAR="${CROMWELL_BUILD_RESOURCES_DIRECTORY}/cromwell_${prior_version}.jar" export CROMWELL_BUILD_CROMWELL_PRIOR_VERSION_JAR - prior_config="$(cromwell::private::get_prior_version_config ${prior_version})" + prior_config="$(cromwell::private::get_prior_version_config "${prior_version}")" if [[ -f "${prior_config}" ]]; then CROMWELL_BUILD_CROMWELL_PRIOR_VERSION_CONFIG="${prior_config}" export CROMWELL_BUILD_CROMWELL_PRIOR_VERSION_CONFIG @@ -609,9 +624,9 @@ cromwell::private::exists_cromwell_jar() { } cromwell::private::assemble_jars() { - # CROMWELL_SBT_ASSEMBLY_COMMAND allows for an override of the default `assembly` command for assembly. + # CROMWELL_BUILD_SBT_ASSEMBLY_COMMAND allows for an override of the default `assembly` command for assembly. # This can be useful to reduce time and memory that might otherwise be spent assembling unused subprojects. - CROMWELL_SBT_ASSEMBLY_LOG_LEVEL=error sbt coverage ${CROMWELL_SBT_ASSEMBLY_COMMAND:-assembly} -error + CROMWELL_SBT_ASSEMBLY_LOG_LEVEL=error sbt coverage ${CROMWELL_BUILD_SBT_ASSEMBLY_COMMAND:-assembly} -error } cromwell::private::generate_code_coverage() { @@ -644,26 +659,27 @@ cromwell::private::push_publish_complete() { # Loosely adapted from https://github.com/broadinstitute/workbench-libs/blob/435a932/scripts/version_update.sh mkdir publish_complete - ( - cd publish_complete || exit 1 + pushd publish_complete > /dev/null + + git init + git config core.sshCommand "ssh -i ${github_private_deploy_key} -F /dev/null" + git config user.email "${CROMWELL_BUILD_GIT_USER_EMAIL}" + git config user.name "${CROMWELL_BUILD_GIT_USER_NAME}" - git init - git config core.sshCommand "ssh -i ${github_private_deploy_key} -F /dev/null" - git config user.email "${CROMWELL_BUILD_GIT_USER_EMAIL}" - git config user.name "${CROMWELL_BUILD_GIT_USER_NAME}" + git remote add "${git_publish_remote}" "${git_repo}" + git checkout -b "${git_publish_branch}" + git commit --allow-empty -m "${git_publish_message}" + git push -f "${git_publish_remote}" "${git_publish_branch}" - git remote add "${git_publish_remote}" "${git_repo}" - git checkout -b "${git_publish_branch}" - git commit --allow-empty -m "${git_publish_message}" - git push -f "${git_publish_remote}" "${git_publish_branch}" - ) + popd > /dev/null } cromwell::private::start_build_heartbeat() { # Sleep one minute between printouts, but don't zombie forever for ((i=0; i < "${CROMWELL_BUILD_HEARTBEAT_MINUTES}"; i++)); do sleep 60 - printf "${CROMWELL_BUILD_HEARTBEAT_MESSAGE}" + # shellcheck disable=SC2059 + printf "${CROMWELL_BUILD_HEARTBEAT_PATTERN}" done & CROMWELL_BUILD_HEARTBEAT_PID=$! } @@ -713,7 +729,7 @@ cromwell::private::kill_centaur_log_tail() { cromwell::private::run_exit_functions() { if [[ -f "${CROMWELL_BUILD_EXIT_FUNCTIONS}" ]]; then local exit_function - while read exit_function; do + while read -r exit_function; do ${exit_function} || true done < "${CROMWELL_BUILD_EXIT_FUNCTIONS}" rm "${CROMWELL_BUILD_EXIT_FUNCTIONS}" || true @@ -755,6 +771,45 @@ cromwell::private::kill_tree() { kill "${pid}" 2> /dev/null } + +cromwell::private::start_conformance_cromwell() { + # Start the Cromwell server in the directory containing input files so it can access them via their relative path + pushd "${CROMWELL_BUILD_CWL_TEST_RESOURCES}" > /dev/null + + # Turn off call caching as hashing doesn't work since it sees local and not GCS paths. + # CWL conformance uses alpine images that do not have bash. + java \ + -Xmx2g \ + -Dconfig.file="${CROMWELL_BUILD_CROMWELL_CONFIG}" \ + -Dcall-caching.enabled=false \ + -Dsystem.job-shell=/bin/sh \ + -jar "${CROMWELL_BUILD_CROMWELL_JAR}" \ + server & + + CROMWELL_BUILD_CONFORMANCE_CROMWELL_PID=$! + + popd > /dev/null +} + +cromwell::private::kill_conformance_cromwell() { + if [[ -n "${CROMWELL_BUILD_CONFORMANCE_CROMWELL_PID+set}" ]]; then + cromwell::build::kill_tree "${CROMWELL_BUILD_CONFORMANCE_CROMWELL_PID}" + fi +} + +cromwell::private::run_conformance_wdl() { + pushd "${CROMWELL_BUILD_CWL_TEST_RESOURCES}" > /dev/null + + java \ + -Xmx6g \ + -Dbackend.providers.Local.config.concurrent-job-limit="${CROMWELL_BUILD_CWL_TEST_PARALLELISM}" \ + -jar "${CROMWELL_BUILD_CROMWELL_JAR}" \ + run "${CROMWELL_BUILD_CWL_TEST_WDL}" \ + -i "${CROMWELL_BUILD_CWL_TEST_INPUTS}" + + popd > /dev/null +} + cromwell::build::exec_test_script() { cromwell::private::create_build_variables if [[ "${CROMWELL_BUILD_RUN_TESTS}" == "false" ]]; then @@ -815,15 +870,11 @@ cromwell::build::setup_centaur_environment() { cromwell::private::add_exit_function cromwell::private::kill_centaur_log_tail } -cromwell::build::run_centaur() { - "${CROMWELL_BUILD_ROOT_DIRECTORY}/centaur/test_cromwell.sh" \ - -n "${CROMWELL_BUILD_CENTAUR_CONFIG}" \ - -l "${CROMWELL_BUILD_LOG_DIRECTORY}" \ - -g \ - "$@" -} - cromwell::build::setup_conformance_environment() { + # Override of the default sbt assembly command which is just `assembly`. + # The conformance runs only need these two subprojects so save a couple of minutes and skip the rest. + export CROMWELL_BUILD_SBT_ASSEMBLY_COMMAND="server/assembly centaurCwlRunner/assembly" + cromwell::private::export_conformance_variables if [[ "${CROMWELL_BUILD_IS_CI}" == "true" ]]; then cromwell::private::install_cwltest @@ -835,6 +886,23 @@ cromwell::build::setup_conformance_environment() { cromwell::private::add_exit_function cromwell::private::kill_build_heartbeat } +cromwell::build::setup_docker_environment() { + cromwell::private::start_build_heartbeat + cromwell::private::add_exit_function cromwell::private::kill_build_heartbeat + + if [[ "${CROMWELL_BUILD_PROVIDER}" == "${CROMWELL_BUILD_PROVIDER_TRAVIS}" ]]; then + # Upgrade docker-compose so that we get the correct exit codes + docker-compose -version + sudo rm /usr/local/bin/docker-compose + curl \ + -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" \ + > docker-compose + chmod +x docker-compose + sudo mv docker-compose /usr/local/bin + docker-compose -version + fi +} + cromwell::build::assemble_jars() { cromwell::private::find_cromwell_jar if [[ "${CROMWELL_BUILD_IS_CI}" == "true" ]] || ! cromwell::private::exists_cromwell_jar; then @@ -848,6 +916,39 @@ cromwell::build::assemble_jars() { fi } +cromwell::build::run_centaur() { + local -a additional_args + additional_args=() + if [[ -n "${CROMWELL_BUILD_CENTAUR_TEST_ADDITIONAL_PARAMETERS-}" ]]; then + # Allow splitting on space to simulate an exported array + # https://stackoverflow.com/questions/5564418/exporting-an-array-in-bash-script#answer-5564589 + # shellcheck disable=SC2206 + additional_args=(${CROMWELL_BUILD_CENTAUR_TEST_ADDITIONAL_PARAMETERS}) + fi + # Handle empty arrays in older versions of bash + # https://stackoverflow.com/questions/7577052/bash-empty-array-expansion-with-set-u#answer-7577209 + "${CROMWELL_BUILD_ROOT_DIRECTORY}/centaur/test_cromwell.sh" \ + -n "${CROMWELL_BUILD_CENTAUR_CONFIG}" \ + -l "${CROMWELL_BUILD_LOG_DIRECTORY}" \ + -g \ + ${additional_args[@]+"${additional_args[@]}"} \ + "$@" +} + +cromwell::build::run_conformance() { + CROMWELL_BUILD_CWL_RUNNER_MODE="${CROMWELL_BUILD_BACKEND_TYPE}" + + export CROMWELL_BUILD_CWL_RUNNER_MODE + + cromwell::private::start_conformance_cromwell + cromwell::private::add_exit_function cromwell::private::kill_conformance_cromwell + + # Give cromwell time to start up + sleep 30 + + cromwell::private::run_conformance_wdl +} + cromwell::build::generate_code_coverage() { if [[ "${CROMWELL_BUILD_GENERATE_COVERAGE}" == "true" ]]; then cromwell::private::generate_code_coverage @@ -896,8 +997,8 @@ cromwell::build::exec_retry_function() { # https://unix.stackexchange.com/a/82610 # https://stackoverflow.com/a/17336953 - for attempt in $(seq 0 ${retry_count}); do - [[ ${attempt} -gt 0 ]] && sleep ${sleep_seconds} + for attempt in $(seq 0 "${retry_count}"); do + [[ ${attempt} -gt 0 ]] && sleep "${sleep_seconds}" ${retried_function} && exit_status=0 && break || exit_status=$? done return ${exit_status} @@ -907,7 +1008,7 @@ cromwell::build::exec_silent_function() { local silent_function silent_function="${1:?exec_silent_function called without a function}"; shift if cromwell::private::is_xtrace_enabled; then - cromwell::private::exec_silent_function ${silent_function} "$@" + cromwell::private::exec_silent_function "${silent_function}" "$@" else ${silent_function} "$@" fi diff --git a/src/ci/bin/testCentaurHoricromtalPapiV2.sh b/src/ci/bin/testCentaurHoricromtalPapiV2.sh index 256a1711dc7..e0b1318b921 100755 --- a/src/ci/bin/testCentaurHoricromtalPapiV2.sh +++ b/src/ci/bin/testCentaurHoricromtalPapiV2.sh @@ -42,7 +42,5 @@ cromwell::build::run_centaur \ -e relative_output_paths \ -e relative_output_paths_colliding \ -e standard_output_paths_colliding_prevented \ - "${CROMWELL_BUILD_CENTAUR_TEST_ADDITIONAL_PARAMETERS:-""}" \ - -d "${CROMWELL_BUILD_CENTAUR_TEST_DIRECTORY}" cromwell::build::generate_code_coverage diff --git a/src/ci/bin/testCentaurPapiUpgradeNewWorkflowsPapiV1.sh b/src/ci/bin/testCentaurPapiUpgradeNewWorkflowsPapiV1.sh index 0322d142844..e04fd68de81 100755 --- a/src/ci/bin/testCentaurPapiUpgradeNewWorkflowsPapiV1.sh +++ b/src/ci/bin/testCentaurPapiUpgradeNewWorkflowsPapiV1.sh @@ -18,6 +18,5 @@ if [ "${CROMWELL_BUILD_PROVIDER}" = "${CROMWELL_BUILD_PROVIDER_TRAVIS}" ] && [ - -p 100 \ -e localdockertest \ -e gpu_on_papi \ - -d "${CROMWELL_BUILD_CENTAUR_TEST_DIRECTORY}" fi diff --git a/src/ci/bin/testCentaurPapiV1.sh b/src/ci/bin/testCentaurPapiV1.sh index 5dc6c1cfe7d..377acd45360 100755 --- a/src/ci/bin/testCentaurPapiV1.sh +++ b/src/ci/bin/testCentaurPapiV1.sh @@ -29,6 +29,5 @@ cromwell::build::run_centaur \ -e relative_output_paths \ -e relative_output_paths_colliding \ -e standard_output_paths_colliding_prevented \ - -d "${CROMWELL_BUILD_CENTAUR_TEST_DIRECTORY}" cromwell::build::generate_code_coverage diff --git a/src/ci/bin/testCentaurPapiV2.sh b/src/ci/bin/testCentaurPapiV2.sh index 83db13deab9..9d34a5b8821 100755 --- a/src/ci/bin/testCentaurPapiV2.sh +++ b/src/ci/bin/testCentaurPapiV2.sh @@ -31,7 +31,5 @@ cromwell::build::run_centaur \ -e relative_output_paths \ -e relative_output_paths_colliding \ -e standard_output_paths_colliding_prevented \ - "${CROMWELL_BUILD_CENTAUR_TEST_ADDITIONAL_PARAMETERS:-""}" \ - -d "${CROMWELL_BUILD_CENTAUR_TEST_DIRECTORY}" cromwell::build::generate_code_coverage diff --git a/src/ci/bin/testConformanceLocal.sh b/src/ci/bin/testConformanceLocal.sh index def67ff2c0d..74e2cabc462 100755 --- a/src/ci/bin/testConformanceLocal.sh +++ b/src/ci/bin/testConformanceLocal.sh @@ -9,48 +9,8 @@ cromwell::build::setup_common_environment cromwell::build::setup_conformance_environment -# Override of the default sbt assembly command which is just `assembly`. -# The conformance runs only need these two subprojects so save a couple of minutes and skip the rest. -export CROMWELL_SBT_ASSEMBLY_COMMAND="server/assembly centaurCwlRunner/assembly" cromwell::build::assemble_jars -CENTAUR_CWL_RUNNER_MODE="local" - -# Export variables used in conf files and commands -export CENTAUR_CWL_RUNNER_MODE - -shutdown_cromwell() { - if [[ -n "${CROMWELL_PID+set}" ]]; then - cromwell::build::kill_tree "${CROMWELL_PID}" - fi -} - -cromwell::build::add_exit_function shutdown_cromwell - -# Start the Cromwell server in the directory containing input files so it can access them via their relative path -cd "${CROMWELL_BUILD_CWL_TEST_RESOURCES}" - -# Turn off call caching as hashing doesn't work since it sees local and not GCS paths. -# CWL conformance uses alpine images that do not have bash. -java \ - -Xmx2g \ - -Dconfig.file="${CROMWELL_BUILD_CROMWELL_CONFIG}" \ - -Dcall-caching.enabled=false \ - -Dsystem.job-shell=/bin/sh \ - -jar "${CROMWELL_BUILD_CROMWELL_JAR}" \ - server & - -CROMWELL_PID=$! - -sleep 30 - -java \ - -Xmx6g \ - -Dbackend.providers.Local.config.concurrent-job-limit="${CROMWELL_BUILD_CWL_TEST_PARALLELISM}" \ - -jar "${CROMWELL_BUILD_CROMWELL_JAR}" \ - run "${CROMWELL_BUILD_CWL_TEST_WDL}" \ - -i "${CROMWELL_BUILD_CWL_TEST_INPUTS}" - -cd "${CROMWELL_BUILD_ROOT_DIRECTORY}" +cromwell::build::run_conformance cromwell::build::generate_code_coverage diff --git a/src/ci/bin/testConformancePapiV2.sh b/src/ci/bin/testConformancePapiV2.sh index 09a4beb209d..59f1ca3b7eb 100755 --- a/src/ci/bin/testConformancePapiV2.sh +++ b/src/ci/bin/testConformancePapiV2.sh @@ -10,52 +10,14 @@ cromwell::build::setup_common_environment cromwell::build::setup_conformance_environment -# Override of the default sbt assembly command which is just `assembly`. -# The conformance runs only need these two subprojects so save a couple of minutes and skip the rest. -export CROMWELL_SBT_ASSEMBLY_COMMAND="server/assembly centaurCwlRunner/assembly" cromwell::build::assemble_jars -CENTAUR_CWL_RUNNER_MODE="papi" GOOGLE_AUTH_MODE="service-account" PAPI_INPUT_GCS_PREFIX=gs://centaur-cwl-conformance-1f501e3/cwl-inputs/ -# Export variables used in conf files and commands -export CENTAUR_CWL_RUNNER_MODE export GOOGLE_AUTH_MODE export PAPI_INPUT_GCS_PREFIX -shutdown_cromwell() { - if [[ -n "${CROMWELL_PID+set}" ]]; then - cromwell::build::kill_tree "${CROMWELL_PID}" - fi -} - -cromwell::build::add_exit_function shutdown_cromwell - -# Start the Cromwell server in the directory containing input files so it can access them via their relative path -cd "${CROMWELL_BUILD_CWL_TEST_RESOURCES}" - -# Turn off call caching as hashing doesn't work since it sees local and not GCS paths. -# CWL conformance uses alpine images that do not have bash. -java \ - -Xmx2g \ - -Dconfig.file="${CROMWELL_BUILD_CROMWELL_CONFIG}" \ - -Dcall-caching.enabled=false \ - -Dsystem.job-shell=/bin/sh \ - -jar "${CROMWELL_BUILD_CROMWELL_JAR}" \ - server & - -CROMWELL_PID=$! - -sleep 30 - -java \ - -Xmx6g \ - -Dbackend.providers.Local.config.concurrent-job-limit="${CROMWELL_BUILD_CWL_TEST_PARALLELISM}" \ - -jar "${CROMWELL_BUILD_CROMWELL_JAR}" \ - run "${CROMWELL_BUILD_CWL_TEST_WDL}" \ - -i "${CROMWELL_BUILD_CWL_TEST_INPUTS}" - -cd "${CROMWELL_BUILD_ROOT_DIRECTORY}" +cromwell::build::run_conformance cromwell::build::generate_code_coverage diff --git a/src/ci/bin/testConformanceTesk.sh b/src/ci/bin/testConformanceTesk.sh index 0d7d077caa3..b309ffac072 100755 --- a/src/ci/bin/testConformanceTesk.sh +++ b/src/ci/bin/testConformanceTesk.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash set -o errexit -o nounset -o pipefail +export CROMWELL_BUILD_REQUIRES_SECURE=true # import in shellcheck / CI / IntelliJ compatible ways # shellcheck source=/dev/null source "${BASH_SOURCE%/*}/test.inc.sh" || source test.inc.sh @@ -10,55 +11,20 @@ if [[ "${CROMWELL_BUILD_EVENT}" != "cron" ]]; then exit 0 fi -export CROMWELL_BUILD_REQUIRES_SECURE=true - cromwell::build::setup_common_environment + cromwell::build::setup_conformance_environment + cromwell::build::assemble_jars -CENTAUR_CWL_RUNNER_MODE="tesk" CENTAUR_CWL_JAVA_ARGS="-Dconfig.file=${CROMWELL_BUILD_RESOURCES_DIRECTORY}/ftp_centaur_cwl_runner.conf" TESK_INPUT_FTP_PREFIX=ftp://ftp.hexdump.org/centaur-cwl-conformance/cwl-inputs/ CROMWELL_BUILD_CWL_TEST_PARALLELISM=8 -# Export variables used in conf files and commands -export CENTAUR_CWL_RUNNER_MODE export CENTAUR_CWL_JAVA_ARGS export TESK_INPUT_FTP_PREFIX export CROMWELL_BUILD_CWL_TEST_PARALLELISM -shutdown_cromwell() { - if [[ -n "${CROMWELL_PID+set}" ]]; then - cromwell::build::kill_tree "${CROMWELL_PID}" - fi -} - -cromwell::build::add_exit_function shutdown_cromwell - -# Start the Cromwell server in the directory containing input files so it can access them via their relative path -cd "${CROMWELL_BUILD_CWL_TEST_RESOURCES}" - -# Turn off call caching as hashing doesn't work since it sees local and not GCS paths. -# CWL conformance uses alpine images that do not have bash. -java \ - -Xmx2g \ - -Dconfig.file="${CROMWELL_BUILD_CROMWELL_CONFIG}" \ - -Dcall-caching.enabled=false \ - -Dsystem.job-shell=/bin/sh \ - -jar "${CROMWELL_BUILD_CROMWELL_JAR}" \ - server & - -CROMWELL_PID=$! - -sleep 30 - -java \ - -Xmx2g \ - -Dbackend.providers.Local.config.concurrent-job-limit="${CROMWELL_BUILD_CWL_TEST_PARALLELISM}" \ - -jar "${CROMWELL_BUILD_CROMWELL_JAR}" \ - run "${CROMWELL_BUILD_CWL_TEST_WDL}" \ - -i "${CROMWELL_BUILD_CWL_TEST_INPUTS}" - -cd "${CROMWELL_BUILD_ROOT_DIRECTORY}" +cromwell::build::run_conformance cromwell::build::generate_code_coverage diff --git a/src/ci/bin/testDockerDeadlock.sh b/src/ci/bin/testDockerDeadlock.sh index fcf42890165..001ec8db95b 100755 --- a/src/ci/bin/testDockerDeadlock.sh +++ b/src/ci/bin/testDockerDeadlock.sh @@ -10,15 +10,7 @@ source "${BASH_SOURCE%/*}/test.inc.sh" || source test.inc.sh cromwell::build::setup_common_environment -if [[ "${CROMWELL_BUILD_PROVIDER}" == "${CROMWELL_BUILD_PROVIDER_TRAVIS}" ]]; then - # Upgrade docker-compose so that we get the correct exit codes - docker-compose -version - sudo rm /usr/local/bin/docker-compose - curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" > docker-compose - chmod +x docker-compose - sudo mv docker-compose /usr/local/bin - docker-compose -version -fi +cromwell::build::setup_docker_environment export TEST_CROMWELL_TAG=test-only-do-not-push @@ -54,4 +46,4 @@ docker-compose \ # - scripts/docker-compose-mysql/compose/mysql/data # - scripts/docker-compose-mysql/cromwell-executions -exit ${exit_code} \ No newline at end of file +exit ${exit_code} diff --git a/src/ci/bin/testDockerScripts.sh b/src/ci/bin/testDockerScripts.sh index 6fca75869e9..91fd2446e0b 100755 --- a/src/ci/bin/testDockerScripts.sh +++ b/src/ci/bin/testDockerScripts.sh @@ -7,22 +7,23 @@ source "${BASH_SOURCE%/*}/test.inc.sh" || source test.inc.sh cromwell::build::setup_common_environment +cromwell::build::setup_docker_environment + # When running locally, an `ADD .` in the Dockerfile might pull in files the local developer does not expect. # Until there is further review of what goes into the docker image (ex: rendered vault secrets!) do not push it yet. docker_tag="broadinstitute/cromwell-docker-develop:test-only-do-not-push" -docker build -t "${docker_tag}" scripts/docker-develop +# https://www.traviscistatus.com/incidents/kyf149kl6bvp +docker build --network=host -t "${docker_tag}" scripts/docker-develop echo "What tests would you like, my dear McMuffins?" echo "1. Testing for install of sbt" docker run --rm "${docker_tag}" which sbt -echo "Goodbye. Skipping test of sbt assembly and cloudwell https://github.com/broadinstitute/cromwell/issues/4933" -exit 0 - echo "2. Testing sbt assembly" -docker run --rm -v "${PWD}:${PWD}" -w "${PWD}" "${docker_tag}" sbt assembly +# https://www.traviscistatus.com/incidents/kyf149kl6bvp +docker run --network=host --rm -v "${PWD}:${PWD}" -w "${PWD}" "${docker_tag}" sbt assembly echo "3. Testing cloudwell docker compose" diff --git a/src/ci/bin/test_bcs.inc.sh b/src/ci/bin/test_bcs.inc.sh index 4fd7692972b..3c9ad36559b 100644 --- a/src/ci/bin/test_bcs.inc.sh +++ b/src/ci/bin/test_bcs.inc.sh @@ -52,7 +52,7 @@ cromwell::private::bcs::try_bcs_login() { cromwell::private::bcs::try_bcs_create_cluster() { local cluster_name - cluster_name=$(printf "cromwell_build_${CROMWELL_BUILD_PROVIDER}_${CROMWELL_BUILD_NUMBER}" | tr -c a-zA-Z0-9_- _) + cluster_name=$(echo "cromwell_build_${CROMWELL_BUILD_PROVIDER}_${CROMWELL_BUILD_NUMBER}" | tr -c a-zA-Z0-9_- _) echo "Creating BCS cluster name: '${cluster_name}'" From 26085f5833cee3c9091d391102d5d0885a2b7d71 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Wed, 8 May 2019 18:10:01 -0400 Subject: [PATCH 35/46] Ignore that failing PAPIv1 test (#4948) --- src/ci/bin/testCentaurPapiV1.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ci/bin/testCentaurPapiV1.sh b/src/ci/bin/testCentaurPapiV1.sh index 377acd45360..c5a577e0993 100755 --- a/src/ci/bin/testCentaurPapiV1.sh +++ b/src/ci/bin/testCentaurPapiV1.sh @@ -29,5 +29,6 @@ cromwell::build::run_centaur \ -e relative_output_paths \ -e relative_output_paths_colliding \ -e standard_output_paths_colliding_prevented \ + -e invalidate_bad_caches_jes_no_copy \ cromwell::build::generate_code_coverage From 39439f9652c92f6c1135e417cee118af2c4d3a32 Mon Sep 17 00:00:00 2001 From: Adam Nichols Date: Thu, 9 May 2019 15:36:20 -0400 Subject: [PATCH 36/46] Contributing formatting (#4937) --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 64071110f29..230d3212bc6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,12 @@ -###Contributing to Cromwell +### Contributing to Cromwell Thank you for contributing to Cromwell. The project is sponsored by Broad Institute and has participants all over the world, who have collectively added tremendous value over the years. This page describes how we handle external contributions to maximize the impact of your work and reduce delays in getting your PRs accepted. -####Run your idea by us first +#### Run your idea by us first If you're thinking of writing a non-trivial amount of code to solve a problem, we encourage you to reach out via an [issue](https://github.com/broadinstitute/cromwell/issues/new) to get feedback. It is likely we will have suggestions about how to proceed. It's also possible, though hopefully rare, that there is a hidden impediment that would prevent your solution from working. If we spot it at the idea stage, we can give you feedback much earlier than if we have to reject your pull request! -####Maintenance considerations +#### Maintenance considerations The Cromwell team at Broad maintains and enhances the application to serve the needs of both Broad Institute and external users. Sometimes, we may identify a feature idea or pull request that works and is a good idea, but we may be unable to commit to maintaining it indefinitely. This may be because it does not align with the strategic direction of the project, or simply due to time constraints on the maintainers. Once again, it always helps to solicit early feedback. -####Reviewing pull requests +#### Reviewing pull requests Because pull requests require a substantial amount of time to review carefully, we prioritize and schedule them into our sprints alongside all of our other work. At present, the team operates on three-week sprints so if you happen to submit a PR early on in the sprint it may be a while before a team member has a chance to look at it. We realize this may be frustrating and strive to provide timely updates about PR status. From 7353b0eca18a528e445412f9843b063125759ad3 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Thu, 9 May 2019 16:31:45 -0400 Subject: [PATCH 37/46] [Release process] Don't update Agora/Rawls and skip smoke tests (#4941) --- scripts/release_processes/README.MD | 9 +++- .../firecloud-develop-hotfix.dot | 33 ++++++++++++++ .../firecloud-develop-hotfix.dot.png | Bin 0 -> 88253 bytes .../release_processes/firecloud-develop.dot | 42 +++--------------- .../firecloud-develop.dot.png | Bin 253609 -> 130187 bytes 5 files changed, 46 insertions(+), 38 deletions(-) create mode 100644 scripts/release_processes/firecloud-develop-hotfix.dot create mode 100644 scripts/release_processes/firecloud-develop-hotfix.dot.png diff --git a/scripts/release_processes/README.MD b/scripts/release_processes/README.MD index 24b4c8319b2..acc034317ea 100644 --- a/scripts/release_processes/README.MD +++ b/scripts/release_processes/README.MD @@ -4,10 +4,17 @@ ![release-cromwell-version](release-cromwell-version.dot.png) -## How to Deploy Cromwell in Firecloud +## How to Deploy Cromwell releases in Firecloud ![firecloud-develop](firecloud-develop.dot.png) +## How to Deploy Cromwell hotfixes in Firecloud + +* This is just like the main release, except we don't have to wait for a QA perf test, and thus +we don't have to iterate the swatomation runs. + +![firecloud-develop](firecloud-develop-hotfix.dot.png) + ## How to Deploy Cromwell in CAAS prod ![caas-prod](caas-prod.dot.png) diff --git a/scripts/release_processes/firecloud-develop-hotfix.dot b/scripts/release_processes/firecloud-develop-hotfix.dot new file mode 100644 index 00000000000..ed22c730acd --- /dev/null +++ b/scripts/release_processes/firecloud-develop-hotfix.dot @@ -0,0 +1,33 @@ +digraph { + + # Nodes + + release_cromwell [shape=oval label="Release new Cromwell version! Woohoo!"]; + + fcdev_branch [shape=oval label="Make firecloud-develop PR for new Cromwell version"]; + fcdev_test [shape=oval label="Retest firecloud-develop PR. Confirm Cromwell version in Jenkins console logs"]; + fcdev_merge [shape=oval label="Merge firecloud-develop PR"]; + + dspjenkins_PR [shape=oval label="Make dsp-jenkins PR setting new Cromwell version"]; + dspjenkins_merge [shape=oval label="Merge dsp-jenkins PR"]; + + jenkins_set [shape=oval label="[Jenkins] Use dsl-seed to make our dsp-jenkins branch the default"]; + jenkins_reset [shape=oval label="[Jenkins] Use dsl-seed to make 'master' the default again"]; + + # Edges + + release_cromwell -> fcdev_branch + release_cromwell -> dspjenkins_PR + + fcdev_branch -> fcdev_test + + dspjenkins_PR -> jenkins_set + + jenkins_set -> fcdev_test + + fcdev_test -> jenkins_reset + + fcdev_test -> dspjenkins_merge + fcdev_test -> fcdev_merge + +} diff --git a/scripts/release_processes/firecloud-develop-hotfix.dot.png b/scripts/release_processes/firecloud-develop-hotfix.dot.png new file mode 100644 index 0000000000000000000000000000000000000000..6549a2eb2ca11f7a221ae847d5601c7dce31fb70 GIT binary patch literal 88253 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

heH#i8lAAwhpH+pNvilDbUYLYw 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%0D7zoqsHnb64L{FXx#7 z7~zaU2_SOi)t`+-P0_ce#tMelRNQ}U^!BFT67_tlK9q*^%vWYsmZQh7hL0g_3nzk+cWpH*-xyy6|Kiph}EC?O~#$>@k18- zq?(!GcsGr?kqVSaKbB&6H81s}`^*~~uVA2Lfs*VJ12n^1`T&rdiWv@qQUlFhI@taG} z6$MEr+S|--w7g8dUy*G(4KGk8_#6GKA<9^9PfhtQlUA3l(-7AeQ7?K=Jy6!>i}7YI z%%{4EZJwRU4<7c_GImLeOz4GW1J3%--DM!XxNBV^n*U^;H9zzDF;ImllAC6Mg~y>e z5RP)5%m)V6L#bSDwH0DEu~OYv(f3Pfi8%&hnaZ~Mz6fhibL#CEV>S`y&xu*jEyT2Q zy&wro$|NObe~%=U=~x?UQbR!rmmz$gR%= zeasF=@j?@`)FvKMuV92}kOv?opUJ1I2oP^5Pbro##jCwvR~tYLvnaEUvMsV5uOvBO zq4u)3fApH~YB!HmUs_9(Fqy0Ulv`xIGGTtQBxUKSvBhV~hK^4n_A8 zLZNTGiJ(ny-cK%bcS{>4#V`Wtq8G?hNeLs6zag!=Mu(Ue^_kdMn=7& zA((N@1W(G7$6HfiqN@q{X#0FM*EDH=BXeE)wHlvxt;JrcO8+Tg2GP7sdeZ8QOo86e zmwu-Z7X=NbN~6n>Z|`kmPia_#@%`@O%Dx@}`O_Bv>wXRRuh(4JYE?k9*z4RfJ<464 z8RcUtkc3W2#7Gz-->>p6RX?IL{XvNjcEt+yPzJyVF;Q zcpQ$e&5(=atH7^htC6E3z|b<|flKp*TC(hLnNYK^QRG ziL@gKDU`~EJA{T)QG)c%f#U3AT%W>wyctXHLm^fGLs|JjV)SDsYli^AKn%~u;<5>~znG#XQQ6HCKH=v21MgQqZ2=F!}QP!Wb+T+Spb zCeeh$+o}P(bRK-eD1=Uj_yGBxH!1w^zP=!O^x#nDuuQHAsSRR}7*wDJE=;7A35A1d zKSWA5wau)80mP`f(CjknOhOm~tFo9PHCfW#4bAa({TujqYqY}dF!h)0$Xd1xzt zR;~<{?xBUDsj21CUFSUpvb6Ue14Xs5{pKk5IQ~ zKMcRdVoZ$NFi*YMA#NOaafDO&z?{RKJ}uZ?rSaVRYW#Yxrhe0dR4&+$ zO?qMm!gcI}fW++t;jrJ6phpq&5vYc+ApLWK5fp+L%BpgQ6_Z#Zw9kM0P%WE^SS6Oy zw9}}4k*{myW^zxpTuVIG{YxL{hkshbPSbyx@c6yH@$JHgTHospuSKs}-Ag;>nMlRc zPJ9uz_UV1hojX78U{e-SF)lji8b{#r-+?9Mm?AqnPXXHakYZGDl9K7uCmA%pv~Zm? zYhoL7dpG=jFbj*N#(1xd6u@JUmapdkoi1j5*aLVXSBRQy7u zncy}A1dHrH1AfD(@jjE5(2PAli_fC}bEE)sRNH6Y?6-zUGUl8srgu_JpaJ@g275)Z z9}U9=z_-*u;{Txs=%>oZ*uMR(tyFHW+*UqGTCrurn!DHWmBrFjxxahKR5KpOmi8dZ zZnlQG6qWM_;3O@9k@_ImkAu6Pr|kAl%vD8ehsR2*xmrSGtVxHYr+pHUoNv5KM0ceX z$@#HKr`h~#9Yw|{pfj6*{L@ySP9S2ysZfv$LPL=-ID(>&%NElhvO0MSJ8s!d_YGDo zT5%*I5~8DsulUMjCHL}TWS!<^IBqbJ$dka(S+i=}cM(BopuZOr12^lH&*?UD#nWwDS1*j&;!S zl>egX1{2-$f=-?^nP|>0;^FCHUP_{{9ByVe-p>Op{=9@tR-fd zs%emE{)$Lejy{5w_l$a#laP?PpK&@YlaW=i%nnI#)c1@VLz&3f{j-83ju{feEHd}H zpmh!CTOIE7oaA|GC8cXXWmbSsQwP?5lBSQ*F_XcXURiB7}m zZLcxx0X}gn3%L}yFw$Ru`iPi$sMDcls{4NInVpqs^?d$ap__1Ne0m0;%9%o%cr_{A zrhz0dNaz91{|M6d!?*!+BD_2Os6J@=G=BwsEO zq(|-!6xV&^pQ4Qc8rA8*7!cjzyTY<`bL8218AXIgDsMjrFa|vK^R8ZG<^#mHFZnB? z`$$JoPx7%2N?#QJt#I)PP=-$>$-4K$_{c`IQsRPc8o?DZaTaedAgANx$@-aJOOd!x za2u}wUvmw}-qj(J7!-UGE@ zRWRgbXamVU~5fp3ni%hABz* zC&|v}I{+2Sh3sf}8)t~lcyfZW>|*#8bPUx9FqG*Qk5ZhZn4amchX~8H+W>(JKvzgQ z=nV8DQsblnmGb(g$yI&vucqZ&+$z{K&OyYClPB7sm$ncOm$vr@BUew?n@GaKS|Ep- zKLJ#JY(a)}wG?%~xl0~X8^JwMH(}SU!!e3T&6S4nxkN?b*Vv>@CUCX<-oGE0Lu>M{ zwBCWY+DYF=c8x63@RDsgOHyupvbpzZXVDA&3QxYeGLrEtd5rK@ljx8B0Qw*{3ohFO zFe7j1{0tOJM?0C)sNs{`XN?;C30t(QRf%!ZsWnY`Cn5Ce;InFaCd;PJh#@jN!uxpqjn=B1|$< zQdz+aFlYf3^GBz``BbuoVk3I3?3?>aOAsh3pY>zKJ@?ZzB-vDnWu@k8Te-QjueJ3Ko%j6J#@Qk5kat&SWmo%gw6aSHas-LhBL&So$#(XH(h4b< z;cw$5r*9?C?}iKI241cOdTKj-qmAAp!h0_q+(FCn__{MmNHlLc1pv7uYf`0h(4*)x zLY9uhb&dZv4iFeagwm=9K+?;L^BNgYol%d*!m>mz1q13%g|m1afXv<*X`D&}Fq-eV zRgXi^h7p$sQdj&pf$S>|cQ2&EW0<8Z*!Vc4sFW0A>={~-57hl01G>eVMT1v6R~J%x zCpSsUtR@|EXG1==hEKNq9%%Wv-M$`R_w*?P4-L%u{W0+GBVWAc0jWL@VuUcNf0cV` z45C+cgaLppd)?La+=Y?dKo{BMTARtxf|jC0p%6s-u`HA|7A5O$nmQSyxSBM zVpR|tob7H)^iI)0F-2+l^m32xs$y?j|BVcCWsrZw{YDl=-pvOWnRxFZ!RcGO{T*Oz z>@M`Y1>hCPlkPt#Y6holCI3_qxjXJJzrZ^x7L9HQ=|AT|9>mMHQ2D&1yd77Ln~kY8 z`|32i!HtFH^c=xZ3J_pfPZ$I+t>q9 z*s$A(oe_G+(fwY))}Z;0AL%z!nBvUE-s4Ry2^yQ{F6aH?={4la`=CcG$G?w8y&>*@ zV|n8CBu8Fs>K$RAH9SV%yo)}W9fP~8)40?=cC#& z>Z^n5Zvk$xJL}w+i`>8O;Qy%7e~I1gBiux3`NMP21DY~JL!rxWKoZn6r-|a-r|pAS z?WqB)H&=NQZyaPgP!(D&?GXAc#j?F{CKtAPyUe?bohBdHK;35x|Eu%)69rnHRcac` zREJqGFJ|t80=~S zGIHzPnFGO=^q}FeNNMDaMOc^9n_~zBhN#T57FpXzqfe8 zS^Ld3Zlcx6Mu>;#qZRDg{LVGHU($NDca|!R=d2|*4S-%pL%nAW%LwKJNEv7#caDa= zS)H-^;nkt$)i_`K2P@*rmxZk#96|5?<m~Tk)_o6)NhTq`6aVpEGmTt0bxI;_lsG<8{jH9U-4lP}@{j zc!y*y?V;sBCU>^pvvwl?>YQxU%?)o9JmQ}N=wI?8!sV)z*2k-Wo{ zWvNwe`AL!@#s~YH`O2>5smBtY6B+88FI57bX3H7HIIRQyc&D`dtsIF;g_4()kvjti z9tT-zhxI9_fJ&1#wc~0>P<$_*LZHqXgODgrEplNBl}SG82VJMt*J`9Uml{C3V&vTZ z_`0aO*lHyvA)VYO?0Y9lobKo1Z@+P;yWbnIuYa1wyQbwIcBWhQGTZb$wWAAJ~pO(B<_?}BId-OqmgQx{rg2}t=P~b*Ub9i#&~jk zyV&s_(jS-V#-QE}2Aj`#4Q+;ib>d z!;8my<&#p;)Cp|N0uv7bzW-58zu*g`Hu-wcka&mI&xv(rwU0jY^3FU0z5S}eTC3uZ zeBO&&zwjbBr^f2>)E~wIlZ7#8J6Rnb?j0ce16G91F=8ST6wsY+i&A(aq z@&q&Wlk=|%0f)pMoeV6p9}g~gByE+ru8vHuK5%~`r@UeIc7``a#ro7O7U=>vRL_n$ z{=~!_5#+p6Yr2;rI_9+aNll`<1{6Vacwh6`DJRmJY92@_yGHtzoA2cr71Yz6tks)^ z=6O^Zl6C*82Dk5QOf2G^>JB|Ba$fvahucBLIP~S<=)4)y=z7HL^CPAx@8T1mLs$Ky zZ-?&ZXe6Q1CJ5=FWr-c1_~L-{Ty2wm8XacAL@u3^-F^Fza|OpR?@LI;)4WAxjCAJh zM-&C$Mn1EZK8X`hd~?gd>WV{mnhf%8^-4{X7*UOMU;R9M^|5rqPHfFMu)DmXsHmkN zFlnZXiRiGS217_uZxp? z{Y*|EOb{*JF8g%Qnv9ifV{kG^PW`#o(Hwc5O{XXRIkn=2KZ0#neI^9-`g!*pv2PY_ z$m;su^PshoGs=d5N@Uy7JE==_jB$RM?OxHW$ui+U%U#p~%&IP*O|`%BmGT2ZbUPZ0 zSU&SEBVx^EGUEq%^OPF4t9+eR+#klZONW8b>omq9k6dgDa@&0;&)citTB~PC1X)ra zpV;f@*av@J)WKNgq@_QzsL96{IGmkyy5jS=Y)Jo^K?)}J{n%$4uZlMUs_va^*L=Fi z!sy^C!B)sTm6^k(WvnxCPKultZ(+;aU+S2n{YqgwIU-PS{@rgIO7@DmBhn~WHMiTH ztxvb_zPYf7N2@v7eW`Y>5;Q}ebsg}yv?c}@(ly+aW4<7y$0!ejQAW=u6q&LKMA>Nj zI|i--q^%e48_Ux_>Kyu>i*u736SyE;C3^^^6p4&`hq>CNDh<+@sVQ68*fzmIv6 z+g2UjNMe5b8B^>+w#jJivxvZ7*tMKCM3y+`GB8m+_`Re|HMNK344rk8!V6Q*%;+Ld zK_$mbJmJ+|@iVQLSZ~)5TSdkA1r7_sz10K#M&cc{NzCseb#XW}Z4bo0opXfnJqpuI z7@o9Up2C@=!lh=S@LQRT4>)8zNw9q)8lZ9iTxn8s(I9PbvF25j#p$%@Rt09n1RKUR z->n~J9qh;a-+4W-q~>r`r3w$_@^2(wHx_3Xyj?y-db~~_RNms=j>96%8$Lv7&*DNO zb}v&Z63ZjP@v^Ugpt> zSKx^kKl7W(zV^gHNN9c-=x``R8=r8Ff4BuM9(uAd-tAIl+?>cf&*S;wm!h+vS@Dh| z{!=hD(3t#ob~e%Sb3Uf9vOz)cM*e=y1>b9$)37ae{}px<7hnGuwYHZDYt{bMCe;SjS%B=X z^JSZQr?`WC8kd=ByyG6Ow`hLWY6j1j4HU_<`tI7W>kMaVaQd(*2CWI>I~N4|MAhfR z6#2W)cxhw8Ugw?G+WGFEAKPW@S)}O{Gz1!neJNE_?>4-!e*|8Ia&tpEX-WMROHVMS zZsYUH-tH##gLd*q>icXSRAnotoyTARzi>eycwByMgd+3IvzH;_mwx(Xk$j+wEv>HE0ZDaTFpPBS3ZzjcAtUPfNLi8v)5nME{Q&OW#w)n(ToQnbiDPsI zSt`5?^t|fRLXxAm+%7$L-uXG^dySlqvz)0mU)w6W-=nTQzv(^bw6PDmuDo&WrF0H> z{*v>&9xp{ZQ=78X*IiFpE~G5XyFWZmr@sk+dY4ALwMI&V79ZL4_Pv%qH-U zG{&F5Z{xDRZ7vnmC=!=W)Ryb}@G(V$)w1P4Z*2Q38szTR&$U{KiVh^_qKXM0fDZ8u zZbmi@U36X-J7qjV*HRgYF{&Qf593(u0jxE5y~%PsRyntp${ns3%{beis2RpSrCCvg z(fW!pd#p~R$DHKo*SLN^kG@%hGGZNfDEGI`k{N(MkYdsM(#DLv0{0->{Phs3IsHw< zg-#$?{I>wIoF4^NL7WqBkn^mQ(mf8U2H1-`IOhb0F+(lztn&Q0*OhG^f;N!@G&iWf zeXy_malWykWT^B1+B)xWw*Pnk8>u~lS~U|IwMEs89fX=iQPiF_iW;$Jt(c+qsH)wf zR8duGt37JfUNvhJwa?q<^F8PHyRLKcU#@qq_w`QlTK9cFpO3qfSo+ZUeNBiF=>x%C*Rb4I#wSvwIvTh zbNA0n=&Am~F!%Pn9onC5v!0F-ls?`4OF*DwJ_eu)S>MX}Mfh?a#Wc2_uP|YP&zO$S zv?Jwdqn9n%QavOCNIjiZi$&uvviv!+(;|Us>kRRVs^Yc_>wYCc<1pOJu=vZFb+BPX zjTas8DdeMT4P}<4C+@gXvshM%pA_}_kHzp_760`~A(7A@2W8*iEiCKvR+#M>C)%2hb!o=e`_TP)*4sAx^+{WH7>l{W z=*7%O=~Zu=a5WWun_X2#r^R}I^nTucIls{D>#be02~AqY6w%E1hRx|$3|z`Hx{V!} zub!*x?TwN{D?X#^J)}08DnD;i{Qg!;@&va3!_o}9|0I`k24lp?o0!I*@5dIdN;^$* zJX|bnm)~C-5jbxbJE|mF_jg>FP~0Up6W}?h-S3M{?~F2|DeFy7vmjHIZe`_vo#xx} zz@HvuT<+@MZ|QJaArr)lfqz^5!giF^ou5XCYm)FG2?FROF`v{y-^s$C-3Jy5EMOATJ?H!&Iziq1uKm2N4oK`FPAfE3e8kETExLF0!*`C1e281mWh8qxdlR`00hwKVmoN@J5FE zlO9!Gp=g|Fy=C(GeP$@K!EzDGtR~WvcZ(@ED^L=TCiZ6f5G{e#pQn6VqS#>w3z%N_S!?v` z?|`4tMjgq6uq19xjQ+fQF>;^UEwIN0J>GJvnf`X*SN?+X%+zagD9bUOI5p9 zIn$;0jB`a9Jv<$a8zB^1z4N6MFC+&%9-wnCPOYS-#p$v*L|7_2PLmYCI-d;xM)-3g zXD#QbNVDNCtpxCpeV%1Mx^GY+%^CAB@+DVZE(!=8MD?Jus^WZJknc`xHS0 zqlG*a5wm}91TY;k%yRn`@VfAowXdqb&m=<19XL*}H9e!(-@5iF7O#g`ujH;a80M;c z-YGl3Zdqoe^LTx2@&O`5tKn({ED_Z^i@&z=3H#3Tn7m2ZUy z{%mD$Kpam@Oc|5x@f=%0V`R`9E5dt)ND_P=!l-?#HxNC~26|B*r?&&&t-^NeYpr@8 ztb%QpPsLs0n#;u|q-lQBV$|;lSTJ$o@wAnk>&-@bfQeOdtbmXj^AMq=&q(_S+2u^@ z-C6Awq+Ko&3UdZ(dmY?AiW8jzx6iWYf~l_mfx#P4hQ5OX47}D zKfH87C2#WyXy?`ZrAnHRS@M3NnG9tH{#LbceP>iQ8~S-AOgth}0Cs2T3CHt>pW`|H z9OtiPzj20Z`21v6^y(J-&QqyzP^=-(AM1suFNC%IBU+v9J$_xZbE`-QBmUxn@ZS9; z@u`(Yp6^MMcG%-~Z>cD`Yv)ImH=GZx+SepCiutO3!z)tV4|Ps*Qoq9AKT8fWox#rg zY0vEJ&KKsC-7x^B#L-CIWI;a^EOcdW5U9DUM<~g{l>i%TL+>d3!JWlS^r9_%qx^r_ z*>ezKelsQvt01$iv0iynLY-LP9cR#Y; zX1uV#$09(TgH%rP{bGtaDY6(wVg&wC_>~_Pd>NDqtIL{|-LPLTF)h zdOhIpU8xVwSJ&UjFyZP7mJsN&Dqzt_6DEh^#;Fyf@@9*J-$lDfbfd=1;_cHHz9-p{ zcSH}h`|yPX-bk|xiNz0b<~1HmZE~K%DXawA8HzVE>(@n!VEQ@fQp2LDVNq?u(6f0O zyh#_|8zd%js6ug>ca?8R|QZ=(@xTzmN4OsEN7#S%31V8(-9#U7ZMKl*V>|YhuD=;}9x+EaXhv z*Q3Y$d~c533OHY7>jVQe#8U=__F%pNT;nJkr*ZA(pO4kE6=S{#3sGd*pH?4;ifZ*8 zx}v>TgLWf#G`CN+wW=p<%7dN1#`UVN`EYrL%CpW-=6_@L+g=pa`?fZazdQP@d5qF2 z#`06_Eo!=Z>|xbtJ=)Ab(tXqQ9TVWpbbfRYCvy6-Cu`kWsN3(RuMiCl7c3Rw@fo~> zH%~dC{}J-Xb#>@=@qwqr(^V_*OLZJ}cy-(K!+YymOkLmbelsz}CRR<_krTo^Pwc0k zR^`i`n~#vsiH<0{0NQw$;&OrO2FXr5$ovk_4p!6|w;fSk?nvM=$p#C$# zu4V4U=Yvr%?=pNo@G!wgrZlmDT%W5lk%Y*2Lr5?m|bQ2k#lLAtY{fmYc>h4)F0uX_6KZF#}wMr|JPd zot_sK3INquN?Mo9(k&kx77YBw2AtMq3Mh1jX$)hek@MY^&TzYI?K7HFtNd&LW{*1( z8Xd$6`MB)M$vG3@Qe_uP7Tw!IMoqE)0PgcE+-CgJ$Y#yA=JQH<&8dV&m8EtoPjjir zdfKr~7Oi`>l(skH3Y`5;8jif{=H70TZKBOf#LGzq#}|6|_;9@BW>n~+$lc~G_l(b{ zbiET^j?3a-(_9OC#MI|da)$%`n(+@k&6}8uA9_LvO8jMarW4Dy?80>Q1GnGoEE%m2 z=FQtf@=aw<10U}jeX$F{Z=3i7M8WEuvc!t*;B7ci>NaG_s zL#jg{ z()C`-bQ#qY_w+saNf{|(e~^^f??AsG{~Ti0?VPOj$!6j-2pN1!pGXm<&%Ce1cF}eJ z5w%-YD+T*M>lkNWVEj}5nar=86*(d}RsjVmNpo4AA+wKVW{3qDT5jw*2DykFL6ZNR` z8lFBi5bHd22qIq)LD)aZ#y#Q=>W)FQmsK&A9L)}kN0f<4F@k7(yrLY&B-yz=sD))C zJ^JTjDN7jilnOOpo`G1S4qJ})<3G3NYG(JqkC;DxHMv_!^Xl}{Dht=##2|vUjKB@? zzDKu^i%9*^OoY!sN!QMRRZuol>69HW!HExqsW5V>(cRO7Y%nZQkW9gFiq%@~1Ih15 zaFo0P`9Yw@K~dbYU-U`bm<@_8|0THWXodE-#K6v)aS6{uQ>Ly{owS*cV)|&&)0Yht zs$#j68?>B)_4J+)18?s2gm9?iqmri|pDFfE>;=_{>#z2dodX2RInYZK(Macjs2qab zC}9luQ88-rZ~%UHSRiE!S$w$UKX9KZK3=Mw00u9Kd_&z58clQr>PeF)=%gLvhn=I0 zAQ4g4sX(BSFFEiRN{LdV@pTY9@Gq+)+|M~0Io3;!3qYw^D8C$Xfn)@-rH&Ep>{1So z@6>;s|lk4P`j z#;UfPErqTp3pTKGBi&LGXhYgW4*e?s$3SPG%)PxDwcq?ONO@j8O2W`&rc_{`wO*$4 zGR&W~*Pxm0Tr%Qm?^vOWXunt$Cgni1R4Ql9+;eaiRIK?s_kxjYh*JA0D9P~Wa^v4C z@nTh(>|#J_(dmLhFLmU=xjgXCUYTSDD7!pv0P@H4;1(iY<%xX;TiQz3(_<+P1P^1W z5QnjDm}f%uVqtg~tS6!sk+6?o*ysWy&fzJ~>bQS$aIwa;>9Wl&ipNL(L@dyLCkWs5 zB;)vI%QPsBTFmDRrD6%>KWS-g0c;dItocqj;9|WZ<)`(F<{bMm%cAguCZTku)^7ZH zyBppnWnoTk^ud9;=sgFEswtQff3AdB{b0}lTt6P_DrCtvU<-XNO+n@)_v^D0&SJV{ z1>Kt?amuRH7heYo?fQoB7 zHrBfRlnL@`q^fDDHm>len3-Ji_F&AAL%Q+rfg&TBL(7fo9Et1r=ne;aA z_<;NF6E_SpO`_$6|Jf)Ev;Rt68lF0_pFgo#lR z8h4maW4i3z2d@u5)_t7)W^bPEF3GyCi7Tk;_a`NR!S||HwcescPE!hzW{Sotb3TGj z^gxwW9K<0AWJnC@r!4m73qM?@tM=B@|5{pLb{?{wQAn0T3-_G9P7OUl1G;v!v`8xQ zq%+zI2P?w(gi(1zc`WtdZ>;NBxBkhQW4KD}K86bPbQ`v#g#8`d2gGkhxJyUJ4=T|b zxW+djTrQMjrb^XUrLs3$YX%wQjMRyR-wj|2f#;S^`F-Azy4utAzSi8)%hhrc`zO3} z3i(;x4zVcKHn`IC%pY~zon1kf^+qkzPKz_k4o3>XTqU3KuG2eL@;{;ll8s4UlN38- zIz5~G5EvSa$RCgA?IEK?h`S!-Np+d~&&3~!5c_>yLojivT*6ar=O-RY?~&H9anM~f zpQf`49;HNh354<&oylOS4^3f5yVr5-E*a0mrJgO{F*b3Wg1$=lzIDdEhL`OsiV4V- zGn?8#Pxp5eU_2duy8CeB;4nz3BIWhQFBzn?2@uvQ`8$?oeKkM``2Ig3;IQku=8Md+ zn}kQNaqg$8N)oQ)_1B}H*G*_T_u%;POpFHUBJQLr`u;OinJc=i9B%x%&1jVdT~%h% zYQenwT>(Xq2mGb491H-o8hZ^Gp(v*jMwpQ&Y^6bJ@;y{T$ zHo#{yq8_7tqMqHh5_v-6&>o;pGC<5fC&r-M)(3BbW$C@Pi}Tjq{eGI#^H{_mPw7o6 zVUt*rSeRosyq5so%^uN|j+8r_a;%(ZgC>T+$XSurn&E4wouby7S5MRB_Sm2}zl&`B z-agxgXfm{?D*gi(_4ctfmUhjU{yHIB<8VJroM>r?vnpGtc3drJoeVW|i|7}-CR&y( z@BrY7PLbsKWv?<9sB+gAkBMu}Vtz+@cbqf4ZCOZf>{^gyV<#)eBvO$DmS8;#`|`iz zw=xxygT72X;B!$m#wk^7nSq3PHA~EiIaBNqo^MSgav8L>-C> zveonL+n__D%3s^fWnh(V{^{JS1r>j~U~y7?#)I`~jKe1~iC2jS3p-SyQGxDH6lmS- zz21I#zo#LWFE_UO`u&$*>)xF!+@1Ed96sW%$+GiCKqx8uPaH0Q9J23veE0_`<--k8#j@rmlvC~BylapC7-QnB7NagPSrBT*b2pY)EG8}E&0gMz z>cqbKq{C7(f321NpWy+0xB4(h=dZbGA7@3aY?^fr!Y@QTmGkJ^WzHUV%GetJjKCKk zWHzm3U)CTW>9Pf+*2_#{GovzMLJr&Umno{>{g~wQzIm%%>l;*}Wlz&(^I|{xHTwsh z2A(!y0y7)6=KG}Zvp`|YQPlYMqU5KlljsU~(o*vb& z!=Ni|N<$MN4K}mHh12~g4XOTb{%wHE9HK4xju7;#HnHAnuY=@UAvwl@c|$xs+~7fq zej=Pbf6)@pDR=ORIy4cH-&$;;_=F3O8eZ|jYhS+Wt#q+QF0p&KT@lR|Y)B|;`Ul32 z&up;f?{|6*H(rWkwiskru_V2j3Ohif% zz99A$$UoAqMwm|exkCBND+2x+84QQZlb=sh07Jw*JkcICq%Mm(>`C69rBgxcuC^tR z>fuFLk=KGb5*ywXHKIk(WRVG(?7PoL}sHVXKsw z=}#YSeuy9`4Yp~{faEDscP{O*2TVQ<`0t7)wwe~9=AeKNxb7cXYy3bW=O24GDQ2hI zpFbSAa8WM2dp~C}sl2Z?T1upaih)%x0nqz-lbAb}?GANt5>O8pn$5Zy9!kY^W)!(+ zsV_q_;+Cw7ZYQfEIdh$p!%8(^HE%MV7Jss&rk5tD2AW7`-KeVb;dtVQ9O-AxQhN7k z{k*|;jvX5}Mhgo%#4p=gxMihf zd!Qjr1cA!vFWV0gO7&kLlIuAHMZegWu!0X4E*i(V~u%J2m)$C>0PRE-E<=y4I7moA%7j%v7866^4|ki~1VwX5{4)7hzaNK{+kx6-CY zg<#5Wh($?89By+*m!515@g)1s87|*jcbao)U#W7beJ%q_HgMqCX1R4rsK;C@I(V6GeD^&JE~p;5=(sJ_+OGu7D<;7l6u)(_G5x+z|FSA zES;-8oaa7IZLJ5Yb-xL#3>|=vHC}=sI7arlh*5wIa1?%J0TQ7zR1!d~w_yPsO-9}?npiIAL? zMriBTAR3A_jR8W+x;i;RPIK+@C-`P0<3fb4h=ja= zm2YMgaM!FQIiDF6psl@^5{r>bY+22GlO0f2QZ#@?c-=)c0i^*LSo)p^c`XPENputv zdR$Yr-zJwvNioj(;%xZ0iljrkY<*9Vk*@+oJLCq2u;A5DWr}CyGps~^_b_)2xd_mp zw+E7M`b?jRA^NEWQBU�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 literal 0 HcmV?d00001 diff --git a/scripts/release_processes/firecloud-develop.dot b/scripts/release_processes/firecloud-develop.dot index 48a077a8c79..b502c23cdeb 100644 --- a/scripts/release_processes/firecloud-develop.dot +++ b/scripts/release_processes/firecloud-develop.dot @@ -4,20 +4,9 @@ digraph { release_cromwell [shape=oval label="Release new Cromwell version! Woohoo!"]; - agora_branch [shape=oval label="Make agora PR with new Cromwell version"]; - agora_PR [shape=oval label="Wait for PR to go green (jenkins must finish!)"]; - agora_merge [shape=oval label="Merge agora PR"]; - - rawls_branch [shape=oval label="Make rawls PR with new Cromwell version"]; - rawls_PR [shape=oval label="Wait for PR to go green (jenkins must finish!)"]; - rawls_merge [shape=oval label="Merge rawls PR"]; - fcdev_branch [shape=oval label="Make firecloud-develop PR for new Cromwell version"]; - fcdev_submodules [shape=oval label="Set rawls/agora submodule targets in firecloud-develop PR"]; fcdev_test [shape=oval label="Retest firecloud-develop PR. Confirm Cromwell version in Jenkins console logs"]; - fcdev_wait_for_agrawal [shape=oval label="Wait for post-merge agora/rawls builds to finish"]; fcdev_update_dev [shape=oval label="Checkout 'dev' branch of firecloud-develop and 'git pull' the latest changes"]; - fcdev_undo_submodules [shape=oval label="Rebase firecloud-develop PR and remove submodule changes"]; fcdev_final_swatomation [shape=oval label="Retest firecloud-develop PR. Confirm Cromwell version in Jenkins console logs"]; fcdev_merge [shape=oval label="Merge firecloud-develop PR"]; @@ -27,47 +16,26 @@ digraph { jenkins_set [shape=oval label="[Jenkins] Use dsl-seed to make our dsp-jenkins branch the default"]; jenkins_reset [shape=oval label="[Jenkins] Use dsl-seed to make 'master' the default again"]; - fiab_smoke [shape=oval label="FIAB smoke test"]; - dev_smoke [shape=oval label="Dev (pre-merge) smoke test"]; qa_perf [shape=oval label="QA performance testing"]; # Edges - release_cromwell -> agora_branch - release_cromwell -> rawls_branch release_cromwell -> fcdev_branch release_cromwell -> dspjenkins_PR - agora_branch -> agora_PR - rawls_branch -> rawls_PR - - fcdev_branch -> fcdev_submodules - agora_PR -> fcdev_submodules - rawls_PR -> fcdev_submodules + fcdev_branch -> fcdev_test dspjenkins_PR -> jenkins_set jenkins_set -> fcdev_test - fcdev_submodules -> fcdev_test + fcdev_test -> jenkins_reset - fcdev_submodules -> fiab_smoke fcdev_test -> qa_perf - fiab_smoke -> qa_perf - - qa_perf -> dev_smoke - - dev_smoke -> agora_merge - dev_smoke -> rawls_merge - dev_smoke -> dspjenkins_merge - - agora_merge -> fcdev_wait_for_agrawal - rawls_merge -> fcdev_wait_for_agrawal - fcdev_wait_for_agrawal -> fcdev_update_dev - fcdev_update_dev -> fcdev_undo_submodules + qa_perf -> dspjenkins_merge - dspjenkins_merge -> fcdev_final_swatomation - fcdev_undo_submodules -> fcdev_final_swatomation + dspjenkins_merge -> fcdev_update_dev + fcdev_update_dev -> fcdev_final_swatomation fcdev_final_swatomation -> fcdev_merge } diff --git a/scripts/release_processes/firecloud-develop.dot.png b/scripts/release_processes/firecloud-develop.dot.png index ccc6138b70c211b412b2bdae1d5183aa07f5fcec..1dd2a82abba18f0adbefe437fa71c1c7a2174ec6 100644 GIT binary patch literal 130187 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 z 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 z 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 Wjtd{+OC#~XKiPW^rAwrY0{#!Kwvs&n literal 253609 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 XQf8G$LV=^4a;yz5HGHSYZ@snP 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�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⪚F0{=-&Kb4v+PRMZPO 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⁢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 z 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

4iA<=FOxS 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 kYybcIpFc@>)?mBroIMQ{lta;2i`(iQvd(} From c06ea7cf5d62843ff787fbdcfe9ff6273a6d0689 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Thu, 9 May 2019 20:32:32 -0400 Subject: [PATCH 38/46] Stop excluding AWS glob tests --- src/ci/bin/testCentaurAws.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/ci/bin/testCentaurAws.sh b/src/ci/bin/testCentaurAws.sh index 95d8ae8da0c..e6f1a668b27 100755 --- a/src/ci/bin/testCentaurAws.sh +++ b/src/ci/bin/testCentaurAws.sh @@ -34,23 +34,18 @@ cromwell::build::run_centaur \ -e localdockertest \ -e inline_file \ -e iwdr_input_string_function \ - -e globbingbehavior \ -e non_root_default_user \ - -e draft3_glob_access \ -e cwl_interpolated_strings \ -e non_root_specified_user \ -e space \ -e draft3_optional_input_from_scatter \ -e iwdr_input_string \ - -e globbingindex \ -e cwl_cache_between_workflows \ -e abort.scheduled_abort \ -e cwl_cache_within_workflow \ - -e globbingscatter \ -e inline_file_custom_entryname \ -e relative_output_paths \ -e relative_output_paths_colliding \ -e standard_output_paths_colliding_prevented \ - -e draft3_globs cromwell::build::generate_code_coverage From bc2d946204f70409c8483ed4447c5d1857870db3 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Thu, 9 May 2019 20:34:15 -0400 Subject: [PATCH 39/46] Remove travis workaround --- src/ci/bin/testDockerScripts.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ci/bin/testDockerScripts.sh b/src/ci/bin/testDockerScripts.sh index 91fd2446e0b..467f65e7a2e 100755 --- a/src/ci/bin/testDockerScripts.sh +++ b/src/ci/bin/testDockerScripts.sh @@ -13,8 +13,7 @@ cromwell::build::setup_docker_environment # Until there is further review of what goes into the docker image (ex: rendered vault secrets!) do not push it yet. docker_tag="broadinstitute/cromwell-docker-develop:test-only-do-not-push" -# https://www.traviscistatus.com/incidents/kyf149kl6bvp -docker build --network=host -t "${docker_tag}" scripts/docker-develop +docker build -t "${docker_tag}" scripts/docker-develop echo "What tests would you like, my dear McMuffins?" @@ -22,8 +21,7 @@ echo "1. Testing for install of sbt" docker run --rm "${docker_tag}" which sbt echo "2. Testing sbt assembly" -# https://www.traviscistatus.com/incidents/kyf149kl6bvp -docker run --network=host --rm -v "${PWD}:${PWD}" -w "${PWD}" "${docker_tag}" sbt assembly +docker run --rm -v "${PWD}:${PWD}" -w "${PWD}" "${docker_tag}" sbt assembly echo "3. Testing cloudwell docker compose" From 1b8e5b3a028432c2e75a3a4d6c7387e11b162045 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Thu, 9 May 2019 21:45:54 -0400 Subject: [PATCH 40/46] Safety net against long running "log an event" actions (#4947) --- .../v2alpha1/api/ActionBuilder.scala | 58 ++++++++++++------- .../v2alpha1/api/ActionCommands.scala | 6 +- .../v2alpha1/PipelinesConversionsSpec.scala | 4 +- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/ActionBuilder.scala b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/ActionBuilder.scala index aff561a57f8..4597e2c9af9 100644 --- a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/ActionBuilder.scala +++ b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/ActionBuilder.scala @@ -12,6 +12,7 @@ import mouse.all._ import org.apache.commons.text.StringEscapeUtils import scala.collection.JavaConverters._ +import scala.concurrent.duration._ /** * Utility singleton to create high level actions. @@ -43,6 +44,11 @@ object ActionBuilder { def withFlags(flags: List[ActionFlag]): Action = action.setFlags(flags |> javaFlags) def withMounts(mounts: List[Mount]): Action = action.setMounts(mounts.asJava) def withLabels(labels: Map[String, String]): Action = action.setLabels(labels.asJava) + def withTimeout(timeout: Duration): Action = timeout match { + case fd: FiniteDuration => action.setTimeout(fd.toSeconds + "s") + case _ => action + } + def scalaLabels: Map[String, String] = { val list = for { @@ -118,12 +124,16 @@ object ActionBuilder { .setCredentials(secret.orNull) } - def cloudSdkShellAction(shellCommand: String)(mounts: List[Mount] = List.empty, flags: List[ActionFlag] = List.empty, labels: Map[String, String] = Map.empty): Action = + def cloudSdkShellAction(shellCommand: String)(mounts: List[Mount] = List.empty, + flags: List[ActionFlag] = List.empty, + labels: Map[String, String] = Map.empty, + timeout: Duration = Duration.Inf): Action = cloudSdkAction .withCommand("/bin/sh", "-c", if (shellCommand.contains("\n")) shellCommand |> ActionCommands.multiLineCommand else shellCommand) .withFlags(flags) .withMounts(mounts) .withLabels(labels) + .withTimeout(timeout) /** * Returns a set of labels for a parameter. @@ -170,14 +180,14 @@ object ActionBuilder { pipelinesParameter match { case _: PipelinesApiInput => val message = "Localizing input %s -> %s".format( - quoted(pipelinesParameter.cloudPath), - quoted(pipelinesParameter.containerPath), + shellEscaped(pipelinesParameter.cloudPath), + shellEscaped(pipelinesParameter.containerPath), ) ActionBuilder.logTimestampedAction(message, List(), actionLabels) case _: PipelinesApiOutput => val message = "Delocalizing output %s -> %s".format( - quoted(pipelinesParameter.containerPath), - quoted(pipelinesParameter.cloudPath), + shellEscaped(pipelinesParameter.containerPath), + shellEscaped(pipelinesParameter.cloudPath), ) ActionBuilder.logTimestampedAction(message, List(ActionFlag.AlwaysRun), actionLabels) } @@ -191,13 +201,20 @@ object ActionBuilder { action.scalaLabels ) } - - def timestampedMessage(message: String) = s"""printf '%s %s\\n' "$$(date -u '+%Y/%m/%d %H:%M:%S')" ${quoted(message)}""" + + // This "sleep 5" is ugly, but hopefully prevents a PAPIv2 race condition whereby very-fast-completing tests never + // get identified as complete (and thus PAPIv2 continues to run the operation indefinitely. + def timestampedMessage(withSleep: Boolean)(message: String) = + (if (withSleep) "sleep 5 && " else "") + + s"""printf '%s %s\\n' "$$(date -u '+%Y/%m/%d %H:%M:%S')" ${shellEscaped(message)}""" /** * Creates an Action that logs the time as UTC plus prints the message. The original actionLabels will also be * applied to the logged action, except that Key.Tag -> some-value will be replaced with Key.Logging -> some-value. * + * Note that these log actions have a timeout of 300 seconds. That's obviously a huge timeout, and is more of a + * safety net in case these actions get stuck. + * * @param message Message to output. * @param actionFlags Flags from the original Action to also apply to the logging Action. * @param actionLabels Labels from the original Action to modify and apply to the logging Action. @@ -208,13 +225,14 @@ object ActionBuilder { actionLabels: Map[String, String]): Action = { // Uses the cloudSdk image as that image will be used for other operations as well. cloudSdkShellAction( - timestampedMessage(message) + timestampedMessage(withSleep = true)(message) )( flags = actionFlags, labels = actionLabels collect { case (key, value) if key == Key.Tag => Key.Logging -> value case (key, value) => key -> value - } + }, + timeout = 300.seconds ) } @@ -223,22 +241,22 @@ object ActionBuilder { val commandArgs: String = Option(action.getCommands) match { case Some(commands) => commands.asScala map { - case command if Option(command).isDefined => s" ${quoted(command)}" + case command if Option(command).isDefined => s" ${shellEscaped(command)}" case _ => "" } mkString "" case None => "" } val entrypointArg: String = Option(action.getEntrypoint) match { - case Some(entrypoint) => s" --entrypoint=${quoted(entrypoint)}" + case Some(entrypoint) => s" --entrypoint=${shellEscaped(entrypoint)}" case None => "" } val environmentArgs: String = Option(action.getEnvironment) match { case Some(environment) => environment.asScala map { - case (key, value) if Option(key).isDefined && Option(value).isDefined => s" -e ${quoted(s"$key:$value")}" - case (key, _) if Option(key).isDefined => s" -e ${quoted(key)}" + case (key, value) if Option(key).isDefined && Option(value).isDefined => s" -e ${shellEscaped(s"$key:$value")}" + case (key, _) if Option(key).isDefined => s" -e ${shellEscaped(key)}" case _ => "" } mkString "" case None => "" @@ -246,7 +264,7 @@ object ActionBuilder { val imageArg: String = Option(action.getImageUri) match { case None => " " - case Some(imageUri) => s" ${quoted(imageUri)}" + case Some(imageUri) => s" ${shellEscaped(imageUri)}" } val mountArgs: String = Option(action.getMounts) match { @@ -254,25 +272,25 @@ object ActionBuilder { case Some(mounts) => mounts.asScala map { case mount if Option(mount).isEmpty => "" - case mount if mount.getReadOnly => s" -v ${quoted(s"/mnt/${mount.getDisk}:${mount.getPath}:ro")}" - case mount => s" -v ${quoted(s"/mnt/${mount.getDisk}:${mount.getPath}")}" + case mount if mount.getReadOnly => s" -v ${shellEscaped(s"/mnt/${mount.getDisk}:${mount.getPath}:ro")}" + case mount => s" -v ${shellEscaped(s"/mnt/${mount.getDisk}:${mount.getPath}")}" } mkString "" } val nameArg: String = Option(action.getName) match { case None => "" - case Some(name) => s" --name ${quoted(name)}" + case Some(name) => s" --name ${shellEscaped(name)}" } val pidNamespaceArg: String = Option(action.getPidNamespace) match { - case Some(pidNamespace) => s" --pid=${quoted(pidNamespace)}" + case Some(pidNamespace) => s" --pid=${shellEscaped(pidNamespace)}" case None => "" } val portMappingArgs: String = Option(action.getPortMappings) match { case Some(portMappings) => portMappings.asScala map { - case (key, value) if Option(key).isDefined => s" -p ${quoted(s"$key:$value")}" + case (key, value) if Option(key).isDefined => s" -p ${shellEscaped(s"$key:$value")}" case (_, value) => s" -p $value" } mkString "" case None => "" @@ -303,7 +321,7 @@ object ActionBuilder { } /** Quotes a string such that it's compatible as a string argument in the shell. */ - private def quoted(any: Any): String = { + private def shellEscaped(any: Any): String = { val str = String.valueOf(any) /* NOTE: escapeXSI is more compact than wrapping in single quotes. Newlines are also stripped by the shell, as they diff --git a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/ActionCommands.scala b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/ActionCommands.scala index 1b16e1d9df3..4b60ffa40ad 100644 --- a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/ActionCommands.scala +++ b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/ActionCommands.scala @@ -90,7 +90,7 @@ object ActionCommands { | break | fi | if [ $$i -lt ${localizationConfiguration.localizationAttempts} ]; then - | ${s"""Waiting ${wait.toSeconds} seconds and retrying""" |> timestampedMessage} + | ${s"""Waiting ${wait.toSeconds} seconds and retrying""" |> timestampedMessage(withSleep = false)} | sleep ${wait.toSeconds} | fi |done @@ -125,13 +125,13 @@ object ActionCommands { |# Record the exit code of the gsutil command without project flag |RC_GSUTIL=$$? |if [ "$$RC_GSUTIL" != "0" ]; then - | ${s"$commandWithoutProject failed" |> timestampedMessage} + | ${s"$commandWithoutProject failed" |> timestampedMessage(withSleep = false)} | # Print the reason of the failure | cat gsutil_output.txt | | # Check if it matches the BucketIsRequesterPaysErrorMessage | if grep -q "$BucketIsRequesterPaysErrorMessage" gsutil_output.txt; then - | ${"Retrying with user project" |> timestampedMessage} + | ${"Retrying with user project" |> timestampedMessage(withSleep = false)} | $commandWithProject | else | exit "$$RC_GSUTIL" diff --git a/supportedBackends/google/pipelines/v2alpha1/src/test/scala/cromwell/backend/google/pipelines/v2alpha1/PipelinesConversionsSpec.scala b/supportedBackends/google/pipelines/v2alpha1/src/test/scala/cromwell/backend/google/pipelines/v2alpha1/PipelinesConversionsSpec.scala index 0714925c3a5..d81c6ca17c2 100644 --- a/supportedBackends/google/pipelines/v2alpha1/src/test/scala/cromwell/backend/google/pipelines/v2alpha1/PipelinesConversionsSpec.scala +++ b/supportedBackends/google/pipelines/v2alpha1/src/test/scala/cromwell/backend/google/pipelines/v2alpha1/PipelinesConversionsSpec.scala @@ -50,11 +50,11 @@ class PipelinesConversionsSpec extends FlatSpec with Matchers { val logging = actions.head logging.keySet.asScala should contain theSameElementsAs - Set("commands", "flags", "imageUri", "labels", "mounts") + Set("commands", "flags", "imageUri", "labels", "mounts", "timeout") logging.get("commands") should be(a[java.util.List[_]]) logging.get("commands").asInstanceOf[java.util.List[_]] should contain( - """printf '%s %s\n' "$(date -u '+%Y/%m/%d %H:%M:%S')" """ + + """sleep 5 && printf '%s %s\n' "$(date -u '+%Y/%m/%d %H:%M:%S')" """ + """Localizing\ input\ dos://dos.example.org/aaaabbbb-cccc-dddd-eeee-abcd0000dcba\ """ + """-\>\ /cromwell_root/path/to/file.bai""" ) From 6018d7c8d0205ac9a0dddef4e5852fb797b1bfdf Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Thu, 9 May 2019 21:46:19 -0400 Subject: [PATCH 41/46] Defend against over-large stderr files (#4952) --- .../core/path/EvenBetterPathMethods.scala | 40 ++++++++++++++++++- .../cromwell/engine/io/nio/NioFlow.scala | 37 ++--------------- .../workflow/WorkflowManagerActor.scala | 5 ++- 3 files changed, 46 insertions(+), 36 deletions(-) diff --git a/core/src/main/scala/cromwell/core/path/EvenBetterPathMethods.scala b/core/src/main/scala/cromwell/core/path/EvenBetterPathMethods.scala index 989c7edb9a0..5d9e22617f2 100644 --- a/core/src/main/scala/cromwell/core/path/EvenBetterPathMethods.scala +++ b/core/src/main/scala/cromwell/core/path/EvenBetterPathMethods.scala @@ -1,14 +1,16 @@ package cromwell.core.path -import java.io.InputStream +import java.io.{BufferedReader, IOException, InputStream, InputStreamReader} import java.nio.file.{FileAlreadyExistsException, Files} import java.nio.file.attribute.{PosixFilePermission, PosixFilePermissions} import better.files.File.OpenOptions +import cromwell.util.TryWithResource.tryWithResource import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext import scala.io.Codec +import scala.util.Failure /** * Implements methods beyond those implemented in NioPathMethods and BetterFileMethods @@ -98,4 +100,40 @@ trait EvenBetterPathMethods { locally(ec) write(content)(openOptions, Codec.UTF8) } + + /* + * The input stream will be closed when this method returns, which means the f function + * cannot leak an open stream. + */ + def withReader[A](f: BufferedReader => A)(implicit ec: ExecutionContext): A = { + + // Use an input reader to convert the byte stream to character stream. Buffered reader for efficiency. + tryWithResource(() => new BufferedReader(new InputStreamReader(this.mediaInputStream, Codec.UTF8.name)))(f).recoverWith({ + case failure => Failure(new IOException(s"Could not read from ${this.pathAsString}: ${failure.getMessage}", failure)) + }).get + } + + /** + * Returns an Array[Byte] from a Path. Limit the array size to "limit" byte if defined. + * @throws IOException if failOnOverflow is true and the file is larger than limit + */ + def limitFileContent(limit: Option[Int], failOnOverflow: Boolean)(implicit ec: ExecutionContext) = withReader { reader => + val bytesIterator = Iterator.continually(reader.read).takeWhile(_ != -1).map(_.toByte) + // Take 1 more than the limit so that we can look at the size and know if it's overflowing + val bytesArray = limit.map(l => bytesIterator.take(l + 1)).getOrElse(bytesIterator).toArray + + limit match { + case Some(l) if failOnOverflow && bytesArray.length > l => + throw new IOException(s"File $this is larger than $l Bytes. Maximum read limits can be adjusted in the configuration under system.input-read-limits.") + case Some(l) => bytesArray.take(l) + case _ => bytesArray + } + } + + /** + * Reads the first limitBytes of a file and makes a String. Prepend with an annotation at the start (to say that this is the + * first n bytes). + */ + def annotatedContentAsStringWithLimit(limitBytes: Int)(implicit ec: ExecutionContext): String = + s"[First $limitBytes bytes]:" + new String(limitFileContent(Option(limitBytes), failOnOverflow = false)) } diff --git a/engine/src/main/scala/cromwell/engine/io/nio/NioFlow.scala b/engine/src/main/scala/cromwell/engine/io/nio/NioFlow.scala index 43a7cae4961..e5bc8a2b135 100644 --- a/engine/src/main/scala/cromwell/engine/io/nio/NioFlow.scala +++ b/engine/src/main/scala/cromwell/engine/io/nio/NioFlow.scala @@ -19,7 +19,6 @@ import cromwell.util.TryWithResource._ import scala.concurrent.ExecutionContext import scala.io.Codec -import scala.util.Failure object NioFlow { def NoopOnRetry(context: IoCommandContext[_])(failure: Throwable) = () } @@ -98,7 +97,7 @@ class NioFlow(parallelism: Int, private def readAsString(read: IoContentAsStringCommand) = IO { new String( - limitFileContent(read.file, read.options.maxBytes, read.options.failOnOverflow), + read.file.limitFileContent(read.options.maxBytes, read.options.failOnOverflow), StandardCharsets.UTF_8 ) } @@ -130,7 +129,7 @@ class NioFlow(parallelism: Int, } private def readLines(exists: IoReadLinesCommand) = IO { - withReader(exists.file) { reader => + exists.file.withReader { reader => Stream.continually(reader.readLine()).takeWhile(_ != null).toList } } @@ -141,35 +140,6 @@ class NioFlow(parallelism: Int, private def createDirectories(path: Path) = path.parent.createDirectories() - /* - * The input stream will be closed when this method returns, which means the f function - * cannot leak an open stream. - */ - private def withReader[A](file: Path)(f: BufferedReader => A): A = { - - // Use an input reader to convert the byte stream to character stream. Buffered reader for efficiency. - tryWithResource(() => new BufferedReader(new InputStreamReader(file.mediaInputStream, Codec.UTF8.name)))(f).recoverWith({ - case failure => Failure(new IOException(s"Could not read from ${file.pathAsString}: ${failure.getMessage}", failure)) - }).get - } - - /** - * Returns an Array[Byte] from a Path. Limit the array size to "limit" byte if defined. - * @throws IOException if failOnOverflow is true and the file is larger than limit - */ - private def limitFileContent(file: Path, limit: Option[Int], failOnOverflow: Boolean) = withReader(file) { reader => - val bytesIterator = Iterator.continually(reader.read).takeWhile(_ != -1).map(_.toByte) - // Take 1 more than the limit so that we can look at the size and know if it's overflowing - val bytesArray = limit.map(l => bytesIterator.take(l + 1)).getOrElse(bytesIterator).toArray - - limit match { - case Some(l) if failOnOverflow && bytesArray.length > l => - throw new IOException(s"File $file is larger than $l Bytes. Maximum read limits can be adjusted in the configuration under system.input-read-limits.") - case Some(l) => bytesArray.take(l) - case _ => bytesArray - } - } - private def getFileHashForDrsPath(drsPath: DrsPath): IO[String] = { val drsFileSystemProvider = drsPath.drsPath.getFileSystem.provider.asInstanceOf[DrsCloudNioFileSystemProvider] @@ -177,14 +147,13 @@ class NioFlow(parallelism: Int, val fileAttributesOption = drsFileSystemProvider.fileProvider.fileAttributes(drsPath.drsPath.cloudHost, drsPath.drsPath.cloudPath) fileAttributesOption match { - case Some(fileAttributes) => { + case Some(fileAttributes) => val fileHashIO = IO { fileAttributes.fileHash } fileHashIO.flatMap({ case Some(fileHash) => IO.pure(fileHash) case None => IO.raiseError(new IOException(s"Error while resolving DRS path $drsPath. The response from Martha doesn't contain the 'md5' hash for the file.")) }) - } case None => IO.raiseError(new IOException(s"Error getting file hash of DRS path $drsPath. Reason: File attributes class DrsCloudNioRegularFileAttributes wasn't defined in DrsCloudNioFileProvider.")) } } diff --git a/engine/src/main/scala/cromwell/engine/workflow/WorkflowManagerActor.scala b/engine/src/main/scala/cromwell/engine/workflow/WorkflowManagerActor.scala index 7fa6dfa0625..558b37ba4e2 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/WorkflowManagerActor.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/WorkflowManagerActor.scala @@ -22,6 +22,7 @@ import cromwell.webservice.EngineStatsActor import net.ceedubs.ficus.Ficus._ import org.apache.commons.lang3.exception.ExceptionUtils +import scala.concurrent.ExecutionContext import scala.concurrent.duration._ import scala.language.postfixOps import scala.util.Try @@ -321,11 +322,13 @@ class WorkflowManagerActor(params: WorkflowManagerActorParams) private def expandFailureReasons(reasons: Seq[Throwable]): String = { + implicit val ec: ExecutionContext = context.dispatcher + reasons map { case reason: ThrowableAggregation => expandFailureReasons(reason.throwables.toSeq) case reason: KnownJobFailureException => val stderrMessage = reason.stderrPath map { path => - val content = Try(path.contentAsString).recover({ + val content = Try(path.annotatedContentAsStringWithLimit(300)).recover({ case e => s"Could not retrieve content: ${e.getMessage}" }).get s"\nCheck the content of stderr for potential additional information: ${path.pathAsString}.\n $content" From 07d747a2f5ca5474eb5139d9d97a7281e8df1537 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Thu, 9 May 2019 21:46:36 -0400 Subject: [PATCH 42/46] Codecov comment off (#4955) --- codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index 67a854bb659..a2425d4e797 100644 --- a/codecov.yml +++ b/codecov.yml @@ -26,4 +26,4 @@ coverage: target: 50% threshold: 5% -comment: false +comment: off From 3b0c2be7012f1ef13ac0b35041f9da53b435fa20 Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Fri, 10 May 2019 11:56:15 -0400 Subject: [PATCH 43/46] Always shift times to UTC, and render w/ 3 digits of millis --- CHANGELOG.md | 19 +++++++ .../centaur/reporting/BigQueryReporter.scala | 13 ++--- .../src/main/scala/common/util/TimeUtil.scala | 22 +++++++++ .../scala/cromwell/api/model/TimeUtil.scala | 22 +++++++++ .../scala/cromwell/api/model/package.scala | 3 +- ...CromwellQueryResultJsonFormatterSpec.scala | 25 +++++++--- .../workflow/WorkflowMetadataHelper.scala | 10 +++- .../workflowstore/SqlWorkflowStore.scala | 5 +- .../WorkflowStoreSubmitActor.scala | 2 +- .../webservice/WorkflowJsonSupport.scala | 3 +- .../metadata/MetadataJsonSupport.scala | 3 +- .../services/metadata/MetadataQuery.scala | 2 + .../impl/MetadataDatabaseAccessSpec.scala | 49 ++++++++++++------- 13 files changed, 137 insertions(+), 41 deletions(-) create mode 100644 common/src/main/scala/common/util/TimeUtil.scala create mode 100644 cromwellApiClient/src/main/scala/cromwell/api/model/TimeUtil.scala diff --git a/CHANGELOG.md b/CHANGELOG.md index 281ed72ba17..db7a911685d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,25 @@ disks: "/some/mnt 20 SSD" ``` Because Cromwell's AWS backend auto-sizes disks, the size specification is simply discarded. +### Time Formatting + +In previous versions of Cromwell, times were converted to strings using +[the default Java formatter](https://docs.oracle.com/javase/8/docs/api/java/time/OffsetDateTime.html#toString--) which +generates a variety of ISO-8601 formats. String conversions also retained whatever server time zone generated that +specific time instance. + +Going forward, times stored in Cromwell metadata, and later returned via the HTTP endpoint, are now converted to UTC +then formatted with exactly three digits of milliseconds. + +For example: +- `2017-01-19T12:34:56-04:00` will now be formatted as +- `2017-01-19T16:34:56.000Z` + +This change only affects newly formatted dates. Older dates already formatted and stored by previous versions of +Cromwell will not be updated however they will still return a +[valid ISO-8601 format](https://en.wikipedia.org/wiki/ISO_8601). The older format may be in various non-UTC time zones, +and may or may not include microseconds or even nanoseconds, for example `2017-01-19T12:34:56.123456789-04:00`. + ### Config Changes #### Heartbeat failure shutdown diff --git a/centaur/src/it/scala/centaur/reporting/BigQueryReporter.scala b/centaur/src/it/scala/centaur/reporting/BigQueryReporter.scala index b1a15f0346e..ddc64e257f2 100644 --- a/centaur/src/it/scala/centaur/reporting/BigQueryReporter.scala +++ b/centaur/src/it/scala/centaur/reporting/BigQueryReporter.scala @@ -4,9 +4,9 @@ import java.time.OffsetDateTime import java.util import cats.effect.IO -import cats.syntax.traverse._ -import cats.syntax.apply._ import cats.instances.list._ +import cats.syntax.apply._ +import cats.syntax.traverse._ import centaur.reporting.BigQueryReporter._ import centaur.test.CentaurTestException import centaur.test.metadata.CallAttemptFailure @@ -15,6 +15,7 @@ import com.google.api.services.bigquery.BigqueryScopes import com.google.auth.Credentials import com.google.cloud.bigquery.InsertAllRequest.RowToInsert import com.google.cloud.bigquery.{BigQuery, BigQueryError, BigQueryOptions, InsertAllRequest, InsertAllResponse, TableId} +import common.util.TimeUtil._ import common.validation.Validation._ import cromwell.cloudsupport.gcp.GoogleConfiguration import cromwell.database.sql.SqlConverters._ @@ -154,7 +155,7 @@ class BigQueryReporter(override val params: ErrorReporterParams) extends ErrorRe "test_message" -> Option(centaurTestException.message), "test_name" -> Option(testEnvironment.name), "test_stack_trace" -> Option(ExceptionUtils.getStackTrace(centaurTestException)), - "test_timestamp" -> Option(OffsetDateTime.now.toString), + "test_timestamp" -> Option(OffsetDateTime.now.toUtcMilliString), "test_workflow_id" -> centaurTestException.workflowIdOption, ).collect { case (key, Some(value)) => (key, value) @@ -165,11 +166,11 @@ class BigQueryReporter(override val params: ErrorReporterParams) extends ErrorRe RowToInsert of Map( "call_fully_qualified_name" -> Option(callAttemptFailure.callFullyQualifiedName), "call_root" -> callAttemptFailure.callRootOption, - "end" -> callAttemptFailure.endOption.map(_.toString), + "end" -> callAttemptFailure.endOption.map(_.toUtcMilliString), "job_attempt" -> Option(callAttemptFailure.jobAttempt), "job_index" -> Option(callAttemptFailure.jobIndex), "message" -> Option(callAttemptFailure.message), - "start" -> callAttemptFailure.startOption.map(_.toString), + "start" -> callAttemptFailure.startOption.map(_.toUtcMilliString), "stderr" -> callAttemptFailure.stderrOption, "stdout" -> callAttemptFailure.stdoutOption, "workflow_id" -> Option(callAttemptFailure.workflowId), @@ -195,7 +196,7 @@ class BigQueryReporter(override val params: ErrorReporterParams) extends ErrorRe "job_attempt" -> metadataEntry.jobAttempt, "job_index" -> metadataEntry.jobIndex, "metadata_key" -> Option(metadataEntry.metadataKey), - "metadata_timestamp" -> Option(metadataEntry.metadataTimestamp.toSystemOffsetDateTime.toString), + "metadata_timestamp" -> Option(metadataEntry.metadataTimestamp.toSystemOffsetDateTime.toUtcMilliString), "metadata_value" -> metadataEntry.metadataValue.map(_.toRawString), "metadata_value_type" -> metadataEntry.metadataValueType, "workflow_execution_uuid" -> Option(metadataEntry.workflowExecutionUuid), diff --git a/common/src/main/scala/common/util/TimeUtil.scala b/common/src/main/scala/common/util/TimeUtil.scala new file mode 100644 index 00000000000..dbd94c99b80 --- /dev/null +++ b/common/src/main/scala/common/util/TimeUtil.scala @@ -0,0 +1,22 @@ +package common.util + +import java.time.format.DateTimeFormatter +import java.time.{OffsetDateTime, ZoneOffset} + +object TimeUtil { + /** + * Instead of "one of" the valid ISO-8601 formats, standardize on this one: + * https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/java/time/OffsetDateTime.java#L1886 + */ + private val Iso8601MillisecondsFormat = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSXXXXX") + + implicit class EnhancedOffsetDateTime(val offsetDateTime: OffsetDateTime) extends AnyVal { + /** + * Discards the original timezone and shifts the time to UTC, then returns the ISO-8601 formatted string with + * exactly three digits of milliseconds. + */ + def toUtcMilliString: String = Option(offsetDateTime).map( + _.atZoneSameInstant(ZoneOffset.UTC).format(Iso8601MillisecondsFormat) + ).orNull + } +} diff --git a/cromwellApiClient/src/main/scala/cromwell/api/model/TimeUtil.scala b/cromwellApiClient/src/main/scala/cromwell/api/model/TimeUtil.scala new file mode 100644 index 00000000000..1688da92ff7 --- /dev/null +++ b/cromwellApiClient/src/main/scala/cromwell/api/model/TimeUtil.scala @@ -0,0 +1,22 @@ +package cromwell.api.model + +import java.time.format.DateTimeFormatter +import java.time.{OffsetDateTime, ZoneOffset} + +object TimeUtil { + /** + * Instead of "one of" the valid ISO-8601 formats, standardize on this one: + * https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/classes/java/time/OffsetDateTime.java#L1886 + */ + private val Iso8601MillisecondsFormat = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSXXXXX") + + implicit class EnhancedOffsetDateTime(val offsetDateTime: OffsetDateTime) extends AnyVal { + /** + * Discards the original timezone and shifts the time to UTC, then returns the ISO-8601 formatted string with + * exactly three digits of milliseconds. + */ + def toUtcMilliString: String = Option(offsetDateTime).map( + _.atZoneSameInstant(ZoneOffset.UTC).format(Iso8601MillisecondsFormat) + ).orNull + } +} diff --git a/cromwellApiClient/src/main/scala/cromwell/api/model/package.scala b/cromwellApiClient/src/main/scala/cromwell/api/model/package.scala index 963f08031fd..f8ed1513009 100644 --- a/cromwellApiClient/src/main/scala/cromwell/api/model/package.scala +++ b/cromwellApiClient/src/main/scala/cromwell/api/model/package.scala @@ -9,6 +9,7 @@ import cats.arrow.FunctionK import cats.data.EitherT import cats.effect.{ContextShift, IO, Timer} import cromwell.api.CromwellClient.UnsuccessfulRequestException +import cromwell.api.model.TimeUtil._ import spray.json.{DefaultJsonProtocol, JsString, JsValue, RootJsonFormat} import scala.concurrent.duration.FiniteDuration @@ -20,7 +21,7 @@ package object model { object OffsetDateTimeJsonFormatter extends DefaultJsonProtocol { object OffsetDateTimeFormat extends RootJsonFormat[OffsetDateTime] { - def write(odt: OffsetDateTime) = new JsString(odt.toString) + def write(offsetDateTime: OffsetDateTime) = new JsString(offsetDateTime.toUtcMilliString) def read(value: JsValue) = value match { case JsString(string) => OffsetDateTime.parse(string) case other => throw new UnsupportedOperationException(s"Cannot deserialize $other into an OffsetDateTime") diff --git a/cromwellApiClient/src/test/scala/cromwell/api/model/CromwellQueryResultJsonFormatterSpec.scala b/cromwellApiClient/src/test/scala/cromwell/api/model/CromwellQueryResultJsonFormatterSpec.scala index f1983e7945f..c134a47ce76 100644 --- a/cromwellApiClient/src/test/scala/cromwell/api/model/CromwellQueryResultJsonFormatterSpec.scala +++ b/cromwellApiClient/src/test/scala/cromwell/api/model/CromwellQueryResultJsonFormatterSpec.scala @@ -11,8 +11,20 @@ class CromwellQueryResultJsonFormatterSpec extends FlatSpec with Matchers { behavior of "CromwellQueryResultJsonFormat" val sampleQueryResult = CromwellQueryResults(results = List( - CromwellQueryResult("switcheroo", WorkflowId.fromString("bee51f36-396d-4e22-8a81-33dedff66bf6"), Failed, OffsetDateTime.parse("2017-07-24T14:44:34.010-04:00"), OffsetDateTime.parse("2017-07-24T14:44:33.227-04:00")), - CromwellQueryResult("switcheroo", WorkflowId.fromString("0071495e-39eb-478e-bc98-8614b986c91e"), Succeeded, OffsetDateTime.parse("2017-07-24T15:06:45.940-04:00"), OffsetDateTime.parse("2017-07-24T15:04:54.372-04:00")) + CromwellQueryResult( + "switcheroo", + WorkflowId.fromString("bee51f36-396d-4e22-8a81-33dedff66bf6"), + Failed, + OffsetDateTime.parse("2017-07-24T14:44:34.010Z"), + OffsetDateTime.parse("2017-07-24T14:44:33.227Z") + ), + CromwellQueryResult( + "switcheroo", + WorkflowId.fromString("0071495e-39eb-478e-bc98-8614b986c91e"), + Succeeded, + OffsetDateTime.parse("2017-07-24T15:06:45.940Z"), + OffsetDateTime.parse("2017-07-24T15:04:54.372Z") + ), )) val sampleJson = """|{ @@ -21,21 +33,20 @@ class CromwellQueryResultJsonFormatterSpec extends FlatSpec with Matchers { | "name": "switcheroo", | "id": "bee51f36-396d-4e22-8a81-33dedff66bf6", | "status": "Failed", - | "end": "2017-07-24T14:44:34.010-04:00", - | "start": "2017-07-24T14:44:33.227-04:00" + | "end": "2017-07-24T14:44:34.010Z", + | "start": "2017-07-24T14:44:33.227Z" | }, | { | "name": "switcheroo", | "id": "0071495e-39eb-478e-bc98-8614b986c91e", | "status": "Succeeded", - | "end": "2017-07-24T15:06:45.940-04:00", - | "start": "2017-07-24T15:04:54.372-04:00" + | "end": "2017-07-24T15:06:45.940Z", + | "start": "2017-07-24T15:04:54.372Z" | } | ] |}""".stripMargin.parseJson.asJsObject it should "write a query result as a structured JsObject" in { - sampleQueryResult.toJson shouldEqual sampleJson } diff --git a/engine/src/main/scala/cromwell/engine/workflow/WorkflowMetadataHelper.scala b/engine/src/main/scala/cromwell/engine/workflow/WorkflowMetadataHelper.scala index 514cd5880b4..96c5fb17588 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/WorkflowMetadataHelper.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/WorkflowMetadataHelper.scala @@ -12,12 +12,18 @@ trait WorkflowMetadataHelper { def serviceRegistryActor: ActorRef def pushWorkflowStart(workflowId: WorkflowId) = { - val startEvent = MetadataEvent(MetadataKey(workflowId, None, WorkflowMetadataKeys.StartTime), MetadataValue(OffsetDateTime.now.toString)) + val startEvent = MetadataEvent( + MetadataKey(workflowId, None, WorkflowMetadataKeys.StartTime), + MetadataValue(OffsetDateTime.now) + ) serviceRegistryActor ! PutMetadataAction(startEvent) } def pushWorkflowEnd(workflowId: WorkflowId) = { - val metadataEventMsg = MetadataEvent(MetadataKey(workflowId, None, WorkflowMetadataKeys.EndTime), MetadataValue(OffsetDateTime.now.toString)) + val metadataEventMsg = MetadataEvent( + MetadataKey(workflowId, None, WorkflowMetadataKeys.EndTime), + MetadataValue(OffsetDateTime.now) + ) serviceRegistryActor ! PutMetadataAction(metadataEventMsg) } diff --git a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/SqlWorkflowStore.scala b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/SqlWorkflowStore.scala index ed889299464..5cf3f703186 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/SqlWorkflowStore.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/SqlWorkflowStore.scala @@ -1,6 +1,6 @@ package cromwell.engine.workflow.workflowstore -import java.time.{Instant, OffsetDateTime, ZoneId} +import java.time.OffsetDateTime import cats.data.NonEmptyList import common.validation.ErrorOr.ErrorOr @@ -154,10 +154,9 @@ case class SqlWorkflowStore(sqlDatabase: WorkflowStoreSqlDatabase) extends Workf ) workflowStoreStateToStartableState(workflowStoreEntry) map { startableState => - val instant = Instant.ofEpochMilli(workflowStoreEntry.submissionTime.getTime) WorkflowToStart( id = WorkflowId.fromString(workflowStoreEntry.workflowExecutionUuid), - submissionTime = OffsetDateTime.ofInstant(instant, ZoneId.of("UTC")), + submissionTime = workflowStoreEntry.submissionTime.toSystemOffsetDateTime, sources = sources, state = startableState) } diff --git a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreSubmitActor.scala b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreSubmitActor.scala index 1139cd4967e..611f8f7dd44 100644 --- a/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreSubmitActor.scala +++ b/engine/src/main/scala/cromwell/engine/workflow/workflowstore/WorkflowStoreSubmitActor.scala @@ -166,7 +166,7 @@ final case class WorkflowStoreSubmitActor(store: WorkflowStore, serviceRegistryA processSource(_.clearEncryptedValues)(originalSourceFiles) map { sourceFiles => val submissionEvents: List[MetadataEvent] = List( - MetadataEvent(MetadataKey(id, None, WorkflowMetadataKeys.SubmissionTime), MetadataValue(OffsetDateTime.now.toString)), + MetadataEvent(MetadataKey(id, None, WorkflowMetadataKeys.SubmissionTime), MetadataValue(OffsetDateTime.now)), MetadataEvent.empty(MetadataKey(id, None, WorkflowMetadataKeys.Inputs)), MetadataEvent.empty(MetadataKey(id, None, WorkflowMetadataKeys.Outputs)), MetadataEvent(MetadataKey(id, None, WorkflowMetadataKeys.Status), MetadataValue(actualWorkflowState)), diff --git a/engine/src/main/scala/cromwell/webservice/WorkflowJsonSupport.scala b/engine/src/main/scala/cromwell/webservice/WorkflowJsonSupport.scala index 3ac00c8fcef..b7da4089d60 100644 --- a/engine/src/main/scala/cromwell/webservice/WorkflowJsonSupport.scala +++ b/engine/src/main/scala/cromwell/webservice/WorkflowJsonSupport.scala @@ -14,6 +14,7 @@ import cromwell.services.healthmonitor.ProtoHealthMonitorServiceActor.{StatusChe import cromwell.webservice.routes.CromwellApiService.BackendResponse import cromwell.webservice.metadata.MetadataBuilderActor.BuiltMetadataResponse import spray.json.{DefaultJsonProtocol, JsString, JsValue, RootJsonFormat} +import common.util.TimeUtil._ object WorkflowJsonSupport extends DefaultJsonProtocol { implicit val workflowStatusResponseProtocol = jsonFormat2(WorkflowStatusResponse) @@ -42,7 +43,7 @@ object WorkflowJsonSupport extends DefaultJsonProtocol { implicit val successResponse = jsonFormat3(SuccessResponse) implicit object DateJsonFormat extends RootJsonFormat[OffsetDateTime] { - override def write(obj: OffsetDateTime) = JsString(obj.toString) + override def write(offsetDateTime: OffsetDateTime) = JsString(offsetDateTime.toUtcMilliString) override def read(json: JsValue): OffsetDateTime = json match { case JsString(str) => OffsetDateTime.parse(str) diff --git a/services/src/main/scala/cromwell/services/metadata/MetadataJsonSupport.scala b/services/src/main/scala/cromwell/services/metadata/MetadataJsonSupport.scala index 0636cb3ce48..42930f463d5 100644 --- a/services/src/main/scala/cromwell/services/metadata/MetadataJsonSupport.scala +++ b/services/src/main/scala/cromwell/services/metadata/MetadataJsonSupport.scala @@ -4,6 +4,7 @@ import java.time.OffsetDateTime import cromwell.core.WorkflowId import spray.json.{DefaultJsonProtocol, JsString, JsValue, RootJsonFormat} +import common.util.TimeUtil._ object MetadataJsonSupport extends DefaultJsonProtocol { implicit object WorkflowIdJsonFormatter extends RootJsonFormat[WorkflowId] { @@ -17,7 +18,7 @@ object MetadataJsonSupport extends DefaultJsonProtocol { } implicit object OffsetDateTimeFormatter extends RootJsonFormat[OffsetDateTime] { - def write(o: OffsetDateTime) = new JsString(o.toString) + def write(offsetDateTime: OffsetDateTime) = new JsString(offsetDateTime.toUtcMilliString) def read(value: JsValue) = throw new UnsupportedOperationException("Reading OffsetDateTime from JSON is currently unsupported") } diff --git a/services/src/main/scala/cromwell/services/metadata/MetadataQuery.scala b/services/src/main/scala/cromwell/services/metadata/MetadataQuery.scala index a963f5004ad..2a91afea3e3 100644 --- a/services/src/main/scala/cromwell/services/metadata/MetadataQuery.scala +++ b/services/src/main/scala/cromwell/services/metadata/MetadataQuery.scala @@ -7,6 +7,7 @@ import cromwell.core._ import cromwell.core.labels.Labels import org.slf4j.{Logger, LoggerFactory} import wom.values._ +import common.util.TimeUtil._ case class MetadataJobKey(callFqn: String, index: Option[Int], attempt: Int) @@ -62,6 +63,7 @@ object MetadataValue { case _: Int | Long | _: java.lang.Long | _: java.lang.Integer => new MetadataValue(value.toString, MetadataInt) case _: Double | Float | _: java.lang.Double | _: java.lang.Float => new MetadataValue(value.toString, MetadataNumber) case _: Boolean | _: java.lang.Boolean => new MetadataValue(value.toString, MetadataBoolean) + case offsetDateTime: OffsetDateTime => new MetadataValue(offsetDateTime.toUtcMilliString, MetadataString) case other => new MetadataValue(other.toString, MetadataString) } } diff --git a/services/src/test/scala/cromwell/services/metadata/impl/MetadataDatabaseAccessSpec.scala b/services/src/test/scala/cromwell/services/metadata/impl/MetadataDatabaseAccessSpec.scala index c4972113d19..de476a1eee7 100644 --- a/services/src/test/scala/cromwell/services/metadata/impl/MetadataDatabaseAccessSpec.scala +++ b/services/src/test/scala/cromwell/services/metadata/impl/MetadataDatabaseAccessSpec.scala @@ -3,6 +3,7 @@ package cromwell.services.metadata.impl import java.time.OffsetDateTime import com.typesafe.config.ConfigFactory +import common.util.TimeUtil._ import cromwell.core.Tags.DbmsTest import cromwell.core._ import cromwell.core.labels.{Label, Labels} @@ -70,10 +71,10 @@ class MetadataDatabaseAccessSpec extends FlatSpec with Matchers with ScalaFuture val workflowKey = MetadataKey(workflowId, jobKey = None, key = null) def keyAndValue(name: String) = Array( - (WorkflowMetadataKeys.SubmissionTime, OffsetDateTime.now.toString), + (WorkflowMetadataKeys.SubmissionTime, OffsetDateTime.now.toUtcMilliString), (WorkflowMetadataKeys.Status, WorkflowSubmitted.toString), (WorkflowMetadataKeys.Name, name), - (WorkflowMetadataKeys.StartTime, OffsetDateTime.now.toString) + (WorkflowMetadataKeys.StartTime, OffsetDateTime.now.toUtcMilliString) ) ++ labelMetadata publishMetadataEvents(workflowKey, keyAndValue(name)).map(_ => workflowId) @@ -85,7 +86,7 @@ class MetadataDatabaseAccessSpec extends FlatSpec with Matchers with ScalaFuture val metadataKeys = Array( (WorkflowMetadataKeys.Status, WorkflowRunning.toString), (WorkflowMetadataKeys.Name, subworkflowName), - (WorkflowMetadataKeys.StartTime, OffsetDateTime.now.toString), + (WorkflowMetadataKeys.StartTime, OffsetDateTime.now.toUtcMilliString), (WorkflowMetadataKeys.ParentWorkflowId, parentWorkflowId.toString), (WorkflowMetadataKeys.RootWorkflowId, parentWorkflowId.toString), ) @@ -154,7 +155,7 @@ class MetadataDatabaseAccessSpec extends FlatSpec with Matchers with ScalaFuture val keyAndValue = Array( (WorkflowMetadataKeys.Status, WorkflowRunning.toString), (WorkflowMetadataKeys.Status, WorkflowSucceeded.toString), - (WorkflowMetadataKeys.EndTime, OffsetDateTime.now.toString)) + (WorkflowMetadataKeys.EndTime, OffsetDateTime.now.toUtcMilliString)) publishMetadataEvents(workflowKey, keyAndValue) } @@ -308,27 +309,37 @@ class MetadataDatabaseAccessSpec extends FlatSpec with Matchers with ScalaFuture } // Filter by start date _ <- dataAccess.queryWorkflowSummaries(WorkflowQueryParameters(Seq( - WorkflowQueryKey.StartDate.name -> workflowQueryResult2.start.get.toString))) map { case (response, _) => - response.results partition { r => r.start.isDefined && r.start.get.compareTo(workflowQueryResult.start.get) >= 0 } match { - case (y, n) if y.nonEmpty && n.isEmpty => // good - case (y, n) => fail(s"Found ${y.size} later workflows and ${n.size} earlier") - } + WorkflowQueryKey.StartDate.name -> workflowQueryResult2.start.get.toUtcMilliString))) map { + case (response, _) => + response.results partition { + r => r.start.isDefined && r.start.get.compareTo(workflowQueryResult.start.get) >= 0 + } match { + case (y, n) if y.nonEmpty && n.isEmpty => // good + case (y, n) => fail(s"Found ${y.size} later workflows and ${n.size} earlier") + } } // Filter by end date _ <- dataAccess.queryWorkflowSummaries(WorkflowQueryParameters(Seq( - WorkflowQueryKey.EndDate.name -> workflowQueryResult.end.get.toString))) map { case (response, _) => - response.results partition { r => r.end.isDefined && r.end.get.compareTo(workflowQueryResult.end.get) <= 0 } match { - case (y, n) if y.nonEmpty && n.isEmpty => // good - case (y, n) => fail(s"Found ${y.size} earlier workflows and ${n.size} later") - } + WorkflowQueryKey.EndDate.name -> workflowQueryResult.end.get.toUtcMilliString))) map { + case (response, _) => + response.results partition { + r => r.end.isDefined && r.end.get.compareTo(workflowQueryResult.end.get) <= 0 + } match { + case (y, n) if y.nonEmpty && n.isEmpty => // good + case (y, n) => fail(s"Found ${y.size} earlier workflows and ${n.size} later") + } } // Filter by submission time _ <- dataAccess.queryWorkflowSummaries(WorkflowQueryParameters(Seq( - WorkflowQueryKey.SubmissionTime.name -> workflowQueryResult2.submission.get.toString))) map { case (response, _) => - response.results partition { r => r.submission.isDefined && r.submission.get.compareTo(workflowQueryResult2.submission.get) <= 0 } match { - case (y, n) if y.nonEmpty && n.isEmpty => // good - case (y, n) => fail(s"Found ${y.size} earlier workflows and ${n.size} later while filtering by submission timestamp") - } + WorkflowQueryKey.SubmissionTime.name -> workflowQueryResult2.submission.get.toUtcMilliString))) map { + case (response, _) => + response.results partition { r => + r.submission.isDefined && r.submission.get.compareTo(workflowQueryResult2.submission.get) <= 0 + } match { + case (y, n) if y.nonEmpty && n.isEmpty => // good + case (y, n) => + fail(s"Found ${y.size} earlier workflows and ${n.size} later while filtering by submission timestamp") + } } // Check for labels in query response _ <- dataAccess.queryWorkflowSummaries(WorkflowQueryParameters(Seq( From 977630a3514ecc5564e9d105a1ed978971ad4377 Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Fri, 10 May 2019 12:48:16 -0400 Subject: [PATCH 44/46] Custom GPUs (#4939) --- CHANGELOG.md | 15 ++ .../ValidatedRuntimeAttributesBuilder.scala | 2 +- .../standardTestCases/gpu_cuda_image.test | 13 ++ .../standardTestCases/gpu_on_papi.test | 15 -- .../gpu_on_papi/gpu_cuda_image.wdl | 48 ++++ .../gpu_on_papi/gpu_on_papi.wdl | 35 +-- .../gpu_on_papi/invalid.inputs.json | 3 + .../gpu_on_papi/valid.inputs.json | 3 + .../gpu_on_papi_invalid.test | 14 ++ .../standardTestCases/gpu_on_papi_valid.test | 18 ++ src/ci/bin/testCentaurPapiV1.sh | 4 +- .../pipelines/common/GpuTypeValidation.scala | 12 +- .../pipelines/common/GpuValidation.scala | 3 + .../common/PapiInstrumentation.scala | 12 +- .../PipelinesApiRuntimeAttributes.scala | 43 ++-- .../api/PipelinesApiRequestManager.scala | 72 ++++-- .../api/PipelinesApiRequestWorker.scala | 6 +- .../PipelinesApiRunCreationClient.scala | 4 +- .../PipelinesApiGpuAttributesSpec.scala | 79 +++++++ .../PipelinesApiRuntimeAttributesSpec.scala | 205 ++++++++---------- .../api/request/AbortRequestHandler.scala | 4 +- .../api/request/GetRequestHandler.scala | 4 +- .../api/request/RunRequestHandler.scala | 2 +- .../pipelines/v2alpha1/GenomicsFactory.scala | 2 +- .../api/request/AbortRequestHandler.scala | 6 +- .../api/request/GetRequestHandler.scala | 6 +- .../api/request/RunRequestHandler.scala | 23 +- 27 files changed, 433 insertions(+), 220 deletions(-) create mode 100644 centaur/src/main/resources/standardTestCases/gpu_cuda_image.test delete mode 100644 centaur/src/main/resources/standardTestCases/gpu_on_papi.test create mode 100644 centaur/src/main/resources/standardTestCases/gpu_on_papi/gpu_cuda_image.wdl create mode 100644 centaur/src/main/resources/standardTestCases/gpu_on_papi/invalid.inputs.json create mode 100644 centaur/src/main/resources/standardTestCases/gpu_on_papi/valid.inputs.json create mode 100644 centaur/src/main/resources/standardTestCases/gpu_on_papi_invalid.test create mode 100644 centaur/src/main/resources/standardTestCases/gpu_on_papi_valid.test create mode 100644 supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/PipelinesApiGpuAttributesSpec.scala diff --git a/CHANGELOG.md b/CHANGELOG.md index 281ed72ba17..ac5cacb532d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,12 @@ NOTE: In the remote chance that the `system.workflow-heartbeats.ttl` has been co then the new configuration value `system.workflow-heartbeats.write-failure-shutdown-duration` must also be explicitly set less than the `ttl`. +#### nVidia Driver Attribute Change + +The runtime attribute `nvidia-driver-version` was previously allowed only as a default runtime attribute in configuration. +Because WDL does not allow attribute names to contain `-` characters, this has been changed to `nvidiaDriverVersion`. +This field is now accepted within WDL files as well as within the configuration file. + #### Logging long running jobs All backends can now emit slow job warnings after a configurable time running. @@ -45,6 +51,15 @@ backend { } ``` +### Runtime Attributes + +#### GPU Attributes + +* The `gpuType` attribute is no longer validated against a whitelist at workflow submission time. Instead, validation now happens at runtime. This allows any valid accelerator to be used. +* The `nvidiaDriverVersion` attribute is now available in WDL `runtime` sections. The default continues to be `390.46` which applies if and only if GPUs are being used. +* A default `gpuType` ("nvidia-tesla-k80") will now be applied if `gpuCount` is specified but `gpuType` is not. +* Similarly, a default `gpuCount` (1) will be applied if `gpuType` is specified but `cpuCount` is not. + ### Bug fixes #### Better validation of workflow heartbeats diff --git a/backend/src/main/scala/cromwell/backend/validation/ValidatedRuntimeAttributesBuilder.scala b/backend/src/main/scala/cromwell/backend/validation/ValidatedRuntimeAttributesBuilder.scala index dc086d18879..0c2ac161eea 100644 --- a/backend/src/main/scala/cromwell/backend/validation/ValidatedRuntimeAttributesBuilder.scala +++ b/backend/src/main/scala/cromwell/backend/validation/ValidatedRuntimeAttributesBuilder.scala @@ -36,7 +36,7 @@ trait ValidatedRuntimeAttributesBuilder { /** * Returns validators suitable for BackendWorkflowInitializationActor.runtimeAttributeValidators. */ - final lazy val validatorMap: Map[String, (Option[WomExpression]) => Boolean] = { + final lazy val validatorMap: Map[String, Option[WomExpression] => Boolean] = { validations.map(validation => validation.key -> validation.validateOptionalWomExpression _ ).toMap diff --git a/centaur/src/main/resources/standardTestCases/gpu_cuda_image.test b/centaur/src/main/resources/standardTestCases/gpu_cuda_image.test new file mode 100644 index 00000000000..a73457420ce --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/gpu_cuda_image.test @@ -0,0 +1,13 @@ +name: gpu_cuda_image +testFormat: workflowsuccess +backends: [Papi] + +files { + workflow: gpu_on_papi/gpu_cuda_image.wdl +} + +metadata { + status: Succeeded + "outputs.gpu_cuda_image.modprobe_check.0": "good" + "outputs.gpu_cuda_image.smi_check.0": "good" +} diff --git a/centaur/src/main/resources/standardTestCases/gpu_on_papi.test b/centaur/src/main/resources/standardTestCases/gpu_on_papi.test deleted file mode 100644 index c4b646709fe..00000000000 --- a/centaur/src/main/resources/standardTestCases/gpu_on_papi.test +++ /dev/null @@ -1,15 +0,0 @@ -name: gpu_on_papi -testFormat: workflowsuccess -backends: [Papi] - -files { - workflow: gpu_on_papi/gpu_on_papi.wdl -} - -metadata { - status: Succeeded - "outputs.gpu_on_papi.tesla80GpuCount": 1 - "outputs.gpu_on_papi.tesla80GpuType": "https://www.googleapis.com/compute/v1/projects/broad-dsde-cromwell-dev/zones/us-central1-c/acceleratorTypes/nvidia-tesla-k80" - "outputs.gpu_on_papi.tesla100GpuCount": 1 - "outputs.gpu_on_papi.tesla100GpuType": "https://www.googleapis.com/compute/v1/projects/broad-dsde-cromwell-dev/zones/us-central1-c/acceleratorTypes/nvidia-tesla-p100" -} diff --git a/centaur/src/main/resources/standardTestCases/gpu_on_papi/gpu_cuda_image.wdl b/centaur/src/main/resources/standardTestCases/gpu_on_papi/gpu_cuda_image.wdl new file mode 100644 index 00000000000..5ce0a5c33a8 --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/gpu_on_papi/gpu_cuda_image.wdl @@ -0,0 +1,48 @@ +version 1.0 + +workflow gpu_cuda_image { + + input { + Array[String] driver_versions = [ "390.46" ] + } + + scatter (driver_version in driver_versions) { + call get_machine_info { input: driver_version = driver_version } + } + + output { + Array[String] modprobe_check = get_machine_info.modprobe_check + Array[String] smi_check = get_machine_info.smi_check + + Array[File] modprobe_contents = get_machine_info.modprobe_content + Array[File] smi_contents = get_machine_info.smi_content + } +} + +task get_machine_info { + input { + String driver_version + } + + command <<< + nvidia-modprobe --version > modprobe + cat modprobe | grep -q "~{driver_version}" && echo "good" > modprobe_check || echo "bad" > modprobe_check + nvidia-smi > smi + cat smi | grep -q "~{driver_version}" && echo "good" > smi_check || echo "bad" > smi_check + >>> + + runtime { + docker: "nvidia/cuda:9.0-cudnn7-devel-ubuntu16.04" + gpuType: "nvidia-tesla-k80" + gpuCount: 1 + nvidiaDriverVersion: driver_version + zones: "us-central1-c" + } + + output { + String modprobe_check = read_string("modprobe_check") + String smi_check = read_string("smi_check") + File modprobe_content = "modprobe" + File smi_content = "smi" + } +} diff --git a/centaur/src/main/resources/standardTestCases/gpu_on_papi/gpu_on_papi.wdl b/centaur/src/main/resources/standardTestCases/gpu_on_papi/gpu_on_papi.wdl index 42138ab81e9..97b0c8aace0 100644 --- a/centaur/src/main/resources/standardTestCases/gpu_on_papi/gpu_on_papi.wdl +++ b/centaur/src/main/resources/standardTestCases/gpu_on_papi/gpu_on_papi.wdl @@ -1,5 +1,26 @@ +version 1.0 + +workflow gpu_on_papi { + + input { + Array[String] gpus + } + + scatter (gpu in gpus) { + call task_with_gpu { input: gpuTypeInput = gpu } + } + + output { + Array[Int] reported_gpu_counts = task_with_gpu.gpuCount + Array[String] reported_gpu_types = task_with_gpu.gpuType + } +} + + task task_with_gpu { - String gpuTypeInput + input { + String gpuTypeInput + } command { curl "https://www.googleapis.com/compute/v1/projects/broad-dsde-cromwell-dev/zones/us-central1-c/instances/$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/name" -H "Metadata-Flavor: Google")?fields=guestAccelerators" -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" -H 'Accept: application/json' --compressed @@ -18,15 +39,3 @@ task task_with_gpu { zones: ["us-central1-c"] } } - -workflow gpu_on_papi { - call task_with_gpu as task_with_tesla_k80 { input: gpuTypeInput = "nvidia-tesla-k80" } - call task_with_gpu as task_with_tesla_p100 { input: gpuTypeInput = "nvidia-tesla-p100" } - - output { - Int tesla80GpuCount = task_with_tesla_k80.gpuCount - String tesla80GpuType = task_with_tesla_k80.gpuType - Int tesla100GpuCount = task_with_tesla_p100.gpuCount - String tesla100GpuType = task_with_tesla_p100.gpuType - } -} diff --git a/centaur/src/main/resources/standardTestCases/gpu_on_papi/invalid.inputs.json b/centaur/src/main/resources/standardTestCases/gpu_on_papi/invalid.inputs.json new file mode 100644 index 00000000000..a5d8f3ba1ac --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/gpu_on_papi/invalid.inputs.json @@ -0,0 +1,3 @@ +{ + "gpu_on_papi.gpus": [ "nonsense value" ] +} diff --git a/centaur/src/main/resources/standardTestCases/gpu_on_papi/valid.inputs.json b/centaur/src/main/resources/standardTestCases/gpu_on_papi/valid.inputs.json new file mode 100644 index 00000000000..1b490c549b9 --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/gpu_on_papi/valid.inputs.json @@ -0,0 +1,3 @@ +{ + "gpu_on_papi.gpus": [ "nvidia-tesla-k80", "nvidia-tesla-p100", "nvidia-tesla-p4" ] +} diff --git a/centaur/src/main/resources/standardTestCases/gpu_on_papi_invalid.test b/centaur/src/main/resources/standardTestCases/gpu_on_papi_invalid.test new file mode 100644 index 00000000000..3923a73b687 --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/gpu_on_papi_invalid.test @@ -0,0 +1,14 @@ +name: gpu_on_papi_invalid +testFormat: workflowfailure +backends: [Papi] + +files { + workflow: gpu_on_papi/gpu_on_papi.wdl + inputs: gpu_on_papi/invalid.inputs.json +} + +metadata { + status: Failed + + "calls.gpu_on_papi.task_with_gpu.failures.0.message": "Unable to complete PAPI request due to a problem with the request (Error: validating pipeline: unsupported accelerator: \"nonsense value\"). See https://cloud.google.com/compute/docs/gpus/ for a list of supported accelerators." +} diff --git a/centaur/src/main/resources/standardTestCases/gpu_on_papi_valid.test b/centaur/src/main/resources/standardTestCases/gpu_on_papi_valid.test new file mode 100644 index 00000000000..59bc0a29014 --- /dev/null +++ b/centaur/src/main/resources/standardTestCases/gpu_on_papi_valid.test @@ -0,0 +1,18 @@ +name: gpu_on_papi_valid +testFormat: workflowsuccess +backends: [Papi] + +files { + workflow: gpu_on_papi/gpu_on_papi.wdl + inputs: gpu_on_papi/valid.inputs.json +} + +metadata { + status: Succeeded + "outputs.gpu_on_papi.reported_gpu_counts.0": 1 + "outputs.gpu_on_papi.reported_gpu_counts.1": 1 + "outputs.gpu_on_papi.reported_gpu_counts.2": 1 + "outputs.gpu_on_papi.reported_gpu_types.0": "https://www.googleapis.com/compute/v1/projects/broad-dsde-cromwell-dev/zones/us-central1-c/acceleratorTypes/nvidia-tesla-k80" + "outputs.gpu_on_papi.reported_gpu_types.1": "https://www.googleapis.com/compute/v1/projects/broad-dsde-cromwell-dev/zones/us-central1-c/acceleratorTypes/nvidia-tesla-p100" + "outputs.gpu_on_papi.reported_gpu_types.2": "https://www.googleapis.com/compute/v1/projects/broad-dsde-cromwell-dev/zones/us-central1-c/acceleratorTypes/nvidia-tesla-p4" +} diff --git a/src/ci/bin/testCentaurPapiV1.sh b/src/ci/bin/testCentaurPapiV1.sh index c5a577e0993..63885cff153 100755 --- a/src/ci/bin/testCentaurPapiV1.sh +++ b/src/ci/bin/testCentaurPapiV1.sh @@ -25,10 +25,12 @@ export GOOGLE_SERVICE_ACCOUNT_JSON cromwell::build::run_centaur \ -p 100 \ -e localdockertest \ - -e gpu_on_papi \ -e relative_output_paths \ -e relative_output_paths_colliding \ -e standard_output_paths_colliding_prevented \ -e invalidate_bad_caches_jes_no_copy \ + -e gpu_cuda_image \ + -e gpu_on_papi_valid \ + -e gpu_on_papi_invalid cromwell::build::generate_code_coverage diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/GpuTypeValidation.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/GpuTypeValidation.scala index 0867fdd1e7d..b9a3ba17d68 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/GpuTypeValidation.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/GpuTypeValidation.scala @@ -3,19 +3,14 @@ package cromwell.backend.google.pipelines.common import cats.syntax.validated._ import common.validation.ErrorOr.ErrorOr import cromwell.backend.google.pipelines.common.GpuResource.GpuType -import cromwell.backend.google.pipelines.common.GpuResource.GpuType.GpuType -import cromwell.backend.google.pipelines.common.GpuTypeValidation._ import cromwell.backend.validation.{OptionalRuntimeAttributesValidation, RuntimeAttributesValidation} import wom.RuntimeAttributesKeys import wom.types.{WomStringType, WomType} import wom.values.{WomString, WomValue} -import scala.util.{Failure, Success, Try} - object GpuTypeValidation { lazy val instance: RuntimeAttributesValidation[GpuType] = new GpuTypeValidation lazy val optional: OptionalRuntimeAttributesValidation[GpuType] = instance.optional - private val SupportedTypesMessage = s"Supported types are ${GpuResource.GpuType.NVIDIATeslaK80.toString}, ${GpuResource.GpuType.NVIDIATeslaP100.toString}" } class GpuTypeValidation extends RuntimeAttributesValidation[GpuType] { @@ -23,10 +18,7 @@ class GpuTypeValidation extends RuntimeAttributesValidation[GpuType] { override def coercion: Traversable[WomType] = Set(WomStringType) override def validateValue: PartialFunction[WomValue, ErrorOr[GpuType]] = { - case WomString(s) => Try(GpuType.withName(s.toString)) match { - case Success(tpe: GpuType) => tpe.validNel - case Failure(_: NoSuchElementException) => s"$s is not a supported GPU type. $SupportedTypesMessage".invalidNel - case Failure(e) => s"Could not parse $s as a valid GPU type. $SupportedTypesMessage. Error: ${e.getMessage}".invalidNel - } + case WomString(s) => GpuType(s).validNel + case other => s"Invalid '$key': String value required but got ${other.womType.friendlyName}. See ${GpuType.MoreDetailsURL} for a list of options".invalidNel } } diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/GpuValidation.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/GpuValidation.scala index 5d8eeb8e0e6..adbb44565a9 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/GpuValidation.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/GpuValidation.scala @@ -2,6 +2,7 @@ package cromwell.backend.google.pipelines.common import cats.data.NonEmptyList import cats.syntax.either._ +import cats.syntax.validated._ import com.typesafe.config.Config import common.validation.ErrorOr.ErrorOr import cromwell.backend.validation.{OptionalRuntimeAttributesValidation, PositiveIntRuntimeAttributesValidation, RuntimeAttributesValidation} @@ -33,5 +34,7 @@ class GpuValidation(attributeName: String) extends PositiveIntRuntimeAttributesV .leftMap(_ => NonEmptyList.one(s"Expecting $key runtime attribute value greater than 0")) .toValidated } + case other => + s"Invalid gpu count. Expected positive Int but got ${other.womType.friendlyName} ${other.toWomString}".invalidNel } } diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PapiInstrumentation.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PapiInstrumentation.scala index 6fc6fc114f8..656597cf29f 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PapiInstrumentation.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PapiInstrumentation.scala @@ -35,15 +35,15 @@ trait PapiInstrumentation extends CromwellInstrumentation { def abortSuccess() = increment(PapiAbortKey.concatNel(SuccessKey), BackendPrefix) def failedQuery(failedQuery: PAPIApiRequestFailed) = failedQuery.query match { - case _: PAPIStatusPollRequest => increment(PapiPollFailedKey.withGoogleThrowable(failedQuery.cause.e), BackendPrefix) - case _: PAPIRunCreationRequest => increment(PapiRunFailedKey.withGoogleThrowable(failedQuery.cause.e), BackendPrefix) - case _: PAPIAbortRequest => increment(PapiAbortFailedKey.withGoogleThrowable(failedQuery.cause.e), BackendPrefix) + case _: PAPIStatusPollRequest => increment(PapiPollFailedKey.withGoogleThrowable(failedQuery.cause.cause), BackendPrefix) + case _: PAPIRunCreationRequest => increment(PapiRunFailedKey.withGoogleThrowable(failedQuery.cause.cause), BackendPrefix) + case _: PAPIAbortRequest => increment(PapiAbortFailedKey.withGoogleThrowable(failedQuery.cause.cause), BackendPrefix) } def retriedQuery(failedQuery: PAPIApiRequestFailed) = failedQuery.query match { - case _: PAPIStatusPollRequest => increment(PapiPollRetriedKey.withGoogleThrowable(failedQuery.cause.e), BackendPrefix) - case _: PAPIRunCreationRequest => increment(PapiRunRetriedKey.withGoogleThrowable(failedQuery.cause.e), BackendPrefix) - case _: PAPIAbortRequest => increment(PapiAbortRetriedKey.withGoogleThrowable(failedQuery.cause.e), BackendPrefix) + case _: PAPIStatusPollRequest => increment(PapiPollRetriedKey.withGoogleThrowable(failedQuery.cause.cause), BackendPrefix) + case _: PAPIRunCreationRequest => increment(PapiRunRetriedKey.withGoogleThrowable(failedQuery.cause.cause), BackendPrefix) + case _: PAPIAbortRequest => increment(PapiAbortRetriedKey.withGoogleThrowable(failedQuery.cause.cause), BackendPrefix) } def updateQueueSize(size: Int) = sendGauge(PapiKey.concatNel("queue_size"), size.toLong, BackendPrefix) diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiRuntimeAttributes.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiRuntimeAttributes.scala index 7af5e693160..cb36dff48c0 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiRuntimeAttributes.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/PipelinesApiRuntimeAttributes.scala @@ -6,13 +6,13 @@ import cats.syntax.traverse._ import cats.syntax.validated._ import com.typesafe.config.Config import common.validation.ErrorOr._ -import cromwell.backend.google.pipelines.common.GpuResource.GpuType.GpuType +import cromwell.backend.google.pipelines.common.GpuResource.GpuType import cromwell.backend.google.pipelines.common.io.{PipelinesApiAttachedDisk, PipelinesApiWorkingDisk} import cromwell.backend.standard.StandardValidatedRuntimeAttributesBuilder import cromwell.backend.validation.{BooleanRuntimeAttributesValidation, _} +import eu.timepit.refined._ import eu.timepit.refined.api.Refined import eu.timepit.refined.numeric.Positive -import net.ceedubs.ficus.Ficus._ import wdl4s.parser.MemoryUnit import wom.RuntimeAttributesKeys import wom.format.MemorySize @@ -21,10 +21,17 @@ import wom.values._ object GpuResource { val DefaultNvidiaDriverVersion = "390.46" - object GpuType extends Enumeration { - type GpuType = Value - val NVIDIATeslaP100 = Value("nvidia-tesla-p100") - val NVIDIATeslaK80 = Value("nvidia-tesla-k80") + + final case class GpuType(name: String) { + override def toString: String = name + } + object GpuType { + val NVIDIATeslaP100 = GpuType("nvidia-tesla-p100") + val NVIDIATeslaK80 = GpuType("nvidia-tesla-k80") + + val DefaultGpuType: GpuType = NVIDIATeslaK80 + val DefaultGpuCount: Int Refined Positive = refineMV[Positive](1) + val MoreDetailsURL = "https://cloud.google.com/compute/docs/gpus/" } } @@ -78,7 +85,10 @@ object PipelinesApiRuntimeAttributes { private def gpuTypeValidation(runtimeConfig: Option[Config]): OptionalRuntimeAttributesValidation[GpuType] = GpuTypeValidation.optional - private def gpuValidation(runtimeConfig: Option[Config]): OptionalRuntimeAttributesValidation[Int Refined Positive] = GpuValidation.optional + val GpuDriverVersionKey = "nvidiaDriverVersion" + private def gpuDriverValidation(runtimeConfig: Option[Config]): OptionalRuntimeAttributesValidation[String] = new StringRuntimeAttributesValidation(GpuDriverVersionKey).optional + + private def gpuCountValidation(runtimeConfig: Option[Config]): OptionalRuntimeAttributesValidation[Int Refined Positive] = GpuValidation.optional private def gpuMinValidation(runtimeConfig: Option[Config]):OptionalRuntimeAttributesValidation[Int Refined Positive] = GpuValidation.optionalMin @@ -130,8 +140,9 @@ object PipelinesApiRuntimeAttributes { def runtimeAttributesBuilder(jesConfiguration: PipelinesApiConfiguration): StandardValidatedRuntimeAttributesBuilder = { val runtimeConfig = jesConfiguration.runtimeConfig StandardValidatedRuntimeAttributesBuilder.default(runtimeConfig).withValidation( - gpuValidation(runtimeConfig), + gpuCountValidation(runtimeConfig), gpuTypeValidation(runtimeConfig), + gpuDriverValidation(runtimeConfig), cpuValidation(runtimeConfig), cpuMinValidation(runtimeConfig), gpuMinValidation(runtimeConfig), @@ -155,14 +166,14 @@ object PipelinesApiRuntimeAttributes { val cpuPlatform: Option[String] = RuntimeAttributesValidation.extractOption(cpuPlatformValidation(runtimeAttrsConfig).key, validatedRuntimeAttributes) // GPU - lazy val nvidiaDriverVersion = runtimeAttrsConfig.flatMap(_.as[Option[String]]("nvidia-driver-version")).getOrElse(GpuResource.DefaultNvidiaDriverVersion) - val gpuType: Option[GpuType] = RuntimeAttributesValidation.extractOption(gpuTypeValidation(runtimeAttrsConfig).key, validatedRuntimeAttributes) - val gpu: Option[Int Refined Positive] = RuntimeAttributesValidation.extractOption(gpuValidation(runtimeAttrsConfig).key, validatedRuntimeAttributes) - val gpuResource = (gpuType, gpu) match { - case (Some(t), Some(g)) => Option(GpuResource(t, g, nvidiaDriverVersion)) - case (Some(_), None) => throw new RuntimeException(s"Please specify how many GPU should be attached to the instance.") - case (None, Some(_)) => throw new RuntimeException(s"Please specify a GPU type: ${GpuResource.GpuType.values.mkString(", ")}") - case (None, None) => None + lazy val gpuType: Option[GpuType] = RuntimeAttributesValidation.extractOption(gpuTypeValidation(runtimeAttrsConfig).key, validatedRuntimeAttributes) + lazy val gpuCount: Option[Int Refined Positive] = RuntimeAttributesValidation.extractOption(gpuCountValidation(runtimeAttrsConfig).key, validatedRuntimeAttributes) + lazy val gpuDriver: Option[String] = RuntimeAttributesValidation.extractOption(gpuDriverValidation(runtimeAttrsConfig).key, validatedRuntimeAttributes) + + val gpuResource: Option[GpuResource] = if (gpuType.isDefined || gpuCount.isDefined || gpuDriver.isDefined) { + Option(GpuResource(gpuType.getOrElse(GpuType.DefaultGpuType), gpuCount.getOrElse(GpuType.DefaultGpuCount), gpuDriver.getOrElse(GpuResource.DefaultNvidiaDriverVersion))) + } else { + None } val zones: Vector[String] = RuntimeAttributesValidation.extract(ZonesValidation, validatedRuntimeAttributes) diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/PipelinesApiRequestManager.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/PipelinesApiRequestManager.scala index fd8182c06fd..c94a62db9a1 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/PipelinesApiRequestManager.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/PipelinesApiRequestManager.scala @@ -27,7 +27,7 @@ import scala.concurrent.duration._ import scala.util.control.NoStackTrace /** - * Holds a set of PAPI request until a JesQueryActor pulls the work. + * Holds a set of PAPI request until a PipelinesApiRequestActor pulls the work. */ class PipelinesApiRequestManager(val qps: Int Refined Positive, requestWorkers: Int Refined Positive, override val serviceRegistryActor: ActorRef) (implicit batchHandler: PipelinesApiRequestHandler) extends Actor @@ -64,16 +64,16 @@ class PipelinesApiRequestManager(val qps: Int Refined Positive, requestWorkers: * */ private val maxBatchRequestSize: Long = 14L * 1024L * 1024L - private val requestTooLargeException = new PAPIApiException(new IllegalArgumentException( - "The task run request has exceeded the maximum PAPI request size." + - "If you have a task with a very large number of inputs and / or outputs in your workflow you should try to reduce it. " + + private val requestTooLargeException = new UserPAPIApiException( + cause = new IllegalArgumentException(s"The task run request has exceeded the maximum PAPI request size ($maxBatchRequestSize bytes)."), + helpfulHint = Option("If you have a task with a very large number of inputs and / or outputs in your workflow you should try to reduce it. " + "Depending on your case you could: 1) Zip your input files together and unzip them in the command. 2) Use a file of file names " + - "and localize the files yourself." - )) + "and localize the files yourself.") + ) private[api] lazy val nbWorkers = requestWorkers.value - // + // private lazy val workerBatchInterval = determineBatchInterval(qps) * nbWorkers.toLong @@ -110,7 +110,8 @@ class PipelinesApiRequestManager(val qps: Int Refined Positive, requestWorkers: } else workQueue :+= create case abort: PAPIAbortRequest => workQueue :+= abort case PipelinesWorkerRequestWork(maxBatchSize) => handleWorkerAskingForWork(sender, maxBatchSize) - case failure: PAPIApiRequestFailed => handleQueryFailure(failure) + case failure: PAPIApiRequestFailed => + handleQueryFailure(failure) case Terminated(actorRef) => onFailure(actorRef, new RuntimeException("PipelinesApiRequestHandler actor termination caught by manager") with NoStackTrace) case other => log.error(s"Unexpected message from {} to ${this.getClass.getSimpleName}: {}", sender().path.name, other) } @@ -137,16 +138,32 @@ class PipelinesApiRequestManager(val qps: Int Refined Positive, requestWorkers: }) } - private def handleQueryFailure(failure: PAPIApiRequestFailed) = if (failure.query.failedAttempts < maxRetries) { - val nextRequest = failure.query.withFailedAttempt - val delay = nextRequest.backoff.backoffMillis.millis - queriesWaitingForRetry = queriesWaitingForRetry + nextRequest - timers.startSingleTimer(nextRequest, nextRequest, delay) - failedQuery(failure) - } else { - retriedQuery(failure) - log.warning(s"PAPI request workers tried and failed $maxRetries times to make ${failure.query.getClass.getSimpleName} request to PAPI") - failure.query.requester ! failure + private def handleQueryFailure(failure: PAPIApiRequestFailed) = { + val userError: Boolean = failure.cause.isInstanceOf[UserPAPIApiException] + + def failQuery(): Unit = { + // NB: we don't count user errors towards the PAPI failed queries count in our metrics: + if (!userError) { + failedQuery(failure) + log.warning(s"PAPI request workers tried and failed ${failure.query.failedAttempts} times to make ${failure.query.getClass.getSimpleName} request to PAPI") + } + + failure.query.requester ! failure + } + + def retryQuery(): Unit = { + retriedQuery(failure) + val nextRequest = failure.query.withFailedAttempt + val delay = nextRequest.backoff.backoffMillis.millis + queriesWaitingForRetry = queriesWaitingForRetry + nextRequest + timers.startSingleTimer(nextRequest, nextRequest, delay) + } + + if (userError || failure.query.failedAttempts >= maxRetries ) { + failQuery() + } else { + retryQuery() + } } private def handleWorkerAskingForWork(papiRequestWorkerActor: ActorRef, maxBatchSize: Int) = { @@ -197,13 +214,13 @@ class PipelinesApiRequestManager(val qps: Int Refined Positive, requestWorkers: work.workBatch.toList.foreach { case statusQuery: PAPIStatusPollRequest => statusPolls += 1 - self ! PipelinesApiStatusQueryFailed(statusQuery, new PAPIApiException(throwable)) + self ! PipelinesApiStatusQueryFailed(statusQuery, new SystemPAPIApiException(throwable)) case runCreationQuery: PAPIRunCreationRequest => runCreations += 1 - self ! PipelinesApiRunCreationQueryFailed(runCreationQuery, new PAPIApiException(throwable)) + self ! PipelinesApiRunCreationQueryFailed(runCreationQuery, new SystemPAPIApiException(throwable)) case abortQuery: PAPIAbortRequest => abortRequests += 1 - self ! PipelinesApiAbortQueryFailed(abortQuery, new PAPIApiException(throwable)) + self ! PipelinesApiAbortQueryFailed(abortQuery, new SystemPAPIApiException(throwable)) } log.warning( @@ -328,7 +345,16 @@ object PipelinesApiRequestManager { override def getMessage: String = e.getMessage } - class PAPIApiException(val e: Throwable) extends RuntimeException(e) with CromwellFatalExceptionMarker { - override def getMessage: String = "Unable to complete PAPI request" + sealed trait PAPIApiException extends RuntimeException with CromwellFatalExceptionMarker { + def cause: Throwable + override def getCause: Throwable = cause + } + + class SystemPAPIApiException(val cause: Throwable) extends PAPIApiException { + override def getMessage: String = s"Unable to complete PAPI request due to system or connection error (${cause.getMessage})" + } + + final class UserPAPIApiException(val cause: Throwable, helpfulHint: Option[String]) extends PAPIApiException { + override def getMessage: String = s"Unable to complete PAPI request due to a problem with the request (${cause.getMessage}). ${helpfulHint.getOrElse("")}" } } diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/PipelinesApiRequestWorker.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/PipelinesApiRequestWorker.scala index e94d92cc713..9d842983d15 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/PipelinesApiRequestWorker.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/PipelinesApiRequestWorker.scala @@ -65,11 +65,7 @@ class PipelinesApiRequestWorker(val pollingManager: ActorRef, val batchInterval: scheduleCheckForWork() case Success(someFailures) => val errors = someFailures collect { case Failure(t) => t.getMessage } - if (log.isDebugEnabled) { - log.warning("PAPI request worker had {} failures making {} requests: {}", errors.size, someFailures.size, errors.mkString(", ")) - } else { - log.warning("PAPI request worker had {} failures making {} requests", errors.size, someFailures.size) - } + log.warning("PAPI request worker had {} failures making {} requests: {}", errors.size, someFailures.size, errors.mkString(System.lineSeparator, "," + System.lineSeparator, "")) scheduleCheckForWork() case Failure(t) => // NB: Should be impossible since we only ever do completionPromise.trySuccess() diff --git a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/clients/PipelinesApiRunCreationClient.scala b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/clients/PipelinesApiRunCreationClient.scala index f6322e05b9a..0ff1920b732 100644 --- a/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/clients/PipelinesApiRunCreationClient.scala +++ b/supportedBackends/google/pipelines/common/src/main/scala/cromwell/backend/google/pipelines/common/api/clients/PipelinesApiRunCreationClient.scala @@ -3,7 +3,7 @@ package cromwell.backend.google.pipelines.common.api.clients import akka.actor.{Actor, ActorLogging, ActorRef} import cromwell.backend.google.pipelines.common.PapiInstrumentation import cromwell.backend.google.pipelines.common.api.PipelinesApiRequestFactory.CreatePipelineParameters -import cromwell.backend.google.pipelines.common.api.PipelinesApiRequestManager.{PAPIApiException, PipelinesApiRunCreationQueryFailed} +import cromwell.backend.google.pipelines.common.api.PipelinesApiRequestManager.{PipelinesApiRunCreationQueryFailed, SystemPAPIApiException} import cromwell.backend.google.pipelines.common.api.{PipelinesApiRequestFactory, PipelinesApiRequestManager} import cromwell.backend.standard.StandardAsyncJob import cromwell.core.WorkflowId @@ -18,7 +18,7 @@ object PipelinesApiRunCreationClient { * Exception used to represent the fact that a job was aborted before a creation attempt was made. * Meaning it was in the queue when the abort request was made, so it was just removed from the queue. */ - case object JobAbortedException extends PAPIApiException(new Exception("The job was removed from the queue before a PAPI creation request was made")) + case object JobAbortedException extends SystemPAPIApiException(new Exception("The job was removed from the queue before a PAPI creation request was made")) } /** diff --git a/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/PipelinesApiGpuAttributesSpec.scala b/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/PipelinesApiGpuAttributesSpec.scala new file mode 100644 index 00000000000..786ff174c23 --- /dev/null +++ b/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/PipelinesApiGpuAttributesSpec.scala @@ -0,0 +1,79 @@ +package cromwell.backend.google.pipelines.common + +import cromwell.backend.google.pipelines.common.GpuResource.GpuType +import cromwell.backend.google.pipelines.common.PipelinesApiTestConfig.papiConfiguration +import org.scalatest.{Matchers, WordSpecLike} +import wom.values.{WomFloat, WomInteger, WomSingleFile, WomString, WomValue} + +class PipelinesApiGpuAttributesSpec + extends WordSpecLike + with Matchers + with PipelinesApiRuntimeAttributesSpecsMixin { + + val validGpuTypes = List( + (Option(WomString("nvidia-tesla-k80")), Option(GpuType.NVIDIATeslaK80)), + (Option(WomString("nvidia-tesla-p100")), Option( GpuType.NVIDIATeslaP100)), + (Option(WomString("custom-gpu-24601")), Option( GpuType("custom-gpu-24601"))), + (None, None)) + val invalidGpuTypes = List( + WomSingleFile("nvidia-tesla-k80"), + WomInteger(100)) + + val validGpuCounts = List( + (Option(WomInteger(1)), Option(1)), + (Option(WomInteger(100)), Option(100)), + (None, None) + ) + val invalidGpuCounts = List( + WomString("ten"), + WomFloat(1.0)) + + validGpuTypes foreach { case (validGpuType, expectedGpuTypeValue) => + validGpuCounts foreach { case (validGpuCount, expectedGpuCountValue) => + s"validate the valid gpu type '$validGpuType' and count '$validGpuCount'" in { + val runtimeAttributes = Map( + "docker" -> WomString("ubuntu:latest") + ) ++ validGpuType.map(t => "gpuType" -> t) ++ validGpuCount.map(c => "gpuCount" -> c) + + val actualRuntimeAttributes = toPapiRuntimeAttributes(runtimeAttributes, emptyWorkflowOptions, papiConfiguration) + + expectedGpuTypeValue match { + case Some(v) => actualRuntimeAttributes.gpuResource.exists(_.gpuType == v) + case None => actualRuntimeAttributes.gpuResource.foreach(_.gpuType == GpuType.DefaultGpuType) + } + + expectedGpuCountValue match { + case Some(v) => actualRuntimeAttributes.gpuResource.exists(_.gpuCount.value == v) + case None => actualRuntimeAttributes.gpuResource.foreach(_.gpuCount.value == GpuType.DefaultGpuCount.value) + } + + } + } + + invalidGpuCounts foreach { invalidGpuCount => + s"not validate a valid gpu type '$validGpuType' but an invalid gpu count '$invalidGpuCount'" in { + val runtimeAttributes: Map[String, WomValue] = Map( + "docker" -> WomString("ubuntu:latest") + ) ++ validGpuType.map(t => "gpuType" -> t) + ("gpuCount" -> invalidGpuCount) + + assertPapiRuntimeAttributesFailedCreation( + runtimeAttributes, + s"Invalid gpu count. Expected positive Int but got") + } + } + } + + invalidGpuTypes foreach { invalidGpuType => + invalidGpuCounts foreach { invalidGpuCount => + s"not validate a invalid gpu type '$invalidGpuType' and invalid gpu count '$invalidGpuCount'" in { + val runtimeAttributes: Map[String, WomValue] = Map( + "docker" -> WomString("ubuntu:latest") + ) + ("gpuType" -> invalidGpuType) + ("gpuCount" -> invalidGpuCount) + + assertPapiRuntimeAttributesFailedCreation( + runtimeAttributes, + s"Invalid gpu count. Expected positive Int but got") + } + } + } +} diff --git a/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/PipelinesApiRuntimeAttributesSpec.scala b/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/PipelinesApiRuntimeAttributesSpec.scala index 3dfc67b4f1a..036efb33ede 100644 --- a/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/PipelinesApiRuntimeAttributesSpec.scala +++ b/supportedBackends/google/pipelines/common/src/test/scala/cromwell/backend/google/pipelines/common/PipelinesApiRuntimeAttributesSpec.scala @@ -2,13 +2,12 @@ package cromwell.backend.google.pipelines.common import cats.data.NonEmptyList import cromwell.backend.RuntimeAttributeDefinition -import cromwell.backend.google.pipelines.common.GpuResource.GpuType import cromwell.backend.google.pipelines.common.PipelinesApiTestConfig.{googleConfiguration, papiAttributes, _} import cromwell.backend.google.pipelines.common.io.{DiskType, PipelinesApiAttachedDisk, PipelinesApiWorkingDisk} import cromwell.backend.validation.{ContinueOnReturnCodeFlag, ContinueOnReturnCodeSet} import cromwell.core.WorkflowOptions import eu.timepit.refined.refineMV -import org.scalatest.{Matchers, WordSpecLike} +import org.scalatest.{Matchers, TestSuite, WordSpecLike} import org.slf4j.helpers.NOPLogger import org.specs2.mock.Mockito import spray.json._ @@ -17,222 +16,182 @@ import wom.format.MemorySize import wom.types._ import wom.values._ -class PipelinesApiRuntimeAttributesSpec extends WordSpecLike with Matchers with Mockito { +import scala.util.{Failure, Success, Try} - def workflowOptionsWithDefaultRA(defaults: Map[String, JsValue]): WorkflowOptions = { - WorkflowOptions(JsObject(Map( - "default_runtime_attributes" -> JsObject(defaults) - ))) - } - - val expectedDefaults = new PipelinesApiRuntimeAttributes(refineMV(1), None, None, Vector("us-central1-b", "us-central1-a"), 0, 10, - MemorySize(2, MemoryUnit.GB), Vector(PipelinesApiWorkingDisk(DiskType.SSD, 10)), "ubuntu:latest", false, - ContinueOnReturnCodeSet(Set(0)), false) +final class PipelinesApiRuntimeAttributesSpec + extends WordSpecLike + with Matchers + with Mockito + with PipelinesApiRuntimeAttributesSpecsMixin { "PipelinesApiRuntimeAttributes" should { "throw an exception when there are no runtime attributes defined." in { val runtimeAttributes = Map.empty[String, WomValue] - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Can't find an attribute value for key docker") + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, "Can't find an attribute value for key docker") } "use hardcoded defaults if not declared in task, workflow options, or config (except for docker)" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest")) val expectedRuntimeAttributes = expectedDefaults - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes, jesConfiguration = noDefaultsJesConfiguration) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes, papiConfiguration = noDefaultsPapiConfiguration) } "validate a valid Docker entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest")) val expectedRuntimeAttributes = expectedDefaults - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "fail to validate an invalid Docker entry" in { val runtimeAttributes = Map("docker" -> WomInteger(1)) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting docker runtime attribute to be a String") + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting docker runtime attribute to be a String") } "validate a valid failOnStderr entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "failOnStderr" -> WomBoolean(true)) val expectedRuntimeAttributes = expectedDefaults.copy(failOnStderr = true) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "fail to validate an invalid failOnStderr entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "failOnStderr" -> WomString("yes")) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting failOnStderr runtime attribute to be a Boolean or a String with values of 'true' or 'false'") + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting failOnStderr runtime attribute to be a Boolean or a String with values of 'true' or 'false'") } "validate a valid continueOnReturnCode integer entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "continueOnReturnCode" -> WomInteger(1)) val expectedRuntimeAttributes = expectedDefaults.copy(continueOnReturnCode = ContinueOnReturnCodeSet(Set(1))) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "validate a valid continueOnReturnCode boolean entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "continueOnReturnCode" -> WomBoolean(false)) val expectedRuntimeAttributes = expectedDefaults.copy(continueOnReturnCode = ContinueOnReturnCodeFlag(false)) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "validate a valid continueOnReturnCode array entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "continueOnReturnCode" -> WomArray(WomArrayType(WomIntegerType), Array(WomInteger(1), WomInteger(2)))) val expectedRuntimeAttributes = expectedDefaults.copy(continueOnReturnCode = ContinueOnReturnCodeSet(Set(1, 2))) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "coerce then validate a valid continueOnReturnCode array entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "continueOnReturnCode" -> WomArray(WomArrayType(WomStringType), Array(WomString("1"), WomString("2")))) val expectedRuntimeAttributes = expectedDefaults.copy(continueOnReturnCode = ContinueOnReturnCodeSet(Set(1, 2))) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "fail to validate an invalid continueOnReturnCode entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "continueOnReturnCode" -> WomString("value")) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting continueOnReturnCode runtime attribute to be either a Boolean, a String 'true' or 'false', or an Array[Int]") - } - - "validate a valid gpu entry (1)" in { - val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "gpuCount" -> WomInteger(1), "gpuType" -> WomString("nvidia-tesla-k80")) - val expectedRuntimeAttributes = expectedDefaults.copy(gpuResource = Option(GpuResource(gpuCount = refineMV(1), gpuType = GpuType.NVIDIATeslaK80))) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) - } - - "validate a valid gpu entry (2)" in { - val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "gpuCount" -> WomInteger(2), "gpuType" -> WomString("nvidia-tesla-p100")) - val expectedRuntimeAttributes = expectedDefaults.copy(gpuResource = Option(GpuResource(gpuCount = refineMV(2), gpuType = GpuType.NVIDIATeslaP100))) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) - } - - // Missing gpu type - "fail to validate an invalid gpu entry (1)" in { - val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "gpuCount" -> WomInteger(1)) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Please specify a GPU type: nvidia-tesla-p100, nvidia-tesla-k80") - } - - // Missing gpu count - "fail to validate an invalid gpu entry (2)" in { - val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "gpuType" -> WomString("nvidia-tesla-p100")) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Please specify how many GPU should be attached to the instance.") - } - - // unrecoginzed gpu type - "fail to validate an invalid gpu entry (3)" in { - val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "gpuCount" -> WomInteger(1), "gpuType" -> WomString("not-a-gpu")) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "not-a-gpu is not a supported GPU type. Supported types are nvidia-tesla-k80, nvidia-tesla-p100") - } - - // gpu count is not an int - "fail to validate an invalid gpu entry (4)" in { - val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "gpuCount" -> WomString("value")) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting gpuCount runtime attribute to be an Integer") + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting continueOnReturnCode runtime attribute to be either a Boolean, a String 'true' or 'false', or an Array[Int]") } "validate a valid cpu entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "cpu" -> WomInteger(2)) val expectedRuntimeAttributes = expectedDefaults.copy(cpu = refineMV(2)) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "validate a valid cpu string entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "cpu" -> WomString("2")) val expectedRuntimeAttributes = expectedDefaults.copy(cpu = refineMV(2)) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "fail to validate an invalid cpu entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "cpu" -> WomString("value")) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting cpu runtime attribute to be an Integer") + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting cpu runtime attribute to be an Integer") } "validate a valid zones entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "zones" -> WomString("us-central-z")) val expectedRuntimeAttributes = expectedDefaults.copy(zones = Vector("us-central-z")) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "fail to validate an invalid zones entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "zones" -> WomInteger(1)) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting zones runtime attribute to be either a whitespace separated String or an Array[String]") + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting zones runtime attribute to be either a whitespace separated String or an Array[String]") } "validate a valid array zones entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "zones" -> WomArray(WomArrayType(WomStringType), Array(WomString("us-central1-y"), WomString("us-central1-z")))) val expectedRuntimeAttributes = expectedDefaults.copy(zones = Vector("us-central1-y", "us-central1-z")) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "fail to validate an invalid array zones entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "zones" -> WomArray(WomArrayType(WomIntegerType), Array(WomInteger(1), WomInteger(2)))) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting zones runtime attribute to be either a whitespace separated String or an Array[String]") + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting zones runtime attribute to be either a whitespace separated String or an Array[String]") } "validate a valid preemptible entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "preemptible" -> WomInteger(3)) val expectedRuntimeAttributes = expectedDefaults.copy(preemptible = 3) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "fail to validate an invalid preemptible entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "preemptible" -> WomString("value")) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting preemptible runtime attribute to be an Integer") } "validate a valid bootDiskSizeGb entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "bootDiskSizeGb" -> WomInteger(4)) val expectedRuntimeAttributes = expectedDefaults.copy(bootDiskSize = 4) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "fail to validate an invalid bootDiskSizeGb entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "bootDiskSizeGb" -> WomString("4GB")) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting bootDiskSizeGb runtime attribute to be an Integer") + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting bootDiskSizeGb runtime attribute to be an Integer") } "validate a valid disks entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "disks" -> WomString("local-disk 20 SSD")) val expectedRuntimeAttributes = expectedDefaults.copy(disks = Seq(PipelinesApiAttachedDisk.parse("local-disk 20 SSD").get)) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "fail to validate an invalid disks entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "disks" -> WomInteger(10)) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting disks runtime attribute to be a comma separated String or Array[String]") + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting disks runtime attribute to be a comma separated String or Array[String]") } "validate a valid disks array entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "disks" -> WomArray(WomArrayType(WomStringType), Array(WomString("local-disk 20 SSD"), WomString("local-disk 30 SSD")))) val expectedRuntimeAttributes = expectedDefaults.copy(disks = Seq(PipelinesApiAttachedDisk.parse("local-disk 20 SSD").get, PipelinesApiAttachedDisk.parse("local-disk 30 SSD").get)) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "fail to validate a valid disks array entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "disks" -> WomArray(WomArrayType(WomStringType), Array(WomString("blah"), WomString("blah blah")))) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Disk strings should be of the format 'local-disk SIZE TYPE' or '/mount/point SIZE TYPE'") + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, "Disk strings should be of the format 'local-disk SIZE TYPE' or '/mount/point SIZE TYPE'") } "validate a valid memory entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "memory" -> WomString("1 GB")) val expectedRuntimeAttributes = expectedDefaults.copy(memory = MemorySize.parse("1 GB").get) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "fail to validate an invalid memory entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "memory" -> WomString("blah")) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting memory runtime attribute to be an Integer or String with format '8 GB'") + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting memory runtime attribute to be an Integer or String with format '8 GB'") } "validate a valid noAddress entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "noAddress" -> WomBoolean(true)) val expectedRuntimeAttributes = expectedDefaults.copy(noAddress = true) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes) } "fail to validate an invalid noAddress entry" in { val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest"), "noAddress" -> WomInteger(1)) - assertJesRuntimeAttributesFailedCreation(runtimeAttributes, + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, "Expecting noAddress runtime attribute to be a Boolean") } @@ -247,7 +206,7 @@ class PipelinesApiRuntimeAttributesSpec extends WordSpecLike with Matchers with val workflowOptions = WorkflowOptions.fromJsonObject(workflowOptionsJson).get val expectedRuntimeAttributes = expectedDefaults.copy(cpu = refineMV(2)) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes, workflowOptions) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes, workflowOptions) } "override config default runtime attributes with task runtime attributes" in { @@ -261,7 +220,7 @@ class PipelinesApiRuntimeAttributesSpec extends WordSpecLike with Matchers with val workflowOptions = WorkflowOptions.fromJsonObject(workflowOptionsJson).get val expectedRuntimeAttributes = expectedDefaults.copy(cpu = refineMV(4)) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes, workflowOptions) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes, workflowOptions) } "override invalid config default attributes with task runtime attributes" in { @@ -275,29 +234,42 @@ class PipelinesApiRuntimeAttributesSpec extends WordSpecLike with Matchers with val workflowOptions = WorkflowOptions.fromJsonObject(workflowOptionsJson).get val expectedRuntimeAttributes = expectedDefaults.copy(cpu = refineMV(4)) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes, workflowOptions) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes, workflowOptions) + } + + "parse cpuPlatform correctly" in { + val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest")) + val workflowOptionsJson = + """{ + | "default_runtime_attributes": { "cpuPlatform": "the platform" } + |} + """.stripMargin.parseJson.asInstanceOf[JsObject] + val workflowOptions = WorkflowOptions.fromJsonObject(workflowOptionsJson).get + val expectedRuntimeAttributes = expectedDefaults.copy(cpuPlatform = Option("the platform")) + assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes, workflowOptions) } } +} + +trait PipelinesApiRuntimeAttributesSpecsMixin { this: TestSuite => - "should parse cpuPlatform correctly" in { - val runtimeAttributes = Map("docker" -> WomString("ubuntu:latest")) - val workflowOptionsJson = - """{ - | "default_runtime_attributes": { "cpuPlatform": "the platform" } - |} - """.stripMargin.parseJson.asInstanceOf[JsObject] - val workflowOptions = WorkflowOptions.fromJsonObject(workflowOptionsJson).get - val expectedRuntimeAttributes = expectedDefaults.copy(cpuPlatform = Option("the platform")) - assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes, expectedRuntimeAttributes, workflowOptions) + def workflowOptionsWithDefaultRA(defaults: Map[String, JsValue]): WorkflowOptions = { + WorkflowOptions(JsObject(Map( + "default_runtime_attributes" -> JsObject(defaults) + ))) } - private def assertJesRuntimeAttributesSuccessfulCreation(runtimeAttributes: Map[String, WomValue], - expectedRuntimeAttributes: PipelinesApiRuntimeAttributes, - workflowOptions: WorkflowOptions = emptyWorkflowOptions, - defaultZones: NonEmptyList[String] = defaultZones, - jesConfiguration: PipelinesApiConfiguration = papiConfiguration): Unit = { + val expectedDefaults = new PipelinesApiRuntimeAttributes(refineMV(1), None, None, Vector("us-central1-b", "us-central1-a"), 0, 10, + MemorySize(2, MemoryUnit.GB), Vector(PipelinesApiWorkingDisk(DiskType.SSD, 10)), "ubuntu:latest", false, + ContinueOnReturnCodeSet(Set(0)), false) + + def assertPapiRuntimeAttributesSuccessfulCreation(runtimeAttributes: Map[String, WomValue], + expectedRuntimeAttributes: PipelinesApiRuntimeAttributes, + workflowOptions: WorkflowOptions = emptyWorkflowOptions, + defaultZones: NonEmptyList[String] = defaultZones, + papiConfiguration: PipelinesApiConfiguration = papiConfiguration): Unit = { try { - val actualRuntimeAttributes = toJesRuntimeAttributes(runtimeAttributes, workflowOptions, jesConfiguration) + val actualRuntimeAttributes = toPapiRuntimeAttributes(runtimeAttributes, workflowOptions, papiConfiguration) assert(actualRuntimeAttributes == expectedRuntimeAttributes) } catch { case ex: RuntimeException => fail(s"Exception was not expected but received: ${ex.getMessage}") @@ -305,31 +277,36 @@ class PipelinesApiRuntimeAttributesSpec extends WordSpecLike with Matchers with () } - private def assertJesRuntimeAttributesFailedCreation(runtimeAttributes: Map[String, WomValue], - exMsg: String, - workflowOptions: WorkflowOptions = emptyWorkflowOptions): Unit = { - try { - toJesRuntimeAttributes(runtimeAttributes, workflowOptions, papiConfiguration) - fail(s"A RuntimeException was expected with message: $exMsg") - } catch { - case ex: RuntimeException => assert(ex.getMessage.contains(exMsg)) + def assertPapiRuntimeAttributesFailedCreation(runtimeAttributes: Map[String, WomValue], + exMsgs: List[String], + workflowOptions: WorkflowOptions): Unit = { + Try(toPapiRuntimeAttributes(runtimeAttributes, workflowOptions, papiConfiguration)) match { + case Success(oops) => + fail(s"Expected error containing strings: ${exMsgs.map(s => s"'$s'").mkString(", ")} but instead got Success($oops)") + case Failure(ex) => exMsgs foreach { exMsg => assert(ex.getMessage.contains(exMsg)) } } () } - private def toJesRuntimeAttributes(runtimeAttributes: Map[String, WomValue], - workflowOptions: WorkflowOptions, - jesConfiguration: PipelinesApiConfiguration): PipelinesApiRuntimeAttributes = { - val runtimeAttributesBuilder = PipelinesApiRuntimeAttributes.runtimeAttributesBuilder(jesConfiguration) + def assertPapiRuntimeAttributesFailedCreation(runtimeAttributes: Map[String, WomValue], + exMsg: String, + workflowOptions: WorkflowOptions = emptyWorkflowOptions): Unit = { + assertPapiRuntimeAttributesFailedCreation(runtimeAttributes, List(exMsg), workflowOptions) + } + + def toPapiRuntimeAttributes(runtimeAttributes: Map[String, WomValue], + workflowOptions: WorkflowOptions, + papiConfiguration: PipelinesApiConfiguration): PipelinesApiRuntimeAttributes = { + val runtimeAttributesBuilder = PipelinesApiRuntimeAttributes.runtimeAttributesBuilder(papiConfiguration) val defaultedAttributes = RuntimeAttributeDefinition.addDefaultsToAttributes( staticRuntimeAttributeDefinitions, workflowOptions)(runtimeAttributes) val validatedRuntimeAttributes = runtimeAttributesBuilder.build(defaultedAttributes, NOPLogger.NOP_LOGGER) - PipelinesApiRuntimeAttributes(validatedRuntimeAttributes, jesConfiguration.runtimeConfig) + PipelinesApiRuntimeAttributes(validatedRuntimeAttributes, papiConfiguration.runtimeConfig) } - private val emptyWorkflowOptions = WorkflowOptions.fromMap(Map.empty).get - private val defaultZones = NonEmptyList.of("us-central1-b", "us-central1-a") - private val noDefaultsJesConfiguration = new PipelinesApiConfiguration(PipelinesApiTestConfig.NoDefaultsConfigurationDescriptor, genomicsFactory, googleConfiguration, papiAttributes) - private val staticRuntimeAttributeDefinitions: Set[RuntimeAttributeDefinition] = + val emptyWorkflowOptions = WorkflowOptions.fromMap(Map.empty).get + val defaultZones = NonEmptyList.of("us-central1-b", "us-central1-a") + val noDefaultsPapiConfiguration = new PipelinesApiConfiguration(PipelinesApiTestConfig.NoDefaultsConfigurationDescriptor, genomicsFactory, googleConfiguration, papiAttributes) + val staticRuntimeAttributeDefinitions: Set[RuntimeAttributeDefinition] = PipelinesApiRuntimeAttributes.runtimeAttributesBuilder(PipelinesApiTestConfig.papiConfiguration).definitions.toSet } diff --git a/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/api/request/AbortRequestHandler.scala b/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/api/request/AbortRequestHandler.scala index 3da2da0cc13..331cab81682 100644 --- a/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/api/request/AbortRequestHandler.scala +++ b/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/api/request/AbortRequestHandler.scala @@ -6,7 +6,7 @@ import com.google.api.client.googleapis.batch.json.JsonBatchCallback import com.google.api.client.googleapis.json.{GoogleJsonError, GoogleJsonErrorContainer} import com.google.api.client.http.{HttpHeaders, HttpRequest} import com.google.api.services.genomics.model.Operation -import cromwell.backend.google.pipelines.common.api.PipelinesApiRequestManager.{GoogleJsonException, PipelinesApiAbortQueryFailed, PAPIAbortRequest, PAPIApiException} +import cromwell.backend.google.pipelines.common.api.PipelinesApiRequestManager._ import cromwell.backend.google.pipelines.common.api.clients.PipelinesApiAbortClient.{PAPIAbortRequestSuccessful, PAPIOperationAlreadyCancelled, PAPIOperationHasAlreadyFinished} import scala.concurrent.{Future, Promise} @@ -29,7 +29,7 @@ trait AbortRequestHandler { this: RequestHandler => originalRequest.requester ! PAPIOperationHasAlreadyFinished(originalRequest.jobId.jobId) completionPromise.trySuccess(Success(())) } else { - pollingManager ! PipelinesApiAbortQueryFailed(originalRequest, new PAPIApiException(GoogleJsonException(e, responseHeaders))) + pollingManager ! PipelinesApiAbortQueryFailed(originalRequest, new SystemPAPIApiException(GoogleJsonException(e, responseHeaders))) completionPromise.trySuccess(Failure(new Exception(mkErrorString(e)))) } diff --git a/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/api/request/GetRequestHandler.scala b/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/api/request/GetRequestHandler.scala index 4c3f563009a..4c683c440b4 100644 --- a/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/api/request/GetRequestHandler.scala +++ b/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/api/request/GetRequestHandler.scala @@ -30,7 +30,7 @@ trait GetRequestHandler { this: RequestHandler => } override def onFailure(e: GoogleJsonError, responseHeaders: HttpHeaders): Unit = { - pollingManager ! PipelinesApiStatusQueryFailed(originalRequest, new PAPIApiException(GoogleJsonException(e, responseHeaders))) + pollingManager ! PipelinesApiStatusQueryFailed(originalRequest, new SystemPAPIApiException(GoogleJsonException(e, responseHeaders))) completionPromise.trySuccess(Failure(new Exception(mkErrorString(e)))) () } @@ -145,4 +145,4 @@ private[api] object GetRequestHandler { private def eventIfExists(name: String, metadata: Map[String, AnyRef], eventName: String): Option[ExecutionEvent] = { metadata.get(name) map { time => ExecutionEvent(eventName, OffsetDateTime.parse(time.toString)) } } -} \ No newline at end of file +} diff --git a/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/api/request/RunRequestHandler.scala b/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/api/request/RunRequestHandler.scala index abe60bb26fc..af8eae8d36e 100644 --- a/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/api/request/RunRequestHandler.scala +++ b/supportedBackends/google/pipelines/v1alpha2/src/main/scala/cromwell/backend/google/pipelines/v1alpha2/api/request/RunRequestHandler.scala @@ -21,7 +21,7 @@ trait RunRequestHandler { this: RequestHandler => } override def onFailure(e: GoogleJsonError, responseHeaders: HttpHeaders): Unit = { - pollingManager ! PipelinesApiRunCreationQueryFailed(originalRequest, new PAPIApiException(GoogleJsonException(e, responseHeaders))) + pollingManager ! PipelinesApiRunCreationQueryFailed(originalRequest, new SystemPAPIApiException(GoogleJsonException(e, responseHeaders))) completionPromise.trySuccess(Failure(new Exception(mkErrorString(e)))) () } diff --git a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/GenomicsFactory.scala b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/GenomicsFactory.scala index 81f7d4bcb7e..78134c6df11 100644 --- a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/GenomicsFactory.scala +++ b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/GenomicsFactory.scala @@ -88,7 +88,7 @@ case class GenomicsFactory(applicationName: String, authMode: GoogleAuthMode, en val accelerators = createPipelineParameters.runtimeAttributes .gpuResource.map(toAccelerator).toList.asJava - + /* * Adjust using docker images used by Cromwell as well as the tool's docker image size if available */ diff --git a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/AbortRequestHandler.scala b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/AbortRequestHandler.scala index 0d649991e6e..663d0a51c17 100644 --- a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/AbortRequestHandler.scala +++ b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/AbortRequestHandler.scala @@ -4,7 +4,7 @@ import akka.actor.ActorRef import com.google.api.client.googleapis.batch.BatchRequest import com.google.api.client.googleapis.json.GoogleJsonError import com.google.api.client.http.HttpHeaders -import cromwell.backend.google.pipelines.common.api.PipelinesApiRequestManager.{GoogleJsonException, PipelinesApiAbortQueryFailed, PAPIAbortRequest, PAPIApiException} +import cromwell.backend.google.pipelines.common.api.PipelinesApiRequestManager._ import cromwell.backend.google.pipelines.common.api.clients.PipelinesApiAbortClient.{PAPIAbortRequestSuccessful, PAPIOperationAlreadyCancelled, PAPIOperationHasAlreadyFinished} import cromwell.cloudsupport.gcp.auth.GoogleAuthMode @@ -21,7 +21,7 @@ trait AbortRequestHandler { this: RequestHandler => abortQuery.requester ! PAPIOperationHasAlreadyFinished(abortQuery.jobId.jobId) Success(()) } else { - pollingManager ! PipelinesApiAbortQueryFailed(abortQuery, new PAPIApiException(GoogleJsonException(e, responseHeaders))) + pollingManager ! PipelinesApiAbortQueryFailed(abortQuery, new SystemPAPIApiException(GoogleJsonException(e, responseHeaders))) Failure(new Exception(mkErrorString(e))) } } @@ -39,7 +39,7 @@ trait AbortRequestHandler { this: RequestHandler => } yield handled } recover { case e => - pollingManager ! PipelinesApiAbortQueryFailed(abortQuery, new PAPIApiException(e)) + pollingManager ! PipelinesApiAbortQueryFailed(abortQuery, new SystemPAPIApiException(e)) Failure(e) } } diff --git a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/GetRequestHandler.scala b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/GetRequestHandler.scala index 6fa092cdff7..34a7923964e 100644 --- a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/GetRequestHandler.scala +++ b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/GetRequestHandler.scala @@ -34,14 +34,14 @@ trait GetRequestHandler { this: RequestHandler => TrySuccess(()) case response => val failure = Try(GoogleJsonError.parse(GoogleAuthMode.jsonFactory, response)) match { - case TrySuccess(googleError) => new PAPIApiException(GoogleJsonException(googleError, response.getHeaders)) - case Failure(_) => new PAPIApiException(new RuntimeException(s"Failed to get status for operation ${pollingRequest.jobId.jobId}: HTTP Status Code: ${response.getStatusCode}")) + case TrySuccess(googleError) => new SystemPAPIApiException(GoogleJsonException(googleError, response.getHeaders)) + case Failure(_) => new SystemPAPIApiException(new RuntimeException(s"Failed to get status for operation ${pollingRequest.jobId.jobId}: HTTP Status Code: ${response.getStatusCode}")) } pollingManager ! PipelinesApiStatusQueryFailed(pollingRequest, failure) Failure(failure) } recover { case e => - pollingManager ! PipelinesApiStatusQueryFailed(pollingRequest, new PAPIApiException(e)) + pollingManager ! PipelinesApiStatusQueryFailed(pollingRequest, new SystemPAPIApiException(e)) Failure(e) } diff --git a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/RunRequestHandler.scala b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/RunRequestHandler.scala index 7b3818ec4ce..1b655b41edb 100644 --- a/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/RunRequestHandler.scala +++ b/supportedBackends/google/pipelines/v2alpha1/src/main/scala/cromwell/backend/google/pipelines/v2alpha1/api/request/RunRequestHandler.scala @@ -8,6 +8,7 @@ import com.google.api.client.http.{HttpHeaders, HttpRequest} import com.google.api.services.genomics.v2alpha1.model.Operation import cromwell.backend.google.pipelines.common.api.PipelinesApiRequestManager._ import cromwell.backend.standard.StandardAsyncJob +import cromwell.backend.google.pipelines.v2alpha1.api.request.RunRequestHandler.HttpUserErrorCodeInitialNumber import scala.concurrent.{Future, Promise} import scala.util.{Failure, Success, Try} @@ -21,8 +22,21 @@ trait RunRequestHandler { this: RequestHandler => } override def onFailure(e: GoogleJsonError, responseHeaders: HttpHeaders): Unit = { - pollingManager ! PipelinesApiRunCreationQueryFailed(originalRequest, new PAPIApiException(GoogleJsonException(e, responseHeaders))) - completionPromise.trySuccess(Failure(new Exception(mkErrorString(e)))) + + val rootCause = new Exception(mkErrorString(e)) + + val papiFailureException = if (e.getCode.toString.startsWith(HttpUserErrorCodeInitialNumber)) { + val helpfulHint = if (rootCause.getMessage.contains("unsupported accelerator")) { + Option("See https://cloud.google.com/compute/docs/gpus/ for a list of supported accelerators.") + } else None + + new UserPAPIApiException(GoogleJsonException(e, responseHeaders), helpfulHint) + } else { + new SystemPAPIApiException(GoogleJsonException(e, responseHeaders)) + } + + pollingManager ! PipelinesApiRunCreationQueryFailed(originalRequest, papiFailureException) + completionPromise.trySuccess(Failure(papiFailureException)) () } } @@ -45,3 +59,8 @@ trait RunRequestHandler { this: RequestHandler => private def getJob(operation: Operation) = StandardAsyncJob(operation.getName) } + +object RunRequestHandler { + // Because HTTP 4xx errors indicate user error: + val HttpUserErrorCodeInitialNumber: String = "4" +} From 747aa97592a1b0350471871bda81c454b6da3bba Mon Sep 17 00:00:00 2001 From: Chris Llanwarne Date: Fri, 10 May 2019 14:17:30 -0400 Subject: [PATCH 45/46] Call cache no copy fixups and centaur error improvements (#4951) --- .../centaur/reporting/Slf4jReporter.scala | 5 +++-- .../invalidate_bad_caches_jes_no_copy.test | 3 +++ .../src/main/scala/centaur/test/Test.scala | 20 ++++++++++++++----- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/centaur/src/it/scala/centaur/reporting/Slf4jReporter.scala b/centaur/src/it/scala/centaur/reporting/Slf4jReporter.scala index c0be908f26f..a5da61eacbc 100644 --- a/centaur/src/it/scala/centaur/reporting/Slf4jReporter.scala +++ b/centaur/src/it/scala/centaur/reporting/Slf4jReporter.scala @@ -24,9 +24,10 @@ class Slf4jReporter(override val params: ErrorReporterParams) IO { val message = s"Test '${testEnvironment.name}' " + - centaurTestException.workflowIdOption.map("with workflow id '" + _ + "' ").getOrElse("") + s"failed on attempt ${testEnvironment.attempt + 1} " + - s"of ${testEnvironment.retries + 1}" + s"of ${testEnvironment.retries + 1} " + + centaurTestException.workflowIdOption.map("with workflow id '" + _ + "' ").getOrElse("") + logger.error(message, centaurTestException) } } diff --git a/centaur/src/main/resources/standardTestCases/invalidate_bad_caches_jes_no_copy.test b/centaur/src/main/resources/standardTestCases/invalidate_bad_caches_jes_no_copy.test index 2b22b9096af..b2986be4b12 100644 --- a/centaur/src/main/resources/standardTestCases/invalidate_bad_caches_jes_no_copy.test +++ b/centaur/src/main/resources/standardTestCases/invalidate_bad_caches_jes_no_copy.test @@ -2,6 +2,9 @@ name: invalidate_bad_caches_jes_no_copy testFormat: workflowsuccess backends: [Papi-Caching-No-Copy] +# No point retrying failures since they'll just end up colliding with previous results: +retryTestFailures: false + files { workflow: invalidate_bad_caches/invalidate_bad_caches_no_copy.wdl } diff --git a/centaur/src/main/scala/centaur/test/Test.scala b/centaur/src/main/scala/centaur/test/Test.scala index 77400c7a668..89a528c1e97 100644 --- a/centaur/src/main/scala/centaur/test/Test.scala +++ b/centaur/src/main/scala/centaur/test/Test.scala @@ -6,11 +6,10 @@ import cats.Monad import cats.effect.IO import cats.instances.list._ import cats.syntax.traverse._ -import centaur.test.metadata.WorkflowFlatMetadata._ import centaur._ import centaur.api.CentaurCromwellClient -import centaur.api.CentaurCromwellClient.LogFailures import centaur.test.metadata.WorkflowFlatMetadata +import centaur.test.metadata.WorkflowFlatMetadata._ import centaur.test.submit.SubmitHttpResponse import centaur.test.workflow.Workflow import com.google.api.services.genomics.Genomics @@ -22,9 +21,10 @@ import com.typesafe.config.Config import common.validation.Validation._ import configs.syntax._ import cromwell.api.CromwellClient.UnsuccessfulRequestException -import cromwell.api.model.{CallCacheDiff, Failed, SubmittedWorkflow, TerminalStatus, WorkflowId, WorkflowMetadata, WorkflowStatus} +import cromwell.api.model.{CallCacheDiff, Failed, SubmittedWorkflow, Succeeded, TerminalStatus, WorkflowId, WorkflowMetadata, WorkflowStatus} import cromwell.cloudsupport.gcp.GoogleConfiguration import cromwell.cloudsupport.gcp.auth.GoogleAuthMode +import io.circe.parser._ import spray.json.JsString import scala.concurrent.ExecutionContext.Implicits.global @@ -204,6 +204,7 @@ object Operations { override def run: IO[SubmittedWorkflow] = status(timeout).timeout(CentaurConfig.maxWorkflowLength) + } } @@ -223,7 +224,17 @@ object Operations { case s if s == expectedStatus => IO.pure(workflow) case s: TerminalStatus => CentaurCromwellClient.metadata(workflow) flatMap { metadata => - val message = s"Unexpected terminal status $s but was waiting for $expectedStatus (workflow ID: ${workflow.id})" + val failuresString = if (expectedStatus == Succeeded) { + (for { + metadataJson <- parse(metadata.value).toOption + asObject <- metadataJson.asObject + failures <- asObject.toMap.get("failures") + } yield s" Metadata 'failures' content: ${failures.spaces2}").getOrElse("No additional failure information found in metadata.") + } else { + "" + } + + val message = s"Unexpected terminal status $s but was waiting for $expectedStatus (workflow ID: ${workflow.id}).$failuresString" IO.raiseError(CentaurTestException(message, testDefinition, workflow, metadata)) } case _ => for { @@ -386,7 +397,6 @@ object Operations { validateMetadata(workflow, expectedMetadata).handleErrorWith({ _ => for { _ <- IO.sleep(2.seconds) - _ = if (LogFailures) Console.err.println(s"Metadata mismatch for ${submittedWorkflow.id} - retrying") recurse <- eventuallyMetadata(workflow, expectedMetadata) } yield recurse }) From a3fc1750acafdfdbb6cb16b5cbe17cb3f82cd2e3 Mon Sep 17 00:00:00 2001 From: Michael Franklin Date: Tue, 14 May 2019 00:33:27 +1000 Subject: [PATCH 46/46] [develop] Cleanup AWS Documentation in Hackathon (#4859) Update AWS docs --- docs/RuntimeAttributes.md | 235 +++++++++++++++++++--------------- docs/backends/AWSBatch.md | 59 +++++++++ docs/tutorials/AwsBatch101.md | 179 ++++++++++++-------------- mkdocs.yml | 1 + 4 files changed, 269 insertions(+), 205 deletions(-) create mode 100644 docs/backends/AWSBatch.md diff --git a/docs/RuntimeAttributes.md b/docs/RuntimeAttributes.md index 9bcff3886f4..12a6166105b 100644 --- a/docs/RuntimeAttributes.md +++ b/docs/RuntimeAttributes.md @@ -5,8 +5,6 @@ Runtime attributes can be specified in one of two ways: 1. Within a task you can specify runtime attributes to customize the environment for the call. 2. [Default runtime attributes](#default-values) for all tasks can be specified in [Workflow Options](wf_options/Overview.md). - >* Certain [Backends](backends/Backends) only support certain runtime attributes. See [Backend Support](#backend-support) for a table. - _Task Example_ ``` @@ -27,6 +25,36 @@ workflow jes_workflow { } ``` + +## Recognized Runtime attributes and Backends + +Cromwell recognizes certain runtime attributes and has the ability to format these for some [Backends](/backends/Backends). See the table below for common attributes that apply to _most_ backends. + +| Runtime Attribute | LOCAL | Google Cloud | AWS Batch | HPC | +| -------------------- |:-----:|:-----:|:-----:|:------:| +| [cpu](#cpu) | | x | x | `cpu` | +| [memory](#memory) | | x | x | `memory_mb` / `memory_gb` | +| [disks](#disks) | | x | | * | +| [docker](#docker) | x | x | x | `docker` (see below) | +| [maxRetries](#maxretries) | x | x | x | * | +| [continueOnReturnCode](#continueonreturncode) | x | x | x | * | +| [failOnStderr](#failonstderr) | x | x | x | * | + + +> `*` The HPC [Shared Filesystem backend](/backends/HPC#shared-filesystem) (SFS) is fully configurable and any number of attributes can be exposed. Cromwell recognizes some of these attributes (`cpu`, `memory` and `docker`) and parses them into the attribute listed in the table which can be used within the HPC backend configuration. + + +### Google Cloud Specific Attributes +There are a number of additional runtime attributes that apply to the Google Cloud Platform: + +- [zones](#zones) +- [preemptible](#preemptible) +- [bootDiskSizeGb](#bootdisksizegb) +- [noAddress](#noaddress) +- [gpuCount and gpuType](#gpucount-and-gputype) + + + ## Expression support Runtime attribute values are interpreted as expressions. This means that it has the ability to express the value of a runtime attribute as a function of one of the task's inputs. @@ -48,7 +76,7 @@ task runtime_test { } ``` -SGE and similar backends may define other configurable runtime attributes beyond the five listed. To find more information about SGE, view [Sun GridEngine](backends/SGE). +HPC backends may define other configurable runtime attributes beyond the five listed, to find out more visit the [SunGridEngine](/backends/SGE) tutorial. ## Default Values @@ -104,61 +132,63 @@ And the effective runtime for `task second` is: Note how for `task second` the WDL value for `docker` is used instead of the default provided in the workflow options. -## `continueOnReturnCode` -When each task finishes it returns a code. Normally, a non-zero return code indicates a failure. However you can override this behavior by specifying the `continueOnReturnCode` attribute. +## Runtime Attribute Descriptions -When set to false, any non-zero return code will be considered a failure. When set to true, all return codes will be considered successful. +### `cpu` -``` -runtime { - continueOnReturnCode: true -} -``` +*Default: _1_* -When set to an integer, or an array of integers, only those integers will be considered as successful return codes. +The `cpu` runtime attribute represents the number of cores that a job requires, however each backend may interpret this differently: -``` -runtime { - continueOnReturnCode: 1 -} -``` +- In Google Cloud: this is interpreted as "the minimum number of cores to use." +- In HPCs (SFS): this is configurable, but usually a reservation and/or limit of number of cores. +Example ``` runtime { - continueOnReturnCode: [0, 1] + cpu: 2 } ``` -Defaults to "0". -## `cpu` +#### CWL + +CWL splits the `cpu` requirement into `cpuMin` and `cpuMax`. If one of them is provided, `cpu` will inherit this value. If both of them are provided, `cpu` will take the value of `cpuMin`. +If none is provided, `cpu` will default to its default value. + +Note: If provided, `cpuMin` and/or `cpuMax` will be available to the [HPC runtime attribute configuration](/tutorials/HPCIntro.md#specifying-the-runtime-attributes-for-your-hpc-tasks). + + +### `memory` +*Default: "2G"* + +Memory is the amount of RAM that should be allocated to a task, however each backend may interpret this differently: -Passed to Google Cloud: "The minimum number of cores to use." +- Google Cloud: The minimum amount of RAM to use. +- SFS: Configurable, but usually a reservation and/or limit of memory. -Passed to SGE, etc.: Configurable, but usually a reservation and/or limit of number of cores. +The memory size is specified as an amount and units of memory, for example "4G": ``` runtime { - cpu: 2 + memory: "4G" } ``` -Defaults to "1". +Within the SFS backend, you can additionally specify `memory_mb` or `memory_gb` as runtime attributes within the configuration. More information can be found [here](https://cromwell.readthedocs.io/en/stable/tutorials/HPCIntro/#specifying-the-runtime-attributes-for-your-hpc-tasks). -**CWL** +#### CWL -CWL splits the `cpu` requirement into `cpuMin` and `cpuMax`. -If one of them is provided, `cpu` will inherit this value. If both of them are provided, `cpu` will take the value of `cpuMin`. -If none is provided, `cpu` will default to its default value. +CWL splits the `memory` requirement into `ramMin` and `ramMax`. If one of them is provided, `memory` will inherit this value. If both of them are provided, `memory` will take the value of `ramMin`. If none is provided, `memory` will default to its default value. + +Note: If provided, `ramMin` and/or `ramMax` will be available to the [HPC runtime attribute configuration](/tutorials/HPCIntro.md#specifying-the-runtime-attributes-for-your-hpc-tasks). -Note: Runtime attributes are handled by backends as they please. -The PAPI backend for instance will only honor `cpuMin`. -If provided, `cpuMin` and/or `cpuMax` will be available to the [HPC runtime attribute configuration](tutorials/HPCIntro.md#specifying-the-runtime-attributes-for-your-hpc-tasks). -## `disks` +### `disks` + +This attribute specifies volumes that will be mounted to the VM for your job. These volumes are where you can read and write files that will be used by the commands within your workflow. -This is currently used by the [Google Cloud backend](backends/Google). You can use this attribute to specify volumes that will be mounted to the VM for your job. These volumes are where you can read and write files that will be used by the commands within your workflow. They are specified as a comma separated list of disks. Each disk is further separated as a space separated triplet (e.g. `local-disk 10 SSD`) consisting of: @@ -168,6 +198,9 @@ They are specified as a comma separated list of disks. Each disk is further sepa All tasks launched on Google Cloud *must* have a `local-disk`. If one is not specified in the runtime section of the task, then a default of `local-disk 10 SSD` will be used. The `local-disk` will be mounted to `/cromwell_root`. +For the AWS Batch backend, the disk volume is managed by AWS EBS with autoscaling capabilities. As such, the Disk size and disk type will be ignored. If provided, the mount point will be verified at runtime. + + The Disk type must be one of "LOCAL", "SSD", or "HDD". When set to "LOCAL", the size of the drive is automatically provisioned by Google so any size specified in WDL will be ignored. All disks are set to auto-delete after the job completes. *Example 1: Changing the Localization Disk* @@ -186,46 +219,67 @@ runtime { } ``` -## `bootDiskSizeGb` +### `docker` + +When specified, Cromwell will run your task within the specified Docker image. -In addition to working disks, Google Cloud allows specification of a boot disk size. This is the disk where the docker image itself is booted (**not the working directory of your task on the VM**). -Its primary purpose is to ensure that larger docker images can fit on the boot disk. ``` runtime { - # Yikes, we have a big OS in this docker image! Allow 50GB to hold it: - bootDiskSizeGb: 50 + docker: "ubuntu:latest" } ``` -Since no `local-disk` entry is specified, Cromwell will automatically add `local-disk 10 SSD` to this list. +- Local: Cromwell will automatically run the docker container. +- SFS: When a docker container exists within a task, the `submit-docker` method is called. See the [Getting started with containers](/tutorials/Containers/) guide for more information. +- GCP: This attribute is mandatory when submitting tasks to Google Cloud. +- AWS Batch: This attribute is mandatory when submitting tasks to AWS Batch. -## `zones` -The ordered list of zone preference (see [Region and Zones](https://cloud.google.com/compute/docs/zones) documentation for specifics). +### `maxRetries` -*The zones are specified as a space separated list, with no commas:* +*Default: _0_* + +This retry option is introduced to provide a method for tackling transient job failures. For example, if a task fails due to a timeout from accessing an external service, then this option helps re-run the failed the task without having to re-run the entire workflow. It takes an Int as a value that indicates the maximum number of times Cromwell should retry a failed task. This retry is applied towards jobs that fail while executing the task command. This method only applies to transient job failures and is a feeble attempt to retry a job, that is it cannot be used to increase memory in out-of-memory situations. + +If using the Google backend, it's important to note that The `maxRetries` count is independent from the [preemptible](#preemptible) count. For example, the task below can be retried up to 6 times if it's preempted 3 times AND the command execution fails 3 times. ``` runtime { - zones: "us-central1-c us-central1-b" + preemptible: 3 + maxRetries: 3 } ``` -Defaults to the configuration setting `genomics.default-zones` in the Google Cloud configuration block, which in turn defaults to using `us-central1-b`. +### `continueOnReturnCode` +*Default: _0_* -## `docker` +When each task finishes it returns a code. Normally, a non-zero return code indicates a failure. However you can override this behavior by specifying the `continueOnReturnCode` attribute. -When specified, cromwell will run your task within the specified Docker image. +When set to false, any non-zero return code will be considered a failure. When set to true, all return codes will be considered successful. ``` runtime { - docker: "ubuntu:latest" + continueOnReturnCode: true } ``` -This attribute is mandatory when submitting tasks to Google Cloud. When running on other backends, they default to not running the process within Docker. +When set to an integer, or an array of integers, only those integers will be considered as successful return codes. + +``` +runtime { + continueOnReturnCode: 1 +} +``` + +``` +runtime { + continueOnReturnCode: [0, 1] +} +``` -## `failOnStderr` +### `failOnStderr` + +*Default: _false_* Some programs write to the standard error stream when there is an error, but still return a zero exit code. Set `failOnStderr` to true for these tasks, and it will be considered a failure if anything is written to the standard error stream. @@ -235,35 +289,25 @@ runtime { } ``` -Defaults to "false". -## `memory` -Passed to Google Cloud: "The minimum amount of RAM to use." +### `zones` -Passed to SGE, etc.: Configurable, but usually a reservation and/or limit of memory. +The ordered list of zone preference (see [Region and Zones](https://cloud.google.com/compute/docs/zones) documentation for specifics). -The memory size is specified as an amount and units of memory, for example "4 G": +*The zones are specified as a space separated list, with no commas:* ``` runtime { - memory: "4G" + zones: "us-central1-c us-central1-b" } ``` -Defaults to "2G". - -**CWL** - -CWL splits the `memory` requirement into `ramMin` and `ramMax`. -If one of them is provided, `memory` will inherit this value. If both of them are provided, `memory` will take the value of `ramMin`. -If none is provided, `memory` will default to its default value. +Defaults to the configuration setting `genomics.default-zones` in the Google Cloud configuration block, which in turn defaults to using `us-central1-b`. -Note: Runtime attributes are handled by backends as they please. -The PAPI backend for instance will only honor `ramMin`. -If provided, `ramMin` and/or `ramMax` will be available to the [HPC runtime attribute configuration](tutorials/HPCIntro.md#specifying-the-runtime-attributes-for-your-hpc-tasks). +### `preemptible` -## `preemptible` +*Default: _0_* Passed to Google Cloud: "If applicable, preemptible machines may be used for the run." @@ -276,37 +320,24 @@ runtime { } ``` -Defaults to 0. -## `gpuCount` and `gpuType` -Attach GPUs to the instance when running on the Pipelines API: https://cloud.google.com/compute/docs/gpus/ -Make sure to choose a zone for which the type of GPU you want to attach is available. -The two types of GPU supported are `nvidia-tesla-k80` and `nvidia-tesla-p100`. - -runtime { - gpuType: "nvidia-tesla-k80" - gpuCount: 2 - zones: ["us-central1-c"] -} - -## `maxRetries` - -This retry option is introduced to provide a strategy for tackling transient job failures. For example, if a task fails due to a timeout from accessing an external service, then this option helps re-run the failed the task without having to re-run the entire workflow. It takes an Int as a value that indicates the maximum number of times Cromwell should retry a failed task. This retry is applied towards jobs that fail while executing the task command. - -If using the Google backend, it's important to note that The `maxRetries` count is independent from the [preemptible](#preemptible) count. For example, the task below can be retried up to 6 times if it's preempted 3 times AND the command execution fails 3 times. +### `bootDiskSizeGb` +In addition to working disks, Google Cloud allows specification of a boot disk size. This is the disk where the docker image itself is booted (**not the working directory of your task on the VM**). +Its primary purpose is to ensure that larger docker images can fit on the boot disk. ``` runtime { - preemptible: 3 - maxRetries: 3 + # Yikes, we have a big OS in this docker image! Allow 50GB to hold it: + bootDiskSizeGb: 50 } ``` -Defaults to 0. +Since no `local-disk` entry is specified, Cromwell will automatically add `local-disk 10 SSD` to this list. + -## `noAddress` +### `noAddress` This runtime attribute adds support to disable assigning external IP addresses to VMs provisioned by the Google backend. If set to true, the VM will NOT be provided with a public IP address, and only contain an internal IP. If this option is enabled, the associated job can only load docker images from Google Container Registry, and the job executable cannot use external services other than Google APIs. @@ -339,25 +370,17 @@ runtime { ``` +### `gpuCount` and `gpuType` -## Backend Support - -[Backends](backends/Backends) only support certain attributes. See table below: - -| Runtime Attribute | LOCAL | Google Cloud | SGE | -| -------------------- |:-----:|:-----:|:-----:| -| [continueOnReturnCode](#continueonreturncode) | x | x | x | -| [cpu](#cpu) | | x | x | -| [disks](#disks) | | x | | -| [zones](#zones) | | x | | -| [docker](#docker) | x | x | x | -| [failOnStderr](#failOnStderr) | x | x | x | -| [memory](#memory) | | x | x | -| [preemptible](#preemptible) | | x | | -| [bootDiskSizeGb](#bootdisksizegb) | | x | | -| [maxRetries](#maxRetries) | x | x | x | -| [noAddress](#noAddress) | | x | | - +Attach GPUs to the instance when running on the Pipelines API: https://cloud.google.com/compute/docs/gpus/ +Make sure to choose a zone for which the type of GPU you want to attach is available. +The two types of GPU supported are `nvidia-tesla-k80` and `nvidia-tesla-p100`. -[Shared Filesystem backend](backends/HPC#shared-filesystem) is fully configurable and thus these attributes do not apply universally. +``` +runtime { + gpuType: "nvidia-tesla-k80" + gpuCount: 2 + zones: ["us-central1-c"] +} +``` diff --git a/docs/backends/AWSBatch.md b/docs/backends/AWSBatch.md new file mode 100644 index 00000000000..a3314ec773b --- /dev/null +++ b/docs/backends/AWSBatch.md @@ -0,0 +1,59 @@ +# AWS Batch Backend + +AWS Batch is a set of batch management capabilities that dynamically provision the optimal quantity and type of compute resources (e.g., CPU or memory optimized instances) based on the volume and specific resource requirements of the batch jobs submitted. + +This section provides details on how to configure the AWS Batch backend with Cromwell. For instructions on common configuration and deployment tutorial, see [Getting started with AWS Batch](https://cromwell.readthedocs.io/en/develop/tutorials/AwsBatch101/). + + + +## Resources and Runtime Attributes + +Cromwell and AWS Batch recognizes number of runtime attributes, more information can be found in the [customize tasks](/RuntimeAttributes#recognized-runtime-attributes-and-backends) page. + + +## Running Cromwell on an EC2 instance + +Cromwell can be run on an EC2 instance and submit jobs to AWS Batch, AWS provide [CloudFormation stacks and guides](https://docs.opendata.aws/genomics-workflows/orchestration/cromwell/cromwell-overview/) to building the correct IAM permissions. + + + +## Scaling Requirements +For a Cromwell server that will run multiple workflows, or workflows with many steps (e.g. ones with large scatter steps), it is recommended to setup a database to store workflow metadata. The application config file will expect a SQL database location. Follow [these instructions](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.create.html) on how to create a serverless Amazon Aurora database. + +## Configuring Cromwell for AWS Batch + +Within the `*.conf` file, you have a number of options to change the Cromwell's interaction with AWS Batch. + +### Filesystems +> More information about filesystems can be found on the [Filesystems page](/filesystems/Filesystems/). +> + +Amazon's S3 storage is a supported filesystem in both the engine and backend, this means that S3 files can be referenced at a workflow level, and as input files, provided they are prefixed by `'s3://'`. + +* filesystems +* filesystems.s3.auth +* filesystems.s3.caching.duplication-strategy + +### Configuring Authentication + +To allow Cromwell to talk to AWS, the `default` authentication scheme uses the [default authentication provider](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) with the following AWS search paths: +- Environment Variables - `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` +- Java system properties - `aws.accessKeyId` and `aws.secretKey` +- Default credential profiles file - Created by the AWS CLI, typically located at `~/.aws/credentials` +- _Instance profile credentials_ - Only relevant on EC2 instances + +### Allowing private Docker containers + +AWS Batch allows the use of private Docker containers by providing `dockerhub` credentials. Under the specific backend's configuration, you can provide the following object: + +```hocon +(backend.providers.AWSBatch.config.)dockerhub = { + // account = "" + // token = "" +} +``` + +### More configuration options + +* `(backend.providers.AWSBatch.config.)concurrent-job-limit` specifies the number of jobs that Cromwell will allow to be running in AWS at the same time. Tune this parameter based on how many nodes are in the compute environment. +* `(backend.providers.AWSBatch.config.)root` points to the S3 bucket where workflow outputs are stored. This becomes a path on the root instance, and by default is cromwell_root. This is monitored by preinstalled daemon that expands drive space on the host, ie AWS EBS autoscale. This path is used as the 'local-disk' for containers. diff --git a/docs/tutorials/AwsBatch101.md b/docs/tutorials/AwsBatch101.md index d3b63cae792..9b8bb272710 100644 --- a/docs/tutorials/AwsBatch101.md +++ b/docs/tutorials/AwsBatch101.md @@ -8,108 +8,77 @@ This tutorial page relies on completing the previous tutorial: ### Goals -At the end of this tutorial you'll have run your first workflow against AWS Batch +At the end of this tutorial you'll have configured your local environment to run workflows using Cromwell on AWS Batch. ### Let's get started! +To create all the resources for running a Cromwell server on AWS using CloudFormation, launch the [Cromwell Full Stack Deployment](https://docs.opendata.aws/genomics-workflows/orchestration/cromwell/cromwell-overview/). Alternatively, this page will walk through the specific steps to configure and run a local Cromwell server using AWS Batch. -**Configuring the AWS environment** +1. [Authenticating a local Cromwell server with AWS](#authenticating-a-local-cromwell-server-with-aws) +2. [Configuring the AWS environment](#configuring-the-aws-environment) +3. [Configuring Cromwell](#configuring-cromwell) +4. [Workflow Source Files](#workflow-source-files) +5. [Running Cromwell and AWS](#running-cromwell-and-aws) +6. [Outputs](#outputs) -Install the AWS CLI. +#### Authenticating a local Cromwell server with AWS -Configure the CLI for use. -Note that if using an EC2 instance, the -instance metadata, provided by an instance role is generally preferred. -Also, any EC2 instance using Amazon Linux -comes with the AWS CLI pre-installed. +The easiest way to allow a local Cromwell server to talk to AWS is to: -Create a S3 bucket to hold Cromwell execution directories. -We will refer to this bucket as `s3-bucket-name`, and the full identifier as `s3://s3-bucket-name`. -`aws s3 mb s3://` +1. Install the AWS CLI through Amazon's [user guide](https://docs.aws.amazon.com/cli/latest/userguide/installing.html). +2. [Configure the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) by calling `aws configure` (provide your `Access Key` and `Secret Access Key` when prompted). -More information and instructions to properly setup an AWS environment to work -properly with Cromwell on AWS can be found on the -[AWS for Genomics Workflow page](https://docs.opendata.aws/genomics-workflows/). +Cromwell can access these credentials through the default authentication provider. For more options, see the [Configuring authentication of Cromwell with AWS](/backends/AWSBatch#configuring-authentication) section below. -**Workflow Source Files** -Copy over the sample `hello.wdl` and `hello.inputs` files to the same directory as the Cromwell jar. -This workflow takes a string value as specified in the inputs file and writes it to stdout. +#### Configuring the AWS environment +Next you'll need the following setup in your AWS account: +- The core set of resources (S3 Bucket, IAM Roles, AWS Batch) +- Custom Compute Resource (Launch Template or AMI) with Cromwell Additions -***hello.wdl*** -``` -task hello { - String addressee - command { - echo "Hello ${addressee}! Welcome to Cromwell . . . on AWS!" - } - output { - String message = read_string(stdout()) - } - runtime { - docker: "ubuntu:latest" - } -} +Information and instructions to setup an AWS environment to work properly with Cromwell can be found on [AWS for Genomics Workflow](https://docs.opendata.aws/genomics-workflows/core-env/introduction/). By deploying the CloudFormation templates provided by AWS, the stack will output the S3 bucket name and two AWS Batch queue ARNs (default and high-priority) used in the Cromwell configuration. -workflow wf_hello { - call hello - output { - hello.message - } -} -``` -***hello.inputs*** -``` -{ - "wf_hello.hello.addressee": "World" -} -``` -**AWS Configuration File** +#### Configuring Cromwell -Copy over the sample `aws.conf` file utilizing -the default credential provider chain -to the same directory that contains your sample WDL, inputs and Cromwell jar. +Now we're going to configure Cromwell to use the AWS resources we just created by updating a `*.conf` file to use the `AWSBackend` at runtime. This requires three pieces of information: -Replace in the configuration file: +- The [AWS Region](https://docs.aws.amazon.com/general/latest/gr/rande.html) where your resources are deployed. +- S3 bucket name where Cromwell will store its execution files. +- The ARN of the AWS Batch queue you want to use for your tasks. -* `` with the region where your resources are launched (e.g. "us-east-1") -* `` with the appropriate bucket name -* `` with either your default or high priority queue ARN +You can replace the placeholders (``, `` and ``) in the following config: -***aws.conf*** -``` +##### `aws.conf` + +```hocon include required(classpath("application")) aws { application-name = "cromwell" - auths = [ { name = "default" scheme = "default" } ] - region = "" } engine { filesystems { - s3 { - auth = "default" - } + s3.auth = "default" } } backend { - default = "AWSBATCH" + default = "AWSBatch" providers { - AWSBATCH { + AWSBatch { actor-factory = "cromwell.backend.impl.aws.AwsBatchBackendLifecycleActorFactory" config { @@ -117,7 +86,7 @@ backend { numCreateDefinitionAttempts = 6 // Base bucket for workflow executions - root = "s3:///cromwell-execution" + root = "s3:///cromwell-execution" // A reference to an auth defined in the `aws` stanza at the top. This auth is used to create // Jobs and manipulate auth JSONs. @@ -137,13 +106,54 @@ backend { } } } + ``` -**Run Workflow** +For more information about this configuration or how to change the behaviour of AWS Batch, visit the [AWS Backend](/backends/AWSBatch) page. -`java -Dconfig.file=aws.conf -jar cromwell-36.jar run hello.wdl -i hello.inputs` -**Outputs** +#### Workflow Source Files + +Lastly, create an example workflow to run. We're going to define a simple workflow that will `echo` a string to the console and return the result to Cromwell. Within AWS Batch (like other cloud providers), we're required to specify a Docker container for every task. + +##### `hello.wdl` + +```wdl +task hello { + String addressee = "Cromwell" + command { + echo "Hello ${addressee}! Welcome to Cromwell . . . on AWS!" + } + output { + String message = read_string(stdout()) + } + runtime { + docker: "ubuntu:latest" + } +} + +workflow wf_hello { + call hello + output { hello.message } +} +``` + +#### Running Cromwell and AWS + +Provided all of the files are within the same directory, we can run our workflow with the following command: + +> **Note**: You might have a different Cromwell version number here + +```bash +java -Dconfig.file=aws.conf -jar cromwell-36.jar run hello.wdl +``` + +This will: +1. Start Cromwell in `run` mode, +2. Prepare `hello.wdl` as a job and submit this to your AWS Batch queue. You can monitor the job within your [AWS Batch dashboard](https://console.aws.amazon.com/batch/home). +3. Run the job, write execution files back to S3, and report progress back to Cromwell. + +#### Outputs The end of your workflow logs should report the workflow outputs. @@ -161,37 +171,8 @@ Success! ### Next steps -You might find the following tutorials interesting to tackle next: - -* [Persisting Data Between Restarts](PersistentServer) -* [Server Mode](ServerMode.md) - -### Configuration Reference - -Auth configuration. This requires a "name" and a "scheme". Authentication schemes are: - -* default: This uses the default authentication provider, which uses - standard AWS search paths (Environment variables, configuration file, instance metadata) - to determine access key and secret key -* custom_keys: This requires the definition of "access-key" and "secret-key" -* assume_role: This requires the definition of "base-auth" and "role-arn". An optional "external-id" can be provided - -More config: - -* concurrent-job-limit -* root -* dockerhub -* dockerhub.account -* dockerhub.token -* filesystems -* filesystems.s3.auth -* filesystems.s3.caching.duplication-strategy -* default-runtime-attributes -* default-runtime-attributes.disks -* default-runtime-attributes.memory -* default-runtime-attributes.zones -* default-runtime-attributes.continueOnReturnCode -* default-runtime-attributes.cpu -* default-runtime-attributes.noAddress -* default-runtime-attributes.docker -* default-runtime-attributes.failOnStderr +You might find the following tutorials and guides interesting to tackle next: + +* [Server Mode](/tutorials/ServerMode) +* [AWS Batch Backend](/backends/AWSBatch) +* [Persisting Data Between Restarts](/tutorials/PersistentServer) diff --git a/mkdocs.yml b/mkdocs.yml index 885ac85d5b8..b1bf557ef52 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -51,6 +51,7 @@ pages: - Overview: backends/Backends.md - Local: backends/Local.md - Google Cloud: backends/Google.md + - AWS Batch: backends/AWSBatch.md - Alibaba Cloud: backends/BCS.md - AWS Batch (beta): backends/AWS.md - GA4GH TES: backends/TES.md