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