github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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 "encoding/base64" 9 "fmt" 10 "io/ioutil" 11 "net" 12 "os" 13 "path/filepath" 14 "strings" 15 "time" 16 17 "github.com/juju/cmd" 18 "github.com/juju/errors" 19 "github.com/juju/loggo" 20 "github.com/juju/names" 21 "github.com/juju/utils" 22 "github.com/juju/utils/series" 23 goyaml "gopkg.in/yaml.v1" 24 "launchpad.net/gnuflag" 25 26 "github.com/juju/juju/agent" 27 agenttools "github.com/juju/juju/agent/tools" 28 agentcmd "github.com/juju/juju/cmd/jujud/agent" 29 cmdutil "github.com/juju/juju/cmd/jujud/util" 30 "github.com/juju/juju/constraints" 31 "github.com/juju/juju/environs" 32 "github.com/juju/juju/environs/config" 33 "github.com/juju/juju/environs/imagemetadata" 34 "github.com/juju/juju/environs/simplestreams" 35 "github.com/juju/juju/instance" 36 "github.com/juju/juju/mongo" 37 "github.com/juju/juju/network" 38 "github.com/juju/juju/state" 39 "github.com/juju/juju/state/cloudimagemetadata" 40 "github.com/juju/juju/state/multiwatcher" 41 "github.com/juju/juju/state/storage" 42 "github.com/juju/juju/state/toolstorage" 43 "github.com/juju/juju/storage/poolmanager" 44 "github.com/juju/juju/utils/ssh" 45 "github.com/juju/juju/version" 46 "github.com/juju/juju/worker/peergrouper" 47 ) 48 49 var ( 50 maybeInitiateMongoServer = peergrouper.MaybeInitiateMongoServer 51 agentInitializeState = agent.InitializeState 52 sshGenerateKey = ssh.GenerateKey 53 newStateStorage = storage.NewStorage 54 minSocketTimeout = 1 * time.Minute 55 logger = loggo.GetLogger("juju.cmd.jujud") 56 ) 57 58 type BootstrapCommand struct { 59 cmd.CommandBase 60 agentcmd.AgentConf 61 EnvConfig map[string]interface{} 62 Constraints constraints.Value 63 Hardware instance.HardwareCharacteristics 64 InstanceId string 65 AdminUsername string 66 ImageMetadataDir string 67 } 68 69 // NewBootstrapCommand returns a new BootstrapCommand that has been initialized. 70 func NewBootstrapCommand() *BootstrapCommand { 71 return &BootstrapCommand{ 72 AgentConf: agentcmd.NewAgentConf(""), 73 } 74 } 75 76 // Info returns a decription of the command. 77 func (c *BootstrapCommand) Info() *cmd.Info { 78 return &cmd.Info{ 79 Name: "bootstrap-state", 80 Purpose: "initialize juju state", 81 } 82 } 83 84 func (c *BootstrapCommand) SetFlags(f *gnuflag.FlagSet) { 85 c.AgentConf.AddFlags(f) 86 yamlBase64Var(f, &c.EnvConfig, "env-config", "", "initial environment configuration (yaml, base64 encoded)") 87 f.Var(constraints.ConstraintsValue{Target: &c.Constraints}, "constraints", "initial environment constraints (space-separated strings)") 88 f.Var(&c.Hardware, "hardware", "hardware characteristics (space-separated strings)") 89 f.StringVar(&c.InstanceId, "instance-id", "", "unique instance-id for bootstrap machine") 90 f.StringVar(&c.AdminUsername, "admin-user", "admin", "set the name for the juju admin user") 91 f.StringVar(&c.ImageMetadataDir, "image-metadata", "", "custom image metadata source dir") 92 } 93 94 // Init initializes the command for running. 95 func (c *BootstrapCommand) Init(args []string) error { 96 if len(c.EnvConfig) == 0 { 97 return cmdutil.RequiredError("env-config") 98 } 99 if c.InstanceId == "" { 100 return cmdutil.RequiredError("instance-id") 101 } 102 if !names.IsValidUser(c.AdminUsername) { 103 return errors.Errorf("%q is not a valid username", c.AdminUsername) 104 } 105 return c.AgentConf.CheckArgs(args) 106 } 107 108 // Run initializes state for an environment. 109 func (c *BootstrapCommand) Run(_ *cmd.Context) error { 110 envCfg, err := config.New(config.NoDefaults, c.EnvConfig) 111 if err != nil { 112 return err 113 } 114 err = c.ReadConfig("machine-0") 115 if err != nil { 116 return err 117 } 118 agentConfig := c.CurrentConfig() 119 network.InitializeFromConfig(agentConfig) 120 121 // agent.Jobs is an optional field in the agent config, and was 122 // introduced after 1.17.2. We default to allowing units on 123 // machine-0 if missing. 124 jobs := agentConfig.Jobs() 125 if len(jobs) == 0 { 126 jobs = []multiwatcher.MachineJob{ 127 multiwatcher.JobManageEnviron, 128 multiwatcher.JobHostUnits, 129 multiwatcher.JobManageNetworking, 130 } 131 } 132 133 // Get the bootstrap machine's addresses from the provider. 134 env, err := environs.New(envCfg) 135 if err != nil { 136 return err 137 } 138 instanceId := instance.Id(c.InstanceId) 139 instances, err := env.Instances([]instance.Id{instanceId}) 140 if err != nil { 141 return err 142 } 143 addrs, err := instances[0].Addresses() 144 if err != nil { 145 return err 146 } 147 148 // Generate a private SSH key for the state servers, and add 149 // the public key to the environment config. We'll add the 150 // private key to StateServingInfo below. 151 privateKey, publicKey, err := sshGenerateKey(config.JujuSystemKey) 152 if err != nil { 153 return errors.Annotate(err, "failed to generate system key") 154 } 155 authorizedKeys := config.ConcatAuthKeys(envCfg.AuthorizedKeys(), publicKey) 156 envCfg, err = env.Config().Apply(map[string]interface{}{ 157 config.AuthKeysConfig: authorizedKeys, 158 }) 159 if err != nil { 160 return errors.Annotate(err, "failed to add public key to environment config") 161 } 162 163 // Generate a shared secret for the Mongo replica set, and write it out. 164 sharedSecret, err := mongo.GenerateSharedSecret() 165 if err != nil { 166 return err 167 } 168 info, ok := agentConfig.StateServingInfo() 169 if !ok { 170 return fmt.Errorf("bootstrap machine config has no state serving info") 171 } 172 info.SharedSecret = sharedSecret 173 info.SystemIdentity = privateKey 174 err = c.ChangeConfig(func(agentConfig agent.ConfigSetter) error { 175 agentConfig.SetStateServingInfo(info) 176 return nil 177 }) 178 if err != nil { 179 return fmt.Errorf("cannot write agent config: %v", err) 180 } 181 agentConfig = c.CurrentConfig() 182 183 // Create system-identity file 184 if err := agent.WriteSystemIdentityFile(agentConfig); err != nil { 185 return err 186 } 187 188 if err := c.startMongo(addrs, agentConfig); err != nil { 189 return err 190 } 191 192 logger.Infof("started mongo") 193 // Initialise state, and store any agent config (e.g. password) changes. 194 var st *state.State 195 var m *state.Machine 196 err = c.ChangeConfig(func(agentConfig agent.ConfigSetter) error { 197 var stateErr error 198 dialOpts := mongo.DefaultDialOpts() 199 200 // Set a longer socket timeout than usual, as the machine 201 // will be starting up and disk I/O slower than usual. This 202 // has been known to cause timeouts in queries. 203 timeouts := envCfg.BootstrapSSHOpts() 204 dialOpts.SocketTimeout = timeouts.Timeout 205 if dialOpts.SocketTimeout < minSocketTimeout { 206 dialOpts.SocketTimeout = minSocketTimeout 207 } 208 209 // We shouldn't attempt to dial peers until we have some. 210 dialOpts.Direct = true 211 212 adminTag := names.NewLocalUserTag(c.AdminUsername) 213 st, m, stateErr = agentInitializeState( 214 adminTag, 215 agentConfig, 216 envCfg, 217 agent.BootstrapMachineConfig{ 218 Addresses: addrs, 219 Constraints: c.Constraints, 220 Jobs: jobs, 221 InstanceId: instanceId, 222 Characteristics: c.Hardware, 223 SharedSecret: sharedSecret, 224 }, 225 dialOpts, 226 environs.NewStatePolicy(), 227 ) 228 return stateErr 229 }) 230 if err != nil { 231 return err 232 } 233 defer st.Close() 234 235 // Populate the tools catalogue. 236 if err := c.populateTools(st, env); err != nil { 237 return err 238 } 239 240 // Add custom image metadata to environment storage. 241 if c.ImageMetadataDir != "" { 242 if err := c.saveCustomImageMetadata(st); err != nil { 243 return err 244 } 245 246 // TODO (anastasiamac 2015-09-24) Remove this once search path is updated.. 247 stor := newStateStorage(st.EnvironUUID(), st.MongoSession()) 248 if err := c.storeCustomImageMetadata(stor); err != nil { 249 return err 250 } 251 } 252 253 // Populate the storage pools. 254 if err := c.populateDefaultStoragePools(st); err != nil { 255 return err 256 } 257 258 // bootstrap machine always gets the vote 259 return m.SetHasVote(true) 260 } 261 262 func (c *BootstrapCommand) startMongo(addrs []network.Address, agentConfig agent.Config) error { 263 logger.Debugf("starting mongo") 264 265 info, ok := agentConfig.MongoInfo() 266 if !ok { 267 return fmt.Errorf("no state info available") 268 } 269 // When bootstrapping, we need to allow enough time for mongo 270 // to start as there's no retry loop in place. 271 // 5 minutes should suffice. 272 bootstrapDialOpts := mongo.DialOpts{Timeout: 5 * time.Minute} 273 dialInfo, err := mongo.DialInfo(info.Info, bootstrapDialOpts) 274 if err != nil { 275 return err 276 } 277 servingInfo, ok := agentConfig.StateServingInfo() 278 if !ok { 279 return fmt.Errorf("agent config has no state serving info") 280 } 281 // Use localhost to dial the mongo server, because it's running in 282 // auth mode and will refuse to perform any operations unless 283 // we dial that address. 284 dialInfo.Addrs = []string{ 285 net.JoinHostPort("127.0.0.1", fmt.Sprint(servingInfo.StatePort)), 286 } 287 288 logger.Debugf("calling ensureMongoServer") 289 ensureServerParams, err := cmdutil.NewEnsureServerParams(agentConfig) 290 if err != nil { 291 return err 292 } 293 err = cmdutil.EnsureMongoServer(ensureServerParams) 294 if err != nil { 295 return err 296 } 297 298 peerAddr := mongo.SelectPeerAddress(addrs) 299 if peerAddr == "" { 300 return fmt.Errorf("no appropriate peer address found in %q", addrs) 301 } 302 peerHostPort := net.JoinHostPort(peerAddr, fmt.Sprint(servingInfo.StatePort)) 303 304 return maybeInitiateMongoServer(peergrouper.InitiateMongoParams{ 305 DialInfo: dialInfo, 306 MemberHostPort: peerHostPort, 307 }) 308 } 309 310 // populateDefaultStoragePools creates the default storage pools. 311 func (c *BootstrapCommand) populateDefaultStoragePools(st *state.State) error { 312 settings := state.NewStateSettings(st) 313 return poolmanager.AddDefaultStoragePools(settings) 314 } 315 316 // populateTools stores uploaded tools in provider storage 317 // and updates the tools metadata. 318 func (c *BootstrapCommand) populateTools(st *state.State, env environs.Environ) error { 319 agentConfig := c.CurrentConfig() 320 dataDir := agentConfig.DataDir() 321 tools, err := agenttools.ReadTools(dataDir, version.Current) 322 if err != nil { 323 return errors.Trace(err) 324 } 325 326 data, err := ioutil.ReadFile(filepath.Join( 327 agenttools.SharedToolsDir(dataDir, version.Current), 328 "tools.tar.gz", 329 )) 330 if err != nil { 331 return errors.Trace(err) 332 } 333 334 storage, err := st.ToolsStorage() 335 if err != nil { 336 return errors.Trace(err) 337 } 338 defer storage.Close() 339 340 var toolsVersions []version.Binary 341 if strings.HasPrefix(tools.URL, "file://") { 342 // Tools were uploaded: clone for each series of the same OS. 343 os, err := series.GetOSFromSeries(tools.Version.Series) 344 if err != nil { 345 return errors.Trace(err) 346 } 347 osSeries := series.OSSupportedSeries(os) 348 for _, series := range osSeries { 349 toolsVersion := tools.Version 350 toolsVersion.Series = series 351 toolsVersions = append(toolsVersions, toolsVersion) 352 } 353 } else { 354 // Tools were downloaded from an external source: don't clone. 355 toolsVersions = []version.Binary{tools.Version} 356 } 357 358 for _, toolsVersion := range toolsVersions { 359 metadata := toolstorage.Metadata{ 360 Version: toolsVersion, 361 Size: tools.Size, 362 SHA256: tools.SHA256, 363 } 364 logger.Debugf("Adding tools: %v", toolsVersion) 365 if err := storage.AddTools(bytes.NewReader(data), metadata); err != nil { 366 return errors.Trace(err) 367 } 368 } 369 return nil 370 } 371 372 // storeCustomImageMetadata reads the custom image metadata from disk, 373 // and stores the files in environment storage with the same relative 374 // paths. 375 func (c *BootstrapCommand) storeCustomImageMetadata(stor storage.Storage) error { 376 logger.Debugf("storing custom image metadata from %q", c.ImageMetadataDir) 377 return filepath.Walk(c.ImageMetadataDir, func(abspath string, info os.FileInfo, err error) error { 378 if err != nil { 379 return err 380 } 381 if !info.Mode().IsRegular() { 382 return nil 383 } 384 relpath, err := filepath.Rel(c.ImageMetadataDir, abspath) 385 if err != nil { 386 return err 387 } 388 f, err := os.Open(abspath) 389 if err != nil { 390 return err 391 } 392 defer f.Close() 393 relpath = filepath.ToSlash(relpath) 394 logger.Debugf("storing %q in environment storage (%d bytes)", relpath, info.Size()) 395 return stor.Put(relpath, f, info.Size()) 396 }) 397 } 398 399 // Override for testing. 400 var seriesFromVersion = series.VersionSeries 401 402 // saveCustomImageMetadata reads the custom image metadata from disk, 403 // and saves it in state server. 404 func (c *BootstrapCommand) saveCustomImageMetadata(st *state.State) error { 405 logger.Debugf("saving custom image metadata from %q", c.ImageMetadataDir) 406 407 baseURL := fmt.Sprintf("file://%s", filepath.ToSlash(c.ImageMetadataDir)) 408 datasource := simplestreams.NewURLDataSource("bootstrap metadata", baseURL, utils.NoVerifySSLHostnames) 409 410 // Read the image metadata, as we'll want to upload it to the environment. 411 imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{}) 412 existingMetadata, _, err := imagemetadata.Fetch( 413 []simplestreams.DataSource{datasource}, imageConstraint, false) 414 if err != nil && !errors.IsNotFound(err) { 415 return errors.Annotate(err, "cannot read image metadata") 416 } 417 418 if len(existingMetadata) == 0 { 419 return nil 420 } 421 msg := "" 422 for _, one := range existingMetadata { 423 m := cloudimagemetadata.Metadata{ 424 cloudimagemetadata.MetadataAttributes{ 425 Stream: one.Stream, 426 Region: one.RegionName, 427 Arch: one.Arch, 428 VirtType: one.VirtType, 429 RootStorageType: one.Storage, 430 Source: "custom", 431 }, 432 one.Id, 433 } 434 s, err := seriesFromVersion(one.Version) 435 if err != nil { 436 return errors.Annotatef(err, "cannot determine series for version %v", one.Version) 437 } 438 m.Series = s 439 err = st.CloudImageMetadataStorage.SaveMetadata(m) 440 if err != nil { 441 return errors.Annotatef(err, "cannot cache image metadata %v", m) 442 } 443 } 444 if len(msg) > 0 { 445 return errors.New(msg) 446 } 447 return nil 448 } 449 450 // yamlBase64Value implements gnuflag.Value on a map[string]interface{}. 451 type yamlBase64Value map[string]interface{} 452 453 // Set decodes the base64 value into yaml then expands that into a map. 454 func (v *yamlBase64Value) Set(value string) error { 455 decoded, err := base64.StdEncoding.DecodeString(value) 456 if err != nil { 457 return err 458 } 459 return goyaml.Unmarshal(decoded, v) 460 } 461 462 func (v *yamlBase64Value) String() string { 463 return fmt.Sprintf("%v", *v) 464 } 465 466 // yamlBase64Var sets up a gnuflag flag analogous to the FlagSet.*Var methods. 467 func yamlBase64Var(fs *gnuflag.FlagSet, target *map[string]interface{}, name string, value string, usage string) { 468 fs.Var((*yamlBase64Value)(target), name, usage) 469 }