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