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