github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	"fmt"
    12  	"sort"
    13  	"time"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/loggo"
    17  	"github.com/juju/txn"
    18  	"github.com/juju/utils"
    19  	"github.com/juju/version"
    20  	"gopkg.in/juju/names.v2"
    21  	"gopkg.in/yaml.v1"
    22  
    23  	"github.com/juju/juju/apiserver/common"
    24  	"github.com/juju/juju/apiserver/facade"
    25  	"github.com/juju/juju/apiserver/params"
    26  	jujucloud "github.com/juju/juju/cloud"
    27  	"github.com/juju/juju/controller/modelmanager"
    28  	"github.com/juju/juju/environs"
    29  	"github.com/juju/juju/environs/config"
    30  	"github.com/juju/juju/migration"
    31  	"github.com/juju/juju/permission"
    32  	"github.com/juju/juju/state"
    33  	"github.com/juju/juju/state/stateenvirons"
    34  	"github.com/juju/juju/tools"
    35  )
    36  
    37  var logger = loggo.GetLogger("juju.apiserver.modelmanager")
    38  
    39  func init() {
    40  	common.RegisterStandardFacade("ModelManager", 2, newFacade)
    41  }
    42  
    43  // ModelManager defines the methods on the modelmanager API endpoint.
    44  type ModelManager interface {
    45  	CreateModel(args params.ModelCreateArgs) (params.ModelInfo, error)
    46  	DumpModels(args params.Entities) params.MapResults
    47  	DumpModelsDB(args params.Entities) params.MapResults
    48  	ListModels(user params.Entity) (params.UserModelList, error)
    49  	DestroyModels(args params.Entities) (params.ErrorResults, error)
    50  }
    51  
    52  // ModelManagerAPI implements the model manager interface and is
    53  // the concrete implementation of the api end point.
    54  type ModelManagerAPI struct {
    55  	state       common.ModelManagerBackend
    56  	check       *common.BlockChecker
    57  	authorizer  facade.Authorizer
    58  	toolsFinder *common.ToolsFinder
    59  	apiUser     names.UserTag
    60  	isAdmin     bool
    61  }
    62  
    63  var _ ModelManager = (*ModelManagerAPI)(nil)
    64  
    65  func newFacade(st *state.State, _ facade.Resources, auth facade.Authorizer) (*ModelManagerAPI, error) {
    66  	configGetter := stateenvirons.EnvironConfigGetter{st}
    67  	return NewModelManagerAPI(common.NewModelManagerBackend(st), configGetter, auth)
    68  }
    69  
    70  // NewModelManagerAPI creates a new api server endpoint for managing
    71  // models.
    72  func NewModelManagerAPI(
    73  	st common.ModelManagerBackend,
    74  	configGetter environs.EnvironConfigGetter,
    75  	authorizer facade.Authorizer,
    76  ) (*ModelManagerAPI, error) {
    77  	if !authorizer.AuthClient() {
    78  		return nil, common.ErrPerm
    79  	}
    80  	// Since we know this is a user tag (because AuthClient is true),
    81  	// we just do the type assertion to the UserTag.
    82  	apiUser, _ := authorizer.GetAuthTag().(names.UserTag)
    83  	// Pretty much all of the user manager methods have special casing for admin
    84  	// users, so look once when we start and remember if the user is an admin.
    85  	isAdmin, err := authorizer.HasPermission(permission.SuperuserAccess, st.ControllerTag())
    86  	if err != nil {
    87  		return nil, errors.Trace(err)
    88  	}
    89  	urlGetter := common.NewToolsURLGetter(st.ModelUUID(), st)
    90  	return &ModelManagerAPI{
    91  		state:       st,
    92  		check:       common.NewBlockChecker(st),
    93  		authorizer:  authorizer,
    94  		toolsFinder: common.NewToolsFinder(configGetter, st, urlGetter),
    95  		apiUser:     apiUser,
    96  		isAdmin:     isAdmin,
    97  	}, nil
    98  }
    99  
   100  // authCheck checks if the user is acting on their own behalf, or if they
   101  // are an administrator acting on behalf of another user.
   102  func (m *ModelManagerAPI) authCheck(user names.UserTag) error {
   103  	if m.isAdmin {
   104  		logger.Tracef("%q is a controller admin", m.apiUser.Canonical())
   105  		return nil
   106  	}
   107  
   108  	// We can't just compare the UserTags themselves as the provider part
   109  	// may be unset, and gets replaced with 'local'. We must compare against
   110  	// the Canonical value of the user tag.
   111  	if m.apiUser.Canonical() == user.Canonical() {
   112  		return nil
   113  	}
   114  	return common.ErrPerm
   115  }
   116  
   117  func (m *ModelManagerAPI) hasWriteAccess(modelTag names.ModelTag) (bool, error) {
   118  	canWrite, err := m.authorizer.HasPermission(permission.WriteAccess, modelTag)
   119  	if errors.IsNotFound(err) {
   120  		return false, nil
   121  	}
   122  	return canWrite, err
   123  }
   124  
   125  // ConfigSource describes a type that is able to provide config.
   126  // Abstracted primarily for testing.
   127  type ConfigSource interface {
   128  	Config() (*config.Config, error)
   129  }
   130  
   131  func (m *ModelManagerAPI) newModelConfig(
   132  	cloudSpec environs.CloudSpec,
   133  	args params.ModelCreateArgs,
   134  	source ConfigSource,
   135  ) (*config.Config, error) {
   136  	// For now, we just smash to the two maps together as we store
   137  	// the account values and the model config together in the
   138  	// *config.Config instance.
   139  	joint := make(map[string]interface{})
   140  	for key, value := range args.Config {
   141  		joint[key] = value
   142  	}
   143  	if _, ok := joint["uuid"]; ok {
   144  		return nil, errors.New("uuid is generated, you cannot specify one")
   145  	}
   146  	if args.Name == "" {
   147  		return nil, errors.NewNotValid(nil, "Name must be specified")
   148  	}
   149  	if _, ok := joint[config.NameKey]; ok {
   150  		return nil, errors.New("name must not be specified in config")
   151  	}
   152  	joint[config.NameKey] = args.Name
   153  
   154  	baseConfig, err := source.Config()
   155  	if err != nil {
   156  		return nil, errors.Trace(err)
   157  	}
   158  
   159  	regionSpec := &environs.RegionSpec{Cloud: cloudSpec.Name, Region: cloudSpec.Region}
   160  	if joint, err = m.state.ComposeNewModelConfig(joint, regionSpec); err != nil {
   161  		return nil, errors.Trace(err)
   162  	}
   163  
   164  	creator := modelmanager.ModelConfigCreator{
   165  		Provider: environs.Provider,
   166  		FindTools: func(n version.Number) (tools.List, error) {
   167  			result, err := m.toolsFinder.FindTools(params.FindToolsParams{
   168  				Number: n,
   169  			})
   170  			if err != nil {
   171  				return nil, errors.Trace(err)
   172  			}
   173  			return result.List, nil
   174  		},
   175  	}
   176  	return creator.NewModelConfig(cloudSpec, baseConfig, joint)
   177  }
   178  
   179  // CreateModel creates a new model using the account and
   180  // model config specified in the args.
   181  func (m *ModelManagerAPI) CreateModel(args params.ModelCreateArgs) (params.ModelInfo, error) {
   182  	result := params.ModelInfo{}
   183  	canAddModel, err := m.authorizer.HasPermission(permission.AddModelAccess, m.state.ControllerTag())
   184  	if err != nil {
   185  		return result, errors.Trace(err)
   186  	}
   187  	if !canAddModel {
   188  		return result, common.ErrPerm
   189  	}
   190  
   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 := m.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  	var cloudTag names.CloudTag
   204  	cloudRegionName := args.CloudRegion
   205  	if args.CloudTag != "" {
   206  		var err error
   207  		cloudTag, err = names.ParseCloudTag(args.CloudTag)
   208  		if err != nil {
   209  			return result, errors.Trace(err)
   210  		}
   211  	} else {
   212  		cloudTag = names.NewCloudTag(controllerModel.Cloud())
   213  	}
   214  	if cloudRegionName == "" && cloudTag.Id() == controllerModel.Cloud() {
   215  		cloudRegionName = controllerModel.CloudRegion()
   216  	}
   217  
   218  	cloud, err := m.state.Cloud(cloudTag.Id())
   219  	if err != nil {
   220  		if errors.IsNotFound(err) && args.CloudTag != "" {
   221  			// A cloud was specified, and it was not found.
   222  			// Annotate the error with the supported clouds.
   223  			clouds, err := m.state.Clouds()
   224  			if err != nil {
   225  				return result, errors.Trace(err)
   226  			}
   227  			cloudNames := make([]string, 0, len(clouds))
   228  			for tag := range clouds {
   229  				cloudNames = append(cloudNames, tag.Id())
   230  			}
   231  			sort.Strings(cloudNames)
   232  			return result, errors.NewNotFound(err, fmt.Sprintf(
   233  				"cloud %q not found, expected one of %q",
   234  				cloudTag.Id(), cloudNames,
   235  			))
   236  		}
   237  		return result, errors.Annotate(err, "getting cloud definition")
   238  	}
   239  
   240  	var cloudCredentialTag names.CloudCredentialTag
   241  	if args.CloudCredentialTag != "" {
   242  		var err error
   243  		cloudCredentialTag, err = names.ParseCloudCredentialTag(args.CloudCredentialTag)
   244  		if err != nil {
   245  			return result, errors.Trace(err)
   246  		}
   247  	} else {
   248  		if ownerTag.Canonical() == controllerModel.Owner().Canonical() {
   249  			cloudCredentialTag, _ = controllerModel.CloudCredential()
   250  		} else {
   251  			// TODO(axw) check if the user has one and only one
   252  			// cloud credential, and if so, use it? For now, we
   253  			// require the user to specify a credential unless
   254  			// the cloud does not require one.
   255  			var hasEmpty bool
   256  			for _, authType := range cloud.AuthTypes {
   257  				if authType != jujucloud.EmptyAuthType {
   258  					continue
   259  				}
   260  				hasEmpty = true
   261  				break
   262  			}
   263  			if !hasEmpty {
   264  				return result, errors.NewNotValid(nil, "no credential specified")
   265  			}
   266  		}
   267  	}
   268  
   269  	var credential *jujucloud.Credential
   270  	if cloudCredentialTag != (names.CloudCredentialTag{}) {
   271  		credentialValue, err := m.state.CloudCredential(cloudCredentialTag)
   272  		if err != nil {
   273  			return result, errors.Annotate(err, "getting credential")
   274  		}
   275  		credential = &credentialValue
   276  	}
   277  
   278  	cloudSpec, err := environs.MakeCloudSpec(cloud, cloudTag.Id(), cloudRegionName, credential)
   279  	if err != nil {
   280  		return result, errors.Trace(err)
   281  	}
   282  
   283  	controllerCfg, err := m.state.ControllerConfig()
   284  	if err != nil {
   285  		return result, errors.Trace(err)
   286  	}
   287  
   288  	newConfig, err := m.newModelConfig(cloudSpec, args, controllerModel)
   289  	if err != nil {
   290  		return result, errors.Annotate(err, "failed to create config")
   291  	}
   292  
   293  	// Create the Environ.
   294  	env, err := environs.New(environs.OpenParams{
   295  		Cloud:  cloudSpec,
   296  		Config: newConfig,
   297  	})
   298  	if err != nil {
   299  		return result, errors.Annotate(err, "failed to open environ")
   300  	}
   301  	if err := env.Create(environs.CreateParams{
   302  		ControllerUUID: controllerCfg.ControllerUUID(),
   303  	}); err != nil {
   304  		return result, errors.Annotate(err, "failed to create environ")
   305  	}
   306  	storageProviderRegistry := stateenvirons.NewStorageProviderRegistry(env)
   307  
   308  	// NOTE: check the agent-version of the config, and if it is > the current
   309  	// version, it is not supported, also check existing tools, and if we don't
   310  	// have tools for that version, also die.
   311  	model, st, err := m.state.NewModel(state.ModelArgs{
   312  		CloudName:       cloudTag.Id(),
   313  		CloudRegion:     cloudRegionName,
   314  		CloudCredential: cloudCredentialTag,
   315  		Config:          newConfig,
   316  		Owner:           ownerTag,
   317  		StorageProviderRegistry: storageProviderRegistry,
   318  	})
   319  	if err != nil {
   320  		return result, errors.Annotate(err, "failed to create new model")
   321  	}
   322  	defer st.Close()
   323  
   324  	return m.getModelInfo(model.ModelTag())
   325  }
   326  
   327  func (m *ModelManagerAPI) dumpModel(args params.Entity) (map[string]interface{}, error) {
   328  	modelTag, err := names.ParseModelTag(args.Tag)
   329  	if err != nil {
   330  		return nil, errors.Trace(err)
   331  	}
   332  
   333  	isModelAdmin, err := m.authorizer.HasPermission(permission.AdminAccess, modelTag)
   334  	if err != nil {
   335  		return nil, errors.Trace(err)
   336  	}
   337  	if !isModelAdmin && !m.isAdmin {
   338  		return nil, common.ErrPerm
   339  	}
   340  
   341  	st := m.state
   342  	if st.ModelTag() != modelTag {
   343  		st, err = m.state.ForModel(modelTag)
   344  		if err != nil {
   345  			if errors.IsNotFound(err) {
   346  				return nil, errors.Trace(common.ErrBadId)
   347  			}
   348  			return nil, errors.Trace(err)
   349  		}
   350  		defer st.Close()
   351  	}
   352  
   353  	bytes, err := migration.ExportModel(st)
   354  	if err != nil {
   355  		return nil, errors.Trace(err)
   356  	}
   357  	// Now read it back into a map.
   358  	var asMap map[string]interface{}
   359  	err = yaml.Unmarshal(bytes, &asMap)
   360  	if err != nil {
   361  		return nil, errors.Trace(err)
   362  	}
   363  	// In order to serialize the map through JSON, we need to make sure
   364  	// that all the embedded maps are map[string]interface{}, not
   365  	// map[interface{}]interface{} which is what YAML gives by default.
   366  	out, err := utils.ConformYAML(asMap)
   367  	if err != nil {
   368  		return nil, errors.Trace(err)
   369  	}
   370  	return out.(map[string]interface{}), nil
   371  }
   372  
   373  func (m *ModelManagerAPI) dumpModelDB(args params.Entity) (map[string]interface{}, error) {
   374  	modelTag, err := names.ParseModelTag(args.Tag)
   375  	if err != nil {
   376  		return nil, errors.Trace(err)
   377  	}
   378  
   379  	isModelAdmin, err := m.authorizer.HasPermission(permission.AdminAccess, modelTag)
   380  	if err != nil {
   381  		return nil, errors.Trace(err)
   382  	}
   383  	if !isModelAdmin && !m.isAdmin {
   384  		return nil, common.ErrPerm
   385  	}
   386  
   387  	st := m.state
   388  	if st.ModelTag() != modelTag {
   389  		st, err = m.state.ForModel(modelTag)
   390  		if err != nil {
   391  			if errors.IsNotFound(err) {
   392  				return nil, errors.Trace(common.ErrBadId)
   393  			}
   394  			return nil, errors.Trace(err)
   395  		}
   396  		defer st.Close()
   397  	}
   398  
   399  	return st.DumpAll()
   400  }
   401  
   402  // DumpModels will export the models into the database agnostic
   403  // representation. The user needs to either be a controller admin, or have
   404  // admin privileges on the model itself.
   405  func (m *ModelManagerAPI) DumpModels(args params.Entities) params.MapResults {
   406  	results := params.MapResults{
   407  		Results: make([]params.MapResult, len(args.Entities)),
   408  	}
   409  	for i, entity := range args.Entities {
   410  		dumped, err := m.dumpModel(entity)
   411  		if err != nil {
   412  			results.Results[i].Error = common.ServerError(err)
   413  			continue
   414  		}
   415  		results.Results[i].Result = dumped
   416  	}
   417  	return results
   418  }
   419  
   420  // DumpModelsDB will gather all documents from all model collections
   421  // for the specified model. The map result contains a map of collection
   422  // names to lists of documents represented as maps.
   423  func (m *ModelManagerAPI) DumpModelsDB(args params.Entities) params.MapResults {
   424  	results := params.MapResults{
   425  		Results: make([]params.MapResult, len(args.Entities)),
   426  	}
   427  	for i, entity := range args.Entities {
   428  		dumped, err := m.dumpModelDB(entity)
   429  		if err != nil {
   430  			results.Results[i].Error = common.ServerError(err)
   431  			continue
   432  		}
   433  		results.Results[i].Result = dumped
   434  	}
   435  	return results
   436  }
   437  
   438  // ListModels returns the models that the specified user
   439  // has access to in the current server.  Only that controller owner
   440  // can list models for any user (at this stage).  Other users
   441  // can only ask about their own models.
   442  func (m *ModelManagerAPI) ListModels(user params.Entity) (params.UserModelList, error) {
   443  	result := params.UserModelList{}
   444  
   445  	userTag, err := names.ParseUserTag(user.Tag)
   446  	if err != nil {
   447  		return result, errors.Trace(err)
   448  	}
   449  
   450  	err = m.authCheck(userTag)
   451  	if err != nil {
   452  		return result, errors.Trace(err)
   453  	}
   454  
   455  	models, err := m.state.ModelsForUser(userTag)
   456  	if err != nil {
   457  		return result, errors.Trace(err)
   458  	}
   459  
   460  	for _, model := range models {
   461  		var lastConn *time.Time
   462  		userLastConn, err := model.LastConnection()
   463  		if err != nil {
   464  			if !state.IsNeverConnectedError(err) {
   465  				return result, errors.Trace(err)
   466  			}
   467  		} else {
   468  			lastConn = &userLastConn
   469  		}
   470  		result.UserModels = append(result.UserModels, params.UserModel{
   471  			Model: params.Model{
   472  				Name:     model.Name(),
   473  				UUID:     model.UUID(),
   474  				OwnerTag: model.Owner().String(),
   475  			},
   476  			LastConnection: lastConn,
   477  		})
   478  	}
   479  
   480  	return result, nil
   481  }
   482  
   483  // DestroyModels will try to destroy the specified models.
   484  // If there is a block on destruction, this method will return an error.
   485  func (m *ModelManagerAPI) DestroyModels(args params.Entities) (params.ErrorResults, error) {
   486  	results := params.ErrorResults{
   487  		Results: make([]params.ErrorResult, len(args.Entities)),
   488  	}
   489  
   490  	destroyModel := func(tag names.ModelTag) error {
   491  		model, err := m.state.GetModel(tag)
   492  		if err != nil {
   493  			return errors.Trace(err)
   494  		}
   495  		if err := m.authCheck(model.Owner()); err != nil {
   496  			return errors.Trace(err)
   497  		}
   498  		return errors.Trace(common.DestroyModel(m.state, model.ModelTag()))
   499  	}
   500  
   501  	for i, arg := range args.Entities {
   502  		tag, err := names.ParseModelTag(arg.Tag)
   503  		if err != nil {
   504  			results.Results[i].Error = common.ServerError(err)
   505  			continue
   506  		}
   507  		if err := destroyModel(tag); err != nil {
   508  			results.Results[i].Error = common.ServerError(err)
   509  			continue
   510  		}
   511  	}
   512  	return results, nil
   513  }
   514  
   515  // ModelInfo returns information about the specified models.
   516  func (m *ModelManagerAPI) ModelInfo(args params.Entities) (params.ModelInfoResults, error) {
   517  	results := params.ModelInfoResults{
   518  		Results: make([]params.ModelInfoResult, len(args.Entities)),
   519  	}
   520  
   521  	getModelInfo := func(arg params.Entity) (params.ModelInfo, error) {
   522  		tag, err := names.ParseModelTag(arg.Tag)
   523  		if err != nil {
   524  			return params.ModelInfo{}, errors.Trace(err)
   525  		}
   526  		return m.getModelInfo(tag)
   527  	}
   528  
   529  	for i, arg := range args.Entities {
   530  		modelInfo, err := getModelInfo(arg)
   531  		if err != nil {
   532  			results.Results[i].Error = common.ServerError(err)
   533  			continue
   534  		}
   535  		results.Results[i].Result = &modelInfo
   536  	}
   537  	return results, nil
   538  }
   539  
   540  func (m *ModelManagerAPI) getModelInfo(tag names.ModelTag) (params.ModelInfo, error) {
   541  	st, err := m.state.ForModel(tag)
   542  	if errors.IsNotFound(err) {
   543  		return params.ModelInfo{}, common.ErrPerm
   544  	} else if err != nil {
   545  		return params.ModelInfo{}, errors.Trace(err)
   546  	}
   547  	defer st.Close()
   548  
   549  	model, err := st.Model()
   550  	if errors.IsNotFound(err) {
   551  		return params.ModelInfo{}, common.ErrPerm
   552  	} else if err != nil {
   553  		return params.ModelInfo{}, errors.Trace(err)
   554  	}
   555  
   556  	cfg, err := model.Config()
   557  	if err != nil {
   558  		return params.ModelInfo{}, errors.Trace(err)
   559  	}
   560  	controllerCfg, err := st.ControllerConfig()
   561  	if err != nil {
   562  		return params.ModelInfo{}, errors.Trace(err)
   563  	}
   564  	users, err := model.Users()
   565  	if err != nil {
   566  		return params.ModelInfo{}, errors.Trace(err)
   567  	}
   568  	status, err := model.Status()
   569  	if err != nil {
   570  		return params.ModelInfo{}, errors.Trace(err)
   571  	}
   572  
   573  	owner := model.Owner()
   574  	info := params.ModelInfo{
   575  		Name:           cfg.Name(),
   576  		UUID:           cfg.UUID(),
   577  		ControllerUUID: controllerCfg.ControllerUUID(),
   578  		OwnerTag:       owner.String(),
   579  		Life:           params.Life(model.Life().String()),
   580  		Status:         common.EntityStatusFromState(status),
   581  		ProviderType:   cfg.Type(),
   582  		DefaultSeries:  config.PreferredSeries(cfg),
   583  		CloudTag:       names.NewCloudTag(model.Cloud()).String(),
   584  		CloudRegion:    model.CloudRegion(),
   585  	}
   586  
   587  	if cloudCredentialTag, ok := model.CloudCredential(); ok {
   588  		info.CloudCredentialTag = cloudCredentialTag.String()
   589  	}
   590  
   591  	authorizedOwner := m.authCheck(owner) == nil
   592  	for _, user := range users {
   593  		if !authorizedOwner && m.authCheck(user.UserTag) != nil {
   594  			// The authenticated user is neither the owner
   595  			// nor administrator, nor the model user, so
   596  			// has no business knowing about the model user.
   597  			continue
   598  		}
   599  
   600  		userInfo, err := common.ModelUserInfo(user, st)
   601  		if err != nil {
   602  			return params.ModelInfo{}, errors.Trace(err)
   603  		}
   604  		info.Users = append(info.Users, userInfo)
   605  	}
   606  
   607  	if len(info.Users) == 0 {
   608  		// No users, which means the authenticated user doesn't
   609  		// have access to the model.
   610  		return params.ModelInfo{}, common.ErrPerm
   611  	}
   612  
   613  	canSeeMachines := authorizedOwner
   614  	if !canSeeMachines {
   615  		if canSeeMachines, err = m.hasWriteAccess(tag); err != nil {
   616  			return params.ModelInfo{}, errors.Trace(err)
   617  		}
   618  	}
   619  	if canSeeMachines {
   620  		if info.Machines, err = common.ModelMachineInfo(st); err != nil {
   621  			return params.ModelInfo{}, err
   622  		}
   623  	}
   624  	return info, nil
   625  }
   626  
   627  // ModifyModelAccess changes the model access granted to users.
   628  func (m *ModelManagerAPI) ModifyModelAccess(args params.ModifyModelAccessRequest) (result params.ErrorResults, _ error) {
   629  	result = params.ErrorResults{
   630  		Results: make([]params.ErrorResult, len(args.Changes)),
   631  	}
   632  
   633  	canModifyController, err := m.authorizer.HasPermission(permission.SuperuserAccess, m.state.ControllerTag())
   634  	if err != nil {
   635  		return result, errors.Trace(err)
   636  	}
   637  	if len(args.Changes) == 0 {
   638  		return result, nil
   639  	}
   640  
   641  	for i, arg := range args.Changes {
   642  		modelAccess := permission.Access(arg.Access)
   643  		if err := permission.ValidateModelAccess(modelAccess); err != nil {
   644  			err = errors.Annotate(err, "could not modify model access")
   645  			result.Results[i].Error = common.ServerError(err)
   646  			continue
   647  		}
   648  
   649  		modelTag, err := names.ParseModelTag(arg.ModelTag)
   650  		if err != nil {
   651  			result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify model access"))
   652  			continue
   653  		}
   654  		canModifyModel, err := m.authorizer.HasPermission(permission.AdminAccess, modelTag)
   655  		if err != nil {
   656  			return result, errors.Trace(err)
   657  		}
   658  		canModify := canModifyController || canModifyModel
   659  
   660  		if !canModify {
   661  			result.Results[i].Error = common.ServerError(common.ErrPerm)
   662  			continue
   663  		}
   664  
   665  		targetUserTag, err := names.ParseUserTag(arg.UserTag)
   666  		if err != nil {
   667  			result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify model access"))
   668  			continue
   669  		}
   670  
   671  		result.Results[i].Error = common.ServerError(
   672  			changeModelAccess(m.state, modelTag, m.apiUser, targetUserTag, arg.Action, modelAccess, m.isAdmin))
   673  	}
   674  	return result, nil
   675  }
   676  
   677  func userAuthorizedToChangeAccess(st common.ModelManagerBackend, userIsAdmin bool, userTag names.UserTag) error {
   678  	if userIsAdmin {
   679  		// Just confirm that the model that has been given is a valid model.
   680  		_, err := st.Model()
   681  		if err != nil {
   682  			return errors.Trace(err)
   683  		}
   684  		return nil
   685  	}
   686  
   687  	// Get the current user's ModelUser for the Model to see if the user has
   688  	// permission to grant or revoke permissions on the model.
   689  	currentUser, err := st.UserAccess(userTag, st.ModelTag())
   690  	if err != nil {
   691  		if errors.IsNotFound(err) {
   692  			// No, this user doesn't have permission.
   693  			return common.ErrPerm
   694  		}
   695  		return errors.Annotate(err, "could not retrieve user")
   696  	}
   697  	if currentUser.Access != permission.AdminAccess {
   698  		return common.ErrPerm
   699  	}
   700  	return nil
   701  }
   702  
   703  // changeModelAccess performs the requested access grant or revoke action for the
   704  // specified user on the specified model.
   705  func changeModelAccess(accessor common.ModelManagerBackend, modelTag names.ModelTag, apiUser, targetUserTag names.UserTag, action params.ModelAction, access permission.Access, userIsAdmin bool) error {
   706  	st, err := accessor.ForModel(modelTag)
   707  	if err != nil {
   708  		return errors.Annotate(err, "could not lookup model")
   709  	}
   710  	defer st.Close()
   711  
   712  	if err := userAuthorizedToChangeAccess(st, userIsAdmin, apiUser); err != nil {
   713  		return errors.Trace(err)
   714  	}
   715  
   716  	switch action {
   717  	case params.GrantModelAccess:
   718  		_, err = st.AddModelUser(modelTag.Id(), state.UserAccessSpec{User: targetUserTag, CreatedBy: apiUser, Access: access})
   719  		if errors.IsAlreadyExists(err) {
   720  			modelUser, err := st.UserAccess(targetUserTag, modelTag)
   721  			if errors.IsNotFound(err) {
   722  				// Conflicts with prior check, must be inconsistent state.
   723  				err = txn.ErrExcessiveContention
   724  			}
   725  			if err != nil {
   726  				return errors.Annotate(err, "could not look up model access for user")
   727  			}
   728  
   729  			// Only set access if greater access is being granted.
   730  			if modelUser.Access.EqualOrGreaterModelAccessThan(access) {
   731  				return errors.Errorf("user already has %q access or greater", access)
   732  			}
   733  			if _, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, access); err != nil {
   734  				return errors.Annotate(err, "could not set model access for user")
   735  			}
   736  			return nil
   737  		}
   738  		return errors.Annotate(err, "could not grant model access")
   739  
   740  	case params.RevokeModelAccess:
   741  		switch access {
   742  		case permission.ReadAccess:
   743  			// Revoking read access removes all access.
   744  			err := st.RemoveUserAccess(targetUserTag, modelTag)
   745  			return errors.Annotate(err, "could not revoke model access")
   746  		case permission.WriteAccess:
   747  			// Revoking write access sets read-only.
   748  			modelUser, err := st.UserAccess(targetUserTag, modelTag)
   749  			if err != nil {
   750  				return errors.Annotate(err, "could not look up model access for user")
   751  			}
   752  			_, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, permission.ReadAccess)
   753  			return errors.Annotate(err, "could not set model access to read-only")
   754  		case permission.AdminAccess:
   755  			// Revoking admin access sets read-write.
   756  			modelUser, err := st.UserAccess(targetUserTag, modelTag)
   757  			if err != nil {
   758  				return errors.Annotate(err, "could not look up model access for user")
   759  			}
   760  			_, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, permission.WriteAccess)
   761  			return errors.Annotate(err, "could not set model access to read-write")
   762  
   763  		default:
   764  			return errors.Errorf("don't know how to revoke %q access", access)
   765  		}
   766  
   767  	default:
   768  		return errors.Errorf("unknown action %q", action)
   769  	}
   770  }
   771  
   772  // ModelDefaults returns the default config values used when creating a new model.
   773  func (m *ModelManagerAPI) ModelDefaults() (params.ModelDefaultsResult, error) {
   774  	result := params.ModelDefaultsResult{}
   775  	if !m.isAdmin {
   776  		return result, common.ErrPerm
   777  	}
   778  
   779  	values, err := m.state.ModelConfigDefaultValues()
   780  	if err != nil {
   781  		return result, errors.Trace(err)
   782  	}
   783  	result.Config = make(map[string]params.ModelDefaults)
   784  	for attr, val := range values {
   785  		settings := params.ModelDefaults{
   786  			Controller: val.Controller,
   787  			Default:    val.Default,
   788  		}
   789  		for _, v := range val.Regions {
   790  			settings.Regions = append(
   791  				settings.Regions, params.RegionDefaults{
   792  					RegionName: v.Name,
   793  					Value:      v.Value})
   794  		}
   795  		result.Config[attr] = settings
   796  	}
   797  	return result, nil
   798  }
   799  
   800  // SetModelDefaults writes new values for the specified default model settings.
   801  func (m *ModelManagerAPI) SetModelDefaults(args params.SetModelDefaults) (params.ErrorResults, error) {
   802  	results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Config))}
   803  	if err := m.check.ChangeAllowed(); err != nil {
   804  		return results, errors.Trace(err)
   805  	}
   806  	for i, arg := range args.Config {
   807  		results.Results[i].Error = common.ServerError(
   808  			m.setModelDefaults(arg),
   809  		)
   810  	}
   811  	return results, nil
   812  }
   813  
   814  func (m *ModelManagerAPI) setModelDefaults(args params.ModelDefaultValues) error {
   815  	if !m.isAdmin {
   816  		return common.ErrPerm
   817  	}
   818  
   819  	if err := m.check.ChangeAllowed(); err != nil {
   820  		return errors.Trace(err)
   821  	}
   822  	// Make sure we don't allow changing agent-version.
   823  	if _, found := args.Config["agent-version"]; found {
   824  		return errors.New("agent-version cannot have a default value")
   825  	}
   826  
   827  	var rspec *environs.RegionSpec
   828  	if args.CloudRegion != "" {
   829  		spec, err := m.makeRegionSpec(args.CloudTag, args.CloudRegion)
   830  		if err != nil {
   831  			return errors.Trace(err)
   832  		}
   833  		rspec = spec
   834  	}
   835  	return m.state.UpdateModelConfigDefaultValues(args.Config, nil, rspec)
   836  }
   837  
   838  // UnsetModelDefaults removes the specified default model settings.
   839  func (m *ModelManagerAPI) UnsetModelDefaults(args params.UnsetModelDefaults) (params.ErrorResults, error) {
   840  	results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Keys))}
   841  	if !m.isAdmin {
   842  		return results, common.ErrPerm
   843  	}
   844  
   845  	if err := m.check.ChangeAllowed(); err != nil {
   846  		return results, errors.Trace(err)
   847  	}
   848  
   849  	for i, arg := range args.Keys {
   850  		var rspec *environs.RegionSpec
   851  		if arg.CloudRegion != "" {
   852  			spec, err := m.makeRegionSpec(arg.CloudTag, arg.CloudRegion)
   853  			if err != nil {
   854  				results.Results[i].Error = common.ServerError(
   855  					errors.Trace(err))
   856  				continue
   857  			}
   858  			rspec = spec
   859  		}
   860  		results.Results[i].Error = common.ServerError(
   861  			m.state.UpdateModelConfigDefaultValues(nil, arg.Keys, rspec),
   862  		)
   863  	}
   864  	return results, nil
   865  }
   866  
   867  // makeRegionSpec is a helper method for methods that call
   868  // state.UpdateModelConfigDefaultValues.
   869  func (m *ModelManagerAPI) makeRegionSpec(cloudTag, r string) (*environs.RegionSpec, error) {
   870  	cTag, err := names.ParseCloudTag(cloudTag)
   871  	if err != nil {
   872  		return nil, errors.Trace(err)
   873  	}
   874  	rspec, err := environs.NewRegionSpec(cTag.Id(), r)
   875  	if err != nil {
   876  		return nil, errors.Trace(err)
   877  	}
   878  	return rspec, nil
   879  }