github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  	"github.com/juju/errors"
     8  	"github.com/juju/names"
     9  	"github.com/juju/utils"
    10  
    11  	"github.com/juju/juju/apiserver/params"
    12  	"github.com/juju/juju/constraints"
    13  	"github.com/juju/juju/environs/config"
    14  	"github.com/juju/juju/instance"
    15  	"github.com/juju/juju/mongo"
    16  	"github.com/juju/juju/network"
    17  	"github.com/juju/juju/state"
    18  	"github.com/juju/juju/state/multiwatcher"
    19  	"github.com/juju/juju/version"
    20  )
    21  
    22  const (
    23  	// BootstrapNonce is used as a nonce for the state server machine.
    24  	BootstrapNonce = "user-admin:bootstrap"
    25  )
    26  
    27  // BootstrapMachineConfig holds configuration information
    28  // to attach to the bootstrap machine.
    29  type BootstrapMachineConfig struct {
    30  	// Addresses holds the bootstrap machine's addresses.
    31  	Addresses []network.Address
    32  
    33  	// Constraints holds the bootstrap machine's constraints.
    34  	// This value is also used for the environment-level constraints.
    35  	Constraints constraints.Value
    36  
    37  	// Jobs holds the jobs that the machine agent will run.
    38  	Jobs []multiwatcher.MachineJob
    39  
    40  	// InstanceId holds the instance id of the bootstrap machine.
    41  	InstanceId instance.Id
    42  
    43  	// Characteristics holds hardware information on the
    44  	// bootstrap machine.
    45  	Characteristics instance.HardwareCharacteristics
    46  
    47  	// SharedSecret is the Mongo replica set shared secret (keyfile).
    48  	SharedSecret string
    49  }
    50  
    51  const BootstrapMachineId = "0"
    52  
    53  // InitializeState should be called on the bootstrap machine's agent
    54  // configuration. It uses that information to create the state server, dial the
    55  // state server, and initialize it. It also generates a new password for the
    56  // bootstrap machine and calls Write to save the the configuration.
    57  //
    58  // The envCfg values will be stored in the state's EnvironConfig; the
    59  // machineCfg values will be used to configure the bootstrap Machine,
    60  // and its constraints will be also be used for the environment-level
    61  // constraints. The connection to the state server will respect the
    62  // given timeout parameter.
    63  //
    64  // InitializeState returns the newly initialized state and bootstrap
    65  // machine. If it fails, the state may well be irredeemably compromised.
    66  func InitializeState(adminUser names.UserTag, c ConfigSetter, envCfg *config.Config, machineCfg BootstrapMachineConfig, dialOpts mongo.DialOpts, policy state.Policy) (_ *state.State, _ *state.Machine, resultErr error) {
    67  	if c.Tag() != names.NewMachineTag(BootstrapMachineId) {
    68  		return nil, nil, errors.Errorf("InitializeState not called with bootstrap machine's configuration")
    69  	}
    70  	servingInfo, ok := c.StateServingInfo()
    71  	if !ok {
    72  		return nil, nil, errors.Errorf("state serving information not available")
    73  	}
    74  	// N.B. no users are set up when we're initializing the state,
    75  	// so don't use any tag or password when opening it.
    76  	info, ok := c.MongoInfo()
    77  	if !ok {
    78  		return nil, nil, errors.Errorf("stateinfo not available")
    79  	}
    80  	info.Tag = nil
    81  	info.Password = c.OldPassword()
    82  
    83  	if err := initMongoAdminUser(info.Info, dialOpts, info.Password); err != nil {
    84  		return nil, nil, errors.Annotate(err, "failed to initialize mongo admin user")
    85  	}
    86  
    87  	logger.Debugf("initializing address %v", info.Addrs)
    88  	st, err := state.Initialize(adminUser, info, envCfg, dialOpts, policy)
    89  	if err != nil {
    90  		return nil, nil, errors.Errorf("failed to initialize state: %v", err)
    91  	}
    92  	logger.Debugf("connected to initial state")
    93  	defer func() {
    94  		if resultErr != nil {
    95  			st.Close()
    96  		}
    97  	}()
    98  	servingInfo.SharedSecret = machineCfg.SharedSecret
    99  	c.SetStateServingInfo(servingInfo)
   100  	if err = initAPIHostPorts(c, st, machineCfg.Addresses, servingInfo.APIPort); err != nil {
   101  		return nil, nil, err
   102  	}
   103  	ssi := paramsStateServingInfoToStateStateServingInfo(servingInfo)
   104  	if err := st.SetStateServingInfo(ssi); err != nil {
   105  		return nil, nil, errors.Errorf("cannot set state serving info: %v", err)
   106  	}
   107  	m, err := initConstraintsAndBootstrapMachine(c, st, machineCfg)
   108  	if err != nil {
   109  		return nil, nil, err
   110  	}
   111  	return st, m, nil
   112  }
   113  
   114  func paramsStateServingInfoToStateStateServingInfo(i params.StateServingInfo) state.StateServingInfo {
   115  	return state.StateServingInfo{
   116  		APIPort:        i.APIPort,
   117  		StatePort:      i.StatePort,
   118  		Cert:           i.Cert,
   119  		PrivateKey:     i.PrivateKey,
   120  		CAPrivateKey:   i.CAPrivateKey,
   121  		SharedSecret:   i.SharedSecret,
   122  		SystemIdentity: i.SystemIdentity,
   123  	}
   124  }
   125  
   126  func initConstraintsAndBootstrapMachine(c ConfigSetter, st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) {
   127  	if err := st.SetEnvironConstraints(cfg.Constraints); err != nil {
   128  		return nil, errors.Errorf("cannot set initial environ constraints: %v", err)
   129  	}
   130  	m, err := initBootstrapMachine(c, st, cfg)
   131  	if err != nil {
   132  		return nil, errors.Errorf("cannot initialize bootstrap machine: %v", err)
   133  	}
   134  	return m, nil
   135  }
   136  
   137  // initMongoAdminUser adds the admin user with the specified
   138  // password to the admin database in Mongo.
   139  func initMongoAdminUser(info mongo.Info, dialOpts mongo.DialOpts, password string) error {
   140  	session, err := mongo.DialWithInfo(info, dialOpts)
   141  	if err != nil {
   142  		return err
   143  	}
   144  	defer session.Close()
   145  	return mongo.SetAdminMongoPassword(session, mongo.AdminUser, password)
   146  }
   147  
   148  // initBootstrapMachine initializes the initial bootstrap machine in state.
   149  func initBootstrapMachine(c ConfigSetter, st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) {
   150  	logger.Infof("initialising bootstrap machine with config: %+v", cfg)
   151  
   152  	jobs := make([]state.MachineJob, len(cfg.Jobs))
   153  	for i, job := range cfg.Jobs {
   154  		machineJob, err := machineJobFromParams(job)
   155  		if err != nil {
   156  			return nil, errors.Errorf("invalid bootstrap machine job %q: %v", job, err)
   157  		}
   158  		jobs[i] = machineJob
   159  	}
   160  	m, err := st.AddOneMachine(state.MachineTemplate{
   161  		Addresses:               cfg.Addresses,
   162  		Series:                  version.Current.Series,
   163  		Nonce:                   BootstrapNonce,
   164  		Constraints:             cfg.Constraints,
   165  		InstanceId:              cfg.InstanceId,
   166  		HardwareCharacteristics: cfg.Characteristics,
   167  		Jobs: jobs,
   168  	})
   169  	if err != nil {
   170  		return nil, errors.Errorf("cannot create bootstrap machine in state: %v", err)
   171  	}
   172  	if m.Id() != BootstrapMachineId {
   173  		return nil, errors.Errorf("bootstrap machine expected id 0, got %q", m.Id())
   174  	}
   175  	// Read the machine agent's password and change it to
   176  	// a new password (other agents will change their password
   177  	// via the API connection).
   178  	logger.Debugf("create new random password for machine %v", m.Id())
   179  
   180  	newPassword, err := utils.RandomPassword()
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  	if err := m.SetPassword(newPassword); err != nil {
   185  		return nil, err
   186  	}
   187  	if err := m.SetMongoPassword(newPassword); err != nil {
   188  		return nil, err
   189  	}
   190  	c.SetPassword(newPassword)
   191  	return m, nil
   192  }
   193  
   194  // initAPIHostPorts sets the initial API host/port addresses in state.
   195  func initAPIHostPorts(c ConfigSetter, st *state.State, addrs []network.Address, apiPort int) error {
   196  	hostPorts := network.AddressesWithPort(addrs, apiPort)
   197  	return st.SetAPIHostPorts([][]network.HostPort{hostPorts})
   198  }
   199  
   200  // machineJobFromParams returns the job corresponding to params.MachineJob.
   201  // TODO(dfc) this function should live in apiserver/params, move there once
   202  // state does not depend on apiserver/params
   203  func machineJobFromParams(job multiwatcher.MachineJob) (state.MachineJob, error) {
   204  	switch job {
   205  	case multiwatcher.JobHostUnits:
   206  		return state.JobHostUnits, nil
   207  	case multiwatcher.JobManageEnviron:
   208  		return state.JobManageEnviron, nil
   209  	case multiwatcher.JobManageNetworking:
   210  		return state.JobManageNetworking, nil
   211  	case multiwatcher.JobManageStateDeprecated:
   212  		// Deprecated in 1.18.
   213  		return state.JobManageStateDeprecated, nil
   214  	default:
   215  		return -1, errors.Errorf("invalid machine job %q", job)
   216  	}
   217  }