github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/agent/agentbootstrap/bootstrap.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package agentbootstrap
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  	"github.com/juju/names"
    10  	"github.com/juju/utils"
    11  	"github.com/juju/utils/series"
    12  
    13  	"github.com/juju/juju/agent"
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/constraints"
    16  	"github.com/juju/juju/controller/modelmanager"
    17  	"github.com/juju/juju/environs/config"
    18  	"github.com/juju/juju/instance"
    19  	"github.com/juju/juju/mongo"
    20  	"github.com/juju/juju/network"
    21  	"github.com/juju/juju/state"
    22  	"github.com/juju/juju/state/multiwatcher"
    23  )
    24  
    25  var logger = loggo.GetLogger("juju.agent.agentbootstrap")
    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  	// BootstrapConstraints holds the bootstrap machine's constraints.
    34  	BootstrapConstraints constraints.Value
    35  
    36  	// ModelConstraints holds the model-level constraints.
    37  	ModelConstraints constraints.Value
    38  
    39  	// Jobs holds the jobs that the machine agent will run.
    40  	Jobs []multiwatcher.MachineJob
    41  
    42  	// InstanceId holds the instance id of the bootstrap machine.
    43  	InstanceId instance.Id
    44  
    45  	// Characteristics holds hardware information on the
    46  	// bootstrap machine.
    47  	Characteristics instance.HardwareCharacteristics
    48  
    49  	// SharedSecret is the Mongo replica set shared secret (keyfile).
    50  	SharedSecret string
    51  }
    52  
    53  // InitializeState should be called on the bootstrap machine's agent
    54  // configuration. It uses that information to create the controller, dial the
    55  // controller, 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 cfg values will be stored in the state's ModelConfig; the
    59  // machineCfg values will be used to configure the bootstrap Machine,
    60  // and its constraints will be also be used for the model-level
    61  // constraints. The connection to the controller 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(
    67  	adminUser names.UserTag,
    68  	c agent.ConfigSetter,
    69  	cfg *config.Config,
    70  	hostedModelConfigAttrs map[string]interface{},
    71  	machineCfg BootstrapMachineConfig,
    72  	dialOpts mongo.DialOpts,
    73  	policy state.Policy,
    74  ) (_ *state.State, _ *state.Machine, resultErr error) {
    75  	if c.Tag() != names.NewMachineTag(agent.BootstrapMachineId) {
    76  		return nil, nil, errors.Errorf("InitializeState not called with bootstrap machine's configuration")
    77  	}
    78  	servingInfo, ok := c.StateServingInfo()
    79  	if !ok {
    80  		return nil, nil, errors.Errorf("state serving information not available")
    81  	}
    82  	// N.B. no users are set up when we're initializing the state,
    83  	// so don't use any tag or password when opening it.
    84  	info, ok := c.MongoInfo()
    85  	if !ok {
    86  		return nil, nil, errors.Errorf("stateinfo not available")
    87  	}
    88  	info.Tag = nil
    89  	info.Password = c.OldPassword()
    90  
    91  	if err := initMongoAdminUser(info.Info, dialOpts, info.Password); err != nil {
    92  		return nil, nil, errors.Annotate(err, "failed to initialize mongo admin user")
    93  	}
    94  
    95  	logger.Debugf("initializing address %v", info.Addrs)
    96  	st, err := state.Initialize(adminUser, info, cfg, dialOpts, policy)
    97  	if err != nil {
    98  		return nil, nil, errors.Errorf("failed to initialize state: %v", err)
    99  	}
   100  	logger.Debugf("connected to initial state")
   101  	defer func() {
   102  		if resultErr != nil {
   103  			st.Close()
   104  		}
   105  	}()
   106  	servingInfo.SharedSecret = machineCfg.SharedSecret
   107  	c.SetStateServingInfo(servingInfo)
   108  
   109  	// Filter out any LXC bridge addresses from the machine addresses.
   110  	machineCfg.Addresses = network.FilterLXCAddresses(machineCfg.Addresses)
   111  
   112  	if err = initAPIHostPorts(c, st, machineCfg.Addresses, servingInfo.APIPort); err != nil {
   113  		return nil, nil, err
   114  	}
   115  	ssi := paramsStateServingInfoToStateStateServingInfo(servingInfo)
   116  	if err := st.SetStateServingInfo(ssi); err != nil {
   117  		return nil, nil, errors.Errorf("cannot set state serving info: %v", err)
   118  	}
   119  	m, err := initConstraintsAndBootstrapMachine(c, st, machineCfg)
   120  	if err != nil {
   121  		return nil, nil, err
   122  	}
   123  
   124  	// Create the initial hosted model, with the model config passed to
   125  	// bootstrap, which contains the UUID, name for the hosted model,
   126  	// and any user supplied config.
   127  	attrs := make(map[string]interface{})
   128  	for k, v := range hostedModelConfigAttrs {
   129  		attrs[k] = v
   130  	}
   131  	hostedModelConfig, err := modelmanager.ModelConfigCreator{}.NewModelConfig(modelmanager.IsAdmin, cfg, attrs)
   132  	if err != nil {
   133  		return nil, nil, errors.Annotate(err, "creating hosted model config")
   134  	}
   135  	_, hostedModelState, err := st.NewModel(state.ModelArgs{
   136  		Config: hostedModelConfig,
   137  		Owner:  adminUser,
   138  	})
   139  	if err != nil {
   140  		return nil, nil, errors.Annotate(err, "creating hosted model")
   141  	}
   142  	if err := hostedModelState.SetModelConstraints(machineCfg.ModelConstraints); err != nil {
   143  		return nil, nil, errors.Annotate(err, "cannot set initial hosted model constraints")
   144  	}
   145  	hostedModelState.Close()
   146  
   147  	return st, m, nil
   148  }
   149  
   150  func paramsStateServingInfoToStateStateServingInfo(i params.StateServingInfo) state.StateServingInfo {
   151  	return state.StateServingInfo{
   152  		APIPort:        i.APIPort,
   153  		StatePort:      i.StatePort,
   154  		Cert:           i.Cert,
   155  		PrivateKey:     i.PrivateKey,
   156  		CAPrivateKey:   i.CAPrivateKey,
   157  		SharedSecret:   i.SharedSecret,
   158  		SystemIdentity: i.SystemIdentity,
   159  	}
   160  }
   161  
   162  func initConstraintsAndBootstrapMachine(c agent.ConfigSetter, st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) {
   163  	if err := st.SetModelConstraints(cfg.ModelConstraints); err != nil {
   164  		return nil, errors.Annotate(err, "cannot set initial model constraints")
   165  	}
   166  	m, err := initBootstrapMachine(c, st, cfg)
   167  	if err != nil {
   168  		return nil, errors.Annotate(err, "cannot initialize bootstrap machine")
   169  	}
   170  	return m, nil
   171  }
   172  
   173  // initMongoAdminUser adds the admin user with the specified
   174  // password to the admin database in Mongo.
   175  func initMongoAdminUser(info mongo.Info, dialOpts mongo.DialOpts, password string) error {
   176  	session, err := mongo.DialWithInfo(info, dialOpts)
   177  	if err != nil {
   178  		return err
   179  	}
   180  	defer session.Close()
   181  	return mongo.SetAdminMongoPassword(session, mongo.AdminUser, password)
   182  }
   183  
   184  // initBootstrapMachine initializes the initial bootstrap machine in state.
   185  func initBootstrapMachine(c agent.ConfigSetter, st *state.State, cfg BootstrapMachineConfig) (*state.Machine, error) {
   186  	logger.Infof("initialising bootstrap machine with config: %+v", cfg)
   187  
   188  	jobs := make([]state.MachineJob, len(cfg.Jobs))
   189  	for i, job := range cfg.Jobs {
   190  		machineJob, err := machineJobFromParams(job)
   191  		if err != nil {
   192  			return nil, errors.Errorf("invalid bootstrap machine job %q: %v", job, err)
   193  		}
   194  		jobs[i] = machineJob
   195  	}
   196  	m, err := st.AddOneMachine(state.MachineTemplate{
   197  		Addresses:               cfg.Addresses,
   198  		Series:                  series.HostSeries(),
   199  		Nonce:                   agent.BootstrapNonce,
   200  		Constraints:             cfg.BootstrapConstraints,
   201  		InstanceId:              cfg.InstanceId,
   202  		HardwareCharacteristics: cfg.Characteristics,
   203  		Jobs: jobs,
   204  	})
   205  	if err != nil {
   206  		return nil, errors.Errorf("cannot create bootstrap machine in state: %v", err)
   207  	}
   208  	if m.Id() != agent.BootstrapMachineId {
   209  		return nil, errors.Errorf("bootstrap machine expected id 0, got %q", m.Id())
   210  	}
   211  	// Read the machine agent's password and change it to
   212  	// a new password (other agents will change their password
   213  	// via the API connection).
   214  	logger.Debugf("create new random password for machine %v", m.Id())
   215  
   216  	newPassword, err := utils.RandomPassword()
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  	if err := m.SetPassword(newPassword); err != nil {
   221  		return nil, err
   222  	}
   223  	if err := m.SetMongoPassword(newPassword); err != nil {
   224  		return nil, err
   225  	}
   226  	c.SetPassword(newPassword)
   227  	return m, nil
   228  }
   229  
   230  // initAPIHostPorts sets the initial API host/port addresses in state.
   231  func initAPIHostPorts(c agent.ConfigSetter, st *state.State, addrs []network.Address, apiPort int) error {
   232  	var hostPorts []network.HostPort
   233  	// First try to select the correct address using the default space where all
   234  	// API servers should be accessible on.
   235  	spaceAddr, ok := network.SelectAddressBySpaces(addrs)
   236  	if ok {
   237  		logger.Debugf("selected %q as API address", spaceAddr.Value)
   238  		hostPorts = network.AddressesWithPort([]network.Address{spaceAddr}, apiPort)
   239  	} else {
   240  		// Fallback to using all instead.
   241  		hostPorts = network.AddressesWithPort(addrs, apiPort)
   242  	}
   243  
   244  	return st.SetAPIHostPorts([][]network.HostPort{hostPorts})
   245  }
   246  
   247  // machineJobFromParams returns the job corresponding to params.MachineJob.
   248  // TODO(dfc) this function should live in apiserver/params, move there once
   249  // state does not depend on apiserver/params
   250  func machineJobFromParams(job multiwatcher.MachineJob) (state.MachineJob, error) {
   251  	switch job {
   252  	case multiwatcher.JobHostUnits:
   253  		return state.JobHostUnits, nil
   254  	case multiwatcher.JobManageModel:
   255  		return state.JobManageModel, nil
   256  	case multiwatcher.JobManageNetworking:
   257  		return state.JobManageNetworking, nil
   258  	default:
   259  		return -1, errors.Errorf("invalid machine job %q", job)
   260  	}
   261  }