github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/modelmanager/modelmanager.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package modelmanager
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  	"gopkg.in/juju/names.v2"
    10  	"gopkg.in/yaml.v2"
    11  
    12  	"github.com/juju/juju/api/base"
    13  	"github.com/juju/juju/api/common"
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/core/instance"
    16  	"github.com/juju/juju/core/model"
    17  	"github.com/juju/juju/environs/config"
    18  	"github.com/juju/juju/permission"
    19  )
    20  
    21  var logger = loggo.GetLogger("juju.api.modelmanager")
    22  
    23  // Client provides methods that the Juju client command uses to interact
    24  // with models stored in the Juju Server.
    25  type Client struct {
    26  	base.ClientFacade
    27  	facade base.FacadeCaller
    28  	*common.ModelStatusAPI
    29  }
    30  
    31  // NewClient creates a new `Client` based on an existing authenticated API
    32  // connection.
    33  func NewClient(st base.APICallCloser) *Client {
    34  	frontend, backend := base.NewClientFacade(st, "ModelManager")
    35  	return &Client{
    36  		ClientFacade:   frontend,
    37  		facade:         backend,
    38  		ModelStatusAPI: common.NewModelStatusAPI(backend),
    39  	}
    40  }
    41  
    42  // CreateModel creates a new model using the model config,
    43  // cloud region and credential specified in the args.
    44  func (c *Client) CreateModel(
    45  	name, owner, cloud, cloudRegion string,
    46  	cloudCredential names.CloudCredentialTag,
    47  	config map[string]interface{},
    48  ) (base.ModelInfo, error) {
    49  	var result base.ModelInfo
    50  	if !names.IsValidUser(owner) {
    51  		return result, errors.Errorf("invalid owner name %q", owner)
    52  	}
    53  	var cloudTag string
    54  	if cloud != "" {
    55  		if !names.IsValidCloud(cloud) {
    56  			return result, errors.Errorf("invalid cloud name %q", cloud)
    57  		}
    58  		cloudTag = names.NewCloudTag(cloud).String()
    59  	}
    60  	var cloudCredentialTag string
    61  	if cloudCredential != (names.CloudCredentialTag{}) {
    62  		cloudCredentialTag = cloudCredential.String()
    63  	}
    64  	createArgs := params.ModelCreateArgs{
    65  		Name:               name,
    66  		OwnerTag:           names.NewUserTag(owner).String(),
    67  		Config:             config,
    68  		CloudTag:           cloudTag,
    69  		CloudRegion:        cloudRegion,
    70  		CloudCredentialTag: cloudCredentialTag,
    71  	}
    72  	var modelInfo params.ModelInfo
    73  	err := c.facade.FacadeCall("CreateModel", createArgs, &modelInfo)
    74  	if err != nil {
    75  		return result, errors.Trace(err)
    76  	}
    77  	return convertParamsModelInfo(modelInfo)
    78  }
    79  
    80  func convertParamsModelInfo(modelInfo params.ModelInfo) (base.ModelInfo, error) {
    81  	cloud, err := names.ParseCloudTag(modelInfo.CloudTag)
    82  	if err != nil {
    83  		return base.ModelInfo{}, err
    84  	}
    85  	var credential string
    86  	if modelInfo.CloudCredentialTag != "" {
    87  		credTag, err := names.ParseCloudCredentialTag(modelInfo.CloudCredentialTag)
    88  		if err != nil {
    89  			return base.ModelInfo{}, err
    90  		}
    91  		credential = credTag.Id()
    92  	}
    93  	ownerTag, err := names.ParseUserTag(modelInfo.OwnerTag)
    94  	if err != nil {
    95  		return base.ModelInfo{}, err
    96  	}
    97  	result := base.ModelInfo{
    98  		Name:            modelInfo.Name,
    99  		UUID:            modelInfo.UUID,
   100  		ControllerUUID:  modelInfo.ControllerUUID,
   101  		IsController:    modelInfo.IsController,
   102  		ProviderType:    modelInfo.ProviderType,
   103  		DefaultSeries:   modelInfo.DefaultSeries,
   104  		Cloud:           cloud.Id(),
   105  		CloudRegion:     modelInfo.CloudRegion,
   106  		CloudCredential: credential,
   107  		Owner:           ownerTag.Id(),
   108  		Life:            string(modelInfo.Life),
   109  		AgentVersion:    modelInfo.AgentVersion,
   110  	}
   111  	modelType := modelInfo.Type
   112  	if modelType == "" {
   113  		modelType = model.IAAS.String()
   114  	}
   115  	result.Type = model.ModelType(modelType)
   116  	result.Status = base.Status{
   117  		Status: modelInfo.Status.Status,
   118  		Info:   modelInfo.Status.Info,
   119  		Data:   make(map[string]interface{}),
   120  		Since:  modelInfo.Status.Since,
   121  	}
   122  	for k, v := range modelInfo.Status.Data {
   123  		result.Status.Data[k] = v
   124  	}
   125  	result.Users = make([]base.UserInfo, len(modelInfo.Users))
   126  	for i, u := range modelInfo.Users {
   127  		result.Users[i] = base.UserInfo{
   128  			UserName:       u.UserName,
   129  			DisplayName:    u.DisplayName,
   130  			Access:         string(u.Access),
   131  			LastConnection: u.LastConnection,
   132  		}
   133  	}
   134  	result.Machines = make([]base.Machine, len(modelInfo.Machines))
   135  	for i, m := range modelInfo.Machines {
   136  		machine := base.Machine{
   137  			Id:          m.Id,
   138  			InstanceId:  m.InstanceId,
   139  			DisplayName: m.DisplayName,
   140  			HasVote:     m.HasVote,
   141  			WantsVote:   m.WantsVote,
   142  			Status:      m.Status,
   143  		}
   144  		if m.Hardware != nil {
   145  			machine.Hardware = &instance.HardwareCharacteristics{
   146  				Arch:             m.Hardware.Arch,
   147  				Mem:              m.Hardware.Mem,
   148  				RootDisk:         m.Hardware.RootDisk,
   149  				CpuCores:         m.Hardware.Cores,
   150  				CpuPower:         m.Hardware.CpuPower,
   151  				Tags:             m.Hardware.Tags,
   152  				AvailabilityZone: m.Hardware.AvailabilityZone,
   153  			}
   154  		}
   155  		result.Machines[i] = machine
   156  	}
   157  	return result, nil
   158  }
   159  
   160  // ListModels returns the models that the specified user
   161  // has access to in the current server.  Only that controller owner
   162  // can list models for any user (at this stage).  Other users
   163  // can only ask about their own models.
   164  func (c *Client) ListModels(user string) ([]base.UserModel, error) {
   165  	var models params.UserModelList
   166  	if !names.IsValidUser(user) {
   167  		return nil, errors.Errorf("invalid user name %q", user)
   168  	}
   169  	entity := params.Entity{names.NewUserTag(user).String()}
   170  	err := c.facade.FacadeCall("ListModels", entity, &models)
   171  	if err != nil {
   172  		return nil, errors.Trace(err)
   173  	}
   174  	result := make([]base.UserModel, len(models.UserModels))
   175  	for i, usermodel := range models.UserModels {
   176  		owner, err := names.ParseUserTag(usermodel.OwnerTag)
   177  		if err != nil {
   178  			return nil, errors.Annotatef(err, "OwnerTag %q at position %d", usermodel.OwnerTag, i)
   179  		}
   180  		modelType := model.ModelType(usermodel.Type)
   181  		if modelType == "" {
   182  			modelType = model.IAAS
   183  		}
   184  		result[i] = base.UserModel{
   185  			Name:           usermodel.Name,
   186  			UUID:           usermodel.UUID,
   187  			Type:           modelType,
   188  			Owner:          owner.Id(),
   189  			LastConnection: usermodel.LastConnection,
   190  		}
   191  	}
   192  	return result, nil
   193  }
   194  
   195  func (c *Client) ListModelSummaries(user string, all bool) ([]base.UserModelSummary, error) {
   196  	var out params.ModelSummaryResults
   197  	if !names.IsValidUser(user) {
   198  		return nil, errors.Errorf("invalid user name %q", user)
   199  	}
   200  	in := params.ModelSummariesRequest{UserTag: names.NewUserTag(user).String(), All: all}
   201  	err := c.facade.FacadeCall("ListModelSummaries", in, &out)
   202  	if err != nil {
   203  		return nil, errors.Trace(err)
   204  	}
   205  	summaries := make([]base.UserModelSummary, len(out.Results))
   206  	for i, r := range out.Results {
   207  		if r.Error != nil {
   208  			// cope with typed error
   209  			summaries[i] = base.UserModelSummary{Error: errors.Trace(r.Error)}
   210  			continue
   211  		}
   212  		summary := r.Result
   213  		modelType := model.ModelType(summary.Type)
   214  		if modelType == "" {
   215  			modelType = model.IAAS
   216  		}
   217  		summaries[i] = base.UserModelSummary{
   218  			Name:               summary.Name,
   219  			UUID:               summary.UUID,
   220  			Type:               modelType,
   221  			ControllerUUID:     summary.ControllerUUID,
   222  			IsController:       summary.IsController,
   223  			ProviderType:       summary.ProviderType,
   224  			DefaultSeries:      summary.DefaultSeries,
   225  			CloudRegion:        summary.CloudRegion,
   226  			Life:               string(summary.Life),
   227  			ModelUserAccess:    string(summary.UserAccess),
   228  			UserLastConnection: summary.UserLastConnection,
   229  			Counts:             make([]base.EntityCount, len(summary.Counts)),
   230  			AgentVersion:       summary.AgentVersion,
   231  		}
   232  		for pos, count := range summary.Counts {
   233  			summaries[i].Counts[pos] = base.EntityCount{string(count.Entity), count.Count}
   234  		}
   235  		summaries[i].Status = base.Status{
   236  			Status: summary.Status.Status,
   237  			Info:   summary.Status.Info,
   238  			Data:   make(map[string]interface{}),
   239  			Since:  summary.Status.Since,
   240  		}
   241  		//TODO (anastasiamac 2017-11-24) do we need status data for summaries?
   242  		// we do not translate it at cmd/presentation layer and is it really a summary?...
   243  		for k, v := range summary.Status.Data {
   244  			summaries[i].Status.Data[k] = v
   245  		}
   246  		if owner, err := names.ParseUserTag(summary.OwnerTag); err != nil {
   247  			summaries[i].Error = errors.Annotatef(err, "while parsing model owner tag")
   248  			continue
   249  		} else {
   250  			summaries[i].Owner = owner.Id()
   251  		}
   252  		if cloud, err := names.ParseCloudTag(summary.CloudTag); err != nil {
   253  			summaries[i].Error = errors.Annotatef(err, "while parsing model cloud tag")
   254  			continue
   255  		} else {
   256  			summaries[i].Cloud = cloud.Id()
   257  		}
   258  		if summary.CloudCredentialTag != "" {
   259  			if credTag, err := names.ParseCloudCredentialTag(summary.CloudCredentialTag); err != nil {
   260  				summaries[i].Error = errors.Annotatef(err, "while parsing model cloud credential tag")
   261  				continue
   262  			} else {
   263  				summaries[i].CloudCredential = credTag.Id()
   264  			}
   265  		}
   266  		if summary.Migration != nil {
   267  			summaries[i].Migration = &base.MigrationSummary{
   268  				Status:    summary.Migration.Status,
   269  				StartTime: summary.Migration.Start,
   270  				EndTime:   summary.Migration.End,
   271  			}
   272  		}
   273  		if summary.SLA != nil {
   274  			summaries[i].SLA = &base.SLASummary{
   275  				Level: summary.SLA.Level,
   276  				Owner: summary.SLA.Owner,
   277  			}
   278  		}
   279  	}
   280  	return summaries, nil
   281  }
   282  
   283  func (c *Client) ModelInfo(tags []names.ModelTag) ([]params.ModelInfoResult, error) {
   284  	entities := params.Entities{
   285  		Entities: make([]params.Entity, len(tags)),
   286  	}
   287  	for i, tag := range tags {
   288  		entities.Entities[i].Tag = tag.String()
   289  	}
   290  	var results params.ModelInfoResults
   291  	err := c.facade.FacadeCall("ModelInfo", entities, &results)
   292  	if err != nil {
   293  		return nil, errors.Trace(err)
   294  	}
   295  	if len(results.Results) != len(tags) {
   296  		return nil, errors.Errorf("expected %d result(s), got %d", len(tags), len(results.Results))
   297  	}
   298  	for i := range results.Results {
   299  		if results.Results[i].Error != nil {
   300  			continue
   301  		}
   302  		if results.Results[i].Result.Type == "" {
   303  			results.Results[i].Result.Type = model.IAAS.String()
   304  		}
   305  	}
   306  	return results.Results, nil
   307  }
   308  
   309  // DumpModel returns the serialized database agnostic model representation.
   310  func (c *Client) DumpModel(model names.ModelTag, simplified bool) (map[string]interface{}, error) {
   311  	if bestVer := c.BestAPIVersion(); bestVer < 3 {
   312  		logger.Debugf("calling older dump model on v%d", bestVer)
   313  		if simplified {
   314  			logger.Warningf("simplified dump-model not available, server too old")
   315  		}
   316  		return c.dumpModelV2(model)
   317  	}
   318  
   319  	var results params.StringResults
   320  	entities := params.DumpModelRequest{
   321  		Entities:   []params.Entity{{Tag: model.String()}},
   322  		Simplified: simplified,
   323  	}
   324  
   325  	err := c.facade.FacadeCall("DumpModels", entities, &results)
   326  	if err != nil {
   327  		return nil, errors.Trace(err)
   328  	}
   329  	if count := len(results.Results); count != 1 {
   330  		return nil, errors.Errorf("unexpected result count: %d", count)
   331  	}
   332  	result := results.Results[0]
   333  	if result.Error != nil {
   334  		return nil, result.Error
   335  	}
   336  	// Parse back into a map.
   337  	var asMap map[string]interface{}
   338  	err = yaml.Unmarshal([]byte(result.Result), &asMap)
   339  	if err != nil {
   340  		return nil, errors.Trace(err)
   341  	}
   342  
   343  	return asMap, nil
   344  }
   345  
   346  func (c *Client) dumpModelV2(model names.ModelTag) (map[string]interface{}, error) {
   347  	var results params.MapResults
   348  	entities := params.Entities{
   349  		Entities: []params.Entity{{Tag: model.String()}},
   350  	}
   351  
   352  	err := c.facade.FacadeCall("DumpModels", entities, &results)
   353  	if err != nil {
   354  		return nil, errors.Trace(err)
   355  	}
   356  	if count := len(results.Results); count != 1 {
   357  		return nil, errors.Errorf("unexpected result count: %d", count)
   358  	}
   359  	result := results.Results[0]
   360  	if result.Error != nil {
   361  		return nil, result.Error
   362  	}
   363  	return result.Result, nil
   364  }
   365  
   366  // DumpModelDB returns all relevant mongo documents for the model.
   367  func (c *Client) DumpModelDB(model names.ModelTag) (map[string]interface{}, error) {
   368  	var results params.MapResults
   369  	entities := params.Entities{
   370  		Entities: []params.Entity{{Tag: model.String()}},
   371  	}
   372  
   373  	err := c.facade.FacadeCall("DumpModelsDB", entities, &results)
   374  	if err != nil {
   375  		return nil, errors.Trace(err)
   376  	}
   377  	if count := len(results.Results); count != 1 {
   378  		return nil, errors.Errorf("unexpected result count: %d", count)
   379  	}
   380  	result := results.Results[0]
   381  	if result.Error != nil {
   382  		return nil, result.Error
   383  	}
   384  	return result.Result, nil
   385  }
   386  
   387  // DestroyModel puts the specified model into a "dying" state, which will
   388  // cause the model's resources to be cleaned up, after which the model will
   389  // be removed.
   390  func (c *Client) DestroyModel(tag names.ModelTag, destroyStorage *bool) error {
   391  	var args interface{}
   392  	if c.BestAPIVersion() < 4 {
   393  		if destroyStorage == nil || !*destroyStorage {
   394  			return errors.New("this Juju controller requires destroyStorage to be true")
   395  		}
   396  		args = params.Entities{Entities: []params.Entity{{Tag: tag.String()}}}
   397  	} else {
   398  		args = params.DestroyModelsParams{
   399  			Models: []params.DestroyModelParams{{
   400  				ModelTag:       tag.String(),
   401  				DestroyStorage: destroyStorage,
   402  			}},
   403  		}
   404  	}
   405  	var results params.ErrorResults
   406  	if err := c.facade.FacadeCall("DestroyModels", args, &results); err != nil {
   407  		return errors.Trace(err)
   408  	}
   409  	if n := len(results.Results); n != 1 {
   410  		return errors.Errorf("expected 1 result, got %d", n)
   411  	}
   412  	if err := results.Results[0].Error; err != nil {
   413  		return errors.Trace(err)
   414  	}
   415  	return nil
   416  }
   417  
   418  // GrantModel grants a user access to the specified models.
   419  func (c *Client) GrantModel(user, access string, modelUUIDs ...string) error {
   420  	return c.modifyModelUser(params.GrantModelAccess, user, access, modelUUIDs)
   421  }
   422  
   423  // RevokeModel revokes a user's access to the specified models.
   424  func (c *Client) RevokeModel(user, access string, modelUUIDs ...string) error {
   425  	return c.modifyModelUser(params.RevokeModelAccess, user, access, modelUUIDs)
   426  }
   427  
   428  func (c *Client) modifyModelUser(action params.ModelAction, user, access string, modelUUIDs []string) error {
   429  	var args params.ModifyModelAccessRequest
   430  
   431  	if !names.IsValidUser(user) {
   432  		return errors.Errorf("invalid username: %q", user)
   433  	}
   434  	userTag := names.NewUserTag(user)
   435  
   436  	modelAccess := permission.Access(access)
   437  	if err := permission.ValidateModelAccess(modelAccess); err != nil {
   438  		return errors.Trace(err)
   439  	}
   440  	for _, m := range modelUUIDs {
   441  		if !names.IsValidModel(m) {
   442  			return errors.Errorf("invalid model: %q", m)
   443  		}
   444  		modelTag := names.NewModelTag(m)
   445  		args.Changes = append(args.Changes, params.ModifyModelAccess{
   446  			UserTag:  userTag.String(),
   447  			Action:   action,
   448  			Access:   params.UserAccessPermission(modelAccess),
   449  			ModelTag: modelTag.String(),
   450  		})
   451  	}
   452  
   453  	var result params.ErrorResults
   454  	err := c.facade.FacadeCall("ModifyModelAccess", args, &result)
   455  	if err != nil {
   456  		return errors.Trace(err)
   457  	}
   458  	if len(result.Results) != len(args.Changes) {
   459  		return errors.Errorf("expected %d results, got %d", len(args.Changes), len(result.Results))
   460  	}
   461  
   462  	for i, r := range result.Results {
   463  		if r.Error != nil && r.Error.Code == params.CodeAlreadyExists {
   464  			logger.Warningf("model %q is already shared with %q", modelUUIDs[i], userTag.Id())
   465  			result.Results[i].Error = nil
   466  		}
   467  	}
   468  	return result.Combine()
   469  }
   470  
   471  // ModelDefaults returns the default values for various sources used when
   472  // creating a new model.
   473  func (c *Client) ModelDefaults() (config.ModelDefaultAttributes, error) {
   474  	result := params.ModelDefaultsResult{}
   475  	err := c.facade.FacadeCall("ModelDefaults", nil, &result)
   476  	if err != nil {
   477  		return nil, errors.Trace(err)
   478  	}
   479  	values := make(config.ModelDefaultAttributes)
   480  	for name, val := range result.Config {
   481  		setting := config.AttributeDefaultValues{
   482  			Default:    val.Default,
   483  			Controller: val.Controller,
   484  		}
   485  		for _, region := range val.Regions {
   486  			setting.Regions = append(setting.Regions, config.RegionDefaultValue{
   487  				Name:  region.RegionName,
   488  				Value: region.Value})
   489  		}
   490  		values[name] = setting
   491  	}
   492  	return values, nil
   493  }
   494  
   495  // SetModelDefaults updates the specified default model config values.
   496  func (c *Client) SetModelDefaults(cloud, region string, config map[string]interface{}) error {
   497  	var cloudTag string
   498  	if cloud != "" {
   499  		cloudTag = names.NewCloudTag(cloud).String()
   500  	}
   501  	args := params.SetModelDefaults{
   502  		Config: []params.ModelDefaultValues{{
   503  			Config:      config,
   504  			CloudTag:    cloudTag,
   505  			CloudRegion: region,
   506  		}},
   507  	}
   508  	var result params.ErrorResults
   509  	err := c.facade.FacadeCall("SetModelDefaults", args, &result)
   510  	if err != nil {
   511  		return err
   512  	}
   513  	return result.OneError()
   514  }
   515  
   516  // UnsetModelDefaults removes the specified default model config values.
   517  func (c *Client) UnsetModelDefaults(cloud, region string, keys ...string) error {
   518  	var cloudTag string
   519  	if cloud != "" {
   520  		cloudTag = names.NewCloudTag(cloud).String()
   521  	}
   522  	args := params.UnsetModelDefaults{
   523  		Keys: []params.ModelUnsetKeys{{
   524  			Keys:        keys,
   525  			CloudTag:    cloudTag,
   526  			CloudRegion: region,
   527  		}},
   528  	}
   529  	var result params.ErrorResults
   530  	err := c.facade.FacadeCall("UnsetModelDefaults", args, &result)
   531  	if err != nil {
   532  		return err
   533  	}
   534  	return result.OneError()
   535  }
   536  
   537  // ChangeModelCredential replaces cloud credential for a given model with the provided one.
   538  func (c *Client) ChangeModelCredential(model names.ModelTag, credential names.CloudCredentialTag) error {
   539  	if bestVer := c.BestAPIVersion(); bestVer < 5 {
   540  		return errors.NotImplementedf("ChangeModelCredential in version %v", bestVer)
   541  	}
   542  
   543  	var out params.ErrorResults
   544  	in := params.ChangeModelCredentialsParams{
   545  		[]params.ChangeModelCredentialParams{
   546  			{ModelTag: model.String(), CloudCredentialTag: credential.String()},
   547  		},
   548  	}
   549  
   550  	err := c.facade.FacadeCall("ChangeModelCredential", in, &out)
   551  	if err != nil {
   552  		return errors.Trace(err)
   553  	}
   554  	return out.OneError()
   555  }