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 }