github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/testing/factory/factory.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package factory 5 6 import ( 7 "fmt" 8 "math/rand" 9 "strconv" 10 "sync/atomic" 11 "time" 12 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils" 15 "github.com/juju/utils/arch" 16 "github.com/juju/utils/series" 17 gc "gopkg.in/check.v1" 18 "gopkg.in/juju/charm.v6-unstable" 19 "gopkg.in/juju/names.v2" 20 21 "github.com/juju/juju/constraints" 22 "github.com/juju/juju/instance" 23 "github.com/juju/juju/network" 24 "github.com/juju/juju/permission" 25 "github.com/juju/juju/state" 26 "github.com/juju/juju/status" 27 "github.com/juju/juju/storage" 28 "github.com/juju/juju/storage/provider" 29 "github.com/juju/juju/testcharms" 30 "github.com/juju/juju/testing" 31 jujuversion "github.com/juju/juju/version" 32 "github.com/juju/version" 33 ) 34 35 const ( 36 symbols = "abcdefghijklmopqrstuvwxyz" 37 ) 38 39 type Factory struct { 40 st *state.State 41 } 42 43 var index uint32 44 45 func NewFactory(st *state.State) *Factory { 46 return &Factory{st: st} 47 } 48 49 // UserParams defines the parameters for creating a user with MakeUser. 50 type UserParams struct { 51 Name string 52 DisplayName string 53 Password string 54 Creator names.Tag 55 NoModelUser bool 56 Disabled bool 57 Access permission.Access 58 } 59 60 // ModelUserParams defines the parameters for creating an environment user. 61 type ModelUserParams struct { 62 User string 63 DisplayName string 64 CreatedBy names.Tag 65 Access permission.Access 66 } 67 68 // CharmParams defines the parameters for creating a charm. 69 type CharmParams struct { 70 Name string 71 Series string 72 Revision string 73 URL string 74 } 75 76 // Params for creating a machine. 77 type MachineParams struct { 78 Series string 79 Jobs []state.MachineJob 80 Password string 81 Nonce string 82 Constraints constraints.Value 83 InstanceId instance.Id 84 Characteristics *instance.HardwareCharacteristics 85 Addresses []network.Address 86 Volumes []state.MachineVolumeParams 87 Filesystems []state.MachineFilesystemParams 88 } 89 90 // ApplicationParams is used when specifying parameters for a new application. 91 type ApplicationParams struct { 92 Name string 93 Charm *state.Charm 94 Status *status.StatusInfo 95 Settings map[string]interface{} 96 Storage map[string]state.StorageConstraints 97 Constraints constraints.Value 98 } 99 100 // UnitParams are used to create units. 101 type UnitParams struct { 102 Application *state.Application 103 Machine *state.Machine 104 Password string 105 SetCharmURL bool 106 Status *status.StatusInfo 107 Constraints constraints.Value 108 } 109 110 // RelationParams are used to create relations. 111 type RelationParams struct { 112 Endpoints []state.Endpoint 113 } 114 115 type MetricParams struct { 116 Unit *state.Unit 117 Time *time.Time 118 Metrics []state.Metric 119 Sent bool 120 DeleteTime *time.Time 121 } 122 123 type ModelParams struct { 124 Name string 125 Owner names.Tag 126 ConfigAttrs testing.Attrs 127 CloudName string 128 CloudRegion string 129 CloudCredential names.CloudCredentialTag 130 StorageProviderRegistry storage.ProviderRegistry 131 } 132 133 type SpaceParams struct { 134 Name string 135 ProviderID network.Id 136 Subnets []string 137 IsPublic bool 138 } 139 140 // RandomSuffix adds a random 5 character suffix to the presented string. 141 func (*Factory) RandomSuffix(prefix string) string { 142 result := prefix 143 for i := 0; i < 5; i++ { 144 result += string(symbols[rand.Intn(len(symbols))]) 145 } 146 return result 147 } 148 149 func uniqueInteger() int { 150 return int(atomic.AddUint32(&index, 1)) 151 } 152 153 func uniqueString(prefix string) string { 154 if prefix == "" { 155 prefix = "no-prefix" 156 } 157 return fmt.Sprintf("%s-%d", prefix, uniqueInteger()) 158 } 159 160 // MakeUser will create a user with values defined by the params. 161 // For attributes of UserParams that are the default empty values, 162 // some meaningful valid values are used instead. 163 // If params is not specified, defaults are used. 164 // If params.NoModelUser is false, the user will also be created 165 // in the current model. 166 func (factory *Factory) MakeUser(c *gc.C, params *UserParams) *state.User { 167 if params == nil { 168 params = &UserParams{} 169 } 170 if params.Name == "" { 171 params.Name = uniqueString("username") 172 } 173 if params.DisplayName == "" { 174 params.DisplayName = uniqueString("display name") 175 } 176 if params.Password == "" { 177 params.Password = "password" 178 } 179 if params.Creator == nil { 180 env, err := factory.st.Model() 181 c.Assert(err, jc.ErrorIsNil) 182 params.Creator = env.Owner() 183 } 184 if params.Access == permission.NoAccess { 185 params.Access = permission.AdminAccess 186 } 187 creatorUserTag := params.Creator.(names.UserTag) 188 user, err := factory.st.AddUser( 189 params.Name, params.DisplayName, params.Password, creatorUserTag.Name()) 190 c.Assert(err, jc.ErrorIsNil) 191 if !params.NoModelUser { 192 _, err := factory.st.AddModelUser(factory.st.ModelUUID(), state.UserAccessSpec{ 193 User: user.UserTag(), 194 CreatedBy: names.NewUserTag(user.CreatedBy()), 195 DisplayName: params.DisplayName, 196 Access: params.Access, 197 }) 198 c.Assert(err, jc.ErrorIsNil) 199 } 200 if params.Disabled { 201 err := user.Disable() 202 c.Assert(err, jc.ErrorIsNil) 203 } 204 return user 205 } 206 207 // MakeModelUser will create a modelUser with values defined by the params. For 208 // attributes of ModelUserParams that are the default empty values, some 209 // meaningful valid values are used instead. If params is not specified, 210 // defaults are used. 211 func (factory *Factory) MakeModelUser(c *gc.C, params *ModelUserParams) permission.UserAccess { 212 if params == nil { 213 params = &ModelUserParams{} 214 } 215 if params.User == "" { 216 user := factory.MakeUser(c, &UserParams{NoModelUser: true}) 217 params.User = user.UserTag().Id() 218 } 219 if params.DisplayName == "" { 220 params.DisplayName = uniqueString("display name") 221 } 222 if params.Access == permission.NoAccess { 223 params.Access = permission.AdminAccess 224 } 225 if params.CreatedBy == nil { 226 env, err := factory.st.Model() 227 c.Assert(err, jc.ErrorIsNil) 228 params.CreatedBy = env.Owner() 229 } 230 createdByUserTag := params.CreatedBy.(names.UserTag) 231 modelUser, err := factory.st.AddModelUser(factory.st.ModelUUID(), state.UserAccessSpec{ 232 User: names.NewUserTag(params.User), 233 CreatedBy: createdByUserTag, 234 DisplayName: params.DisplayName, 235 Access: params.Access, 236 }) 237 c.Assert(err, jc.ErrorIsNil) 238 return modelUser 239 } 240 241 func (factory *Factory) paramsFillDefaults(c *gc.C, params *MachineParams) *MachineParams { 242 if params == nil { 243 params = &MachineParams{} 244 } 245 if params.Series == "" { 246 params.Series = "quantal" 247 } 248 if params.Nonce == "" { 249 params.Nonce = "nonce" 250 } 251 if len(params.Jobs) == 0 { 252 params.Jobs = []state.MachineJob{state.JobHostUnits} 253 } 254 if params.InstanceId == "" { 255 params.InstanceId = instance.Id(uniqueString("id")) 256 } 257 if params.Password == "" { 258 var err error 259 params.Password, err = utils.RandomPassword() 260 c.Assert(err, jc.ErrorIsNil) 261 } 262 if params.Characteristics == nil { 263 arch := "amd64" 264 mem := uint64(64 * 1024 * 1024 * 1024) 265 hardware := instance.HardwareCharacteristics{ 266 Arch: &arch, 267 Mem: &mem, 268 } 269 params.Characteristics = &hardware 270 } 271 272 return params 273 } 274 275 // MakeMachineNested will make a machine nested in the machine with ID given. 276 func (factory *Factory) MakeMachineNested(c *gc.C, parentId string, params *MachineParams) *state.Machine { 277 params = factory.paramsFillDefaults(c, params) 278 machineTemplate := state.MachineTemplate{ 279 Series: params.Series, 280 Jobs: params.Jobs, 281 Volumes: params.Volumes, 282 Filesystems: params.Filesystems, 283 Constraints: params.Constraints, 284 } 285 286 m, err := factory.st.AddMachineInsideMachine( 287 machineTemplate, 288 parentId, 289 instance.LXD, 290 ) 291 c.Assert(err, jc.ErrorIsNil) 292 err = m.SetProvisioned(params.InstanceId, params.Nonce, params.Characteristics) 293 c.Assert(err, jc.ErrorIsNil) 294 current := version.Binary{ 295 Number: jujuversion.Current, 296 Arch: arch.HostArch(), 297 Series: series.HostSeries(), 298 } 299 err = m.SetAgentVersion(current) 300 c.Assert(err, jc.ErrorIsNil) 301 return m 302 } 303 304 // MakeMachine will add a machine with values defined in params. For some 305 // values in params, if they are missing, some meaningful empty values will be 306 // set. 307 // If params is not specified, defaults are used. 308 func (factory *Factory) MakeMachine(c *gc.C, params *MachineParams) *state.Machine { 309 machine, _ := factory.MakeMachineReturningPassword(c, params) 310 return machine 311 } 312 313 // MakeMachineReturningPassword will add a machine with values defined in 314 // params. For some values in params, if they are missing, some meaningful 315 // empty values will be set. If params is not specified, defaults are used. 316 // The machine and its password are returned. 317 func (factory *Factory) MakeMachineReturningPassword(c *gc.C, params *MachineParams) (*state.Machine, string) { 318 params = factory.paramsFillDefaults(c, params) 319 return factory.makeMachineReturningPassword(c, params, true) 320 } 321 322 // MakeUnprovisionedMachineReturningPassword will add a machine with values 323 // defined in params. For some values in params, if they are missing, some 324 // meaningful empty values will be set. If params is not specified, defaults 325 // are used. The machine and its password are returned; the machine will not 326 // be provisioned. 327 func (factory *Factory) MakeUnprovisionedMachineReturningPassword(c *gc.C, params *MachineParams) (*state.Machine, string) { 328 if params != nil { 329 c.Assert(params.Nonce, gc.Equals, "") 330 c.Assert(params.InstanceId, gc.Equals, instance.Id("")) 331 c.Assert(params.Characteristics, gc.IsNil) 332 } 333 params = factory.paramsFillDefaults(c, params) 334 params.Nonce = "" 335 params.InstanceId = "" 336 params.Characteristics = nil 337 return factory.makeMachineReturningPassword(c, params, false) 338 } 339 340 func (factory *Factory) makeMachineReturningPassword(c *gc.C, params *MachineParams, setProvisioned bool) (*state.Machine, string) { 341 machineTemplate := state.MachineTemplate{ 342 Series: params.Series, 343 Jobs: params.Jobs, 344 Volumes: params.Volumes, 345 Filesystems: params.Filesystems, 346 Constraints: params.Constraints, 347 } 348 machine, err := factory.st.AddOneMachine(machineTemplate) 349 c.Assert(err, jc.ErrorIsNil) 350 if setProvisioned { 351 err = machine.SetProvisioned(params.InstanceId, params.Nonce, params.Characteristics) 352 c.Assert(err, jc.ErrorIsNil) 353 } 354 err = machine.SetPassword(params.Password) 355 c.Assert(err, jc.ErrorIsNil) 356 if len(params.Addresses) > 0 { 357 err := machine.SetProviderAddresses(params.Addresses...) 358 c.Assert(err, jc.ErrorIsNil) 359 } 360 current := version.Binary{ 361 Number: jujuversion.Current, 362 Arch: arch.HostArch(), 363 Series: series.HostSeries(), 364 } 365 err = machine.SetAgentVersion(current) 366 c.Assert(err, jc.ErrorIsNil) 367 return machine, params.Password 368 } 369 370 // MakeCharm creates a charm with the values specified in params. 371 // Sensible default values are substituted for missing ones. 372 // Supported charms depend on the charm/testing package. 373 // Currently supported charms: 374 // all-hooks, category, dummy, format2, logging, monitoring, mysql, 375 // mysql-alternative, riak, terracotta, upgrade1, upgrade2, varnish, 376 // varnish-alternative, wordpress. 377 // If params is not specified, defaults are used. 378 func (factory *Factory) MakeCharm(c *gc.C, params *CharmParams) *state.Charm { 379 if params == nil { 380 params = &CharmParams{} 381 } 382 if params.Name == "" { 383 params.Name = "mysql" 384 } 385 if params.Series == "" { 386 params.Series = "quantal" 387 } 388 if params.Revision == "" { 389 params.Revision = fmt.Sprintf("%d", uniqueInteger()) 390 } 391 if params.URL == "" { 392 params.URL = fmt.Sprintf("cs:%s/%s-%s", params.Series, params.Name, params.Revision) 393 } 394 395 ch := testcharms.Repo.CharmDir(params.Name) 396 397 curl := charm.MustParseURL(params.URL) 398 bundleSHA256 := uniqueString("bundlesha") 399 info := state.CharmInfo{ 400 Charm: ch, 401 ID: curl, 402 StoragePath: "fake-storage-path", 403 SHA256: bundleSHA256, 404 } 405 charm, err := factory.st.AddCharm(info) 406 c.Assert(err, jc.ErrorIsNil) 407 return charm 408 } 409 410 // MakeApplication creates an application with the specified parameters, substituting 411 // sane defaults for missing values. 412 // If params is not specified, defaults are used. 413 func (factory *Factory) MakeApplication(c *gc.C, params *ApplicationParams) *state.Application { 414 if params == nil { 415 params = &ApplicationParams{} 416 } 417 if params.Charm == nil { 418 params.Charm = factory.MakeCharm(c, nil) 419 } 420 if params.Name == "" { 421 params.Name = params.Charm.Meta().Name 422 } 423 application, err := factory.st.AddApplication(state.AddApplicationArgs{ 424 Name: params.Name, 425 Charm: params.Charm, 426 Settings: charm.Settings(params.Settings), 427 Storage: params.Storage, 428 Constraints: params.Constraints, 429 }) 430 c.Assert(err, jc.ErrorIsNil) 431 432 if params.Status != nil { 433 now := time.Now() 434 s := status.StatusInfo{ 435 Status: params.Status.Status, 436 Message: params.Status.Message, 437 Data: params.Status.Data, 438 Since: &now, 439 } 440 err = application.SetStatus(s) 441 c.Assert(err, jc.ErrorIsNil) 442 } 443 444 return application 445 } 446 447 // MakeUnit creates an application unit with specified params, filling in 448 // sane defaults for missing values. 449 // If params is not specified, defaults are used. 450 func (factory *Factory) MakeUnit(c *gc.C, params *UnitParams) *state.Unit { 451 unit, _ := factory.MakeUnitReturningPassword(c, params) 452 return unit 453 } 454 455 // MakeUnit creates an application unit with specified params, filling in sane 456 // defaults for missing values. If params is not specified, defaults are used. 457 // The unit and its password are returned. 458 func (factory *Factory) MakeUnitReturningPassword(c *gc.C, params *UnitParams) (*state.Unit, string) { 459 if params == nil { 460 params = &UnitParams{} 461 } 462 if params.Machine == nil { 463 params.Machine = factory.MakeMachine(c, nil) 464 } 465 if params.Application == nil { 466 params.Application = factory.MakeApplication(c, &ApplicationParams{ 467 Constraints: params.Constraints, 468 }) 469 } 470 if params.Password == "" { 471 var err error 472 params.Password, err = utils.RandomPassword() 473 c.Assert(err, jc.ErrorIsNil) 474 } 475 unit, err := params.Application.AddUnit() 476 c.Assert(err, jc.ErrorIsNil) 477 err = unit.AssignToMachine(params.Machine) 478 c.Assert(err, jc.ErrorIsNil) 479 480 agentTools := version.Binary{ 481 Number: jujuversion.Current, 482 Arch: arch.HostArch(), 483 Series: params.Application.Series(), 484 } 485 err = unit.SetAgentVersion(agentTools) 486 c.Assert(err, jc.ErrorIsNil) 487 if params.SetCharmURL { 488 applicationCharmURL, _ := params.Application.CharmURL() 489 err = unit.SetCharmURL(applicationCharmURL) 490 c.Assert(err, jc.ErrorIsNil) 491 } 492 err = unit.SetPassword(params.Password) 493 c.Assert(err, jc.ErrorIsNil) 494 495 if params.Status != nil { 496 now := time.Now() 497 s := status.StatusInfo{ 498 Status: params.Status.Status, 499 Message: params.Status.Message, 500 Data: params.Status.Data, 501 Since: &now, 502 } 503 err = unit.SetStatus(s) 504 c.Assert(err, jc.ErrorIsNil) 505 } 506 507 return unit, params.Password 508 } 509 510 // MakeMetric makes a metric with specified params, filling in 511 // sane defaults for missing values. 512 // If params is not specified, defaults are used. 513 func (factory *Factory) MakeMetric(c *gc.C, params *MetricParams) *state.MetricBatch { 514 now := time.Now().Round(time.Second).UTC() 515 if params == nil { 516 params = &MetricParams{} 517 } 518 if params.Unit == nil { 519 meteredCharm := factory.MakeCharm(c, &CharmParams{Name: "metered", URL: "cs:quantal/metered"}) 520 meteredApplication := factory.MakeApplication(c, &ApplicationParams{Charm: meteredCharm}) 521 params.Unit = factory.MakeUnit(c, &UnitParams{Application: meteredApplication, SetCharmURL: true}) 522 } 523 if params.Time == nil { 524 params.Time = &now 525 } 526 if params.Metrics == nil { 527 params.Metrics = []state.Metric{{"pings", strconv.Itoa(uniqueInteger()), *params.Time}} 528 } 529 530 chURL, ok := params.Unit.CharmURL() 531 c.Assert(ok, gc.Equals, true) 532 533 metric, err := factory.st.AddMetrics( 534 state.BatchParam{ 535 UUID: utils.MustNewUUID().String(), 536 Created: *params.Time, 537 CharmURL: chURL.String(), 538 Metrics: params.Metrics, 539 Unit: params.Unit.UnitTag(), 540 }) 541 c.Assert(err, jc.ErrorIsNil) 542 if params.Sent { 543 t := now 544 if params.DeleteTime != nil { 545 t = *params.DeleteTime 546 } 547 err := metric.SetSent(t) 548 c.Assert(err, jc.ErrorIsNil) 549 } 550 return metric 551 } 552 553 // MakeRelation create a relation with specified params, filling in sane 554 // defaults for missing values. 555 // If params is not specified, defaults are used. 556 func (factory *Factory) MakeRelation(c *gc.C, params *RelationParams) *state.Relation { 557 if params == nil { 558 params = &RelationParams{} 559 } 560 if len(params.Endpoints) == 0 { 561 s1 := factory.MakeApplication(c, &ApplicationParams{ 562 Charm: factory.MakeCharm(c, &CharmParams{ 563 Name: "mysql", 564 }), 565 }) 566 e1, err := s1.Endpoint("server") 567 c.Assert(err, jc.ErrorIsNil) 568 569 s2 := factory.MakeApplication(c, &ApplicationParams{ 570 Charm: factory.MakeCharm(c, &CharmParams{ 571 Name: "wordpress", 572 }), 573 }) 574 e2, err := s2.Endpoint("db") 575 c.Assert(err, jc.ErrorIsNil) 576 577 params.Endpoints = []state.Endpoint{e1, e2} 578 } 579 580 relation, err := factory.st.AddRelation(params.Endpoints...) 581 c.Assert(err, jc.ErrorIsNil) 582 583 return relation 584 } 585 586 // MakeModel creates an model with specified params, 587 // filling in sane defaults for missing values. If params is nil, 588 // defaults are used for all values. 589 // 590 // By default the new model shares the same owner as the calling 591 // Factory's model. 592 func (factory *Factory) MakeModel(c *gc.C, params *ModelParams) *state.State { 593 if params == nil { 594 params = new(ModelParams) 595 } 596 if params.Name == "" { 597 params.Name = uniqueString("testenv") 598 } 599 if params.CloudName == "" { 600 params.CloudName = "dummy" 601 } 602 if params.CloudRegion == "" { 603 params.CloudRegion = "dummy-region" 604 } 605 if params.Owner == nil { 606 origEnv, err := factory.st.Model() 607 c.Assert(err, jc.ErrorIsNil) 608 params.Owner = origEnv.Owner() 609 } 610 if params.StorageProviderRegistry == nil { 611 params.StorageProviderRegistry = provider.CommonStorageProviders() 612 } 613 // It only makes sense to make an model with the same provider 614 // as the initial model, or things will break elsewhere. 615 currentCfg, err := factory.st.ModelConfig() 616 c.Assert(err, jc.ErrorIsNil) 617 618 uuid, err := utils.NewUUID() 619 c.Assert(err, jc.ErrorIsNil) 620 cfg := testing.CustomModelConfig(c, testing.Attrs{ 621 "name": params.Name, 622 "uuid": uuid.String(), 623 "type": currentCfg.Type(), 624 }.Merge(params.ConfigAttrs)) 625 _, st, err := factory.st.NewModel(state.ModelArgs{ 626 CloudName: params.CloudName, 627 CloudRegion: params.CloudRegion, 628 CloudCredential: params.CloudCredential, 629 Config: cfg, 630 Owner: params.Owner.(names.UserTag), 631 StorageProviderRegistry: params.StorageProviderRegistry, 632 }) 633 c.Assert(err, jc.ErrorIsNil) 634 return st 635 } 636 637 // MakeSpace will create a new space with the specified params. If the space 638 // name is not set, a unique space name is created. 639 func (factory *Factory) MakeSpace(c *gc.C, params *SpaceParams) *state.Space { 640 if params == nil { 641 params = new(SpaceParams) 642 } 643 if params.Name == "" { 644 params.Name = uniqueString("space-") 645 } 646 space, err := factory.st.AddSpace(params.Name, params.ProviderID, params.Subnets, params.IsPublic) 647 c.Assert(err, jc.ErrorIsNil) 648 return space 649 }