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  }