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