github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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  
    13  	"github.com/juju/names"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils"
    16  	"github.com/juju/utils/proxy"
    17  	gc "gopkg.in/check.v1"
    18  	"gopkg.in/juju/charm.v5"
    19  
    20  	"github.com/juju/juju/api"
    21  	"github.com/juju/juju/api/block"
    22  	"github.com/juju/juju/api/uniter"
    23  	"github.com/juju/juju/instance"
    24  	"github.com/juju/juju/juju/testing"
    25  	"github.com/juju/juju/network"
    26  	"github.com/juju/juju/state"
    27  	"github.com/juju/juju/state/multiwatcher"
    28  	"github.com/juju/juju/storage"
    29  	"github.com/juju/juju/worker/uniter/runner"
    30  	"github.com/juju/juju/worker/uniter/runner/jujuc"
    31  )
    32  
    33  var noProxies = proxy.Settings{}
    34  var apiAddrs = []string{"a1:123", "a2:123"}
    35  var expectedApiAddrs = strings.Join(apiAddrs, " ")
    36  
    37  // MockEnvPaths implements Paths for tests that don't need to actually touch
    38  // the filesystem.
    39  type MockEnvPaths struct{}
    40  
    41  func (MockEnvPaths) GetToolsDir() string {
    42  	return "path-to-tools"
    43  }
    44  
    45  func (MockEnvPaths) GetCharmDir() string {
    46  	return "path-to-charm"
    47  }
    48  
    49  func (MockEnvPaths) GetJujucSocket() string {
    50  	return "path-to-jujuc.socket"
    51  }
    52  
    53  func (MockEnvPaths) GetMetricsSpoolDir() string {
    54  	return "path-to-metrics-spool-dir"
    55  }
    56  
    57  // RealPaths implements Paths for tests that do touch the filesystem.
    58  type RealPaths struct {
    59  	tools        string
    60  	charm        string
    61  	socket       string
    62  	metricsspool string
    63  }
    64  
    65  func osDependentSockPath(c *gc.C) string {
    66  	sockPath := filepath.Join(c.MkDir(), "test.sock")
    67  	if runtime.GOOS == "windows" {
    68  		return `\\.\pipe` + sockPath[2:]
    69  	}
    70  	return sockPath
    71  }
    72  
    73  func NewRealPaths(c *gc.C) RealPaths {
    74  	return RealPaths{
    75  		tools:        c.MkDir(),
    76  		charm:        c.MkDir(),
    77  		socket:       osDependentSockPath(c),
    78  		metricsspool: c.MkDir(),
    79  	}
    80  }
    81  
    82  func (p RealPaths) GetMetricsSpoolDir() string {
    83  	return p.metricsspool
    84  }
    85  
    86  func (p RealPaths) GetToolsDir() string {
    87  	return p.tools
    88  }
    89  
    90  func (p RealPaths) GetCharmDir() string {
    91  	return p.charm
    92  }
    93  
    94  func (p RealPaths) GetJujucSocket() string {
    95  	return p.socket
    96  }
    97  
    98  // HookContextSuite contains shared setup for various other test suites. Test
    99  // methods should not be added to this type, because they'll get run repeatedly.
   100  type HookContextSuite struct {
   101  	testing.JujuConnSuite
   102  	service  *state.Service
   103  	unit     *state.Unit
   104  	machine  *state.Machine
   105  	relch    *state.Charm
   106  	relunits map[int]*state.RelationUnit
   107  	storage  *storageContextAccessor
   108  
   109  	st             *api.State
   110  	uniter         *uniter.State
   111  	apiUnit        *uniter.Unit
   112  	meteredApiUnit *uniter.Unit
   113  	meteredCharm   *state.Charm
   114  	apiRelunits    map[int]*uniter.RelationUnit
   115  	BlockHelper
   116  }
   117  
   118  func (s *HookContextSuite) SetUpTest(c *gc.C) {
   119  	var err error
   120  	s.JujuConnSuite.SetUpTest(c)
   121  	s.BlockHelper = NewBlockHelper(s.APIState)
   122  	c.Assert(s.BlockHelper, gc.NotNil)
   123  	s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() })
   124  
   125  	// reset
   126  	s.machine = nil
   127  
   128  	sch := s.AddTestingCharm(c, "wordpress")
   129  	s.service = s.AddTestingService(c, "u", sch)
   130  	s.unit = s.AddUnit(c, s.service)
   131  
   132  	s.meteredCharm = s.AddTestingCharm(c, "metered")
   133  	meteredService := s.AddTestingService(c, "m", s.meteredCharm)
   134  	meteredUnit := s.addUnit(c, meteredService)
   135  	err = meteredUnit.SetCharmURL(s.meteredCharm.URL())
   136  	c.Assert(err, jc.ErrorIsNil)
   137  
   138  	password, err := utils.RandomPassword()
   139  	err = s.unit.SetPassword(password)
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	s.st = s.OpenAPIAs(c, s.unit.Tag(), password)
   142  	s.uniter, err = s.st.Uniter()
   143  	c.Assert(err, jc.ErrorIsNil)
   144  	c.Assert(s.uniter, gc.NotNil)
   145  	s.apiUnit, err = s.uniter.Unit(s.unit.Tag().(names.UnitTag))
   146  	c.Assert(err, jc.ErrorIsNil)
   147  
   148  	err = meteredUnit.SetPassword(password)
   149  	c.Assert(err, jc.ErrorIsNil)
   150  	meteredState := s.OpenAPIAs(c, meteredUnit.Tag(), password)
   151  	meteredUniter, err := meteredState.Uniter()
   152  	s.meteredApiUnit, err = meteredUniter.Unit(meteredUnit.Tag().(names.UnitTag))
   153  	c.Assert(err, jc.ErrorIsNil)
   154  
   155  	// Note: The unit must always have a charm URL set, because this
   156  	// happens as part of the installation process (that happens
   157  	// before the initial install hook).
   158  	err = s.unit.SetCharmURL(sch.URL())
   159  	c.Assert(err, jc.ErrorIsNil)
   160  	s.relch = s.AddTestingCharm(c, "mysql")
   161  	s.relunits = map[int]*state.RelationUnit{}
   162  	s.apiRelunits = map[int]*uniter.RelationUnit{}
   163  	s.AddContextRelation(c, "db0")
   164  	s.AddContextRelation(c, "db1")
   165  
   166  	storageData0 := names.NewStorageTag("data/0")
   167  	s.storage = &storageContextAccessor{
   168  		map[names.StorageTag]*contextStorage{
   169  			storageData0: &contextStorage{
   170  				storageData0,
   171  				storage.StorageKindBlock,
   172  				"/dev/sdb",
   173  			},
   174  		},
   175  	}
   176  }
   177  
   178  func (s *HookContextSuite) GetContext(
   179  	c *gc.C, relId int, remoteName string,
   180  ) jujuc.Context {
   181  	uuid, err := utils.NewUUID()
   182  	c.Assert(err, jc.ErrorIsNil)
   183  	return s.getHookContext(
   184  		c, uuid.String(), relId, remoteName, noProxies,
   185  	)
   186  }
   187  
   188  func (s *HookContextSuite) addUnit(c *gc.C, svc *state.Service) *state.Unit {
   189  	unit, err := svc.AddUnit()
   190  	c.Assert(err, jc.ErrorIsNil)
   191  	if s.machine != nil {
   192  		err = unit.AssignToMachine(s.machine)
   193  		c.Assert(err, jc.ErrorIsNil)
   194  		return unit
   195  	}
   196  
   197  	err = s.State.AssignUnit(unit, state.AssignCleanEmpty)
   198  	c.Assert(err, jc.ErrorIsNil)
   199  	machineId, err := unit.AssignedMachineId()
   200  	c.Assert(err, jc.ErrorIsNil)
   201  	s.machine, err = s.State.Machine(machineId)
   202  	c.Assert(err, jc.ErrorIsNil)
   203  	zone := "a-zone"
   204  	hwc := instance.HardwareCharacteristics{
   205  		AvailabilityZone: &zone,
   206  	}
   207  	err = s.machine.SetProvisioned("i-exist", "fake_nonce", &hwc)
   208  	c.Assert(err, jc.ErrorIsNil)
   209  	return unit
   210  }
   211  
   212  func (s *HookContextSuite) AddUnit(c *gc.C, svc *state.Service) *state.Unit {
   213  	unit := s.addUnit(c, svc)
   214  	name := strings.Replace(unit.Name(), "/", "-", 1)
   215  	privateAddr := network.NewScopedAddress(name+".testing.invalid", network.ScopeCloudLocal)
   216  	err := s.machine.SetProviderAddresses(privateAddr)
   217  	c.Assert(err, jc.ErrorIsNil)
   218  	return unit
   219  }
   220  
   221  func (s *HookContextSuite) AddContextRelation(c *gc.C, name string) {
   222  	s.AddTestingService(c, name, s.relch)
   223  	eps, err := s.State.InferEndpoints("u", name)
   224  	c.Assert(err, jc.ErrorIsNil)
   225  	rel, err := s.State.AddRelation(eps...)
   226  	c.Assert(err, jc.ErrorIsNil)
   227  	ru, err := rel.Unit(s.unit)
   228  	c.Assert(err, jc.ErrorIsNil)
   229  	err = ru.EnterScope(map[string]interface{}{"relation-name": name})
   230  	c.Assert(err, jc.ErrorIsNil)
   231  	s.relunits[rel.Id()] = ru
   232  	apiRel, err := s.uniter.Relation(rel.Tag().(names.RelationTag))
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	apiRelUnit, err := apiRel.Unit(s.apiUnit)
   235  	c.Assert(err, jc.ErrorIsNil)
   236  	s.apiRelunits[rel.Id()] = apiRelUnit
   237  }
   238  
   239  func (s *HookContextSuite) getHookContext(c *gc.C, uuid string, relid int,
   240  	remote string, proxies proxy.Settings) *runner.HookContext {
   241  	if relid != -1 {
   242  		_, found := s.apiRelunits[relid]
   243  		c.Assert(found, jc.IsTrue)
   244  	}
   245  	facade, err := s.st.Uniter()
   246  	c.Assert(err, jc.ErrorIsNil)
   247  
   248  	relctxs := map[int]*runner.ContextRelation{}
   249  	for relId, relUnit := range s.apiRelunits {
   250  		cache := runner.NewRelationCache(relUnit.ReadSettings, nil)
   251  		relctxs[relId] = runner.NewContextRelation(relUnit, cache)
   252  	}
   253  
   254  	env, err := s.State.Environment()
   255  	c.Assert(err, jc.ErrorIsNil)
   256  
   257  	context, err := runner.NewHookContext(s.apiUnit, facade, "TestCtx", uuid,
   258  		env.Name(), relid, remote, relctxs, apiAddrs, names.NewUserTag("owner"),
   259  		proxies, false, nil, nil, s.machine.Tag().(names.MachineTag), NewRealPaths(c))
   260  	c.Assert(err, jc.ErrorIsNil)
   261  	return context
   262  }
   263  
   264  func (s *HookContextSuite) getMeteredHookContext(c *gc.C, uuid string, relid int,
   265  	remote string, proxies proxy.Settings, canAddMetrics bool, metrics *charm.Metrics) *runner.HookContext {
   266  	if relid != -1 {
   267  		_, found := s.apiRelunits[relid]
   268  		c.Assert(found, jc.IsTrue)
   269  	}
   270  	facade, err := s.st.Uniter()
   271  	c.Assert(err, jc.ErrorIsNil)
   272  
   273  	relctxs := map[int]*runner.ContextRelation{}
   274  	for relId, relUnit := range s.apiRelunits {
   275  		cache := runner.NewRelationCache(relUnit.ReadSettings, nil)
   276  		relctxs[relId] = runner.NewContextRelation(relUnit, cache)
   277  	}
   278  
   279  	context, err := runner.NewHookContext(s.meteredApiUnit, facade, "TestCtx", uuid,
   280  		"test-env-name", relid, remote, relctxs, apiAddrs, names.NewUserTag("owner"),
   281  		proxies, canAddMetrics, metrics, nil, s.machine.Tag().(names.MachineTag), NewRealPaths(c))
   282  	c.Assert(err, jc.ErrorIsNil)
   283  	return context
   284  }
   285  
   286  func (s *HookContextSuite) metricsDefinition(name string) *charm.Metrics {
   287  	return &charm.Metrics{Metrics: map[string]charm.Metric{name: {Type: charm.MetricTypeGauge, Description: "generated metric"}}}
   288  }
   289  
   290  // hookSpec supports makeCharm.
   291  type hookSpec struct {
   292  	// dir is the directory to create the hook in.
   293  	dir string
   294  	// name is the name of the hook.
   295  	name string
   296  	// perm is the file permissions of the hook.
   297  	perm os.FileMode
   298  	// code is the exit status of the hook.
   299  	code int
   300  	// stdout holds a string to print to stdout
   301  	stdout string
   302  	// stderr holds a string to print to stderr
   303  	stderr string
   304  	// background holds a string to print in the background after 0.2s.
   305  	background string
   306  }
   307  
   308  // makeCharm constructs a fake charm dir containing a single named hook
   309  // with permissions perm and exit code code. If output is non-empty,
   310  // the charm will write it to stdout and stderr, with each one prefixed
   311  // by name of the stream.
   312  func makeCharm(c *gc.C, spec hookSpec, charmDir string) {
   313  	dir := charmDir
   314  	if spec.dir != "" {
   315  		dir = filepath.Join(dir, spec.dir)
   316  		err := os.Mkdir(dir, 0755)
   317  		c.Assert(err, jc.ErrorIsNil)
   318  	}
   319  	c.Logf("openfile perm %v", spec.perm)
   320  	hook, err := os.OpenFile(
   321  		filepath.Join(dir, spec.name), os.O_CREATE|os.O_WRONLY, spec.perm,
   322  	)
   323  	c.Assert(err, jc.ErrorIsNil)
   324  	defer func() {
   325  		c.Assert(hook.Close(), gc.IsNil)
   326  	}()
   327  
   328  	printf := func(f string, a ...interface{}) {
   329  		_, err := fmt.Fprintf(hook, f+"\n", a...)
   330  		c.Assert(err, jc.ErrorIsNil)
   331  	}
   332  	if runtime.GOOS != "windows" {
   333  		printf("#!/bin/bash")
   334  	}
   335  	printf(echoPidScript)
   336  	if spec.stdout != "" {
   337  		printf("echo %s", spec.stdout)
   338  	}
   339  	if spec.stderr != "" {
   340  		printf("echo %s >&2", spec.stderr)
   341  	}
   342  	if spec.background != "" {
   343  		// Print something fairly quickly, then sleep for
   344  		// quite a long time - if the hook execution is
   345  		// blocking because of the background process,
   346  		// the hook execution will take much longer than
   347  		// expected.
   348  		printf("(sleep 0.2; echo %s; sleep 10) &", spec.background)
   349  	}
   350  	printf("exit %d", spec.code)
   351  }
   352  
   353  type storageContextAccessor struct {
   354  	storage map[names.StorageTag]*contextStorage
   355  }
   356  
   357  func (s *storageContextAccessor) Storage(tag names.StorageTag) (jujuc.ContextStorageAttachment, bool) {
   358  	storage, ok := s.storage[tag]
   359  	return storage, ok
   360  }
   361  
   362  type contextStorage struct {
   363  	tag      names.StorageTag
   364  	kind     storage.StorageKind
   365  	location string
   366  }
   367  
   368  func (c *contextStorage) Tag() names.StorageTag {
   369  	return c.tag
   370  }
   371  
   372  func (c *contextStorage) Kind() storage.StorageKind {
   373  	return c.kind
   374  }
   375  
   376  func (c *contextStorage) Location() string {
   377  	return c.location
   378  }
   379  
   380  type BlockHelper struct {
   381  	blockClient *block.Client
   382  }
   383  
   384  // NewBlockHelper creates a block switch used in testing
   385  // to manage desired juju blocks.
   386  func NewBlockHelper(st *api.State) BlockHelper {
   387  	return BlockHelper{
   388  		blockClient: block.NewClient(st),
   389  	}
   390  }
   391  
   392  // on switches on desired block and
   393  // asserts that no errors were encountered.
   394  func (s *BlockHelper) on(c *gc.C, blockType multiwatcher.BlockType, msg string) {
   395  	c.Assert(s.blockClient.SwitchBlockOn(string(blockType), msg), gc.IsNil)
   396  }
   397  
   398  // BlockAllChanges switches changes block on.
   399  // This prevents all changes to juju environment.
   400  func (s *BlockHelper) BlockAllChanges(c *gc.C, msg string) {
   401  	s.on(c, multiwatcher.BlockChange, msg)
   402  }
   403  
   404  // BlockRemoveObject switches remove block on.
   405  // This prevents any object/entity removal on juju environment
   406  func (s *BlockHelper) BlockRemoveObject(c *gc.C, msg string) {
   407  	s.on(c, multiwatcher.BlockRemove, msg)
   408  }
   409  
   410  // BlockDestroyEnvironment switches destroy block on.
   411  // This prevents juju environment destruction.
   412  func (s *BlockHelper) BlockDestroyEnvironment(c *gc.C, msg string) {
   413  	s.on(c, multiwatcher.BlockDestroy, msg)
   414  }
   415  
   416  func (s *BlockHelper) Close() {
   417  	s.blockClient.Close()
   418  }