github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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/arch" 19 "github.com/juju/utils/proxy" 20 "github.com/juju/utils/series" 21 "github.com/juju/utils/shell" 22 "github.com/juju/utils/symlink" 23 24 "github.com/juju/juju/agent" 25 "github.com/juju/juju/cloudconfig" 26 "github.com/juju/juju/cloudconfig/cloudinit" 27 "github.com/juju/juju/cloudconfig/instancecfg" 28 "github.com/juju/juju/constraints" 29 "github.com/juju/juju/container" 30 "github.com/juju/juju/container/factory" 31 "github.com/juju/juju/environs" 32 "github.com/juju/juju/environs/config" 33 "github.com/juju/juju/environs/filestorage" 34 "github.com/juju/juju/environs/httpstorage" 35 "github.com/juju/juju/environs/storage" 36 "github.com/juju/juju/instance" 37 "github.com/juju/juju/juju/osenv" 38 "github.com/juju/juju/mongo" 39 "github.com/juju/juju/network" 40 "github.com/juju/juju/provider/common" 41 "github.com/juju/juju/service" 42 servicecommon "github.com/juju/juju/service/common" 43 "github.com/juju/juju/state/multiwatcher" 44 "github.com/juju/juju/tools" 45 "github.com/juju/juju/worker/terminationworker" 46 ) 47 48 // boostrapInstanceId is just the name we give to the bootstrap machine. 49 // Using "localhost" because it is, and it makes sense. 50 const bootstrapInstanceId instance.Id = "localhost" 51 52 // localEnviron implements Environ. 53 var _ environs.Environ = (*localEnviron)(nil) 54 55 type localEnviron struct { 56 common.SupportsUnitPlacementPolicy 57 58 localMutex sync.Mutex 59 config *environConfig 60 name string 61 bridgeAddress string 62 localStorage storage.Storage 63 storageListener net.Listener 64 containerManager container.Manager 65 } 66 67 // SupportedArchitectures is specified on the EnvironCapability interface. 68 func (*localEnviron) SupportedArchitectures() ([]string, error) { 69 localArch := arch.HostArch() 70 return []string{localArch}, nil 71 } 72 73 func (*localEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error { 74 if placement != "" { 75 return fmt.Errorf("unknown placement directive: %s", placement) 76 } 77 return nil 78 } 79 80 func (env *localEnviron) machineAgentServiceName() string { 81 return "juju-agent-" + env.config.namespace() 82 } 83 84 func ensureNotRoot() error { 85 if checkIfRoot() { 86 return fmt.Errorf("bootstrapping a local environment must not be done as root") 87 } 88 return nil 89 } 90 91 // Bootstrap is specified in the Environ interface. 92 func (env *localEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (string, string, environs.BootstrapFinalizer, error) { 93 if err := ensureNotRoot(); err != nil { 94 return "", "", nil, err 95 } 96 97 // Make sure there are tools available for the 98 // host's architecture and series. 99 if _, err := args.AvailableTools.Match(tools.Filter{ 100 Arch: arch.HostArch(), 101 Series: series.HostSeries(), 102 }); err != nil { 103 return "", "", nil, err 104 } 105 106 cfg, err := env.Config().Apply(map[string]interface{}{ 107 // Record the bootstrap IP, so the containers know where to go for storage. 108 "bootstrap-ip": env.bridgeAddress, 109 }) 110 if err == nil { 111 err = env.SetConfig(cfg) 112 } 113 if err != nil { 114 logger.Errorf("failed to apply bootstrap-ip to config: %v", err) 115 return "", "", nil, err 116 } 117 return arch.HostArch(), series.HostSeries(), env.finishBootstrap, nil 118 } 119 120 // finishBootstrap converts the machine config to cloud-config, 121 // converts that to a script, and then executes it locally. 122 func (env *localEnviron) finishBootstrap(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig) error { 123 icfg.InstanceId = bootstrapInstanceId 124 icfg.DataDir = env.config.rootDir() 125 icfg.LogDir = fmt.Sprintf("/var/log/juju-%s", env.config.namespace()) 126 icfg.CloudInitOutputLog = filepath.Join(icfg.DataDir, "cloud-init-output.log") 127 128 // No JobManageNetworking added in order not to change the network 129 // configuration of the user's machine. 130 icfg.Jobs = []multiwatcher.MachineJob{multiwatcher.JobManageEnviron} 131 132 icfg.MachineAgentServiceName = env.machineAgentServiceName() 133 icfg.AgentEnvironment = map[string]string{ 134 agent.Namespace: env.config.namespace(), 135 agent.StorageDir: env.config.storageDir(), 136 agent.StorageAddr: env.config.storageAddr(), 137 agent.LxcBridge: env.config.networkBridge(), 138 139 // The local provider only supports a single state server, 140 // so we make the oplog size to a small value. This makes 141 // the preallocation faster with no disadvantage. 142 agent.MongoOplogSize: "1", // 1MB 143 } 144 145 if err := instancecfg.FinishInstanceConfig(icfg, env.Config()); err != nil { 146 return errors.Trace(err) 147 } 148 149 // Since Juju's state machine is currently the host machine 150 // for local providers, don't stomp on it. 151 cfgAttrs := env.config.AllAttrs() 152 if val, ok := cfgAttrs["enable-os-refresh-update"].(bool); !ok { 153 logger.Infof("local provider; disabling refreshing OS updates.") 154 icfg.EnableOSRefreshUpdate = false 155 } else { 156 icfg.EnableOSRefreshUpdate = val 157 } 158 if val, ok := cfgAttrs["enable-os-upgrade"].(bool); !ok { 159 logger.Infof("local provider; disabling OS upgrades.") 160 icfg.EnableOSUpgrade = false 161 } else { 162 icfg.EnableOSUpgrade = val 163 } 164 165 // don't write proxy or mirror settings for local machine 166 icfg.AptProxySettings = proxy.Settings{} 167 icfg.ProxySettings = proxy.Settings{} 168 icfg.AptMirror = "" 169 170 cloudcfg, err := cloudinit.New(icfg.Series) 171 if err != nil { 172 return errors.Trace(err) 173 } 174 cloudcfg.SetSystemUpdate(icfg.EnableOSRefreshUpdate) 175 cloudcfg.SetSystemUpgrade(icfg.EnableOSUpgrade) 176 177 // Since rsyslogd is restricted by apparmor to only write to /var/log/** 178 // we now provide a symlink to the written file in the local log dir. 179 // Also, we leave the old all-machines.log file in 180 // /var/log/juju-{{namespace}} until we start the environment again. So 181 // potentially remove it at the start of the cloud-init. 182 localLogDir := filepath.Join(icfg.DataDir, "log") 183 if err := os.RemoveAll(localLogDir); err != nil { 184 return errors.Trace(err) 185 } 186 if err := symlink.New(icfg.LogDir, localLogDir); err != nil { 187 return errors.Trace(err) 188 } 189 if err := os.Remove(icfg.CloudInitOutputLog); err != nil && !os.IsNotExist(err) { 190 return errors.Trace(err) 191 } 192 cloudcfg.AddScripts( 193 fmt.Sprintf("rm -fr %s", icfg.LogDir), 194 fmt.Sprintf("rm -f /var/spool/rsyslog/machine-0-%s", env.config.namespace()), 195 ) 196 udata, err := cloudconfig.NewUserdataConfig(icfg, cloudcfg) 197 if err != nil { 198 return errors.Trace(err) 199 } 200 if err := udata.ConfigureJuju(); err != nil { 201 return errors.Trace(err) 202 } 203 return executeCloudConfig(ctx, icfg, cloudcfg) 204 } 205 206 var executeCloudConfig = func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig, cloudcfg cloudinit.CloudConfig) error { 207 // Finally, convert cloud-config to a script and execute it. 208 configScript, err := cloudcfg.RenderScript() 209 if err != nil { 210 return nil 211 } 212 script := shell.DumpFileOnErrorScript(icfg.CloudInitOutputLog) + configScript 213 cmd := exec.Command("sudo", "/bin/bash", "-s") 214 cmd.Stdin = strings.NewReader(script) 215 cmd.Stdout = ctx.GetStdout() 216 cmd.Stderr = ctx.GetStderr() 217 return cmd.Run() 218 } 219 220 // StateServerInstances is specified in the Environ interface. 221 func (env *localEnviron) StateServerInstances() ([]instance.Id, error) { 222 agentsDir := filepath.Join(env.config.rootDir(), "agents") 223 _, err := os.Stat(agentsDir) 224 if os.IsNotExist(err) { 225 return nil, environs.ErrNotBootstrapped 226 } 227 if err != nil { 228 return nil, err 229 } 230 return []instance.Id{bootstrapInstanceId}, nil 231 } 232 233 // Config is specified in the Environ interface. 234 func (env *localEnviron) Config() *config.Config { 235 env.localMutex.Lock() 236 defer env.localMutex.Unlock() 237 return env.config.Config 238 } 239 240 // SetConfig is specified in the Environ interface. 241 func (env *localEnviron) SetConfig(cfg *config.Config) error { 242 ecfg, err := providerInstance.newConfig(cfg) 243 if err != nil { 244 logger.Errorf("failed to create new environ config: %v", err) 245 return errors.Trace(err) 246 } 247 env.localMutex.Lock() 248 defer env.localMutex.Unlock() 249 env.config = ecfg 250 env.name = ecfg.Name() 251 containerType := ecfg.container() 252 managerConfig := container.ManagerConfig{ 253 container.ConfigName: env.config.namespace(), 254 container.ConfigLogDir: env.config.logDir(), 255 } 256 var imageURLGetter container.ImageURLGetter 257 if containerType == instance.LXC { 258 if useLxcClone, ok := cfg.LXCUseClone(); ok { 259 managerConfig["use-clone"] = fmt.Sprint(useLxcClone) 260 } 261 if useLxcCloneAufs, ok := cfg.LXCUseCloneAUFS(); ok { 262 managerConfig["use-aufs"] = fmt.Sprint(useLxcCloneAufs) 263 } 264 // For lxc containers, we cache image tarballs in the environment storage, so here 265 // we construct a URL getter. 266 if uuid, ok := ecfg.UUID(); ok { 267 var caCert []byte = nil 268 if cert, ok := cfg.CACert(); ok { 269 caCert = []byte(cert) 270 } 271 imageURLGetter = container.NewImageURLGetter(ecfg.stateServerAddr(), uuid, caCert) 272 } 273 } 274 env.containerManager, err = factory.NewContainerManager( 275 containerType, managerConfig, imageURLGetter) 276 if err != nil { 277 return errors.Trace(err) 278 } 279 280 // When the localEnviron value is created on the client 281 // side, the bootstrap-ip attribute will not exist, 282 // because it is only set *within* the running 283 // environment, not in the configuration created by 284 // Prepare. 285 // 286 // When bootstrapIPAddress returns a non-empty string, 287 // we know we are running server-side and thus must use 288 // httpstorage. 289 if addr := ecfg.bootstrapIPAddress(); addr != "" { 290 env.bridgeAddress = addr 291 return nil 292 } 293 // If we get to here, it is because we haven't yet bootstrapped an 294 // environment, and saved the config in it, or we are running a command 295 // from the command line, so it is ok to work on the assumption that we 296 // have direct access to the directories. 297 if err := env.config.createDirs(); err != nil { 298 return errors.Trace(err) 299 } 300 // Record the network bridge address and create a filestorage. 301 if err := env.resolveBridgeAddress(cfg); err != nil { 302 return errors.Trace(err) 303 } 304 return env.setLocalStorage() 305 } 306 307 // resolveBridgeAddress finishes up the setup of the environment in 308 // situations where there is no machine agent running yet. 309 func (env *localEnviron) resolveBridgeAddress(cfg *config.Config) error { 310 // We need the provider config to get the network bridge. 311 config, err := providerInstance.newConfig(cfg) 312 if err != nil { 313 logger.Errorf("failed to create new environ config: %v", err) 314 return err 315 } 316 networkBridge := config.networkBridge() 317 bridgeAddress, err := getAddressForInterface(networkBridge) 318 if err != nil { 319 logger.Infof("configure a different bridge using 'network-bridge' in the config file") 320 return fmt.Errorf("cannot find address of network-bridge: %q: %v", networkBridge, err) 321 } 322 logger.Debugf("found %q as address for %q", bridgeAddress, networkBridge) 323 env.bridgeAddress = bridgeAddress 324 return nil 325 } 326 327 // setLocalStorage creates a filestorage so tools can 328 // be synced and so forth without having a machine agent 329 // running. 330 func (env *localEnviron) setLocalStorage() error { 331 storage, err := filestorage.NewFileStorageWriter(env.config.storageDir()) 332 if err != nil { 333 return err 334 } 335 env.localStorage = storage 336 return nil 337 } 338 339 var unsupportedConstraints = []string{ 340 constraints.CpuCores, 341 constraints.CpuPower, 342 constraints.InstanceType, 343 constraints.Tags, 344 } 345 346 // ConstraintsValidator is defined on the Environs interface. 347 func (env *localEnviron) ConstraintsValidator() (constraints.Validator, error) { 348 validator := constraints.NewValidator() 349 validator.RegisterUnsupported(unsupportedConstraints) 350 supportedArches, err := env.SupportedArchitectures() 351 if err != nil { 352 return nil, err 353 } 354 validator.RegisterVocabulary(constraints.Arch, supportedArches) 355 return validator, nil 356 } 357 358 // MaintainInstance is specified in the InstanceBroker interface. 359 func (*localEnviron) MaintainInstance(args environs.StartInstanceParams) error { 360 return nil 361 } 362 363 // StartInstance is specified in the InstanceBroker interface. 364 func (env *localEnviron) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 365 if args.InstanceConfig.HasNetworks() { 366 return nil, fmt.Errorf("starting instances with networks is not supported yet.") 367 } 368 series := args.Tools.OneSeries() 369 logger.Debugf("StartInstance: %q, %s", args.InstanceConfig.MachineId, series) 370 args.InstanceConfig.Tools = args.Tools[0] 371 372 args.InstanceConfig.MachineContainerType = env.config.container() 373 logger.Debugf("tools: %#v", args.InstanceConfig.Tools) 374 if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, env.config.Config); err != nil { 375 return nil, err 376 } 377 // TODO: evaluate the impact of setting the constraints on the 378 // instanceConfig for all machines rather than just state server nodes. 379 // This limitation is why the constraints are assigned directly here. 380 args.InstanceConfig.Constraints = args.Constraints 381 args.InstanceConfig.AgentEnvironment[agent.Namespace] = env.config.namespace() 382 inst, hardware, err := createContainer(env, args) 383 if err != nil { 384 return nil, err 385 } 386 return &environs.StartInstanceResult{ 387 Instance: inst, 388 Hardware: hardware, 389 }, nil 390 } 391 392 // Override for testing. 393 var createContainer = func(env *localEnviron, args environs.StartInstanceParams) (instance.Instance, *instance.HardwareCharacteristics, error) { 394 series := args.Tools.OneSeries() 395 network := container.BridgeNetworkConfig(env.config.networkBridge(), 0, args.NetworkInfo) 396 allowLoopMounts, _ := env.config.AllowLXCLoopMounts() 397 isLXC := env.config.container() == instance.LXC 398 storage := &container.StorageConfig{ 399 AllowMount: !isLXC || allowLoopMounts, 400 } 401 inst, hardware, err := env.containerManager.CreateContainer(args.InstanceConfig, series, network, storage) 402 if err != nil { 403 return nil, nil, err 404 } 405 return inst, hardware, nil 406 } 407 408 // StopInstances is specified in the InstanceBroker interface. 409 func (env *localEnviron) StopInstances(ids ...instance.Id) error { 410 for _, id := range ids { 411 if id == bootstrapInstanceId { 412 return fmt.Errorf("cannot stop the bootstrap instance") 413 } 414 if err := env.containerManager.DestroyContainer(id); err != nil { 415 return err 416 } 417 } 418 return nil 419 } 420 421 // Instances is specified in the Environ interface. 422 func (env *localEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) { 423 if len(ids) == 0 { 424 return nil, nil 425 } 426 insts, err := env.AllInstances() 427 if err != nil { 428 return nil, err 429 } 430 allInstances := make(map[instance.Id]instance.Instance) 431 for _, inst := range insts { 432 allInstances[inst.Id()] = inst 433 } 434 var found int 435 insts = make([]instance.Instance, len(ids)) 436 for i, id := range ids { 437 if inst, ok := allInstances[id]; ok { 438 insts[i] = inst 439 found++ 440 } 441 } 442 if found == 0 { 443 insts, err = nil, environs.ErrNoInstances 444 } else if found < len(ids) { 445 err = environs.ErrPartialInstances 446 } else { 447 err = nil 448 } 449 return insts, err 450 } 451 452 // AllInstances is specified in the InstanceBroker interface. 453 func (env *localEnviron) AllInstances() (instances []instance.Instance, err error) { 454 instances = append(instances, &localInstance{bootstrapInstanceId, env}) 455 // Add in all the containers as well. 456 lxcInstances, err := env.containerManager.ListContainers() 457 if err != nil { 458 return nil, err 459 } 460 for _, inst := range lxcInstances { 461 instances = append(instances, &localInstance{inst.Id(), env}) 462 } 463 return instances, nil 464 } 465 466 // Storage is specified in the Environ interface. 467 func (env *localEnviron) Storage() storage.Storage { 468 // localStorage is non-nil if we're running from the CLI 469 if env.localStorage != nil { 470 return env.localStorage 471 } 472 return httpstorage.Client(env.config.storageAddr()) 473 } 474 475 // Destroy is specified in the Environ interface. 476 func (env *localEnviron) Destroy() error { 477 // If bootstrap failed, for example because the user 478 // lacks sudo rights, then the agents won't have been 479 // installed. If that's the case, we can just remove 480 // the data-dir and exit. 481 agentsDir := filepath.Join(env.config.rootDir(), "agents") 482 if _, err := os.Stat(agentsDir); os.IsNotExist(err) { 483 // If we can't remove the root dir, then continue 484 // and attempt as root anyway. 485 if os.RemoveAll(env.config.rootDir()) == nil { 486 return nil 487 } 488 } 489 if !checkIfRoot() { 490 juju, err := exec.LookPath(os.Args[0]) 491 if err != nil { 492 return err 493 } 494 args := []string{ 495 "env", osenv.JujuHomeEnvKey + "=" + osenv.JujuHome(), 496 juju, "destroy-environment", "-y", "--force", env.Config().Name(), 497 } 498 cmd := exec.Command("sudo", args...) 499 cmd.Stdout = os.Stdout 500 cmd.Stderr = os.Stderr 501 return cmd.Run() 502 } 503 // Kill all running instances. This must be done as 504 // root, or listing/stopping containers will fail. 505 containers, err := env.containerManager.ListContainers() 506 if err != nil { 507 return err 508 } 509 for _, inst := range containers { 510 if err := env.containerManager.DestroyContainer(inst.Id()); err != nil { 511 return err 512 } 513 } 514 cmd := exec.Command( 515 "pkill", 516 fmt.Sprintf("-%d", terminationworker.TerminationSignal), 517 "-f", filepath.Join(regexp.QuoteMeta(env.config.rootDir()), ".*", "jujud"), 518 ) 519 if err := cmd.Run(); err != nil { 520 if err, ok := err.(*exec.ExitError); ok { 521 // Exit status 1 means no processes were matched: 522 // we don't consider this an error here. 523 if err.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() != 1 { 524 return errors.Annotate(err, "failed to kill jujud") 525 } 526 } 527 } 528 // Stop the mongo database and machine agent. We log any errors but 529 // do not fail, so that remaining "destroy" steps will still happen. 530 // 531 // We run through twice, since this races with the agent's teardown. 532 // We only log errors on the second time through, since if an error 533 // occurred, we sould expect it to be due to the service no longer 534 // existing. 535 for i := 0; i < 2; i++ { 536 err = mongoRemoveService(env.config.namespace()) 537 if err != nil && !errors.IsNotFound(err) && i > 0 { 538 logger.Errorf("while stopping mongod: %v", err) 539 } 540 svc, err := discoverService(env.machineAgentServiceName()) 541 if err == nil { 542 if err := svc.Stop(); err != nil && i > 0 { 543 logger.Errorf("while stopping machine agent: %v", err) 544 } 545 if err := svc.Remove(); err != nil && i > 0 { 546 logger.Errorf("while disabling machine agent: %v", err) 547 } 548 } 549 } 550 551 // Finally, remove the data-dir. 552 if err := os.RemoveAll(env.config.rootDir()); err != nil && !os.IsNotExist(err) { 553 // Before we return the error, just check to see if the directory is 554 // there. There is a race condition with the agent with the removing 555 // of the directory, and due to a bug 556 // (https://code.google.com/p/go/issues/detail?id=7776) the 557 // os.IsNotExist error isn't always returned. 558 if _, statErr := os.Stat(env.config.rootDir()); os.IsNotExist(statErr) { 559 return nil 560 } 561 return err 562 } 563 return nil 564 } 565 566 type agentService interface { 567 Stop() error 568 Remove() error 569 } 570 571 var mongoRemoveService = func(namespace string) error { 572 return mongo.RemoveService(namespace) 573 } 574 575 var discoverService = func(name string) (agentService, error) { 576 return service.DiscoverService(name, servicecommon.Conf{}) 577 } 578 579 // OpenPorts is specified in the Environ interface. 580 func (env *localEnviron) OpenPorts(ports []network.PortRange) error { 581 return fmt.Errorf("open ports not implemented") 582 } 583 584 // ClosePorts is specified in the Environ interface. 585 func (env *localEnviron) ClosePorts(ports []network.PortRange) error { 586 return fmt.Errorf("close ports not implemented") 587 } 588 589 // Ports is specified in the Environ interface. 590 func (env *localEnviron) Ports() ([]network.PortRange, error) { 591 return nil, nil 592 } 593 594 // Provider is specified in the Environ interface. 595 func (env *localEnviron) Provider() environs.EnvironProvider { 596 return providerInstance 597 }