github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/utils"
    12  	"github.com/juju/utils/clock"
    13  	"github.com/juju/utils/series"
    14  	"gopkg.in/juju/names.v2"
    15  
    16  	"github.com/juju/juju/agent"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/cloud"
    19  	"github.com/juju/juju/cloudconfig/instancecfg"
    20  	"github.com/juju/juju/controller/modelmanager"
    21  	"github.com/juju/juju/environs"
    22  	"github.com/juju/juju/environs/config"
    23  	"github.com/juju/juju/instance"
    24  	"github.com/juju/juju/mongo"
    25  	"github.com/juju/juju/network"
    26  	"github.com/juju/juju/state"
    27  	"github.com/juju/juju/state/multiwatcher"
    28  	"github.com/juju/juju/storage"
    29  )
    30  
    31  var logger = loggo.GetLogger("juju.agent.agentbootstrap")
    32  
    33  // InitializeStateParams holds parameters used for initializing the state
    34  // database.
    35  type InitializeStateParams struct {
    36  	instancecfg.StateInitializationParams
    37  
    38  	// BootstrapMachineAddresses holds the bootstrap machine's addresses.
    39  	BootstrapMachineAddresses []network.Address
    40  
    41  	// BootstrapMachineJobs holds the jobs that the bootstrap machine
    42  	// agent will run.
    43  	BootstrapMachineJobs []multiwatcher.MachineJob
    44  
    45  	// SharedSecret is the Mongo replica set shared secret (keyfile).
    46  	SharedSecret string
    47  
    48  	// Provider is called to obtain an EnvironProvider.
    49  	Provider func(string) (environs.EnvironProvider, error)
    50  
    51  	// StorageProviderRegistry is used to determine and store the
    52  	// details of the default storage pools.
    53  	StorageProviderRegistry storage.ProviderRegistry
    54  }
    55  
    56  // InitializeState should be called on the bootstrap machine's agent
    57  // configuration. It uses that information to create the controller, dial the
    58  // controller, and initialize it. It also generates a new password for the
    59  // bootstrap machine and calls Write to save the the configuration.
    60  //
    61  // The cfg values will be stored in the state's ModelConfig; the
    62  // machineCfg values will be used to configure the bootstrap Machine,
    63  // and its constraints will be also be used for the model-level
    64  // constraints. The connection to the controller will respect the
    65  // given timeout parameter.
    66  //
    67  // InitializeState returns the newly initialized state and bootstrap
    68  // machine. If it fails, the state may well be irredeemably compromised.
    69  func InitializeState(
    70  	adminUser names.UserTag,
    71  	c agent.ConfigSetter,
    72  	args InitializeStateParams,
    73  	dialOpts mongo.DialOpts,
    74  	newPolicy state.NewPolicyFunc,
    75  ) (_ *state.State, _ *state.Machine, resultErr error) {
    76  	if c.Tag() != names.NewMachineTag(agent.BootstrapMachineId) {
    77  		return nil, nil, errors.Errorf("InitializeState not called with bootstrap machine's configuration")
    78  	}
    79  	servingInfo, ok := c.StateServingInfo()
    80  	if !ok {
    81  		return nil, nil, errors.Errorf("state serving information not available")
    82  	}
    83  	// N.B. no users are set up when we're initializing the state,
    84  	// so don't use any tag or password when opening it.
    85  	info, ok := c.MongoInfo()
    86  	if !ok {
    87  		return nil, nil, errors.Errorf("stateinfo not available")
    88  	}
    89  	info.Tag = nil
    90  	info.Password = c.OldPassword()
    91  
    92  	if err := initMongoAdminUser(info.Info, dialOpts, info.Password); err != nil {
    93  		return nil, nil, errors.Annotate(err, "failed to initialize mongo admin user")
    94  	}
    95  
    96  	cloudCredentials := make(map[names.CloudCredentialTag]cloud.Credential)
    97  	var cloudCredentialTag names.CloudCredentialTag
    98  	if args.ControllerCloudCredential != nil && args.ControllerCloudCredentialName != "" {
    99  		cloudCredentialTag = names.NewCloudCredentialTag(fmt.Sprintf(
   100  			"%s/%s/%s",
   101  			args.ControllerCloudName,
   102  			adminUser.Canonical(),
   103  			args.ControllerCloudCredentialName,
   104  		))
   105  		cloudCredentials[cloudCredentialTag] = *args.ControllerCloudCredential
   106  	}
   107  
   108  	logger.Debugf("initializing address %v", info.Addrs)
   109  	st, err := state.Initialize(state.InitializeParams{
   110  		Clock: clock.WallClock,
   111  		ControllerModelArgs: state.ModelArgs{
   112  			Owner:                   adminUser,
   113  			Config:                  args.ControllerModelConfig,
   114  			Constraints:             args.ModelConstraints,
   115  			CloudName:               args.ControllerCloudName,
   116  			CloudRegion:             args.ControllerCloudRegion,
   117  			CloudCredential:         cloudCredentialTag,
   118  			StorageProviderRegistry: args.StorageProviderRegistry,
   119  		},
   120  		CloudName:                 args.ControllerCloudName,
   121  		Cloud:                     args.ControllerCloud,
   122  		CloudCredentials:          cloudCredentials,
   123  		ControllerConfig:          args.ControllerConfig,
   124  		ControllerInheritedConfig: args.ControllerInheritedConfig,
   125  		RegionInheritedConfig:     args.RegionInheritedConfig,
   126  		MongoInfo:                 info,
   127  		MongoDialOpts:             dialOpts,
   128  		NewPolicy:                 newPolicy,
   129  	})
   130  	if err != nil {
   131  		return nil, nil, errors.Errorf("failed to initialize state: %v", err)
   132  	}
   133  	logger.Debugf("connected to initial state")
   134  	defer func() {
   135  		if resultErr != nil {
   136  			st.Close()
   137  		}
   138  	}()
   139  	servingInfo.SharedSecret = args.SharedSecret
   140  	c.SetStateServingInfo(servingInfo)
   141  
   142  	// Filter out any LXC or LXD bridge addresses from the machine addresses.
   143  	args.BootstrapMachineAddresses = network.FilterBridgeAddresses(args.BootstrapMachineAddresses)
   144  
   145  	if err = initAPIHostPorts(c, st, args.BootstrapMachineAddresses, servingInfo.APIPort); err != nil {
   146  		return nil, nil, err
   147  	}
   148  	ssi := paramsStateServingInfoToStateStateServingInfo(servingInfo)
   149  	if err := st.SetStateServingInfo(ssi); err != nil {
   150  		return nil, nil, errors.Errorf("cannot set state serving info: %v", err)
   151  	}
   152  	m, err := initBootstrapMachine(c, st, args)
   153  	if err != nil {
   154  		return nil, nil, errors.Annotate(err, "cannot initialize bootstrap machine")
   155  	}
   156  
   157  	// Create the initial hosted model, with the model config passed to
   158  	// bootstrap, which contains the UUID, name for the hosted model,
   159  	// and any user supplied config. We also copy the authorized-keys
   160  	// from the controller model.
   161  	attrs := make(map[string]interface{})
   162  	for k, v := range args.HostedModelConfig {
   163  		attrs[k] = v
   164  	}
   165  	attrs[config.AuthorizedKeysKey] = args.ControllerModelConfig.AuthorizedKeys()
   166  
   167  	// Construct a CloudSpec to pass on to NewModelConfig below.
   168  	cloudSpec, err := environs.MakeCloudSpec(
   169  		args.ControllerCloud,
   170  		args.ControllerCloudName,
   171  		args.ControllerCloudRegion,
   172  		args.ControllerCloudCredential,
   173  	)
   174  	if err != nil {
   175  		return nil, nil, errors.Trace(err)
   176  	}
   177  
   178  	controllerUUID := args.ControllerConfig.ControllerUUID()
   179  	creator := modelmanager.ModelConfigCreator{Provider: args.Provider}
   180  	hostedModelConfig, err := creator.NewModelConfig(
   181  		cloudSpec, args.ControllerModelConfig, attrs,
   182  	)
   183  	if err != nil {
   184  		return nil, nil, errors.Annotate(err, "creating hosted model config")
   185  	}
   186  	provider, err := args.Provider(cloudSpec.Type)
   187  	if err != nil {
   188  		return nil, nil, errors.Annotate(err, "getting environ provider")
   189  	}
   190  	hostedModelEnv, err := provider.Open(environs.OpenParams{
   191  		Cloud:  cloudSpec,
   192  		Config: hostedModelConfig,
   193  	})
   194  	if err != nil {
   195  		return nil, nil, errors.Annotate(err, "opening hosted model environment")
   196  	}
   197  	if err := hostedModelEnv.Create(environs.CreateParams{
   198  		ControllerUUID: controllerUUID,
   199  	}); err != nil {
   200  		return nil, nil, errors.Annotate(err, "creating hosted model environment")
   201  	}
   202  
   203  	_, hostedModelState, err := st.NewModel(state.ModelArgs{
   204  		Owner:                   adminUser,
   205  		Config:                  hostedModelConfig,
   206  		Constraints:             args.ModelConstraints,
   207  		CloudName:               args.ControllerCloudName,
   208  		CloudRegion:             args.ControllerCloudRegion,
   209  		CloudCredential:         cloudCredentialTag,
   210  		StorageProviderRegistry: args.StorageProviderRegistry,
   211  	})
   212  	if err != nil {
   213  		return nil, nil, errors.Annotate(err, "creating hosted model")
   214  	}
   215  	hostedModelState.Close()
   216  
   217  	return st, m, nil
   218  }
   219  
   220  func paramsStateServingInfoToStateStateServingInfo(i params.StateServingInfo) state.StateServingInfo {
   221  	return state.StateServingInfo{
   222  		APIPort:        i.APIPort,
   223  		StatePort:      i.StatePort,
   224  		Cert:           i.Cert,
   225  		PrivateKey:     i.PrivateKey,
   226  		CAPrivateKey:   i.CAPrivateKey,
   227  		SharedSecret:   i.SharedSecret,
   228  		SystemIdentity: i.SystemIdentity,
   229  	}
   230  }
   231  
   232  // initMongoAdminUser adds the admin user with the specified
   233  // password to the admin database in Mongo.
   234  func initMongoAdminUser(info mongo.Info, dialOpts mongo.DialOpts, password string) error {
   235  	session, err := mongo.DialWithInfo(info, dialOpts)
   236  	if err != nil {
   237  		return err
   238  	}
   239  	defer session.Close()
   240  	return mongo.SetAdminMongoPassword(session, mongo.AdminUser, password)
   241  }
   242  
   243  // initBootstrapMachine initializes the initial bootstrap machine in state.
   244  func initBootstrapMachine(c agent.ConfigSetter, st *state.State, args InitializeStateParams) (*state.Machine, error) {
   245  	logger.Infof("initialising bootstrap machine with config: %+v", args)
   246  
   247  	jobs := make([]state.MachineJob, len(args.BootstrapMachineJobs))
   248  	for i, job := range args.BootstrapMachineJobs {
   249  		machineJob, err := machineJobFromParams(job)
   250  		if err != nil {
   251  			return nil, errors.Errorf("invalid bootstrap machine job %q: %v", job, err)
   252  		}
   253  		jobs[i] = machineJob
   254  	}
   255  	var hardware instance.HardwareCharacteristics
   256  	if args.BootstrapMachineHardwareCharacteristics != nil {
   257  		hardware = *args.BootstrapMachineHardwareCharacteristics
   258  	}
   259  	m, err := st.AddOneMachine(state.MachineTemplate{
   260  		Addresses:               args.BootstrapMachineAddresses,
   261  		Series:                  series.HostSeries(),
   262  		Nonce:                   agent.BootstrapNonce,
   263  		Constraints:             args.BootstrapMachineConstraints,
   264  		InstanceId:              args.BootstrapMachineInstanceId,
   265  		HardwareCharacteristics: hardware,
   266  		Jobs: jobs,
   267  	})
   268  	if err != nil {
   269  		return nil, errors.Errorf("cannot create bootstrap machine in state: %v", err)
   270  	}
   271  	if m.Id() != agent.BootstrapMachineId {
   272  		return nil, errors.Errorf("bootstrap machine expected id 0, got %q", m.Id())
   273  	}
   274  	// Read the machine agent's password and change it to
   275  	// a new password (other agents will change their password
   276  	// via the API connection).
   277  	logger.Debugf("create new random password for machine %v", m.Id())
   278  
   279  	newPassword, err := utils.RandomPassword()
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  	if err := m.SetPassword(newPassword); err != nil {
   284  		return nil, err
   285  	}
   286  	if err := m.SetMongoPassword(newPassword); err != nil {
   287  		return nil, err
   288  	}
   289  	c.SetPassword(newPassword)
   290  	return m, nil
   291  }
   292  
   293  // initAPIHostPorts sets the initial API host/port addresses in state.
   294  func initAPIHostPorts(c agent.ConfigSetter, st *state.State, addrs []network.Address, apiPort int) error {
   295  	var hostPorts []network.HostPort
   296  	// First try to select the correct address using the default space where all
   297  	// API servers should be accessible on.
   298  	spaceAddr, ok := network.SelectAddressBySpaces(addrs)
   299  	if ok {
   300  		logger.Debugf("selected %q as API address", spaceAddr.Value)
   301  		hostPorts = network.AddressesWithPort([]network.Address{spaceAddr}, apiPort)
   302  	} else {
   303  		// Fallback to using all instead.
   304  		hostPorts = network.AddressesWithPort(addrs, apiPort)
   305  	}
   306  
   307  	return st.SetAPIHostPorts([][]network.HostPort{hostPorts})
   308  }
   309  
   310  // machineJobFromParams returns the job corresponding to params.MachineJob.
   311  // TODO(dfc) this function should live in apiserver/params, move there once
   312  // state does not depend on apiserver/params
   313  func machineJobFromParams(job multiwatcher.MachineJob) (state.MachineJob, error) {
   314  	switch job {
   315  	case multiwatcher.JobHostUnits:
   316  		return state.JobHostUnits, nil
   317  	case multiwatcher.JobManageModel:
   318  		return state.JobManageModel, nil
   319  	default:
   320  		return -1, errors.Errorf("invalid machine job %q", job)
   321  	}
   322  }