github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/runner/util_test.go (about)

     1  // Copyright 2012-2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package runner_test
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/juju/names"
    15  	jujutesting "github.com/juju/testing"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/juju/utils"
    18  	"github.com/juju/utils/proxy"
    19  	"github.com/juju/utils/set"
    20  	gc "gopkg.in/check.v1"
    21  	"gopkg.in/juju/charm.v5"
    22  
    23  	"github.com/juju/juju/api"
    24  	"github.com/juju/juju/api/block"
    25  	"github.com/juju/juju/api/uniter"
    26  	"github.com/juju/juju/instance"
    27  	"github.com/juju/juju/juju/testing"
    28  	"github.com/juju/juju/network"
    29  	"github.com/juju/juju/state"
    30  	"github.com/juju/juju/state/multiwatcher"
    31  	"github.com/juju/juju/storage"
    32  	"github.com/juju/juju/worker/uniter/runner"
    33  	"github.com/juju/juju/worker/uniter/runner/jujuc"
    34  )
    35  
    36  var noProxies = proxy.Settings{}
    37  var apiAddrs = []string{"a1:123", "a2:123"}
    38  var expectedApiAddrs = strings.Join(apiAddrs, " ")
    39  
    40  // MockEnvPaths implements Paths for tests that don't need to actually touch
    41  // the filesystem.
    42  type MockEnvPaths struct{}
    43  
    44  func (MockEnvPaths) GetToolsDir() string {
    45  	return "path-to-tools"
    46  }
    47  
    48  func (MockEnvPaths) GetCharmDir() string {
    49  	return "path-to-charm"
    50  }
    51  
    52  func (MockEnvPaths) GetJujucSocket() string {
    53  	return "path-to-jujuc.socket"
    54  }
    55  
    56  func (MockEnvPaths) GetMetricsSpoolDir() string {
    57  	return "path-to-metrics-spool-dir"
    58  }
    59  
    60  // RealPaths implements Paths for tests that do touch the filesystem.
    61  type RealPaths struct {
    62  	tools        string
    63  	charm        string
    64  	socket       string
    65  	metricsspool string
    66  }
    67  
    68  func osDependentSockPath(c *gc.C) string {
    69  	sockPath := filepath.Join(c.MkDir(), "test.sock")
    70  	if runtime.GOOS == "windows" {
    71  		return `\\.\pipe` + sockPath[2:]
    72  	}
    73  	return sockPath
    74  }
    75  
    76  func NewRealPaths(c *gc.C) RealPaths {
    77  	return RealPaths{
    78  		tools:        c.MkDir(),
    79  		charm:        c.MkDir(),
    80  		socket:       osDependentSockPath(c),
    81  		metricsspool: c.MkDir(),
    82  	}
    83  }
    84  
    85  func (p RealPaths) GetMetricsSpoolDir() string {
    86  	return p.metricsspool
    87  }
    88  
    89  func (p RealPaths) GetToolsDir() string {
    90  	return p.tools
    91  }
    92  
    93  func (p RealPaths) GetCharmDir() string {
    94  	return p.charm
    95  }
    96  
    97  func (p RealPaths) GetJujucSocket() string {
    98  	return p.socket
    99  }
   100  
   101  // HookContextSuite contains shared setup for various other test suites. Test
   102  // methods should not be added to this type, because they'll get run repeatedly.
   103  type HookContextSuite struct {
   104  	testing.JujuConnSuite
   105  	service  *state.Service
   106  	unit     *state.Unit
   107  	machine  *state.Machine
   108  	relch    *state.Charm
   109  	relunits map[int]*state.RelationUnit
   110  	storage  *storageContextAccessor
   111  
   112  	st             api.Connection
   113  	uniter         *uniter.State
   114  	apiUnit        *uniter.Unit
   115  	meteredApiUnit *uniter.Unit
   116  	meteredCharm   *state.Charm
   117  	apiRelunits    map[int]*uniter.RelationUnit
   118  	BlockHelper
   119  }
   120  
   121  func (s *HookContextSuite) SetUpTest(c *gc.C) {
   122  	var err error
   123  	s.JujuConnSuite.SetUpTest(c)
   124  	s.BlockHelper = NewBlockHelper(s.APIState)
   125  	c.Assert(s.BlockHelper, gc.NotNil)
   126  	s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() })
   127  
   128  	// reset
   129  	s.machine = nil
   130  
   131  	sch := s.AddTestingCharm(c, "wordpress")
   132  	s.service = s.AddTestingService(c, "u", sch)
   133  	s.unit = s.AddUnit(c, s.service)
   134  
   135  	s.meteredCharm = s.AddTestingCharm(c, "metered")
   136  	meteredService := s.AddTestingService(c, "m", s.meteredCharm)
   137  	meteredUnit := s.addUnit(c, meteredService)
   138  	err = meteredUnit.SetCharmURL(s.meteredCharm.URL())
   139  	c.Assert(err, jc.ErrorIsNil)
   140  
   141  	password, err := utils.RandomPassword()
   142  	err = s.unit.SetPassword(password)
   143  	c.Assert(err, jc.ErrorIsNil)
   144  	s.st = s.OpenAPIAs(c, s.unit.Tag(), password)
   145  	s.uniter, err = s.st.Uniter()
   146  	c.Assert(err, jc.ErrorIsNil)
   147  	c.Assert(s.uniter, gc.NotNil)
   148  	s.apiUnit, err = s.uniter.Unit(s.unit.Tag().(names.UnitTag))
   149  	c.Assert(err, jc.ErrorIsNil)
   150  
   151  	err = meteredUnit.SetPassword(password)
   152  	c.Assert(err, jc.ErrorIsNil)
   153  	meteredState := s.OpenAPIAs(c, meteredUnit.Tag(), password)
   154  	meteredUniter, err := meteredState.Uniter()
   155  	s.meteredApiUnit, err = meteredUniter.Unit(meteredUnit.Tag().(names.UnitTag))
   156  	c.Assert(err, jc.ErrorIsNil)
   157  
   158  	// Note: The unit must always have a charm URL set, because this
   159  	// happens as part of the installation process (that happens
   160  	// before the initial install hook).
   161  	err = s.unit.SetCharmURL(sch.URL())
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	s.relch = s.AddTestingCharm(c, "mysql")
   164  	s.relunits = map[int]*state.RelationUnit{}
   165  	s.apiRelunits = map[int]*uniter.RelationUnit{}
   166  	s.AddContextRelation(c, "db0")
   167  	s.AddContextRelation(c, "db1")
   168  
   169  	storageData0 := names.NewStorageTag("data/0")
   170  	s.storage = &storageContextAccessor{
   171  		map[names.StorageTag]*contextStorage{
   172  			storageData0: &contextStorage{
   173  				storageData0,
   174  				storage.StorageKindBlock,
   175  				"/dev/sdb",
   176  			},
   177  		},
   178  	}
   179  }
   180  
   181  func (s *HookContextSuite) GetContext(
   182  	c *gc.C, relId int, remoteName string,
   183  ) jujuc.Context {
   184  	uuid, err := utils.NewUUID()
   185  	c.Assert(err, jc.ErrorIsNil)
   186  	return s.getHookContext(
   187  		c, uuid.String(), relId, remoteName, noProxies,
   188  	)
   189  }
   190  
   191  func (s *HookContextSuite) addUnit(c *gc.C, svc *state.Service) *state.Unit {
   192  	unit, err := svc.AddUnit()
   193  	c.Assert(err, jc.ErrorIsNil)
   194  	if s.machine != nil {
   195  		err = unit.AssignToMachine(s.machine)
   196  		c.Assert(err, jc.ErrorIsNil)
   197  		return unit
   198  	}
   199  
   200  	err = s.State.AssignUnit(unit, state.AssignCleanEmpty)
   201  	c.Assert(err, jc.ErrorIsNil)
   202  	machineId, err := unit.AssignedMachineId()
   203  	c.Assert(err, jc.ErrorIsNil)
   204  	s.machine, err = s.State.Machine(machineId)
   205  	c.Assert(err, jc.ErrorIsNil)
   206  	zone := "a-zone"
   207  	hwc := instance.HardwareCharacteristics{
   208  		AvailabilityZone: &zone,
   209  	}
   210  	err = s.machine.SetProvisioned("i-exist", "fake_nonce", &hwc)
   211  	c.Assert(err, jc.ErrorIsNil)
   212  	return unit
   213  }
   214  
   215  func (s *HookContextSuite) AddUnit(c *gc.C, svc *state.Service) *state.Unit {
   216  	unit := s.addUnit(c, svc)
   217  	name := strings.Replace(unit.Name(), "/", "-", 1)
   218  	privateAddr := network.NewScopedAddress(name+".testing.invalid", network.ScopeCloudLocal)
   219  	err := s.machine.SetProviderAddresses(privateAddr)
   220  	c.Assert(err, jc.ErrorIsNil)
   221  	return unit
   222  }
   223  
   224  func (s *HookContextSuite) AddContextRelation(c *gc.C, name string) {
   225  	s.AddTestingService(c, name, s.relch)
   226  	eps, err := s.State.InferEndpoints("u", name)
   227  	c.Assert(err, jc.ErrorIsNil)
   228  	rel, err := s.State.AddRelation(eps...)
   229  	c.Assert(err, jc.ErrorIsNil)
   230  	ru, err := rel.Unit(s.unit)
   231  	c.Assert(err, jc.ErrorIsNil)
   232  	err = ru.EnterScope(map[string]interface{}{"relation-name": name})
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	s.relunits[rel.Id()] = ru
   235  	apiRel, err := s.uniter.Relation(rel.Tag().(names.RelationTag))
   236  	c.Assert(err, jc.ErrorIsNil)
   237  	apiRelUnit, err := apiRel.Unit(s.apiUnit)
   238  	c.Assert(err, jc.ErrorIsNil)
   239  	s.apiRelunits[rel.Id()] = apiRelUnit
   240  }
   241  
   242  func (s *HookContextSuite) getHookContext(c *gc.C, uuid string, relid int,
   243  	remote string, proxies proxy.Settings) *runner.HookContext {
   244  	if relid != -1 {
   245  		_, found := s.apiRelunits[relid]
   246  		c.Assert(found, jc.IsTrue)
   247  	}
   248  	facade, err := s.st.Uniter()
   249  	c.Assert(err, jc.ErrorIsNil)
   250  
   251  	relctxs := map[int]*runner.ContextRelation{}
   252  	for relId, relUnit := range s.apiRelunits {
   253  		cache := runner.NewRelationCache(relUnit.ReadSettings, nil)
   254  		relctxs[relId] = runner.NewContextRelation(relUnit, cache)
   255  	}
   256  
   257  	env, err := s.State.Environment()
   258  	c.Assert(err, jc.ErrorIsNil)
   259  
   260  	context, err := runner.NewHookContext(s.apiUnit, facade, "TestCtx", uuid,
   261  		env.Name(), relid, remote, relctxs, apiAddrs,
   262  		proxies, false, nil, nil, s.machine.Tag().(names.MachineTag),
   263  		NewRealPaths(c))
   264  	c.Assert(err, jc.ErrorIsNil)
   265  	return context
   266  }
   267  
   268  func (s *HookContextSuite) getMeteredHookContext(c *gc.C, uuid string, relid int,
   269  	remote string, proxies proxy.Settings, canAddMetrics bool, metrics *charm.Metrics, paths RealPaths) *runner.HookContext {
   270  	if relid != -1 {
   271  		_, found := s.apiRelunits[relid]
   272  		c.Assert(found, jc.IsTrue)
   273  	}
   274  	facade, err := s.st.Uniter()
   275  	c.Assert(err, jc.ErrorIsNil)
   276  
   277  	relctxs := map[int]*runner.ContextRelation{}
   278  	for relId, relUnit := range s.apiRelunits {
   279  		cache := runner.NewRelationCache(relUnit.ReadSettings, nil)
   280  		relctxs[relId] = runner.NewContextRelation(relUnit, cache)
   281  	}
   282  
   283  	context, err := runner.NewHookContext(s.meteredApiUnit, facade, "TestCtx", uuid,
   284  		"test-env-name", relid, remote, relctxs, apiAddrs,
   285  		proxies, canAddMetrics, metrics, nil, s.machine.Tag().(names.MachineTag),
   286  		paths)
   287  	c.Assert(err, jc.ErrorIsNil)
   288  	return context
   289  }
   290  
   291  func (s *HookContextSuite) metricsDefinition(name string) *charm.Metrics {
   292  	return &charm.Metrics{Metrics: map[string]charm.Metric{name: {Type: charm.MetricTypeGauge, Description: "generated metric"}}}
   293  }
   294  
   295  func (s *HookContextSuite) AssertCoreContext(c *gc.C, ctx runner.Context) {
   296  	c.Assert(ctx.UnitName(), gc.Equals, "u/0")
   297  	c.Assert(runner.ContextMachineTag(ctx), jc.DeepEquals, names.NewMachineTag("0"))
   298  
   299  	expect, expectOK := s.unit.PrivateAddress()
   300  	actual, actualOK := ctx.PrivateAddress()
   301  	c.Assert(actual, gc.Equals, expect)
   302  	c.Assert(actualOK, gc.Equals, expectOK)
   303  
   304  	expect, expectOK = s.unit.PublicAddress()
   305  	actual, actualOK = ctx.PublicAddress()
   306  	c.Assert(actual, gc.Equals, expect)
   307  	c.Assert(actualOK, gc.Equals, expectOK)
   308  
   309  	env, err := s.State.Environment()
   310  	c.Assert(err, jc.ErrorIsNil)
   311  	name, uuid := runner.ContextEnvInfo(ctx)
   312  	c.Assert(name, gc.Equals, env.Name())
   313  	c.Assert(uuid, gc.Equals, env.UUID())
   314  
   315  	c.Assert(ctx.RelationIds(), gc.HasLen, 2)
   316  
   317  	r, found := ctx.Relation(0)
   318  	c.Assert(found, jc.IsTrue)
   319  	c.Assert(r.Name(), gc.Equals, "db")
   320  	c.Assert(r.FakeId(), gc.Equals, "db:0")
   321  
   322  	r, found = ctx.Relation(1)
   323  	c.Assert(found, jc.IsTrue)
   324  	c.Assert(r.Name(), gc.Equals, "db")
   325  	c.Assert(r.FakeId(), gc.Equals, "db:1")
   326  }
   327  
   328  func (s *HookContextSuite) AssertNotActionContext(c *gc.C, ctx runner.Context) {
   329  	actionData, err := ctx.ActionData()
   330  	c.Assert(actionData, gc.IsNil)
   331  	c.Assert(err, gc.ErrorMatches, "not running an action")
   332  }
   333  
   334  func (s *HookContextSuite) AssertActionContext(c *gc.C, ctx runner.Context) {
   335  	actionData, err := ctx.ActionData()
   336  	c.Assert(actionData, gc.NotNil)
   337  	c.Assert(err, jc.ErrorIsNil)
   338  }
   339  
   340  func (s *HookContextSuite) AssertNotStorageContext(c *gc.C, ctx runner.Context) {
   341  	storageAttachment, ok := ctx.HookStorage()
   342  	c.Assert(storageAttachment, gc.IsNil)
   343  	c.Assert(ok, jc.IsFalse)
   344  }
   345  
   346  func (s *HookContextSuite) AssertStorageContext(c *gc.C, ctx runner.Context, id string, attachment storage.StorageAttachmentInfo) {
   347  	fromCache, ok := ctx.HookStorage()
   348  	c.Assert(ok, jc.IsTrue)
   349  	c.Assert(fromCache, gc.NotNil)
   350  	c.Assert(fromCache.Tag().Id(), gc.Equals, id)
   351  	c.Assert(fromCache.Kind(), gc.Equals, attachment.Kind)
   352  	c.Assert(fromCache.Location(), gc.Equals, attachment.Location)
   353  }
   354  
   355  func (s *HookContextSuite) AssertRelationContext(c *gc.C, ctx runner.Context, relId int, remoteUnit string) *runner.ContextRelation {
   356  	actualRemoteUnit, _ := ctx.RemoteUnitName()
   357  	c.Assert(actualRemoteUnit, gc.Equals, remoteUnit)
   358  	rel, found := ctx.HookRelation()
   359  	c.Assert(found, jc.IsTrue)
   360  	c.Assert(rel.Id(), gc.Equals, relId)
   361  	return rel.(*runner.ContextRelation)
   362  }
   363  
   364  func (s *HookContextSuite) AssertNotRelationContext(c *gc.C, ctx runner.Context) {
   365  	rel, found := ctx.HookRelation()
   366  	c.Assert(rel, gc.IsNil)
   367  	c.Assert(found, jc.IsFalse)
   368  }
   369  
   370  // hookSpec supports makeCharm.
   371  type hookSpec struct {
   372  	// dir is the directory to create the hook in.
   373  	dir string
   374  	// name is the name of the hook.
   375  	name string
   376  	// perm is the file permissions of the hook.
   377  	perm os.FileMode
   378  	// code is the exit status of the hook.
   379  	code int
   380  	// stdout holds a string to print to stdout
   381  	stdout string
   382  	// stderr holds a string to print to stderr
   383  	stderr string
   384  	// background holds a string to print in the background after 0.2s.
   385  	background string
   386  }
   387  
   388  // makeCharm constructs a fake charm dir containing a single named hook
   389  // with permissions perm and exit code code. If output is non-empty,
   390  // the charm will write it to stdout and stderr, with each one prefixed
   391  // by name of the stream.
   392  func makeCharm(c *gc.C, spec hookSpec, charmDir string) {
   393  	dir := charmDir
   394  	if spec.dir != "" {
   395  		dir = filepath.Join(dir, spec.dir)
   396  		err := os.Mkdir(dir, 0755)
   397  		c.Assert(err, jc.ErrorIsNil)
   398  	}
   399  	c.Logf("openfile perm %v", spec.perm)
   400  	hook, err := os.OpenFile(
   401  		filepath.Join(dir, spec.name), os.O_CREATE|os.O_WRONLY, spec.perm,
   402  	)
   403  	c.Assert(err, jc.ErrorIsNil)
   404  	defer func() {
   405  		c.Assert(hook.Close(), gc.IsNil)
   406  	}()
   407  
   408  	printf := func(f string, a ...interface{}) {
   409  		_, err := fmt.Fprintf(hook, f+"\n", a...)
   410  		c.Assert(err, jc.ErrorIsNil)
   411  	}
   412  	if runtime.GOOS != "windows" {
   413  		printf("#!/bin/bash")
   414  	}
   415  	printf(echoPidScript)
   416  	if spec.stdout != "" {
   417  		printf("echo %s", spec.stdout)
   418  	}
   419  	if spec.stderr != "" {
   420  		printf("echo %s >&2", spec.stderr)
   421  	}
   422  	if spec.background != "" {
   423  		// Print something fairly quickly, then sleep for
   424  		// quite a long time - if the hook execution is
   425  		// blocking because of the background process,
   426  		// the hook execution will take much longer than
   427  		// expected.
   428  		printf("(sleep 0.2; echo %s; sleep 10) &", spec.background)
   429  	}
   430  	printf("exit %d", spec.code)
   431  }
   432  
   433  type storageContextAccessor struct {
   434  	storage map[names.StorageTag]*contextStorage
   435  }
   436  
   437  func (s *storageContextAccessor) StorageTags() []names.StorageTag {
   438  	tags := set.NewTags()
   439  	for tag := range s.storage {
   440  		tags.Add(tag)
   441  	}
   442  	storageTags := make([]names.StorageTag, len(tags))
   443  	for i, tag := range tags.SortedValues() {
   444  		storageTags[i] = tag.(names.StorageTag)
   445  	}
   446  	return storageTags
   447  }
   448  
   449  func (s *storageContextAccessor) Storage(tag names.StorageTag) (jujuc.ContextStorageAttachment, bool) {
   450  	storage, ok := s.storage[tag]
   451  	return storage, ok
   452  }
   453  
   454  type contextStorage struct {
   455  	tag      names.StorageTag
   456  	kind     storage.StorageKind
   457  	location string
   458  }
   459  
   460  func (c *contextStorage) Tag() names.StorageTag {
   461  	return c.tag
   462  }
   463  
   464  func (c *contextStorage) Kind() storage.StorageKind {
   465  	return c.kind
   466  }
   467  
   468  func (c *contextStorage) Location() string {
   469  	return c.location
   470  }
   471  
   472  type BlockHelper struct {
   473  	blockClient *block.Client
   474  }
   475  
   476  // NewBlockHelper creates a block switch used in testing
   477  // to manage desired juju blocks.
   478  func NewBlockHelper(st api.Connection) BlockHelper {
   479  	return BlockHelper{
   480  		blockClient: block.NewClient(st),
   481  	}
   482  }
   483  
   484  // on switches on desired block and
   485  // asserts that no errors were encountered.
   486  func (s *BlockHelper) on(c *gc.C, blockType multiwatcher.BlockType, msg string) {
   487  	c.Assert(s.blockClient.SwitchBlockOn(string(blockType), msg), gc.IsNil)
   488  }
   489  
   490  // BlockAllChanges switches changes block on.
   491  // This prevents all changes to juju environment.
   492  func (s *BlockHelper) BlockAllChanges(c *gc.C, msg string) {
   493  	s.on(c, multiwatcher.BlockChange, msg)
   494  }
   495  
   496  // BlockRemoveObject switches remove block on.
   497  // This prevents any object/entity removal on juju environment
   498  func (s *BlockHelper) BlockRemoveObject(c *gc.C, msg string) {
   499  	s.on(c, multiwatcher.BlockRemove, msg)
   500  }
   501  
   502  // BlockDestroyEnvironment switches destroy block on.
   503  // This prevents juju environment destruction.
   504  func (s *BlockHelper) BlockDestroyEnvironment(c *gc.C, msg string) {
   505  	s.on(c, multiwatcher.BlockDestroy, msg)
   506  }
   507  
   508  func (s *BlockHelper) Close() {
   509  	s.blockClient.Close()
   510  }
   511  
   512  // StubMetricsRecorder implements the MetricsRecorder interface.
   513  type StubMetricsRecorder struct {
   514  	*jujutesting.Stub
   515  }
   516  
   517  // AddMetric implements the MetricsRecorder interface.
   518  func (s StubMetricsRecorder) AddMetric(key, value string, created time.Time) error {
   519  	s.AddCall("AddMetric", key, value, created)
   520  	return nil
   521  }
   522  
   523  func (mr *StubMetricsRecorder) IsDeclaredMetric(key string) bool {
   524  	mr.MethodCall(mr, "IsDeclaredMetric", key)
   525  	return true
   526  }
   527  
   528  // Close implements the MetricsRecorder interface.
   529  func (s StubMetricsRecorder) Close() error {
   530  	s.AddCall("Close")
   531  	return nil
   532  }
   533  
   534  var _ runner.MetricsRecorder = (*StubMetricsRecorder)(nil)