launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/provider/local/environ.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package local 5 6 import ( 7 "fmt" 8 "net" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "strings" 13 "sync" 14 15 "launchpad.net/juju-core/agent" 16 coreCloudinit "launchpad.net/juju-core/cloudinit" 17 "launchpad.net/juju-core/cloudinit/sshinit" 18 "launchpad.net/juju-core/constraints" 19 "launchpad.net/juju-core/container" 20 "launchpad.net/juju-core/container/factory" 21 "launchpad.net/juju-core/environs" 22 "launchpad.net/juju-core/environs/bootstrap" 23 "launchpad.net/juju-core/environs/cloudinit" 24 "launchpad.net/juju-core/environs/config" 25 "launchpad.net/juju-core/environs/filestorage" 26 "launchpad.net/juju-core/environs/httpstorage" 27 "launchpad.net/juju-core/environs/simplestreams" 28 "launchpad.net/juju-core/environs/storage" 29 envtools "launchpad.net/juju-core/environs/tools" 30 "launchpad.net/juju-core/instance" 31 "launchpad.net/juju-core/juju/osenv" 32 "launchpad.net/juju-core/provider/common" 33 "launchpad.net/juju-core/state" 34 "launchpad.net/juju-core/state/api" 35 "launchpad.net/juju-core/tools" 36 "launchpad.net/juju-core/version" 37 "launchpad.net/juju-core/worker/terminationworker" 38 ) 39 40 // boostrapInstanceId is just the name we give to the bootstrap machine. 41 // Using "localhost" because it is, and it makes sense. 42 const bootstrapInstanceId instance.Id = "localhost" 43 44 // localEnviron implements Environ. 45 var _ environs.Environ = (*localEnviron)(nil) 46 47 // localEnviron implements SupportsCustomSources. 48 var _ envtools.SupportsCustomSources = (*localEnviron)(nil) 49 50 type localEnviron struct { 51 localMutex sync.Mutex 52 config *environConfig 53 name string 54 bridgeAddress string 55 localStorage storage.Storage 56 storageListener net.Listener 57 containerManager container.Manager 58 } 59 60 // GetToolsSources returns a list of sources which are used to search for simplestreams tools metadata. 61 func (e *localEnviron) GetToolsSources() ([]simplestreams.DataSource, error) { 62 // Add the simplestreams source off the control bucket. 63 return []simplestreams.DataSource{ 64 storage.NewStorageSimpleStreamsDataSource(e.Storage(), storage.BaseToolsPath)}, nil 65 } 66 67 // Name is specified in the Environ interface. 68 func (env *localEnviron) Name() string { 69 return env.name 70 } 71 72 func (env *localEnviron) mongoServiceName() string { 73 return "juju-db-" + env.config.namespace() 74 } 75 76 func (env *localEnviron) machineAgentServiceName() string { 77 return "juju-agent-" + env.config.namespace() 78 } 79 80 func (env *localEnviron) rsyslogConfPath() string { 81 return fmt.Sprintf("/etc/rsyslog.d/25-juju-%s.conf", env.config.namespace()) 82 } 83 84 // PrecheckInstance is specified in the environs.Prechecker interface. 85 func (*localEnviron) PrecheckInstance(series string, cons constraints.Value) error { 86 return nil 87 } 88 89 // PrecheckContainer is specified in the environs.Prechecker interface. 90 func (*localEnviron) PrecheckContainer(series string, kind instance.ContainerType) error { 91 // This check can either go away or be relaxed when the local 92 // provider can do nested containers. 93 return environs.NewContainersUnsupported("local provider does not support nested containers") 94 } 95 96 func ensureNotRoot() error { 97 if checkIfRoot() { 98 return fmt.Errorf("bootstrapping a local environment must not be done as root") 99 } 100 return nil 101 } 102 103 // Bootstrap is specified in the Environ interface. 104 func (env *localEnviron) Bootstrap(ctx environs.BootstrapContext, cons constraints.Value) error { 105 if err := ensureNotRoot(); err != nil { 106 return err 107 } 108 privateKey, err := common.GenerateSystemSSHKey(env) 109 if err != nil { 110 return err 111 } 112 113 // Before we write the agent config file, we need to make sure the 114 // instance is saved in the StateInfo. 115 stateFileURL, err := bootstrap.CreateStateFile(env.Storage()) 116 if err != nil { 117 return err 118 } 119 if err := bootstrap.SaveState(env.Storage(), &bootstrap.BootstrapState{ 120 StateInstances: []instance.Id{bootstrapInstanceId}, 121 }); err != nil { 122 logger.Errorf("failed to save state instances: %v", err) 123 return err 124 } 125 126 vers := version.Current 127 selectedTools, err := common.EnsureBootstrapTools(env, vers.Series, &vers.Arch) 128 if err != nil { 129 return err 130 } 131 132 // Record the bootstrap IP, so the containers know where to go for storage. 133 cfg, err := env.Config().Apply(map[string]interface{}{ 134 "bootstrap-ip": env.bridgeAddress, 135 }) 136 if err == nil { 137 err = env.SetConfig(cfg) 138 } 139 if err != nil { 140 logger.Errorf("failed to apply bootstrap-ip to config: %v", err) 141 return err 142 } 143 144 bootstrapJobs, err := agent.MarshalBootstrapJobs(state.JobManageEnviron) 145 if err != nil { 146 return err 147 } 148 149 mcfg := environs.NewBootstrapMachineConfig(stateFileURL, privateKey) 150 mcfg.Tools = selectedTools[0] 151 mcfg.DataDir = env.config.rootDir() 152 mcfg.LogDir = env.config.logDir() 153 mcfg.RsyslogConfPath = env.rsyslogConfPath() 154 mcfg.CloudInitOutputLog = filepath.Join(mcfg.LogDir, "cloud-init-output.log") 155 mcfg.DisablePackageCommands = true 156 mcfg.MachineAgentServiceName = env.machineAgentServiceName() 157 mcfg.MongoServiceName = env.mongoServiceName() 158 mcfg.AgentEnvironment = map[string]string{ 159 agent.Namespace: env.config.namespace(), 160 agent.StorageDir: env.config.storageDir(), 161 agent.StorageAddr: env.config.storageAddr(), 162 agent.BootstrapJobs: bootstrapJobs, 163 } 164 if err := environs.FinishMachineConfig(mcfg, cfg, cons); err != nil { 165 return err 166 } 167 // don't write proxy settings for local machine 168 mcfg.AptProxySettings = osenv.ProxySettings{} 169 mcfg.ProxySettings = osenv.ProxySettings{} 170 cloudcfg := coreCloudinit.New() 171 // Since rsyslogd is restricted by apparmor to only write to /var/log/** 172 // we now provide a symlink to the written file in the local log dir. 173 // Also, we leave the old all-machines.log file in 174 // /var/log/juju-{{namespace}} until we start the environment again. So 175 // potentially remove it at the start of the cloud-init. 176 logfile := fmt.Sprintf("/var/log/juju-%s/all-machines.log", env.config.namespace()) 177 cloudcfg.AddScripts( 178 fmt.Sprintf("[ -f %s ] && rm %s", logfile, logfile), 179 fmt.Sprintf("ln -s %s %s/", logfile, env.config.logDir())) 180 if err := cloudinit.ConfigureJuju(mcfg, cloudcfg); err != nil { 181 return err 182 } 183 return finishBootstrap(mcfg, cloudcfg, ctx) 184 } 185 186 // finishBootstrap converts the machine config to cloud-config, 187 // converts that to a script, and then executes it locally. 188 // 189 // mcfg is supplied for testing purposes. 190 var finishBootstrap = func(mcfg *cloudinit.MachineConfig, cloudcfg *coreCloudinit.Config, ctx environs.BootstrapContext) error { 191 script, err := sshinit.ConfigureScript(cloudcfg) 192 if err != nil { 193 return nil 194 } 195 cmd := exec.Command("sudo", "/bin/bash", "-s") 196 cmd.Stdin = strings.NewReader(script) 197 cmd.Stdout = ctx.Stdout() 198 cmd.Stderr = ctx.Stderr() 199 return cmd.Run() 200 } 201 202 // StateInfo is specified in the Environ interface. 203 func (env *localEnviron) StateInfo() (*state.Info, *api.Info, error) { 204 return common.StateInfo(env) 205 } 206 207 // Config is specified in the Environ interface. 208 func (env *localEnviron) Config() *config.Config { 209 env.localMutex.Lock() 210 defer env.localMutex.Unlock() 211 return env.config.Config 212 } 213 214 // SetConfig is specified in the Environ interface. 215 func (env *localEnviron) SetConfig(cfg *config.Config) error { 216 ecfg, err := providerInstance.newConfig(cfg) 217 if err != nil { 218 logger.Errorf("failed to create new environ config: %v", err) 219 return err 220 } 221 env.localMutex.Lock() 222 defer env.localMutex.Unlock() 223 env.config = ecfg 224 env.name = ecfg.Name() 225 226 env.containerManager, err = factory.NewContainerManager( 227 ecfg.container(), 228 container.ManagerConfig{ 229 Name: env.config.namespace(), 230 LogDir: env.config.logDir(), 231 }) 232 if err != nil { 233 return err 234 } 235 236 // When the localEnviron value is created on the client 237 // side, the bootstrap-ip attribute will not exist, 238 // because it is only set *within* the running 239 // environment, not in the configuration created by 240 // Prepare. 241 // 242 // When bootstrapIPAddress returns a non-empty string, 243 // we know we are running server-side and thus must use 244 // httpstorage. 245 if addr := ecfg.bootstrapIPAddress(); addr != "" { 246 env.bridgeAddress = addr 247 return nil 248 } 249 // If we get to here, it is because we haven't yet bootstrapped an 250 // environment, and saved the config in it, or we are running a command 251 // from the command line, so it is ok to work on the assumption that we 252 // have direct access to the directories. 253 if err := env.config.createDirs(); err != nil { 254 return err 255 } 256 // Record the network bridge address and create a filestorage. 257 if err := env.resolveBridgeAddress(cfg); err != nil { 258 return err 259 } 260 return env.setLocalStorage() 261 } 262 263 // resolveBridgeAddress finishes up the setup of the environment in 264 // situations where there is no machine agent running yet. 265 func (env *localEnviron) resolveBridgeAddress(cfg *config.Config) error { 266 // We need the provider config to get the network bridge. 267 config, err := providerInstance.newConfig(cfg) 268 if err != nil { 269 logger.Errorf("failed to create new environ config: %v", err) 270 return err 271 } 272 networkBridge := config.networkBridge() 273 bridgeAddress, err := getAddressForInterface(networkBridge) 274 if err != nil { 275 logger.Infof("configure a different bridge using 'network-bridge' in the config file") 276 return fmt.Errorf("cannot find address of network-bridge: %q: %v", networkBridge, err) 277 } 278 logger.Debugf("found %q as address for %q", bridgeAddress, networkBridge) 279 env.bridgeAddress = bridgeAddress 280 return nil 281 } 282 283 // setLocalStorage creates a filestorage so tools can 284 // be synced and so forth without having a machine agent 285 // running. 286 func (env *localEnviron) setLocalStorage() error { 287 storage, err := filestorage.NewFileStorageWriter(env.config.storageDir(), filestorage.UseDefaultTmpDir) 288 if err != nil { 289 return err 290 } 291 env.localStorage = storage 292 return nil 293 } 294 295 // StartInstance is specified in the InstanceBroker interface. 296 func (env *localEnviron) StartInstance(cons constraints.Value, possibleTools tools.List, 297 machineConfig *cloudinit.MachineConfig) (instance.Instance, *instance.HardwareCharacteristics, error) { 298 299 series := possibleTools.OneSeries() 300 logger.Debugf("StartInstance: %q, %s", machineConfig.MachineId, series) 301 machineConfig.Tools = possibleTools[0] 302 machineConfig.MachineContainerType = env.config.container() 303 logger.Debugf("tools: %#v", machineConfig.Tools) 304 network := container.BridgeNetworkConfig(env.config.networkBridge()) 305 if err := environs.FinishMachineConfig(machineConfig, env.config.Config, cons); err != nil { 306 return nil, nil, err 307 } 308 // TODO: evaluate the impact of setting the contstraints on the 309 // machineConfig for all machines rather than just state server nodes. 310 // This limiation is why the constraints are assigned directly here. 311 machineConfig.Constraints = cons 312 machineConfig.AgentEnvironment[agent.Namespace] = env.config.namespace() 313 inst, hardware, err := env.containerManager.StartContainer(machineConfig, series, network) 314 if err != nil { 315 return nil, nil, err 316 } 317 return inst, hardware, nil 318 } 319 320 // StartInstance is specified in the InstanceBroker interface. 321 func (env *localEnviron) StopInstances(instances []instance.Instance) error { 322 for _, inst := range instances { 323 if inst.Id() == bootstrapInstanceId { 324 return fmt.Errorf("cannot stop the bootstrap instance") 325 } 326 if err := env.containerManager.StopContainer(inst); err != nil { 327 return err 328 } 329 } 330 return nil 331 } 332 333 // Instances is specified in the Environ interface. 334 func (env *localEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) { 335 if len(ids) == 0 { 336 return nil, nil 337 } 338 insts, err := env.AllInstances() 339 if err != nil { 340 return nil, err 341 } 342 allInstances := make(map[instance.Id]instance.Instance) 343 for _, inst := range insts { 344 allInstances[inst.Id()] = inst 345 } 346 var found int 347 insts = make([]instance.Instance, len(ids)) 348 for i, id := range ids { 349 if inst, ok := allInstances[id]; ok { 350 insts[i] = inst 351 found++ 352 } 353 } 354 if found == 0 { 355 insts, err = nil, environs.ErrNoInstances 356 } else if found < len(ids) { 357 err = environs.ErrPartialInstances 358 } else { 359 err = nil 360 } 361 return insts, err 362 } 363 364 // AllInstances is specified in the InstanceBroker interface. 365 func (env *localEnviron) AllInstances() (instances []instance.Instance, err error) { 366 instances = append(instances, &localInstance{bootstrapInstanceId, env}) 367 // Add in all the containers as well. 368 lxcInstances, err := env.containerManager.ListContainers() 369 if err != nil { 370 return nil, err 371 } 372 for _, inst := range lxcInstances { 373 instances = append(instances, &localInstance{inst.Id(), env}) 374 } 375 return instances, nil 376 } 377 378 // Storage is specified in the Environ interface. 379 func (env *localEnviron) Storage() storage.Storage { 380 // localStorage is non-nil if we're running from the CLI 381 if env.localStorage != nil { 382 return env.localStorage 383 } 384 return httpstorage.Client(env.config.storageAddr()) 385 } 386 387 // Destroy is specified in the Environ interface. 388 func (env *localEnviron) Destroy() error { 389 // Kill all running instances. This must be done as 390 // root, or listing/stopping containers will fail. 391 if !checkIfRoot() { 392 juju, err := exec.LookPath(os.Args[0]) 393 if err != nil { 394 return err 395 } 396 args := []string{osenv.JujuHomeEnvKey + "=" + osenv.JujuHome()} 397 args = append(args, juju) 398 args = append(args, os.Args[1:]...) 399 args = append(args, "-y") 400 cmd := exec.Command("sudo", args...) 401 cmd.Stdout = os.Stdout 402 cmd.Stderr = os.Stderr 403 return cmd.Run() 404 } else { 405 containers, err := env.containerManager.ListContainers() 406 if err != nil { 407 return err 408 } 409 for _, inst := range containers { 410 if err := env.containerManager.StopContainer(inst); err != nil { 411 return err 412 } 413 } 414 cmd := exec.Command( 415 "pkill", 416 fmt.Sprintf("-%d", terminationworker.TerminationSignal), 417 "jujud", 418 ) 419 return cmd.Run() 420 } 421 } 422 423 // OpenPorts is specified in the Environ interface. 424 func (env *localEnviron) OpenPorts(ports []instance.Port) error { 425 return fmt.Errorf("open ports not implemented") 426 } 427 428 // ClosePorts is specified in the Environ interface. 429 func (env *localEnviron) ClosePorts(ports []instance.Port) error { 430 return fmt.Errorf("close ports not implemented") 431 } 432 433 // Ports is specified in the Environ interface. 434 func (env *localEnviron) Ports() ([]instance.Port, error) { 435 return nil, nil 436 } 437 438 // Provider is specified in the Environ interface. 439 func (env *localEnviron) Provider() environs.EnvironProvider { 440 return providerInstance 441 }