Implement garbage collection when deleting cached OCI VMs (#185)

* Properly calculate remotely-retrieved manifest digests

* Implement garbage collection when deleting cached OCI VMs
This commit is contained in:
Nikolay Edigaryev 2022-08-18 17:13:06 +04:00 committed by GitHub
parent fea916dacc
commit 1a1f19e169
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 58 additions and 4 deletions

View File

@ -20,6 +20,10 @@ struct VMDirectory: Prunable {
baseURL.appendingPathComponent("nvram.bin")
}
var explicitlyPulledMark: URL {
baseURL.appendingPathComponent(".explicitly-pulled")
}
var name: String {
baseURL.lastPathComponent
}
@ -89,4 +93,12 @@ struct VMDirectory: Prunable {
func sizeBytes() throws -> Int {
try configURL.sizeBytes() + diskURL.sizeBytes() + nvramURL.sizeBytes()
}
func markExplicitlyPulled() {
FileManager.default.createFile(atPath: explicitlyPulledMark.path, contents: nil)
}
func isExplicitlyPulled() -> Bool {
FileManager.default.fileExists(atPath: explicitlyPulledMark.path)
}
}

View File

@ -42,6 +42,44 @@ class VMStorageOCI: PrunableStorage {
func delete(_ name: RemoteName) throws {
try FileManager.default.removeItem(at: vmURL(name))
try gc()
}
func gc() throws {
var refCounts = Dictionary<URL, UInt>()
guard let enumerator = FileManager.default.enumerator(at: baseURL,
includingPropertiesForKeys: [.isSymbolicLinkKey]) else {
return
}
for case let foundURL as URL in enumerator {
let isSymlink = try foundURL.resourceValues(forKeys: [.isSymbolicLinkKey]).isSymbolicLink!
// Perform garbage collection for tag-based images
// with broken outgoing references
if isSymlink && foundURL == foundURL.resolvingSymlinksInPath() {
try FileManager.default.removeItem(at: foundURL)
continue
}
let vmDir = VMDirectory(baseURL: foundURL.resolvingSymlinksInPath())
if !vmDir.initialized {
continue
}
refCounts[vmDir.baseURL] = (refCounts[vmDir.baseURL] ?? 0) + (isSymlink ? 1 : 0)
}
// Perform garbage collection for digest-based images
// with no incoming references
for (baseURL, incRefCount) in refCounts {
let vmDir = VMDirectory(baseURL: baseURL)
if !vmDir.isExplicitlyPulled() && incRefCount == 0 {
try FileManager.default.removeItem(at: baseURL)
}
}
}
func list() throws -> [(String, VMDirectory, Bool)] {
@ -82,10 +120,10 @@ class VMStorageOCI: PrunableStorage {
func pull(_ name: RemoteName, registry: Registry) async throws {
defaultLogger.appendNewLine("pulling manifest...")
let (manifest, _) = try await registry.pullManifest(reference: name.reference.value)
let (manifest, manifestData) = try await registry.pullManifest(reference: name.reference.value)
var digestName = RemoteName(host: name.host, namespace: name.namespace,
reference: Reference(digest: try manifest.digest()))
let digestName = RemoteName(host: name.host, namespace: name.namespace,
reference: Reference(digest: Digest.hash(manifestData)))
if !exists(digestName) {
let tmpVMDir = try VMDirectory.temporary()
@ -113,8 +151,12 @@ class VMStorageOCI: PrunableStorage {
}
if name != digestName {
// Overwrite the old symbolic link
// Create new or overwrite the old symbolic link
try link(from: digestName, to: name)
} else {
// Ensure that images pulled by content digest
// are excluded from garbage collection
VMDirectory(baseURL: vmURL(name)).markExplicitlyPulled()
}
}