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 }