github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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/juju/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 _ envtools.SupportsCustomSources = (*manualEnviron)(nil) 62 63 var errNoStartInstance = errors.New("manual provider cannot start instances") 64 var errNoStopInstance = errors.New("manual provider cannot stop instances") 65 66 func (*manualEnviron) StartInstance(constraints.Value, tools.List, *cloudinit.MachineConfig) (instance.Instance, *instance.HardwareCharacteristics, error) { 67 return nil, nil, errNoStartInstance 68 } 69 70 func (*manualEnviron) StopInstances([]instance.Instance) error { 71 return errNoStopInstance 72 } 73 74 func (e *manualEnviron) AllInstances() ([]instance.Instance, error) { 75 return e.Instances([]instance.Id{manual.BootstrapInstanceId}) 76 } 77 78 func (e *manualEnviron) envConfig() (cfg *environConfig) { 79 e.cfgmutex.Lock() 80 cfg = e.cfg 81 e.cfgmutex.Unlock() 82 return cfg 83 } 84 85 func (e *manualEnviron) Config() *config.Config { 86 return e.envConfig().Config 87 } 88 89 func (e *manualEnviron) Name() string { 90 return e.envConfig().Name() 91 } 92 93 func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, cons constraints.Value) error { 94 // Set "use-sshstorage" to false, so agents know not to use sshstorage. 95 cfg, err := e.Config().Apply(map[string]interface{}{"use-sshstorage": false}) 96 if err != nil { 97 return err 98 } 99 if err := e.SetConfig(cfg); err != nil { 100 return err 101 } 102 envConfig := e.envConfig() 103 host := envConfig.bootstrapHost() 104 hc, series, err := manual.DetectSeriesAndHardwareCharacteristics(host) 105 if err != nil { 106 return err 107 } 108 selectedTools, err := common.EnsureBootstrapTools(e, series, hc.Arch) 109 if err != nil { 110 return err 111 } 112 return manual.Bootstrap(manual.BootstrapArgs{ 113 Context: ctx, 114 Host: host, 115 DataDir: dataDir, 116 Environ: e, 117 PossibleTools: selectedTools, 118 Series: series, 119 HardwareCharacteristics: &hc, 120 }) 121 } 122 123 func (e *manualEnviron) StateInfo() (*state.Info, *api.Info, error) { 124 return common.StateInfo(e) 125 } 126 127 func (e *manualEnviron) SetConfig(cfg *config.Config) error { 128 e.cfgmutex.Lock() 129 defer e.cfgmutex.Unlock() 130 envConfig, err := manualProvider{}.validate(cfg, e.cfg.Config) 131 if err != nil { 132 return err 133 } 134 // Set storage. If "use-sshstorage" is true then use the SSH storage. 135 // Otherwise, use HTTP storage. 136 // 137 // We don't change storage once it's been set. Storage parameters 138 // are fixed at bootstrap time, and it is not possible to change 139 // them. 140 if e.storage == nil { 141 var stor storage.Storage 142 if envConfig.useSSHStorage() { 143 storageDir := e.StorageDir() 144 storageTmpdir := path.Join(dataDir, storageTmpSubdir) 145 stor, err = newSSHStorage("ubuntu@"+e.cfg.bootstrapHost(), storageDir, storageTmpdir) 146 if err != nil { 147 return fmt.Errorf("initialising SSH storage failed: %v", err) 148 } 149 } else { 150 caCertPEM, ok := envConfig.CACert() 151 if !ok { 152 // should not be possible to validate base config 153 return fmt.Errorf("ca-cert not set") 154 } 155 authkey := envConfig.storageAuthKey() 156 stor, err = httpstorage.ClientTLS(envConfig.storageAddr(), caCertPEM, authkey) 157 if err != nil { 158 return fmt.Errorf("initialising HTTPS storage failed: %v", err) 159 } 160 } 161 e.storage = stor 162 } 163 e.cfg = envConfig 164 return nil 165 } 166 167 // Implements environs.Environ. 168 // 169 // This method will only ever return an Instance for the Id 170 // environ/manual.BootstrapInstanceId. If any others are 171 // specified, then ErrPartialInstances or ErrNoInstances 172 // will result. 173 func (e *manualEnviron) Instances(ids []instance.Id) (instances []instance.Instance, err error) { 174 instances = make([]instance.Instance, len(ids)) 175 var found bool 176 for i, id := range ids { 177 if id == manual.BootstrapInstanceId { 178 instances[i] = manualBootstrapInstance{e.envConfig().bootstrapHost()} 179 found = true 180 } else { 181 err = environs.ErrPartialInstances 182 } 183 } 184 if !found { 185 err = environs.ErrNoInstances 186 } 187 return instances, err 188 } 189 190 var newSSHStorage = func(sshHost, storageDir, storageTmpdir string) (storage.Storage, error) { 191 return sshstorage.NewSSHStorage(sshstorage.NewSSHStorageParams{ 192 Host: sshHost, 193 StorageDir: storageDir, 194 TmpDir: storageTmpdir, 195 }) 196 } 197 198 // GetToolsSources returns a list of sources which are 199 // used to search for simplestreams tools metadata. 200 func (e *manualEnviron) GetToolsSources() ([]simplestreams.DataSource, error) { 201 // Add the simplestreams source off private storage. 202 return []simplestreams.DataSource{ 203 storage.NewStorageSimpleStreamsDataSource("cloud storage", e.Storage(), storage.BaseToolsPath), 204 }, nil 205 } 206 207 func (e *manualEnviron) Storage() storage.Storage { 208 e.cfgmutex.Lock() 209 defer e.cfgmutex.Unlock() 210 return e.storage 211 } 212 213 var runSSHCommand = func(host string, command []string) (stderr string, err error) { 214 cmd := ssh.Command(host, command, nil) 215 var stderrBuf bytes.Buffer 216 cmd.Stderr = &stderrBuf 217 err = cmd.Run() 218 return stderrBuf.String(), err 219 } 220 221 func (e *manualEnviron) Destroy() error { 222 stderr, err := runSSHCommand( 223 "ubuntu@"+e.envConfig().bootstrapHost(), 224 []string{"sudo", "pkill", fmt.Sprintf("-%d", terminationworker.TerminationSignal), "jujud"}, 225 ) 226 if err != nil { 227 if stderr := strings.TrimSpace(stderr); len(stderr) > 0 { 228 err = fmt.Errorf("%v (%v)", err, stderr) 229 } 230 } 231 return err 232 } 233 234 func (*manualEnviron) PrecheckInstance(series string, cons constraints.Value) error { 235 return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`) 236 } 237 238 func (e *manualEnviron) OpenPorts(ports []instance.Port) error { 239 return nil 240 } 241 242 func (e *manualEnviron) ClosePorts(ports []instance.Port) error { 243 return nil 244 } 245 246 func (e *manualEnviron) Ports() ([]instance.Port, error) { 247 return []instance.Port{}, nil 248 } 249 250 func (*manualEnviron) Provider() environs.EnvironProvider { 251 return manualProvider{} 252 } 253 254 func (e *manualEnviron) StorageAddr() string { 255 return e.envConfig().storageListenAddr() 256 } 257 258 func (e *manualEnviron) StorageDir() string { 259 return path.Join(dataDir, storageSubdir) 260 } 261 262 func (e *manualEnviron) SharedStorageAddr() string { 263 return "" 264 } 265 266 func (e *manualEnviron) SharedStorageDir() string { 267 return "" 268 } 269 270 func (e *manualEnviron) StorageCACert() []byte { 271 if bytes, ok := e.envConfig().CACert(); ok { 272 return bytes 273 } 274 return nil 275 } 276 277 func (e *manualEnviron) StorageCAKey() []byte { 278 if bytes, ok := e.envConfig().CAPrivateKey(); ok { 279 return bytes 280 } 281 return nil 282 } 283 284 func (e *manualEnviron) StorageHostnames() []string { 285 cfg := e.envConfig() 286 hostnames := []string{cfg.bootstrapHost()} 287 if ip := net.ParseIP(cfg.storageListenIPAddress()); ip != nil { 288 if !ip.IsUnspecified() { 289 hostnames = append(hostnames, ip.String()) 290 } 291 } 292 return hostnames 293 } 294 295 func (e *manualEnviron) StorageAuthKey() string { 296 return e.envConfig().storageAuthKey() 297 } 298 299 var _ localstorage.LocalTLSStorageConfig = (*manualEnviron)(nil)