github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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/proxy" 16 "github.com/juju/utils/tailer" 17 "launchpad.net/golxc" 18 19 "github.com/juju/juju/cloudconfig/containerinit" 20 "github.com/juju/juju/container" 21 "github.com/juju/juju/instance" 22 "github.com/juju/juju/juju/arch" 23 ) 24 25 var ( 26 TemplateLockDir = "/var/lib/juju/locks" 27 28 TemplateStopTimeout = 5 * time.Minute 29 ) 30 31 func AcquireTemplateLock(name, message string) (*container.Lock, error) { 32 logger.Infof("wait for flock on %v", name) 33 lock, err := container.NewLock(TemplateLockDir, name) 34 if err != nil { 35 logger.Tracef("failed to create flock for template: %v", err) 36 return nil, err 37 } 38 err = lock.Lock(message) 39 if err != nil { 40 logger.Tracef("failed to acquire lock for template: %v", err) 41 return nil, err 42 } 43 return lock, nil 44 } 45 46 // Make sure a template exists that we can clone from. 47 func EnsureCloneTemplate( 48 backingFilesystem string, 49 series string, 50 networkConfig *container.NetworkConfig, 51 authorizedKeys string, 52 aptProxy proxy.Settings, 53 aptMirror string, 54 enablePackageUpdates bool, 55 enableOSUpgrades bool, 56 imageURLGetter container.ImageURLGetter, 57 useAUFS bool, 58 ) (golxc.Container, error) { 59 name := fmt.Sprintf("juju-%s-lxc-template", series) 60 containerDirectory, err := container.NewDirectory(name) 61 if err != nil { 62 return nil, err 63 } 64 65 lock, err := AcquireTemplateLock(name, "ensure clone exists") 66 if err != nil { 67 return nil, err 68 } 69 defer lock.Unlock() 70 71 lxcContainer := LxcObjectFactory.New(name) 72 // Early exit if the container has been constructed before. 73 if lxcContainer.IsConstructed() { 74 logger.Infof("template exists, continuing") 75 return lxcContainer, nil 76 } 77 logger.Infof("template does not exist, creating") 78 79 userData, err := containerinit.TemplateUserData( 80 series, 81 authorizedKeys, 82 aptProxy, 83 aptMirror, 84 enablePackageUpdates, 85 enableOSUpgrades, 86 networkConfig, 87 ) 88 if err != nil { 89 logger.Tracef("failed to create template user data for template: %v", err) 90 return nil, err 91 } 92 userDataFilename, err := containerinit.WriteCloudInitFile(containerDirectory, userData) 93 if err != nil { 94 return nil, err 95 } 96 97 templateParams := []string{ 98 "--debug", // Debug errors in the cloud image 99 "--userdata", userDataFilename, // Our groovey cloud-init 100 "--hostid", name, // Use the container name as the hostid 101 "-r", series, 102 } 103 var caCert []byte 104 if imageURLGetter != nil { 105 arch := arch.HostArch() 106 imageURL, err := imageURLGetter.ImageURL(instance.LXC, series, arch) 107 if err != nil { 108 return nil, errors.Annotatef(err, "cannot determine cached image URL") 109 } 110 templateParams = append(templateParams, "-T", imageURL) 111 caCert = imageURLGetter.CACert() 112 } 113 var extraCreateArgs []string 114 if backingFilesystem == Btrfs { 115 extraCreateArgs = append(extraCreateArgs, "-B", Btrfs) 116 } 117 118 // Create the container. 119 logger.Tracef("create the template container") 120 err = createContainer( 121 lxcContainer, 122 containerDirectory, 123 networkConfig, 124 extraCreateArgs, 125 templateParams, 126 caCert, 127 ) 128 if err != nil { 129 logger.Errorf("lxc template container creation failed: %v", err) 130 return nil, err 131 } 132 // Make sure that the mount dir has been created. 133 logger.Tracef("make the mount dir for the shared logs") 134 if err := os.MkdirAll(internalLogDir(name), 0755); err != nil { 135 logger.Tracef("failed to create internal /var/log/juju mount dir: %v", err) 136 return nil, err 137 } 138 139 // Start the lxc container with the appropriate settings for grabbing the 140 // console output and a log file. 141 consoleFile := filepath.Join(containerDirectory, "console.log") 142 lxcContainer.SetLogFile(filepath.Join(containerDirectory, "container.log"), golxc.LogDebug) 143 logger.Tracef("start the container") 144 // We explicitly don't pass through the config file to the container.Start 145 // method as we have passed it through at container creation time. This 146 // is necessary to get the appropriate rootfs reference without explicitly 147 // setting it ourselves. 148 if err = lxcContainer.Start("", consoleFile); err != nil { 149 logger.Errorf("container failed to start: %v", err) 150 return nil, err 151 } 152 logger.Infof("template container started, now wait for it to stop") 153 // Perhaps we should wait for it to finish, and the question becomes "how 154 // long do we wait for it to complete?" 155 156 console, err := os.Open(consoleFile) 157 if err != nil { 158 // can't listen 159 return nil, err 160 } 161 162 tailWriter := &logTail{tick: time.Now()} 163 consoleTailer := tailer.NewTailer(console, tailWriter, nil) 164 defer consoleTailer.Stop() 165 166 // We should wait maybe 1 minute between output? 167 // if no output check to see if stopped 168 // If we have no output and still running, something has probably gone wrong 169 for lxcContainer.IsRunning() { 170 if tailWriter.lastTick().Before(time.Now().Add(-TemplateStopTimeout)) { 171 logger.Infof("not heard anything from the template log for five minutes") 172 return nil, fmt.Errorf("template container %q did not stop", name) 173 } 174 time.Sleep(time.Second) 175 } 176 177 return lxcContainer, nil 178 } 179 180 type logTail struct { 181 tick time.Time 182 mutex sync.Mutex 183 } 184 185 var _ io.Writer = (*logTail)(nil) 186 187 func (t *logTail) Write(data []byte) (int, error) { 188 logger.Tracef(string(data)) 189 t.mutex.Lock() 190 defer t.mutex.Unlock() 191 t.tick = time.Now() 192 return len(data), nil 193 } 194 195 func (t *logTail) lastTick() time.Time { 196 t.mutex.Lock() 197 defer t.mutex.Unlock() 198 tick := t.tick 199 return tick 200 }