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