github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 gc "gopkg.in/check.v1" 17 "gopkg.in/juju/charm.v4" 18 19 "github.com/juju/juju/environs" 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/network" 22 "github.com/juju/juju/state" 23 "github.com/juju/juju/testcharms" 24 "github.com/juju/juju/testing" 25 ) 26 27 const ( 28 symbols = "abcdefghijklmopqrstuvwxyz" 29 ) 30 31 type Factory struct { 32 st *state.State 33 } 34 35 var index uint32 36 37 func NewFactory(st *state.State) *Factory { 38 return &Factory{st: st} 39 } 40 41 type UserParams struct { 42 Name string 43 DisplayName string 44 Password string 45 Creator names.Tag 46 NoEnvUser bool 47 Disabled bool 48 } 49 50 // EnvUserParams defines the parameters for creating an environment user. 51 type EnvUserParams struct { 52 User string 53 DisplayName string 54 CreatedBy names.Tag 55 } 56 57 // CharmParams defines the parameters for creating a charm. 58 type CharmParams struct { 59 Name string 60 Series string 61 Revision string 62 URL string 63 } 64 65 // Params for creating a machine. 66 type MachineParams struct { 67 Series string 68 Jobs []state.MachineJob 69 Password string 70 Nonce string 71 InstanceId instance.Id 72 Characteristics *instance.HardwareCharacteristics 73 Addresses []network.Address 74 } 75 76 // ServiceParams is used when specifying parameters for a new service. 77 type ServiceParams struct { 78 Name string 79 Charm *state.Charm 80 Creator names.Tag 81 } 82 83 // UnitParams are used to create units. 84 type UnitParams struct { 85 Service *state.Service 86 Machine *state.Machine 87 SetCharmURL bool 88 } 89 90 // RelationParams are used to create relations. 91 type RelationParams struct { 92 Endpoints []state.Endpoint 93 } 94 95 type MetricParams struct { 96 Unit *state.Unit 97 Time *time.Time 98 Metrics []state.Metric 99 Sent bool 100 } 101 102 type EnvParams struct { 103 Name string 104 Owner names.Tag 105 ConfigAttrs testing.Attrs 106 Prepare bool 107 } 108 109 // RandomSuffix adds a random 5 character suffix to the presented string. 110 func (*Factory) RandomSuffix(prefix string) string { 111 result := prefix 112 for i := 0; i < 5; i++ { 113 result += string(symbols[rand.Intn(len(symbols))]) 114 } 115 return result 116 } 117 118 func uniqueInteger() int { 119 index = atomic.AddUint32(&index, 1) 120 return int(index) 121 } 122 123 func uniqueString(prefix string) string { 124 if prefix == "" { 125 prefix = "no-prefix" 126 } 127 return fmt.Sprintf("%s-%d", prefix, uniqueInteger()) 128 } 129 130 // MakeUser will create a user with values defined by the params. 131 // For attributes of UserParams that are the default empty values, 132 // some meaningful valid values are used instead. 133 // If params is not specified, defaults are used. 134 func (factory *Factory) MakeUser(c *gc.C, params *UserParams) *state.User { 135 if params == nil { 136 params = &UserParams{} 137 } 138 if params.Name == "" { 139 params.Name = uniqueString("username") 140 } 141 if params.DisplayName == "" { 142 params.DisplayName = uniqueString("display name") 143 } 144 if params.Password == "" { 145 params.Password = "password" 146 } 147 if params.Creator == nil { 148 env, err := factory.st.Environment() 149 c.Assert(err, jc.ErrorIsNil) 150 params.Creator = env.Owner() 151 } 152 creatorUserTag := params.Creator.(names.UserTag) 153 user, err := factory.st.AddUser( 154 params.Name, params.DisplayName, params.Password, creatorUserTag.Name()) 155 c.Assert(err, jc.ErrorIsNil) 156 if !params.NoEnvUser { 157 _, err := factory.st.AddEnvironmentUser(user.UserTag(), names.NewUserTag(user.CreatedBy())) 158 c.Assert(err, jc.ErrorIsNil) 159 } 160 if params.Disabled { 161 err := user.Disable() 162 c.Assert(err, jc.ErrorIsNil) 163 } 164 return user 165 } 166 167 // MakeEnvUser will create a envUser with values defined by the params. For 168 // attributes of EnvUserParams that are the default empty values, some 169 // meaningful valid values are used instead. If params is not specified, 170 // defaults are used. 171 func (factory *Factory) MakeEnvUser(c *gc.C, params *EnvUserParams) *state.EnvironmentUser { 172 if params == nil { 173 params = &EnvUserParams{} 174 } 175 if params.User == "" { 176 user := factory.MakeUser(c, &UserParams{NoEnvUser: true}) 177 params.User = user.UserTag().Username() 178 } 179 if params.CreatedBy == nil { 180 user := factory.MakeUser(c, nil) 181 params.CreatedBy = user.UserTag() 182 } 183 createdByUserTag := params.CreatedBy.(names.UserTag) 184 envUser, err := factory.st.AddEnvironmentUser(names.NewUserTag(params.User), createdByUserTag) 185 c.Assert(err, jc.ErrorIsNil) 186 return envUser 187 } 188 189 func (factory *Factory) paramsFillDefaults(c *gc.C, params *MachineParams) *MachineParams { 190 if params == nil { 191 params = &MachineParams{} 192 } 193 if params.Series == "" { 194 params.Series = "quantal" 195 } 196 if params.Nonce == "" { 197 params.Nonce = "nonce" 198 } 199 if len(params.Jobs) == 0 { 200 params.Jobs = []state.MachineJob{state.JobHostUnits} 201 } 202 if params.InstanceId == "" { 203 params.InstanceId = instance.Id(uniqueString("id")) 204 } 205 if params.Password == "" { 206 var err error 207 params.Password, err = utils.RandomPassword() 208 c.Assert(err, jc.ErrorIsNil) 209 } 210 return params 211 } 212 213 func machineParamsToTemplate(p *MachineParams) state.MachineTemplate { 214 return state.MachineTemplate{ 215 Series: p.Series, 216 Nonce: p.Nonce, 217 Jobs: p.Jobs, 218 InstanceId: p.InstanceId, 219 } 220 } 221 222 // MakeMachineNested will make a machine nested in the machine with ID given. 223 func (factory *Factory) MakeMachineNested(c *gc.C, parentId string, params *MachineParams) *state.Machine { 224 params = factory.paramsFillDefaults(c, params) 225 mTmpl := machineParamsToTemplate(params) 226 // Cannot specify an instance id for a new container. 227 mTmpl.InstanceId = "" 228 // Cannot specify a nonce without an instance ID. 229 mTmpl.Nonce = "" 230 231 m, err := factory.st.AddMachineInsideMachine( 232 mTmpl, 233 parentId, 234 instance.LXC, 235 ) 236 c.Assert(err, jc.ErrorIsNil) 237 return m 238 } 239 240 // MakeMachine will add a machine with values defined in params. For some 241 // values in params, if they are missing, some meaningful empty values will be 242 // set. 243 // If params is not specified, defaults are used. 244 func (factory *Factory) MakeMachine(c *gc.C, params *MachineParams) *state.Machine { 245 machine, _ := factory.MakeMachineReturningPassword(c, params) 246 return machine 247 } 248 249 // MakeMachineReturningPassword will add a machine with values defined in 250 // params. For some values in params, if they are missing, some meaningful 251 // empty values will be set. If params is not specified, defaults are used. 252 // The machine and its password are returned. 253 func (factory *Factory) MakeMachineReturningPassword(c *gc.C, params *MachineParams) (*state.Machine, string) { 254 params = factory.paramsFillDefaults(c, params) 255 machine, err := factory.st.AddMachine(params.Series, params.Jobs...) 256 c.Assert(err, jc.ErrorIsNil) 257 err = machine.SetProvisioned(params.InstanceId, params.Nonce, params.Characteristics) 258 c.Assert(err, jc.ErrorIsNil) 259 err = machine.SetPassword(params.Password) 260 c.Assert(err, jc.ErrorIsNil) 261 if len(params.Addresses) > 0 { 262 err := machine.SetAddresses(params.Addresses...) 263 c.Assert(err, jc.ErrorIsNil) 264 } 265 return machine, params.Password 266 } 267 268 // MakeCharm creates a charm with the values specified in params. 269 // Sensible default values are substituted for missing ones. 270 // Supported charms depend on the charm/testing package. 271 // Currently supported charms: 272 // all-hooks, category, dummy, format2, logging, monitoring, mysql, 273 // mysql-alternative, riak, terracotta, upgrade1, upgrade2, varnish, 274 // varnish-alternative, wordpress. 275 // If params is not specified, defaults are used. 276 func (factory *Factory) MakeCharm(c *gc.C, params *CharmParams) *state.Charm { 277 if params == nil { 278 params = &CharmParams{} 279 } 280 if params.Name == "" { 281 params.Name = "mysql" 282 } 283 if params.Series == "" { 284 params.Series = "quantal" 285 } 286 if params.Revision == "" { 287 params.Revision = fmt.Sprintf("%d", uniqueInteger()) 288 } 289 if params.URL == "" { 290 params.URL = fmt.Sprintf("cs:%s/%s-%s", params.Series, params.Name, params.Revision) 291 } 292 293 ch := testcharms.Repo.CharmDir(params.Name) 294 295 curl := charm.MustParseURL(params.URL) 296 bundleSHA256 := uniqueString("bundlesha") 297 charm, err := factory.st.AddCharm(ch, curl, "fake-storage-path", bundleSHA256) 298 c.Assert(err, jc.ErrorIsNil) 299 return charm 300 } 301 302 // MakeService creates a service with the specified parameters, substituting 303 // sane defaults for missing values. 304 // If params is not specified, defaults are used. 305 func (factory *Factory) MakeService(c *gc.C, params *ServiceParams) *state.Service { 306 if params == nil { 307 params = &ServiceParams{} 308 } 309 if params.Charm == nil { 310 params.Charm = factory.MakeCharm(c, nil) 311 } 312 if params.Name == "" { 313 params.Name = params.Charm.Meta().Name 314 } 315 if params.Creator == nil { 316 creator := factory.MakeUser(c, nil) 317 params.Creator = creator.Tag() 318 } 319 _ = params.Creator.(names.UserTag) 320 service, err := factory.st.AddService(params.Name, params.Creator.String(), params.Charm, nil, nil) 321 c.Assert(err, jc.ErrorIsNil) 322 return service 323 } 324 325 // MakeUnit creates a service unit with specified params, filling in 326 // sane defaults for missing values. 327 // If params is not specified, defaults are used. 328 func (factory *Factory) MakeUnit(c *gc.C, params *UnitParams) *state.Unit { 329 if params == nil { 330 params = &UnitParams{} 331 } 332 if params.Machine == nil { 333 params.Machine = factory.MakeMachine(c, nil) 334 } 335 if params.Service == nil { 336 params.Service = factory.MakeService(c, nil) 337 } 338 unit, err := params.Service.AddUnit() 339 c.Assert(err, jc.ErrorIsNil) 340 err = unit.AssignToMachine(params.Machine) 341 c.Assert(err, jc.ErrorIsNil) 342 if params.SetCharmURL { 343 serviceCharmURL, _ := params.Service.CharmURL() 344 err = unit.SetCharmURL(serviceCharmURL) 345 c.Assert(err, jc.ErrorIsNil) 346 } 347 return unit 348 } 349 350 // MakeMetric makes a metric with specified params, filling in 351 // sane defaults for missing values. 352 // If params is not specified, defaults are used. 353 func (factory *Factory) MakeMetric(c *gc.C, params *MetricParams) *state.MetricBatch { 354 now := time.Now().Round(time.Second).UTC() 355 if params == nil { 356 params = &MetricParams{} 357 } 358 if params.Unit == nil { 359 meteredCharm := factory.MakeCharm(c, &CharmParams{Name: "metered", URL: "cs:quantal/metered"}) 360 meteredService := factory.MakeService(c, &ServiceParams{Charm: meteredCharm}) 361 params.Unit = factory.MakeUnit(c, &UnitParams{Service: meteredService, SetCharmURL: true}) 362 } 363 if params.Time == nil { 364 params.Time = &now 365 } 366 if params.Metrics == nil { 367 params.Metrics = []state.Metric{{"pings", strconv.Itoa(uniqueInteger()), *params.Time}} 368 } 369 370 metric, err := params.Unit.AddMetrics(*params.Time, params.Metrics) 371 c.Assert(err, jc.ErrorIsNil) 372 if params.Sent { 373 err := metric.SetSent() 374 c.Assert(err, jc.ErrorIsNil) 375 } 376 return metric 377 } 378 379 // MakeRelation create a relation with specified params, filling in sane 380 // defaults for missing values. 381 // If params is not specified, defaults are used. 382 func (factory *Factory) MakeRelation(c *gc.C, params *RelationParams) *state.Relation { 383 if params == nil { 384 params = &RelationParams{} 385 } 386 if len(params.Endpoints) == 0 { 387 s1 := factory.MakeService(c, &ServiceParams{ 388 Charm: factory.MakeCharm(c, &CharmParams{ 389 Name: "mysql", 390 }), 391 }) 392 e1, err := s1.Endpoint("server") 393 c.Assert(err, jc.ErrorIsNil) 394 395 s2 := factory.MakeService(c, &ServiceParams{ 396 Charm: factory.MakeCharm(c, &CharmParams{ 397 Name: "wordpress", 398 }), 399 }) 400 e2, err := s2.Endpoint("db") 401 c.Assert(err, jc.ErrorIsNil) 402 403 params.Endpoints = []state.Endpoint{e1, e2} 404 } 405 406 relation, err := factory.st.AddRelation(params.Endpoints...) 407 c.Assert(err, jc.ErrorIsNil) 408 409 return relation 410 } 411 412 // MakeEnvironment creates an environment with specified params, 413 // filling in sane defaults for missing values. If params is nil, 414 // defaults are used for all values. 415 // 416 // By default the new enviroment shares the same owner as the calling 417 // Factory's environment. 418 func (factory *Factory) MakeEnvironment(c *gc.C, params *EnvParams) *state.State { 419 if params == nil { 420 params = new(EnvParams) 421 } 422 if params.Name == "" { 423 params.Name = uniqueString("testenv") 424 } 425 if params.Owner == nil { 426 origEnv, err := factory.st.Environment() 427 c.Assert(err, jc.ErrorIsNil) 428 params.Owner = origEnv.Owner() 429 } 430 // It only makes sense to make an environment with the same provider 431 // as the initial environment, or things will break elsewhere. 432 currentCfg, err := factory.st.EnvironConfig() 433 c.Assert(err, jc.ErrorIsNil) 434 435 uuid, err := utils.NewUUID() 436 c.Assert(err, jc.ErrorIsNil) 437 cfg := testing.CustomEnvironConfig(c, testing.Attrs{ 438 "name": params.Name, 439 "uuid": uuid.String(), 440 "type": currentCfg.Type(), 441 "state-port": currentCfg.StatePort(), 442 "api-port": currentCfg.APIPort(), 443 }.Merge(params.ConfigAttrs)) 444 _, st, err := factory.st.NewEnvironment(cfg, params.Owner.(names.UserTag)) 445 c.Assert(err, jc.ErrorIsNil) 446 if params.Prepare { 447 // Prepare the environment. 448 provider, err := environs.Provider(cfg.Type()) 449 c.Assert(err, jc.ErrorIsNil) 450 cfg, err = provider.PrepareForCreateEnvironment(cfg) 451 c.Assert(err, jc.ErrorIsNil) 452 // Now save the config back. 453 err = st.UpdateEnvironConfig(cfg.AllAttrs(), nil, nil) 454 c.Assert(err, jc.ErrorIsNil) 455 } 456 return st 457 }