From 33d35cf90a6e51842ee6ec9a6f604ef9bf5ae4d6 Mon Sep 17 00:00:00 2001 From: Mike Fedosin Date: Wed, 30 Oct 2019 19:21:52 +0100 Subject: [PATCH] Use "cache" module to cache libvirt and baremetal images --- pkg/tfvars/baremetal/baremetal.go | 13 ++- pkg/tfvars/internal/cache/cache.go | 5 + pkg/tfvars/libvirt/cache.go | 176 ----------------------------- pkg/tfvars/libvirt/libvirt.go | 12 +- 4 files changed, 23 insertions(+), 183 deletions(-) delete mode 100644 pkg/tfvars/libvirt/cache.go diff --git a/pkg/tfvars/baremetal/baremetal.go b/pkg/tfvars/baremetal/baremetal.go index 0ec67cfc28d..8d4c300afd1 100644 --- a/pkg/tfvars/baremetal/baremetal.go +++ b/pkg/tfvars/baremetal/baremetal.go @@ -6,7 +6,7 @@ import ( "fmt" "github.com/metal3-io/baremetal-operator/pkg/bmc" "github.com/metal3-io/baremetal-operator/pkg/hardware" - libvirttfvars "github.com/openshift/installer/pkg/tfvars/libvirt" + "github.com/openshift/installer/pkg/tfvars/internal/cache" "github.com/openshift/installer/pkg/types/baremetal" "github.com/pkg/errors" "net/url" @@ -31,9 +31,14 @@ type config struct { // TFVars generates bare metal specific Terraform variables. func TFVars(libvirtURI, bootstrapProvisioningIP, bootstrapOSImage, externalBridge, provisioningBridge string, platformHosts []*baremetal.Host, image string) ([]byte, error) { - bootstrapOSImage, err := libvirttfvars.CachedImage(bootstrapOSImage) - if err != nil { - return nil, errors.Wrap(err, "failed to use cached bootstrap libvirt image") + // Reuse bootstrapOSImage if it is already a local path URI + if !strings.HasPrefix(bootstrapOSImage, "file://") { + var err error + bootstrapOSImage, err = cache.DownloadImageFile(bootstrapOSImage) + if err != nil { + return nil, errors.Wrap(err, "failed to use cached bootstrap libvirt image") + } + bootstrapOSImage = cache.PathToURI(bootstrapOSImage) } var hosts, rootDevices, properties, driverInfos, instanceInfos []map[string]interface{} diff --git a/pkg/tfvars/internal/cache/cache.go b/pkg/tfvars/internal/cache/cache.go index 126fb5300ce..72173f43976 100644 --- a/pkg/tfvars/internal/cache/cache.go +++ b/pkg/tfvars/internal/cache/cache.go @@ -199,3 +199,8 @@ func DownloadImageFile(baseURL string) (string, error) { return DownloadFile(baseURL, imageDataType) } + +// PathToURI converts a local file path into a URI +func PathToURI(filePath string) string { + return fmt.Sprintf("file://%s", filepath.ToSlash(filePath)) +} diff --git a/pkg/tfvars/libvirt/cache.go b/pkg/tfvars/libvirt/cache.go deleted file mode 100644 index 5ed46b57c01..00000000000 --- a/pkg/tfvars/libvirt/cache.go +++ /dev/null @@ -1,176 +0,0 @@ -package libvirt - -import ( - "crypto/md5" - "encoding/hex" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "strings" - - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -// CachedImage returns the location of the cached image. -// FIXME: Exported for use by baremetal platform. -func CachedImage(uri string) (string, error) { - return cachedImage(uri) -} - -// cachedImage leaves non-file:// image URIs unalterered. -// Other URIs are retrieved with a local cache at -// $XDG_CACHE_HOME/openshift-install/libvirt [1]. This allows you to -// use the same remote image URI multiple times without needing to -// worry about redundant downloads, although you will want to -// periodically blow away your cache. -// -// [1]: https://standards.freedesktop.org/basedir-spec/basedir-spec-0.7.html -func cachedImage(uri string) (string, error) { - if strings.HasPrefix(uri, "file://") { - return uri, nil - } - - logrus.Infof("Fetching OS image: %s", filepath.Base(uri)) - - baseCacheDir, err := os.UserCacheDir() - if err != nil { - return uri, err - } - - cacheDir := filepath.Join(baseCacheDir, "openshift-install", "libvirt") - httpCacheDir := filepath.Join(cacheDir, "http") - // We used to use httpCacheDir, warn if it still exists since the user may - // want to delete it - _, err = os.Stat(httpCacheDir) - if err == nil || !os.IsNotExist(err) { - logrus.Infof("The installer no longer uses %q, it can be deleted", httpCacheDir) - } - - resp, err := http.Get(uri) - if err != nil { - return uri, err - } - if resp.StatusCode != 200 { - return uri, fmt.Errorf("%s while getting %s", resp.Status, uri) - } - defer resp.Body.Close() - - key, err := cacheKey(resp.Header.Get("ETag")) - if err != nil { - return uri, fmt.Errorf("invalid ETag for %s: %v", uri, err) - } - - imageCacheDir := filepath.Join(cacheDir, "image") - err = os.MkdirAll(imageCacheDir, 0777) - if err != nil { - return uri, err - } - - imagePath := filepath.Join(imageCacheDir, key) - _, err = os.Stat(imagePath) - if err == nil { - logrus.Debugf("Using cached OS image %q", imagePath) - } else { - if !os.IsNotExist(err) { - return uri, err - } - - err = cacheImage(resp.Body, imagePath) - if err != nil { - return uri, err - } - } - - return fmt.Sprintf("file://%s", filepath.ToSlash(imagePath)), nil -} - -func cacheKey(etag string) (key string, err error) { - if etag == "" { - return "", fmt.Errorf("caching is not supported when ETag is unset") - } - etagSections := strings.SplitN(etag, "\"", 3) - if len(etagSections) != 3 { - return "", fmt.Errorf("broken quoting: %s", etag) - } - if etagSections[0] == "W/" { - return "", fmt.Errorf("caching is not supported for weak ETags: %s", etag) - } - opaque := etagSections[1] - if opaque == "" { - return "", fmt.Errorf("caching is not supported when the opaque tag is unset: %s", etag) - } - hashed := md5.Sum([]byte(opaque)) - return hex.EncodeToString(hashed[:]), nil -} - -func cacheImage(reader io.Reader, imagePath string) (err error) { - logrus.Debugf("Unpacking OS image into %q...", imagePath) - - flockPath := fmt.Sprintf("%s.lock", imagePath) - flock, err := os.Create(flockPath) - if err != nil { - return err - } - defer flock.Close() - defer func() { - err2 := os.Remove(flockPath) - if err == nil { - err = err2 - } - }() - - err = unix.Flock(int(flock.Fd()), unix.LOCK_EX) - if err != nil { - return err - } - defer func() { - err2 := unix.Flock(int(flock.Fd()), unix.LOCK_UN) - if err == nil { - err = err2 - } - }() - - _, err = os.Stat(imagePath) - if err != nil && !os.IsNotExist(err) { - return nil // another cacheImage beat us to it - } - - tempPath := fmt.Sprintf("%s.tmp", imagePath) - - // Delete the temporary file that may have been left over from previous launches. - err = os.Remove(tempPath) - if err != nil { - if !os.IsNotExist(err) { - return fmt.Errorf("failed to clean up %s: %v", tempPath, err) - } - } else { - logrus.Debugf("Temporary file %v that remained after the previous launches was deleted", tempPath) - } - - file, err := os.OpenFile(tempPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0444) - if err != nil { - return err - } - closed := false - defer func() { - if !closed { - file.Close() - } - }() - - _, err = io.Copy(file, reader) - if err != nil { - return err - } - - err = file.Close() - if err != nil { - return err - } - closed = true - - return os.Rename(tempPath, imagePath) -} diff --git a/pkg/tfvars/libvirt/libvirt.go b/pkg/tfvars/libvirt/libvirt.go index a51fbfba11d..de9a67d4af9 100644 --- a/pkg/tfvars/libvirt/libvirt.go +++ b/pkg/tfvars/libvirt/libvirt.go @@ -6,9 +6,11 @@ import ( "fmt" "net" "strconv" + "strings" "github.com/apparentlymart/go-cidr/cidr" "github.com/openshift/cluster-api-provider-libvirt/pkg/apis/libvirtproviderconfig/v1beta1" + "github.com/openshift/installer/pkg/tfvars/internal/cache" "github.com/pkg/errors" ) @@ -34,9 +36,13 @@ func TFVars(masterConfig *v1beta1.LibvirtMachineProviderConfig, osImage string, return nil, err } - osImage, err = cachedImage(osImage) - if err != nil { - return nil, errors.Wrap(err, "failed to use cached libvirt image") + // Reuse osImage if it is already a local path URI + if !strings.HasPrefix(osImage, "file://") { + osImage, err = cache.DownloadImageFile(osImage) + if err != nil { + return nil, errors.Wrap(err, "failed to use cached libvirt image") + } + osImage = cache.PathToURI(osImage) } cfg := &config{