github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/modelmanager/modelmanager.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package modelmanager defines an API end point for functions dealing with
     5  // models.  Creating, listing and sharing models. This facade is available at
     6  // the root of the controller API, and as such, there is no implicit Model
     7  // assocated.
     8  package modelmanager
     9  
    10  import (
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/loggo"
    15  	"github.com/juju/names"
    16  	"github.com/juju/txn"
    17  	"github.com/juju/version"
    18  
    19  	"github.com/juju/juju/apiserver/common"
    20  	"github.com/juju/juju/apiserver/params"
    21  	"github.com/juju/juju/controller/modelmanager"
    22  	"github.com/juju/juju/environs/config"
    23  	"github.com/juju/juju/juju/permission"
    24  	"github.com/juju/juju/state"
    25  	"github.com/juju/juju/tools"
    26  )
    27  
    28  var logger = loggo.GetLogger("juju.apiserver.modelmanager")
    29  
    30  func init() {
    31  	common.RegisterStandardFacade("ModelManager", 2, newFacade)
    32  }
    33  
    34  // ModelManager defines the methods on the modelmanager API endpoint.
    35  type ModelManager interface {
    36  	ConfigSkeleton(args params.ModelSkeletonConfigArgs) (params.ModelConfigResult, error)
    37  	CreateModel(args params.ModelCreateArgs) (params.Model, error)
    38  	ListModels(user params.Entity) (params.UserModelList, error)
    39  }
    40  
    41  // ModelManagerAPI implements the model manager interface and is
    42  // the concrete implementation of the api end point.
    43  type ModelManagerAPI struct {
    44  	state       Backend
    45  	authorizer  common.Authorizer
    46  	toolsFinder *common.ToolsFinder
    47  	apiUser     names.UserTag
    48  	isAdmin     bool
    49  }
    50  
    51  var _ ModelManager = (*ModelManagerAPI)(nil)
    52  
    53  func newFacade(st *state.State, resources *common.Resources, auth common.Authorizer) (*ModelManagerAPI, error) {
    54  	return NewModelManagerAPI(NewStateBackend(st), auth)
    55  }
    56  
    57  // NewModelManagerAPI creates a new api server endpoint for managing
    58  // models.
    59  func NewModelManagerAPI(st Backend, authorizer common.Authorizer) (*ModelManagerAPI, error) {
    60  	if !authorizer.AuthClient() {
    61  		return nil, common.ErrPerm
    62  	}
    63  	// Since we know this is a user tag (because AuthClient is true),
    64  	// we just do the type assertion to the UserTag.
    65  	apiUser, _ := authorizer.GetAuthTag().(names.UserTag)
    66  	// Pretty much all of the user manager methods have special casing for admin
    67  	// users, so look once when we start and remember if the user is an admin.
    68  	isAdmin, err := st.IsControllerAdministrator(apiUser)
    69  	if err != nil {
    70  		return nil, errors.Trace(err)
    71  	}
    72  	urlGetter := common.NewToolsURLGetter(st.ModelUUID(), st)
    73  	return &ModelManagerAPI{
    74  		state:       st,
    75  		authorizer:  authorizer,
    76  		toolsFinder: common.NewToolsFinder(st, st, urlGetter),
    77  		apiUser:     apiUser,
    78  		isAdmin:     isAdmin,
    79  	}, nil
    80  }
    81  
    82  // authCheck checks if the user is acting on their own behalf, or if they
    83  // are an administrator acting on behalf of another user.
    84  func (m *ModelManagerAPI) authCheck(user names.UserTag) error {
    85  	if m.isAdmin {
    86  		logger.Tracef("%q is a controller admin", m.apiUser.Canonical())
    87  		return nil
    88  	}
    89  
    90  	// We can't just compare the UserTags themselves as the provider part
    91  	// may be unset, and gets replaced with 'local'. We must compare against
    92  	// the Canonical value of the user tag.
    93  	if m.apiUser.Canonical() == user.Canonical() {
    94  		return nil
    95  	}
    96  	return common.ErrPerm
    97  }
    98  
    99  // ConfigSource describes a type that is able to provide config.
   100  // Abstracted primarily for testing.
   101  type ConfigSource interface {
   102  	Config() (*config.Config, error)
   103  }
   104  
   105  // ConfigSkeleton returns config values to be used as a starting point for the
   106  // API caller to construct a valid model specific config.  The provider
   107  // and region params are there for future use, and current behaviour expects
   108  // both of these to be empty.
   109  func (mm *ModelManagerAPI) ConfigSkeleton(args params.ModelSkeletonConfigArgs) (params.ModelConfigResult, error) {
   110  	var result params.ModelConfigResult
   111  	if args.Region != "" {
   112  		return result, errors.NotValidf("region value %q", args.Region)
   113  	}
   114  
   115  	controllerEnv, err := mm.state.ControllerModel()
   116  	if err != nil {
   117  		return result, errors.Trace(err)
   118  	}
   119  	config, err := mm.configSkeleton(controllerEnv, args.Provider)
   120  	if err != nil {
   121  		return result, errors.Trace(err)
   122  	}
   123  
   124  	result.Config = config
   125  	return result, nil
   126  }
   127  
   128  func (mm *ModelManagerAPI) configSkeleton(source ConfigSource, requestedProviderType string) (map[string]interface{}, error) {
   129  	baseConfig, err := source.Config()
   130  	if err != nil {
   131  		return nil, errors.Trace(err)
   132  	}
   133  	if requestedProviderType != "" && baseConfig.Type() != requestedProviderType {
   134  		return nil, errors.Errorf(
   135  			"cannot create new model with credentials for provider type %q on controller with provider type %q",
   136  			requestedProviderType, baseConfig.Type())
   137  	}
   138  	baseMap := baseConfig.AllAttrs()
   139  
   140  	fields, err := modelmanager.RestrictedProviderFields(baseConfig.Type())
   141  	if err != nil {
   142  		return nil, errors.Trace(err)
   143  	}
   144  
   145  	var result = make(map[string]interface{})
   146  	for _, field := range fields {
   147  		if value, found := baseMap[field]; found {
   148  			result[field] = value
   149  		}
   150  	}
   151  	return result, nil
   152  }
   153  
   154  func (mm *ModelManagerAPI) newModelConfig(args params.ModelCreateArgs, source ConfigSource) (*config.Config, error) {
   155  	// For now, we just smash to the two maps together as we store
   156  	// the account values and the model config together in the
   157  	// *config.Config instance.
   158  	joint := make(map[string]interface{})
   159  	for key, value := range args.Config {
   160  		joint[key] = value
   161  	}
   162  	// Account info overrides any config values.
   163  	for key, value := range args.Account {
   164  		joint[key] = value
   165  	}
   166  	if _, ok := joint["uuid"]; ok {
   167  		return nil, errors.New("uuid is generated, you cannot specify one")
   168  	}
   169  	baseConfig, err := source.Config()
   170  	if err != nil {
   171  		return nil, errors.Trace(err)
   172  	}
   173  	creator := modelmanager.ModelConfigCreator{
   174  		FindTools: func(n version.Number) (tools.List, error) {
   175  			result, err := mm.toolsFinder.FindTools(params.FindToolsParams{
   176  				Number: n,
   177  			})
   178  			if err != nil {
   179  				return nil, errors.Trace(err)
   180  			}
   181  			return result.List, nil
   182  		},
   183  	}
   184  	return creator.NewModelConfig(mm.isAdmin, baseConfig, joint)
   185  }
   186  
   187  // CreateModel creates a new model using the account and
   188  // model config specified in the args.
   189  func (mm *ModelManagerAPI) CreateModel(args params.ModelCreateArgs) (params.Model, error) {
   190  	result := params.Model{}
   191  	// Get the controller model first. We need it both for the state
   192  	// server owner and the ability to get the config.
   193  	controllerModel, err := mm.state.ControllerModel()
   194  	if err != nil {
   195  		return result, errors.Trace(err)
   196  	}
   197  
   198  	ownerTag, err := names.ParseUserTag(args.OwnerTag)
   199  	if err != nil {
   200  		return result, errors.Trace(err)
   201  	}
   202  
   203  	// Any user is able to create themselves an model (until real fine
   204  	// grain permissions are available), and admins (the creator of the state
   205  	// server model) are able to create models for other people.
   206  	err = mm.authCheck(ownerTag)
   207  	if err != nil {
   208  		return result, errors.Trace(err)
   209  	}
   210  
   211  	newConfig, err := mm.newModelConfig(args, controllerModel)
   212  	if err != nil {
   213  		return result, errors.Annotate(err, "failed to create config")
   214  	}
   215  	// NOTE: check the agent-version of the config, and if it is > the current
   216  	// version, it is not supported, also check existing tools, and if we don't
   217  	// have tools for that version, also die.
   218  	model, st, err := mm.state.NewModel(state.ModelArgs{Config: newConfig, Owner: ownerTag})
   219  	if err != nil {
   220  		return result, errors.Annotate(err, "failed to create new model")
   221  	}
   222  	defer st.Close()
   223  
   224  	result.Name = model.Name()
   225  	result.UUID = model.UUID()
   226  	result.OwnerTag = model.Owner().String()
   227  
   228  	return result, nil
   229  }
   230  
   231  // ListModels returns the models that the specified user
   232  // has access to in the current server.  Only that controller owner
   233  // can list models for any user (at this stage).  Other users
   234  // can only ask about their own models.
   235  func (mm *ModelManagerAPI) ListModels(user params.Entity) (params.UserModelList, error) {
   236  	result := params.UserModelList{}
   237  
   238  	userTag, err := names.ParseUserTag(user.Tag)
   239  	if err != nil {
   240  		return result, errors.Trace(err)
   241  	}
   242  
   243  	err = mm.authCheck(userTag)
   244  	if err != nil {
   245  		return result, errors.Trace(err)
   246  	}
   247  
   248  	models, err := mm.state.ModelsForUser(userTag)
   249  	if err != nil {
   250  		return result, errors.Trace(err)
   251  	}
   252  
   253  	for _, model := range models {
   254  		var lastConn *time.Time
   255  		userLastConn, err := model.LastConnection()
   256  		if err != nil {
   257  			if !state.IsNeverConnectedError(err) {
   258  				return result, errors.Trace(err)
   259  			}
   260  		} else {
   261  			lastConn = &userLastConn
   262  		}
   263  		result.UserModels = append(result.UserModels, params.UserModel{
   264  			Model: params.Model{
   265  				Name:     model.Name(),
   266  				UUID:     model.UUID(),
   267  				OwnerTag: model.Owner().String(),
   268  			},
   269  			LastConnection: lastConn,
   270  		})
   271  	}
   272  
   273  	return result, nil
   274  }
   275  
   276  // ModelInfo returns information about the specified models.
   277  func (m *ModelManagerAPI) ModelInfo(args params.Entities) (params.ModelInfoResults, error) {
   278  	results := params.ModelInfoResults{
   279  		Results: make([]params.ModelInfoResult, len(args.Entities)),
   280  	}
   281  
   282  	getModelInfo := func(arg params.Entity) (params.ModelInfo, error) {
   283  		tag, err := names.ParseModelTag(arg.Tag)
   284  		if err != nil {
   285  			return params.ModelInfo{}, err
   286  		}
   287  
   288  		st, err := m.state.ForModel(tag)
   289  		if errors.IsNotFound(err) {
   290  			return params.ModelInfo{}, common.ErrPerm
   291  		} else if err != nil {
   292  			return params.ModelInfo{}, err
   293  		}
   294  		defer st.Close()
   295  
   296  		model, err := st.Model()
   297  		if errors.IsNotFound(err) {
   298  			return params.ModelInfo{}, common.ErrPerm
   299  		} else if err != nil {
   300  			return params.ModelInfo{}, err
   301  		}
   302  
   303  		cfg, err := model.Config()
   304  		if err != nil {
   305  			return params.ModelInfo{}, err
   306  		}
   307  		users, err := model.Users()
   308  		if err != nil {
   309  			return params.ModelInfo{}, err
   310  		}
   311  		status, err := model.Status()
   312  		if err != nil {
   313  			return params.ModelInfo{}, err
   314  		}
   315  
   316  		owner := model.Owner()
   317  		info := params.ModelInfo{
   318  			Name:           cfg.Name(),
   319  			UUID:           cfg.UUID(),
   320  			ControllerUUID: cfg.ControllerUUID(),
   321  			OwnerTag:       owner.String(),
   322  			Life:           params.Life(model.Life().String()),
   323  			Status:         common.EntityStatusFromState(status),
   324  			ProviderType:   cfg.Type(),
   325  			DefaultSeries:  config.PreferredSeries(cfg),
   326  		}
   327  
   328  		authorizedOwner := m.authCheck(owner) == nil
   329  		for _, user := range users {
   330  			if !authorizedOwner && m.authCheck(user.UserTag()) != nil {
   331  				// The authenticated user is neither the owner
   332  				// nor administrator, nor the model user, so
   333  				// has no business knowing about the model user.
   334  				continue
   335  			}
   336  			userInfo, err := common.ModelUserInfo(user)
   337  			if err != nil {
   338  				return params.ModelInfo{}, errors.Trace(err)
   339  			}
   340  			info.Users = append(info.Users, userInfo)
   341  		}
   342  
   343  		if len(info.Users) == 0 {
   344  			// No users, which means the authenticated user doesn't
   345  			// have access to the model.
   346  			return params.ModelInfo{}, common.ErrPerm
   347  		}
   348  
   349  		return info, nil
   350  	}
   351  
   352  	for i, arg := range args.Entities {
   353  		modelInfo, err := getModelInfo(arg)
   354  		if err != nil {
   355  			results.Results[i].Error = common.ServerError(err)
   356  			continue
   357  		}
   358  		results.Results[i].Result = &modelInfo
   359  	}
   360  	return results, nil
   361  }
   362  
   363  // ModifyModelAccess changes the model access granted to users.
   364  func (m *ModelManagerAPI) ModifyModelAccess(args params.ModifyModelAccessRequest) (result params.ErrorResults, err error) {
   365  	result = params.ErrorResults{
   366  		Results: make([]params.ErrorResult, len(args.Changes)),
   367  	}
   368  	if len(args.Changes) == 0 {
   369  		return result, nil
   370  	}
   371  
   372  	for i, arg := range args.Changes {
   373  		modelAccess, err := FromModelAccessParam(arg.Access)
   374  		if err != nil {
   375  			err = errors.Annotate(err, "could not modify model access")
   376  			result.Results[i].Error = common.ServerError(err)
   377  			continue
   378  		}
   379  
   380  		targetUserTag, err := names.ParseUserTag(arg.UserTag)
   381  		if err != nil {
   382  			result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify model access"))
   383  			continue
   384  		}
   385  		modelTag, err := names.ParseModelTag(arg.ModelTag)
   386  		if err != nil {
   387  			result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify model access"))
   388  			continue
   389  		}
   390  
   391  		result.Results[i].Error = common.ServerError(
   392  			ChangeModelAccess(m.state, modelTag, m.apiUser, targetUserTag, arg.Action, modelAccess, m.isAdmin))
   393  	}
   394  	return result, nil
   395  }
   396  
   397  // resolveStateAccess returns the state representation of the logical model
   398  // access type.
   399  func resolveStateAccess(access permission.ModelAccess) (state.ModelAccess, error) {
   400  	var fail state.ModelAccess
   401  	switch access {
   402  	case permission.ModelReadAccess:
   403  		return state.ModelReadAccess, nil
   404  	case permission.ModelWriteAccess:
   405  		// TODO: Initially, we'll map "write" access to admin-level access.
   406  		// Post Juju-2.0, support for more nuanced access will be added to the
   407  		// permission business logic and state model.
   408  		return state.ModelAdminAccess, nil
   409  	}
   410  	logger.Errorf("invalid access permission: %+v", access)
   411  	return fail, errors.Errorf("invalid access permission")
   412  }
   413  
   414  // isGreaterAccess returns whether the new access provides more permissions
   415  // than the current access.
   416  // TODO(cmars): If/when more access types are implemented in state,
   417  //   the implementation of this function will certainly need to change, and it
   418  //   should be abstracted away to juju/permission as pure business logic
   419  //   instead of operating on state values.
   420  func isGreaterAccess(currentAccess, newAccess state.ModelAccess) bool {
   421  	if currentAccess == state.ModelReadAccess && newAccess == state.ModelAdminAccess {
   422  		return true
   423  	}
   424  	return false
   425  }
   426  
   427  func userAuthorizedToChangeAccess(st Backend, userIsAdmin bool, userTag names.UserTag) error {
   428  	if userIsAdmin {
   429  		// Just confirm that the model that has been given is a valid model.
   430  		_, err := st.Model()
   431  		if err != nil {
   432  			return errors.Trace(err)
   433  		}
   434  		return nil
   435  	}
   436  
   437  	// Get the current user's ModelUser for the Model to see if the user has
   438  	// permission to grant or revoke permissions on the model.
   439  	currentUser, err := st.ModelUser(userTag)
   440  	if err != nil {
   441  		if errors.IsNotFound(err) {
   442  			// No, this user doesn't have permission.
   443  			return common.ErrPerm
   444  		}
   445  		return errors.Annotate(err, "could not retrieve user")
   446  	}
   447  	if currentUser.Access() != state.ModelAdminAccess {
   448  		return common.ErrPerm
   449  	}
   450  	return nil
   451  }
   452  
   453  // ChangeModelAccess performs the requested access grant or revoke action for the
   454  // specified user on the specified model.
   455  func ChangeModelAccess(accessor Backend, modelTag names.ModelTag, apiUser, targetUserTag names.UserTag, action params.ModelAction, access permission.ModelAccess, userIsAdmin bool) error {
   456  	st, err := accessor.ForModel(modelTag)
   457  	if err != nil {
   458  		return errors.Annotate(err, "could not lookup model")
   459  	}
   460  	defer st.Close()
   461  
   462  	if err := userAuthorizedToChangeAccess(st, userIsAdmin, apiUser); err != nil {
   463  		return errors.Trace(err)
   464  	}
   465  
   466  	stateAccess, err := resolveStateAccess(access)
   467  	if err != nil {
   468  		return errors.Annotate(err, "could not resolve model access")
   469  	}
   470  
   471  	switch action {
   472  	case params.GrantModelAccess:
   473  		_, err = st.AddModelUser(state.ModelUserSpec{User: targetUserTag, CreatedBy: apiUser, Access: stateAccess})
   474  		if errors.IsAlreadyExists(err) {
   475  			modelUser, err := st.ModelUser(targetUserTag)
   476  			if errors.IsNotFound(err) {
   477  				// Conflicts with prior check, must be inconsistent state.
   478  				err = txn.ErrExcessiveContention
   479  			}
   480  			if err != nil {
   481  				return errors.Annotate(err, "could not look up model access for user")
   482  			}
   483  
   484  			// Only set access if greater access is being granted.
   485  			if isGreaterAccess(modelUser.Access(), stateAccess) {
   486  				err = modelUser.SetAccess(stateAccess)
   487  				if err != nil {
   488  					return errors.Annotate(err, "could not set model access for user")
   489  				}
   490  			} else {
   491  				return errors.Errorf("user already has %q access", modelUser.Access())
   492  			}
   493  			return nil
   494  		}
   495  		return errors.Annotate(err, "could not grant model access")
   496  
   497  	case params.RevokeModelAccess:
   498  		if stateAccess == state.ModelReadAccess {
   499  			// Revoking read access removes all access.
   500  			err := st.RemoveModelUser(targetUserTag)
   501  			return errors.Annotate(err, "could not revoke model access")
   502  
   503  		} else if stateAccess == state.ModelAdminAccess {
   504  			// Revoking admin access sets read-only.
   505  			modelUser, err := st.ModelUser(targetUserTag)
   506  			if err != nil {
   507  				return errors.Annotate(err, "could not look up model access for user")
   508  			}
   509  			err = modelUser.SetAccess(state.ModelReadAccess)
   510  			return errors.Annotate(err, "could not set model access to read-only")
   511  
   512  		} else {
   513  			return errors.Errorf("don't know how to revoke %q access", stateAccess)
   514  		}
   515  
   516  	default:
   517  		return errors.Errorf("unknown action %q", action)
   518  	}
   519  }
   520  
   521  // FromModelAccessParam returns the logical model access type from the API wireformat type.
   522  func FromModelAccessParam(paramAccess params.ModelAccessPermission) (permission.ModelAccess, error) {
   523  	var fail permission.ModelAccess
   524  	switch paramAccess {
   525  	case params.ModelReadAccess:
   526  		return permission.ModelReadAccess, nil
   527  	case params.ModelWriteAccess:
   528  		return permission.ModelWriteAccess, nil
   529  	}
   530  	return fail, errors.Errorf("invalid model access permission %q", paramAccess)
   531  }