github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/apiserver/environmentmanager/environmentmanager.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // The environmentmanager package defines an API end point for functions 5 // dealing with envionments. Creating, listing and sharing environments. 6 package environmentmanager 7 8 import ( 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/names" 14 "github.com/juju/utils" 15 16 "github.com/juju/juju/apiserver/common" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/environs/config" 20 "github.com/juju/juju/feature" 21 "github.com/juju/juju/state" 22 "github.com/juju/juju/version" 23 ) 24 25 var logger = loggo.GetLogger("juju.apiserver.environmentmanager") 26 27 func init() { 28 common.RegisterStandardFacadeForFeature("EnvironmentManager", 1, NewEnvironmentManagerAPI, feature.JES) 29 } 30 31 // EnvironmentManager defines the methods on the environmentmanager API end 32 // point. 33 type EnvironmentManager interface { 34 ConfigSkeleton(args params.EnvironmentSkeletonConfigArgs) (params.EnvironConfigResult, error) 35 CreateEnvironment(args params.EnvironmentCreateArgs) (params.Environment, error) 36 ListEnvironments(user params.Entity) (params.UserEnvironmentList, error) 37 } 38 39 // EnvironmentManagerAPI implements the environment manager interface and is 40 // the concrete implementation of the api end point. 41 type EnvironmentManagerAPI struct { 42 state stateInterface 43 authorizer common.Authorizer 44 toolsFinder *common.ToolsFinder 45 } 46 47 var _ EnvironmentManager = (*EnvironmentManagerAPI)(nil) 48 49 // NewEnvironmentManagerAPI creates a new api server endpoint for managing 50 // environments. 51 func NewEnvironmentManagerAPI( 52 st *state.State, 53 resources *common.Resources, 54 authorizer common.Authorizer, 55 ) (*EnvironmentManagerAPI, error) { 56 if !authorizer.AuthClient() { 57 return nil, common.ErrPerm 58 } 59 60 urlGetter := common.NewToolsURLGetter(st.EnvironUUID(), st) 61 return &EnvironmentManagerAPI{ 62 state: getState(st), 63 authorizer: authorizer, 64 toolsFinder: common.NewToolsFinder(st, st, urlGetter), 65 }, nil 66 } 67 68 // authCheck checks if the user is acting on their own behalf, or if they 69 // are an administrator acting on behalf of another user. 70 func (em *EnvironmentManagerAPI) authCheck(user names.UserTag) error { 71 // Since we know this is a user tag (because AuthClient is true), 72 // we just do the type assertion to the UserTag. 73 apiUser, _ := em.authorizer.GetAuthTag().(names.UserTag) 74 isAdmin, err := em.state.IsSystemAdministrator(apiUser) 75 if err != nil { 76 return errors.Trace(err) 77 } 78 if isAdmin { 79 logger.Tracef("%q is a system admin", apiUser.Username()) 80 return nil 81 } 82 83 // We can't just compare the UserTags themselves as the provider part 84 // may be unset, and gets replaced with 'local'. We must compare against 85 // the Username of the user tag. 86 if apiUser.Username() == user.Username() { 87 return nil 88 } 89 return common.ErrPerm 90 } 91 92 // ConfigSource describes a type that is able to provide config. 93 // Abstracted primarily for testing. 94 type ConfigSource interface { 95 Config() (*config.Config, error) 96 } 97 98 var configValuesFromStateServer = []string{ 99 "type", 100 "ca-cert", 101 "state-port", 102 "api-port", 103 "syslog-port", 104 "rsyslog-ca-cert", 105 "rsyslog-ca-key", 106 } 107 108 // ConfigSkeleton returns config values to be used as a starting point for the 109 // API caller to construct a valid environment specific config. The provider 110 // and region params are there for future use, and current behaviour expects 111 // both of these to be empty. 112 func (em *EnvironmentManagerAPI) ConfigSkeleton(args params.EnvironmentSkeletonConfigArgs) (params.EnvironConfigResult, error) { 113 var result params.EnvironConfigResult 114 if args.Provider != "" { 115 return result, errors.NotValidf("provider value %q", args.Provider) 116 } 117 if args.Region != "" { 118 return result, errors.NotValidf("region value %q", args.Region) 119 } 120 121 stateServerEnv, err := em.state.StateServerEnvironment() 122 if err != nil { 123 return result, errors.Trace(err) 124 } 125 126 config, err := em.configSkeleton(stateServerEnv) 127 if err != nil { 128 return result, errors.Trace(err) 129 } 130 131 result.Config = config 132 return result, nil 133 } 134 135 func (em *EnvironmentManagerAPI) restrictedProviderFields(providerType string) ([]string, error) { 136 provider, err := environs.Provider(providerType) 137 if err != nil { 138 return nil, errors.Trace(err) 139 } 140 141 var fields []string 142 fields = append(fields, configValuesFromStateServer...) 143 fields = append(fields, provider.RestrictedConfigAttributes()...) 144 return fields, nil 145 } 146 147 func (em *EnvironmentManagerAPI) configSkeleton(source ConfigSource) (map[string]interface{}, error) { 148 baseConfig, err := source.Config() 149 if err != nil { 150 return nil, errors.Trace(err) 151 } 152 baseMap := baseConfig.AllAttrs() 153 154 fields, err := em.restrictedProviderFields(baseConfig.Type()) 155 if err != nil { 156 return nil, errors.Trace(err) 157 } 158 159 var result = make(map[string]interface{}) 160 for _, field := range fields { 161 if value, found := baseMap[field]; found { 162 result[field] = value 163 } 164 } 165 return result, nil 166 } 167 168 func (em *EnvironmentManagerAPI) checkVersion(cfg map[string]interface{}) error { 169 // If there is no agent-version specified, use the current version. 170 // otherwise we need to check for tools 171 value, found := cfg["agent-version"] 172 if !found { 173 cfg["agent-version"] = version.Current.Number.String() 174 return nil 175 } 176 valuestr, ok := value.(string) 177 if !ok { 178 return errors.Errorf("agent-version must be a string but has type '%T'", value) 179 } 180 num, err := version.Parse(valuestr) 181 if err != nil { 182 return errors.Trace(err) 183 } 184 if comp := num.Compare(version.Current.Number); comp > 0 { 185 return errors.Errorf("agent-version cannot be greater than the server: %s", version.Current.Number) 186 } else if comp < 0 { 187 // Look to see if we have tools available for that version. 188 // Obviously if the version is the same, we have the tools available. 189 list, err := em.toolsFinder.FindTools(params.FindToolsParams{ 190 Number: num, 191 }) 192 if err != nil { 193 return errors.Trace(err) 194 } 195 logger.Tracef("found tools: %#v", list) 196 if len(list.List) == 0 { 197 return errors.Errorf("no tools found for version %s", num) 198 } 199 } 200 return nil 201 } 202 203 func (em *EnvironmentManagerAPI) validConfig(attrs map[string]interface{}) (*config.Config, error) { 204 cfg, err := config.New(config.UseDefaults, attrs) 205 if err != nil { 206 return nil, errors.Annotate(err, "creating config from values failed") 207 } 208 provider, err := environs.Provider(cfg.Type()) 209 if err != nil { 210 return nil, errors.Trace(err) 211 } 212 cfg, err = provider.PrepareForCreateEnvironment(cfg) 213 if err != nil { 214 return nil, errors.Trace(err) 215 } 216 cfg, err = provider.Validate(cfg, nil) 217 if err != nil { 218 return nil, errors.Annotate(err, "provider validation failed") 219 } 220 return cfg, nil 221 } 222 223 func (em *EnvironmentManagerAPI) newEnvironmentConfig(args params.EnvironmentCreateArgs, source ConfigSource) (*config.Config, error) { 224 // For now, we just smash to the two maps together as we store 225 // the account values and the environment config together in the 226 // *config.Config instance. 227 joint := make(map[string]interface{}) 228 for key, value := range args.Config { 229 joint[key] = value 230 } 231 // Account info overrides any config values. 232 for key, value := range args.Account { 233 joint[key] = value 234 } 235 if _, found := joint["uuid"]; found { 236 return nil, errors.New("uuid is generated, you cannot specify one") 237 } 238 baseConfig, err := source.Config() 239 if err != nil { 240 return nil, errors.Trace(err) 241 } 242 baseMap := baseConfig.AllAttrs() 243 fields, err := em.restrictedProviderFields(baseConfig.Type()) 244 if err != nil { 245 return nil, errors.Trace(err) 246 } 247 // Before comparing any values, we need to push the config through 248 // the provider validation code. One of the reasons for this is that 249 // numbers being serialized through JSON get turned into float64. The 250 // schema code used in config will convert these back into integers. 251 // However, before we can create a valid config, we need to make sure 252 // we copy across fields from the main config that aren't there. 253 for _, field := range fields { 254 if _, found := joint[field]; !found { 255 if baseValue, found := baseMap[field]; found { 256 joint[field] = baseValue 257 } 258 } 259 } 260 261 cfg, err := em.validConfig(joint) 262 if err != nil { 263 return nil, errors.Trace(err) 264 } 265 attrs := cfg.AllAttrs() 266 // Any values that would normally be copied from the state server 267 // config can also be defined, but if they differ from the state server 268 // values, an error is returned. 269 for _, field := range fields { 270 if value, found := attrs[field]; found { 271 if serverValue := baseMap[field]; value != serverValue { 272 return nil, errors.Errorf( 273 "specified %s \"%v\" does not match apiserver \"%v\"", 274 field, value, serverValue) 275 } 276 } 277 } 278 if err := em.checkVersion(attrs); err != nil { 279 return nil, errors.Trace(err) 280 } 281 282 // Generate the UUID for the server. 283 uuid, err := utils.NewUUID() 284 if err != nil { 285 return nil, errors.Annotate(err, "failed to generate environment uuid") 286 } 287 attrs["uuid"] = uuid.String() 288 289 return em.validConfig(attrs) 290 } 291 292 // CreateEnvironment creates a new environment using the account and 293 // environment config specified in the args. 294 func (em *EnvironmentManagerAPI) CreateEnvironment(args params.EnvironmentCreateArgs) (params.Environment, error) { 295 result := params.Environment{} 296 // Get the state server environment first. We need it both for the state 297 // server owner and the ability to get the config. 298 stateServerEnv, err := em.state.StateServerEnvironment() 299 if err != nil { 300 return result, errors.Trace(err) 301 } 302 303 ownerTag, err := names.ParseUserTag(args.OwnerTag) 304 if err != nil { 305 return result, errors.Trace(err) 306 } 307 308 // Any user is able to create themselves an environment (until real fine 309 // grain permissions are available), and admins (the creator of the state 310 // server environment) are able to create environments for other people. 311 err = em.authCheck(ownerTag) 312 if err != nil { 313 return result, errors.Trace(err) 314 } 315 316 newConfig, err := em.newEnvironmentConfig(args, stateServerEnv) 317 if err != nil { 318 return result, errors.Trace(err) 319 } 320 // NOTE: check the agent-version of the config, and if it is > the current 321 // version, it is not supported, also check existing tools, and if we don't 322 // have tools for that version, also die. 323 env, st, err := em.state.NewEnvironment(newConfig, ownerTag) 324 if err != nil { 325 return result, errors.Annotate(err, "failed to create new environment") 326 } 327 defer st.Close() 328 329 result.Name = env.Name() 330 result.UUID = env.UUID() 331 result.OwnerTag = env.Owner().String() 332 333 return result, nil 334 } 335 336 // ListEnvironments returns the environments that the specified user 337 // has access to in the current server. Only that state server owner 338 // can list environments for any user (at this stage). Other users 339 // can only ask about their own environments. 340 func (em *EnvironmentManagerAPI) ListEnvironments(user params.Entity) (params.UserEnvironmentList, error) { 341 result := params.UserEnvironmentList{} 342 343 userTag, err := names.ParseUserTag(user.Tag) 344 if err != nil { 345 return result, errors.Trace(err) 346 } 347 348 err = em.authCheck(userTag) 349 if err != nil { 350 return result, errors.Trace(err) 351 } 352 353 environments, err := em.state.EnvironmentsForUser(userTag) 354 if err != nil { 355 return result, errors.Trace(err) 356 } 357 358 for _, env := range environments { 359 var lastConn *time.Time 360 userLastConn, err := env.LastConnection() 361 if err != nil { 362 if !state.IsNeverConnectedError(err) { 363 return result, errors.Trace(err) 364 } 365 } else { 366 lastConn = &userLastConn 367 } 368 result.UserEnvironments = append(result.UserEnvironments, params.UserEnvironment{ 369 Environment: params.Environment{ 370 Name: env.Name(), 371 UUID: env.UUID(), 372 OwnerTag: env.Owner().String(), 373 }, 374 LastConnection: lastConn, 375 }) 376 logger.Debugf("list env: %s, %s, %s", env.Name(), env.UUID(), env.Owner()) 377 } 378 379 return result, nil 380 }