github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/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 "encoding/base64" 8 "fmt" 9 "net" 10 "time" 11 12 "github.com/juju/cmd" 13 "launchpad.net/gnuflag" 14 "launchpad.net/goyaml" 15 16 "github.com/juju/juju/agent" 17 "github.com/juju/juju/constraints" 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/environs/config" 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/mongo" 22 "github.com/juju/juju/network" 23 "github.com/juju/juju/state" 24 "github.com/juju/juju/state/api/params" 25 "github.com/juju/juju/worker/peergrouper" 26 ) 27 28 type BootstrapCommand struct { 29 cmd.CommandBase 30 AgentConf 31 EnvConfig map[string]interface{} 32 Constraints constraints.Value 33 Hardware instance.HardwareCharacteristics 34 InstanceId string 35 } 36 37 // Info returns a decription of the command. 38 func (c *BootstrapCommand) Info() *cmd.Info { 39 return &cmd.Info{ 40 Name: "bootstrap-state", 41 Purpose: "initialize juju state", 42 } 43 } 44 45 func (c *BootstrapCommand) SetFlags(f *gnuflag.FlagSet) { 46 c.AgentConf.AddFlags(f) 47 yamlBase64Var(f, &c.EnvConfig, "env-config", "", "initial environment configuration (yaml, base64 encoded)") 48 f.Var(constraints.ConstraintsValue{Target: &c.Constraints}, "constraints", "initial environment constraints (space-separated strings)") 49 f.Var(&c.Hardware, "hardware", "hardware characteristics (space-separated strings)") 50 f.StringVar(&c.InstanceId, "instance-id", "", "unique instance-id for bootstrap machine") 51 } 52 53 // Init initializes the command for running. 54 func (c *BootstrapCommand) Init(args []string) error { 55 if len(c.EnvConfig) == 0 { 56 return requiredError("env-config") 57 } 58 if c.InstanceId == "" { 59 return requiredError("instance-id") 60 } 61 return c.AgentConf.CheckArgs(args) 62 } 63 64 // Run initializes state for an environment. 65 func (c *BootstrapCommand) Run(_ *cmd.Context) error { 66 envCfg, err := config.New(config.NoDefaults, c.EnvConfig) 67 if err != nil { 68 return err 69 } 70 err = c.ReadConfig("machine-0") 71 if err != nil { 72 return err 73 } 74 agentConfig := c.CurrentConfig() 75 76 // agent.Jobs is an optional field in the agent config, and was 77 // introduced after 1.17.2. We default to allowing units on 78 // machine-0 if missing. 79 jobs := agentConfig.Jobs() 80 if len(jobs) == 0 { 81 jobs = []params.MachineJob{ 82 params.JobManageEnviron, 83 params.JobHostUnits, 84 } 85 } 86 87 // Get the bootstrap machine's addresses from the provider. 88 env, err := environs.New(envCfg) 89 if err != nil { 90 return err 91 } 92 instanceId := instance.Id(c.InstanceId) 93 instances, err := env.Instances([]instance.Id{instanceId}) 94 if err != nil { 95 return err 96 } 97 addrs, err := instances[0].Addresses() 98 if err != nil { 99 return err 100 } 101 102 // Create system-identity file 103 if err := agent.WriteSystemIdentityFile(agentConfig); err != nil { 104 return err 105 } 106 107 // Generate a shared secret for the Mongo replica set, and write it out. 108 sharedSecret, err := mongo.GenerateSharedSecret() 109 if err != nil { 110 return err 111 } 112 info, ok := agentConfig.StateServingInfo() 113 if !ok { 114 return fmt.Errorf("bootstrap machine config has no state serving info") 115 } 116 info.SharedSecret = sharedSecret 117 err = c.ChangeConfig(func(agentConfig agent.ConfigSetter) { 118 agentConfig.SetStateServingInfo(info) 119 }) 120 if err != nil { 121 return fmt.Errorf("cannot write agent config: %v", err) 122 } 123 agentConfig = c.CurrentConfig() 124 125 if err := c.startMongo(addrs, agentConfig); err != nil { 126 return err 127 } 128 129 logger.Infof("started mongo") 130 // Initialise state, and store any agent config (e.g. password) changes. 131 var st *state.State 132 var m *state.Machine 133 err = nil 134 writeErr := c.ChangeConfig(func(agentConfig agent.ConfigSetter) { 135 st, m, err = agent.InitializeState( 136 agentConfig, 137 envCfg, 138 agent.BootstrapMachineConfig{ 139 Addresses: addrs, 140 Constraints: c.Constraints, 141 Jobs: jobs, 142 InstanceId: instanceId, 143 Characteristics: c.Hardware, 144 SharedSecret: sharedSecret, 145 }, 146 mongo.DefaultDialOpts(), 147 environs.NewStatePolicy(), 148 ) 149 }) 150 if writeErr != nil { 151 return fmt.Errorf("cannot write initial configuration: %v", err) 152 } 153 if err != nil { 154 return err 155 } 156 defer st.Close() 157 158 // bootstrap machine always gets the vote 159 return m.SetHasVote(true) 160 } 161 162 func (c *BootstrapCommand) startMongo(addrs []network.Address, agentConfig agent.Config) error { 163 logger.Debugf("starting mongo") 164 165 info, ok := agentConfig.StateInfo() 166 if !ok { 167 return fmt.Errorf("no state info available") 168 } 169 // When bootstrapping, we need to allow enough time for mongo 170 // to start as there's no retry loop in place. 171 // 5 minutes should suffice. 172 bootstrapDialOpts := mongo.DialOpts{Timeout: 5 * time.Minute} 173 dialInfo, err := mongo.DialInfo(info.Info, bootstrapDialOpts) 174 if err != nil { 175 return err 176 } 177 servingInfo, ok := agentConfig.StateServingInfo() 178 if !ok { 179 return fmt.Errorf("agent config has no state serving info") 180 } 181 // Use localhost to dial the mongo server, because it's running in 182 // auth mode and will refuse to perform any operations unless 183 // we dial that address. 184 dialInfo.Addrs = []string{ 185 net.JoinHostPort("127.0.0.1", fmt.Sprint(servingInfo.StatePort)), 186 } 187 188 logger.Debugf("calling ensureMongoServer") 189 err = ensureMongoServer( 190 agentConfig.DataDir(), 191 agentConfig.Value(agent.Namespace), 192 servingInfo, 193 ) 194 if err != nil { 195 return err 196 } 197 198 peerAddr := mongo.SelectPeerAddress(addrs) 199 if peerAddr == "" { 200 return fmt.Errorf("no appropriate peer address found in %q", addrs) 201 } 202 peerHostPort := net.JoinHostPort(peerAddr, fmt.Sprint(servingInfo.StatePort)) 203 204 return maybeInitiateMongoServer(peergrouper.InitiateMongoParams{ 205 DialInfo: dialInfo, 206 MemberHostPort: peerHostPort, 207 }) 208 } 209 210 // yamlBase64Value implements gnuflag.Value on a map[string]interface{}. 211 type yamlBase64Value map[string]interface{} 212 213 // Set decodes the base64 value into yaml then expands that into a map. 214 func (v *yamlBase64Value) Set(value string) error { 215 decoded, err := base64.StdEncoding.DecodeString(value) 216 if err != nil { 217 return err 218 } 219 return goyaml.Unmarshal(decoded, v) 220 } 221 222 func (v *yamlBase64Value) String() string { 223 return fmt.Sprintf("%v", *v) 224 } 225 226 // yamlBase64Var sets up a gnuflag flag analogous to the FlagSet.*Var methods. 227 func yamlBase64Var(fs *gnuflag.FlagSet, target *map[string]interface{}, name string, value string, usage string) { 228 fs.Var((*yamlBase64Value)(target), name, usage) 229 }