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