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