github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/controller/modelmanager/createmodel.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package modelmanager provides the business logic for
     5  // model management operations in the controller.
     6  package modelmanager
     7  
     8  import (
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/utils"
    12  	"github.com/juju/version"
    13  
    14  	"github.com/juju/juju/environs"
    15  	"github.com/juju/juju/environs/config"
    16  	"github.com/juju/juju/tools"
    17  )
    18  
    19  var (
    20  	logger = loggo.GetLogger("juju.controller.modelmanager")
    21  )
    22  
    23  // ModelConfigCreator provides a method of creating a new model config.
    24  //
    25  // The zero value of ModelConfigCreator is usable with the limitations
    26  // noted on each struct field.
    27  type ModelConfigCreator struct {
    28  	// Provider will be used to obtain EnvironProviders for preparing
    29  	// and validating configuration.
    30  	Provider func(string) (environs.EnvironProvider, error)
    31  
    32  	// FindTools, if non-nil, will be used to validate the agent-version
    33  	// value in NewModelConfig if it differs from the base configuration.
    34  	//
    35  	// If FindTools is nil, agent-version may not be different to the
    36  	// base configuration.
    37  	FindTools func(version.Number) (tools.List, error)
    38  }
    39  
    40  // NewModelConfig returns a new model config given a base (controller) config
    41  // and a set of attributes that will be specific to the new model, overriding
    42  // any non-restricted attributes in the base configuration. The resulting
    43  // config will be suitable for creating a new model in state.
    44  //
    45  // If "attrs" does not include a UUID, a new, random one will be generated
    46  // and added to the config.
    47  //
    48  // The config will be validated with the provider before being returned.
    49  func (c ModelConfigCreator) NewModelConfig(
    50  	cloud environs.CloudSpec,
    51  	base *config.Config,
    52  	attrs map[string]interface{},
    53  ) (*config.Config, error) {
    54  
    55  	if err := c.checkVersion(base, attrs); err != nil {
    56  		return nil, errors.Trace(err)
    57  	}
    58  	provider, err := c.Provider(cloud.Type)
    59  	if err != nil {
    60  		return nil, errors.Trace(err)
    61  	}
    62  
    63  	// Before comparing any values, we need to push the config through
    64  	// the provider validation code. One of the reasons for this is that
    65  	// numbers being serialized through JSON get turned into float64. The
    66  	// schema code used in config will convert these back into integers.
    67  	// However, before we can create a valid config, we need to make sure
    68  	// we copy across fields from the main config that aren't there.
    69  	baseAttrs := base.AllAttrs()
    70  	restrictedFields, err := RestrictedProviderFields(provider)
    71  	if err != nil {
    72  		return nil, errors.Trace(err)
    73  	}
    74  	for _, field := range restrictedFields {
    75  		if _, ok := attrs[field]; !ok {
    76  			if baseValue, ok := baseAttrs[field]; ok {
    77  				attrs[field] = baseValue
    78  			}
    79  		}
    80  	}
    81  
    82  	// Generate a new UUID for the model as necessary,
    83  	// and finalize the new config.
    84  	if _, ok := attrs[config.UUIDKey]; !ok {
    85  		uuid, err := utils.NewUUID()
    86  		if err != nil {
    87  			return nil, errors.Trace(err)
    88  		}
    89  		attrs[config.UUIDKey] = uuid.String()
    90  	}
    91  	cfg, err := finalizeConfig(provider, cloud, attrs)
    92  	if err != nil {
    93  		return nil, errors.Trace(err)
    94  	}
    95  
    96  	// Any values that would normally be copied from the controller
    97  	// config can also be defined, but if they differ from the controller
    98  	// values, an error is returned.
    99  	attrs = cfg.AllAttrs()
   100  	for _, field := range restrictedFields {
   101  		if value, ok := attrs[field]; ok {
   102  			if serverValue := baseAttrs[field]; value != serverValue {
   103  				return nil, errors.Errorf(
   104  					"specified %s \"%v\" does not match controller \"%v\"",
   105  					field, value, serverValue)
   106  			}
   107  		}
   108  	}
   109  
   110  	return cfg, nil
   111  }
   112  
   113  func (c *ModelConfigCreator) checkVersion(base *config.Config, attrs map[string]interface{}) error {
   114  	baseVersion, ok := base.AgentVersion()
   115  	if !ok {
   116  		return errors.Errorf("agent-version not found in base config")
   117  	}
   118  
   119  	// If there is no agent-version specified, use the current version.
   120  	// otherwise we need to check for tools
   121  	value, ok := attrs["agent-version"]
   122  	if !ok {
   123  		attrs["agent-version"] = baseVersion.String()
   124  		return nil
   125  	}
   126  	versionStr, ok := value.(string)
   127  	if !ok {
   128  		return errors.Errorf("agent-version must be a string but has type '%T'", value)
   129  	}
   130  	versionNumber, err := version.Parse(versionStr)
   131  	if err != nil {
   132  		return errors.Trace(err)
   133  	}
   134  
   135  	n := versionNumber.Compare(baseVersion)
   136  	switch {
   137  	case n > 0:
   138  		return errors.Errorf(
   139  			"agent-version (%s) cannot be greater than the controller (%s)",
   140  			versionNumber, baseVersion,
   141  		)
   142  	case n == 0:
   143  		// If the version is the same as the base config,
   144  		// then assume tools are available.
   145  		return nil
   146  	case n < 0:
   147  		if c.FindTools == nil {
   148  			return errors.New(
   149  				"agent-version does not match base config, " +
   150  					"and no tools-finder is supplied",
   151  			)
   152  		}
   153  	}
   154  
   155  	// Look to see if we have tools available for that version.
   156  	list, err := c.FindTools(versionNumber)
   157  	if err != nil {
   158  		return errors.Trace(err)
   159  	}
   160  	if len(list) == 0 {
   161  		return errors.Errorf("no tools found for version %s", versionNumber)
   162  	}
   163  	logger.Tracef("found tools: %#v", list)
   164  	return nil
   165  }
   166  
   167  // RestrictedProviderFields returns the set of config fields that may not be
   168  // overridden.
   169  //
   170  // TODO(axw) restricted config should go away. There should be no provider-
   171  // specific config, since models should be independent of each other; and
   172  // anything that should not change across models should be in the controller
   173  // config.
   174  func RestrictedProviderFields(provider environs.EnvironProvider) ([]string, error) {
   175  	var fields []string
   176  	// For now, all models in a controller must be of the same type.
   177  	fields = append(fields, config.TypeKey)
   178  	return fields, nil
   179  }
   180  
   181  // finalizeConfig creates the config object from attributes,
   182  // and calls EnvironProvider.PrepareConfig.
   183  func finalizeConfig(
   184  	provider environs.EnvironProvider,
   185  	cloud environs.CloudSpec,
   186  	attrs map[string]interface{},
   187  ) (*config.Config, error) {
   188  	cfg, err := config.New(config.UseDefaults, attrs)
   189  	if err != nil {
   190  		return nil, errors.Annotate(err, "creating config from values failed")
   191  	}
   192  	cfg, err = provider.PrepareConfig(environs.PrepareConfigParams{
   193  		Cloud:  cloud,
   194  		Config: cfg,
   195  	})
   196  	if err != nil {
   197  		return nil, errors.Annotate(err, "provider config preparation failed")
   198  	}
   199  	cfg, err = provider.Validate(cfg, nil)
   200  	if err != nil {
   201  		return nil, errors.Annotate(err, "provider config validation failed")
   202  	}
   203  	return cfg, nil
   204  }