github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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/agent/mongo" 20 "github.com/juju/juju/constraints" 21 "github.com/juju/juju/environs" 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/network" 26 "github.com/juju/juju/environs/simplestreams" 27 "github.com/juju/juju/environs/sshstorage" 28 "github.com/juju/juju/environs/storage" 29 envtools "github.com/juju/juju/environs/tools" 30 "github.com/juju/juju/instance" 31 "github.com/juju/juju/juju/arch" 32 "github.com/juju/juju/provider/common" 33 "github.com/juju/juju/state" 34 "github.com/juju/juju/state/api" 35 "github.com/juju/juju/utils/ssh" 36 "github.com/juju/juju/worker/localstorage" 37 "github.com/juju/juju/worker/terminationworker" 38 ) 39 40 const ( 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 common.SupportsUnitPlacementPolicy 55 56 cfg *environConfig 57 cfgmutex sync.Mutex 58 storage storage.Storage 59 ubuntuUserInited bool 60 ubuntuUserInitMutex sync.Mutex 61 } 62 63 var _ envtools.SupportsCustomSources = (*manualEnviron)(nil) 64 65 var errNoStartInstance = errors.New("manual provider cannot start instances") 66 var errNoStopInstance = errors.New("manual provider cannot stop instances") 67 68 func (*manualEnviron) StartInstance(args environs.StartInstanceParams) (instance.Instance, *instance.HardwareCharacteristics, []network.Info, error) { 69 return nil, nil, nil, errNoStartInstance 70 } 71 72 func (*manualEnviron) StopInstances(...instance.Id) error { 73 return errNoStopInstance 74 } 75 76 func (e *manualEnviron) AllInstances() ([]instance.Instance, error) { 77 return e.Instances([]instance.Id{manual.BootstrapInstanceId}) 78 } 79 80 func (e *manualEnviron) envConfig() (cfg *environConfig) { 81 e.cfgmutex.Lock() 82 cfg = e.cfg 83 e.cfgmutex.Unlock() 84 return cfg 85 } 86 87 func (e *manualEnviron) Config() *config.Config { 88 return e.envConfig().Config 89 } 90 91 func (e *manualEnviron) Name() string { 92 return e.envConfig().Name() 93 } 94 95 // SupportedArchitectures is specified on the EnvironCapability interface. 96 func (e *manualEnviron) SupportedArchitectures() ([]string, error) { 97 return arch.AllSupportedArches, nil 98 } 99 100 // SupportNetworks is specified on the EnvironCapability interface. 101 func (e *manualEnviron) SupportNetworks() bool { 102 return false 103 } 104 105 func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) error { 106 // Set "use-sshstorage" to false, so agents know not to use sshstorage. 107 cfg, err := e.Config().Apply(map[string]interface{}{"use-sshstorage": false}) 108 if err != nil { 109 return err 110 } 111 if err := e.SetConfig(cfg); err != nil { 112 return err 113 } 114 envConfig := e.envConfig() 115 // TODO(axw) consider how we can use placement to override bootstrap-host. 116 host := envConfig.bootstrapHost() 117 hc, series, err := manual.DetectSeriesAndHardwareCharacteristics(host) 118 if err != nil { 119 return err 120 } 121 selectedTools, err := common.EnsureBootstrapTools(ctx, e, series, hc.Arch) 122 if err != nil { 123 return err 124 } 125 return manual.Bootstrap(manual.BootstrapArgs{ 126 Context: ctx, 127 Host: host, 128 DataDir: agent.DefaultDataDir, 129 Environ: e, 130 PossibleTools: selectedTools, 131 Series: series, 132 HardwareCharacteristics: &hc, 133 }) 134 } 135 136 func (e *manualEnviron) StateInfo() (*state.Info, *api.Info, error) { 137 return common.StateInfo(e) 138 } 139 140 func (e *manualEnviron) SetConfig(cfg *config.Config) error { 141 e.cfgmutex.Lock() 142 defer e.cfgmutex.Unlock() 143 envConfig, err := manualProvider{}.validate(cfg, e.cfg.Config) 144 if err != nil { 145 return err 146 } 147 // Set storage. If "use-sshstorage" is true then use the SSH storage. 148 // Otherwise, use HTTP storage. 149 // 150 // We don't change storage once it's been set. Storage parameters 151 // are fixed at bootstrap time, and it is not possible to change 152 // them. 153 if e.storage == nil { 154 var stor storage.Storage 155 if envConfig.useSSHStorage() { 156 storageDir := e.StorageDir() 157 storageTmpdir := path.Join(agent.DefaultDataDir, storageTmpSubdir) 158 stor, err = newSSHStorage("ubuntu@"+e.cfg.bootstrapHost(), storageDir, storageTmpdir) 159 if err != nil { 160 return fmt.Errorf("initialising SSH storage failed: %v", err) 161 } 162 } else { 163 caCertPEM, ok := envConfig.CACert() 164 if !ok { 165 // should not be possible to validate base config 166 return fmt.Errorf("ca-cert not set") 167 } 168 authkey := envConfig.storageAuthKey() 169 stor, err = httpstorage.ClientTLS(envConfig.storageAddr(), caCertPEM, authkey) 170 if err != nil { 171 return fmt.Errorf("initialising HTTPS storage failed: %v", err) 172 } 173 } 174 e.storage = stor 175 } 176 e.cfg = envConfig 177 return nil 178 } 179 180 // Implements environs.Environ. 181 // 182 // This method will only ever return an Instance for the Id 183 // environ/manual.BootstrapInstanceId. If any others are 184 // specified, then ErrPartialInstances or ErrNoInstances 185 // will result. 186 func (e *manualEnviron) Instances(ids []instance.Id) (instances []instance.Instance, err error) { 187 instances = make([]instance.Instance, len(ids)) 188 var found bool 189 for i, id := range ids { 190 if id == manual.BootstrapInstanceId { 191 instances[i] = manualBootstrapInstance{e.envConfig().bootstrapHost()} 192 found = true 193 } else { 194 err = environs.ErrPartialInstances 195 } 196 } 197 if !found { 198 err = environs.ErrNoInstances 199 } 200 return instances, err 201 } 202 203 // AllocateAddress requests a new address to be allocated for the 204 // given instance on the given network. This is not supported on the 205 // manual provider. 206 func (*manualEnviron) AllocateAddress(_ instance.Id, _ network.Id) (instance.Address, error) { 207 return instance.Address{}, errors.NotSupportedf("AllocateAddress") 208 } 209 210 var newSSHStorage = func(sshHost, storageDir, storageTmpdir string) (storage.Storage, error) { 211 logger.Debugf("using ssh storage at host %q dir %q", sshHost, storageDir) 212 return sshstorage.NewSSHStorage(sshstorage.NewSSHStorageParams{ 213 Host: sshHost, 214 StorageDir: storageDir, 215 TmpDir: storageTmpdir, 216 }) 217 } 218 219 // GetToolsSources returns a list of sources which are 220 // used to search for simplestreams tools metadata. 221 func (e *manualEnviron) GetToolsSources() ([]simplestreams.DataSource, error) { 222 // Add the simplestreams source off private storage. 223 return []simplestreams.DataSource{ 224 storage.NewStorageSimpleStreamsDataSource("cloud storage", e.Storage(), storage.BaseToolsPath), 225 }, nil 226 } 227 228 func (e *manualEnviron) Storage() storage.Storage { 229 e.cfgmutex.Lock() 230 defer e.cfgmutex.Unlock() 231 return e.storage 232 } 233 234 var runSSHCommand = func(host string, command []string, stdin string) (stderr string, err error) { 235 cmd := ssh.Command(host, command, nil) 236 var stderrBuf bytes.Buffer 237 cmd.Stdin = strings.NewReader(stdin) 238 cmd.Stderr = &stderrBuf 239 err = cmd.Run() 240 return stderrBuf.String(), err 241 } 242 243 func (e *manualEnviron) Destroy() error { 244 script := ` 245 set -x 246 pkill -%d jujud && exit 247 stop %s 248 rm -f /etc/init/juju* 249 rm -f /etc/rsyslog.d/*juju* 250 rm -fr %s %s 251 exit 0 252 ` 253 script = fmt.Sprintf( 254 script, 255 terminationworker.TerminationSignal, 256 mongo.ServiceName(""), 257 utils.ShQuote(agent.DefaultDataDir), 258 utils.ShQuote(agent.DefaultLogDir), 259 ) 260 stderr, err := runSSHCommand( 261 "ubuntu@"+e.envConfig().bootstrapHost(), 262 []string{"sudo", "/bin/bash"}, script, 263 ) 264 if err != nil { 265 if stderr := strings.TrimSpace(stderr); len(stderr) > 0 { 266 err = fmt.Errorf("%v (%v)", err, stderr) 267 } 268 } 269 return err 270 } 271 272 func (*manualEnviron) PrecheckInstance(series string, _ constraints.Value, placement string) error { 273 return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`) 274 } 275 276 var unsupportedConstraints = []string{ 277 constraints.CpuPower, 278 constraints.InstanceType, 279 constraints.Tags, 280 } 281 282 // ConstraintsValidator is defined on the Environs interface. 283 func (e *manualEnviron) ConstraintsValidator() (constraints.Validator, error) { 284 validator := constraints.NewValidator() 285 validator.RegisterUnsupported(unsupportedConstraints) 286 return validator, nil 287 } 288 289 func (e *manualEnviron) OpenPorts(ports []instance.Port) error { 290 return nil 291 } 292 293 func (e *manualEnviron) ClosePorts(ports []instance.Port) error { 294 return nil 295 } 296 297 func (e *manualEnviron) Ports() ([]instance.Port, error) { 298 return []instance.Port{}, nil 299 } 300 301 func (*manualEnviron) Provider() environs.EnvironProvider { 302 return manualProvider{} 303 } 304 305 func (e *manualEnviron) StorageAddr() string { 306 return e.envConfig().storageListenAddr() 307 } 308 309 func (e *manualEnviron) StorageDir() string { 310 return path.Join(agent.DefaultDataDir, storageSubdir) 311 } 312 313 func (e *manualEnviron) SharedStorageAddr() string { 314 return "" 315 } 316 317 func (e *manualEnviron) SharedStorageDir() string { 318 return "" 319 } 320 321 func (e *manualEnviron) StorageCACert() string { 322 if cert, ok := e.envConfig().CACert(); ok { 323 return cert 324 } 325 return "" 326 } 327 328 func (e *manualEnviron) StorageCAKey() string { 329 if key, ok := e.envConfig().CAPrivateKey(); ok { 330 return key 331 } 332 return "" 333 } 334 335 func (e *manualEnviron) StorageHostnames() []string { 336 cfg := e.envConfig() 337 hostnames := []string{cfg.bootstrapHost()} 338 if ip := net.ParseIP(cfg.storageListenIPAddress()); ip != nil { 339 if !ip.IsUnspecified() { 340 hostnames = append(hostnames, ip.String()) 341 } 342 } 343 return hostnames 344 } 345 346 func (e *manualEnviron) StorageAuthKey() string { 347 return e.envConfig().storageAuthKey() 348 } 349 350 var _ localstorage.LocalTLSStorageConfig = (*manualEnviron)(nil)