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 }