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