launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/provider/manual/environ.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package manual 5 6 import ( 7 "bytes" 8 "errors" 9 "fmt" 10 "net" 11 "path" 12 "strings" 13 "sync" 14 15 "github.com/loggo/loggo" 16 17 "launchpad.net/juju-core/constraints" 18 "launchpad.net/juju-core/environs" 19 "launchpad.net/juju-core/environs/cloudinit" 20 "launchpad.net/juju-core/environs/config" 21 "launchpad.net/juju-core/environs/httpstorage" 22 "launchpad.net/juju-core/environs/manual" 23 "launchpad.net/juju-core/environs/simplestreams" 24 "launchpad.net/juju-core/environs/sshstorage" 25 "launchpad.net/juju-core/environs/storage" 26 envtools "launchpad.net/juju-core/environs/tools" 27 "launchpad.net/juju-core/instance" 28 "launchpad.net/juju-core/provider/common" 29 "launchpad.net/juju-core/state" 30 "launchpad.net/juju-core/state/api" 31 "launchpad.net/juju-core/tools" 32 "launchpad.net/juju-core/utils/ssh" 33 "launchpad.net/juju-core/worker/localstorage" 34 "launchpad.net/juju-core/worker/terminationworker" 35 ) 36 37 const ( 38 // TODO(axw) make this configurable? 39 dataDir = "/var/lib/juju" 40 41 // storageSubdir is the subdirectory of 42 // dataDir in which storage will be located. 43 storageSubdir = "storage" 44 45 // storageTmpSubdir is the subdirectory of 46 // dataDir in which temporary storage will 47 // be located. 48 storageTmpSubdir = "storage-tmp" 49 ) 50 51 var logger = loggo.GetLogger("juju.provider.manual") 52 53 type manualEnviron struct { 54 cfg *environConfig 55 cfgmutex sync.Mutex 56 storage storage.Storage 57 ubuntuUserInited bool 58 ubuntuUserInitMutex sync.Mutex 59 } 60 61 var _ environs.BootstrapStorager = (*manualEnviron)(nil) 62 var _ envtools.SupportsCustomSources = (*manualEnviron)(nil) 63 64 var errNoStartInstance = errors.New("manual provider cannot start instances") 65 var errNoStopInstance = errors.New("manual provider cannot stop instances") 66 67 func (*manualEnviron) StartInstance(constraints.Value, tools.List, *cloudinit.MachineConfig) (instance.Instance, *instance.HardwareCharacteristics, error) { 68 return nil, nil, errNoStartInstance 69 } 70 71 func (*manualEnviron) StopInstances([]instance.Instance) error { 72 return errNoStopInstance 73 } 74 75 func (e *manualEnviron) AllInstances() ([]instance.Instance, error) { 76 return e.Instances([]instance.Id{manual.BootstrapInstanceId}) 77 } 78 79 func (e *manualEnviron) envConfig() (cfg *environConfig) { 80 e.cfgmutex.Lock() 81 cfg = e.cfg 82 e.cfgmutex.Unlock() 83 return cfg 84 } 85 86 func (e *manualEnviron) Config() *config.Config { 87 return e.envConfig().Config 88 } 89 90 func (e *manualEnviron) Name() string { 91 return e.envConfig().Name() 92 } 93 94 var initUbuntuUser = manual.InitUbuntuUser 95 96 func (e *manualEnviron) ensureBootstrapUbuntuUser(ctx environs.BootstrapContext) error { 97 e.ubuntuUserInitMutex.Lock() 98 defer e.ubuntuUserInitMutex.Unlock() 99 if e.ubuntuUserInited { 100 return nil 101 } 102 cfg := e.envConfig() 103 err := initUbuntuUser(cfg.bootstrapHost(), cfg.bootstrapUser(), cfg.AuthorizedKeys(), ctx.Stdin(), ctx.Stdout()) 104 if err != nil { 105 logger.Errorf("initializing ubuntu user: %v", err) 106 return err 107 } 108 logger.Infof("initialized ubuntu user") 109 e.ubuntuUserInited = true 110 return nil 111 } 112 113 func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, cons constraints.Value) error { 114 if err := e.ensureBootstrapUbuntuUser(ctx); err != nil { 115 return err 116 } 117 // Set "use-sshstorage" to false, so agents know not to use sshstorage. 118 cfg, err := e.Config().Apply(map[string]interface{}{"use-sshstorage": false}) 119 if err != nil { 120 return err 121 } 122 if err := e.SetConfig(cfg); err != nil { 123 return err 124 } 125 envConfig := e.envConfig() 126 host := envConfig.bootstrapHost() 127 hc, series, err := manual.DetectSeriesAndHardwareCharacteristics(host) 128 if err != nil { 129 return err 130 } 131 selectedTools, err := common.EnsureBootstrapTools(e, series, hc.Arch) 132 if err != nil { 133 return err 134 } 135 return manual.Bootstrap(manual.BootstrapArgs{ 136 Context: ctx, 137 Host: host, 138 DataDir: dataDir, 139 Environ: e, 140 PossibleTools: selectedTools, 141 Series: series, 142 HardwareCharacteristics: &hc, 143 }) 144 } 145 146 func (e *manualEnviron) StateInfo() (*state.Info, *api.Info, error) { 147 return common.StateInfo(e) 148 } 149 150 func (e *manualEnviron) SetConfig(cfg *config.Config) error { 151 e.cfgmutex.Lock() 152 defer e.cfgmutex.Unlock() 153 envConfig, err := manualProvider{}.validate(cfg, e.cfg.Config) 154 if err != nil { 155 return err 156 } 157 // Set storage. If "use-sshstorage" is true then use the SSH storage. 158 // Otherwise, use HTTP storage. 159 // 160 // We don't change storage once it's been set. Storage parameters 161 // are fixed at bootstrap time, and it is not possible to change 162 // them. 163 if e.storage == nil { 164 var stor storage.Storage 165 if envConfig.useSSHStorage() { 166 storageDir := e.StorageDir() 167 storageTmpdir := path.Join(dataDir, storageTmpSubdir) 168 stor, err = newSSHStorage("ubuntu@"+e.cfg.bootstrapHost(), storageDir, storageTmpdir) 169 if err != nil { 170 return fmt.Errorf("initialising SSH storage failed: %v", err) 171 } 172 } else { 173 caCertPEM, ok := envConfig.CACert() 174 if !ok { 175 // should not be possible to validate base config 176 return fmt.Errorf("ca-cert not set") 177 } 178 authkey := envConfig.storageAuthKey() 179 stor, err = httpstorage.ClientTLS(envConfig.storageAddr(), caCertPEM, authkey) 180 if err != nil { 181 return fmt.Errorf("initialising HTTPS storage failed: %v", err) 182 } 183 } 184 e.storage = stor 185 } 186 e.cfg = envConfig 187 return nil 188 } 189 190 // Implements environs.Environ. 191 // 192 // This method will only ever return an Instance for the Id 193 // environ/manual.BootstrapInstanceId. If any others are 194 // specified, then ErrPartialInstances or ErrNoInstances 195 // will result. 196 func (e *manualEnviron) Instances(ids []instance.Id) (instances []instance.Instance, err error) { 197 instances = make([]instance.Instance, len(ids)) 198 var found bool 199 for i, id := range ids { 200 if id == manual.BootstrapInstanceId { 201 instances[i] = manualBootstrapInstance{e.envConfig().bootstrapHost()} 202 found = true 203 } else { 204 err = environs.ErrPartialInstances 205 } 206 } 207 if !found { 208 err = environs.ErrNoInstances 209 } 210 return instances, err 211 } 212 213 var newSSHStorage = func(sshHost, storageDir, storageTmpdir string) (storage.Storage, error) { 214 return sshstorage.NewSSHStorage(sshstorage.NewSSHStorageParams{ 215 Host: sshHost, 216 StorageDir: storageDir, 217 TmpDir: storageTmpdir, 218 }) 219 } 220 221 // Implements environs.BootstrapStorager. 222 func (e *manualEnviron) EnableBootstrapStorage(ctx environs.BootstrapContext) error { 223 return e.ensureBootstrapUbuntuUser(ctx) 224 } 225 226 // GetToolsSources returns a list of sources which are 227 // used to search for simplestreams tools metadata. 228 func (e *manualEnviron) GetToolsSources() ([]simplestreams.DataSource, error) { 229 // Add the simplestreams source off private storage. 230 return []simplestreams.DataSource{ 231 storage.NewStorageSimpleStreamsDataSource(e.Storage(), storage.BaseToolsPath), 232 }, nil 233 } 234 235 func (e *manualEnviron) Storage() storage.Storage { 236 e.cfgmutex.Lock() 237 defer e.cfgmutex.Unlock() 238 return e.storage 239 } 240 241 var runSSHCommand = func(host string, command []string) (stderr string, err error) { 242 cmd := ssh.Command(host, command, nil) 243 var stderrBuf bytes.Buffer 244 cmd.Stderr = &stderrBuf 245 err = cmd.Run() 246 return stderrBuf.String(), err 247 } 248 249 func (e *manualEnviron) Destroy() error { 250 stderr, err := runSSHCommand( 251 "ubuntu@"+e.envConfig().bootstrapHost(), 252 []string{"sudo", "pkill", fmt.Sprintf("-%d", terminationworker.TerminationSignal), "jujud"}, 253 ) 254 if err != nil { 255 if stderr := strings.TrimSpace(stderr); len(stderr) > 0 { 256 err = fmt.Errorf("%v (%v)", err, stderr) 257 } 258 } 259 return err 260 } 261 262 func (e *manualEnviron) OpenPorts(ports []instance.Port) error { 263 return nil 264 } 265 266 func (e *manualEnviron) ClosePorts(ports []instance.Port) error { 267 return nil 268 } 269 270 func (e *manualEnviron) Ports() ([]instance.Port, error) { 271 return []instance.Port{}, nil 272 } 273 274 func (*manualEnviron) Provider() environs.EnvironProvider { 275 return manualProvider{} 276 } 277 278 func (e *manualEnviron) StorageAddr() string { 279 return e.envConfig().storageListenAddr() 280 } 281 282 func (e *manualEnviron) StorageDir() string { 283 return path.Join(dataDir, storageSubdir) 284 } 285 286 func (e *manualEnviron) SharedStorageAddr() string { 287 return "" 288 } 289 290 func (e *manualEnviron) SharedStorageDir() string { 291 return "" 292 } 293 294 func (e *manualEnviron) StorageCACert() []byte { 295 if bytes, ok := e.envConfig().CACert(); ok { 296 return bytes 297 } 298 return nil 299 } 300 301 func (e *manualEnviron) StorageCAKey() []byte { 302 if bytes, ok := e.envConfig().CAPrivateKey(); ok { 303 return bytes 304 } 305 return nil 306 } 307 308 func (e *manualEnviron) StorageHostnames() []string { 309 cfg := e.envConfig() 310 hostnames := []string{cfg.bootstrapHost()} 311 if ip := net.ParseIP(cfg.storageListenIPAddress()); ip != nil { 312 if !ip.IsUnspecified() { 313 hostnames = append(hostnames, ip.String()) 314 } 315 } 316 return hostnames 317 } 318 319 func (e *manualEnviron) StorageAuthKey() string { 320 return e.envConfig().storageAuthKey() 321 } 322 323 var _ localstorage.LocalTLSStorageConfig = (*manualEnviron)(nil)