1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
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
14
"github.com/juju/errors"
15
"github.com/juju/loggo"
17
"github.com/juju/utils"
18
"github.com/juju/version"
19
"gopkg.in/juju/names.v2"
22
"github.com/juju/juju/apiserver/common"
23
"github.com/juju/juju/apiserver/facade"
24
"github.com/juju/juju/apiserver/params"
25
jujucloud "github.com/juju/juju/cloud"
26
"github.com/juju/juju/controller/modelmanager"
27
"github.com/juju/juju/core/description"
28
"github.com/juju/juju/environs"
29
"github.com/juju/juju/environs/config"
30
"github.com/juju/juju/juju/permission"
31
"github.com/juju/juju/migration"
32
"github.com/juju/juju/state"
33
"github.com/juju/juju/state/stateenvirons"
34
"github.com/juju/juju/tools"
37
var logger = loggo.GetLogger("juju.apiserver.modelmanager")
40
common.RegisterStandardFacade("ModelManager", 2, newFacade)
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
ListModels(user params.Entity) (params.UserModelList, error)
51
// ModelManagerAPI implements the model manager interface and is
52
// the concrete implementation of the api end point.
53
type ModelManagerAPI struct {
54
state common.ModelManagerBackend
55
authorizer facade.Authorizer
56
toolsFinder *common.ToolsFinder
61
var _ ModelManager = (*ModelManagerAPI)(nil)
63
func newFacade(st *state.State, _ facade.Resources, auth facade.Authorizer) (*ModelManagerAPI, error) {
64
configGetter := stateenvirons.EnvironConfigGetter{st}
65
return NewModelManagerAPI(common.NewModelManagerBackend(st), configGetter, auth)
68
// NewModelManagerAPI creates a new api server endpoint for managing
70
func NewModelManagerAPI(
71
st common.ModelManagerBackend,
72
configGetter environs.EnvironConfigGetter,
73
authorizer facade.Authorizer,
74
) (*ModelManagerAPI, error) {
75
if !authorizer.AuthClient() {
76
return nil, common.ErrPerm
78
// Since we know this is a user tag (because AuthClient is true),
79
// we just do the type assertion to the UserTag.
80
apiUser, _ := authorizer.GetAuthTag().(names.UserTag)
81
// Pretty much all of the user manager methods have special casing for admin
82
// users, so look once when we start and remember if the user is an admin.
83
isAdmin, err := st.IsControllerAdministrator(apiUser)
85
return nil, errors.Trace(err)
87
urlGetter := common.NewToolsURLGetter(st.ModelUUID(), st)
88
return &ModelManagerAPI{
90
authorizer: authorizer,
91
toolsFinder: common.NewToolsFinder(configGetter, st, urlGetter),
97
// authCheck checks if the user is acting on their own behalf, or if they
98
// are an administrator acting on behalf of another user.
99
func (m *ModelManagerAPI) authCheck(user names.UserTag) error {
101
logger.Tracef("%q is a controller admin", m.apiUser.Canonical())
105
// We can't just compare the UserTags themselves as the provider part
106
// may be unset, and gets replaced with 'local'. We must compare against
107
// the Canonical value of the user tag.
108
if m.apiUser.Canonical() == user.Canonical() {
111
return common.ErrPerm
114
// ConfigSource describes a type that is able to provide config.
115
// Abstracted primarily for testing.
116
type ConfigSource interface {
117
Config() (*config.Config, error)
120
func (mm *ModelManagerAPI) newModelConfig(
121
cloudSpec environs.CloudSpec,
122
args params.ModelCreateArgs,
123
controllerUUID string,
125
) (*config.Config, error) {
126
// For now, we just smash to the two maps together as we store
127
// the account values and the model config together in the
128
// *config.Config instance.
129
joint := make(map[string]interface{})
130
for key, value := range args.Config {
133
if _, ok := joint["uuid"]; ok {
134
return nil, errors.New("uuid is generated, you cannot specify one")
137
return nil, errors.NewNotValid(nil, "Name must be specified")
139
if _, ok := joint[config.NameKey]; ok {
140
return nil, errors.New("name must not be specified in config")
142
joint[config.NameKey] = args.Name
144
// Copy credential attributes across to model config.
145
// TODO(axw) credentials should not be going into model config.
146
if cloudSpec.Credential != nil {
147
for key, value := range cloudSpec.Credential.Attributes() {
152
baseConfig, err := source.Config()
154
return nil, errors.Trace(err)
156
if joint, err = mm.state.ComposeNewModelConfig(joint); err != nil {
157
return nil, errors.Trace(err)
159
creator := modelmanager.ModelConfigCreator{
160
Provider: environs.Provider,
161
FindTools: func(n version.Number) (tools.List, error) {
162
result, err := mm.toolsFinder.FindTools(params.FindToolsParams{
166
return nil, errors.Trace(err)
168
return result.List, nil
171
return creator.NewModelConfig(cloudSpec, controllerUUID, baseConfig, joint)
174
// CreateModel creates a new model using the account and
175
// model config specified in the args.
176
func (mm *ModelManagerAPI) CreateModel(args params.ModelCreateArgs) (params.ModelInfo, error) {
177
result := params.ModelInfo{}
178
// TODO(perrito666) this check should be part of the authCheck, without this check
179
// any user in the controller may create models.
181
return result, errors.Trace(common.ErrPerm)
183
// Get the controller model first. We need it both for the state
184
// server owner and the ability to get the config.
185
controllerModel, err := mm.state.ControllerModel()
187
return result, errors.Trace(err)
190
ownerTag, err := names.ParseUserTag(args.OwnerTag)
192
return result, errors.Trace(err)
195
// Any user is able to create themselves an model (until real fine
196
// grain permissions are available), and admins (the creator of the state
197
// server model) are able to create models for other people.
198
err = mm.authCheck(ownerTag)
200
return result, errors.Trace(err)
203
cloudName := controllerModel.Cloud()
204
cloud, err := mm.state.Cloud(cloudName)
206
return result, errors.Annotate(err, "getting cloud definition")
209
cloudCredentialName := args.CloudCredential
210
if cloudCredentialName == "" {
211
if ownerTag.Canonical() == controllerModel.Owner().Canonical() {
212
cloudCredentialName = controllerModel.CloudCredential()
214
// TODO(axw) check if the user has one and only one
215
// cloud credential, and if so, use it? For now, we
216
// require the user to specify a credential unless
217
// the cloud does not require one.
219
for _, authType := range cloud.AuthTypes {
220
if authType != jujucloud.EmptyAuthType {
227
return result, errors.NewNotValid(nil, "no credential specified")
232
cloudRegionName := args.CloudRegion
233
if cloudRegionName == "" {
234
cloudRegionName = controllerModel.CloudRegion()
237
var credential *jujucloud.Credential
238
if cloudCredentialName != "" {
239
ownerCredentials, err := mm.state.CloudCredentials(ownerTag, controllerModel.Cloud())
241
return result, errors.Annotate(err, "getting credentials")
243
elem, ok := ownerCredentials[cloudCredentialName]
245
return result, errors.NewNotValid(nil, fmt.Sprintf(
246
"no such credential %q", cloudCredentialName,
252
cloudSpec, err := environs.MakeCloudSpec(cloud, cloudName, cloudRegionName, credential)
254
return result, errors.Trace(err)
257
controllerCfg, err := mm.state.ControllerConfig()
259
return result, errors.Trace(err)
262
newConfig, err := mm.newModelConfig(cloudSpec, args, controllerCfg.ControllerUUID(), controllerModel)
264
return result, errors.Annotate(err, "failed to create config")
267
// Create the Environ.
268
env, err := environs.New(environs.OpenParams{
273
return result, errors.Annotate(err, "failed to open environ")
275
if err := env.Create(environs.CreateParams{
276
ControllerUUID: controllerCfg.ControllerUUID(),
278
return result, errors.Annotate(err, "failed to create environ")
280
storageProviderRegistry := stateenvirons.NewStorageProviderRegistry(env)
282
// NOTE: check the agent-version of the config, and if it is > the current
283
// version, it is not supported, also check existing tools, and if we don't
284
// have tools for that version, also die.
285
model, st, err := mm.state.NewModel(state.ModelArgs{
286
CloudName: cloudName,
287
CloudRegion: cloudRegionName,
288
CloudCredential: cloudCredentialName,
291
StorageProviderRegistry: storageProviderRegistry,
294
return result, errors.Annotate(err, "failed to create new model")
298
return mm.getModelInfo(model.ModelTag())
301
func (mm *ModelManagerAPI) dumpModel(args params.Entity) (map[string]interface{}, error) {
302
modelTag, err := names.ParseModelTag(args.Tag)
304
return nil, errors.Trace(err)
308
if st.ModelTag() != modelTag {
309
st, err = mm.state.ForModel(modelTag)
311
if errors.IsNotFound(err) {
312
return nil, errors.Trace(common.ErrBadId)
314
return nil, errors.Trace(err)
319
// Check model permissions if the user isn't a controller admin.
321
user, err := st.UserAccess(mm.apiUser, mm.state.ModelTag())
323
if errors.IsNotFound(err) {
324
return nil, errors.Trace(common.ErrPerm)
326
// Something weird went on.
327
return nil, errors.Trace(err)
329
if user.Access != description.AdminAccess {
330
return nil, errors.Trace(common.ErrPerm)
334
bytes, err := migration.ExportModel(st)
336
return nil, errors.Trace(err)
338
// Now read it back into a map.
339
var asMap map[string]interface{}
340
err = yaml.Unmarshal(bytes, &asMap)
342
return nil, errors.Trace(err)
344
// In order to serialize the map through JSON, we need to make sure
345
// that all the embedded maps are map[string]interface{}, not
346
// map[interface{}]interface{} which is what YAML gives by default.
347
out, err := utils.ConformYAML(asMap)
349
return nil, errors.Trace(err)
351
return out.(map[string]interface{}), nil
354
// DumpModels will export the models into the database agnostic
355
// representation. The user needs to either be a controller admin, or have
356
// admin privileges on the model itself.
357
func (mm *ModelManagerAPI) DumpModels(args params.Entities) params.MapResults {
358
results := params.MapResults{
359
Results: make([]params.MapResult, len(args.Entities)),
361
for i, entity := range args.Entities {
362
dumped, err := mm.dumpModel(entity)
364
results.Results[i].Error = common.ServerError(err)
367
results.Results[i].Result = dumped
372
// ListModels returns the models that the specified user
373
// has access to in the current server. Only that controller owner
374
// can list models for any user (at this stage). Other users
375
// can only ask about their own models.
376
func (mm *ModelManagerAPI) ListModels(user params.Entity) (params.UserModelList, error) {
377
result := params.UserModelList{}
379
userTag, err := names.ParseUserTag(user.Tag)
381
return result, errors.Trace(err)
384
err = mm.authCheck(userTag)
386
return result, errors.Trace(err)
389
models, err := mm.state.ModelsForUser(userTag)
391
return result, errors.Trace(err)
394
for _, model := range models {
395
var lastConn *time.Time
396
userLastConn, err := model.LastConnection()
398
if !state.IsNeverConnectedError(err) {
399
return result, errors.Trace(err)
402
lastConn = &userLastConn
404
result.UserModels = append(result.UserModels, params.UserModel{
408
OwnerTag: model.Owner().String(),
410
LastConnection: lastConn,
417
// DestroyModel will try to destroy the current model.
418
// If there is a block on destruction, this method will return an error.
419
func (m *ModelManagerAPI) DestroyModel() error {
420
// Any user is able to delete their own model (until real fine
421
// grain permissions are available), and admins (the creator of the state
422
// server model) are able to delete models for other people.
423
model, err := m.state.Model()
425
return errors.Trace(err)
427
err = m.authCheck(model.Owner())
429
return errors.Trace(err)
431
return errors.Trace(common.DestroyModel(m.state, model.ModelTag()))
434
// ModelInfo returns information about the specified models.
435
func (m *ModelManagerAPI) ModelInfo(args params.Entities) (params.ModelInfoResults, error) {
436
results := params.ModelInfoResults{
437
Results: make([]params.ModelInfoResult, len(args.Entities)),
440
getModelInfo := func(arg params.Entity) (params.ModelInfo, error) {
441
tag, err := names.ParseModelTag(arg.Tag)
443
return params.ModelInfo{}, err
445
return m.getModelInfo(tag)
448
for i, arg := range args.Entities {
449
modelInfo, err := getModelInfo(arg)
451
results.Results[i].Error = common.ServerError(err)
454
results.Results[i].Result = &modelInfo
459
func (m *ModelManagerAPI) getModelInfo(tag names.ModelTag) (params.ModelInfo, error) {
460
st, err := m.state.ForModel(tag)
461
if errors.IsNotFound(err) {
462
return params.ModelInfo{}, common.ErrPerm
463
} else if err != nil {
464
return params.ModelInfo{}, err
468
model, err := st.Model()
469
if errors.IsNotFound(err) {
470
return params.ModelInfo{}, common.ErrPerm
471
} else if err != nil {
472
return params.ModelInfo{}, err
475
cfg, err := model.Config()
477
return params.ModelInfo{}, err
479
controllerCfg, err := st.ControllerConfig()
481
return params.ModelInfo{}, err
483
users, err := model.Users()
485
return params.ModelInfo{}, err
487
status, err := model.Status()
489
return params.ModelInfo{}, err
492
owner := model.Owner()
493
info := params.ModelInfo{
496
ControllerUUID: controllerCfg.ControllerUUID(),
497
OwnerTag: owner.String(),
498
Life: params.Life(model.Life().String()),
499
Status: common.EntityStatusFromState(status),
500
ProviderType: cfg.Type(),
501
DefaultSeries: config.PreferredSeries(cfg),
502
Cloud: model.Cloud(),
503
CloudRegion: model.CloudRegion(),
504
CloudCredential: model.CloudCredential(),
507
authorizedOwner := m.authCheck(owner) == nil
508
for _, user := range users {
509
if !authorizedOwner && m.authCheck(user.UserTag) != nil {
510
// The authenticated user is neither the owner
511
// nor administrator, nor the model user, so
512
// has no business knowing about the model user.
516
userInfo, err := common.ModelUserInfo(user, st)
518
return params.ModelInfo{}, errors.Trace(err)
520
info.Users = append(info.Users, userInfo)
523
if len(info.Users) == 0 {
524
// No users, which means the authenticated user doesn't
525
// have access to the model.
526
return params.ModelInfo{}, common.ErrPerm
532
// ModifyModelAccess changes the model access granted to users.
533
func (m *ModelManagerAPI) ModifyModelAccess(args params.ModifyModelAccessRequest) (result params.ErrorResults, _ error) {
534
result = params.ErrorResults{
535
Results: make([]params.ErrorResult, len(args.Changes)),
537
if len(args.Changes) == 0 {
541
for i, arg := range args.Changes {
542
modelAccess, err := FromModelAccessParam(arg.Access)
544
err = errors.Annotate(err, "could not modify model access")
545
result.Results[i].Error = common.ServerError(err)
549
targetUserTag, err := names.ParseUserTag(arg.UserTag)
551
result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify model access"))
554
modelTag, err := names.ParseModelTag(arg.ModelTag)
556
result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify model access"))
560
result.Results[i].Error = common.ServerError(
561
ChangeModelAccess(m.state, modelTag, m.apiUser, targetUserTag, arg.Action, modelAccess, m.isAdmin))
566
// resolveDescriptionAccess returns the state representation of the logical model
568
func resolveDescriptionAccess(access permission.ModelAccess) (description.Access, error) {
569
var fail description.Access
571
case permission.ModelAdminAccess:
572
return description.AdminAccess, nil
573
case permission.ModelReadAccess:
574
return description.ReadAccess, nil
575
case permission.ModelWriteAccess:
576
return description.WriteAccess, nil
578
logger.Errorf("invalid access permission: %+v", access)
579
return fail, errors.Errorf("invalid access permission")
582
func userAuthorizedToChangeAccess(st common.ModelManagerBackend, userIsAdmin bool, userTag names.UserTag) error {
584
// Just confirm that the model that has been given is a valid model.
587
return errors.Trace(err)
592
// Get the current user's ModelUser for the Model to see if the user has
593
// permission to grant or revoke permissions on the model.
594
currentUser, err := st.UserAccess(userTag, st.ModelTag())
596
if errors.IsNotFound(err) {
597
// No, this user doesn't have permission.
598
return common.ErrPerm
600
return errors.Annotate(err, "could not retrieve user")
602
if currentUser.Access != description.AdminAccess {
603
return common.ErrPerm
608
// ChangeModelAccess performs the requested access grant or revoke action for the
609
// specified user on the specified model.
610
func ChangeModelAccess(accessor common.ModelManagerBackend, modelTag names.ModelTag, apiUser, targetUserTag names.UserTag, action params.ModelAction, access permission.ModelAccess, userIsAdmin bool) error {
611
st, err := accessor.ForModel(modelTag)
613
return errors.Annotate(err, "could not lookup model")
617
if err := userAuthorizedToChangeAccess(st, userIsAdmin, apiUser); err != nil {
618
return errors.Trace(err)
621
descriptionAccess, err := resolveDescriptionAccess(access)
623
return errors.Annotate(err, "could not resolve model access")
626
if descriptionAccess == description.UndefinedAccess {
627
return errors.NotValidf("changing model access to %q", description.UndefinedAccess)
631
case params.GrantModelAccess:
632
_, err = st.AddModelUser(state.UserAccessSpec{User: targetUserTag, CreatedBy: apiUser, Access: descriptionAccess})
633
if errors.IsAlreadyExists(err) {
634
modelUser, err := st.UserAccess(targetUserTag, st.ModelTag())
635
if errors.IsNotFound(err) {
636
// Conflicts with prior check, must be inconsistent state.
637
err = txn.ErrExcessiveContention
640
return errors.Annotate(err, "could not look up model access for user")
643
// Only set access if greater access is being granted.
644
if modelUser.Access.EqualOrGreaterModelAccessThan(descriptionAccess) {
645
return errors.Errorf("user already has %q access or greater", descriptionAccess)
647
if _, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, descriptionAccess); err != nil {
648
return errors.Annotate(err, "could not set model access for user")
652
return errors.Annotate(err, "could not grant model access")
654
case params.RevokeModelAccess:
655
switch descriptionAccess {
656
case description.ReadAccess:
657
// Revoking read access removes all access.
658
err := st.RemoveUserAccess(targetUserTag, st.ModelTag())
659
return errors.Annotate(err, "could not revoke model access")
660
case description.WriteAccess:
661
// Revoking write access sets read-only.
662
modelUser, err := st.UserAccess(targetUserTag, st.ModelTag())
664
return errors.Annotate(err, "could not look up model access for user")
666
_, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, description.ReadAccess)
667
return errors.Annotate(err, "could not set model access to read-only")
668
case description.AdminAccess:
669
// Revoking admin access sets read-write.
670
modelUser, err := st.UserAccess(targetUserTag, st.ModelTag())
672
return errors.Annotate(err, "could not look up model access for user")
674
_, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, description.WriteAccess)
675
return errors.Annotate(err, "could not set model access to read-write")
678
return errors.Errorf("don't know how to revoke %q access", descriptionAccess)
682
return errors.Errorf("unknown action %q", action)
686
// FromModelAccessParam returns the logical model access type from the API wireformat type.
687
func FromModelAccessParam(paramAccess params.UserAccessPermission) (permission.ModelAccess, error) {
688
var fail permission.ModelAccess
690
case params.ModelReadAccess:
691
return permission.ModelReadAccess, nil
692
case params.ModelWriteAccess:
693
return permission.ModelWriteAccess, nil
694
case params.ModelAdminAccess:
695
return permission.ModelAdminAccess, nil
697
return fail, errors.Errorf("invalid model access permission %q", paramAccess)