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 }