github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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  	"net"
    10  	"path"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/loggo"
    16  	"github.com/juju/utils"
    17  	"github.com/juju/utils/arch"
    18  
    19  	"github.com/juju/juju/agent"
    20  	"github.com/juju/juju/cloudconfig/instancecfg"
    21  	"github.com/juju/juju/constraints"
    22  	"github.com/juju/juju/environs"
    23  	"github.com/juju/juju/environs/config"
    24  	"github.com/juju/juju/environs/httpstorage"
    25  	"github.com/juju/juju/environs/manual"
    26  	"github.com/juju/juju/environs/sshstorage"
    27  	"github.com/juju/juju/environs/storage"
    28  	"github.com/juju/juju/instance"
    29  	"github.com/juju/juju/mongo"
    30  	"github.com/juju/juju/network"
    31  	"github.com/juju/juju/provider/common"
    32  	"github.com/juju/juju/utils/ssh"
    33  	"github.com/juju/juju/worker/localstorage"
    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  	// storageSubdir is the subdirectory of
    43  	// dataDir in which storage will be located.
    44  	storageSubdir = "storage"
    45  
    46  	// storageTmpSubdir is the subdirectory of
    47  	// dataDir in which temporary storage will
    48  	// be located.
    49  	storageTmpSubdir = "storage-tmp"
    50  )
    51  
    52  var (
    53  	logger                                       = loggo.GetLogger("juju.provider.manual")
    54  	manualCheckProvisioned                       = manual.CheckProvisioned
    55  	manualDetectSeriesAndHardwareCharacteristics = manual.DetectSeriesAndHardwareCharacteristics
    56  )
    57  
    58  type manualEnviron struct {
    59  	common.SupportsUnitPlacementPolicy
    60  
    61  	cfg                 *environConfig
    62  	cfgmutex            sync.Mutex
    63  	storage             storage.Storage
    64  	ubuntuUserInited    bool
    65  	ubuntuUserInitMutex sync.Mutex
    66  }
    67  
    68  var errNoStartInstance = errors.New("manual provider cannot start instances")
    69  var errNoStopInstance = errors.New("manual provider cannot stop instances")
    70  
    71  // MaintainInstance is specified in the InstanceBroker interface.
    72  func (*manualEnviron) MaintainInstance(args environs.StartInstanceParams) error {
    73  	return nil
    74  }
    75  
    76  func (*manualEnviron) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
    77  	return nil, errNoStartInstance
    78  }
    79  
    80  func (*manualEnviron) StopInstances(...instance.Id) error {
    81  	return errNoStopInstance
    82  }
    83  
    84  func (e *manualEnviron) AllInstances() ([]instance.Instance, error) {
    85  	return e.Instances([]instance.Id{BootstrapInstanceId})
    86  }
    87  
    88  func (e *manualEnviron) envConfig() (cfg *environConfig) {
    89  	e.cfgmutex.Lock()
    90  	cfg = e.cfg
    91  	e.cfgmutex.Unlock()
    92  	return cfg
    93  }
    94  
    95  func (e *manualEnviron) Config() *config.Config {
    96  	return e.envConfig().Config
    97  }
    98  
    99  // SupportedArchitectures is specified on the EnvironCapability interface.
   100  func (e *manualEnviron) SupportedArchitectures() ([]string, error) {
   101  	return arch.AllSupportedArches, nil
   102  }
   103  
   104  func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (arch, series string, _ environs.BootstrapFinalizer, _ error) {
   105  	// Set "use-sshstorage" to false, so agents know not to use sshstorage.
   106  	cfg, err := e.Config().Apply(map[string]interface{}{"use-sshstorage": false})
   107  	if err != nil {
   108  		return "", "", nil, err
   109  	}
   110  	if err := e.SetConfig(cfg); err != nil {
   111  		return "", "", nil, err
   112  	}
   113  	agentEnv, err := localstorage.StoreConfig(e)
   114  	if err != nil {
   115  		return "", "", nil, err
   116  	}
   117  	envConfig := e.envConfig()
   118  	// TODO(axw) consider how we can use placement to override bootstrap-host.
   119  	host := envConfig.bootstrapHost()
   120  	provisioned, err := manualCheckProvisioned(host)
   121  	if err != nil {
   122  		return "", "", nil, errors.Annotate(err, "failed to check provisioned status")
   123  	}
   124  	if provisioned {
   125  		return "", "", nil, manual.ErrProvisioned
   126  	}
   127  	hc, series, err := manualDetectSeriesAndHardwareCharacteristics(host)
   128  	if err != nil {
   129  		return "", "", nil, err
   130  	}
   131  	finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig) error {
   132  		icfg.InstanceId = BootstrapInstanceId
   133  		icfg.HardwareCharacteristics = &hc
   134  		if err := instancecfg.FinishInstanceConfig(icfg, e.Config()); err != nil {
   135  			return err
   136  		}
   137  		for k, v := range agentEnv {
   138  			icfg.AgentEnvironment[k] = v
   139  		}
   140  		return common.ConfigureMachine(ctx, ssh.DefaultClient, host, icfg)
   141  	}
   142  	return *hc.Arch, series, finalize, nil
   143  }
   144  
   145  // StateServerInstances is specified in the Environ interface.
   146  func (e *manualEnviron) StateServerInstances() ([]instance.Id, error) {
   147  	// If we're running from the bootstrap host, then
   148  	// useSSHStorage will be false; in that case, we
   149  	// do not need or want to verify the bootstrap host.
   150  	if e.envConfig().useSSHStorage() {
   151  		if err := e.verifyBootstrapHost(); err != nil {
   152  			return nil, err
   153  		}
   154  	}
   155  	return []instance.Id{BootstrapInstanceId}, nil
   156  }
   157  
   158  func (e *manualEnviron) verifyBootstrapHost() error {
   159  	// First verify that the environment is bootstrapped by checking
   160  	// if the agents directory exists. Note that we cannot test the
   161  	// root data directory, as that is created in the process of
   162  	// initialising sshstorage.
   163  	agentsDir := path.Join(agent.DefaultPaths.DataDir, "agents")
   164  	const noAgentDir = "no-agent-dir"
   165  	stdin := fmt.Sprintf(
   166  		"test -d %s || echo %s",
   167  		utils.ShQuote(agentsDir),
   168  		noAgentDir,
   169  	)
   170  	out, err := runSSHCommand(
   171  		"ubuntu@"+e.cfg.bootstrapHost(),
   172  		[]string{"/bin/bash"},
   173  		stdin,
   174  	)
   175  	if err != nil {
   176  		return err
   177  	}
   178  	if out = strings.TrimSpace(out); len(out) > 0 {
   179  		if out == noAgentDir {
   180  			return environs.ErrNotBootstrapped
   181  		}
   182  		err := errors.Errorf("unexpected output: %q", out)
   183  		logger.Infof(err.Error())
   184  		return err
   185  	}
   186  	return nil
   187  }
   188  
   189  func (e *manualEnviron) SetConfig(cfg *config.Config) error {
   190  	e.cfgmutex.Lock()
   191  	defer e.cfgmutex.Unlock()
   192  	_, err := manualProvider{}.validate(cfg, e.cfg.Config)
   193  	if err != nil {
   194  		return err
   195  	}
   196  	envConfig := newEnvironConfig(cfg, cfg.UnknownAttrs())
   197  	// Set storage. If "use-sshstorage" is true then use the SSH storage.
   198  	// Otherwise, use HTTP storage.
   199  	//
   200  	// We don't change storage once it's been set. Storage parameters
   201  	// are fixed at bootstrap time, and it is not possible to change
   202  	// them.
   203  	if e.storage == nil {
   204  		var stor storage.Storage
   205  		if envConfig.useSSHStorage() {
   206  			storageDir := e.StorageDir()
   207  			storageTmpdir := path.Join(agent.DefaultPaths.DataDir, storageTmpSubdir)
   208  			stor, err = newSSHStorage("ubuntu@"+e.cfg.bootstrapHost(), storageDir, storageTmpdir)
   209  			if err != nil {
   210  				return fmt.Errorf("initialising SSH storage failed: %v", err)
   211  			}
   212  		} else {
   213  			caCertPEM, ok := envConfig.CACert()
   214  			if !ok {
   215  				// should not be possible to validate base config
   216  				return fmt.Errorf("ca-cert not set")
   217  			}
   218  			authkey := envConfig.storageAuthKey()
   219  			stor, err = httpstorage.ClientTLS(envConfig.storageAddr(), caCertPEM, authkey)
   220  			if err != nil {
   221  				return fmt.Errorf("initialising HTTPS storage failed: %v", err)
   222  			}
   223  		}
   224  		e.storage = stor
   225  	}
   226  	e.cfg = envConfig
   227  	return nil
   228  }
   229  
   230  // Implements environs.Environ.
   231  //
   232  // This method will only ever return an Instance for the Id
   233  // BootstrapInstanceId. If any others are specified, then
   234  // ErrPartialInstances or ErrNoInstances will result.
   235  func (e *manualEnviron) Instances(ids []instance.Id) (instances []instance.Instance, err error) {
   236  	instances = make([]instance.Instance, len(ids))
   237  	var found bool
   238  	for i, id := range ids {
   239  		if id == BootstrapInstanceId {
   240  			instances[i] = manualBootstrapInstance{e.envConfig().bootstrapHost()}
   241  			found = true
   242  		} else {
   243  			err = environs.ErrPartialInstances
   244  		}
   245  	}
   246  	if !found {
   247  		err = environs.ErrNoInstances
   248  	}
   249  	return instances, err
   250  }
   251  
   252  var newSSHStorage = func(sshHost, storageDir, storageTmpdir string) (storage.Storage, error) {
   253  	logger.Debugf("using ssh storage at host %q dir %q", sshHost, storageDir)
   254  	return sshstorage.NewSSHStorage(sshstorage.NewSSHStorageParams{
   255  		Host:       sshHost,
   256  		StorageDir: storageDir,
   257  		TmpDir:     storageTmpdir,
   258  	})
   259  }
   260  
   261  func (e *manualEnviron) Storage() storage.Storage {
   262  	e.cfgmutex.Lock()
   263  	defer e.cfgmutex.Unlock()
   264  	return e.storage
   265  }
   266  
   267  var runSSHCommand = func(host string, command []string, stdin string) (stdout string, err error) {
   268  	cmd := ssh.Command(host, command, nil)
   269  	cmd.Stdin = strings.NewReader(stdin)
   270  	var stdoutBuf, stderrBuf bytes.Buffer
   271  	cmd.Stdout = &stdoutBuf
   272  	cmd.Stderr = &stderrBuf
   273  	if err := cmd.Run(); err != nil {
   274  		if stderr := strings.TrimSpace(stderrBuf.String()); len(stderr) > 0 {
   275  			err = errors.Annotate(err, stderr)
   276  		}
   277  		return "", err
   278  	}
   279  	return stdoutBuf.String(), nil
   280  }
   281  
   282  func (e *manualEnviron) Destroy() error {
   283  	script := `
   284  set -x
   285  pkill -%d jujud && exit
   286  stop %s
   287  rm -f /etc/init/juju*
   288  rm -f /etc/rsyslog.d/*juju*
   289  rm -fr %s %s
   290  exit 0
   291  `
   292  	script = fmt.Sprintf(
   293  		script,
   294  		terminationworker.TerminationSignal,
   295  		mongo.ServiceName(""),
   296  		utils.ShQuote(agent.DefaultPaths.DataDir),
   297  		utils.ShQuote(agent.DefaultPaths.LogDir),
   298  	)
   299  	_, err := runSSHCommand(
   300  		"ubuntu@"+e.envConfig().bootstrapHost(),
   301  		[]string{"sudo", "/bin/bash"}, script,
   302  	)
   303  	return err
   304  }
   305  
   306  func (*manualEnviron) PrecheckInstance(series string, _ constraints.Value, placement string) error {
   307  	return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`)
   308  }
   309  
   310  var unsupportedConstraints = []string{
   311  	constraints.CpuPower,
   312  	constraints.InstanceType,
   313  	constraints.Tags,
   314  }
   315  
   316  // ConstraintsValidator is defined on the Environs interface.
   317  func (e *manualEnviron) ConstraintsValidator() (constraints.Validator, error) {
   318  	validator := constraints.NewValidator()
   319  	validator.RegisterUnsupported(unsupportedConstraints)
   320  	return validator, nil
   321  }
   322  
   323  func (e *manualEnviron) OpenPorts(ports []network.PortRange) error {
   324  	return nil
   325  }
   326  
   327  func (e *manualEnviron) ClosePorts(ports []network.PortRange) error {
   328  	return nil
   329  }
   330  
   331  func (e *manualEnviron) Ports() ([]network.PortRange, error) {
   332  	return nil, nil
   333  }
   334  
   335  func (*manualEnviron) Provider() environs.EnvironProvider {
   336  	return manualProvider{}
   337  }
   338  
   339  func (e *manualEnviron) StorageAddr() string {
   340  	return e.envConfig().storageListenAddr()
   341  }
   342  
   343  func (e *manualEnviron) StorageDir() string {
   344  	return path.Join(agent.DefaultPaths.DataDir, storageSubdir)
   345  }
   346  
   347  func (e *manualEnviron) SharedStorageAddr() string {
   348  	return ""
   349  }
   350  
   351  func (e *manualEnviron) SharedStorageDir() string {
   352  	return ""
   353  }
   354  
   355  func (e *manualEnviron) StorageCACert() string {
   356  	if cert, ok := e.envConfig().CACert(); ok {
   357  		return cert
   358  	}
   359  	return ""
   360  }
   361  
   362  func (e *manualEnviron) StorageCAKey() string {
   363  	if key, ok := e.envConfig().CAPrivateKey(); ok {
   364  		return key
   365  	}
   366  	return ""
   367  }
   368  
   369  func (e *manualEnviron) StorageHostnames() []string {
   370  	cfg := e.envConfig()
   371  	hostnames := []string{cfg.bootstrapHost()}
   372  	if ip := net.ParseIP(cfg.storageListenIPAddress()); ip != nil {
   373  		if !ip.IsUnspecified() {
   374  			hostnames = append(hostnames, ip.String())
   375  		}
   376  	}
   377  	return hostnames
   378  }
   379  
   380  func (e *manualEnviron) StorageAuthKey() string {
   381  	return e.envConfig().storageAuthKey()
   382  }
   383  
   384  var _ localstorage.LocalTLSStorageConfig = (*manualEnviron)(nil)