Skip to content

Commit

Permalink
feat: 支持清理引用不存在的Node TencentBlueKing#2306
Browse files Browse the repository at this point in the history
* feat: 支持清理引用不存在的Node TencentBlueKing#2306

* feat: 增加单元测试 TencentBlueKing#2306

* feat: 增加单元测试 TencentBlueKing#2306

* feat: 增加单元测试 TencentBlueKing#2306
  • Loading branch information
cnlkl authored Jul 10, 2024
1 parent ccf97a0 commit 811fce4
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,11 @@ class DeletedNodeCleanupJob(
if (node.sha256.isNullOrEmpty() || node.sha256 == FAKE_SHA256) return
try {
val credentialsKey = getCredentialsKey(node.projectId, node.repoName)
if (!decrementFileReferences(node.sha256, credentialsKey)) {
val deletedDays = node.deleted?.let { Duration.between(it, LocalDateTime.now()).toDays() } ?: 0
val keepRefLostNode = deletedDays < properties.keepRefLostNodeDays
// 需要保留Node用于排查问题时不补偿创建引用,避免引用创建后node记录可以被正常删除
val createIfNotExists = !keepRefLostNode
if (!decrementFileReferences(node.sha256, credentialsKey, createIfNotExists)) {
logger.warn("Clean up node fail collection[$collectionName], node[$node]")
return
}
Expand All @@ -185,7 +189,7 @@ class DeletedNodeCleanupJob(
context.fileCount.addAndGet(result?.deletedCount ?: 0)
}

private fun decrementFileReferences(sha256: String, credentialsKey: String?): Boolean {
private fun decrementFileReferences(sha256: String, credentialsKey: String?, createIfNotExists: Boolean): Boolean {
val collectionName = COLLECTION_FILE_REFERENCE + MongoShardingUtils.shardingSequence(sha256, SHARDING_COUNT)
val criteria = buildCriteria(sha256, credentialsKey)
criteria.and(FileReference::count.name).gt(0)
Expand All @@ -201,6 +205,13 @@ class DeletedNodeCleanupJob(
val newQuery = Query(buildCriteria(sha256, credentialsKey))
mongoTemplate.findOne<FileReference>(newQuery, collectionName) ?: run {
logger.error("Failed to decrement reference of file [$sha256] on credentialsKey [$credentialsKey]")
if (createIfNotExists) {
/* 早期FileReferenceCleanupJob在最终存储不存在时,不会判断对应的node是否存在而是直接删除引用,
* 导致出现node存在而引用不存在的情况,此处为这些引用缺失的数据补偿创建引用以清理对应的node及存储
*/
mongoTemplate.upsert(newQuery, Update().inc(FileReference::count.name, 0), collectionName)
return true
}
return false
}

Expand Down Expand Up @@ -253,7 +264,9 @@ class DeletedNodeCleanupJob(
keySet.add(defaultCredentials.key)
}
keySet.forEach {
decrementFileReferences(sha256, it)
// 由于不确定node在哪个存储,此处无法确定为丢失引用的node创建哪个存储的引用,因此引用丢失时不补偿创建引用
// StorageReconcileJob中会为缺少引用的存储文件补偿创建引用
decrementFileReferences(sha256, it, false)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties(value = "job.deleted-node-cleanup")
class DeletedNodeCleanupJobProperties(
override var cron: String = "0 0 2/6 * * ?",
var deletedNodeReserveDays: Long = 15L
var deletedNodeReserveDays: Long = 15L,
/**
* 保留引用丢失的node天数
*/
var keepRefLostNodeDays: Long = 16L,
) : MongodbJobProperties()
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package com.tencent.bkrepo.job.batch.task.clean

import com.tencent.bkrepo.common.api.pojo.Response
import com.tencent.bkrepo.common.artifact.hash.sha256
import com.tencent.bkrepo.common.mongo.constant.ID
import com.tencent.bkrepo.common.mongo.dao.util.sharding.HashShardingUtils
import com.tencent.bkrepo.common.storage.credentials.InnerCosCredentials
import com.tencent.bkrepo.job.SHARDING_COUNT
import com.tencent.bkrepo.job.UT_PROJECT_ID
import com.tencent.bkrepo.job.UT_REPO_NAME
import com.tencent.bkrepo.job.UT_SHA256
import com.tencent.bkrepo.job.batch.JobBaseTest
import com.tencent.bkrepo.job.batch.context.DeletedNodeCleanupJobContext
import com.tencent.bkrepo.job.migrate.MigrateRepoStorageService
import com.tencent.bkrepo.repository.api.StorageCredentialsClient
import org.bson.types.ObjectId
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.whenever
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.findOne
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.data.mongodb.core.query.Query
import org.springframework.data.mongodb.core.query.isEqualTo
import java.time.LocalDateTime

@DisplayName("已删除Node清理Job测试")
@DataMongoTest
class DeletedNodeCleanupJobTest @Autowired constructor(
private val deletedNodeCleanupJob: DeletedNodeCleanupJob,
private val mongoTemplate: MongoTemplate,
) : JobBaseTest() {

@MockBean
private lateinit var migrateRepoStorageService: MigrateRepoStorageService

@MockBean
private lateinit var storageCredentialsClient: StorageCredentialsClient

@BeforeAll
fun beforeAll() {
createRepo()
}

@BeforeEach
fun beforeEach() {
whenever(storageCredentialsClient.list(anyOrNull())).thenReturn(
Response(code = 0, data = emptyList())
)
whenever(storageCredentialsClient.findByKey(anyOrNull())).thenReturn(
Response(code = 0, data = InnerCosCredentials())
)
}

@Test
fun testRefNotExists() {
// mock data
val node = buildNode()
val nodeShouldKeep = mongoTemplate.insert(node, COLLECTION_NAME)
val nodeShouldNotKeep = mongoTemplate.insert(
node.copy(
id = ObjectId.get().toHexString(),
deleted = LocalDateTime.now().minusDays(40L),
sha256 = node.sha256!!.sha256()
)
)

// test repo of node was deleted
assertNotNull(
mongoTemplate.findOne(Query.query(Criteria.where(ID).isEqualTo(nodeShouldKeep.id)), COLLECTION_NAME)
)
mongoTemplate.remove(Query(), DeletedNodeCleanupJob.Repository::class.java)
deletedNodeCleanupJob.run(nodeShouldKeep, COLLECTION_NAME, DeletedNodeCleanupJobContext())
// 未补偿创建ref
assertNull(findRef(nodeShouldKeep.sha256!!))
// node被删除
assertNull(mongoTemplate.findOne(Query.query(Criteria.where(ID).isEqualTo(nodeShouldKeep.id)), COLLECTION_NAME))

// 恢复数据
createRepo()
mongoTemplate.insert(nodeShouldKeep, COLLECTION_NAME)

// test nodeShouldKeep
assertNull(findRef(nodeShouldKeep.sha256!!))
deletedNodeCleanupJob.run(nodeShouldKeep, COLLECTION_NAME, DeletedNodeCleanupJobContext())
// 未补偿创建ref
assertNull(findRef(nodeShouldKeep.sha256!!))
// node未被删除
assertNotNull(
mongoTemplate.findOne(Query.query(Criteria.where(ID).isEqualTo(nodeShouldKeep.id)), COLLECTION_NAME)
)

// test nodeShouldNotKeep
assertNull(findRef(nodeShouldNotKeep.sha256!!))
deletedNodeCleanupJob.run(nodeShouldNotKeep, COLLECTION_NAME, DeletedNodeCleanupJobContext())
// 成功补偿创建ref
assertEquals(0, findRef(nodeShouldNotKeep.sha256!!)!!.count.toInt())
// node被删除
assertNull(
mongoTemplate.findOne(Query.query(Criteria.where(ID).isEqualTo(nodeShouldNotKeep.id)), COLLECTION_NAME)
)
}

private fun findRef(sha256: String, credentialsKey: String? = null): DeletedNodeCleanupJob.FileReference? {
val criteria = Criteria
.where(DeletedNodeCleanupJob.FileReference::sha256.name).isEqualTo(sha256)
.and(DeletedNodeCleanupJob.FileReference::credentialsKey.name).isEqualTo(credentialsKey)
val collectionName = "file_reference_${HashShardingUtils.shardingSequenceFor(sha256, SHARDING_COUNT)}"
return mongoTemplate.findOne(Query(criteria), DeletedNodeCleanupJob.FileReference::class.java, collectionName)
}

private fun buildNode() = DeletedNodeCleanupJob.Node(
id = ObjectId.get().toHexString(),
projectId = UT_PROJECT_ID,
repoName = UT_REPO_NAME,
folder = false,
sha256 = UT_SHA256,
deleted = LocalDateTime.now().minusDays(1L),
clusterNames = null
)

private fun createRepo() {
mongoTemplate.insert(
DeletedNodeCleanupJob.Repository(
id = ObjectId.get().toHexString(),
projectId = UT_PROJECT_ID,
name = UT_REPO_NAME,
credentialsKey = null
)
)
}

companion object {
private val COLLECTION_NAME = "node_${HashShardingUtils.shardingSequenceFor(UT_PROJECT_ID, SHARDING_COUNT)}"
}
}

0 comments on commit 811fce4

Please sign in to comment.