github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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  	"errors"
     9  	"fmt"
    10  	"net"
    11  	"path"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/juju/loggo"
    16  
    17  	"launchpad.net/juju-core/constraints"
    18  	"launchpad.net/juju-core/environs"
    19  	"launchpad.net/juju-core/environs/cloudinit"
    20  	"launchpad.net/juju-core/environs/config"
    21  	"launchpad.net/juju-core/environs/httpstorage"
    22  	"launchpad.net/juju-core/environs/manual"
    23  	"launchpad.net/juju-core/environs/simplestreams"
    24  	"launchpad.net/juju-core/environs/sshstorage"
    25  	"launchpad.net/juju-core/environs/storage"
    26  	envtools "launchpad.net/juju-core/environs/tools"
    27  	"launchpad.net/juju-core/instance"
    28  	"launchpad.net/juju-core/provider/common"
    29  	"launchpad.net/juju-core/state"
    30  	"launchpad.net/juju-core/state/api"
    31  	"launchpad.net/juju-core/tools"
    32  	"launchpad.net/juju-core/utils/ssh"
    33  	"launchpad.net/juju-core/worker/localstorage"
    34  	"launchpad.net/juju-core/worker/terminationworker"
    35  )
    36  
    37  const (
    38  	// TODO(axw) make this configurable?
    39  	dataDir = "/var/lib/juju"
    40  
    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  	cfg                 *environConfig
    55  	cfgmutex            sync.Mutex
    56  	storage             storage.Storage
    57  	ubuntuUserInited    bool
    58  	ubuntuUserInitMutex sync.Mutex
    59  }
    60  
    61  var _ envtools.SupportsCustomSources = (*manualEnviron)(nil)
    62  
    63  var errNoStartInstance = errors.New("manual provider cannot start instances")
    64  var errNoStopInstance = errors.New("manual provider cannot stop instances")
    65  
    66  func (*manualEnviron) StartInstance(constraints.Value, tools.List, *cloudinit.MachineConfig) (instance.Instance, *instance.HardwareCharacteristics, error) {
    67  	return nil, nil, errNoStartInstance
    68  }
    69  
    70  func (*manualEnviron) StopInstances([]instance.Instance) error {
    71  	return errNoStopInstance
    72  }
    73  
    74  func (e *manualEnviron) AllInstances() ([]instance.Instance, error) {
    75  	return e.Instances([]instance.Id{manual.BootstrapInstanceId})
    76  }
    77  
    78  func (e *manualEnviron) envConfig() (cfg *environConfig) {
    79  	e.cfgmutex.Lock()
    80  	cfg = e.cfg
    81  	e.cfgmutex.Unlock()
    82  	return cfg
    83  }
    84  
    85  func (e *manualEnviron) Config() *config.Config {
    86  	return e.envConfig().Config
    87  }
    88  
    89  func (e *manualEnviron) Name() string {
    90  	return e.envConfig().Name()
    91  }
    92  
    93  func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, cons constraints.Value) error {
    94  	// Set "use-sshstorage" to false, so agents know not to use sshstorage.
    95  	cfg, err := e.Config().Apply(map[string]interface{}{"use-sshstorage": false})
    96  	if err != nil {
    97  		return err
    98  	}
    99  	if err := e.SetConfig(cfg); err != nil {
   100  		return err
   101  	}
   102  	envConfig := e.envConfig()
   103  	host := envConfig.bootstrapHost()
   104  	hc, series, err := manual.DetectSeriesAndHardwareCharacteristics(host)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	selectedTools, err := common.EnsureBootstrapTools(e, series, hc.Arch)
   109  	if err != nil {
   110  		return err
   111  	}
   112  	return manual.Bootstrap(manual.BootstrapArgs{
   113  		Context:                 ctx,
   114  		Host:                    host,
   115  		DataDir:                 dataDir,
   116  		Environ:                 e,
   117  		PossibleTools:           selectedTools,
   118  		Series:                  series,
   119  		HardwareCharacteristics: &hc,
   120  	})
   121  }
   122  
   123  func (e *manualEnviron) StateInfo() (*state.Info, *api.Info, error) {
   124  	return common.StateInfo(e)
   125  }
   126  
   127  func (e *manualEnviron) SetConfig(cfg *config.Config) error {
   128  	e.cfgmutex.Lock()
   129  	defer e.cfgmutex.Unlock()
   130  	envConfig, err := manualProvider{}.validate(cfg, e.cfg.Config)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	// Set storage. If "use-sshstorage" is true then use the SSH storage.
   135  	// Otherwise, use HTTP storage.
   136  	//
   137  	// We don't change storage once it's been set. Storage parameters
   138  	// are fixed at bootstrap time, and it is not possible to change
   139  	// them.
   140  	if e.storage == nil {
   141  		var stor storage.Storage
   142  		if envConfig.useSSHStorage() {
   143  			storageDir := e.StorageDir()
   144  			storageTmpdir := path.Join(dataDir, storageTmpSubdir)
   145  			stor, err = newSSHStorage("ubuntu@"+e.cfg.bootstrapHost(), storageDir, storageTmpdir)
   146  			if err != nil {
   147  				return fmt.Errorf("initialising SSH storage failed: %v", err)
   148  			}
   149  		} else {
   150  			caCertPEM, ok := envConfig.CACert()
   151  			if !ok {
   152  				// should not be possible to validate base config
   153  				return fmt.Errorf("ca-cert not set")
   154  			}
   155  			authkey := envConfig.storageAuthKey()
   156  			stor, err = httpstorage.ClientTLS(envConfig.storageAddr(), caCertPEM, authkey)
   157  			if err != nil {
   158  				return fmt.Errorf("initialising HTTPS storage failed: %v", err)
   159  			}
   160  		}
   161  		e.storage = stor
   162  	}
   163  	e.cfg = envConfig
   164  	return nil
   165  }
   166  
   167  // Implements environs.Environ.
   168  //
   169  // This method will only ever return an Instance for the Id
   170  // environ/manual.BootstrapInstanceId. If any others are
   171  // specified, then ErrPartialInstances or ErrNoInstances
   172  // will result.
   173  func (e *manualEnviron) Instances(ids []instance.Id) (instances []instance.Instance, err error) {
   174  	instances = make([]instance.Instance, len(ids))
   175  	var found bool
   176  	for i, id := range ids {
   177  		if id == manual.BootstrapInstanceId {
   178  			instances[i] = manualBootstrapInstance{e.envConfig().bootstrapHost()}
   179  			found = true
   180  		} else {
   181  			err = environs.ErrPartialInstances
   182  		}
   183  	}
   184  	if !found {
   185  		err = environs.ErrNoInstances
   186  	}
   187  	return instances, err
   188  }
   189  
   190  var newSSHStorage = func(sshHost, storageDir, storageTmpdir string) (storage.Storage, error) {
   191  	return sshstorage.NewSSHStorage(sshstorage.NewSSHStorageParams{
   192  		Host:       sshHost,
   193  		StorageDir: storageDir,
   194  		TmpDir:     storageTmpdir,
   195  	})
   196  }
   197  
   198  // GetToolsSources returns a list of sources which are
   199  // used to search for simplestreams tools metadata.
   200  func (e *manualEnviron) GetToolsSources() ([]simplestreams.DataSource, error) {
   201  	// Add the simplestreams source off private storage.
   202  	return []simplestreams.DataSource{
   203  		storage.NewStorageSimpleStreamsDataSource("cloud storage", e.Storage(), storage.BaseToolsPath),
   204  	}, nil
   205  }
   206  
   207  func (e *manualEnviron) Storage() storage.Storage {
   208  	e.cfgmutex.Lock()
   209  	defer e.cfgmutex.Unlock()
   210  	return e.storage
   211  }
   212  
   213  var runSSHCommand = func(host string, command []string) (stderr string, err error) {
   214  	cmd := ssh.Command(host, command, nil)
   215  	var stderrBuf bytes.Buffer
   216  	cmd.Stderr = &stderrBuf
   217  	err = cmd.Run()
   218  	return stderrBuf.String(), err
   219  }
   220  
   221  func (e *manualEnviron) Destroy() error {
   222  	stderr, err := runSSHCommand(
   223  		"ubuntu@"+e.envConfig().bootstrapHost(),
   224  		[]string{"sudo", "pkill", fmt.Sprintf("-%d", terminationworker.TerminationSignal), "jujud"},
   225  	)
   226  	if err != nil {
   227  		if stderr := strings.TrimSpace(stderr); len(stderr) > 0 {
   228  			err = fmt.Errorf("%v (%v)", err, stderr)
   229  		}
   230  	}
   231  	return err
   232  }
   233  
   234  func (*manualEnviron) PrecheckInstance(series string, cons constraints.Value) error {
   235  	return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`)
   236  }
   237  
   238  func (e *manualEnviron) OpenPorts(ports []instance.Port) error {
   239  	return nil
   240  }
   241  
   242  func (e *manualEnviron) ClosePorts(ports []instance.Port) error {
   243  	return nil
   244  }
   245  
   246  func (e *manualEnviron) Ports() ([]instance.Port, error) {
   247  	return []instance.Port{}, nil
   248  }
   249  
   250  func (*manualEnviron) Provider() environs.EnvironProvider {
   251  	return manualProvider{}
   252  }
   253  
   254  func (e *manualEnviron) StorageAddr() string {
   255  	return e.envConfig().storageListenAddr()
   256  }
   257  
   258  func (e *manualEnviron) StorageDir() string {
   259  	return path.Join(dataDir, storageSubdir)
   260  }
   261  
   262  func (e *manualEnviron) SharedStorageAddr() string {
   263  	return ""
   264  }
   265  
   266  func (e *manualEnviron) SharedStorageDir() string {
   267  	return ""
   268  }
   269  
   270  func (e *manualEnviron) StorageCACert() []byte {
   271  	if bytes, ok := e.envConfig().CACert(); ok {
   272  		return bytes
   273  	}
   274  	return nil
   275  }
   276  
   277  func (e *manualEnviron) StorageCAKey() []byte {
   278  	if bytes, ok := e.envConfig().CAPrivateKey(); ok {
   279  		return bytes
   280  	}
   281  	return nil
   282  }
   283  
   284  func (e *manualEnviron) StorageHostnames() []string {
   285  	cfg := e.envConfig()
   286  	hostnames := []string{cfg.bootstrapHost()}
   287  	if ip := net.ParseIP(cfg.storageListenIPAddress()); ip != nil {
   288  		if !ip.IsUnspecified() {
   289  			hostnames = append(hostnames, ip.String())
   290  		}
   291  	}
   292  	return hostnames
   293  }
   294  
   295  func (e *manualEnviron) StorageAuthKey() string {
   296  	return e.envConfig().storageAuthKey()
   297  }
   298  
   299  var _ localstorage.LocalTLSStorageConfig = (*manualEnviron)(nil)