github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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"
    16  	"github.com/juju/utils/proxy"
    17  	"github.com/juju/utils/tailer"
    18  	"launchpad.net/golxc"
    19  
    20  	corecloudinit "github.com/juju/juju/cloudinit"
    21  	"github.com/juju/juju/container"
    22  	"github.com/juju/juju/environs/cloudinit"
    23  	"github.com/juju/juju/instance"
    24  	"github.com/juju/juju/juju/arch"
    25  )
    26  
    27  const (
    28  	templateShutdownUpstartFilename = "/etc/init/juju-template-restart.conf"
    29  	templateShutdownUpstartScript   = `
    30  description "Juju lxc template shutdown job"
    31  author "Juju Team <juju@lists.ubuntu.com>"
    32  start on stopped cloud-final
    33  
    34  script
    35    shutdown -h now
    36  end script
    37  
    38  post-stop script
    39    rm ` + templateShutdownUpstartFilename + `
    40  end script
    41  `
    42  )
    43  
    44  var (
    45  	TemplateLockDir = "/var/lib/juju/locks"
    46  
    47  	TemplateStopTimeout = 5 * time.Minute
    48  )
    49  
    50  // templateUserData returns a minimal user data necessary for the template.
    51  // This should have the authorized keys, base packages, the cloud archive if
    52  // necessary,  initial apt proxy config, and it should do the apt-get
    53  // update/upgrade initially.
    54  func templateUserData(
    55  	series string,
    56  	authorizedKeys string,
    57  	aptProxy proxy.Settings,
    58  	aptMirror string,
    59  	enablePackageUpdates bool,
    60  	enableOSUpgrades bool,
    61  ) ([]byte, error) {
    62  	config := corecloudinit.New()
    63  	config.AddScripts(
    64  		"set -xe", // ensure we run all the scripts or abort.
    65  	)
    66  	config.AddSSHAuthorizedKeys(authorizedKeys)
    67  	if enablePackageUpdates {
    68  		cloudinit.MaybeAddCloudArchiveCloudTools(config, series)
    69  	}
    70  	cloudinit.AddAptCommands(aptProxy, aptMirror, config, enablePackageUpdates, enableOSUpgrades)
    71  	config.AddScripts(
    72  		fmt.Sprintf(
    73  			"printf '%%s\n' %s > %s",
    74  			utils.ShQuote(templateShutdownUpstartScript),
    75  			templateShutdownUpstartFilename,
    76  		))
    77  
    78  	renderer, err := corecloudinit.NewRenderer(series)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	data, err := renderer.Render(config)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	return data, nil
    87  }
    88  
    89  func AcquireTemplateLock(name, message string) (*container.Lock, error) {
    90  	logger.Infof("wait for flock on %v", name)
    91  	lock, err := container.NewLock(TemplateLockDir, name)
    92  	if err != nil {
    93  		logger.Tracef("failed to create flock for template: %v", err)
    94  		return nil, err
    95  	}
    96  	err = lock.Lock(message)
    97  	if err != nil {
    98  		logger.Tracef("failed to acquire lock for template: %v", err)
    99  		return nil, err
   100  	}
   101  	return lock, nil
   102  }
   103  
   104  // Make sure a template exists that we can clone from.
   105  func EnsureCloneTemplate(
   106  	backingFilesystem string,
   107  	series string,
   108  	networkConfig *container.NetworkConfig,
   109  	authorizedKeys string,
   110  	aptProxy proxy.Settings,
   111  	aptMirror string,
   112  	enablePackageUpdates bool,
   113  	enableOSUpgrades bool,
   114  	imageURLGetter container.ImageURLGetter,
   115  ) (golxc.Container, error) {
   116  	name := fmt.Sprintf("juju-%s-lxc-template", series)
   117  	containerDirectory, err := container.NewDirectory(name)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	lock, err := AcquireTemplateLock(name, "ensure clone exists")
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	defer lock.Unlock()
   127  
   128  	lxcContainer := LxcObjectFactory.New(name)
   129  	// Early exit if the container has been constructed before.
   130  	if lxcContainer.IsConstructed() {
   131  		logger.Infof("template exists, continuing")
   132  		return lxcContainer, nil
   133  	}
   134  	logger.Infof("template does not exist, creating")
   135  
   136  	userData, err := templateUserData(
   137  		series,
   138  		authorizedKeys,
   139  		aptProxy,
   140  		aptMirror,
   141  		enablePackageUpdates,
   142  		enableOSUpgrades,
   143  	)
   144  	if err != nil {
   145  		logger.Tracef("failed to create template user data for template: %v", err)
   146  		return nil, err
   147  	}
   148  	userDataFilename, err := container.WriteCloudInitFile(containerDirectory, userData)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	templateParams := []string{
   154  		"--debug",                      // Debug errors in the cloud image
   155  		"--userdata", userDataFilename, // Our groovey cloud-init
   156  		"--hostid", name, // Use the container name as the hostid
   157  		"-r", series,
   158  	}
   159  	var caCert []byte
   160  	if imageURLGetter != nil {
   161  		arch := arch.HostArch()
   162  		imageURL, err := imageURLGetter.ImageURL(instance.LXC, series, arch)
   163  		if err != nil {
   164  			return nil, errors.Annotatef(err, "cannot determine cached image URL")
   165  		}
   166  		templateParams = append(templateParams, "-T", imageURL)
   167  		caCert = imageURLGetter.CACert()
   168  	}
   169  	var extraCreateArgs []string
   170  	if backingFilesystem == Btrfs {
   171  		extraCreateArgs = append(extraCreateArgs, "-B", Btrfs)
   172  	}
   173  
   174  	// Create the container.
   175  	logger.Tracef("create the template container")
   176  	err = createContainer(
   177  		lxcContainer,
   178  		containerDirectory,
   179  		networkConfig,
   180  		extraCreateArgs,
   181  		templateParams,
   182  		caCert,
   183  	)
   184  	if err != nil {
   185  		logger.Errorf("lxc template container creation failed: %v", err)
   186  		return nil, err
   187  	}
   188  	// Make sure that the mount dir has been created.
   189  	logger.Tracef("make the mount dir for the shared logs")
   190  	if err := os.MkdirAll(internalLogDir(name), 0755); err != nil {
   191  		logger.Tracef("failed to create internal /var/log/juju mount dir: %v", err)
   192  		return nil, err
   193  	}
   194  
   195  	// Start the lxc container with the appropriate settings for grabbing the
   196  	// console output and a log file.
   197  	consoleFile := filepath.Join(containerDirectory, "console.log")
   198  	lxcContainer.SetLogFile(filepath.Join(containerDirectory, "container.log"), golxc.LogDebug)
   199  	logger.Tracef("start the container")
   200  	// We explicitly don't pass through the config file to the container.Start
   201  	// method as we have passed it through at container creation time.  This
   202  	// is necessary to get the appropriate rootfs reference without explicitly
   203  	// setting it ourselves.
   204  	if err = lxcContainer.Start("", consoleFile); err != nil {
   205  		logger.Errorf("container failed to start: %v", err)
   206  		return nil, err
   207  	}
   208  	logger.Infof("template container started, now wait for it to stop")
   209  	// Perhaps we should wait for it to finish, and the question becomes "how
   210  	// long do we wait for it to complete?"
   211  
   212  	console, err := os.Open(consoleFile)
   213  	if err != nil {
   214  		// can't listen
   215  		return nil, err
   216  	}
   217  
   218  	tailWriter := &logTail{tick: time.Now()}
   219  	consoleTailer := tailer.NewTailer(console, tailWriter, nil)
   220  	defer consoleTailer.Stop()
   221  
   222  	// We should wait maybe 1 minute between output?
   223  	// if no output check to see if stopped
   224  	// If we have no output and still running, something has probably gone wrong
   225  	for lxcContainer.IsRunning() {
   226  		if tailWriter.lastTick().Before(time.Now().Add(-TemplateStopTimeout)) {
   227  			logger.Infof("not heard anything from the template log for five minutes")
   228  			return nil, fmt.Errorf("template container %q did not stop", name)
   229  		}
   230  		time.Sleep(time.Second)
   231  	}
   232  
   233  	return lxcContainer, nil
   234  }
   235  
   236  type logTail struct {
   237  	tick  time.Time
   238  	mutex sync.Mutex
   239  }
   240  
   241  var _ io.Writer = (*logTail)(nil)
   242  
   243  func (t *logTail) Write(data []byte) (int, error) {
   244  	logger.Tracef(string(data))
   245  	t.mutex.Lock()
   246  	defer t.mutex.Unlock()
   247  	t.tick = time.Now()
   248  	return len(data), nil
   249  }
   250  
   251  func (t *logTail) lastTick() time.Time {
   252  	t.mutex.Lock()
   253  	defer t.mutex.Unlock()
   254  	tick := t.tick
   255  	return tick
   256  }