github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cloudconfig/podcfg/podcfg.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package podcfg
     5  
     6  import (
     7  	"net"
     8  	"path"
     9  	"strconv"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/version"
    14  	"gopkg.in/juju/names.v2"
    15  
    16  	"github.com/juju/juju/agent"
    17  	"github.com/juju/juju/api"
    18  	"github.com/juju/juju/cloudconfig/instancecfg"
    19  	"github.com/juju/juju/controller"
    20  	"github.com/juju/juju/environs/config"
    21  	"github.com/juju/juju/environs/tags"
    22  	"github.com/juju/juju/juju/paths"
    23  	"github.com/juju/juju/state/multiwatcher"
    24  )
    25  
    26  var logger = loggo.GetLogger("juju.cloudconfig.podcfg")
    27  
    28  // ControllerPodConfig represents initialization information for a new juju caas controller pod.
    29  type ControllerPodConfig struct {
    30  	// Tags is a set of tags/labels to set on the Pod, if supported. This
    31  	// should be populated using the PodLabels method in this package.
    32  	Tags map[string]string
    33  
    34  	// Bootstrap contains bootstrap-specific configuration. If this is set,
    35  	// Controller must also be set.
    36  	Bootstrap *BootstrapConfig
    37  
    38  	// Controller contains controller-specific configuration. If this is
    39  	// set, then the instance will be configured as a controller pod.
    40  	Controller *ControllerConfig
    41  
    42  	// APIInfo holds the means for the new pod to communicate with the
    43  	// juju state API. Unless the new pod is running a controller (Controller is
    44  	// set), there must be at least one controller address supplied.
    45  	// The entity name must match that of the pod being started,
    46  	// or be empty when starting a controller.
    47  	APIInfo *api.Info
    48  
    49  	// ControllerTag identifies the controller.
    50  	ControllerTag names.ControllerTag
    51  
    52  	// JujuVersion is the juju version.
    53  	JujuVersion version.Number
    54  
    55  	// DataDir holds the directory that juju state will be put in the new
    56  	// instance.
    57  	DataDir string
    58  
    59  	// LogDir holds the directory that juju logs will be written to.
    60  	LogDir string
    61  
    62  	// MetricsSpoolDir represents the spool directory path, where all
    63  	// metrics are stored.
    64  	MetricsSpoolDir string
    65  
    66  	// Jobs holds what machine jobs to run.
    67  	Jobs []multiwatcher.MachineJob
    68  
    69  	// MachineId identifies the new machine.
    70  	MachineId string // TODO(caas): change it to PodId once we introduced the new tag for pod.
    71  
    72  	// AgentEnvironment defines additional configuration variables to set in
    73  	// the pod agent config.
    74  	AgentEnvironment map[string]string
    75  }
    76  
    77  // BootstrapConfig represents bootstrap-specific initialization information
    78  // for a new juju caas pod. This is only relevant for the bootstrap pod.
    79  type BootstrapConfig struct {
    80  	instancecfg.BootstrapConfig
    81  }
    82  
    83  // ControllerConfig represents controller-specific initialization information
    84  // for a new juju caas pod. This is only relevant for controller pod.
    85  type ControllerConfig struct {
    86  	instancecfg.ControllerConfig
    87  }
    88  
    89  // AgentConfig returns an agent config.
    90  func (cfg *ControllerPodConfig) AgentConfig(tag names.Tag) (agent.ConfigSetterWriter, error) {
    91  	var password, cacert string
    92  	if cfg.Controller == nil {
    93  		password = cfg.APIInfo.Password
    94  		cacert = cfg.APIInfo.CACert
    95  	} else {
    96  		password = cfg.Controller.MongoInfo.Password
    97  		cacert = cfg.Controller.MongoInfo.CACert
    98  	}
    99  	configParams := agent.AgentConfigParams{
   100  		Paths: agent.Paths{
   101  			DataDir:         cfg.DataDir,
   102  			LogDir:          cfg.LogDir,
   103  			MetricsSpoolDir: cfg.MetricsSpoolDir,
   104  		},
   105  		Jobs:              cfg.Jobs,
   106  		Tag:               tag,
   107  		UpgradedToVersion: cfg.JujuVersion,
   108  		Password:          password,
   109  		APIAddresses:      cfg.APIHostAddrs(),
   110  		CACert:            cacert,
   111  		Values:            cfg.AgentEnvironment,
   112  		Controller:        cfg.ControllerTag,
   113  		Model:             cfg.APIInfo.ModelTag,
   114  	}
   115  	return agent.NewStateMachineConfig(configParams, cfg.Bootstrap.StateServingInfo)
   116  }
   117  
   118  // APIHostAddrs returns a list of api server addresses.
   119  func (cfg *ControllerPodConfig) APIHostAddrs() []string {
   120  	var hosts []string
   121  	if cfg.Bootstrap != nil {
   122  		hosts = append(hosts, net.JoinHostPort(
   123  			"localhost", strconv.Itoa(cfg.Bootstrap.StateServingInfo.APIPort)),
   124  		)
   125  	}
   126  	if cfg.APIInfo != nil {
   127  		hosts = append(hosts, cfg.APIInfo.Addrs...)
   128  	}
   129  	return hosts
   130  }
   131  
   132  // APIHosts returns api a list of server addresses.
   133  func (cfg *ControllerPodConfig) APIHosts() []string {
   134  	var hosts []string
   135  	if cfg.Bootstrap != nil {
   136  		hosts = append(hosts, "localhost")
   137  	}
   138  	if cfg.APIInfo != nil {
   139  		for _, addr := range cfg.APIInfo.Addrs {
   140  			host, _, err := net.SplitHostPort(addr)
   141  			if err != nil {
   142  				logger.Errorf("Can't split API address %q to host:port - %q", host, err)
   143  				continue
   144  			}
   145  			hosts = append(hosts, host)
   146  		}
   147  	}
   148  	return hosts
   149  }
   150  
   151  // VerifyConfig verifies that the ControllerPodConfig is valid.
   152  func (cfg *ControllerPodConfig) VerifyConfig() (err error) {
   153  	defer errors.DeferredAnnotatef(&err, "invalid machine configuration")
   154  	if !names.IsValidMachine(cfg.MachineId) {
   155  		return errors.New("invalid machine id")
   156  	}
   157  	if cfg.DataDir == "" {
   158  		return errors.New("missing var directory")
   159  	}
   160  	if cfg.LogDir == "" {
   161  		return errors.New("missing log directory")
   162  	}
   163  	if cfg.MetricsSpoolDir == "" {
   164  		return errors.New("missing metrics spool directory")
   165  	}
   166  	if len(cfg.Jobs) == 0 {
   167  		return errors.New("missing machine jobs")
   168  	}
   169  	if cfg.JujuVersion == version.Zero {
   170  		return errors.New("missing juju version")
   171  	}
   172  	if cfg.APIInfo == nil {
   173  		return errors.New("missing API info")
   174  	}
   175  	if cfg.APIInfo.ModelTag.Id() == "" {
   176  		return errors.New("missing model tag")
   177  	}
   178  	if len(cfg.APIInfo.CACert) == 0 {
   179  		return errors.New("missing API CA certificate")
   180  	}
   181  	if cfg.Controller != nil {
   182  		if err := cfg.verifyControllerConfig(); err != nil {
   183  			return errors.Trace(err)
   184  		}
   185  	}
   186  	if cfg.Bootstrap != nil {
   187  		if err := cfg.verifyBootstrapConfig(); err != nil {
   188  			return errors.Trace(err)
   189  		}
   190  	} else {
   191  		if cfg.APIInfo.Tag != names.NewMachineTag(cfg.MachineId) {
   192  			return errors.New("API entity tag must match started machine")
   193  		}
   194  		if len(cfg.APIInfo.Addrs) == 0 {
   195  			return errors.New("missing API hosts")
   196  		}
   197  	}
   198  	return nil
   199  }
   200  
   201  func (cfg *ControllerPodConfig) verifyBootstrapConfig() (err error) {
   202  	defer errors.DeferredAnnotatef(&err, "invalid bootstrap configuration")
   203  	if cfg.Controller == nil {
   204  		return errors.New("bootstrap config supplied without controller config")
   205  	}
   206  	if err := cfg.Bootstrap.VerifyConfig(); err != nil {
   207  		return errors.Trace(err)
   208  	}
   209  	if cfg.APIInfo.Tag != nil || cfg.Controller.MongoInfo.Tag != nil {
   210  		return errors.New("entity tag must be nil when bootstrapping")
   211  	}
   212  	return nil
   213  }
   214  
   215  func (cfg *ControllerPodConfig) verifyControllerConfig() (err error) {
   216  	defer errors.DeferredAnnotatef(&err, "invalid controller configuration")
   217  	if err := cfg.Controller.VerifyConfig(); err != nil {
   218  		return errors.Trace(err)
   219  	}
   220  	if cfg.Bootstrap == nil {
   221  		if len(cfg.Controller.MongoInfo.Addrs) == 0 {
   222  			return errors.New("missing state hosts")
   223  		}
   224  		if cfg.Controller.MongoInfo.Tag != names.NewMachineTag(cfg.MachineId) {
   225  			return errors.New("entity tag must match started machine")
   226  		}
   227  	}
   228  	return nil
   229  }
   230  
   231  // VerifyConfig verifies that the BootstrapConfig is valid.
   232  func (cfg *BootstrapConfig) VerifyConfig() (err error) {
   233  	if cfg.ControllerModelConfig == nil {
   234  		return errors.New("missing model configuration")
   235  	}
   236  	if len(cfg.StateServingInfo.Cert) == 0 {
   237  		return errors.New("missing controller certificate")
   238  	}
   239  	if len(cfg.StateServingInfo.PrivateKey) == 0 {
   240  		return errors.New("missing controller private key")
   241  	}
   242  	if len(cfg.StateServingInfo.CAPrivateKey) == 0 {
   243  		return errors.New("missing ca cert private key")
   244  	}
   245  	if cfg.StateServingInfo.StatePort == 0 {
   246  		return errors.New("missing state port")
   247  	}
   248  	if cfg.StateServingInfo.APIPort == 0 {
   249  		return errors.New("missing API port")
   250  	}
   251  	return nil
   252  }
   253  
   254  // VerifyConfig verifies that the ControllerConfig is valid.
   255  func (cfg *ControllerConfig) VerifyConfig() error {
   256  	if cfg.MongoInfo == nil {
   257  		return errors.New("missing state info")
   258  	}
   259  	if len(cfg.MongoInfo.CACert) == 0 {
   260  		return errors.New("missing CA certificate")
   261  	}
   262  	return nil
   263  }
   264  
   265  // NewControllerPodConfig sets up a basic pod configuration. You'll still need to supply more information,
   266  // but this takes care of the fixed entries and the ones that are
   267  // always needed.
   268  func NewControllerPodConfig(
   269  	controllerTag names.ControllerTag,
   270  	machineID, series string,
   271  	apiInfo *api.Info,
   272  ) (*ControllerPodConfig, error) {
   273  	dataDir, err := paths.DataDir(series)
   274  	if err != nil {
   275  		return nil, err
   276  	}
   277  	logDir, err := paths.LogDir(series)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  	metricsSpoolDir, err := paths.MetricsSpoolDir(series)
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  	pcfg := &ControllerPodConfig{
   286  		// Fixed entries.
   287  		DataDir:         dataDir,
   288  		LogDir:          path.Join(logDir, "juju"),
   289  		MetricsSpoolDir: metricsSpoolDir,
   290  		// CAAS only has JobManageModel.
   291  		Jobs: []multiwatcher.MachineJob{
   292  			multiwatcher.JobManageModel,
   293  		},
   294  		Tags: map[string]string{},
   295  		// Parameter entries.
   296  		ControllerTag: controllerTag,
   297  		MachineId:     machineID,
   298  		APIInfo:       apiInfo,
   299  	}
   300  	return pcfg, nil
   301  }
   302  
   303  // NewBootstrapControllerPodConfig sets up a basic pod configuration for a
   304  // bootstrap pod.  You'll still need to supply more information, but this
   305  // takes care of the fixed entries and the ones that are always needed.
   306  func NewBootstrapControllerPodConfig(config controller.Config, series string) (*ControllerPodConfig, error) {
   307  	// For a bootstrap pod, the caller must provide the state.Info
   308  	// and the api.Info. The machine id must *always* be "0".
   309  	pcfg, err := NewControllerPodConfig(names.NewControllerTag(config.ControllerUUID()), "0", series, nil)
   310  	if err != nil {
   311  		return nil, err
   312  	}
   313  	pcfg.Controller = &ControllerConfig{}
   314  	pcfg.Controller.Config = make(map[string]interface{})
   315  	for k, v := range config {
   316  		pcfg.Controller.Config[k] = v
   317  	}
   318  	pcfg.Bootstrap = &BootstrapConfig{
   319  		instancecfg.BootstrapConfig{
   320  			StateInitializationParams: instancecfg.StateInitializationParams{},
   321  		},
   322  	}
   323  	pcfg.Jobs = []multiwatcher.MachineJob{
   324  		multiwatcher.JobManageModel,
   325  	}
   326  	return pcfg, nil
   327  }
   328  
   329  // PopulateControllerPodConfig is called both from the FinishControllerPodConfig below,
   330  // which does have access to the environment config, and from the container
   331  // provisioners, which don't have access to the environment config. Everything
   332  // that is needed to provision a container needs to be returned to the
   333  // provisioner in the ContainerConfig structure. Those values are then used to
   334  // call this function.
   335  func PopulateControllerPodConfig(pcfg *ControllerPodConfig, providerType string) error {
   336  	if pcfg.AgentEnvironment == nil {
   337  		pcfg.AgentEnvironment = make(map[string]string)
   338  	}
   339  	pcfg.AgentEnvironment[agent.ProviderType] = providerType
   340  	return nil
   341  }
   342  
   343  // FinishControllerPodConfig sets fields on a ControllerPodConfig that can be determined by
   344  // inspecting a plain config.Config and the pod constraints at the last
   345  // moment before creating podspec. It assumes that the supplied Config comes
   346  // from an environment that has passed through all the validation checks in the
   347  // Bootstrap func, and that has set an agent-version (via finding the tools to,
   348  // use for bootstrap, or otherwise).
   349  func FinishControllerPodConfig(pcfg *ControllerPodConfig, cfg *config.Config) (err error) {
   350  	defer errors.DeferredAnnotatef(&err, "cannot complete pod configuration")
   351  	if err := PopulateControllerPodConfig(pcfg, cfg.Type()); err != nil {
   352  		return errors.Trace(err)
   353  	}
   354  	return nil
   355  }
   356  
   357  // PodLabels returns the minimum set of tags that should be set on a
   358  // pod, if the provider supports them.
   359  func PodLabels(modelUUID, controllerUUID string, tagger tags.ResourceTagger, jobs []multiwatcher.MachineJob) map[string]string {
   360  	podLabels := tags.ResourceTags(
   361  		names.NewModelTag(modelUUID),
   362  		names.NewControllerTag(controllerUUID),
   363  		tagger,
   364  	)
   365  	// always be a controller.
   366  	podLabels[tags.JujuIsController] = "true"
   367  	return podLabels
   368  }