github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/manual/environ.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package manual
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/loggo"
    17  	"github.com/juju/utils"
    18  	"github.com/juju/utils/arch"
    19  	"github.com/juju/utils/ssh"
    20  
    21  	"github.com/juju/juju/agent"
    22  	"github.com/juju/juju/cloudconfig/instancecfg"
    23  	"github.com/juju/juju/constraints"
    24  	"github.com/juju/juju/environs"
    25  	"github.com/juju/juju/environs/config"
    26  	"github.com/juju/juju/environs/manual"
    27  	"github.com/juju/juju/instance"
    28  	"github.com/juju/juju/juju/names"
    29  	"github.com/juju/juju/mongo"
    30  	"github.com/juju/juju/network"
    31  	"github.com/juju/juju/provider/common"
    32  	"github.com/juju/juju/worker/terminationworker"
    33  )
    34  
    35  const (
    36  	// BootstrapInstanceId is the instance ID used
    37  	// for the manual provider's bootstrap instance.
    38  	BootstrapInstanceId instance.Id = "manual:"
    39  )
    40  
    41  var (
    42  	logger                                       = loggo.GetLogger("juju.provider.manual")
    43  	manualCheckProvisioned                       = manual.CheckProvisioned
    44  	manualDetectSeriesAndHardwareCharacteristics = manual.DetectSeriesAndHardwareCharacteristics
    45  )
    46  
    47  type manualEnviron struct {
    48  	common.SupportsUnitPlacementPolicy
    49  
    50  	cfg                 *environConfig
    51  	cfgmutex            sync.Mutex
    52  	ubuntuUserInited    bool
    53  	ubuntuUserInitMutex sync.Mutex
    54  }
    55  
    56  var errNoStartInstance = errors.New("manual provider cannot start instances")
    57  var errNoStopInstance = errors.New("manual provider cannot stop instances")
    58  
    59  // MaintainInstance is specified in the InstanceBroker interface.
    60  func (*manualEnviron) MaintainInstance(args environs.StartInstanceParams) error {
    61  	return nil
    62  }
    63  
    64  func (*manualEnviron) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
    65  	return nil, errNoStartInstance
    66  }
    67  
    68  func (*manualEnviron) StopInstances(...instance.Id) error {
    69  	return errNoStopInstance
    70  }
    71  
    72  func (e *manualEnviron) AllInstances() ([]instance.Instance, error) {
    73  	return e.Instances([]instance.Id{BootstrapInstanceId})
    74  }
    75  
    76  func (e *manualEnviron) envConfig() (cfg *environConfig) {
    77  	e.cfgmutex.Lock()
    78  	cfg = e.cfg
    79  	e.cfgmutex.Unlock()
    80  	return cfg
    81  }
    82  
    83  func (e *manualEnviron) Config() *config.Config {
    84  	return e.envConfig().Config
    85  }
    86  
    87  // SupportedArchitectures is specified on the EnvironCapability interface.
    88  func (e *manualEnviron) SupportedArchitectures() ([]string, error) {
    89  	return arch.AllSupportedArches, nil
    90  }
    91  
    92  // Bootstrap is specified on the Environ interface.
    93  func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) {
    94  	envConfig := e.envConfig()
    95  	host := envConfig.bootstrapHost()
    96  	provisioned, err := manualCheckProvisioned(host)
    97  	if err != nil {
    98  		return nil, errors.Annotate(err, "failed to check provisioned status")
    99  	}
   100  	if provisioned {
   101  		return nil, manual.ErrProvisioned
   102  	}
   103  	hc, series, err := manualDetectSeriesAndHardwareCharacteristics(host)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig) error {
   108  		icfg.InstanceId = BootstrapInstanceId
   109  		icfg.HardwareCharacteristics = &hc
   110  		if err := instancecfg.FinishInstanceConfig(icfg, e.Config()); err != nil {
   111  			return err
   112  		}
   113  		return common.ConfigureMachine(ctx, ssh.DefaultClient, host, icfg)
   114  	}
   115  
   116  	result := &environs.BootstrapResult{
   117  		Arch:     *hc.Arch,
   118  		Series:   series,
   119  		Finalize: finalize,
   120  	}
   121  	return result, nil
   122  }
   123  
   124  // ControllerInstances is specified in the Environ interface.
   125  func (e *manualEnviron) ControllerInstances() ([]instance.Id, error) {
   126  	arg0 := filepath.Base(os.Args[0])
   127  	if arg0 != names.Jujud {
   128  		// Not running inside the controller, so we must
   129  		// verify the host.
   130  		if err := e.verifyBootstrapHost(); err != nil {
   131  			return nil, err
   132  		}
   133  	}
   134  	return []instance.Id{BootstrapInstanceId}, nil
   135  }
   136  
   137  func (e *manualEnviron) verifyBootstrapHost() error {
   138  	// First verify that the environment is bootstrapped by checking
   139  	// if the agents directory exists. Note that we cannot test the
   140  	// root data directory, as that is created in the process of
   141  	// initialising sshstorage.
   142  	agentsDir := path.Join(agent.DefaultPaths.DataDir, "agents")
   143  	const noAgentDir = "no-agent-dir"
   144  	stdin := fmt.Sprintf(
   145  		"test -d %s || echo %s",
   146  		utils.ShQuote(agentsDir),
   147  		noAgentDir,
   148  	)
   149  	out, err := runSSHCommand(
   150  		"ubuntu@"+e.cfg.bootstrapHost(),
   151  		[]string{"/bin/bash"},
   152  		stdin,
   153  	)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	if out = strings.TrimSpace(out); len(out) > 0 {
   158  		if out == noAgentDir {
   159  			return environs.ErrNotBootstrapped
   160  		}
   161  		err := errors.Errorf("unexpected output: %q", out)
   162  		logger.Infof(err.Error())
   163  		return err
   164  	}
   165  	return nil
   166  }
   167  
   168  func (e *manualEnviron) SetConfig(cfg *config.Config) error {
   169  	e.cfgmutex.Lock()
   170  	defer e.cfgmutex.Unlock()
   171  	_, err := manualProvider{}.validate(cfg, e.cfg.Config)
   172  	if err != nil {
   173  		return err
   174  	}
   175  	e.cfg = newModelConfig(cfg, cfg.UnknownAttrs())
   176  	return nil
   177  }
   178  
   179  // Implements environs.Environ.
   180  //
   181  // This method will only ever return an Instance for the Id
   182  // BootstrapInstanceId. If any others are specified, then
   183  // ErrPartialInstances or ErrNoInstances will result.
   184  func (e *manualEnviron) Instances(ids []instance.Id) (instances []instance.Instance, err error) {
   185  	instances = make([]instance.Instance, len(ids))
   186  	var found bool
   187  	for i, id := range ids {
   188  		if id == BootstrapInstanceId {
   189  			instances[i] = manualBootstrapInstance{e.envConfig().bootstrapHost()}
   190  			found = true
   191  		} else {
   192  			err = environs.ErrPartialInstances
   193  		}
   194  	}
   195  	if !found {
   196  		err = environs.ErrNoInstances
   197  	}
   198  	return instances, err
   199  }
   200  
   201  var runSSHCommand = func(host string, command []string, stdin string) (stdout string, err error) {
   202  	cmd := ssh.Command(host, command, nil)
   203  	cmd.Stdin = strings.NewReader(stdin)
   204  	var stdoutBuf, stderrBuf bytes.Buffer
   205  	cmd.Stdout = &stdoutBuf
   206  	cmd.Stderr = &stderrBuf
   207  	if err := cmd.Run(); err != nil {
   208  		if stderr := strings.TrimSpace(stderrBuf.String()); len(stderr) > 0 {
   209  			err = errors.Annotate(err, stderr)
   210  		}
   211  		return "", err
   212  	}
   213  	return stdoutBuf.String(), nil
   214  }
   215  
   216  func (e *manualEnviron) Destroy() error {
   217  	script := `
   218  set -x
   219  touch %s
   220  pkill -%d jujud && exit
   221  stop %s
   222  rm -f /etc/init/juju*
   223  rm -fr %s %s
   224  exit 0
   225  `
   226  	script = fmt.Sprintf(
   227  		script,
   228  		// WARNING: this is linked with the use of uninstallFile in
   229  		// the agent package. Don't change it without extreme care,
   230  		// and handling for mismatches with already-deployed agents.
   231  		utils.ShQuote(path.Join(
   232  			agent.DefaultPaths.DataDir,
   233  			agent.UninstallFile,
   234  		)),
   235  		terminationworker.TerminationSignal,
   236  		mongo.ServiceName,
   237  		utils.ShQuote(agent.DefaultPaths.DataDir),
   238  		utils.ShQuote(agent.DefaultPaths.LogDir),
   239  	)
   240  	_, err := runSSHCommand(
   241  		"ubuntu@"+e.envConfig().bootstrapHost(),
   242  		[]string{"sudo", "/bin/bash"}, script,
   243  	)
   244  	return err
   245  }
   246  
   247  func (*manualEnviron) PrecheckInstance(series string, _ constraints.Value, placement string) error {
   248  	return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`)
   249  }
   250  
   251  var unsupportedConstraints = []string{
   252  	constraints.CpuPower,
   253  	constraints.InstanceType,
   254  	constraints.Tags,
   255  	constraints.VirtType,
   256  }
   257  
   258  // ConstraintsValidator is defined on the Environs interface.
   259  func (e *manualEnviron) ConstraintsValidator() (constraints.Validator, error) {
   260  	validator := constraints.NewValidator()
   261  	validator.RegisterUnsupported(unsupportedConstraints)
   262  	return validator, nil
   263  }
   264  
   265  func (e *manualEnviron) OpenPorts(ports []network.PortRange) error {
   266  	return nil
   267  }
   268  
   269  func (e *manualEnviron) ClosePorts(ports []network.PortRange) error {
   270  	return nil
   271  }
   272  
   273  func (e *manualEnviron) Ports() ([]network.PortRange, error) {
   274  	return nil, nil
   275  }
   276  
   277  func (*manualEnviron) Provider() environs.EnvironProvider {
   278  	return manualProvider{}
   279  }