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  }