Skip to content

Commit

Permalink
Fix Condalock fetching for cached build layers (#727)
Browse files Browse the repository at this point in the history

Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
Co-authored-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
  • Loading branch information
munishchouhan and pditommaso authored Dec 15, 2024
1 parent df2ddb9 commit 13a5937
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,17 @@ class BuildLogServiceImpl implements BuildLogService {
if( !logs ) return
try {
String condaLock = extractCondaLockFile(logs)
if (condaLock){
/* When a container image is cached, dockerfile does not get executed.
In that case condalock file will contain "cat environment.lock" because its not been executed.
So wave will check the previous builds of that container image
and render the condalock file from latest successful build
and replace with the current build's condalock file.
*/
if( condaLock && condaLock.contains('cat environment.lock') ){
condaLock = fetchValidCondaLock(buildId)
}

if ( condaLock ){
log.debug "Storing conda lock for buildId: $buildId"
final uploadRequest = UploadRequest.fromBytes(condaLock.bytes, condaLockKey(buildId))
objectStorageOperations.upload(uploadRequest)
Expand Down Expand Up @@ -198,4 +208,32 @@ class BuildLogServiceImpl implements BuildLogService {
.replaceAll(/#\d+ \d+\.\d+\s*/, '')
}

String fetchValidCondaLock(String buildId) {
try {
final result = fetchValidCondaLock0(buildId)
if( result )
log.debug "Container Image is already cached for buildId: $buildId - uploading build's condalock file from buildId: $result"
else
log.warn "Container Image is already cached for buildId: $buildId - Unable to find condalock file from previous build"
return result
}
catch (Throwable t) {
log.error "Unable to determine condalock content for buildId: ${buildId} - cause: ${t.message}", t
return null
}
}

private String fetchValidCondaLock0(String buildId) {
def builds = persistenceService.allBuilds(buildId.split('-')[1].split('_')[0])
for (def build : builds) {
if ( build.succeeded() ){
def curCondaLock = fetchCondaLockString(build.buildId)
if( curCondaLock && !curCondaLock.contains('cat environment.lock') ){
return curCondaLock
}
}
}
return null
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,19 @@ import spock.lang.Specification
import spock.lang.Unroll

import io.micronaut.objectstorage.InputStreamMapper
import io.micronaut.objectstorage.ObjectStorageOperations
import io.micronaut.objectstorage.aws.AwsS3Configuration
import io.micronaut.objectstorage.aws.AwsS3ObjectStorageEntry
import io.micronaut.objectstorage.aws.AwsS3Operations
import io.seqera.wave.service.persistence.PersistenceService
import io.seqera.wave.service.persistence.WaveBuildRecord
import io.seqera.wave.test.AwsS3TestContainer
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
import software.amazon.awssdk.core.ResponseInputStream
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.model.GetObjectResponse

/**
*
Expand Down Expand Up @@ -167,4 +173,74 @@ class BuildLogsServiceTest extends Specification implements AwsS3TestContainer {
noExceptionThrown()
}

def 'should return valid conda lock from previous successful build'() {
given:
def persistenceService = Mock(PersistenceService)
def objectStorageOperations = Mock(ObjectStorageOperations)
def service = new BuildLogServiceImpl(persistenceService: persistenceService, objectStorageOperations: objectStorageOperations)
def build1 = Mock(WaveBuildRecord) {
succeeded() >> false
buildId >> 'bd-abc_1'
}
def build2 = Mock(WaveBuildRecord) {
succeeded() >> true
buildId >> 'bd-abc_2'
}
def build3 = Mock(WaveBuildRecord) {
succeeded() >> true
buildId >> 'bd-abc_3'
}
def responseMetadata = GetObjectResponse.builder()
.contentLength(1024L)
.contentType("text/plain")
.build()
def contentStream = new ByteArrayInputStream("valid conda lock".bytes);
def responseInputStream = new ResponseInputStream<>(responseMetadata, contentStream);
persistenceService.allBuilds(_) >> [build1, build2]
objectStorageOperations.retrieve(service.condaLockKey('bd-abc_2')) >> Optional.of(new AwsS3ObjectStorageEntry('bd-abc_2', responseInputStream))

expect:
service.fetchValidCondaLock('bd-abc_3') == 'valid conda lock'
}

def 'should return null when no successful build has valid conda lock'() {
given:
def persistenceService = Mock(PersistenceService)
def objectStorageOperations = Mock(ObjectStorageOperations)
def service = new BuildLogServiceImpl(persistenceService: persistenceService, objectStorageOperations: objectStorageOperations)
def build1 = Mock(WaveBuildRecord) {
succeeded() >> false
buildId >> 'bd-abc_1'
}
def build2 = Mock(WaveBuildRecord) {
succeeded() >> true
buildId >> 'bd-abc_2'
}
def build3 = Mock(WaveBuildRecord) {
succeeded() >> true
buildId >> 'bd-abc_3'
}
def responseMetadata = GetObjectResponse.builder()
.contentLength(1024L)
.contentType("text/plain")
.build()
def contentStream = new ByteArrayInputStream("cat environment.lock".bytes);
def responseInputStream = new ResponseInputStream<>(responseMetadata, contentStream);
persistenceService.allBuilds(_) >> [build1, build2]
objectStorageOperations.retrieve(service.condaLockKey('bd-abc_2')) >> Optional.of(new AwsS3ObjectStorageEntry('bd-abc_2', responseInputStream))

expect:
service.fetchValidCondaLock('bd-abc_3') == null
}

def 'should return null when no builds are available'() {
given:
def persistenceService = Mock(PersistenceService)
def service = new BuildLogServiceImpl(persistenceService: persistenceService)
persistenceService.allBuilds(_) >> []

expect:
service.fetchValidCondaLock('bd-abc_1') == null
}

}

0 comments on commit 13a5937

Please sign in to comment.