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