[lxc-devel] [lxd/master] Store the simplestreams cache to disk

stgraber on Github lxc-bot at linuxcontainers.org
Fri Oct 14 01:58:09 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1222 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20161014/42762b81/attachment.bin>
-------------- next part --------------
From 30afe435745323c7ddfe3369d0f4e663950d1f91 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 13 Oct 2016 21:53:51 -0400
Subject: [PATCH] Store the simplestreams cache to disk
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Right now, we store simplestreams data for up to an hour. This is done
through a simple memory cache in LXD.

The goal of this was to decrease load on the simplestreams servers and
that's been working pretty well so far.

One thing that this cache doesn't help much with is dealing with
simplestreams servers being offline or the client being temporarily
disconnected from the internet.

Right now once the cache times out, LXD will refresh it, if that fails,
then the cache is gone.

This commit makes it so that our cache persists across LXD restarts by
serializing it to disk. It also changes its use a bit, so that if we
fail to refresh the cache, we log a warning and bump its expiry for
another hour.

This effectively allows LXD to be used completely offline and work fine
with any image that's already in the local cache.

Closes #2487

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go        |  13 +++++
 lxd/daemon_images.go | 134 ++++++++++++++++++++++++++++++++++++++++++++-------
 shared/util.go       |  13 +++++
 3 files changed, 142 insertions(+), 18 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 949d7dc..63d694f 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -748,6 +748,9 @@ func (d *Daemon) Init() error {
 	d.lxcpath = shared.VarPath("containers")
 
 	/* Make sure all our directories are available */
+	if err := os.MkdirAll(shared.CachePath(), 0700); err != nil {
+		return err
+	}
 	if err := os.MkdirAll(shared.VarPath("containers"), 0711); err != nil {
 		return err
 	}
@@ -821,6 +824,12 @@ func (d *Daemon) Init() error {
 		if err != nil {
 			return err
 		}
+
+		/* Restore simplestreams cache */
+		err = imageLoadStreamCache(d)
+		if err != nil {
+			return err
+		}
 	}
 
 	/* Log expiry */
@@ -1166,6 +1175,10 @@ func (d *Daemon) Stop() error {
 	d.devlxd.Close()
 	shared.LogInfof("Stopped /dev/lxd handler")
 
+	shared.LogInfof("Saving simplestreams cache")
+	imageSaveStreamCache()
+	shared.LogInfof("Saved simplestreams cache")
+
 	if d.MockMode || forceStop {
 		return nil
 	}
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index b7ea919..876bd7a 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -4,26 +4,79 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"mime"
 	"mime/multipart"
 	"os"
 	"path/filepath"
+	"strings"
 	"sync"
 	"time"
 
+	"gopkg.in/yaml.v2"
+
 	"github.com/lxc/lxd/shared"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
+// Simplestream cache
 type imageStreamCacheEntry struct {
-	ss     *shared.SimpleStreams
-	expiry time.Time
+	Aliases      shared.ImageAliases `yaml:"aliases"`
+	Fingerprints []string            `yaml:"fingerprints"`
+	expiry       time.Time
+	ss           *shared.SimpleStreams
 }
 
 var imageStreamCache = map[string]*imageStreamCacheEntry{}
 var imageStreamCacheLock sync.Mutex
 
+func imageSaveStreamCache() error {
+	data, err := yaml.Marshal(&imageStreamCache)
+	if err != nil {
+		return err
+	}
+
+	err = ioutil.WriteFile(shared.CachePath("simplestreams.yaml"), data, 0600)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func imageLoadStreamCache(d *Daemon) error {
+	imageStreamCacheLock.Lock()
+	defer imageStreamCacheLock.Unlock()
+
+	if !shared.PathExists(shared.CachePath("simplestreams.yaml")) {
+		return nil
+	}
+
+	content, err := ioutil.ReadFile(shared.CachePath("simplestreams.yaml"))
+	if err != nil {
+		return err
+	}
+
+	err = yaml.Unmarshal(content, imageStreamCache)
+	if err != nil {
+		return err
+	}
+
+	for url, entry := range imageStreamCache {
+		if entry.ss == nil {
+			ss, err := shared.SimpleStreamsClient(url, d.proxy)
+			if err != nil {
+				return err
+			}
+
+			entry.ss = ss
+		}
+	}
+
+	return nil
+}
+
 // ImageDownload checks if we have that Image Fingerprint else
 // downloads the image from a remote server.
 func (d *Daemon) ImageDownload(op *operation, server string, protocol string, certificate string, secret string, alias string, forContainer bool, autoUpdate bool) (string, error) {
@@ -42,34 +95,79 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		imageStreamCacheLock.Lock()
 		entry, _ := imageStreamCache[server]
 		if entry == nil || entry.expiry.Before(time.Now()) {
-			ss, err = shared.SimpleStreamsClient(server, d.proxy)
-			if err != nil {
+			refresh := func() (*imageStreamCacheEntry, error) {
+				// Setup simplestreams client
+				ss, err = shared.SimpleStreamsClient(server, d.proxy)
+				if err != nil {
+					return nil, err
+				}
+
+				// Get all aliases
+				aliases, err := ss.ListAliases()
+				if err != nil {
+					return nil, err
+				}
+
+				// Get all fingerprints
+				images, err := ss.ListImages()
+				if err != nil {
+					return nil, err
+				}
+
+				fingerprints := []string{}
+				for _, image := range images {
+					fingerprints = append(fingerprints, image.Fingerprint)
+				}
+
+				// Generate cache entry
+				entry = &imageStreamCacheEntry{ss: ss, Aliases: aliases, Fingerprints: fingerprints, expiry: time.Now().Add(time.Hour)}
+				imageStreamCache[server] = entry
+				imageSaveStreamCache()
+
+				return entry, nil
+			}
+
+			newEntry, err := refresh()
+			if err == nil {
+				// Cache refreshed
+				entry = newEntry
+			} else if entry != nil {
+				// Failed to fetch entry but existing cache
+				shared.LogWarn("Unable to refresh cache, using stale entry", log.Ctx{"server": server})
+				entry.expiry = time.Now().Add(time.Hour)
+			} else {
+				// Failed to fetch entry and nothing in cache
 				imageStreamCacheLock.Unlock()
 				return "", err
 			}
-
-			entry = &imageStreamCacheEntry{ss: ss, expiry: time.Now().Add(time.Hour)}
-			imageStreamCache[server] = entry
 		} else {
-			shared.LogDebugf("Using SimpleStreams cache entry for %s, expires at %s", server, entry.expiry)
+			shared.LogDebug("Using SimpleStreams cache entry", log.Ctx{"server": server, "expiry": entry.expiry})
 			ss = entry.ss
 		}
 		imageStreamCacheLock.Unlock()
 
-		target := ss.GetAlias(fp)
-		if target != "" {
-			fp = target
-		}
+		// Expand aliases
+		for _, alias := range entry.Aliases {
+			if alias.Name != fp {
+				continue
+			}
 
-		image, err := ss.GetImageInfo(fp)
-		if err != nil {
-			return "", err
+			fp = alias.Target
+			break
 		}
 
-		if fp == alias {
-			alias = image.Fingerprint
+		// Expand fingerprint
+		for _, fingerprint := range entry.Fingerprints {
+			if !strings.HasPrefix(fingerprint, fp) {
+				continue
+			}
+
+			if fp == alias {
+				alias = fingerprint
+			}
+			fp = fingerprint
+			break
 		}
-		fp = image.Fingerprint
 	} else if protocol == "lxd" {
 		target, err := remoteGetImageFingerprint(d, server, certificate, fp)
 		if err == nil && target != "" {
diff --git a/shared/util.go b/shared/util.go
index 7c994a4..2cf0371 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -93,6 +93,19 @@ func VarPath(path ...string) string {
 	return filepath.Join(items...)
 }
 
+// CachePath returns the directory that LXD should its cache under. If LXD_DIR is
+// set, this path is $LXD_DIR/cache, otherwise it is /var/cache/lxd.
+func CachePath(path ...string) string {
+	varDir := os.Getenv("LXD_DIR")
+	logDir := "/var/cache/lxd"
+	if varDir != "" {
+		logDir = filepath.Join(varDir, "cache")
+	}
+	items := []string{logDir}
+	items = append(items, path...)
+	return filepath.Join(items...)
+}
+
 // LogPath returns the directory that LXD should put logs under. If LXD_DIR is
 // set, this path is $LXD_DIR/logs, otherwise it is /var/log/lxd.
 func LogPath(path ...string) string {


More information about the lxc-devel mailing list