Skip to content

Commit

Permalink
Merge branch 'main' into bmurri/simplify-task-management
Browse files Browse the repository at this point in the history
  • Loading branch information
BMurri committed Nov 12, 2024
2 parents 4c9bfe0 + 0245462 commit aa5372b
Show file tree
Hide file tree
Showing 15 changed files with 696 additions and 21 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[Cromwell](https://cromwell.readthedocs.io/en/stable/) is a workflow management system for scientific workflows, orchestrating the computing tasks needed for genomics analysis. Originally developed by the [Broad Institute](https://github.com/broadinstitute/cromwell), Cromwell is also used in the GATK Best Practices genome analysis pipeline. Cromwell supports running scripts at various scales, including your local machine, a local computing cluster, and on the cloud. <br />

Cromwell on Azure configures all Azure resources needed to run workflows with Cromwell on the Microsoft Azure cloud, and uses the [GA4GH TES](https://cromwell.readthedocs.io/en/develop/backends/TES/) backend for orchestrating the tasks that create a workflow. The installation sets up an Azure Kubernetes cluster to run the Cromwell, TES, and Trigger Service containers, and uses the Azure Batch PaaS service to execute each task in a workflow on its own VM, enabling scale-out to thousands of machines. Cromwell workflows can be written using the [WDL](https://github.com/openwdl/wdl) scripting language. To see examples of WDL scripts - see this ['Learn WDL'](https://github.com/openwdl/learn-wdl) repository on GitHub.<br />
Cromwell on Azure configures all Azure resources needed to run workflows with Cromwell on the Microsoft Azure cloud, and uses the [GA4GH TES](https://cromwell.readthedocs.io/en/develop/backends/TES/) backend for orchestrating the tasks that create a workflow. The installation sets up an Azure Kubernetes cluster to run the Cromwell, TES, and Trigger Service containers, and uses the Azure Batch PaaS service to execute each task in a workflow in its own VM, enabling scale-out to thousands of machines. Cromwell workflows can be written using the [WDL](https://github.com/openwdl/wdl) scripting language. To see examples of WDL scripts - see this ['Learn WDL'](https://github.com/openwdl/learn-wdl) repository on GitHub.<br />

### Latest release
* https://github.com/microsoft/CromwellOnAzure/releases
Expand Down
3 changes: 1 addition & 2 deletions src/TriggerService.Tests/CromwellOnAzureEnvironmentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,7 @@ private TriggerHostedService SetCromwellOnAzureEnvironment(string accountAuthori
ApplicationInsightsAccountName = "fakeappinsights"
});

var azureCloudConfig = AzureCloudConfig.FromKnownCloudNameAsync().Result;
var environment = new TriggerHostedService(logger, triggerServiceOptions.Object, cromwellApiClient, repository, storageUtility.Object, azureCloudConfig);
var environment = new TriggerHostedService(logger, triggerServiceOptions.Object, cromwellApiClient, repository, storageUtility.Object, AzureCloudConfig.ForUnitTesting());
return environment;
}

Expand Down
3 changes: 1 addition & 2 deletions src/TriggerService.Tests/ProcessAbortRequestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ public async Task FailedAbortRequestFileGetsMovedToFailedSubdirectory()
.Setup(x => x.GetStorageAccountsUsingMsiAsync(It.IsAny<string>()))
.Returns(Task.FromResult((new List<IAzureStorage>(), azureStorage.Object)));

var azureCloudConfig = AzureCloudConfig.FromKnownCloudNameAsync().Result;
var cromwellOnAzureEnvironment = new TriggerHostedService(logger, triggerServiceOptions.Object, cromwellApiClient, repository.Object, storageUtility.Object, azureCloudConfig);
var cromwellOnAzureEnvironment = new TriggerHostedService(logger, triggerServiceOptions.Object, cromwellApiClient, repository.Object, storageUtility.Object, AzureCloudConfig.ForUnitTesting());
await cromwellOnAzureEnvironment.ProcessAndAbortWorkflowsAsync();
return (newTriggerName, newTriggerContent);
}
Expand Down
7 changes: 2 additions & 5 deletions src/TriggerService.Tests/ProcessNewWorkflowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,13 @@ public async Task NewWorkflowsThatFailToPostToCromwellAreMovedToFailedSubdirecto
storageUtility
.Setup(x => x.GetStorageAccountsUsingMsiAsync(It.IsAny<string>()))
.Returns(Task.FromResult((new List<IAzureStorage>(), azureStorage.Object)));
var azureCloudConfig = AzureCloudConfig.FromKnownCloudNameAsync().Result;
var cromwellOnAzureEnvironment = new TriggerHostedService(
logger,
optionsMock.Object,
cromwellApiClient,
tesTaskRepository,
storageUtility.Object,
azureCloudConfig);
AzureCloudConfig.ForUnitTesting());

