Skip to content

Commit

Permalink
feat: Proxy支持删除未同步的文件存储 TencentBlueKing#1836
Browse files Browse the repository at this point in the history
  • Loading branch information
yaoxuwan committed Mar 7, 2024
1 parent fc0657b commit 5be36a0
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,4 @@ interface ProxyRepositoryClient {
@ApiParam(value = "仓库名称", required = true)
@PathVariable repoName: String
): Response<RepositoryInfo?>

@ApiOperation("查询仓库列表")
@GetMapping("/list/{projectId}")
fun listRepo(
@ApiParam(value = "所属项目", required = true)
@PathVariable projectId: String
): Response<List<RepositoryInfo>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,4 @@ class ProxyRepositoryController(
override fun getRepoInfo(projectId: String, repoName: String): Response<RepositoryInfo?> {
return ResponseBuilder.success(repositoryService.getRepoInfo(projectId, repoName, null))
}

override fun listRepo(projectId: String): Response<List<RepositoryInfo>> {
return ResponseBuilder.success(repositoryService.listRepo(projectId))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.
*
* Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved.
*
* BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.
*
* A copy of the MIT License is included in this file.
*
*
* Terms of the MIT License:
* ---------------------------------------------------
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package com.tencent.bkrepo.proxy.artifact.storage

import com.tencent.bkrepo.common.service.exception.RemoteErrorCodeException
import com.tencent.bkrepo.common.storage.filesystem.ArtifactFileVisitor
import com.tencent.bkrepo.repository.api.proxy.ProxyFileReferenceClient
import org.slf4j.LoggerFactory
import java.io.File
import java.nio.file.FileVisitResult
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes

/**
* 不同步时,清理本地存储的文件遍历器
*/
class ProxyCleanFileVisitor(
private val proxyFileReferenceClient: ProxyFileReferenceClient,
private val cacheExpireDays: Int
) : ArtifactFileVisitor() {
override fun needWalk(): Boolean {
return true
}

override fun visitFile(filePath: Path, attrs: BasicFileAttributes?): FileVisitResult {
try {
if (filePath.toString().endsWith(".sync")) {
val syncFile = filePath.toFile()
val dataFile = File(filePath.toString().removeSuffix(".sync"))
cleanFile(dataFile, syncFile)
} else {
ProxyStorageUtils.deleteCacheFile(filePath, cacheExpireDays)
}
} catch (e: Exception) {
logger.error("clean file error: ", e)
}
return FileVisitResult.CONTINUE
}

/**
* 当文件索引为0时,删除本地存储
*/
private fun cleanFile(dataFile: File, syncFile: File) {
val sha256 = dataFile.name
val credentialKeys = ProxyStorageUtils.readStorageCredentialKeys(syncFile)
try {
credentialKeys.forEach {
val fileReference = proxyFileReferenceClient.count(sha256, it).data!!
if (fileReference > 0) {
logger.info("skip clean file[$sha256] which file reference is $fileReference on storage[$it]")
return
}
}
} catch (e: RemoteErrorCodeException) {
logger.warn("get sha256[$sha256] file reference failed: ${e.errorMessage}")
return
}
dataFile.delete()
syncFile.delete()
logger.info("clean file[$sha256] success")
}

companion object {
private val logger = LoggerFactory.getLogger(ProxyCleanFileVisitor::class.java)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@
package com.tencent.bkrepo.proxy.artifact.storage

import com.tencent.bkrepo.common.api.constant.ensureSuffix
import com.tencent.bkrepo.common.api.util.StreamUtils.readText
import com.tencent.bkrepo.common.artifact.api.ArtifactFile
import com.tencent.bkrepo.common.artifact.stream.ArtifactInputStream
import com.tencent.bkrepo.common.artifact.stream.EmptyInputStream
import com.tencent.bkrepo.common.artifact.stream.Range
import com.tencent.bkrepo.common.artifact.stream.artifactStream
import com.tencent.bkrepo.common.storage.core.AbstractStorageService
import com.tencent.bkrepo.common.storage.credentials.FileSystemCredentials
import com.tencent.bkrepo.common.storage.credentials.StorageCredentials
import com.tencent.bkrepo.common.storage.filesystem.FileSystemClient
import com.tencent.bkrepo.repository.api.proxy.ProxyFileReferenceClient
import org.apache.commons.io.IOUtils
import java.nio.charset.Charset
import java.util.concurrent.atomic.AtomicBoolean
Expand All @@ -45,6 +46,7 @@ class ProxyStorageService : AbstractStorageService() {

/**
* 多存储一个sha256.sync文件,用来标记对应sha256的文件待同步至服务端
* sha256.sync文件中记录credentialsKey
*/
override fun doStore(
path: String,
Expand All @@ -53,22 +55,24 @@ class ProxyStorageService : AbstractStorageService() {
credentials: StorageCredentials,
cancel: AtomicBoolean?
) {
val proxyCredentials = storageProperties.defaultStorageCredentials()
when {
artifactFile.isInMemory() -> {
fileStorage.store(path, filename, artifactFile.getInputStream(), artifactFile.getSize(), credentials)
fileStorage.store(
path, filename, artifactFile.getInputStream(), artifactFile.getSize(), proxyCredentials
)
}
artifactFile.isFallback() -> {
fileStorage.store(path, filename, artifactFile.flushToFile(), credentials)
fileStorage.store(path, filename, artifactFile.flushToFile(), proxyCredentials)
}
else -> {
fileStorage.store(path, filename, artifactFile.flushToFile(), credentials)
fileStorage.store(path, filename, artifactFile.flushToFile(), proxyCredentials)
}
}
val syncFilename = filename.ensureSuffix(".sync")
val inputStream =
credentials.key?.let { IOUtils.toInputStream(it, Charset.defaultCharset()) } ?: EmptyInputStream()
val size = credentials.key?.length?.toLong() ?: 0L
fileStorage.store(path, syncFilename, inputStream, size, credentials)
val inputStream = IOUtils.toInputStream(credentials.key.toString(), Charset.defaultCharset())
val size = credentials.key.toString().length.toLong()
fileStorage.store(path, syncFilename, inputStream, size, storageProperties.defaultStorageCredentials())
}

override fun doLoad(
Expand All @@ -77,25 +81,62 @@ class ProxyStorageService : AbstractStorageService() {
range: Range,
credentials: StorageCredentials
): ArtifactInputStream? {
return fileStorage.load(path, filename, range, credentials)?.artifactStream(range)
return fileStorage.load(path, filename, range, storageProperties.defaultStorageCredentials())
?.artifactStream(range)
}

override fun doDelete(path: String, filename: String, credentials: StorageCredentials) {
return fileStorage.delete(path, filename, credentials)
return fileStorage.delete(path, filename, storageProperties.defaultStorageCredentials())
}

override fun doExist(path: String, filename: String, credentials: StorageCredentials): Boolean {
return fileStorage.exist(path, filename, credentials)
val proxyCredentials = storageProperties.defaultStorageCredentials()
val dataFileExist = fileStorage.exist(path, filename, proxyCredentials)
if (!dataFileExist) {
return false
}

// 数据文件存在,确认对应credentialsKey已记录
val syncFileName = filename.plus(".sync")
val syncFileExist = fileStorage.exist(path, syncFileName, proxyCredentials)
if (syncFileExist) {
val keys = fileStorage.load(path, syncFileName, Range.FULL_RANGE, proxyCredentials)?.use {
it.readText().lines().toMutableSet()
}
keys?.add(credentials.key.toString())
val content = keys.orEmpty().joinToString(System.lineSeparator())
val inputStream = IOUtils.toInputStream(content, Charset.defaultCharset())
val size = content.length.toLong()
fileStorage.delete(path, syncFileName, proxyCredentials)
fileStorage.store(path, syncFileName, inputStream, size, proxyCredentials)
} else {
val inputStream = IOUtils.toInputStream(credentials.key.toString(), Charset.defaultCharset())
val size = credentials.key.toString().length.toLong()
fileStorage.store(path, syncFileName, inputStream, size, storageProperties.defaultStorageCredentials())
}
return true
}

/**
* 同步数据到服务端
*/
fun sync(rate: Long, cacheExpireDays: Int) {
val credentials = getCredentialsOrDefault(null)
val credentials = storageProperties.defaultStorageCredentials()
val visitor = ProxySyncFileVisitor(rate, cacheExpireDays)
require(credentials is FileSystemCredentials)
FileSystemClient(credentials.path).walk(visitor)
return
}

/**
* 不同步时,清理已删除文件对应存储
*/
fun clean(
proxyFileReferenceClient: ProxyFileReferenceClient,
cacheExpireDays: Int
) {
val credentials = storageProperties.defaultStorageCredentials()
val visitor = ProxyCleanFileVisitor(proxyFileReferenceClient, cacheExpireDays)
require(credentials is FileSystemCredentials)
FileSystemClient(credentials.path).walk(visitor)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.
*
* Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved.
*
* BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.
*
* A copy of the MIT License is included in this file.
*
*
* Terms of the MIT License:
* ---------------------------------------------------
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package com.tencent.bkrepo.proxy.artifact.storage

import com.tencent.bkrepo.common.storage.util.existReal
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.attribute.BasicFileAttributeView
import java.time.Instant
import java.time.temporal.ChronoUnit

object ProxyStorageUtils {

fun deleteCacheFile(filePath: Path, cacheExpireDays: Int) {
if (filePath.toFile().isDirectory) {
return
}
val syncFilePath = Paths.get(filePath.toString().plus(".sync"))
// 还未同步到服务端
if (syncFilePath.existReal()) {
return
}
val view = Files.getFileAttributeView(filePath, BasicFileAttributeView::class.java)
val attributes = view.readAttributes()
val aTime = attributes.lastAccessTime().toInstant()
val expireTime = Instant.now().minus(cacheExpireDays.toLong(), ChronoUnit.DAYS)
if (aTime.isBefore(expireTime)) {
filePath.toFile().delete()
}
}

fun readStorageCredentialKeys(syncFile: File): List<String?> {
if (syncFile.length() == 0L) {
return listOf(null)
}

val keys = mutableListOf<String?>()
syncFile.readLines().forEach {
if (it == "null") {
keys.add(null)
} else {
keys.add(it)
}
}
return keys
}
}
Loading

0 comments on commit 5be36a0

Please sign in to comment.