Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
mcovarr committed May 29, 2020
2 parents c6468c0 + fc6ad11 commit aecae0e
Show file tree
Hide file tree
Showing 108 changed files with 6,302 additions and 413 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.DS_Store
.artifactory
.idea/*
**/.idea/*
.ensime_cache/*
.config/*
.local/*
Expand Down Expand Up @@ -47,3 +48,6 @@ private_docker_papi_v2_usa.options
tesk_application_ftp.conf
ftp_centaur_cwl_runner.conf
tesk_application.conf
**/__pycache__/
**/venv/
exome_germline_single_sample_v1.3/
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ env:
BUILD_TYPE=dbms
- >-
BUILD_TYPE=singleWorkflowRunner
- >-
BUILD_TYPE=metadataComparisonPython
script:
- src/ci/bin/test.sh
notifications:
Expand Down
33 changes: 32 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
# Cromwell Change Log

## 51 Release Notes

### Changes and Warnings

The configuration format for call cache blacklisting has been updated, please see the [call caching documentation](
https://cromwell.readthedocs.io/en/stable/Configuring/#call-caching) for details.

### Bug fixes

* Fixed a bug where the `size(...)` function did not work correctly on files
from a shared filesystem if `size(...)` was called in the input section on a
relative path.
+ Fixed a bug where the `use_relative_output_paths` option would not preserve intermediate folders.

### New functionality

#### Call caching blacklisting improvements

Cromwell previously supported blacklisting GCS buckets containing cache hits which could not be copied for permissions
reasons. Cromwell now adds support for blacklisting individual cache hits which could not be copied for any reason,
as well as grouping blacklist caches according to a workflow option key. More information available in the [
call caching documentation]( https://cromwell.readthedocs.io/en/stable/Configuring/#call-caching).

#### new xxh64 and fingerprint strategies for call caching

Existing call cache strategies `path` and `path+modtime` don't work when using docker on shared filesystems
(SFS backend, i.e. not in cloud storage). The `file` (md5sum) strategy works, but uses a lot of resources.
Two faster strategies have been added for this use case: `xxh64` and
`fingerprint`. `xxh64` is a lightweight hashing algorithm, `fingerprint` is a strategy designed to be very
lightweight. Read more about it in the [call caching documentation](
https://cromwell.readthedocs.io/en/stable/Configuring/#call-caching).

## 50 Release Notes

### Changes and Warnings
Expand All @@ -11,7 +43,6 @@ Cromwell's metadata archival configuration has changed in a backwards incompatib
please see
[the updated documentation](https://cromwell.readthedocs.io/en/stable/Configuring#hybrid-metadata-storage-classic-carbonite) for details.


## 49 Release Notes

### Changes and Warnings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ package cromwell.backend
import cromwell.backend.MetricableCacheCopyErrorCategory.MetricableCacheCopyErrorCategory
import cromwell.core.JobKey
import cromwell.core.simpleton.WomValueSimpleton
import cromwell.services.CallCaching.CallCachingEntryId

object BackendCacheHitCopyingActor {
final case class CopyOutputsCommand(womValueSimpletons: Seq[WomValueSimpleton], jobDetritusFiles: Map[String, String], returnCode: Option[Int])
final case class CopyOutputsCommand(womValueSimpletons: Seq[WomValueSimpleton], jobDetritusFiles: Map[String, String], cacheHit: CallCachingEntryId, returnCode: Option[Int])

final case class CopyingOutputsFailedResponse(jobKey: JobKey, cacheCopyAttempt: Int, failure: CacheCopyError)
final case class CopyingOutputsFailedResponse(jobKey: JobKey, cacheCopyAttempt: Int, failure: CacheCopyFailure)

sealed trait CacheCopyError
final case class LoggableCacheCopyError(failure: Throwable) extends CacheCopyError
final case class MetricableCacheCopyError(failureCategory: MetricableCacheCopyErrorCategory) extends CacheCopyError
sealed trait CacheCopyFailure
/** A cache hit copy was attempted but failed. */
final case class CopyAttemptError(failure: Throwable) extends CacheCopyFailure
/** Copying was requested for a blacklisted cache hit, however the cache hit copying actor found the hit had already
* been blacklisted so no novel copy attempt was made. */
final case class BlacklistSkip(failureCategory: MetricableCacheCopyErrorCategory) extends CacheCopyFailure
}

object MetricableCacheCopyErrorCategory {
Expand All @@ -20,4 +24,5 @@ object MetricableCacheCopyErrorCategory {
override def toString: String = getClass.getSimpleName.stripSuffix("$").toLowerCase
}
final case object BucketBlacklisted extends MetricableCacheCopyErrorCategory
final case object HitBlacklisted extends MetricableCacheCopyErrorCategory
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,59 @@ package cromwell.backend.standard.callcaching

import com.google.common.cache.{CacheBuilder, CacheLoader}
import cromwell.core.CacheConfig
import cromwell.services.CallCaching.CallCachingEntryId

case class BlacklistCache(config: CacheConfig) {
val cache = {
// Queries to the blacklist cache return false by default (i.e. not blacklisted).
val falseLoader = new CacheLoader[String, java.lang.Boolean]() {
override def load(key: String): java.lang.Boolean = false
sealed trait BlacklistStatus
case object BadCacheResult extends BlacklistStatus
case object GoodCacheResult extends BlacklistStatus
case object UntestedCacheResult extends BlacklistStatus

sealed abstract class BlacklistCache(bucketCacheConfig: CacheConfig,
hitCacheConfig: CacheConfig,
val name: Option[String]) {
val bucketCache = {
// Queries to the bucket blacklist cache return UntestedCacheResult by default.
val unknownLoader = new CacheLoader[String, BlacklistStatus]() {
override def load(key: String): BlacklistStatus = UntestedCacheResult
}

CacheBuilder.
newBuilder().
concurrencyLevel(bucketCacheConfig.concurrency).
maximumSize(bucketCacheConfig.size).
expireAfterWrite(bucketCacheConfig.ttl.length, bucketCacheConfig.ttl.unit).
build[String, BlacklistStatus](unknownLoader)
}

val hitCache = {
// Queries to the hit blacklist cache return UntestedCacheResult by default (i.e. not blacklisted).
val unknownLoader = new CacheLoader[CallCachingEntryId, BlacklistStatus]() {
override def load(key: CallCachingEntryId): BlacklistStatus = UntestedCacheResult
}

CacheBuilder.
newBuilder().
concurrencyLevel(config.concurrency).
maximumSize(config.size).
expireAfterWrite(config.ttl.length, config.ttl.unit).
build[String, java.lang.Boolean](falseLoader)
concurrencyLevel(hitCacheConfig.concurrency).
maximumSize(hitCacheConfig.size).
expireAfterWrite(hitCacheConfig.ttl.length, hitCacheConfig.ttl.unit).
build[CallCachingEntryId, BlacklistStatus](unknownLoader)
}

def isBlacklisted(bucket: String): Boolean = cache.get(bucket)
def getBlacklistStatus(hit: CallCachingEntryId): BlacklistStatus = hitCache.get(hit)

def blacklist(bucket: String): Unit = cache.put(bucket, true)
def getBlacklistStatus(bucket: String): BlacklistStatus = bucketCache.get(bucket)

def blacklist(hit: CallCachingEntryId): Unit = hitCache.put(hit, BadCacheResult)

def blacklist(bucket: String): Unit = bucketCache.put(bucket, BadCacheResult)

def whitelist(hit: CallCachingEntryId): Unit = hitCache.put(hit, GoodCacheResult)

def whitelist(bucket: String): Unit = bucketCache.put(bucket, GoodCacheResult)
}

class RootWorkflowBlacklistCache(bucketCacheConfig: CacheConfig, hitCacheConfig: CacheConfig) extends
BlacklistCache(bucketCacheConfig = bucketCacheConfig, hitCacheConfig = hitCacheConfig, name = None)

class GroupingBlacklistCache(bucketCacheConfig: CacheConfig, hitCacheConfig: CacheConfig, val group: String) extends
BlacklistCache(bucketCacheConfig = bucketCacheConfig, hitCacheConfig = hitCacheConfig, name = Option(group))
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package cromwell.backend.standard.callcaching

import akka.event.LoggingAdapter
import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache}
import com.typesafe.config.Config
import cromwell.core.{CacheConfig, HasWorkflowIdAndSources}
import mouse.boolean._
import net.ceedubs.ficus.Ficus._

import scala.concurrent.duration._
import scala.language.postfixOps

object CallCachingBlacklistManager {
object Defaults {
object Groupings {
val Concurrency = 10000
val Size = 1000L
val Ttl = 2 hours
}
object Hits {
val Concurrency = 10000
val Size = 20000L
val Ttl = 1 hour
}
object Buckets {
val Concurrency = 10000
val Size = 1000L
val Ttl = 1 hour
}
}
}

class CallCachingBlacklistManager(rootConfig: Config, logger: LoggingAdapter) {

// Defined if "call-caching.blacklist-cache.enabled = true".
private val blacklistCacheConfig: Option[Unit] =
rootConfig.getOrElse("call-caching.blacklist-cache.enabled", false).option(())

// Defined if `blacklistCacheConfig` is defined and "call-caching.blacklist-cache.groupings.workflow-option" is defined.
private val blacklistGroupingWorkflowOptionKey: Option[String] = for {
_ <- blacklistCacheConfig // Only return a groupings cache if blacklisting is enabled.
workflowOption <- rootConfig.as[Option[String]]("call-caching.blacklist-cache.groupings.workflow-option")
} yield workflowOption

// Defined if `blacklistGroupingWorkflowOptionKey` is defined.
private val blacklistGroupingCacheConfig: Option[CacheConfig] = {
import CallCachingBlacklistManager.Defaults.Groupings._
for {
_ <- blacklistGroupingWorkflowOptionKey
groupingsOption = rootConfig.as[Option[Config]] ("call-caching.blacklist-cache.groupings")
conf = CacheConfig.config(groupingsOption, defaultConcurrency = Concurrency, defaultSize = Size, defaultTtl = Ttl)
} yield conf
}

// Defined if `blacklistCacheConfig` is defined.
private val blacklistBucketCacheConfig: Option[CacheConfig] = {
import CallCachingBlacklistManager.Defaults.Buckets._
for {
_ <- blacklistCacheConfig
bucketsOption = rootConfig.as[Option[Config]]("call-caching.blacklist-cache.buckets")
conf = CacheConfig.config(bucketsOption, defaultConcurrency = Concurrency, defaultSize = Size, defaultTtl = Ttl)
} yield conf
}

// Defined if `blacklistCacheConfig` is defined.
private val blacklistHitCacheConfig: Option[CacheConfig] = {
import CallCachingBlacklistManager.Defaults.Hits._
for {
_ <- blacklistCacheConfig
hitsOption = rootConfig.as[Option[Config]]("call-caching.blacklist-cache.hits")
conf = CacheConfig.config(hitsOption, defaultConcurrency = Concurrency, defaultSize = Size, defaultTtl = Ttl)
} yield conf
}

// If configuration allows, build a cache of blacklist groupings to BlacklistCaches.
private val blacklistGroupingsCache: Option[LoadingCache[String, BlacklistCache]] = {
def buildBlacklistGroupingsCache(groupingConfig: CacheConfig, bucketConfig: CacheConfig, hitConfig: CacheConfig): LoadingCache[String, BlacklistCache] = {
val emptyBlacklistCacheLoader = new CacheLoader[String, BlacklistCache]() {
override def load(key: String): BlacklistCache = new GroupingBlacklistCache(
bucketCacheConfig = bucketConfig,
hitCacheConfig = hitConfig,
group = key
)
}

CacheBuilder.
newBuilder().
concurrencyLevel(groupingConfig.concurrency).
maximumSize(groupingConfig.size).
expireAfterWrite(groupingConfig.ttl.length, groupingConfig.ttl.unit).
build[String, BlacklistCache](emptyBlacklistCacheLoader)
}

for {
groupingsConfig <- blacklistGroupingCacheConfig
bucketsConfig <- blacklistBucketCacheConfig
hitsConfig <- blacklistHitCacheConfig
} yield buildBlacklistGroupingsCache(groupingsConfig, bucketsConfig, hitsConfig)
}

/**
* If configured return a group blacklist cache, otherwise if configured return a root workflow cache,
* otherwise return nothing.
*/
def blacklistCacheFor(workflow: HasWorkflowIdAndSources): Option[BlacklistCache] = {
// If configuration is set up for blacklist groups and a blacklist group is specified in workflow options,
// get the BlacklistCache for the group.
val groupBlacklistCache: Option[BlacklistCache] = for {
groupings <- blacklistGroupingsCache
groupKey <- blacklistGroupingWorkflowOptionKey
groupFromWorkflowOptions <- workflow.sources.workflowOptions.get(groupKey).toOption
} yield groupings.get(groupFromWorkflowOptions)

// Build a blacklist cache for a single, ungrouped root workflow.
def rootWorkflowBlacklistCache: Option[BlacklistCache] = for {
bucketConfig <- blacklistBucketCacheConfig
hitConfig <- blacklistHitCacheConfig
} yield new RootWorkflowBlacklistCache(bucketCacheConfig = bucketConfig, hitCacheConfig = hitConfig)

// Return the group blacklist cache if available, otherwise a blacklist cache for the root workflow.
val maybeCache = groupBlacklistCache orElse rootWorkflowBlacklistCache
maybeCache collect {
case group: GroupingBlacklistCache =>
logger.info("Workflow {} using group blacklist cache '{}' containing blacklist status for {} hits and {} buckets.",
workflow.id, group.group, group.hitCache.size(), group.bucketCache.size())
case _: RootWorkflowBlacklistCache =>
logger.info("Workflow {} using root workflow blacklist cache.", workflow.id)
}
maybeCache
}
}
Loading

0 comments on commit aecae0e

Please sign in to comment.