await cromwellOnAzureEnvironment.ProcessAndAbortWorkflowsAsync();

Expand Down Expand Up @@ -209,15 +208,13 @@ public async Task NewWorkflowsThatFailToParseAsJsonAreAnotatedAndMovedToFailedSu
.Setup(x => x.GetStorageAccountsUsingMsiAsync(It.IsAny<string>()))
.Returns(Task.FromResult((new List<IAzureStorage>(), azureStorage.Object)));

var azureCloudConfig = AzureCloudConfig.FromKnownCloudNameAsync().Result;

var cromwellOnAzureEnvironment = new TriggerHostedService(
logger,
triggerServiceOptions.Object,
cromwellApiClient2,
tesTaskRepository,
storageUtility.Object,
azureCloudConfig);
AzureCloudConfig.ForUnitTesting());

await cromwellOnAzureEnvironment.ProcessAndAbortWorkflowsAsync();
Assert.IsTrue(newTriggerName.StartsWith("failed/"));
Expand Down
3 changes: 1 addition & 2 deletions src/TriggerService.Tests/TriggerEngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ public async Task TriggerEngineRunsAndOnlyLogsAvailabilityOncePerSystemUponAvail
.Setup(x => x.GetStorageAccountsUsingMsiAsync(It.IsAny<string>()))
.Returns(Task.FromResult((new List<IAzureStorage>(), azureStorage.Object)));

var azureCloudConfig = AzureCloudConfig.FromKnownCloudNameAsync().Result;
var triggerHostedService = new TriggerHostedService(logger, triggerServiceOptions.Object, cromwellApiClient, tesTaskRepository, storageUtility.Object, azureCloudConfig);
var triggerHostedService = new TriggerHostedService(logger, triggerServiceOptions.Object, cromwellApiClient, tesTaskRepository, storageUtility.Object, AzureCloudConfig.ForUnitTesting());

//var engine = new TriggerHostedService(loggerFactory, environment.Object, TimeSpan.FromMilliseconds(25), TimeSpan.FromMilliseconds(25));
_ = Task.Run(() => triggerHostedService.StartAsync(new System.Threading.CancellationToken()));
Expand Down
1 change: 0 additions & 1 deletion src/TriggerService.Tests/TriggerService.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\TriggerService\TriggerService.csproj" />
</ItemGroup>
</Project>
3 changes: 1 addition & 2 deletions src/TriggerService.Tests/UpdateWorkflowStatusTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,7 @@ static string ShardString(int shard) =>
.Setup(x => x.GetStorageAccountsUsingMsiAsync(It.IsAny<string>()))
.Returns(Task.FromResult((new List<IAzureStorage>(), azureStorage.Object)));

var azureCloudConfig = AzureCloudConfig.FromKnownCloudNameAsync().Result;
var cromwellOnAzureEnvironment = new TriggerHostedService(logger, triggerServiceOptions.Object, cromwellApiClient.Object, repository.Object, storageUtility.Object, azureCloudConfig);
var cromwellOnAzureEnvironment = new TriggerHostedService(logger, triggerServiceOptions.Object, cromwellApiClient.Object, repository.Object, storageUtility.Object, AzureCloudConfig.ForUnitTesting());

await cromwellOnAzureEnvironment.UpdateWorkflowStatusesAsync();

