Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
148 lines (129 sloc) 4.62 KB
// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
// +build go1.3
package lxdclient
import (
"fmt"
"github.com/juju/errors"
"github.com/juju/loggo"
"github.com/lxc/lxd"
"github.com/juju/juju/utils/stringforwarder"
)
type rawImageClient interface {
GetAlias(string) string
}
type remoteClient interface {
URL() string
GetAlias(name string) string
// This is like lxd.Client.CopyImage() but simplified and allows us to
// inject a testing double.
CopyImage(imageTarget string, dest rawImageClient, aliases []string, callback func(string)) error
}
type imageClient struct {
raw rawImageClient
connectToSource func(Remote) (remoteClient, error)
}
type rawWrapper struct {
*lxd.Client
}
func (r rawWrapper) URL() string {
return r.Client.BaseURL
}
func (r rawWrapper) CopyImage(imageTarget string, dest rawImageClient, aliases []string, callback func(string)) error {
rawDest, ok := dest.(*lxd.Client)
if !ok {
return errors.Errorf("can only copy images to a real lxd.Client instance")
}
return r.Client.CopyImage(
imageTarget,
rawDest,
false, // copy_aliases
aliases, // create these aliases
false, // make the image public
true, // autoUpdate,
callback,
)
}
func connectToRaw(remote Remote) (remoteClient, error) {
raw, err := newRawClient(remote)
if err != nil {
return nil, err
}
return rawWrapper{raw}, nil
}
// progressContext takes progress messages from LXD and just writes them to
// the associated logger at the given log level.
type progressContext struct {
logger loggo.Logger
level loggo.Level
context string // a format string that should take a single %s parameter
forward func(string) // pass messages onward
}
func (p *progressContext) copyProgress(progress string) {
msg := fmt.Sprintf(p.context, progress)
p.logger.Logf(p.level, msg)
if p.forward != nil {
p.forward(msg)
}
}
// EnsureImageExists makes sure we have a local image so we can launch a
// container.
// @param series: OS series (trusty, precise, etc)
// @param architecture: (TODO) The architecture of the image we want to use
// @param trustLocal: (TODO) check if we already have an image with the right alias.
// Setting this to False means we will always check the remote sources and only
// launch the newest version.
// @param sources: a list of Remotes that we will look in for the image.
// @param copyProgressHandler: a callback function. If we have to download an
// image, we will call this with messages indicating how much of the download
// we have completed (and where we are downloading it from).
func (i *imageClient) EnsureImageExists(series string, sources []Remote, copyProgressHandler func(string)) error {
// TODO(jam) Find a way to test this, even though lxd.Client can't
// really be stubbed out because CopyImage takes one directly and pokes
// at private methods so we can't easily tweak it.
name := i.ImageNameForSeries(series)
lastErr := errors.New("image not imported!")
for _, remote := range sources {
source, err := i.connectToSource(remote)
if err != nil {
logger.Infof("failed to connect to %q: %s", remote.Host, err)
lastErr = err
continue
}
// TODO(jam): there are multiple possible spellings for aliases,
// unfortunately. cloud-images only hosts ubuntu images, and
// aliases them as "trusty" or "trusty/amd64" or
// "trusty/amd64/20160304". However, we should be more
// explicit. and use "ubuntu/trusty/amd64" as our default
// naming scheme, and only fall back for synchronization.
target := source.GetAlias(series)
if target == "" {
logger.Infof("no image for %s found in %s", name, source.URL())
// TODO(jam) Add a test that we skip sources that don't
// have what we are looking for
continue
}
logger.Infof("found image from %s for %s = %s",
source.URL(), series, target)
forwarder := stringforwarder.New(copyProgressHandler)
defer func() {
dropCount := forwarder.Stop()
logger.Debugf("dropped %d progress messages", dropCount)
}()
adapter := &progressContext{
logger: logger,
level: loggo.INFO,
context: fmt.Sprintf("copying image for %s from %s: %%s", name, source.URL()),
forward: forwarder.Forward,
}
err = source.CopyImage(series, i.raw, []string{name}, adapter.copyProgress)
return errors.Annotatef(err, "unable to get LXD image for %s", name)
}
return lastErr
}
// A common place to compute image names (aliases) based on the series
func (i imageClient) ImageNameForSeries(series string) string {
// TODO(jam) Do we need 'ubuntu' in there? We only need it if "series"
// would collide, but all our supported series are disjoint
return fmt.Sprintf("ubuntu-%s", series)
}