github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/agent/bootstrap.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package agent
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/names"
    10  	"github.com/juju/utils"
    11  
    12  	"github.com/juju/juju/constraints"
    13  	"github.com/juju/juju/environs/config"
    14  	"github.com/juju/juju/instance"
    15  	"github.com/juju/juju/state"
    16  	"github.com/juju/juju/state/api/params"
    17  	"github.com/juju/juju/version"
    18  )
    19  
    20  // InitializeState should be called on the bootstrap machine's agent
    21  // configuration. It uses that information to create the state server, dial the
    22  // state server, and initialize it. It also generates a new password for the
    23  // bootstrap machine and calls Write to save the the configuration.
    24  //
    25  // The envCfg values will be stored in the state's EnvironConfig; the
    26  // machineCfg values will be used to configure the bootstrap Machine,
    27  // and its constraints will be also be used for the environment-level
    28  // constraints. The connection to the state server will respect the
    29  // given timeout parameter.
    30  //
    31  // InitializeState returns the newly initialized state and bootstrap
    32  // machine. If it fails, the state may well be irredeemably compromised.
    33  type StateInitializer interface {
    34  	InitializeState(envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts, policy state.Policy) (*state.State, *state.Machine, error)
    35  }
    36  
    37  // BootstrapMachineConfig holds configuration information
    38  // to attach to the bootstrap machine.
    39  type BootstrapMachineConfig struct {
    40  	// Addresses holds the bootstrap machine's addresses.
    41  	Addresses []instance.Address
    42  
    43  	// Constraints holds the bootstrap machine's constraints.
    44  	// This value is also used for the environment-level constraints.
    45  	Constraints constraints.Value
    46  
    47  	// Jobs holds the jobs that the machine agent will run.
    48  	Jobs []params.MachineJob
    49  
    50  	// InstanceId holds the instance id of the bootstrap machine.
    51  	InstanceId instance.Id
    52  
    53  	// Characteristics holds hardware information on the
    54  	// bootstrap machine.
    55  	Characteristics instance.HardwareCharacteristics
    56  
    57  	// SharedSecret is the Mongo replica set shared secret (keyfile).
    58  	SharedSecret string
    59  }
    60  
    61  const BootstrapMachineId = "0"
    62  
    63  func InitializeState(c ConfigSetter, envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts, policy state.Policy) (_ *state.State, _ *state.Machine, resultErr error) {
    64  	if c.Tag() != names.MachineTag(BootstrapMachineId) {
    65  		return nil, nil, fmt.Errorf("InitializeState not called with bootstrap machine's configuration")
    66  	}
    67  	servingInfo, ok := c.StateServingInfo()
    68  	if !ok {
    69  		return nil, nil, fmt.Errorf("state serving information not available")
    70  	}
    71  	// N.B. no users are set up when we're initializing the state,
    72  	// so don't use any tag or password when opening it.
    73  	info, ok := c.StateInfo()
    74  	if !ok {
    75  		return nil, nil, fmt.Errorf("stateinfo not available")
    76  	}
    77  	info.Tag = ""
    78  	info.Password = ""
    79  
    80  	logger.Debugf("initializing address %v", info.Addrs)
    81  	st, err := state.Initialize(info, envCfg, timeout, policy)
    82  	if err != nil {
    83  		return nil, nil, fmt.Errorf("failed to initialize state: %v", err)
    84  	}
    85  	logger.Debugf("connected to initial state")
    86  	defer func() {
    87  		if resultErr != nil {
    88  			st.Close()
    89  		}
    90  	}()
    91  	servingInfo.SharedSecret = machineCfg.SharedSecret
    92  	c.SetStateServingInfo(servingInfo)
    93  	if err = initAPIHostPorts(c, st, machineCfg.Addresses, servingInfo.APIPort); err != nil {
    94  		return nil, nil, err
    95  	}
    96  	if err := st.SetStateServingInfo(servingInfo); err != nil {
    97  		return nil, nil, fmt.Errorf("cannot set state serving info: %v", err)
    98  	}
    99  	m, err := initUsersAndBootstrapMachine(c, st, machineCfg)
   100  	if err != nil {
   101  		return nil, nil, err
   102  	}
   103  	return st, m, nil
   104  }
   105  
   106  func initUsersAndBootstrapMachine(c ConfigSetter, st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) {
   107  	if err := initBootstrapUser(st, c.OldPassword()); err != nil {
   108  		return nil, fmt.Errorf("cannot initialize bootstrap user: %v", err)
   109  	}
   110  	if err := st.SetEnvironConstraints(cfg.Constraints); err != nil {
   111  		return nil, fmt.Errorf("cannot set initial environ constraints: %v", err)
   112  	}
   113  	m, err := initBootstrapMachine(c, st, cfg)
   114  	if err != nil {
   115  		return nil, fmt.Errorf("cannot initialize bootstrap machine: %v", err)
   116  	}
   117  	return m, nil
   118  }
   119  
   120  // initBootstrapUser creates the initial admin user for the database, and sets
   121  // the initial password.
   122  func initBootstrapUser(st *state.State, passwordHash string) error {
   123  	logger.Debugf("adding admin user")
   124  	// Set up initial authentication.
   125  	u, err := st.AddUser("admin", "", "")
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	// Note that at bootstrap time, the password is set to
   131  	// the hash of its actual value. The first time a client
   132  	// connects to mongo, it changes the mongo password
   133  	// to the original password.
   134  	logger.Debugf("setting password hash for admin user")
   135  	// TODO(jam): http://pad.lv/1248839
   136  	// We could teach bootstrap how to generate a custom salt and apply
   137  	// that to the hash that was generated. At which point we'd need to set
   138  	// it here. For now, we pass "" so that on first login we will create a
   139  	// new salt, but the fixed-salt password is still available from
   140  	// cloud-init.
   141  	if err := u.SetPasswordHash(passwordHash, ""); err != nil {
   142  		return err
   143  	}
   144  	if err := st.SetAdminMongoPassword(passwordHash); err != nil {
   145  		return err
   146  	}
   147  	return nil
   148  }
   149  
   150  // initBootstrapMachine initializes the initial bootstrap machine in state.
   151  func initBootstrapMachine(c ConfigSetter, st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) {
   152  	logger.Infof("initialising bootstrap machine with config: %+v", cfg)
   153  
   154  	jobs := make([]state.MachineJob, len(cfg.Jobs))
   155  	for i, job := range cfg.Jobs {
   156  		machineJob, err := state.MachineJobFromParams(job)
   157  		if err != nil {
   158  			return nil, fmt.Errorf("invalid bootstrap machine job %q: %v", job, err)
   159  		}
   160  		jobs[i] = machineJob
   161  	}
   162  	m, err := st.AddOneMachine(state.MachineTemplate{
   163  		Addresses:               cfg.Addresses,
   164  		Series:                  version.Current.Series,
   165  		Nonce:                   state.BootstrapNonce,
   166  		Constraints:             cfg.Constraints,
   167  		InstanceId:              cfg.InstanceId,
   168  		HardwareCharacteristics: cfg.Characteristics,
   169  		Jobs: jobs,
   170  	})
   171  	if err != nil {
   172  		return nil, fmt.Errorf("cannot create bootstrap machine in state: %v", err)
   173  	}
   174  	if m.Id() != BootstrapMachineId {
   175  		return nil, fmt.Errorf("bootstrap machine expected id 0, got %q", m.Id())
   176  	}
   177  	// Read the machine agent's password and change it to
   178  	// a new password (other agents will change their password
   179  	// via the API connection).
   180  	logger.Debugf("create new random password for machine %v", m.Id())
   181  
   182  	newPassword, err := utils.RandomPassword()
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  	if err := m.SetPassword(newPassword); err != nil {
   187  		return nil, err
   188  	}
   189  	if err := m.SetMongoPassword(newPassword); err != nil {
   190  		return nil, err
   191  	}
   192  	c.SetPassword(newPassword)
   193  	return m, nil
   194  }
   195  
   196  // initAPIHostPorts sets the initial API host/port addresses in state.
   197  func initAPIHostPorts(c ConfigSetter, st *state.State, addrs []instance.Address, apiPort int) error {
   198  	hostPorts := instance.AddressesWithPort(addrs, apiPort)
   199  	return st.SetAPIHostPorts([][]instance.HostPort{hostPorts})
   200  }