Expand Down
240 changes: 240 additions & 0 deletions src/deploy-cromwell-on-azure.Tests/HoconUtilTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Hocon;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CromwellOnAzureDeployer.Tests
{
[TestClass]
public class HoconUtilTests
{
// TODO: check ability to handle substitutions, ideally by not making them

[TestMethod]
public void TestIncludeResourceValue()
{
using HoconUtil hocon = new(@"include required(classpath(""application""))");

Assert.AreEqual(@"include required(classpath(""application""))".ReplaceLineEndings(), hocon.ToString(hocon.Parse()).ReplaceLineEndings());
}

[TestMethod]
public void TestIncludeFileValue()
{
using HoconUtil hocon = new(@"{
text : include ""application.txt""
}");

Assert.AreEqual(@"{
text : include ""application.txt""
}".ReplaceLineEndings(), hocon.ToString(hocon.Parse()).ReplaceLineEndings());
}

[TestMethod]
public void TestIncludeUrlValue()
{
using HoconUtil hocon = new(@"{
text : include ""http://www.bing.com/""
}");

Assert.AreEqual(@"{
text : include ""http://www.bing.com/""
}".ReplaceLineEndings(), hocon.ToString(hocon.Parse()).ReplaceLineEndings());
}

[TestMethod]
public void TestMultipleIncludes()
{
using HoconUtil hocon = new(@"{
text1 : include ""application.txt""
text2 : include ""http://www.bing.com/""
}");

Assert.AreEqual(@"{
text1 : include ""application.txt""
text2 : include ""http://www.bing.com/""
}".ReplaceLineEndings(), hocon.ToString(hocon.Parse()).ReplaceLineEndings());
}

[TestMethod]
public void TestUpdate()
{
using HoconUtil hocon = new(@"include required(classpath(""application""))
akka.http.host-connection-pool.max-open-requests = 16384
akka.http.host-connection-pool.max-connections = 2000
call-caching {
enabled = false
}
system {
input-read-limits {
lines = 1000000
}
}
engine {
filesystems {
local {
enabled: true
}
http {
enabled: true
}
}
}
workflow-options {
workflow-log-dir: ""/cromwell-workflow-logs""
workflow-log-temporary: false
}
backend {
default = ""TES""
providers {
TES {
actor-factory = ""cromwell.backend.impl.tes.TesBackendLifecycleActorFactory""
config {
filesystems {
http { }
}
root = ""/cromwell-executions""
dockerRoot = ""/cromwell-executions""
endpoint = ""http://tes/v1/tasks""
use_tes_11_preview_backend_parameters = true
default-runtime-attributes {
cpu: 1
failOnStderr: false
continueOnReturnCode: 0
memory: ""2 GB""
disk: ""10 GB""
preemptible: true
}
}
}
}
}
database {
db.url = ""jdbc:postgresql://db.postgres.database.azure.com/cromwell_db?sslmode=require""
db.user = ""cromwell""
db.password = ""password""
db.driver = ""org.postgresql.Driver""
profile = ""slick.jdbc.PostgresProfile$""
db.connectionTimeout = 15000
}");
var conf = hocon.Parse();

var changes = HoconParser.Parse($@"
filesystems.blob {{
class = ""cromwell.filesystems.blob.BlobPathBuilderFactory""
global {{
class = ""cromwell.filesystems.blob.BlobFileSystemManager""
config.subscription = ""{"subscription"}""
}}
}}
engine.filesystems.blob.enabled: true
backend.providers.TES.config {{
filesystems {{
http.enabled: true
local.enabled: true
blob.enabled: true
}}
root = ""https://{"storageAccount"}.blob.{"storageSuffix"}/cromwell-executions/""
}}").Value.GetObject();

conf.Value.GetObject().Merge(changes);

Assert.AreEqual(@"include required(classpath(""application""))
akka.http.host-connection-pool.max-open-requests = 16384
akka.http.host-connection-pool.max-connections = 2000
call-caching {
enabled = false
}
system {
input-read-limits {
lines = 1000000
}
}
engine.filesystems : {
local : {
enabled : true
},
http : {
enabled : true
},
blob : {
enabled : true
}
}
workflow-options {
workflow-log-dir: ""/cromwell-workflow-logs""
workflow-log-temporary: false
}
backend : {
default : ""TES"",
providers : {
TES : {
actor-factory : ""cromwell.backend.impl.tes.TesBackendLifecycleActorFactory"",
config : {
filesystems : {
http : {
enabled : true
},
local : {
enabled : true
},
blob : {
enabled : true
}
},
root : ""https://storageAccount.blob.storageSuffix/cromwell-executions/"",
dockerRoot : ""/cromwell-executions"",
endpoint : ""http://tes/v1/tasks"",
use_tes_11_preview_backend_parameters : true,
default-runtime-attributes : {
cpu : 1,
failOnStderr : false,
continueOnReturnCode : 0,
memory : ""2 GB"",
disk : ""10 GB"",
preemptible : true
}
}
}
}
}
database {
db.url = ""jdbc:postgresql://db.postgres.database.azure.com/cromwell_db?sslmode=require""
db.user = ""cromwell""
db.password = ""password""
db.driver = ""org.postgresql.Driver""
profile = ""slick.jdbc.PostgresProfile$""
db.connectionTimeout = 15000
}
filesystems : {
blob : {
class : ""cromwell.filesystems.blob.BlobPathBuilderFactory"",
global : {
class : ""cromwell.filesystems.blob.BlobFileSystemManager"",
config : {
subscription : ""subscription""
}
}
}
}".ReplaceLineEndings(), hocon.ToString(conf).ReplaceLineEndings());
}
}
}
38 changes: 38 additions & 0 deletions src/deploy-cromwell-on-azure/Deployer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,41 @@ await Execute("Connecting to Azure Services...", async () =>
}
}

if (installedVersion is null || installedVersion < new Version(5, 5, 1))
{
var cromwellConfig = GetBlobClient(storageAccountData, ConfigurationContainerName, CromwellConfigurationFileName);
var configContent = await DownloadTextFromStorageAccountAsync(cromwellConfig, cts.Token);

if (!configContent.Contains(".blob.", StringComparison.Ordinal))
{
using HoconUtil hocon = new(configContent);
var conf = hocon.Parse();

var changes = Hocon.HoconParser.Parse($@"
filesystems.blob {{
class = ""cromwell.filesystems.blob.BlobPathBuilderFactory""
global {{
class = ""cromwell.filesystems.blob.BlobFileSystemManager""
config.subscription = ""{configuration.SubscriptionId}""
}}
}}
engine.filesystems.blob.enabled: true
backend.providers.TES.config {{
filesystems {{
http.enabled: true
local.enabled: true
blob.enabled: true
}}
root = ""https://{storageAccountData.Name}.blob.{azureCloudConfig.Suffixes.StorageSuffix}/{ExecutionsContainerName}/""
}}").Value.GetObject();

conf.Value.GetObject().Merge(changes);
await UploadTextToStorageAccountAsync(cromwellConfig, hocon.ToString(conf).ReplaceLineEndings("\r\n"), cts.Token);
}
}

//if (installedVersion is null || installedVersion < new Version(x, y, z))
//{
//}
Expand Down Expand Up @@ -1482,6 +1517,9 @@ await UploadTextToStorageAccountAsync(GetBlobClient(storageAccount, Configuratio
new Utility.ConfigReplaceTextItem("{DatabasePassword}", $"\"{configuration.PostgreSqlCromwellUserPassword}\""),
new Utility.ConfigReplaceTextItem("{DatabaseDriver}", $"\"org.postgresql.Driver\""),
new Utility.ConfigReplaceTextItem("{DatabaseProfile}", "\"slick.jdbc.PostgresProfile$\""),
new Utility.ConfigReplaceTextItem("{StorageAccount}", configuration.StorageAccountName),
new Utility.ConfigReplaceTextItem("{Subscription}", configuration.SubscriptionId),
new Utility.ConfigReplaceTextItem("{StorageSuffix}", azureCloudConfig.Suffixes.StorageSuffix),
], "scripts", CromwellConfigurationFileName), cts.Token);

await UploadTextToStorageAccountAsync(GetBlobClient(storageAccount, TesInternalContainerName, $"{ConfigurationContainerName}/{AllowedVmSizesFileName}"), Utility.GetFileContent("scripts", AllowedVmSizesFileName), cts.Token);
Expand Down
Loading

0 comments on commit aa5372b

Please sign in to comment.