1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
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"
21
"github.com/juju/juju/constraints"
22
"github.com/juju/juju/core/description"
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/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"
36
symbols = "abcdefghijklmopqrstuvwxyz"
45
func NewFactory(st *state.State) *Factory {
46
return &Factory{st: st}
49
// UserParams defines the parameters for creating a user with MakeUser.
50
type UserParams struct {
57
Access description.Access
60
// ModelUserParams defines the parameters for creating an environment user.
61
type ModelUserParams struct {
65
Access description.Access
68
// CharmParams defines the parameters for creating a charm.
69
type CharmParams struct {
76
// Params for creating a machine.
77
type MachineParams struct {
79
Jobs []state.MachineJob
82
Constraints constraints.Value
83
InstanceId instance.Id
84
Characteristics *instance.HardwareCharacteristics
85
Addresses []network.Address
86
Volumes []state.MachineVolumeParams
87
Filesystems []state.MachineFilesystemParams
90
// ApplicationParams is used when specifying parameters for a new application.
91
type ApplicationParams struct {
94
Status *status.StatusInfo
95
Settings map[string]interface{}
96
Storage map[string]state.StorageConstraints
97
Constraints constraints.Value
100
// UnitParams are used to create units.
101
type UnitParams struct {
102
Application *state.Application
103
Machine *state.Machine
106
Status *status.StatusInfo
107
Constraints constraints.Value
110
// RelationParams are used to create relations.
111
type RelationParams struct {
112
Endpoints []state.Endpoint
115
type MetricParams struct {
118
Metrics []state.Metric
120
DeleteTime *time.Time
123
type ModelParams struct {
126
ConfigAttrs testing.Attrs
129
CloudCredential string
130
StorageProviderRegistry storage.ProviderRegistry
133
type SpaceParams struct {
135
ProviderID network.Id
140
// RandomSuffix adds a random 5 character suffix to the presented string.
141
func (*Factory) RandomSuffix(prefix string) string {
143
for i := 0; i < 5; i++ {
144
result += string(symbols[rand.Intn(len(symbols))])
149
func uniqueInteger() int {
150
return int(atomic.AddUint32(&index, 1))
153
func uniqueString(prefix string) string {
157
return fmt.Sprintf("%s-%d", prefix, uniqueInteger())
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 {
168
params = &UserParams{}
170
if params.Name == "" {
171
params.Name = uniqueString("username")
173
if params.DisplayName == "" {
174
params.DisplayName = uniqueString("display name")
176
if params.Password == "" {
177
params.Password = "password"
179
if params.Creator == nil {
180
env, err := factory.st.Model()
181
c.Assert(err, jc.ErrorIsNil)
182
params.Creator = env.Owner()
184
if params.Access == description.UndefinedAccess {
185
params.Access = description.AdminAccess
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(state.UserAccessSpec{
193
User: user.UserTag(),
194
CreatedBy: names.NewUserTag(user.CreatedBy()),
195
DisplayName: params.DisplayName,
196
Access: params.Access,
198
c.Assert(err, jc.ErrorIsNil)
201
err := user.Disable()
202
c.Assert(err, jc.ErrorIsNil)
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) description.UserAccess {
213
params = &ModelUserParams{}
215
if params.User == "" {
216
user := factory.MakeUser(c, &UserParams{NoModelUser: true})
217
params.User = user.UserTag().Canonical()
219
if params.DisplayName == "" {
220
params.DisplayName = uniqueString("display name")
222
if params.Access == description.UndefinedAccess {
223
params.Access = description.AdminAccess
225
if params.CreatedBy == nil {
226
env, err := factory.st.Model()
227
c.Assert(err, jc.ErrorIsNil)
228
params.CreatedBy = env.Owner()
230
createdByUserTag := params.CreatedBy.(names.UserTag)
231
modelUser, err := factory.st.AddModelUser(state.UserAccessSpec{
232
User: names.NewUserTag(params.User),
233
CreatedBy: createdByUserTag,
234
DisplayName: params.DisplayName,
235
Access: params.Access,
237
c.Assert(err, jc.ErrorIsNil)
241
func (factory *Factory) paramsFillDefaults(c *gc.C, params *MachineParams) *MachineParams {
243
params = &MachineParams{}
245
if params.Series == "" {
246
params.Series = "quantal"
248
if params.Nonce == "" {
249
params.Nonce = "nonce"
251
if len(params.Jobs) == 0 {
252
params.Jobs = []state.MachineJob{state.JobHostUnits}
254
if params.InstanceId == "" {
255
params.InstanceId = instance.Id(uniqueString("id"))
257
if params.Password == "" {
259
params.Password, err = utils.RandomPassword()
260
c.Assert(err, jc.ErrorIsNil)
262
if params.Characteristics == nil {
264
mem := uint64(64 * 1024 * 1024 * 1024)
265
hardware := instance.HardwareCharacteristics{
269
params.Characteristics = &hardware
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,
281
Volumes: params.Volumes,
282
Filesystems: params.Filesystems,
283
Constraints: params.Constraints,
286
m, err := factory.st.AddMachineInsideMachine(
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(),
299
err = m.SetAgentVersion(current)
300
c.Assert(err, jc.ErrorIsNil)
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
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)
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
machineTemplate := state.MachineTemplate{
320
Series: params.Series,
322
Volumes: params.Volumes,
323
Filesystems: params.Filesystems,
324
Constraints: params.Constraints,
326
machine, err := factory.st.AddOneMachine(machineTemplate)
327
c.Assert(err, jc.ErrorIsNil)
328
err = machine.SetProvisioned(params.InstanceId, params.Nonce, params.Characteristics)
329
c.Assert(err, jc.ErrorIsNil)
330
err = machine.SetPassword(params.Password)
331
c.Assert(err, jc.ErrorIsNil)
332
if len(params.Addresses) > 0 {
333
err := machine.SetProviderAddresses(params.Addresses...)
334
c.Assert(err, jc.ErrorIsNil)
336
current := version.Binary{
337
Number: jujuversion.Current,
338
Arch: arch.HostArch(),
339
Series: series.HostSeries(),
341
err = machine.SetAgentVersion(current)
342
c.Assert(err, jc.ErrorIsNil)
343
return machine, params.Password
346
// MakeCharm creates a charm with the values specified in params.
347
// Sensible default values are substituted for missing ones.
348
// Supported charms depend on the charm/testing package.
349
// Currently supported charms:
350
// all-hooks, category, dummy, format2, logging, monitoring, mysql,
351
// mysql-alternative, riak, terracotta, upgrade1, upgrade2, varnish,
352
// varnish-alternative, wordpress.
353
// If params is not specified, defaults are used.
354
func (factory *Factory) MakeCharm(c *gc.C, params *CharmParams) *state.Charm {
356
params = &CharmParams{}
358
if params.Name == "" {
359
params.Name = "mysql"
361
if params.Series == "" {
362
params.Series = "quantal"
364
if params.Revision == "" {
365
params.Revision = fmt.Sprintf("%d", uniqueInteger())
367
if params.URL == "" {
368
params.URL = fmt.Sprintf("cs:%s/%s-%s", params.Series, params.Name, params.Revision)
371
ch := testcharms.Repo.CharmDir(params.Name)
373
curl := charm.MustParseURL(params.URL)
374
bundleSHA256 := uniqueString("bundlesha")
375
info := state.CharmInfo{
378
StoragePath: "fake-storage-path",
379
SHA256: bundleSHA256,
381
charm, err := factory.st.AddCharm(info)
382
c.Assert(err, jc.ErrorIsNil)
386
// MakeApplication creates an application with the specified parameters, substituting
387
// sane defaults for missing values.
388
// If params is not specified, defaults are used.
389
func (factory *Factory) MakeApplication(c *gc.C, params *ApplicationParams) *state.Application {
391
params = &ApplicationParams{}
393
if params.Charm == nil {
394
params.Charm = factory.MakeCharm(c, nil)
396
if params.Name == "" {
397
params.Name = params.Charm.Meta().Name
399
application, err := factory.st.AddApplication(state.AddApplicationArgs{
402
Settings: charm.Settings(params.Settings),
403
Storage: params.Storage,
404
Constraints: params.Constraints,
406
c.Assert(err, jc.ErrorIsNil)
408
if params.Status != nil {
410
s := status.StatusInfo{
411
Status: params.Status.Status,
412
Message: params.Status.Message,
413
Data: params.Status.Data,
416
err = application.SetStatus(s)
417
c.Assert(err, jc.ErrorIsNil)
423
// MakeUnit creates an application unit with specified params, filling in
424
// sane defaults for missing values.
425
// If params is not specified, defaults are used.
426
func (factory *Factory) MakeUnit(c *gc.C, params *UnitParams) *state.Unit {
427
unit, _ := factory.MakeUnitReturningPassword(c, params)
431
// MakeUnit creates an application unit with specified params, filling in sane
432
// defaults for missing values. If params is not specified, defaults are used.
433
// The unit and its password are returned.
434
func (factory *Factory) MakeUnitReturningPassword(c *gc.C, params *UnitParams) (*state.Unit, string) {
436
params = &UnitParams{}
438
if params.Machine == nil {
439
params.Machine = factory.MakeMachine(c, nil)
441
if params.Application == nil {
442
params.Application = factory.MakeApplication(c, &ApplicationParams{
443
Constraints: params.Constraints,
446
if params.Password == "" {
448
params.Password, err = utils.RandomPassword()
449
c.Assert(err, jc.ErrorIsNil)
451
unit, err := params.Application.AddUnit()
452
c.Assert(err, jc.ErrorIsNil)
453
err = unit.AssignToMachine(params.Machine)
454
c.Assert(err, jc.ErrorIsNil)
456
agentTools := version.Binary{
457
Number: jujuversion.Current,
458
Arch: arch.HostArch(),
459
Series: params.Application.Series(),
461
err = unit.SetAgentVersion(agentTools)
462
c.Assert(err, jc.ErrorIsNil)
463
if params.SetCharmURL {
464
applicationCharmURL, _ := params.Application.CharmURL()
465
err = unit.SetCharmURL(applicationCharmURL)
466
c.Assert(err, jc.ErrorIsNil)
468
err = unit.SetPassword(params.Password)
469
c.Assert(err, jc.ErrorIsNil)
471
if params.Status != nil {
473
s := status.StatusInfo{
474
Status: params.Status.Status,
475
Message: params.Status.Message,
476
Data: params.Status.Data,
479
err = unit.SetStatus(s)
480
c.Assert(err, jc.ErrorIsNil)
483
return unit, params.Password
486
// MakeMetric makes a metric with specified params, filling in
487
// sane defaults for missing values.
488
// If params is not specified, defaults are used.
489
func (factory *Factory) MakeMetric(c *gc.C, params *MetricParams) *state.MetricBatch {
490
now := time.Now().Round(time.Second).UTC()
492
params = &MetricParams{}
494
if params.Unit == nil {
495
meteredCharm := factory.MakeCharm(c, &CharmParams{Name: "metered", URL: "cs:quantal/metered"})
496
meteredApplication := factory.MakeApplication(c, &ApplicationParams{Charm: meteredCharm})
497
params.Unit = factory.MakeUnit(c, &UnitParams{Application: meteredApplication, SetCharmURL: true})
499
if params.Time == nil {
502
if params.Metrics == nil {
503
params.Metrics = []state.Metric{{"pings", strconv.Itoa(uniqueInteger()), *params.Time}}
506
chURL, ok := params.Unit.CharmURL()
507
c.Assert(ok, gc.Equals, true)
509
metric, err := factory.st.AddMetrics(
511
UUID: utils.MustNewUUID().String(),
512
Created: *params.Time,
513
CharmURL: chURL.String(),
514
Metrics: params.Metrics,
515
Unit: params.Unit.UnitTag(),
517
c.Assert(err, jc.ErrorIsNil)
520
if params.DeleteTime != nil {
521
t = *params.DeleteTime
523
err := metric.SetSent(t)
524
c.Assert(err, jc.ErrorIsNil)
529
// MakeRelation create a relation with specified params, filling in sane
530
// defaults for missing values.
531
// If params is not specified, defaults are used.
532
func (factory *Factory) MakeRelation(c *gc.C, params *RelationParams) *state.Relation {
534
params = &RelationParams{}
536
if len(params.Endpoints) == 0 {
537
s1 := factory.MakeApplication(c, &ApplicationParams{
538
Charm: factory.MakeCharm(c, &CharmParams{
542
e1, err := s1.Endpoint("server")
543
c.Assert(err, jc.ErrorIsNil)
545
s2 := factory.MakeApplication(c, &ApplicationParams{
546
Charm: factory.MakeCharm(c, &CharmParams{
550
e2, err := s2.Endpoint("db")
551
c.Assert(err, jc.ErrorIsNil)
553
params.Endpoints = []state.Endpoint{e1, e2}
556
relation, err := factory.st.AddRelation(params.Endpoints...)
557
c.Assert(err, jc.ErrorIsNil)
562
// MakeModel creates an model with specified params,
563
// filling in sane defaults for missing values. If params is nil,
564
// defaults are used for all values.
566
// By default the new model shares the same owner as the calling
568
func (factory *Factory) MakeModel(c *gc.C, params *ModelParams) *state.State {
570
params = new(ModelParams)
572
if params.Name == "" {
573
params.Name = uniqueString("testenv")
575
if params.CloudName == "" {
576
params.CloudName = "dummy"
578
if params.Owner == nil {
579
origEnv, err := factory.st.Model()
580
c.Assert(err, jc.ErrorIsNil)
581
params.Owner = origEnv.Owner()
583
if params.StorageProviderRegistry == nil {
584
params.StorageProviderRegistry = provider.CommonStorageProviders()
586
// It only makes sense to make an model with the same provider
587
// as the initial model, or things will break elsewhere.
588
currentCfg, err := factory.st.ModelConfig()
589
c.Assert(err, jc.ErrorIsNil)
591
uuid, err := utils.NewUUID()
592
c.Assert(err, jc.ErrorIsNil)
593
cfg := testing.CustomModelConfig(c, testing.Attrs{
595
"uuid": uuid.String(),
596
"type": currentCfg.Type(),
597
}.Merge(params.ConfigAttrs))
598
_, st, err := factory.st.NewModel(state.ModelArgs{
599
CloudName: params.CloudName,
600
CloudRegion: params.CloudRegion,
601
CloudCredential: params.CloudCredential,
603
Owner: params.Owner.(names.UserTag),
604
StorageProviderRegistry: params.StorageProviderRegistry,
606
c.Assert(err, jc.ErrorIsNil)
610
// MakeSpace will create a new space with the specified params. If the space
611
// name is not set, a unique space name is created.
612
func (factory *Factory) MakeSpace(c *gc.C, params *SpaceParams) *state.Space {
614
params = new(SpaceParams)
616
if params.Name == "" {
617
params.Name = uniqueString("space-")
619
space, err := factory.st.AddSpace(params.Name, params.ProviderID, params.Subnets, params.IsPublic)
620
c.Assert(err, jc.ErrorIsNil)