github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  }