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