github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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 "fmt" 9 "net" 10 "path" 11 "strings" 12 "sync" 13 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 "github.com/juju/utils" 17 "github.com/juju/utils/arch" 18 19 "github.com/juju/juju/agent" 20 "github.com/juju/juju/cloudconfig/instancecfg" 21 "github.com/juju/juju/constraints" 22 "github.com/juju/juju/environs" 23 "github.com/juju/juju/environs/config" 24 "github.com/juju/juju/environs/httpstorage" 25 "github.com/juju/juju/environs/manual" 26 "github.com/juju/juju/environs/sshstorage" 27 "github.com/juju/juju/environs/storage" 28 "github.com/juju/juju/instance" 29 "github.com/juju/juju/mongo" 30 "github.com/juju/juju/network" 31 "github.com/juju/juju/provider/common" 32 "github.com/juju/juju/utils/ssh" 33 "github.com/juju/juju/worker/localstorage" 34 "github.com/juju/juju/worker/terminationworker" 35 ) 36 37 const ( 38 // BootstrapInstanceId is the instance ID used 39 // for the manual provider's bootstrap instance. 40 BootstrapInstanceId instance.Id = "manual:" 41 42 // storageSubdir is the subdirectory of 43 // dataDir in which storage will be located. 44 storageSubdir = "storage" 45 46 // storageTmpSubdir is the subdirectory of 47 // dataDir in which temporary storage will 48 // be located. 49 storageTmpSubdir = "storage-tmp" 50 ) 51 52 var ( 53 logger = loggo.GetLogger("juju.provider.manual") 54 manualCheckProvisioned = manual.CheckProvisioned 55 manualDetectSeriesAndHardwareCharacteristics = manual.DetectSeriesAndHardwareCharacteristics 56 ) 57 58 type manualEnviron struct { 59 common.SupportsUnitPlacementPolicy 60 61 cfg *environConfig 62 cfgmutex sync.Mutex 63 storage storage.Storage 64 ubuntuUserInited bool 65 ubuntuUserInitMutex sync.Mutex 66 } 67 68 var errNoStartInstance = errors.New("manual provider cannot start instances") 69 var errNoStopInstance = errors.New("manual provider cannot stop instances") 70 71 // MaintainInstance is specified in the InstanceBroker interface. 72 func (*manualEnviron) MaintainInstance(args environs.StartInstanceParams) error { 73 return nil 74 } 75 76 func (*manualEnviron) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 77 return nil, errNoStartInstance 78 } 79 80 func (*manualEnviron) StopInstances(...instance.Id) error { 81 return errNoStopInstance 82 } 83 84 func (e *manualEnviron) AllInstances() ([]instance.Instance, error) { 85 return e.Instances([]instance.Id{BootstrapInstanceId}) 86 } 87 88 func (e *manualEnviron) envConfig() (cfg *environConfig) { 89 e.cfgmutex.Lock() 90 cfg = e.cfg 91 e.cfgmutex.Unlock() 92 return cfg 93 } 94 95 func (e *manualEnviron) Config() *config.Config { 96 return e.envConfig().Config 97 } 98 99 // SupportedArchitectures is specified on the EnvironCapability interface. 100 func (e *manualEnviron) SupportedArchitectures() ([]string, error) { 101 return arch.AllSupportedArches, nil 102 } 103 104 func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (arch, series string, _ environs.BootstrapFinalizer, _ error) { 105 // Set "use-sshstorage" to false, so agents know not to use sshstorage. 106 cfg, err := e.Config().Apply(map[string]interface{}{"use-sshstorage": false}) 107 if err != nil { 108 return "", "", nil, err 109 } 110 if err := e.SetConfig(cfg); err != nil { 111 return "", "", nil, err 112 } 113 agentEnv, err := localstorage.StoreConfig(e) 114 if err != nil { 115 return "", "", nil, err 116 } 117 envConfig := e.envConfig() 118 // TODO(axw) consider how we can use placement to override bootstrap-host. 119 host := envConfig.bootstrapHost() 120 provisioned, err := manualCheckProvisioned(host) 121 if err != nil { 122 return "", "", nil, errors.Annotate(err, "failed to check provisioned status") 123 } 124 if provisioned { 125 return "", "", nil, manual.ErrProvisioned 126 } 127 hc, series, err := manualDetectSeriesAndHardwareCharacteristics(host) 128 if err != nil { 129 return "", "", nil, err 130 } 131 finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig) error { 132 icfg.InstanceId = BootstrapInstanceId 133 icfg.HardwareCharacteristics = &hc 134 if err := instancecfg.FinishInstanceConfig(icfg, e.Config()); err != nil { 135 return err 136 } 137 for k, v := range agentEnv { 138 icfg.AgentEnvironment[k] = v 139 } 140 return common.ConfigureMachine(ctx, ssh.DefaultClient, host, icfg) 141 } 142 return *hc.Arch, series, finalize, nil 143 } 144 145 // StateServerInstances is specified in the Environ interface. 146 func (e *manualEnviron) StateServerInstances() ([]instance.Id, error) { 147 // If we're running from the bootstrap host, then 148 // useSSHStorage will be false; in that case, we 149 // do not need or want to verify the bootstrap host. 150 if e.envConfig().useSSHStorage() { 151 if err := e.verifyBootstrapHost(); err != nil { 152 return nil, err 153 } 154 } 155 return []instance.Id{BootstrapInstanceId}, nil 156 } 157 158 func (e *manualEnviron) verifyBootstrapHost() error { 159 // First verify that the environment is bootstrapped by checking 160 // if the agents directory exists. Note that we cannot test the 161 // root data directory, as that is created in the process of 162 // initialising sshstorage. 163 agentsDir := path.Join(agent.DefaultPaths.DataDir, "agents") 164 const noAgentDir = "no-agent-dir" 165 stdin := fmt.Sprintf( 166 "test -d %s || echo %s", 167 utils.ShQuote(agentsDir), 168 noAgentDir, 169 ) 170 out, err := runSSHCommand( 171 "ubuntu@"+e.cfg.bootstrapHost(), 172 []string{"/bin/bash"}, 173 stdin, 174 ) 175 if err != nil { 176 return err 177 } 178 if out = strings.TrimSpace(out); len(out) > 0 { 179 if out == noAgentDir { 180 return environs.ErrNotBootstrapped 181 } 182 err := errors.Errorf("unexpected output: %q", out) 183 logger.Infof(err.Error()) 184 return err 185 } 186 return nil 187 } 188 189 func (e *manualEnviron) SetConfig(cfg *config.Config) error { 190 e.cfgmutex.Lock() 191 defer e.cfgmutex.Unlock() 192 _, err := manualProvider{}.validate(cfg, e.cfg.Config) 193 if err != nil { 194 return err 195 } 196 envConfig := newEnvironConfig(cfg, cfg.UnknownAttrs()) 197 // Set storage. If "use-sshstorage" is true then use the SSH storage. 198 // Otherwise, use HTTP storage. 199 // 200 // We don't change storage once it's been set. Storage parameters 201 // are fixed at bootstrap time, and it is not possible to change 202 // them. 203 if e.storage == nil { 204 var stor storage.Storage 205 if envConfig.useSSHStorage() { 206 storageDir := e.StorageDir() 207 storageTmpdir := path.Join(agent.DefaultPaths.DataDir, storageTmpSubdir) 208 stor, err = newSSHStorage("ubuntu@"+e.cfg.bootstrapHost(), storageDir, storageTmpdir) 209 if err != nil { 210 return fmt.Errorf("initialising SSH storage failed: %v", err) 211 } 212 } else { 213 caCertPEM, ok := envConfig.CACert() 214 if !ok { 215 // should not be possible to validate base config 216 return fmt.Errorf("ca-cert not set") 217 } 218 authkey := envConfig.storageAuthKey() 219 stor, err = httpstorage.ClientTLS(envConfig.storageAddr(), caCertPEM, authkey) 220 if err != nil { 221 return fmt.Errorf("initialising HTTPS storage failed: %v", err) 222 } 223 } 224 e.storage = stor 225 } 226 e.cfg = envConfig 227 return nil 228 } 229 230 // Implements environs.Environ. 231 // 232 // This method will only ever return an Instance for the Id 233 // BootstrapInstanceId. If any others are specified, then 234 // ErrPartialInstances or ErrNoInstances will result. 235 func (e *manualEnviron) Instances(ids []instance.Id) (instances []instance.Instance, err error) { 236 instances = make([]instance.Instance, len(ids)) 237 var found bool 238 for i, id := range ids { 239 if id == BootstrapInstanceId { 240 instances[i] = manualBootstrapInstance{e.envConfig().bootstrapHost()} 241 found = true 242 } else { 243 err = environs.ErrPartialInstances 244 } 245 } 246 if !found { 247 err = environs.ErrNoInstances 248 } 249 return instances, err 250 } 251 252 var newSSHStorage = func(sshHost, storageDir, storageTmpdir string) (storage.Storage, error) { 253 logger.Debugf("using ssh storage at host %q dir %q", sshHost, storageDir) 254 return sshstorage.NewSSHStorage(sshstorage.NewSSHStorageParams{ 255 Host: sshHost, 256 StorageDir: storageDir, 257 TmpDir: storageTmpdir, 258 }) 259 } 260 261 func (e *manualEnviron) Storage() storage.Storage { 262 e.cfgmutex.Lock() 263 defer e.cfgmutex.Unlock() 264 return e.storage 265 } 266 267 var runSSHCommand = func(host string, command []string, stdin string) (stdout string, err error) { 268 cmd := ssh.Command(host, command, nil) 269 cmd.Stdin = strings.NewReader(stdin) 270 var stdoutBuf, stderrBuf bytes.Buffer 271 cmd.Stdout = &stdoutBuf 272 cmd.Stderr = &stderrBuf 273 if err := cmd.Run(); err != nil { 274 if stderr := strings.TrimSpace(stderrBuf.String()); len(stderr) > 0 { 275 err = errors.Annotate(err, stderr) 276 } 277 return "", err 278 } 279 return stdoutBuf.String(), nil 280 } 281 282 func (e *manualEnviron) Destroy() error { 283 script := ` 284 set -x 285 pkill -%d jujud && exit 286 stop %s 287 rm -f /etc/init/juju* 288 rm -f /etc/rsyslog.d/*juju* 289 rm -fr %s %s 290 exit 0 291 ` 292 script = fmt.Sprintf( 293 script, 294 terminationworker.TerminationSignal, 295 mongo.ServiceName(""), 296 utils.ShQuote(agent.DefaultPaths.DataDir), 297 utils.ShQuote(agent.DefaultPaths.LogDir), 298 ) 299 _, err := runSSHCommand( 300 "ubuntu@"+e.envConfig().bootstrapHost(), 301 []string{"sudo", "/bin/bash"}, script, 302 ) 303 return err 304 } 305 306 func (*manualEnviron) PrecheckInstance(series string, _ constraints.Value, placement string) error { 307 return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`) 308 } 309 310 var unsupportedConstraints = []string{ 311 constraints.CpuPower, 312 constraints.InstanceType, 313 constraints.Tags, 314 } 315 316 // ConstraintsValidator is defined on the Environs interface. 317 func (e *manualEnviron) ConstraintsValidator() (constraints.Validator, error) { 318 validator := constraints.NewValidator() 319 validator.RegisterUnsupported(unsupportedConstraints) 320 return validator, nil 321 } 322 323 func (e *manualEnviron) OpenPorts(ports []network.PortRange) error { 324 return nil 325 } 326 327 func (e *manualEnviron) ClosePorts(ports []network.PortRange) error { 328 return nil 329 } 330 331 func (e *manualEnviron) Ports() ([]network.PortRange, error) { 332 return nil, nil 333 } 334 335 func (*manualEnviron) Provider() environs.EnvironProvider { 336 return manualProvider{} 337 } 338 339 func (e *manualEnviron) StorageAddr() string { 340 return e.envConfig().storageListenAddr() 341 } 342 343 func (e *manualEnviron) StorageDir() string { 344 return path.Join(agent.DefaultPaths.DataDir, storageSubdir) 345 } 346 347 func (e *manualEnviron) SharedStorageAddr() string { 348 return "" 349 } 350 351 func (e *manualEnviron) SharedStorageDir() string { 352 return "" 353 } 354 355 func (e *manualEnviron) StorageCACert() string { 356 if cert, ok := e.envConfig().CACert(); ok { 357 return cert 358 } 359 return "" 360 } 361 362 func (e *manualEnviron) StorageCAKey() string { 363 if key, ok := e.envConfig().CAPrivateKey(); ok { 364 return key 365 } 366 return "" 367 } 368 369 func (e *manualEnviron) StorageHostnames() []string { 370 cfg := e.envConfig() 371 hostnames := []string{cfg.bootstrapHost()} 372 if ip := net.ParseIP(cfg.storageListenIPAddress()); ip != nil { 373 if !ip.IsUnspecified() { 374 hostnames = append(hostnames, ip.String()) 375 } 376 } 377 return hostnames 378 } 379 380 func (e *manualEnviron) StorageAuthKey() string { 381 return e.envConfig().storageAuthKey() 382 } 383 384 var _ localstorage.LocalTLSStorageConfig = (*manualEnviron)(nil)