github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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/featureflag"
    20  	"github.com/juju/utils/ssh"
    21  
    22  	"github.com/juju/juju/agent"
    23  	"github.com/juju/juju/cloudconfig/instancecfg"
    24  	"github.com/juju/juju/constraints"
    25  	"github.com/juju/juju/environs"
    26  	"github.com/juju/juju/environs/config"
    27  	"github.com/juju/juju/environs/manual"
    28  	"github.com/juju/juju/feature"
    29  	"github.com/juju/juju/instance"
    30  	"github.com/juju/juju/juju/names"
    31  	"github.com/juju/juju/mongo"
    32  	"github.com/juju/juju/network"
    33  	"github.com/juju/juju/provider/common"
    34  	"github.com/juju/juju/worker/terminationworker"
    35  )
    36  
    37  const (
    38  	// BootstrapInstanceId is the instance ID used
    39  	// for the manual provider's bootstrap instance.
    40  	BootstrapInstanceId instance.Id = "manual:"
    41  )
    42  
    43  var (
    44  	logger                 = loggo.GetLogger("juju.provider.manual")
    45  	manualCheckProvisioned = manual.CheckProvisioned
    46  )
    47  
    48  type manualEnviron struct {
    49  	host string
    50  	user string
    51  	mu   sync.Mutex
    52  	cfg  *environConfig
    53  	// hw and series are detected by running a script on the
    54  	// target machine. We cache these, as they should not change.
    55  	hw     *instance.HardwareCharacteristics
    56  	series string
    57  }
    58  
    59  var errNoStartInstance = errors.New("manual provider cannot start instances")
    60  var errNoStopInstance = errors.New("manual provider cannot stop instances")
    61  
    62  // MaintainInstance is specified in the InstanceBroker interface.
    63  func (*manualEnviron) MaintainInstance(args environs.StartInstanceParams) error {
    64  	return nil
    65  }
    66  
    67  func (*manualEnviron) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
    68  	return nil, errNoStartInstance
    69  }
    70  
    71  func (*manualEnviron) StopInstances(...instance.Id) error {
    72  	return errNoStopInstance
    73  }
    74  
    75  func (e *manualEnviron) AllInstances() ([]instance.Instance, error) {
    76  	return e.Instances([]instance.Id{BootstrapInstanceId})
    77  }
    78  
    79  func (e *manualEnviron) envConfig() (cfg *environConfig) {
    80  	e.mu.Lock()
    81  	cfg = e.cfg
    82  	e.mu.Unlock()
    83  	return cfg
    84  }
    85  
    86  func (e *manualEnviron) Config() *config.Config {
    87  	return e.envConfig().Config
    88  }
    89  
    90  // PrepareForBootstrap is part of the Environ interface.
    91  func (e *manualEnviron) PrepareForBootstrap(ctx environs.BootstrapContext) error {
    92  	if err := ensureBootstrapUbuntuUser(ctx, e.host, e.user, e.envConfig()); err != nil {
    93  		return err
    94  	}
    95  	return nil
    96  }
    97  
    98  // Create is part of the Environ interface.
    99  func (e *manualEnviron) Create(environs.CreateParams) error {
   100  	return nil
   101  }
   102  
   103  // Bootstrap is part of the Environ interface.
   104  func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) {
   105  	provisioned, err := manualCheckProvisioned(e.host)
   106  	if err != nil {
   107  		return nil, errors.Annotate(err, "failed to check provisioned status")
   108  	}
   109  	if provisioned {
   110  		return nil, manual.ErrProvisioned
   111  	}
   112  	hw, series, err := e.seriesAndHardwareCharacteristics()
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig, _ environs.BootstrapDialOpts) error {
   117  		icfg.Bootstrap.BootstrapMachineInstanceId = BootstrapInstanceId
   118  		icfg.Bootstrap.BootstrapMachineHardwareCharacteristics = hw
   119  		if err := instancecfg.FinishInstanceConfig(icfg, e.Config()); err != nil {
   120  			return err
   121  		}
   122  		return common.ConfigureMachine(ctx, ssh.DefaultClient, e.host, icfg)
   123  	}
   124  
   125  	result := &environs.BootstrapResult{
   126  		Arch:     *hw.Arch,
   127  		Series:   series,
   128  		Finalize: finalize,
   129  	}
   130  	return result, nil
   131  }
   132  
   133  // ControllerInstances is specified in the Environ interface.
   134  func (e *manualEnviron) ControllerInstances(controllerUUID string) ([]instance.Id, error) {
   135  	if !isRunningController() {
   136  		// Not running inside the controller, so we must
   137  		// verify the host.
   138  		if err := e.verifyBootstrapHost(); err != nil {
   139  			return nil, err
   140  		}
   141  	}
   142  	return []instance.Id{BootstrapInstanceId}, nil
   143  }
   144  
   145  func (e *manualEnviron) verifyBootstrapHost() error {
   146  	// First verify that the environment is bootstrapped by checking
   147  	// if the agents directory exists. Note that we cannot test the
   148  	// root data directory, as that is created in the process of
   149  	// initialising sshstorage.
   150  	agentsDir := path.Join(agent.DefaultPaths.DataDir, "agents")
   151  	const noAgentDir = "no-agent-dir"
   152  	stdin := fmt.Sprintf(
   153  		"test -d %s || echo %s",
   154  		utils.ShQuote(agentsDir),
   155  		noAgentDir,
   156  	)
   157  	out, _, err := runSSHCommand(
   158  		"ubuntu@"+e.host,
   159  		[]string{"/bin/bash"},
   160  		stdin,
   161  	)
   162  	if err != nil {
   163  		return err
   164  	}
   165  	if out = strings.TrimSpace(out); len(out) > 0 {
   166  		if out == noAgentDir {
   167  			return environs.ErrNotBootstrapped
   168  		}
   169  		err := errors.Errorf("unexpected output: %q", out)
   170  		logger.Infof(err.Error())
   171  		return err
   172  	}
   173  	return nil
   174  }
   175  
   176  func (e *manualEnviron) SetConfig(cfg *config.Config) error {
   177  	e.mu.Lock()
   178  	defer e.mu.Unlock()
   179  	_, err := manualProvider{}.validate(cfg, e.cfg.Config)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	e.cfg = newModelConfig(cfg, cfg.UnknownAttrs())
   184  	return nil
   185  }
   186  
   187  // Implements environs.Environ.
   188  //
   189  // This method will only ever return an Instance for the Id
   190  // BootstrapInstanceId. If any others are specified, then
   191  // ErrPartialInstances or ErrNoInstances will result.
   192  func (e *manualEnviron) Instances(ids []instance.Id) (instances []instance.Instance, err error) {
   193  	instances = make([]instance.Instance, len(ids))
   194  	var found bool
   195  	for i, id := range ids {
   196  		if id == BootstrapInstanceId {
   197  			instances[i] = manualBootstrapInstance{e.host}
   198  			found = true
   199  		} else {
   200  			err = environs.ErrPartialInstances
   201  		}
   202  	}
   203  	if !found {
   204  		err = environs.ErrNoInstances
   205  	}
   206  	return instances, err
   207  }
   208  
   209  var runSSHCommand = func(host string, command []string, stdin string) (stdout, stderr string, err error) {
   210  	cmd := ssh.Command(host, command, nil)
   211  	cmd.Stdin = strings.NewReader(stdin)
   212  	var stdoutBuf, stderrBuf bytes.Buffer
   213  	cmd.Stdout = &stdoutBuf
   214  	cmd.Stderr = &stderrBuf
   215  	if err := cmd.Run(); err != nil {
   216  		if stderr := strings.TrimSpace(stderrBuf.String()); len(stderr) > 0 {
   217  			err = errors.Annotate(err, stderr)
   218  		}
   219  		return "", "", err
   220  	}
   221  	return stdoutBuf.String(), stderrBuf.String(), nil
   222  }
   223  
   224  // Destroy implements the Environ interface.
   225  func (e *manualEnviron) Destroy() error {
   226  	// There is nothing we can do for manual environments,
   227  	// except when destroying the controller as a whole
   228  	// (see DestroyController below).
   229  	return nil
   230  }
   231  
   232  // DestroyController implements the Environ interface.
   233  func (e *manualEnviron) DestroyController(controllerUUID string) error {
   234  	script := `
   235  set -x
   236  touch %s
   237  # If jujud is running, we then wait for a while for it to stop.
   238  stopped=0
   239  if pkill -%d jujud; then
   240      for i in ` + "`seq 1 30`" + `; do
   241          if pgrep jujud > /dev/null ; then
   242              sleep 1
   243          else
   244              echo "jujud stopped"
   245              stopped=1
   246              break
   247          fi
   248      done
   249  fi
   250  if [ $stopped -ne 1 ]; then
   251      # If jujud didn't stop nicely, we kill it hard here.
   252      %spkill -9 jujud
   253      service %s stop
   254  fi
   255  rm -f /etc/init/juju*
   256  rm -f /etc/systemd/system/juju*
   257  rm -fr %s %s
   258  exit 0
   259  `
   260  	var diagnostics string
   261  	if featureflag.Enabled(feature.DeveloperMode) {
   262  		diagnostics = `
   263      echo "Dump engine report and goroutines for stuck jujud"
   264      source /etc/profile.d/juju-introspection.sh
   265      juju-engine-report
   266      juju-goroutines
   267  `
   268  	}
   269  	script = fmt.Sprintf(
   270  		script,
   271  		// WARNING: this is linked with the use of uninstallFile in
   272  		// the agent package. Don't change it without extreme care,
   273  		// and handling for mismatches with already-deployed agents.
   274  		utils.ShQuote(path.Join(
   275  			agent.DefaultPaths.DataDir,
   276  			agent.UninstallFile,
   277  		)),
   278  		terminationworker.TerminationSignal,
   279  		diagnostics,
   280  		mongo.ServiceName,
   281  		utils.ShQuote(agent.DefaultPaths.DataDir),
   282  		utils.ShQuote(agent.DefaultPaths.LogDir),
   283  	)
   284  	logger.Tracef("destroy controller script: %s", script)
   285  	stdout, stderr, err := runSSHCommand(
   286  		"ubuntu@"+e.host,
   287  		[]string{"sudo", "/bin/bash"}, script,
   288  	)
   289  	logger.Debugf("script stdout: \n%s", stdout)
   290  	logger.Debugf("script stderr: \n%s", stderr)
   291  	return err
   292  }
   293  
   294  func (*manualEnviron) PrecheckInstance(series string, _ constraints.Value, placement string) error {
   295  	return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`)
   296  }
   297  
   298  var unsupportedConstraints = []string{
   299  	constraints.CpuPower,
   300  	constraints.InstanceType,
   301  	constraints.Tags,
   302  	constraints.VirtType,
   303  }
   304  
   305  // ConstraintsValidator is defined on the Environs interface.
   306  func (e *manualEnviron) ConstraintsValidator() (constraints.Validator, error) {
   307  	validator := constraints.NewValidator()
   308  	validator.RegisterUnsupported(unsupportedConstraints)
   309  	if isRunningController() {
   310  		validator.UpdateVocabulary(constraints.Arch, []string{arch.HostArch()})
   311  	} else {
   312  		// We're running outside of the Juju controller, so we must
   313  		// SSH to the machine and detect its architecture.
   314  		hw, _, err := e.seriesAndHardwareCharacteristics()
   315  		if err != nil {
   316  			return nil, errors.Trace(err)
   317  		}
   318  		validator.UpdateVocabulary(constraints.Arch, []string{*hw.Arch})
   319  	}
   320  	return validator, nil
   321  }
   322  
   323  func (e *manualEnviron) seriesAndHardwareCharacteristics() (_ *instance.HardwareCharacteristics, series string, _ error) {
   324  	e.mu.Lock()
   325  	defer e.mu.Unlock()
   326  	if e.hw != nil {
   327  		return e.hw, e.series, nil
   328  	}
   329  	hw, series, err := manual.DetectSeriesAndHardwareCharacteristics(e.host)
   330  	if err != nil {
   331  		return nil, "", errors.Trace(err)
   332  	}
   333  	e.hw, e.series = &hw, series
   334  	return e.hw, e.series, nil
   335  }
   336  
   337  func (e *manualEnviron) OpenPorts(ports []network.PortRange) error {
   338  	return nil
   339  }
   340  
   341  func (e *manualEnviron) ClosePorts(ports []network.PortRange) error {
   342  	return nil
   343  }
   344  
   345  func (e *manualEnviron) Ports() ([]network.PortRange, error) {
   346  	return nil, nil
   347  }
   348  
   349  func (*manualEnviron) Provider() environs.EnvironProvider {
   350  	return manualProvider{}
   351  }
   352  
   353  func isRunningController() bool {
   354  	return filepath.Base(os.Args[0]) == names.Jujud
   355  }