github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/provider/local/environ.go (about) 1 // Copyright 2013, 2014 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 "regexp" 13 "strings" 14 "sync" 15 "syscall" 16 17 "github.com/juju/errors" 18 "github.com/juju/utils/proxy" 19 "github.com/juju/utils/shell" 20 21 "github.com/juju/juju/agent" 22 "github.com/juju/juju/agent/mongo" 23 coreCloudinit "github.com/juju/juju/cloudinit" 24 "github.com/juju/juju/cloudinit/sshinit" 25 "github.com/juju/juju/constraints" 26 "github.com/juju/juju/container" 27 "github.com/juju/juju/container/factory" 28 "github.com/juju/juju/environs" 29 "github.com/juju/juju/environs/bootstrap" 30 "github.com/juju/juju/environs/cloudinit" 31 "github.com/juju/juju/environs/config" 32 "github.com/juju/juju/environs/filestorage" 33 "github.com/juju/juju/environs/httpstorage" 34 "github.com/juju/juju/environs/network" 35 "github.com/juju/juju/environs/simplestreams" 36 "github.com/juju/juju/environs/storage" 37 envtools "github.com/juju/juju/environs/tools" 38 "github.com/juju/juju/instance" 39 "github.com/juju/juju/juju/arch" 40 "github.com/juju/juju/juju/osenv" 41 "github.com/juju/juju/provider/common" 42 "github.com/juju/juju/state" 43 "github.com/juju/juju/state/api" 44 "github.com/juju/juju/state/api/params" 45 "github.com/juju/juju/upstart" 46 "github.com/juju/juju/version" 47 "github.com/juju/juju/worker/terminationworker" 48 ) 49 50 // boostrapInstanceId is just the name we give to the bootstrap machine. 51 // Using "localhost" because it is, and it makes sense. 52 const bootstrapInstanceId instance.Id = "localhost" 53 54 // localEnviron implements Environ. 55 var _ environs.Environ = (*localEnviron)(nil) 56 57 // localEnviron implements SupportsCustomSources. 58 var _ envtools.SupportsCustomSources = (*localEnviron)(nil) 59 60 type localEnviron struct { 61 common.SupportsUnitPlacementPolicy 62 63 localMutex sync.Mutex 64 config *environConfig 65 name string 66 bridgeAddress string 67 localStorage storage.Storage 68 storageListener net.Listener 69 containerManager container.Manager 70 } 71 72 // GetToolsSources returns a list of sources which are used to search for simplestreams tools metadata. 73 func (e *localEnviron) GetToolsSources() ([]simplestreams.DataSource, error) { 74 // Add the simplestreams source off the control bucket. 75 return []simplestreams.DataSource{ 76 storage.NewStorageSimpleStreamsDataSource("cloud storage", e.Storage(), storage.BaseToolsPath)}, nil 77 } 78 79 // SupportedArchitectures is specified on the EnvironCapability interface. 80 func (*localEnviron) SupportedArchitectures() ([]string, error) { 81 localArch := arch.HostArch() 82 return []string{localArch}, nil 83 } 84 85 // SupportNetworks is specified on the EnvironCapability interface. 86 func (*localEnviron) SupportNetworks() bool { 87 return false 88 } 89 90 func (*localEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error { 91 if placement != "" { 92 return fmt.Errorf("unknown placement directive: %s", placement) 93 } 94 return nil 95 } 96 97 // Name is specified in the Environ interface. 98 func (env *localEnviron) Name() string { 99 return env.name 100 } 101 102 func (env *localEnviron) machineAgentServiceName() string { 103 return "juju-agent-" + env.config.namespace() 104 } 105 106 func ensureNotRoot() error { 107 if checkIfRoot() { 108 return fmt.Errorf("bootstrapping a local environment must not be done as root") 109 } 110 return nil 111 } 112 113 // Bootstrap is specified in the Environ interface. 114 func (env *localEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) error { 115 if err := ensureNotRoot(); err != nil { 116 return err 117 } 118 privateKey, err := common.GenerateSystemSSHKey(env) 119 if err != nil { 120 return err 121 } 122 123 // Before we write the agent config file, we need to make sure the 124 // instance is saved in the StateInfo. 125 if err := bootstrap.SaveState(env.Storage(), &bootstrap.BootstrapState{ 126 StateInstances: []instance.Id{bootstrapInstanceId}, 127 }); err != nil { 128 logger.Errorf("failed to save state instances: %v", err) 129 return err 130 } 131 132 vers := version.Current 133 selectedTools, err := common.EnsureBootstrapTools(ctx, env, vers.Series, &vers.Arch) 134 if err != nil { 135 return err 136 } 137 138 // Record the bootstrap IP, so the containers know where to go for storage. 139 cfg, err := env.Config().Apply(map[string]interface{}{ 140 "bootstrap-ip": env.bridgeAddress, 141 }) 142 if err == nil { 143 err = env.SetConfig(cfg) 144 } 145 if err != nil { 146 logger.Errorf("failed to apply bootstrap-ip to config: %v", err) 147 return err 148 } 149 150 mcfg := environs.NewBootstrapMachineConfig(privateKey) 151 mcfg.InstanceId = bootstrapInstanceId 152 mcfg.Tools = selectedTools[0] 153 mcfg.DataDir = env.config.rootDir() 154 mcfg.LogDir = fmt.Sprintf("/var/log/juju-%s", env.config.namespace()) 155 mcfg.Jobs = []params.MachineJob{params.JobManageEnviron} 156 mcfg.CloudInitOutputLog = filepath.Join(mcfg.DataDir, "cloud-init-output.log") 157 mcfg.DisablePackageCommands = true 158 mcfg.MachineAgentServiceName = env.machineAgentServiceName() 159 mcfg.AgentEnvironment = map[string]string{ 160 agent.Namespace: env.config.namespace(), 161 agent.StorageDir: env.config.storageDir(), 162 agent.StorageAddr: env.config.storageAddr(), 163 } 164 if err := environs.FinishMachineConfig(mcfg, cfg, args.Constraints); err != nil { 165 return err 166 } 167 // don't write proxy settings for local machine 168 mcfg.AptProxySettings = proxy.Settings{} 169 mcfg.ProxySettings = proxy.Settings{} 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 localLogDir := filepath.Join(mcfg.DataDir, "log") 177 if err := os.RemoveAll(localLogDir); err != nil { 178 return err 179 } 180 if err := os.Symlink(mcfg.LogDir, localLogDir); err != nil { 181 return err 182 } 183 if err := os.Remove(mcfg.CloudInitOutputLog); err != nil && !os.IsNotExist(err) { 184 return err 185 } 186 cloudcfg.AddScripts( 187 fmt.Sprintf("rm -fr %s", mcfg.LogDir), 188 fmt.Sprintf("rm -f /var/spool/rsyslog/machine-0-%s", env.config.namespace()), 189 ) 190 if err := cloudinit.ConfigureJuju(mcfg, cloudcfg); err != nil { 191 return err 192 } 193 return finishBootstrap(mcfg, cloudcfg, ctx) 194 } 195 196 // finishBootstrap converts the machine config to cloud-config, 197 // converts that to a script, and then executes it locally. 198 // 199 // mcfg is supplied for testing purposes. 200 var finishBootstrap = func(mcfg *cloudinit.MachineConfig, cloudcfg *coreCloudinit.Config, ctx environs.BootstrapContext) error { 201 configScript, err := sshinit.ConfigureScript(cloudcfg) 202 if err != nil { 203 return nil 204 } 205 script := shell.DumpFileOnErrorScript(mcfg.CloudInitOutputLog) + configScript 206 cmd := exec.Command("sudo", "/bin/bash", "-s") 207 cmd.Stdin = strings.NewReader(script) 208 cmd.Stdout = ctx.GetStdout() 209 cmd.Stderr = ctx.GetStderr() 210 return cmd.Run() 211 } 212 213 // StateInfo is specified in the Environ interface. 214 func (env *localEnviron) StateInfo() (*state.Info, *api.Info, error) { 215 return common.StateInfo(env) 216 } 217 218 // Config is specified in the Environ interface. 219 func (env *localEnviron) Config() *config.Config { 220 env.localMutex.Lock() 221 defer env.localMutex.Unlock() 222 return env.config.Config 223 } 224 225 // SetConfig is specified in the Environ interface. 226 func (env *localEnviron) SetConfig(cfg *config.Config) error { 227 ecfg, err := providerInstance.newConfig(cfg) 228 if err != nil { 229 logger.Errorf("failed to create new environ config: %v", err) 230 return err 231 } 232 env.localMutex.Lock() 233 defer env.localMutex.Unlock() 234 env.config = ecfg 235 env.name = ecfg.Name() 236 containerType := ecfg.container() 237 managerConfig := container.ManagerConfig{ 238 container.ConfigName: env.config.namespace(), 239 container.ConfigLogDir: env.config.logDir(), 240 } 241 if containerType == instance.LXC { 242 if useLxcClone, ok := cfg.LXCUseClone(); ok { 243 managerConfig["use-clone"] = fmt.Sprint(useLxcClone) 244 } 245 if useLxcCloneAufs, ok := cfg.LXCUseCloneAUFS(); ok { 246 managerConfig["use-aufs"] = fmt.Sprint(useLxcCloneAufs) 247 } 248 } 249 env.containerManager, err = factory.NewContainerManager( 250 containerType, managerConfig) 251 if err != nil { 252 return err 253 } 254 255 // When the localEnviron value is created on the client 256 // side, the bootstrap-ip attribute will not exist, 257 // because it is only set *within* the running 258 // environment, not in the configuration created by 259 // Prepare. 260 // 261 // When bootstrapIPAddress returns a non-empty string, 262 // we know we are running server-side and thus must use 263 // httpstorage. 264 if addr := ecfg.bootstrapIPAddress(); addr != "" { 265 env.bridgeAddress = addr 266 return nil 267 } 268 // If we get to here, it is because we haven't yet bootstrapped an 269 // environment, and saved the config in it, or we are running a command 270 // from the command line, so it is ok to work on the assumption that we 271 // have direct access to the directories. 272 if err := env.config.createDirs(); err != nil { 273 return err 274 } 275 // Record the network bridge address and create a filestorage. 276 if err := env.resolveBridgeAddress(cfg); err != nil { 277 return err 278 } 279 return env.setLocalStorage() 280 } 281 282 // resolveBridgeAddress finishes up the setup of the environment in 283 // situations where there is no machine agent running yet. 284 func (env *localEnviron) resolveBridgeAddress(cfg *config.Config) error { 285 // We need the provider config to get the network bridge. 286 config, err := providerInstance.newConfig(cfg) 287 if err != nil { 288 logger.Errorf("failed to create new environ config: %v", err) 289 return err 290 } 291 networkBridge := config.networkBridge() 292 bridgeAddress, err := getAddressForInterface(networkBridge) 293 if err != nil { 294 logger.Infof("configure a different bridge using 'network-bridge' in the config file") 295 return fmt.Errorf("cannot find address of network-bridge: %q: %v", networkBridge, err) 296 } 297 logger.Debugf("found %q as address for %q", bridgeAddress, networkBridge) 298 env.bridgeAddress = bridgeAddress 299 return nil 300 } 301 302 // setLocalStorage creates a filestorage so tools can 303 // be synced and so forth without having a machine agent 304 // running. 305 func (env *localEnviron) setLocalStorage() error { 306 storage, err := filestorage.NewFileStorageWriter(env.config.storageDir()) 307 if err != nil { 308 return err 309 } 310 env.localStorage = storage 311 return nil 312 } 313 314 var unsupportedConstraints = []string{ 315 constraints.CpuCores, 316 constraints.CpuPower, 317 constraints.InstanceType, 318 constraints.Tags, 319 } 320 321 // ConstraintsValidator is defined on the Environs interface. 322 func (env *localEnviron) ConstraintsValidator() (constraints.Validator, error) { 323 validator := constraints.NewValidator() 324 validator.RegisterUnsupported(unsupportedConstraints) 325 supportedArches, err := env.SupportedArchitectures() 326 if err != nil { 327 return nil, err 328 } 329 validator.RegisterVocabulary(constraints.Arch, supportedArches) 330 return validator, nil 331 } 332 333 // StartInstance is specified in the InstanceBroker interface. 334 func (env *localEnviron) StartInstance(args environs.StartInstanceParams) (instance.Instance, *instance.HardwareCharacteristics, []network.Info, error) { 335 if args.MachineConfig.HasNetworks() { 336 return nil, nil, nil, fmt.Errorf("starting instances with networks is not supported yet.") 337 } 338 series := args.Tools.OneSeries() 339 logger.Debugf("StartInstance: %q, %s", args.MachineConfig.MachineId, series) 340 args.MachineConfig.Tools = args.Tools[0] 341 args.MachineConfig.MachineContainerType = env.config.container() 342 logger.Debugf("tools: %#v", args.MachineConfig.Tools) 343 network := container.BridgeNetworkConfig(env.config.networkBridge()) 344 if err := environs.FinishMachineConfig(args.MachineConfig, env.config.Config, args.Constraints); err != nil { 345 return nil, nil, nil, err 346 } 347 // TODO: evaluate the impact of setting the contstraints on the 348 // machineConfig for all machines rather than just state server nodes. 349 // This limiation is why the constraints are assigned directly here. 350 args.MachineConfig.Constraints = args.Constraints 351 args.MachineConfig.AgentEnvironment[agent.Namespace] = env.config.namespace() 352 inst, hardware, err := env.containerManager.CreateContainer(args.MachineConfig, series, network) 353 if err != nil { 354 return nil, nil, nil, err 355 } 356 return inst, hardware, nil, nil 357 } 358 359 // StopInstances is specified in the InstanceBroker interface. 360 func (env *localEnviron) StopInstances(ids ...instance.Id) error { 361 for _, id := range ids { 362 if id == bootstrapInstanceId { 363 return fmt.Errorf("cannot stop the bootstrap instance") 364 } 365 if err := env.containerManager.DestroyContainer(id); err != nil { 366 return err 367 } 368 } 369 return nil 370 } 371 372 // Instances is specified in the Environ interface. 373 func (env *localEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) { 374 if len(ids) == 0 { 375 return nil, nil 376 } 377 insts, err := env.AllInstances() 378 if err != nil { 379 return nil, err 380 } 381 allInstances := make(map[instance.Id]instance.Instance) 382 for _, inst := range insts { 383 allInstances[inst.Id()] = inst 384 } 385 var found int 386 insts = make([]instance.Instance, len(ids)) 387 for i, id := range ids { 388 if inst, ok := allInstances[id]; ok { 389 insts[i] = inst 390 found++ 391 } 392 } 393 if found == 0 { 394 insts, err = nil, environs.ErrNoInstances 395 } else if found < len(ids) { 396 err = environs.ErrPartialInstances 397 } else { 398 err = nil 399 } 400 return insts, err 401 } 402 403 // AllocateAddress requests a new address to be allocated for the 404 // given instance on the given network. This is not supported on the 405 // local provider. 406 func (*localEnviron) AllocateAddress(_ instance.Id, _ network.Id) (instance.Address, error) { 407 return instance.Address{}, errors.NotSupportedf("AllocateAddress") 408 } 409 410 // AllInstances is specified in the InstanceBroker interface. 411 func (env *localEnviron) AllInstances() (instances []instance.Instance, err error) { 412 instances = append(instances, &localInstance{bootstrapInstanceId, env}) 413 // Add in all the containers as well. 414 lxcInstances, err := env.containerManager.ListContainers() 415 if err != nil { 416 return nil, err 417 } 418 for _, inst := range lxcInstances { 419 instances = append(instances, &localInstance{inst.Id(), env}) 420 } 421 return instances, nil 422 } 423 424 // Storage is specified in the Environ interface. 425 func (env *localEnviron) Storage() storage.Storage { 426 // localStorage is non-nil if we're running from the CLI 427 if env.localStorage != nil { 428 return env.localStorage 429 } 430 return httpstorage.Client(env.config.storageAddr()) 431 } 432 433 // Destroy is specified in the Environ interface. 434 func (env *localEnviron) Destroy() error { 435 // If bootstrap failed, for example because the user 436 // lacks sudo rights, then the agents won't have been 437 // installed. If that's the case, we can just remove 438 // the data-dir and exit. 439 agentsDir := filepath.Join(env.config.rootDir(), "agents") 440 if _, err := os.Stat(agentsDir); os.IsNotExist(err) { 441 // If we can't remove the root dir, then continue 442 // and attempt as root anyway. 443 if os.RemoveAll(env.config.rootDir()) == nil { 444 return nil 445 } 446 } 447 if !checkIfRoot() { 448 juju, err := exec.LookPath(os.Args[0]) 449 if err != nil { 450 return err 451 } 452 args := []string{ 453 "env", osenv.JujuHomeEnvKey + "=" + osenv.JujuHome(), 454 juju, "destroy-environment", "-y", "--force", env.Name(), 455 } 456 cmd := exec.Command("sudo", args...) 457 cmd.Stdout = os.Stdout 458 cmd.Stderr = os.Stderr 459 return cmd.Run() 460 } 461 // Kill all running instances. This must be done as 462 // root, or listing/stopping containers will fail. 463 containers, err := env.containerManager.ListContainers() 464 if err != nil { 465 return err 466 } 467 for _, inst := range containers { 468 if err := env.containerManager.DestroyContainer(inst.Id()); err != nil { 469 return err 470 } 471 } 472 cmd := exec.Command( 473 "pkill", 474 fmt.Sprintf("-%d", terminationworker.TerminationSignal), 475 "-f", filepath.Join(regexp.QuoteMeta(env.config.rootDir()), ".*", "jujud"), 476 ) 477 if err := cmd.Run(); err != nil { 478 if err, ok := err.(*exec.ExitError); ok { 479 // Exit status 1 means no processes were matched: 480 // we don't consider this an error here. 481 if err.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() != 1 { 482 return errors.Annotate(err, "failed to kill jujud") 483 } 484 } 485 } 486 // Stop the mongo database and machine agent. It's possible that the 487 // service doesn't exist or is not running, so don't check the error. 488 mongo.RemoveService(env.config.namespace()) 489 upstart.NewService(env.machineAgentServiceName()).StopAndRemove() 490 491 // Finally, remove the data-dir. 492 if err := os.RemoveAll(env.config.rootDir()); err != nil && !os.IsNotExist(err) { 493 // Before we return the error, just check to see if the directory is 494 // there. There is a race condition with the agent with the removing 495 // of the directory, and due to a bug 496 // (https://code.google.com/p/go/issues/detail?id=7776) the 497 // os.IsNotExist error isn't always returned. 498 if _, statErr := os.Stat(env.config.rootDir()); os.IsNotExist(statErr) { 499 return nil 500 } 501 return err 502 } 503 return nil 504 } 505 506 // OpenPorts is specified in the Environ interface. 507 func (env *localEnviron) OpenPorts(ports []instance.Port) error { 508 return fmt.Errorf("open ports not implemented") 509 } 510 511 // ClosePorts is specified in the Environ interface. 512 func (env *localEnviron) ClosePorts(ports []instance.Port) error { 513 return fmt.Errorf("close ports not implemented") 514 } 515 516 // Ports is specified in the Environ interface. 517 func (env *localEnviron) Ports() ([]instance.Port, error) { 518 return nil, nil 519 } 520 521 // Provider is specified in the Environ interface. 522 func (env *localEnviron) Provider() environs.EnvironProvider { 523 return providerInstance 524 }