Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Copyright 2014 Canonical Ltd. | |
| // Licensed under the AGPLv3, see LICENCE file for details. | |
| package lxc | |
| import ( | |
| "fmt" | |
| "io" | |
| "os" | |
| "path/filepath" | |
| "sync" | |
| "time" | |
| "github.com/juju/utils" | |
| "github.com/juju/utils/proxy" | |
| "github.com/juju/utils/tailer" | |
| "launchpad.net/golxc" | |
| coreCloudinit "github.com/juju/juju/cloudinit" | |
| "github.com/juju/juju/container" | |
| "github.com/juju/juju/environs/cloudinit" | |
| ) | |
| const ( | |
| templateShutdownUpstartFilename = "/etc/init/juju-template-restart.conf" | |
| templateShutdownUpstartScript = ` | |
| description "Juju lxc template shutdown job" | |
| author "Juju Team <juju@lists.ubuntu.com>" | |
| start on stopped cloud-final | |
| script | |
| shutdown -h now | |
| end script | |
| post-stop script | |
| rm ` + templateShutdownUpstartFilename + ` | |
| end script | |
| ` | |
| ) | |
| var ( | |
| TemplateLockDir = "/var/lib/juju/locks" | |
| TemplateStopTimeout = 5 * time.Minute | |
| ) | |
| // templateUserData returns a minimal user data necessary for the template. | |
| // This should have the authorized keys, base packages, the cloud archive if | |
| // necessary, initial apt proxy config, and it should do the apt-get | |
| // update/upgrade initially. | |
| func templateUserData( | |
| series string, | |
| authorizedKeys string, | |
| aptProxy proxy.Settings, | |
| ) ([]byte, error) { | |
| config := coreCloudinit.New() | |
| config.AddScripts( | |
| "set -xe", // ensure we run all the scripts or abort. | |
| ) | |
| config.AddSSHAuthorizedKeys(authorizedKeys) | |
| cloudinit.MaybeAddCloudArchiveCloudTools(config, series) | |
| cloudinit.AddAptCommands(aptProxy, config) | |
| config.AddScripts( | |
| fmt.Sprintf( | |
| "printf '%%s\n' %s > %s", | |
| utils.ShQuote(templateShutdownUpstartScript), | |
| templateShutdownUpstartFilename, | |
| )) | |
| data, err := config.Render() | |
| if err != nil { | |
| return nil, err | |
| } | |
| return data, nil | |
| } | |
| func AcquireTemplateLock(name, message string) (*container.Lock, error) { | |
| logger.Infof("wait for flock on %v", name) | |
| lock, err := container.NewLock(TemplateLockDir, name) | |
| if err != nil { | |
| logger.Tracef("failed to create flock for template: %v", err) | |
| return nil, err | |
| } | |
| err = lock.Lock(message) | |
| if err != nil { | |
| logger.Tracef("failed to acquire lock for template: %v", err) | |
| return nil, err | |
| } | |
| return lock, nil | |
| } | |
| // Make sure a template exists that we can clone from. | |
| func EnsureCloneTemplate( | |
| backingFilesystem string, | |
| series string, | |
| network *container.NetworkConfig, | |
| authorizedKeys string, | |
| aptProxy proxy.Settings, | |
| ) (golxc.Container, error) { | |
| name := fmt.Sprintf("juju-%s-lxc-template", series) | |
| containerDirectory, err := container.NewDirectory(name) | |
| if err != nil { | |
| return nil, err | |
| } | |
| lock, err := AcquireTemplateLock(name, "ensure clone exists") | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer lock.Unlock() | |
| lxcContainer := LxcObjectFactory.New(name) | |
| // Early exit if the container has been constructed before. | |
| if lxcContainer.IsConstructed() { | |
| logger.Infof("template exists, continuing") | |
| return lxcContainer, nil | |
| } | |
| logger.Infof("template does not exist, creating") | |
| userData, err := templateUserData(series, authorizedKeys, aptProxy) | |
| if err != nil { | |
| logger.Tracef("failed to create template user data for template: %v", err) | |
| return nil, err | |
| } | |
| userDataFilename, err := container.WriteCloudInitFile(containerDirectory, userData) | |
| if err != nil { | |
| return nil, err | |
| } | |
| configFile, err := writeLxcConfig(network, containerDirectory) | |
| if err != nil { | |
| logger.Errorf("failed to write config file: %v", err) | |
| return nil, err | |
| } | |
| templateParams := []string{ | |
| "--debug", // Debug errors in the cloud image | |
| "--userdata", userDataFilename, // Our groovey cloud-init | |
| "--hostid", name, // Use the container name as the hostid | |
| "-r", series, | |
| } | |
| var extraCreateArgs []string | |
| if backingFilesystem == Btrfs { | |
| extraCreateArgs = append(extraCreateArgs, "-B", Btrfs) | |
| } | |
| // Create the container. | |
| logger.Tracef("create the container") | |
| if err := lxcContainer.Create(configFile, defaultTemplate, extraCreateArgs, templateParams); err != nil { | |
| logger.Errorf("lxc container creation failed: %v", err) | |
| return nil, err | |
| } | |
| // Make sure that the mount dir has been created. | |
| logger.Tracef("make the mount dir for the shared logs") | |
| if err := os.MkdirAll(internalLogDir(name), 0755); err != nil { | |
| logger.Tracef("failed to create internal /var/log/juju mount dir: %v", err) | |
| return nil, err | |
| } | |
| // Start the lxc container with the appropriate settings for grabbing the | |
| // console output and a log file. | |
| consoleFile := filepath.Join(containerDirectory, "console.log") | |
| lxcContainer.SetLogFile(filepath.Join(containerDirectory, "container.log"), golxc.LogDebug) | |
| logger.Tracef("start the container") | |
| // We explicitly don't pass through the config file to the container.Start | |
| // method as we have passed it through at container creation time. This | |
| // is necessary to get the appropriate rootfs reference without explicitly | |
| // setting it ourselves. | |
| if err = lxcContainer.Start("", consoleFile); err != nil { | |
| logger.Errorf("container failed to start: %v", err) | |
| return nil, err | |
| } | |
| logger.Infof("template container started, now wait for it to stop") | |
| // Perhaps we should wait for it to finish, and the question becomes "how | |
| // long do we wait for it to complete?" | |
| console, err := os.Open(consoleFile) | |
| if err != nil { | |
| // can't listen | |
| return nil, err | |
| } | |
| tailWriter := &logTail{tick: time.Now()} | |
| consoleTailer := tailer.NewTailer(console, tailWriter, nil) | |
| defer consoleTailer.Stop() | |
| // We should wait maybe 1 minute between output? | |
| // if no output check to see if stopped | |
| // If we have no output and still running, something has probably gone wrong | |
| for lxcContainer.IsRunning() { | |
| if tailWriter.lastTick().Before(time.Now().Add(-TemplateStopTimeout)) { | |
| logger.Infof("not heard anything from the template log for five minutes") | |
| return nil, fmt.Errorf("template container %q did not stop", name) | |
| } | |
| time.Sleep(time.Second) | |
| } | |
| return lxcContainer, nil | |
| } | |
| type logTail struct { | |
| tick time.Time | |
| mutex sync.Mutex | |
| } | |
| var _ io.Writer = (*logTail)(nil) | |
| func (t *logTail) Write(data []byte) (int, error) { | |
| logger.Tracef(string(data)) | |
| t.mutex.Lock() | |
| defer t.mutex.Unlock() | |
| t.tick = time.Now() | |
| return len(data), nil | |
| } | |
| func (t *logTail) lastTick() time.Time { | |
| t.mutex.Lock() | |
| defer t.mutex.Unlock() | |
| tick := t.tick | |
| return tick | |
| } |