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  }