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  }