github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/environs/cloudinit.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package environs
     5  
     6  import (
     7  	"fmt"
     8  	"path"
     9  	"strconv"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names"
    13  	"github.com/juju/utils"
    14  	"github.com/juju/utils/proxy"
    15  
    16  	"github.com/juju/juju/agent"
    17  	"github.com/juju/juju/api"
    18  	"github.com/juju/juju/apiserver/params"
    19  	coreCloudinit "github.com/juju/juju/cloudinit"
    20  	"github.com/juju/juju/constraints"
    21  	"github.com/juju/juju/environs/cloudinit"
    22  	"github.com/juju/juju/environs/config"
    23  	"github.com/juju/juju/juju/paths"
    24  	"github.com/juju/juju/mongo"
    25  	"github.com/juju/juju/state/multiwatcher"
    26  	"github.com/juju/juju/version"
    27  )
    28  
    29  // DataDir is the default data directory.  Tests can override this
    30  // where needed, so they don't need to mess with global system state.
    31  var DataDir = agent.DefaultDataDir
    32  
    33  // logDir returns a filesystem path to the location where applications
    34  // may create a folder containing logs
    35  var logDir = paths.MustSucceed(paths.LogDir(version.Current.Series))
    36  
    37  // DefaultBridgeName is the network bridge device name used for LXC
    38  // and KVM containers.
    39  const DefaultBridgeName = "juju-br0"
    40  
    41  // NewMachineConfig sets up a basic machine configuration, for a
    42  // non-bootstrap node. You'll still need to supply more information,
    43  // but this takes care of the fixed entries and the ones that are
    44  // always needed.
    45  func NewMachineConfig(
    46  	machineID,
    47  	machineNonce,
    48  	imageStream,
    49  	series string,
    50  	secureServerConnections bool,
    51  	networks []string,
    52  	mongoInfo *mongo.MongoInfo,
    53  	apiInfo *api.Info,
    54  ) (*cloudinit.MachineConfig, error) {
    55  	dataDir, err := paths.DataDir(series)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  	logDir, err := paths.LogDir(series)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	cloudInitOutputLog := path.Join(logDir, "cloud-init-output.log")
    64  	mcfg := &cloudinit.MachineConfig{
    65  		// Fixed entries.
    66  		DataDir:                 dataDir,
    67  		LogDir:                  path.Join(logDir, "juju"),
    68  		Jobs:                    []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
    69  		CloudInitOutputLog:      cloudInitOutputLog,
    70  		MachineAgentServiceName: "jujud-" + names.NewMachineTag(machineID).String(),
    71  		Series:                  series,
    72  
    73  		// Parameter entries.
    74  		MachineId:    machineID,
    75  		MachineNonce: machineNonce,
    76  		Networks:     networks,
    77  		MongoInfo:    mongoInfo,
    78  		APIInfo:      apiInfo,
    79  		ImageStream:  imageStream,
    80  		AgentEnvironment: map[string]string{
    81  			agent.AllowsSecureConnection: strconv.FormatBool(secureServerConnections),
    82  		},
    83  	}
    84  	return mcfg, nil
    85  }
    86  
    87  // NewBootstrapMachineConfig sets up a basic machine configuration for a
    88  // bootstrap node.  You'll still need to supply more information, but this
    89  // takes care of the fixed entries and the ones that are always needed.
    90  func NewBootstrapMachineConfig(cons constraints.Value, series string) (*cloudinit.MachineConfig, error) {
    91  	// For a bootstrap instance, FinishMachineConfig will provide the
    92  	// state.Info and the api.Info. The machine id must *always* be "0".
    93  	mcfg, err := NewMachineConfig("0", agent.BootstrapNonce, "", series, true, nil, nil, nil)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	mcfg.Bootstrap = true
    98  	mcfg.Jobs = []multiwatcher.MachineJob{
    99  		multiwatcher.JobManageEnviron,
   100  		multiwatcher.JobHostUnits,
   101  	}
   102  	mcfg.Constraints = cons
   103  	return mcfg, nil
   104  }
   105  
   106  // PopulateMachineConfig is called both from the FinishMachineConfig below,
   107  // which does have access to the environment config, and from the container
   108  // provisioners, which don't have access to the environment config. Everything
   109  // that is needed to provision a container needs to be returned to the
   110  // provisioner in the ContainerConfig structure. Those values are then used to
   111  // call this function.
   112  func PopulateMachineConfig(mcfg *cloudinit.MachineConfig,
   113  	providerType, authorizedKeys string,
   114  	sslHostnameVerification bool,
   115  	proxySettings, aptProxySettings proxy.Settings,
   116  	aptMirror string,
   117  	preferIPv6 bool,
   118  	enableOSRefreshUpdates bool,
   119  	enableOSUpgrade bool,
   120  ) error {
   121  	if authorizedKeys == "" {
   122  		return fmt.Errorf("environment configuration has no authorized-keys")
   123  	}
   124  	mcfg.AuthorizedKeys = authorizedKeys
   125  	if mcfg.AgentEnvironment == nil {
   126  		mcfg.AgentEnvironment = make(map[string]string)
   127  	}
   128  	mcfg.AgentEnvironment[agent.ProviderType] = providerType
   129  	mcfg.AgentEnvironment[agent.ContainerType] = string(mcfg.MachineContainerType)
   130  	mcfg.DisableSSLHostnameVerification = !sslHostnameVerification
   131  	mcfg.ProxySettings = proxySettings
   132  	mcfg.AptProxySettings = aptProxySettings
   133  	mcfg.AptMirror = aptMirror
   134  	mcfg.PreferIPv6 = preferIPv6
   135  	mcfg.EnableOSRefreshUpdate = enableOSRefreshUpdates
   136  	mcfg.EnableOSUpgrade = enableOSUpgrade
   137  	return nil
   138  }
   139  
   140  // FinishMachineConfig sets fields on a MachineConfig that can be determined by
   141  // inspecting a plain config.Config and the machine constraints at the last
   142  // moment before bootstrapping. It assumes that the supplied Config comes from
   143  // an environment that has passed through all the validation checks in the
   144  // Bootstrap func, and that has set an agent-version (via finding the tools to,
   145  // use for bootstrap, or otherwise).
   146  // TODO(fwereade) This function is not meant to be "good" in any serious way:
   147  // it is better that this functionality be collected in one place here than
   148  // that it be spread out across 3 or 4 providers, but this is its only
   149  // redeeming feature.
   150  func FinishMachineConfig(mcfg *cloudinit.MachineConfig, cfg *config.Config) (err error) {
   151  	defer errors.DeferredAnnotatef(&err, "cannot complete machine configuration")
   152  
   153  	if err := PopulateMachineConfig(
   154  		mcfg,
   155  		cfg.Type(),
   156  		cfg.AuthorizedKeys(),
   157  		cfg.SSLHostnameVerification(),
   158  		cfg.ProxySettings(),
   159  		cfg.AptProxySettings(),
   160  		cfg.AptMirror(),
   161  		cfg.PreferIPv6(),
   162  		cfg.EnableOSRefreshUpdate(),
   163  		cfg.EnableOSUpgrade(),
   164  	); err != nil {
   165  		return errors.Trace(err)
   166  	}
   167  
   168  	if isStateMachineConfig(mcfg) {
   169  		// Add NUMACTL preference. Needed to work for both bootstrap and high availability
   170  		// Only makes sense for state server
   171  		logger.Debugf("Setting numa ctl preference to %v", cfg.NumaCtlPreference())
   172  		// Unfortunately, AgentEnvironment can only take strings as values
   173  		mcfg.AgentEnvironment[agent.NumaCtlPreference] = fmt.Sprintf("%v", cfg.NumaCtlPreference())
   174  	}
   175  	// The following settings are only appropriate at bootstrap time. At the
   176  	// moment, the only state server is the bootstrap node, but this
   177  	// will probably change.
   178  	if !mcfg.Bootstrap {
   179  		return nil
   180  	}
   181  	if mcfg.APIInfo != nil || mcfg.MongoInfo != nil {
   182  		return errors.New("machine configuration already has api/state info")
   183  	}
   184  	caCert, hasCACert := cfg.CACert()
   185  	if !hasCACert {
   186  		return errors.New("environment configuration has no ca-cert")
   187  	}
   188  	password := cfg.AdminSecret()
   189  	if password == "" {
   190  		return errors.New("environment configuration has no admin-secret")
   191  	}
   192  	passwordHash := utils.UserPasswordHash(password, utils.CompatSalt)
   193  	envUUID, uuidSet := cfg.UUID()
   194  	if !uuidSet {
   195  		return errors.New("config missing environment uuid")
   196  	}
   197  	mcfg.APIInfo = &api.Info{
   198  		Password:   passwordHash,
   199  		CACert:     caCert,
   200  		EnvironTag: names.NewEnvironTag(envUUID),
   201  	}
   202  	mcfg.MongoInfo = &mongo.MongoInfo{Password: passwordHash, Info: mongo.Info{CACert: caCert}}
   203  
   204  	// These really are directly relevant to running a state server.
   205  	// Initially, generate a state server certificate with no host IP
   206  	// addresses in the SAN field. Once the state server is up and the
   207  	// NIC addresses become known, the certificate can be regenerated.
   208  	cert, key, err := cfg.GenerateStateServerCertAndKey(nil)
   209  	if err != nil {
   210  		return errors.Annotate(err, "cannot generate state server certificate")
   211  	}
   212  	caPrivateKey, hasCAPrivateKey := cfg.CAPrivateKey()
   213  	if !hasCAPrivateKey {
   214  		return errors.New("environment configuration has no ca-private-key")
   215  	}
   216  	srvInfo := params.StateServingInfo{
   217  		StatePort:    cfg.StatePort(),
   218  		APIPort:      cfg.APIPort(),
   219  		Cert:         string(cert),
   220  		PrivateKey:   string(key),
   221  		CAPrivateKey: caPrivateKey,
   222  	}
   223  	mcfg.StateServingInfo = &srvInfo
   224  	if mcfg.Config, err = BootstrapConfig(cfg); err != nil {
   225  		return errors.Trace(err)
   226  	}
   227  
   228  	return nil
   229  }
   230  
   231  // isStateMachineConfig determines if given machine configuration
   232  // is for State Server by iterating over machine's jobs.
   233  // If JobManageEnviron is present, this is a state server.
   234  func isStateMachineConfig(mcfg *cloudinit.MachineConfig) bool {
   235  	for _, aJob := range mcfg.Jobs {
   236  		if aJob == multiwatcher.JobManageEnviron {
   237  			return true
   238  		}
   239  	}
   240  	return false
   241  }
   242  
   243  func configureCloudinit(mcfg *cloudinit.MachineConfig, cloudcfg *coreCloudinit.Config) (cloudinit.UserdataConfig, error) {
   244  	// When bootstrapping, we only want to apt-get update/upgrade
   245  	// and setup the SSH keys. The rest we leave to cloudinit/sshinit.
   246  	udata, err := cloudinit.NewUserdataConfig(mcfg, cloudcfg)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  	if mcfg.Bootstrap {
   251  		err = udata.ConfigureBasic()
   252  		if err != nil {
   253  			return nil, err
   254  		}
   255  		return udata, nil
   256  	}
   257  	err = udata.Configure()
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  	return udata, nil
   262  }
   263  
   264  // ComposeUserData fills out the provided cloudinit configuration structure
   265  // so it is suitable for initialising a machine with the given configuration,
   266  // and then renders it and returns it as a binary (gzipped) blob of user data.
   267  //
   268  // If the provided cloudcfg is nil, a new one will be created internally.
   269  func ComposeUserData(mcfg *cloudinit.MachineConfig, cloudcfg *coreCloudinit.Config) ([]byte, error) {
   270  	if cloudcfg == nil {
   271  		cloudcfg = coreCloudinit.New()
   272  	}
   273  	udata, err := configureCloudinit(mcfg, cloudcfg)
   274  	if err != nil {
   275  		return nil, err
   276  	}
   277  	data, err := udata.Render()
   278  	logger.Tracef("Generated cloud init:\n%s", string(data))
   279  	if err != nil {
   280  		return nil, err
   281  	}
   282  	return utils.Gzip(data), nil
   283  }