github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/jujud/bootstrap.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package main 5 6 import ( 7 "bytes" 8 "fmt" 9 "io/ioutil" 10 "net" 11 "os" 12 "path/filepath" 13 "strings" 14 "time" 15 16 "github.com/juju/cmd" 17 "github.com/juju/errors" 18 "github.com/juju/gnuflag" 19 "github.com/juju/loggo" 20 "github.com/juju/utils/arch" 21 "github.com/juju/utils/series" 22 "github.com/juju/utils/ssh" 23 "github.com/juju/version" 24 "gopkg.in/juju/names.v2" 25 26 "github.com/juju/juju/agent" 27 "github.com/juju/juju/agent/agentbootstrap" 28 agenttools "github.com/juju/juju/agent/tools" 29 "github.com/juju/juju/cloudconfig/instancecfg" 30 agentcmd "github.com/juju/juju/cmd/jujud/agent" 31 cmdutil "github.com/juju/juju/cmd/jujud/util" 32 "github.com/juju/juju/environs" 33 "github.com/juju/juju/environs/config" 34 "github.com/juju/juju/environs/imagemetadata" 35 "github.com/juju/juju/environs/simplestreams" 36 envtools "github.com/juju/juju/environs/tools" 37 "github.com/juju/juju/instance" 38 "github.com/juju/juju/mongo" 39 "github.com/juju/juju/network" 40 "github.com/juju/juju/state" 41 "github.com/juju/juju/state/binarystorage" 42 "github.com/juju/juju/state/cloudimagemetadata" 43 "github.com/juju/juju/state/multiwatcher" 44 "github.com/juju/juju/state/stateenvirons" 45 "github.com/juju/juju/tools" 46 jujuversion "github.com/juju/juju/version" 47 "github.com/juju/juju/worker/peergrouper" 48 ) 49 50 var ( 51 initiateMongoServer = peergrouper.InitiateMongoServer 52 agentInitializeState = agentbootstrap.InitializeState 53 sshGenerateKey = ssh.GenerateKey 54 minSocketTimeout = 1 * time.Minute 55 logger = loggo.GetLogger("juju.cmd.jujud") 56 ) 57 58 const adminUserName = "admin" 59 60 // BootstrapCommand represents a jujud bootstrap command. 61 type BootstrapCommand struct { 62 cmd.CommandBase 63 agentcmd.AgentConf 64 BootstrapParamsFile string 65 Timeout time.Duration 66 } 67 68 // NewBootstrapCommand returns a new BootstrapCommand that has been initialized. 69 func NewBootstrapCommand() *BootstrapCommand { 70 return &BootstrapCommand{ 71 AgentConf: agentcmd.NewAgentConf(""), 72 } 73 } 74 75 // Info returns a decription of the command. 76 func (c *BootstrapCommand) Info() *cmd.Info { 77 return &cmd.Info{ 78 Name: "bootstrap-state", 79 Purpose: "initialize juju state", 80 } 81 } 82 83 // SetFlags adds the flags for this command to the passed gnuflag.FlagSet. 84 func (c *BootstrapCommand) SetFlags(f *gnuflag.FlagSet) { 85 c.AgentConf.AddFlags(f) 86 f.DurationVar(&c.Timeout, "timeout", time.Duration(0), "set the bootstrap timeout") 87 } 88 89 // Init initializes the command for running. 90 func (c *BootstrapCommand) Init(args []string) error { 91 if len(args) == 0 { 92 return errors.New("bootstrap-params file must be specified") 93 } 94 if err := cmd.CheckEmpty(args[1:]); err != nil { 95 return err 96 } 97 c.BootstrapParamsFile = args[0] 98 return c.AgentConf.CheckArgs(args[1:]) 99 } 100 101 // Run initializes state for an environment. 102 func (c *BootstrapCommand) Run(_ *cmd.Context) error { 103 bootstrapParamsData, err := ioutil.ReadFile(c.BootstrapParamsFile) 104 if err != nil { 105 return errors.Annotate(err, "reading bootstrap params file") 106 } 107 var args instancecfg.StateInitializationParams 108 if err := args.Unmarshal(bootstrapParamsData); err != nil { 109 return errors.Trace(err) 110 } 111 112 err = c.ReadConfig("machine-0") 113 if err != nil { 114 return errors.Annotate(err, "cannot read config") 115 } 116 agentConfig := c.CurrentConfig() 117 118 // agent.Jobs is an optional field in the agent config, and was 119 // introduced after 1.17.2. We default to allowing units on 120 // machine-0 if missing. 121 jobs := agentConfig.Jobs() 122 if len(jobs) == 0 { 123 jobs = []multiwatcher.MachineJob{ 124 multiwatcher.JobManageModel, 125 multiwatcher.JobHostUnits, 126 } 127 } 128 129 // Get the bootstrap machine's addresses from the provider. 130 cloudSpec, err := environs.MakeCloudSpec( 131 args.ControllerCloud, 132 args.ControllerCloudName, 133 args.ControllerCloudRegion, 134 args.ControllerCloudCredential, 135 ) 136 if err != nil { 137 return errors.Trace(err) 138 } 139 env, err := environs.New(environs.OpenParams{ 140 Cloud: cloudSpec, 141 Config: args.ControllerModelConfig, 142 }) 143 if err != nil { 144 return errors.Annotate(err, "new environ") 145 } 146 newConfigAttrs := make(map[string]interface{}) 147 148 // Check to see if a newer agent version has been requested 149 // by the bootstrap client. 150 desiredVersion, ok := args.ControllerModelConfig.AgentVersion() 151 if ok && desiredVersion != jujuversion.Current { 152 // If we have been asked for a newer version, ensure the newer 153 // tools can actually be found, or else bootstrap won't complete. 154 stream := envtools.PreferredStream(&desiredVersion, args.ControllerModelConfig.Development(), args.ControllerModelConfig.AgentStream()) 155 logger.Infof("newer tools requested, looking for %v in stream %v", desiredVersion, stream) 156 filter := tools.Filter{ 157 Number: desiredVersion, 158 Arch: arch.HostArch(), 159 Series: series.HostSeries(), 160 } 161 _, toolsErr := envtools.FindTools(env, -1, -1, stream, filter) 162 if toolsErr == nil { 163 logger.Infof("tools are available, upgrade will occur after bootstrap") 164 } 165 if errors.IsNotFound(toolsErr) { 166 // Newer tools not available, so revert to using the tools 167 // matching the current agent version. 168 logger.Warningf("newer tools for %q not available, sticking with version %q", desiredVersion, jujuversion.Current) 169 newConfigAttrs["agent-version"] = jujuversion.Current.String() 170 } else if toolsErr != nil { 171 logger.Errorf("cannot find newer tools: %v", toolsErr) 172 return toolsErr 173 } 174 } 175 176 instances, err := env.Instances([]instance.Id{args.BootstrapMachineInstanceId}) 177 if err != nil { 178 return errors.Annotate(err, "getting bootstrap instance") 179 } 180 addrs, err := instances[0].Addresses() 181 if err != nil { 182 return errors.Annotate(err, "bootstrap instance addresses") 183 } 184 185 // When machine addresses are reported from state, they have 186 // duplicates removed. We should do the same here so that 187 // there is not unnecessary churn in the mongo replicaset. 188 // TODO (cherylj) Add explicit unit tests for this - tracked 189 // by bug #1544158. 190 addrs = network.MergedAddresses([]network.Address{}, addrs) 191 192 // Generate a private SSH key for the controllers, and add 193 // the public key to the environment config. We'll add the 194 // private key to StateServingInfo below. 195 privateKey, publicKey, err := sshGenerateKey(config.JujuSystemKey) 196 if err != nil { 197 return errors.Annotate(err, "failed to generate system key") 198 } 199 authorizedKeys := config.ConcatAuthKeys(args.ControllerModelConfig.AuthorizedKeys(), publicKey) 200 newConfigAttrs[config.AuthorizedKeysKey] = authorizedKeys 201 202 // Generate a shared secret for the Mongo replica set, and write it out. 203 sharedSecret, err := mongo.GenerateSharedSecret() 204 if err != nil { 205 return err 206 } 207 info, ok := agentConfig.StateServingInfo() 208 if !ok { 209 return fmt.Errorf("bootstrap machine config has no state serving info") 210 } 211 info.SharedSecret = sharedSecret 212 info.SystemIdentity = privateKey 213 err = c.ChangeConfig(func(agentConfig agent.ConfigSetter) error { 214 agentConfig.SetStateServingInfo(info) 215 return nil 216 }) 217 if err != nil { 218 return fmt.Errorf("cannot write agent config: %v", err) 219 } 220 221 agentConfig = c.CurrentConfig() 222 223 // Create system-identity file 224 if err := agent.WriteSystemIdentityFile(agentConfig); err != nil { 225 return err 226 } 227 228 if err := c.startMongo(addrs, agentConfig); err != nil { 229 return errors.Annotate(err, "failed to start mongo") 230 } 231 232 controllerModelCfg, err := env.Config().Apply(newConfigAttrs) 233 if err != nil { 234 return errors.Annotate(err, "failed to update model config") 235 } 236 args.ControllerModelConfig = controllerModelCfg 237 238 // Initialise state, and store any agent config (e.g. password) changes. 239 var st *state.State 240 var m *state.Machine 241 err = c.ChangeConfig(func(agentConfig agent.ConfigSetter) error { 242 var stateErr error 243 dialOpts := mongo.DefaultDialOpts() 244 245 // Set a longer socket timeout than usual, as the machine 246 // will be starting up and disk I/O slower than usual. This 247 // has been known to cause timeouts in queries. 248 dialOpts.SocketTimeout = c.Timeout 249 if dialOpts.SocketTimeout < minSocketTimeout { 250 dialOpts.SocketTimeout = minSocketTimeout 251 } 252 253 // We shouldn't attempt to dial peers until we have some. 254 dialOpts.Direct = true 255 256 adminTag := names.NewLocalUserTag(adminUserName) 257 st, m, stateErr = agentInitializeState( 258 adminTag, 259 agentConfig, 260 agentbootstrap.InitializeStateParams{ 261 StateInitializationParams: args, 262 BootstrapMachineAddresses: addrs, 263 BootstrapMachineJobs: jobs, 264 SharedSecret: sharedSecret, 265 Provider: environs.Provider, 266 StorageProviderRegistry: stateenvirons.NewStorageProviderRegistry(env), 267 }, 268 dialOpts, 269 stateenvirons.GetNewPolicyFunc( 270 stateenvirons.GetNewEnvironFunc(environs.New), 271 ), 272 ) 273 return stateErr 274 }) 275 if err != nil { 276 return err 277 } 278 defer st.Close() 279 280 // Populate the tools catalogue. 281 if err := c.populateTools(st, env); err != nil { 282 return err 283 } 284 285 // Populate the GUI archive catalogue. 286 if err := c.populateGUIArchive(st, env); err != nil { 287 // Do not stop the bootstrapping process for Juju GUI archive errors. 288 logger.Warningf("cannot set up Juju GUI: %s", err) 289 } else { 290 logger.Debugf("Juju GUI successfully set up") 291 } 292 293 // Add custom image metadata to environment storage. 294 if len(args.CustomImageMetadata) > 0 { 295 if err := c.saveCustomImageMetadata(st, env, args.CustomImageMetadata); err != nil { 296 return err 297 } 298 } 299 300 // bootstrap machine always gets the vote 301 return m.SetHasVote(true) 302 } 303 304 func (c *BootstrapCommand) startMongo(addrs []network.Address, agentConfig agent.Config) error { 305 logger.Debugf("starting mongo") 306 307 info, ok := agentConfig.MongoInfo() 308 if !ok { 309 return fmt.Errorf("no state info available") 310 } 311 // When bootstrapping, we need to allow enough time for mongo 312 // to start as there's no retry loop in place. 313 // 5 minutes should suffice. 314 mongoDialOpts := mongo.DialOpts{Timeout: 5 * time.Minute} 315 dialInfo, err := mongo.DialInfo(info.Info, mongoDialOpts) 316 if err != nil { 317 return err 318 } 319 servingInfo, ok := agentConfig.StateServingInfo() 320 if !ok { 321 return fmt.Errorf("agent config has no state serving info") 322 } 323 // Use localhost to dial the mongo server, because it's running in 324 // auth mode and will refuse to perform any operations unless 325 // we dial that address. 326 dialInfo.Addrs = []string{ 327 net.JoinHostPort("127.0.0.1", fmt.Sprint(servingInfo.StatePort)), 328 } 329 330 logger.Debugf("calling ensureMongoServer") 331 ensureServerParams, err := cmdutil.NewEnsureServerParams(agentConfig) 332 if err != nil { 333 return err 334 } 335 err = cmdutil.EnsureMongoServer(ensureServerParams) 336 if err != nil { 337 return err 338 } 339 340 peerAddr := mongo.SelectPeerAddress(addrs) 341 if peerAddr == "" { 342 return fmt.Errorf("no appropriate peer address found in %q", addrs) 343 } 344 peerHostPort := net.JoinHostPort(peerAddr, fmt.Sprint(servingInfo.StatePort)) 345 346 if err := initiateMongoServer(peergrouper.InitiateMongoParams{ 347 DialInfo: dialInfo, 348 MemberHostPort: peerHostPort, 349 }); err != nil { 350 return err 351 } 352 logger.Infof("started mongo") 353 return nil 354 } 355 356 // populateTools stores uploaded tools in provider storage 357 // and updates the tools metadata. 358 func (c *BootstrapCommand) populateTools(st *state.State, env environs.Environ) error { 359 agentConfig := c.CurrentConfig() 360 dataDir := agentConfig.DataDir() 361 362 current := version.Binary{ 363 Number: jujuversion.Current, 364 Arch: arch.HostArch(), 365 Series: series.HostSeries(), 366 } 367 tools, err := agenttools.ReadTools(dataDir, current) 368 if err != nil { 369 return errors.Trace(err) 370 } 371 372 data, err := ioutil.ReadFile(filepath.Join( 373 agenttools.SharedToolsDir(dataDir, current), 374 "tools.tar.gz", 375 )) 376 if err != nil { 377 return errors.Trace(err) 378 } 379 380 toolstorage, err := st.ToolsStorage() 381 if err != nil { 382 return errors.Trace(err) 383 } 384 defer toolstorage.Close() 385 386 var toolsVersions []version.Binary 387 if strings.HasPrefix(tools.URL, "file://") { 388 // Tools were uploaded: clone for each series of the same OS. 389 os, err := series.GetOSFromSeries(tools.Version.Series) 390 if err != nil { 391 return errors.Trace(err) 392 } 393 osSeries := series.OSSupportedSeries(os) 394 for _, series := range osSeries { 395 toolsVersion := tools.Version 396 toolsVersion.Series = series 397 toolsVersions = append(toolsVersions, toolsVersion) 398 } 399 } else { 400 // Tools were downloaded from an external source: don't clone. 401 toolsVersions = []version.Binary{tools.Version} 402 } 403 404 for _, toolsVersion := range toolsVersions { 405 metadata := binarystorage.Metadata{ 406 Version: toolsVersion.String(), 407 Size: tools.Size, 408 SHA256: tools.SHA256, 409 } 410 logger.Debugf("Adding tools: %v", toolsVersion) 411 if err := toolstorage.Add(bytes.NewReader(data), metadata); err != nil { 412 return errors.Trace(err) 413 } 414 } 415 return nil 416 } 417 418 // populateGUIArchive stores the uploaded Juju GUI archive in provider storage, 419 // updates the GUI metadata and set the current Juju GUI version. 420 func (c *BootstrapCommand) populateGUIArchive(st *state.State, env environs.Environ) error { 421 agentConfig := c.CurrentConfig() 422 dataDir := agentConfig.DataDir() 423 guistorage, err := st.GUIStorage() 424 if err != nil { 425 return errors.Trace(err) 426 } 427 defer guistorage.Close() 428 gui, err := agenttools.ReadGUIArchive(dataDir) 429 if err != nil { 430 return errors.Annotate(err, "cannot fetch GUI info") 431 } 432 f, err := os.Open(filepath.Join(agenttools.SharedGUIDir(dataDir), "gui.tar.bz2")) 433 if err != nil { 434 return errors.Annotate(err, "cannot read GUI archive") 435 } 436 defer f.Close() 437 if err := guistorage.Add(f, binarystorage.Metadata{ 438 Version: gui.Version.String(), 439 Size: gui.Size, 440 SHA256: gui.SHA256, 441 }); err != nil { 442 return errors.Annotate(err, "cannot store GUI archive") 443 } 444 if err = st.GUISetVersion(gui.Version); err != nil { 445 return errors.Annotate(err, "cannot set current GUI version") 446 } 447 return nil 448 } 449 450 // Override for testing. 451 var seriesFromVersion = series.VersionSeries 452 453 // saveCustomImageMetadata stores the custom image metadata to the database, 454 func (c *BootstrapCommand) saveCustomImageMetadata(st *state.State, env environs.Environ, imageMetadata []*imagemetadata.ImageMetadata) error { 455 logger.Debugf("saving custom image metadata") 456 return storeImageMetadataInState(st, env, "custom", simplestreams.CUSTOM_CLOUD_DATA, imageMetadata) 457 } 458 459 // storeImageMetadataInState writes image metadata into state store. 460 func storeImageMetadataInState(st *state.State, env environs.Environ, source string, priority int, existingMetadata []*imagemetadata.ImageMetadata) error { 461 if len(existingMetadata) == 0 { 462 return nil 463 } 464 cfg := env.Config() 465 metadataState := make([]cloudimagemetadata.Metadata, len(existingMetadata)) 466 for i, one := range existingMetadata { 467 m := cloudimagemetadata.Metadata{ 468 MetadataAttributes: cloudimagemetadata.MetadataAttributes{ 469 Stream: one.Stream, 470 Region: one.RegionName, 471 Arch: one.Arch, 472 VirtType: one.VirtType, 473 RootStorageType: one.Storage, 474 Source: source, 475 Version: one.Version, 476 }, 477 Priority: priority, 478 ImageId: one.Id, 479 } 480 s, err := seriesFromVersion(one.Version) 481 if err != nil { 482 return errors.Annotatef(err, "cannot determine series for version %v", one.Version) 483 } 484 m.Series = s 485 if m.Stream == "" { 486 m.Stream = cfg.ImageStream() 487 } 488 if m.Source == "" { 489 m.Source = "custom" 490 } 491 metadataState[i] = m 492 } 493 if err := st.CloudImageMetadataStorage.SaveMetadata(metadataState); err != nil { 494 return errors.Annotatef(err, "cannot cache image metadata") 495 } 496 return nil 497 }