github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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/agent/mongo"
    20  	"github.com/juju/juju/constraints"
    21  	"github.com/juju/juju/environs"
    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/network"
    26  	"github.com/juju/juju/environs/simplestreams"
    27  	"github.com/juju/juju/environs/sshstorage"
    28  	"github.com/juju/juju/environs/storage"
    29  	envtools "github.com/juju/juju/environs/tools"
    30  	"github.com/juju/juju/instance"
    31  	"github.com/juju/juju/juju/arch"
    32  	"github.com/juju/juju/provider/common"
    33  	"github.com/juju/juju/state"
    34  	"github.com/juju/juju/state/api"
    35  	"github.com/juju/juju/utils/ssh"
    36  	"github.com/juju/juju/worker/localstorage"
    37  	"github.com/juju/juju/worker/terminationworker"
    38  )
    39  
    40  const (
    41  	// storageSubdir is the subdirectory of
    42  	// dataDir in which storage will be located.
    43  	storageSubdir = "storage"
    44  
    45  	// storageTmpSubdir is the subdirectory of
    46  	// dataDir in which temporary storage will
    47  	// be located.
    48  	storageTmpSubdir = "storage-tmp"
    49  )
    50  
    51  var logger = loggo.GetLogger("juju.provider.manual")
    52  
    53  type manualEnviron struct {
    54  	common.SupportsUnitPlacementPolicy
    55  
    56  	cfg                 *environConfig
    57  	cfgmutex            sync.Mutex
    58  	storage             storage.Storage
    59  	ubuntuUserInited    bool
    60  	ubuntuUserInitMutex sync.Mutex
    61  }
    62  
    63  var _ envtools.SupportsCustomSources = (*manualEnviron)(nil)
    64  
    65  var errNoStartInstance = errors.New("manual provider cannot start instances")
    66  var errNoStopInstance = errors.New("manual provider cannot stop instances")
    67  
    68  func (*manualEnviron) StartInstance(args environs.StartInstanceParams) (instance.Instance, *instance.HardwareCharacteristics, []network.Info, error) {
    69  	return nil, nil, nil, errNoStartInstance
    70  }
    71  
    72  func (*manualEnviron) StopInstances(...instance.Id) error {
    73  	return errNoStopInstance
    74  }
    75  
    76  func (e *manualEnviron) AllInstances() ([]instance.Instance, error) {
    77  	return e.Instances([]instance.Id{manual.BootstrapInstanceId})
    78  }
    79  
    80  func (e *manualEnviron) envConfig() (cfg *environConfig) {
    81  	e.cfgmutex.Lock()
    82  	cfg = e.cfg
    83  	e.cfgmutex.Unlock()
    84  	return cfg
    85  }
    86  
    87  func (e *manualEnviron) Config() *config.Config {
    88  	return e.envConfig().Config
    89  }
    90  
    91  func (e *manualEnviron) Name() string {
    92  	return e.envConfig().Name()
    93  }
    94  
    95  // SupportedArchitectures is specified on the EnvironCapability interface.
    96  func (e *manualEnviron) SupportedArchitectures() ([]string, error) {
    97  	return arch.AllSupportedArches, nil
    98  }
    99  
   100  // SupportNetworks is specified on the EnvironCapability interface.
   101  func (e *manualEnviron) SupportNetworks() bool {
   102  	return false
   103  }
   104  
   105  func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) error {
   106  	// Set "use-sshstorage" to false, so agents know not to use sshstorage.
   107  	cfg, err := e.Config().Apply(map[string]interface{}{"use-sshstorage": false})
   108  	if err != nil {
   109  		return err
   110  	}
   111  	if err := e.SetConfig(cfg); err != nil {
   112  		return err
   113  	}
   114  	envConfig := e.envConfig()
   115  	// TODO(axw) consider how we can use placement to override bootstrap-host.
   116  	host := envConfig.bootstrapHost()
   117  	hc, series, err := manual.DetectSeriesAndHardwareCharacteristics(host)
   118  	if err != nil {
   119  		return err
   120  	}
   121  	selectedTools, err := common.EnsureBootstrapTools(ctx, e, series, hc.Arch)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	return manual.Bootstrap(manual.BootstrapArgs{
   126  		Context:                 ctx,
   127  		Host:                    host,
   128  		DataDir:                 agent.DefaultDataDir,
   129  		Environ:                 e,
   130  		PossibleTools:           selectedTools,
   131  		Series:                  series,
   132  		HardwareCharacteristics: &hc,
   133  	})
   134  }
   135  
   136  func (e *manualEnviron) StateInfo() (*state.Info, *api.Info, error) {
   137  	return common.StateInfo(e)
   138  }
   139  
   140  func (e *manualEnviron) SetConfig(cfg *config.Config) error {
   141  	e.cfgmutex.Lock()
   142  	defer e.cfgmutex.Unlock()
   143  	envConfig, err := manualProvider{}.validate(cfg, e.cfg.Config)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	// Set storage. If "use-sshstorage" is true then use the SSH storage.
   148  	// Otherwise, use HTTP storage.
   149  	//
   150  	// We don't change storage once it's been set. Storage parameters
   151  	// are fixed at bootstrap time, and it is not possible to change
   152  	// them.
   153  	if e.storage == nil {
   154  		var stor storage.Storage
   155  		if envConfig.useSSHStorage() {
   156  			storageDir := e.StorageDir()
   157  			storageTmpdir := path.Join(agent.DefaultDataDir, storageTmpSubdir)
   158  			stor, err = newSSHStorage("ubuntu@"+e.cfg.bootstrapHost(), storageDir, storageTmpdir)
   159  			if err != nil {
   160  				return fmt.Errorf("initialising SSH storage failed: %v", err)
   161  			}
   162  		} else {
   163  			caCertPEM, ok := envConfig.CACert()
   164  			if !ok {
   165  				// should not be possible to validate base config
   166  				return fmt.Errorf("ca-cert not set")
   167  			}
   168  			authkey := envConfig.storageAuthKey()
   169  			stor, err = httpstorage.ClientTLS(envConfig.storageAddr(), caCertPEM, authkey)
   170  			if err != nil {
   171  				return fmt.Errorf("initialising HTTPS storage failed: %v", err)
   172  			}
   173  		}
   174  		e.storage = stor
   175  	}
   176  	e.cfg = envConfig
   177  	return nil
   178  }
   179  
   180  // Implements environs.Environ.
   181  //
   182  // This method will only ever return an Instance for the Id
   183  // environ/manual.BootstrapInstanceId. If any others are
   184  // specified, then ErrPartialInstances or ErrNoInstances
   185  // will result.
   186  func (e *manualEnviron) Instances(ids []instance.Id) (instances []instance.Instance, err error) {
   187  	instances = make([]instance.Instance, len(ids))
   188  	var found bool
   189  	for i, id := range ids {
   190  		if id == manual.BootstrapInstanceId {
   191  			instances[i] = manualBootstrapInstance{e.envConfig().bootstrapHost()}
   192  			found = true
   193  		} else {
   194  			err = environs.ErrPartialInstances
   195  		}
   196  	}
   197  	if !found {
   198  		err = environs.ErrNoInstances
   199  	}
   200  	return instances, err
   201  }
   202  
   203  // AllocateAddress requests a new address to be allocated for the
   204  // given instance on the given network. This is not supported on the
   205  // manual provider.
   206  func (*manualEnviron) AllocateAddress(_ instance.Id, _ network.Id) (instance.Address, error) {
   207  	return instance.Address{}, errors.NotSupportedf("AllocateAddress")
   208  }
   209  
   210  var newSSHStorage = func(sshHost, storageDir, storageTmpdir string) (storage.Storage, error) {
   211  	logger.Debugf("using ssh storage at host %q dir %q", sshHost, storageDir)
   212  	return sshstorage.NewSSHStorage(sshstorage.NewSSHStorageParams{
   213  		Host:       sshHost,
   214  		StorageDir: storageDir,
   215  		TmpDir:     storageTmpdir,
   216  	})
   217  }
   218  
   219  // GetToolsSources returns a list of sources which are
   220  // used to search for simplestreams tools metadata.
   221  func (e *manualEnviron) GetToolsSources() ([]simplestreams.DataSource, error) {
   222  	// Add the simplestreams source off private storage.
   223  	return []simplestreams.DataSource{
   224  		storage.NewStorageSimpleStreamsDataSource("cloud storage", e.Storage(), storage.BaseToolsPath),
   225  	}, nil
   226  }
   227  
   228  func (e *manualEnviron) Storage() storage.Storage {
   229  	e.cfgmutex.Lock()
   230  	defer e.cfgmutex.Unlock()
   231  	return e.storage
   232  }
   233  
   234  var runSSHCommand = func(host string, command []string, stdin string) (stderr string, err error) {
   235  	cmd := ssh.Command(host, command, nil)
   236  	var stderrBuf bytes.Buffer
   237  	cmd.Stdin = strings.NewReader(stdin)
   238  	cmd.Stderr = &stderrBuf
   239  	err = cmd.Run()
   240  	return stderrBuf.String(), err
   241  }
   242  
   243  func (e *manualEnviron) Destroy() error {
   244  	script := `
   245  set -x
   246  pkill -%d jujud && exit
   247  stop %s
   248  rm -f /etc/init/juju*
   249  rm -f /etc/rsyslog.d/*juju*
   250  rm -fr %s %s
   251  exit 0
   252  `
   253  	script = fmt.Sprintf(
   254  		script,
   255  		terminationworker.TerminationSignal,
   256  		mongo.ServiceName(""),
   257  		utils.ShQuote(agent.DefaultDataDir),
   258  		utils.ShQuote(agent.DefaultLogDir),
   259  	)
   260  	stderr, err := runSSHCommand(
   261  		"ubuntu@"+e.envConfig().bootstrapHost(),
   262  		[]string{"sudo", "/bin/bash"}, script,
   263  	)
   264  	if err != nil {
   265  		if stderr := strings.TrimSpace(stderr); len(stderr) > 0 {
   266  			err = fmt.Errorf("%v (%v)", err, stderr)
   267  		}
   268  	}
   269  	return err
   270  }
   271  
   272  func (*manualEnviron) PrecheckInstance(series string, _ constraints.Value, placement string) error {
   273  	return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`)
   274  }
   275  
   276  var unsupportedConstraints = []string{
   277  	constraints.CpuPower,
   278  	constraints.InstanceType,
   279  	constraints.Tags,
   280  }
   281  
   282  // ConstraintsValidator is defined on the Environs interface.
   283  func (e *manualEnviron) ConstraintsValidator() (constraints.Validator, error) {
   284  	validator := constraints.NewValidator()
   285  	validator.RegisterUnsupported(unsupportedConstraints)
   286  	return validator, nil
   287  }
   288  
   289  func (e *manualEnviron) OpenPorts(ports []instance.Port) error {
   290  	return nil
   291  }
   292  
   293  func (e *manualEnviron) ClosePorts(ports []instance.Port) error {
   294  	return nil
   295  }
   296  
   297  func (e *manualEnviron) Ports() ([]instance.Port, error) {
   298  	return []instance.Port{}, nil
   299  }
   300  
   301  func (*manualEnviron) Provider() environs.EnvironProvider {
   302  	return manualProvider{}
   303  }
   304  
   305  func (e *manualEnviron) StorageAddr() string {
   306  	return e.envConfig().storageListenAddr()
   307  }
   308  
   309  func (e *manualEnviron) StorageDir() string {
   310  	return path.Join(agent.DefaultDataDir, storageSubdir)
   311  }
   312  
   313  func (e *manualEnviron) SharedStorageAddr() string {
   314  	return ""
   315  }
   316  
   317  func (e *manualEnviron) SharedStorageDir() string {
   318  	return ""
   319  }
   320  
   321  func (e *manualEnviron) StorageCACert() string {
   322  	if cert, ok := e.envConfig().CACert(); ok {
   323  		return cert
   324  	}
   325  	return ""
   326  }
   327  
   328  func (e *manualEnviron) StorageCAKey() string {
   329  	if key, ok := e.envConfig().CAPrivateKey(); ok {
   330  		return key
   331  	}
   332  	return ""
   333  }
   334  
   335  func (e *manualEnviron) StorageHostnames() []string {
   336  	cfg := e.envConfig()
   337  	hostnames := []string{cfg.bootstrapHost()}
   338  	if ip := net.ParseIP(cfg.storageListenIPAddress()); ip != nil {
   339  		if !ip.IsUnspecified() {
   340  			hostnames = append(hostnames, ip.String())
   341  		}
   342  	}
   343  	return hostnames
   344  }
   345  
   346  func (e *manualEnviron) StorageAuthKey() string {
   347  	return e.envConfig().storageAuthKey()
   348  }
   349  
   350  var _ localstorage.LocalTLSStorageConfig = (*manualEnviron)(nil)