github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/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/config"
    22  	"github.com/juju/juju/environs/httpstorage"
    23  	"github.com/juju/juju/environs/manual"
    24  	"github.com/juju/juju/environs/simplestreams"
    25  	"github.com/juju/juju/environs/sshstorage"
    26  	"github.com/juju/juju/environs/storage"
    27  	envtools "github.com/juju/juju/environs/tools"
    28  	"github.com/juju/juju/instance"
    29  	"github.com/juju/juju/juju/arch"
    30  	"github.com/juju/juju/mongo"
    31  	"github.com/juju/juju/network"
    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) (network.Address, error) {
   207  	return network.Address{}, errors.NotSupportedf("AllocateAddress")
   208  }
   209  
   210  // ListNetworks returns basic information about all networks known
   211  // by the provider for the environment. They may be unknown to juju
   212  // yet (i.e. when called initially or when a new network was created).
   213  // This is not implemented by the manual provider yet.
   214  func (*manualEnviron) ListNetworks() ([]network.BasicInfo, error) {
   215  	return nil, errors.NotImplementedf("ListNetworks")
   216  }
   217  
   218  var newSSHStorage = func(sshHost, storageDir, storageTmpdir string) (storage.Storage, error) {
   219  	logger.Debugf("using ssh storage at host %q dir %q", sshHost, storageDir)
   220  	return sshstorage.NewSSHStorage(sshstorage.NewSSHStorageParams{
   221  		Host:       sshHost,
   222  		StorageDir: storageDir,
   223  		TmpDir:     storageTmpdir,
   224  	})
   225  }
   226  
   227  // GetToolsSources returns a list of sources which are
   228  // used to search for simplestreams tools metadata.
   229  func (e *manualEnviron) GetToolsSources() ([]simplestreams.DataSource, error) {
   230  	// Add the simplestreams source off private storage.
   231  	return []simplestreams.DataSource{
   232  		storage.NewStorageSimpleStreamsDataSource("cloud storage", e.Storage(), storage.BaseToolsPath),
   233  	}, nil
   234  }
   235  
   236  func (e *manualEnviron) Storage() storage.Storage {
   237  	e.cfgmutex.Lock()
   238  	defer e.cfgmutex.Unlock()
   239  	return e.storage
   240  }
   241  
   242  var runSSHCommand = func(host string, command []string, stdin string) (stderr string, err error) {
   243  	cmd := ssh.Command(host, command, nil)
   244  	var stderrBuf bytes.Buffer
   245  	cmd.Stdin = strings.NewReader(stdin)
   246  	cmd.Stderr = &stderrBuf
   247  	err = cmd.Run()
   248  	return stderrBuf.String(), err
   249  }
   250  
   251  func (e *manualEnviron) Destroy() error {
   252  	script := `
   253  set -x
   254  pkill -%d jujud && exit
   255  stop %s
   256  rm -f /etc/init/juju*
   257  rm -f /etc/rsyslog.d/*juju*
   258  rm -fr %s %s
   259  exit 0
   260  `
   261  	script = fmt.Sprintf(
   262  		script,
   263  		terminationworker.TerminationSignal,
   264  		mongo.ServiceName(""),
   265  		utils.ShQuote(agent.DefaultDataDir),
   266  		utils.ShQuote(agent.DefaultLogDir),
   267  	)
   268  	stderr, err := runSSHCommand(
   269  		"ubuntu@"+e.envConfig().bootstrapHost(),
   270  		[]string{"sudo", "/bin/bash"}, script,
   271  	)
   272  	if err != nil {
   273  		if stderr := strings.TrimSpace(stderr); len(stderr) > 0 {
   274  			err = fmt.Errorf("%v (%v)", err, stderr)
   275  		}
   276  	}
   277  	return err
   278  }
   279  
   280  func (*manualEnviron) PrecheckInstance(series string, _ constraints.Value, placement string) error {
   281  	return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`)
   282  }
   283  
   284  var unsupportedConstraints = []string{
   285  	constraints.CpuPower,
   286  	constraints.InstanceType,
   287  	constraints.Tags,
   288  }
   289  
   290  // ConstraintsValidator is defined on the Environs interface.
   291  func (e *manualEnviron) ConstraintsValidator() (constraints.Validator, error) {
   292  	validator := constraints.NewValidator()
   293  	validator.RegisterUnsupported(unsupportedConstraints)
   294  	return validator, nil
   295  }
   296  
   297  func (e *manualEnviron) OpenPorts(ports []network.Port) error {
   298  	return nil
   299  }
   300  
   301  func (e *manualEnviron) ClosePorts(ports []network.Port) error {
   302  	return nil
   303  }
   304  
   305  func (e *manualEnviron) Ports() ([]network.Port, error) {
   306  	return []network.Port{}, nil
   307  }
   308  
   309  func (*manualEnviron) Provider() environs.EnvironProvider {
   310  	return manualProvider{}
   311  }
   312  
   313  func (e *manualEnviron) StorageAddr() string {
   314  	return e.envConfig().storageListenAddr()
   315  }
   316  
   317  func (e *manualEnviron) StorageDir() string {
   318  	return path.Join(agent.DefaultDataDir, storageSubdir)
   319  }
   320  
   321  func (e *manualEnviron) SharedStorageAddr() string {
   322  	return ""
   323  }
   324  
   325  func (e *manualEnviron) SharedStorageDir() string {
   326  	return ""
   327  }
   328  
   329  func (e *manualEnviron) StorageCACert() string {
   330  	if cert, ok := e.envConfig().CACert(); ok {
   331  		return cert
   332  	}
   333  	return ""
   334  }
   335  
   336  func (e *manualEnviron) StorageCAKey() string {
   337  	if key, ok := e.envConfig().CAPrivateKey(); ok {
   338  		return key
   339  	}
   340  	return ""
   341  }
   342  
   343  func (e *manualEnviron) StorageHostnames() []string {
   344  	cfg := e.envConfig()
   345  	hostnames := []string{cfg.bootstrapHost()}
   346  	if ip := net.ParseIP(cfg.storageListenIPAddress()); ip != nil {
   347  		if !ip.IsUnspecified() {
   348  			hostnames = append(hostnames, ip.String())
   349  		}
   350  	}
   351  	return hostnames
   352  }
   353  
   354  func (e *manualEnviron) StorageAuthKey() string {
   355  	return e.envConfig().storageAuthKey()
   356  }
   357  
   358  var _ localstorage.LocalTLSStorageConfig = (*manualEnviron)(nil)