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