github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/container/lxc/clonetemplate.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lxc
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/utils/proxy"
    16  	"github.com/juju/utils/tailer"
    17  	"launchpad.net/golxc"
    18  
    19  	"github.com/juju/juju/cloudconfig/containerinit"
    20  	"github.com/juju/juju/container"
    21  	"github.com/juju/juju/instance"
    22  	"github.com/juju/juju/juju/arch"
    23  )
    24  
    25  var (
    26  	TemplateLockDir = "/var/lib/juju/locks"
    27  
    28  	TemplateStopTimeout = 5 * time.Minute
    29  )
    30  
    31  func AcquireTemplateLock(name, message string) (*container.Lock, error) {
    32  	logger.Infof("wait for flock on %v", name)
    33  	lock, err := container.NewLock(TemplateLockDir, name)
    34  	if err != nil {
    35  		logger.Tracef("failed to create flock for template: %v", err)
    36  		return nil, err
    37  	}
    38  	err = lock.Lock(message)
    39  	if err != nil {
    40  		logger.Tracef("failed to acquire lock for template: %v", err)
    41  		return nil, err
    42  	}
    43  	return lock, nil
    44  }
    45  
    46  // Make sure a template exists that we can clone from.
    47  func EnsureCloneTemplate(
    48  	backingFilesystem string,
    49  	series string,
    50  	networkConfig *container.NetworkConfig,
    51  	authorizedKeys string,
    52  	aptProxy proxy.Settings,
    53  	aptMirror string,
    54  	enablePackageUpdates bool,
    55  	enableOSUpgrades bool,
    56  	imageURLGetter container.ImageURLGetter,
    57  	useAUFS bool,
    58  ) (golxc.Container, error) {
    59  	name := fmt.Sprintf("juju-%s-lxc-template", series)
    60  	containerDirectory, err := container.NewDirectory(name)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	lock, err := AcquireTemplateLock(name, "ensure clone exists")
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	defer lock.Unlock()
    70  
    71  	lxcContainer := LxcObjectFactory.New(name)
    72  	// Early exit if the container has been constructed before.
    73  	if lxcContainer.IsConstructed() {
    74  		logger.Infof("template exists, continuing")
    75  		return lxcContainer, nil
    76  	}
    77  	logger.Infof("template does not exist, creating")
    78  
    79  	userData, err := containerinit.TemplateUserData(
    80  		series,
    81  		authorizedKeys,
    82  		aptProxy,
    83  		aptMirror,
    84  		enablePackageUpdates,
    85  		enableOSUpgrades,
    86  		networkConfig,
    87  	)
    88  	if err != nil {
    89  		logger.Tracef("failed to create template user data for template: %v", err)
    90  		return nil, err
    91  	}
    92  	userDataFilename, err := containerinit.WriteCloudInitFile(containerDirectory, userData)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	templateParams := []string{
    98  		"--debug",                      // Debug errors in the cloud image
    99  		"--userdata", userDataFilename, // Our groovey cloud-init
   100  		"--hostid", name, // Use the container name as the hostid
   101  		"-r", series,
   102  	}
   103  	var caCert []byte
   104  	if imageURLGetter != nil {
   105  		arch := arch.HostArch()
   106  		imageURL, err := imageURLGetter.ImageURL(instance.LXC, series, arch)
   107  		if err != nil {
   108  			return nil, errors.Annotatef(err, "cannot determine cached image URL")
   109  		}
   110  		templateParams = append(templateParams, "-T", imageURL)
   111  		caCert = imageURLGetter.CACert()
   112  	}
   113  	var extraCreateArgs []string
   114  	if backingFilesystem == Btrfs {
   115  		extraCreateArgs = append(extraCreateArgs, "-B", Btrfs)
   116  	}
   117  
   118  	// Create the container.
   119  	logger.Tracef("create the template container")
   120  	err = createContainer(
   121  		lxcContainer,
   122  		containerDirectory,
   123  		networkConfig,
   124  		extraCreateArgs,
   125  		templateParams,
   126  		caCert,
   127  	)
   128  	if err != nil {
   129  		logger.Errorf("lxc template container creation failed: %v", err)
   130  		return nil, err
   131  	}
   132  	// Make sure that the mount dir has been created.
   133  	logger.Tracef("make the mount dir for the shared logs")
   134  	if err := os.MkdirAll(internalLogDir(name), 0755); err != nil {
   135  		logger.Tracef("failed to create internal /var/log/juju mount dir: %v", err)
   136  		return nil, err
   137  	}
   138  
   139  	// Start the lxc container with the appropriate settings for grabbing the
   140  	// console output and a log file.
   141  	consoleFile := filepath.Join(containerDirectory, "console.log")
   142  	lxcContainer.SetLogFile(filepath.Join(containerDirectory, "container.log"), golxc.LogDebug)
   143  	logger.Tracef("start the container")
   144  	// We explicitly don't pass through the config file to the container.Start
   145  	// method as we have passed it through at container creation time.  This
   146  	// is necessary to get the appropriate rootfs reference without explicitly
   147  	// setting it ourselves.
   148  	if err = lxcContainer.Start("", consoleFile); err != nil {
   149  		logger.Errorf("container failed to start: %v", err)
   150  		return nil, err
   151  	}
   152  	logger.Infof("template container started, now wait for it to stop")
   153  	// Perhaps we should wait for it to finish, and the question becomes "how
   154  	// long do we wait for it to complete?"
   155  
   156  	console, err := os.Open(consoleFile)
   157  	if err != nil {
   158  		// can't listen
   159  		return nil, err
   160  	}
   161  
   162  	tailWriter := &logTail{tick: time.Now()}
   163  	consoleTailer := tailer.NewTailer(console, tailWriter, nil)
   164  	defer consoleTailer.Stop()
   165  
   166  	// We should wait maybe 1 minute between output?
   167  	// if no output check to see if stopped
   168  	// If we have no output and still running, something has probably gone wrong
   169  	for lxcContainer.IsRunning() {
   170  		if tailWriter.lastTick().Before(time.Now().Add(-TemplateStopTimeout)) {
   171  			logger.Infof("not heard anything from the template log for five minutes")
   172  			return nil, fmt.Errorf("template container %q did not stop", name)
   173  		}
   174  		time.Sleep(time.Second)
   175  	}
   176  
   177  	return lxcContainer, nil
   178  }
   179  
   180  type logTail struct {
   181  	tick  time.Time
   182  	mutex sync.Mutex
   183  }
   184  
   185  var _ io.Writer = (*logTail)(nil)
   186  
   187  func (t *logTail) Write(data []byte) (int, error) {
   188  	logger.Tracef(string(data))
   189  	t.mutex.Lock()
   190  	defer t.mutex.Unlock()
   191  	t.tick = time.Now()
   192  	return len(data), nil
   193  }
   194  
   195  func (t *logTail) lastTick() time.Time {
   196  	t.mutex.Lock()
   197  	defer t.mutex.Unlock()
   198  	tick := t.tick
   199  	return tick
   200  }