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 }