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 }