dnfjson: Add a cache of dnf-json results

This adds a cache structure with timeout handling and cache cleanup.
Also adds some testing of the new functions.
This commit is contained in:
Brian C. Lane 2022-08-25 15:34:12 -07:00 committed by Tom Gundersen
parent f4aed3e6e2
commit 35059ca60e
2 changed files with 109 additions and 0 deletions

View file

@ -9,6 +9,8 @@ import (
"sync"
"time"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/gobwas/glob"
)
@ -207,3 +209,60 @@ func dirSize(path string) (uint64, error) {
err := filepath.Walk(path, sizer)
return size, err
}
// dnfResults holds the results of a dnfjson request
// expire is the time the request was made, used to expire the entry
type dnfResults struct {
expire time.Time
pkgs rpmmd.PackageList
}
// dnfCache is a cache of results from dnf-json requests
type dnfCache struct {
results map[string]dnfResults
timeout time.Duration
*sync.RWMutex
}
// NewDNFCache returns a pointer to an initialized dnfCache struct
func NewDNFCache(timeout time.Duration) *dnfCache {
return &dnfCache{
results: make(map[string]dnfResults),
timeout: timeout,
RWMutex: new(sync.RWMutex),
}
}
// CleanCache deletes unused cache entries
// This prevents the cache from growing for longer than the timeout interval
func (d *dnfCache) CleanCache() {
d.Lock()
defer d.Unlock()
// Delete expired resultCache entries
for k := range d.results {
if time.Since(d.results[k].expire) > d.timeout {
delete(d.results, k)
}
}
}
// Get returns the package list and true if cached
// or an empty list and false if not cached or if cache is timed out
func (d *dnfCache) Get(hash string) (rpmmd.PackageList, bool) {
d.RLock()
defer d.RUnlock()
result, ok := d.results[hash]
if !ok || time.Since(result.expire) >= d.timeout {
return rpmmd.PackageList{}, false
}
return result.pkgs, true
}
// Store saves the package list in the cache
func (d *dnfCache) Store(hash string, pkgs rpmmd.PackageList) {
d.Lock()
defer d.Unlock()
d.results[hash] = dnfResults{expire: time.Now(), pkgs: pkgs}
}

View file

@ -8,6 +8,8 @@ import (
"testing"
"time"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/stretchr/testify/assert"
)
@ -273,3 +275,51 @@ func TestCacheCleanup(t *testing.T) {
})
}
}
// Mock package list to use in testing
var PackageList = rpmmd.PackageList{
rpmmd.Package{
Name: "package0",
Summary: "package summary",
Description: "package description",
URL: "https://package-url/",
Epoch: 0,
Version: "1.0.0",
Release: "3",
Arch: "x86_64",
License: "MIT",
},
}
func TestDNFCacheStoreGet(t *testing.T) {
cache := NewDNFCache(1 * time.Second)
assert.Equal(t, cache.timeout, 1*time.Second)
assert.NotNil(t, cache.RWMutex)
cache.Store("notreallyahash", PackageList)
assert.Equal(t, 1, len(cache.results))
pkgs, ok := cache.Get("notreallyahash")
assert.True(t, ok)
assert.Equal(t, "package0", pkgs[0].Name)
}
func TestDNFCacheTimeout(t *testing.T) {
cache := NewDNFCache(1 * time.Second)
cache.Store("notreallyahash", PackageList)
_, ok := cache.Get("notreallyahash")
assert.True(t, ok)
time.Sleep(2 * time.Second)
_, ok = cache.Get("notreallyahash")
assert.False(t, ok)
}
func TestDNFCacheCleanup(t *testing.T) {
cache := NewDNFCache(1 * time.Second)
cache.Store("notreallyahash", PackageList)
time.Sleep(2 * time.Second)
assert.Equal(t, 1, len(cache.results))
cache.CleanCache()
assert.Equal(t, 0, len(cache.results))
_, ok := cache.Get("notreallyahash")
assert.False(t, ok)
}