launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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  	"encoding/json"
     8  	"fmt"
     9  
    10  	"launchpad.net/juju-core/constraints"
    11  	"launchpad.net/juju-core/environs/config"
    12  	"launchpad.net/juju-core/instance"
    13  	"launchpad.net/juju-core/names"
    14  	"launchpad.net/juju-core/state"
    15  	"launchpad.net/juju-core/version"
    16  )
    17  
    18  // InitializeState should be called on the bootstrap machine's agent
    19  // configuration. It uses that information to dial the state server and
    20  // initialize it. It also generates a new password for the bootstrap
    21  // machine and calls Write to save the the configuration.
    22  //
    23  // The envCfg values will be stored in the state's EnvironConfig; the
    24  // machineCfg values will be used to configure the bootstrap Machine,
    25  // and its constraints will be also be used for the environment-level
    26  // constraints. The connection to the state server will respect the
    27  // given timeout parameter.
    28  //
    29  // InitializeState returns the newly initialized state and bootstrap
    30  // machine. If it fails, the state may well be irredeemably compromised.
    31  type StateInitializer interface {
    32  	InitializeState(envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts) (*state.State, *state.Machine, error)
    33  }
    34  
    35  // MarshalBootstrapJobs may be used to marshal a set of
    36  // machine jobs for the bootstrap agent, to be added
    37  // into the agent configuration.
    38  func MarshalBootstrapJobs(jobs ...state.MachineJob) (string, error) {
    39  	b, err := json.Marshal(jobs)
    40  	return string(b), err
    41  }
    42  
    43  // UnmarshalBootstrapJobs unmarshals a set of machine
    44  // jobs marshalled with MarshalBootstrapJobs.
    45  func UnmarshalBootstrapJobs(s string) (jobs []state.MachineJob, err error) {
    46  	err = json.Unmarshal([]byte(s), &jobs)
    47  	return jobs, err
    48  }
    49  
    50  // BootstrapMachineConfig holds configuration information
    51  // to attach to the bootstrap machine.
    52  type BootstrapMachineConfig struct {
    53  	// Constraints holds the bootstrap machine's constraints.
    54  	// This value is also used for the environment-level constraints.
    55  	Constraints constraints.Value
    56  
    57  	// Jobs holds the jobs that the machine agent will run.
    58  	Jobs []state.MachineJob
    59  
    60  	// InstanceId holds the instance id of the bootstrap machine.
    61  	InstanceId instance.Id
    62  
    63  	// Characteristics holds hardware information on the
    64  	// bootstrap machine.
    65  	Characteristics instance.HardwareCharacteristics
    66  }
    67  
    68  const bootstrapMachineId = "0"
    69  
    70  func (c *configInternal) InitializeState(envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts) (*state.State, *state.Machine, error) {
    71  	if c.Tag() != names.MachineTag(bootstrapMachineId) {
    72  		return nil, nil, fmt.Errorf("InitializeState not called with bootstrap machine's configuration")
    73  	}
    74  	info := state.Info{
    75  		Addrs:  c.stateDetails.addresses,
    76  		CACert: c.caCert,
    77  	}
    78  	logger.Debugf("initializing address %v", info.Addrs)
    79  	st, err := state.Initialize(&info, envCfg, timeout)
    80  	if err != nil {
    81  		return nil, nil, fmt.Errorf("failed to initialize state: %v", err)
    82  	}
    83  	logger.Debugf("connected to initial state")
    84  	m, err := c.initUsersAndBootstrapMachine(st, machineCfg)
    85  	if err != nil {
    86  		st.Close()
    87  		return nil, nil, err
    88  	}
    89  	return st, m, nil
    90  }
    91  
    92  func (c *configInternal) initUsersAndBootstrapMachine(st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) {
    93  	if err := initBootstrapUser(st, c.oldPassword); err != nil {
    94  		return nil, fmt.Errorf("cannot initialize bootstrap user: %v", err)
    95  	}
    96  	if err := st.SetEnvironConstraints(cfg.Constraints); err != nil {
    97  		return nil, fmt.Errorf("cannot set initial environ constraints: %v", err)
    98  	}
    99  	m, err := c.initBootstrapMachine(st, cfg)
   100  	if err != nil {
   101  		return nil, fmt.Errorf("cannot initialize bootstrap machine: %v", err)
   102  	}
   103  	return m, nil
   104  }
   105  
   106  // initBootstrapUser creates the initial admin user for the database, and sets
   107  // the initial password.
   108  func initBootstrapUser(st *state.State, passwordHash string) error {
   109  	logger.Debugf("adding admin user")
   110  	// Set up initial authentication.
   111  	u, err := st.AddUser("admin", "")
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	// Note that at bootstrap time, the password is set to
   117  	// the hash of its actual value. The first time a client
   118  	// connects to mongo, it changes the mongo password
   119  	// to the original password.
   120  	logger.Debugf("setting password hash for admin user")
   121  	// TODO(jam): http://pad.lv/1248839
   122  	// We could teach bootstrap how to generate a custom salt and apply
   123  	// that to the hash that was generated. At which point we'd need to set
   124  	// it here. For now, we pass "" so that on first login we will create a
   125  	// new salt, but the fixed-salt password is still available from
   126  	// cloud-init.
   127  	if err := u.SetPasswordHash(passwordHash, ""); err != nil {
   128  		return err
   129  	}
   130  	if err := st.SetAdminMongoPassword(passwordHash); err != nil {
   131  		return err
   132  	}
   133  	return nil
   134  }
   135  
   136  // initBootstrapMachine initializes the initial bootstrap machine in state.
   137  func (c *configInternal) initBootstrapMachine(st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) {
   138  
   139  	logger.Infof("initialising bootstrap machine with config: %+v", cfg)
   140  
   141  	m, err := st.AddOneMachine(state.MachineTemplate{
   142  		Series:                  version.Current.Series,
   143  		Nonce:                   state.BootstrapNonce,
   144  		Constraints:             cfg.Constraints,
   145  		InstanceId:              cfg.InstanceId,
   146  		HardwareCharacteristics: cfg.Characteristics,
   147  		Jobs: cfg.Jobs,
   148  	})
   149  	if err != nil {
   150  		return nil, fmt.Errorf("cannot create bootstrap machine in state: %v", err)
   151  	}
   152  	if m.Id() != bootstrapMachineId {
   153  		return nil, fmt.Errorf("bootstrap machine expected id 0, got %q", m.Id())
   154  	}
   155  	// Read the machine agent's password and change it to
   156  	// a new password (other agents will change their password
   157  	// via the API connection).
   158  	logger.Debugf("create new random password for machine %v", m.Id())
   159  
   160  	newPassword, err := c.writeNewPassword()
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  	if err := m.SetMongoPassword(newPassword); err != nil {
   165  		return nil, err
   166  	}
   167  	if err := m.SetPassword(newPassword); err != nil {
   168  		return nil, err
   169  	}
   170  	return m, nil
   171  }