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