lxd/images: Optimize image distribution

If storage.image_volumes is set, only download and distribute the image
if needed. That way, if multiple cluster members have set the same value
for this option, and it's backed by remote storage, the image will be
downloaded only once.

Signed-off-by: Thomas Hipp <thomas.hipp@canonical.com>
This commit is contained in:
Thomas Hipp 2021-03-11 08:35:05 +01:00
parent a95714d514
commit 36f513cf4e
No known key found for this signature in database
GPG Key ID: 36F3DB891755E09B

View File

@ -1240,6 +1240,46 @@ func autoUpdateImages(ctx context.Context, d *Daemon) error {
}
func distributeImage(ctx context.Context, d *Daemon, nodes []string, oldFingerprint string, newImage *api.Image) error {
// Get config of all nodes (incl. own) and check for storage.images_volume.
// If the setting is missing, distribute the image to the node.
// If the option is set, only distribute the image once to nodes with this
// specific pool/volume.
// imageVolumes is a list containing of all image volumes specified by
// storage.images_volume. Since this option is node specific, the values
// may be different for each cluster member.
var imageVolumes []string
err := d.db.Transaction(func(tx *db.NodeTx) error {
config, err := node.ConfigLoad(tx)
if err != nil {
return err
}
vol := config.StorageImagesVolume()
if vol != "" {
fields := strings.Split(vol, "/")
_, pool, _, err := d.cluster.GetStoragePool(fields[0])
if err != nil {
return errors.Wrap(err, "Failed to get pool info")
}
// Add the volume to the list if the pool is backed by remote
// storage as only then the volumes are shared.
if shared.StringInSlice(pool.Driver, db.StorageRemoteDriverNames()) {
imageVolumes = append(imageVolumes, vol)
}
}
return nil
})
// No need to return with an error as this is only an optimization in the
// distribution process. Instead, only log the error.
if err != nil {
logger.Warn("Failed to load config", log.Ctx{"err": err})
}
// Skip own node
address, _ := node.ClusterAddress(d.db)
@ -1280,6 +1320,48 @@ func distributeImage(ctx context.Context, d *Daemon, nodes []string, oldFingerpr
client = client.UseTarget(nodeInfo.Name)
resp, _, err := client.GetServer()
if err != nil {
logger.Warn("Failed to retrieve information about cluster member", log.Ctx{"err": err, "address": nodeAddress})
} else {
vol := ""
val := resp.Config["storage.images_volume"]
if val != nil {
vol = val.(string)
}
skipDistribution := false
// If storage.images_volume is set on the cluster member, check if
// the image has already been downloaded to this volume. If so,
// skip distributing the image to this cluster member.
// If the option is unset, distribute the image.
if vol != "" {
for _, imageVolume := range imageVolumes {
if imageVolume == vol {
skipDistribution = true
break
}
}
if skipDistribution {
continue
}
fields := strings.Split(vol, "/")
pool, _, err := client.GetStoragePool(fields[0])
if err != nil {
logger.Warn("Failed to get pool info", log.Ctx{"err": err, "pool": fields[0]})
} else {
if shared.StringInSlice(pool.Driver, db.StorageRemoteDriverNames()) {
imageVolumes = append(imageVolumes, vol)
}
}
}
}
createArgs := &lxd.ImageCreateArgs{}
imageMetaPath := shared.VarPath("images", newImage.Fingerprint)
imageRootfsPath := shared.VarPath("images", newImage.Fingerprint+".rootfs")