github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/worker/uniter/context_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package uniter_test
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"time"
    13  
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils"
    16  	"github.com/juju/utils/proxy"
    17  	gc "launchpad.net/gocheck"
    18  
    19  	"github.com/juju/juju/charm"
    20  	"github.com/juju/juju/instance"
    21  	"github.com/juju/juju/juju/testing"
    22  	"github.com/juju/juju/state"
    23  	"github.com/juju/juju/state/api"
    24  	"github.com/juju/juju/state/api/params"
    25  	apiuniter "github.com/juju/juju/state/api/uniter"
    26  	"github.com/juju/juju/worker/uniter"
    27  	"github.com/juju/juju/worker/uniter/jujuc"
    28  )
    29  
    30  var noProxies = proxy.Settings{}
    31  
    32  type RunHookSuite struct {
    33  	HookContextSuite
    34  }
    35  
    36  var _ = gc.Suite(&RunHookSuite{})
    37  
    38  type hookSpec struct {
    39  	// name is the name of the hook.
    40  	name string
    41  	// perm is the file permissions of the hook.
    42  	perm os.FileMode
    43  	// code is the exit status of the hook.
    44  	code int
    45  	// stdout holds a string to print to stdout
    46  	stdout string
    47  	// stderr holds a string to print to stderr
    48  	stderr string
    49  	// background holds a string to print in the background after 0.2s.
    50  	background string
    51  }
    52  
    53  // makeCharm constructs a fake charm dir containing a single named hook
    54  // with permissions perm and exit code code.  If output is non-empty,
    55  // the charm will write it to stdout and stderr, with each one prefixed
    56  // by name of the stream.  It returns the charm directory and the path
    57  // to which the hook script will write environment variables.
    58  func makeCharm(c *gc.C, spec hookSpec) (charmDir, outPath string) {
    59  	charmDir = c.MkDir()
    60  	hooksDir := filepath.Join(charmDir, "hooks")
    61  	err := os.Mkdir(hooksDir, 0755)
    62  	c.Assert(err, gc.IsNil)
    63  	c.Logf("openfile perm %v", spec.perm)
    64  	hook, err := os.OpenFile(filepath.Join(hooksDir, spec.name), os.O_CREATE|os.O_WRONLY, spec.perm)
    65  	c.Assert(err, gc.IsNil)
    66  	defer hook.Close()
    67  	printf := func(f string, a ...interface{}) {
    68  		if _, err := fmt.Fprintf(hook, f+"\n", a...); err != nil {
    69  			panic(err)
    70  		}
    71  	}
    72  	outPath = filepath.Join(c.MkDir(), "hook.out")
    73  	printf("#!/bin/bash")
    74  	printf("env > " + outPath)
    75  	if spec.stdout != "" {
    76  		printf("echo %s", spec.stdout)
    77  	}
    78  	if spec.stderr != "" {
    79  		printf("echo %s >&2", spec.stderr)
    80  	}
    81  	if spec.background != "" {
    82  		// Print something fairly quickly, then sleep for
    83  		// quite a long time - if the hook execution is
    84  		// blocking because of the background process,
    85  		// the hook execution will take much longer than
    86  		// expected.
    87  		printf("(sleep 0.2; echo %s; sleep 10) &", spec.background)
    88  	}
    89  	printf("exit %d", spec.code)
    90  	return charmDir, outPath
    91  }
    92  
    93  func AssertEnvContains(c *gc.C, lines []string, env map[string]string) {
    94  	for k, v := range env {
    95  		sought := k + "=" + v
    96  		found := false
    97  		for _, line := range lines {
    98  			if line == sought {
    99  				found = true
   100  				continue
   101  			}
   102  		}
   103  		comment := gc.Commentf("expected to find %v among %v", sought, lines)
   104  		c.Assert(found, jc.IsTrue, comment)
   105  	}
   106  }
   107  
   108  func AssertEnv(c *gc.C, outPath string, charmDir string, env map[string]string, uuid string) {
   109  	out, err := ioutil.ReadFile(outPath)
   110  	c.Assert(err, gc.IsNil)
   111  	lines := strings.Split(string(out), "\n")
   112  	AssertEnvContains(c, lines, env)
   113  	AssertEnvContains(c, lines, map[string]string{
   114  		"DEBIAN_FRONTEND":          "noninteractive",
   115  		"APT_LISTCHANGES_FRONTEND": "none",
   116  		"CHARM_DIR":                charmDir,
   117  		"JUJU_AGENT_SOCKET":        "/path/to/socket",
   118  		"JUJU_ENV_UUID":            uuid,
   119  	})
   120  }
   121  
   122  // LineBufferSize matches the constant used when creating
   123  // the bufio line reader.
   124  const lineBufferSize = 4096
   125  
   126  var apiAddrs = []string{"a1:123", "a2:123"}
   127  var expectedApiAddrs = strings.Join(apiAddrs, " ")
   128  
   129  var runHookTests = []struct {
   130  	summary       string
   131  	relid         int
   132  	remote        string
   133  	spec          hookSpec
   134  	err           string
   135  	env           map[string]string
   136  	proxySettings proxy.Settings
   137  }{
   138  	{
   139  		summary: "missing hook is not an error",
   140  		relid:   -1,
   141  	}, {
   142  		summary: "report failure to execute hook",
   143  		relid:   -1,
   144  		spec:    hookSpec{perm: 0600},
   145  		err:     `exec: .*something-happened": permission denied`,
   146  	}, {
   147  		summary: "report error indicated by hook's exit status",
   148  		relid:   -1,
   149  		spec: hookSpec{
   150  			perm: 0700,
   151  			code: 99,
   152  		},
   153  		err: "exit status 99",
   154  	}, {
   155  		summary: "output logging",
   156  		relid:   -1,
   157  		spec: hookSpec{
   158  			perm:   0700,
   159  			stdout: "stdout",
   160  			stderr: "stderr",
   161  		},
   162  	}, {
   163  		summary: "output logging with background process",
   164  		relid:   -1,
   165  		spec: hookSpec{
   166  			perm:       0700,
   167  			stdout:     "stdout",
   168  			background: "not printed",
   169  		},
   170  	}, {
   171  		summary: "long line split",
   172  		relid:   -1,
   173  		spec: hookSpec{
   174  			perm:   0700,
   175  			stdout: strings.Repeat("a", lineBufferSize+10),
   176  		},
   177  	}, {
   178  		summary: "check shell environment for non-relation hook context",
   179  		relid:   -1,
   180  		spec:    hookSpec{perm: 0700},
   181  		proxySettings: proxy.Settings{
   182  			Http: "http", Https: "https", Ftp: "ftp", NoProxy: "no proxy"},
   183  		env: map[string]string{
   184  			"JUJU_UNIT_NAME":     "u/0",
   185  			"JUJU_API_ADDRESSES": expectedApiAddrs,
   186  			"JUJU_ENV_NAME":      "test-env-name",
   187  			"http_proxy":         "http",
   188  			"HTTP_PROXY":         "http",
   189  			"https_proxy":        "https",
   190  			"HTTPS_PROXY":        "https",
   191  			"ftp_proxy":          "ftp",
   192  			"FTP_PROXY":          "ftp",
   193  			"no_proxy":           "no proxy",
   194  			"NO_PROXY":           "no proxy",
   195  		},
   196  	}, {
   197  		summary: "check shell environment for relation-broken hook context",
   198  		relid:   1,
   199  		spec:    hookSpec{perm: 0700},
   200  		env: map[string]string{
   201  			"JUJU_UNIT_NAME":     "u/0",
   202  			"JUJU_API_ADDRESSES": expectedApiAddrs,
   203  			"JUJU_ENV_NAME":      "test-env-name",
   204  			"JUJU_RELATION":      "db",
   205  			"JUJU_RELATION_ID":   "db:1",
   206  			"JUJU_REMOTE_UNIT":   "",
   207  		},
   208  	}, {
   209  		summary: "check shell environment for relation hook context",
   210  		relid:   1,
   211  		remote:  "r/1",
   212  		spec:    hookSpec{perm: 0700},
   213  		env: map[string]string{
   214  			"JUJU_UNIT_NAME":     "u/0",
   215  			"JUJU_API_ADDRESSES": expectedApiAddrs,
   216  			"JUJU_ENV_NAME":      "test-env-name",
   217  			"JUJU_RELATION":      "db",
   218  			"JUJU_RELATION_ID":   "db:1",
   219  			"JUJU_REMOTE_UNIT":   "r/1",
   220  		},
   221  	},
   222  }
   223  
   224  func (s *RunHookSuite) TestRunHook(c *gc.C) {
   225  	uuid, err := utils.NewUUID()
   226  	c.Assert(err, gc.IsNil)
   227  	for i, t := range runHookTests {
   228  		c.Logf("\ntest %d: %s; perm %v", i, t.summary, t.spec.perm)
   229  		ctx := s.getHookContext(c, uuid.String(), t.relid, t.remote, t.proxySettings)
   230  		var charmDir, outPath string
   231  		var hookExists bool
   232  		if t.spec.perm == 0 {
   233  			charmDir = c.MkDir()
   234  		} else {
   235  			spec := t.spec
   236  			spec.name = "something-happened"
   237  			c.Logf("makeCharm %#v", spec)
   238  			charmDir, outPath = makeCharm(c, spec)
   239  			hookExists = true
   240  		}
   241  		toolsDir := c.MkDir()
   242  		t0 := time.Now()
   243  		err := ctx.RunHook("something-happened", charmDir, toolsDir, "/path/to/socket")
   244  		if t.err == "" && hookExists {
   245  			c.Assert(err, gc.IsNil)
   246  		} else if !hookExists {
   247  			c.Assert(uniter.IsMissingHookError(err), jc.IsTrue)
   248  		} else {
   249  			c.Assert(err, gc.ErrorMatches, t.err)
   250  		}
   251  		if t.env != nil {
   252  			env := map[string]string{"PATH": toolsDir + ":" + os.Getenv("PATH")}
   253  			for k, v := range t.env {
   254  				env[k] = v
   255  			}
   256  			AssertEnv(c, outPath, charmDir, env, uuid.String())
   257  		}
   258  		if t.spec.background != "" && time.Now().Sub(t0) > 5*time.Second {
   259  			c.Errorf("background process holding up hook execution")
   260  		}
   261  	}
   262  }
   263  
   264  // split the line into buffer-sized lengths.
   265  func splitLine(s string) []string {
   266  	var ss []string
   267  	for len(s) > lineBufferSize {
   268  		ss = append(ss, s[0:lineBufferSize])
   269  		s = s[lineBufferSize:]
   270  	}
   271  	if len(s) > 0 {
   272  		ss = append(ss, s)
   273  	}
   274  	return ss
   275  }
   276  
   277  func (s *RunHookSuite) TestRunHookRelationFlushing(c *gc.C) {
   278  	// Create a charm with a breaking hook.
   279  	uuid, err := utils.NewUUID()
   280  	c.Assert(err, gc.IsNil)
   281  	ctx := s.getHookContext(c, uuid.String(), -1, "", noProxies)
   282  	charmDir, _ := makeCharm(c, hookSpec{
   283  		name: "something-happened",
   284  		perm: 0700,
   285  		code: 123,
   286  	})
   287  
   288  	// Mess with multiple relation settings.
   289  	node0, err := s.relctxs[0].Settings()
   290  	node0.Set("foo", "1")
   291  	node1, err := s.relctxs[1].Settings()
   292  	node1.Set("bar", "2")
   293  
   294  	// Run the failing hook.
   295  	err = ctx.RunHook("something-happened", charmDir, c.MkDir(), "/path/to/socket")
   296  	c.Assert(err, gc.ErrorMatches, "exit status 123")
   297  
   298  	// Check that the changes to the local settings nodes have been discarded.
   299  	node0, err = s.relctxs[0].Settings()
   300  	c.Assert(err, gc.IsNil)
   301  	c.Assert(node0.Map(), gc.DeepEquals, params.RelationSettings{"relation-name": "db0"})
   302  	node1, err = s.relctxs[1].Settings()
   303  	c.Assert(err, gc.IsNil)
   304  	c.Assert(node1.Map(), gc.DeepEquals, params.RelationSettings{"relation-name": "db1"})
   305  
   306  	// Check that the changes have been written to state.
   307  	settings0, err := s.relunits[0].ReadSettings("u/0")
   308  	c.Assert(err, gc.IsNil)
   309  	c.Assert(settings0, gc.DeepEquals, map[string]interface{}{"relation-name": "db0"})
   310  	settings1, err := s.relunits[1].ReadSettings("u/0")
   311  	c.Assert(err, gc.IsNil)
   312  	c.Assert(settings1, gc.DeepEquals, map[string]interface{}{"relation-name": "db1"})
   313  
   314  	// Create a charm with a working hook, and mess with settings again.
   315  	charmDir, _ = makeCharm(c, hookSpec{
   316  		name: "something-happened",
   317  		perm: 0700,
   318  	})
   319  	node0.Set("baz", "3")
   320  	node1.Set("qux", "4")
   321  
   322  	// Run the hook.
   323  	err = ctx.RunHook("something-happened", charmDir, c.MkDir(), "/path/to/socket")
   324  	c.Assert(err, gc.IsNil)
   325  
   326  	// Check that the changes to the local settings nodes are still there.
   327  	node0, err = s.relctxs[0].Settings()
   328  	c.Assert(err, gc.IsNil)
   329  	c.Assert(node0.Map(), gc.DeepEquals, params.RelationSettings{
   330  		"relation-name": "db0",
   331  		"baz":           "3",
   332  	})
   333  	node1, err = s.relctxs[1].Settings()
   334  	c.Assert(err, gc.IsNil)
   335  	c.Assert(node1.Map(), gc.DeepEquals, params.RelationSettings{
   336  		"relation-name": "db1",
   337  		"qux":           "4",
   338  	})
   339  
   340  	// Check that the changes have been written to state.
   341  	settings0, err = s.relunits[0].ReadSettings("u/0")
   342  	c.Assert(err, gc.IsNil)
   343  	c.Assert(settings0, gc.DeepEquals, map[string]interface{}{
   344  		"relation-name": "db0",
   345  		"baz":           "3",
   346  	})
   347  	settings1, err = s.relunits[1].ReadSettings("u/0")
   348  	c.Assert(err, gc.IsNil)
   349  	c.Assert(settings1, gc.DeepEquals, map[string]interface{}{
   350  		"relation-name": "db1",
   351  		"qux":           "4",
   352  	})
   353  }
   354  
   355  type ContextRelationSuite struct {
   356  	testing.JujuConnSuite
   357  	svc *state.Service
   358  	rel *state.Relation
   359  	ru  *state.RelationUnit
   360  
   361  	st         *api.State
   362  	uniter     *apiuniter.State
   363  	apiRelUnit *apiuniter.RelationUnit
   364  }
   365  
   366  var _ = gc.Suite(&ContextRelationSuite{})
   367  
   368  func (s *ContextRelationSuite) SetUpTest(c *gc.C) {
   369  	s.JujuConnSuite.SetUpTest(c)
   370  	ch := s.AddTestingCharm(c, "riak")
   371  	var err error
   372  	s.svc = s.AddTestingService(c, "u", ch)
   373  	rels, err := s.svc.Relations()
   374  	c.Assert(err, gc.IsNil)
   375  	c.Assert(rels, gc.HasLen, 1)
   376  	s.rel = rels[0]
   377  	unit, err := s.svc.AddUnit()
   378  	c.Assert(err, gc.IsNil)
   379  	s.ru, err = s.rel.Unit(unit)
   380  	c.Assert(err, gc.IsNil)
   381  	err = s.ru.EnterScope(nil)
   382  	c.Assert(err, gc.IsNil)
   383  
   384  	password, err := utils.RandomPassword()
   385  	c.Assert(err, gc.IsNil)
   386  	err = unit.SetPassword(password)
   387  	c.Assert(err, gc.IsNil)
   388  	s.st = s.OpenAPIAs(c, unit.Tag(), password)
   389  	s.uniter = s.st.Uniter()
   390  	c.Assert(s.uniter, gc.NotNil)
   391  
   392  	apiRel, err := s.uniter.Relation(s.rel.Tag())
   393  	c.Assert(err, gc.IsNil)
   394  	apiUnit, err := s.uniter.Unit(unit.Tag())
   395  	c.Assert(err, gc.IsNil)
   396  	s.apiRelUnit, err = apiRel.Unit(apiUnit)
   397  	c.Assert(err, gc.IsNil)
   398  }
   399  
   400  func (s *ContextRelationSuite) TestChangeMembers(c *gc.C) {
   401  	ctx := uniter.NewContextRelation(s.apiRelUnit, nil)
   402  	c.Assert(ctx.UnitNames(), gc.HasLen, 0)
   403  
   404  	// Check the units and settings after a simple update.
   405  	ctx.UpdateMembers(uniter.SettingsMap{
   406  		"u/2": {"baz": "2"},
   407  		"u/4": {"qux": "4"},
   408  	})
   409  	c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/2", "u/4"})
   410  	assertSettings := func(unit string, expect params.RelationSettings) {
   411  		actual, err := ctx.ReadSettings(unit)
   412  		c.Assert(err, gc.IsNil)
   413  		c.Assert(actual, gc.DeepEquals, expect)
   414  	}
   415  	assertSettings("u/2", params.RelationSettings{"baz": "2"})
   416  	assertSettings("u/4", params.RelationSettings{"qux": "4"})
   417  
   418  	// Send a second update; check that members are only added, not removed.
   419  	ctx.UpdateMembers(uniter.SettingsMap{
   420  		"u/1": {"foo": "1"},
   421  		"u/2": {"abc": "2"},
   422  		"u/3": {"bar": "3"},
   423  	})
   424  	c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1", "u/2", "u/3", "u/4"})
   425  
   426  	// Check that all settings remain cached.
   427  	assertSettings("u/1", params.RelationSettings{"foo": "1"})
   428  	assertSettings("u/2", params.RelationSettings{"abc": "2"})
   429  	assertSettings("u/3", params.RelationSettings{"bar": "3"})
   430  	assertSettings("u/4", params.RelationSettings{"qux": "4"})
   431  
   432  	// Delete a member, and check that it is no longer a member...
   433  	ctx.DeleteMember("u/2")
   434  	c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1", "u/3", "u/4"})
   435  
   436  	// ...and that its settings are no longer cached.
   437  	_, err := ctx.ReadSettings("u/2")
   438  	c.Assert(err, gc.ErrorMatches, "cannot read settings for unit \"u/2\" in relation \"u:ring\": settings not found")
   439  }
   440  
   441  func (s *ContextRelationSuite) TestMemberCaching(c *gc.C) {
   442  	unit, err := s.svc.AddUnit()
   443  	c.Assert(err, gc.IsNil)
   444  	ru, err := s.rel.Unit(unit)
   445  	c.Assert(err, gc.IsNil)
   446  	err = ru.EnterScope(map[string]interface{}{"blib": "blob"})
   447  	c.Assert(err, gc.IsNil)
   448  	settings, err := ru.Settings()
   449  	c.Assert(err, gc.IsNil)
   450  	settings.Set("ping", "pong")
   451  	_, err = settings.Write()
   452  	c.Assert(err, gc.IsNil)
   453  	ctx := uniter.NewContextRelation(s.apiRelUnit, map[string]int64{"u/1": 0})
   454  
   455  	// Check that uncached settings are read from state.
   456  	m, err := ctx.ReadSettings("u/1")
   457  	c.Assert(err, gc.IsNil)
   458  	expectMap := settings.Map()
   459  	expectSettings := convertMap(expectMap)
   460  	c.Assert(m, gc.DeepEquals, expectSettings)
   461  
   462  	// Check that changes to state do not affect the cached settings.
   463  	settings.Set("ping", "pow")
   464  	_, err = settings.Write()
   465  	c.Assert(err, gc.IsNil)
   466  	m, err = ctx.ReadSettings("u/1")
   467  	c.Assert(err, gc.IsNil)
   468  	c.Assert(m, gc.DeepEquals, expectSettings)
   469  
   470  	// Check that ClearCache spares the members cache.
   471  	ctx.ClearCache()
   472  	m, err = ctx.ReadSettings("u/1")
   473  	c.Assert(err, gc.IsNil)
   474  	c.Assert(m, gc.DeepEquals, expectSettings)
   475  
   476  	// Check that updating the context overwrites the cached settings, and
   477  	// that the contents of state are ignored.
   478  	ctx.UpdateMembers(uniter.SettingsMap{"u/1": {"entirely": "different"}})
   479  	m, err = ctx.ReadSettings("u/1")
   480  	c.Assert(err, gc.IsNil)
   481  	c.Assert(m, gc.DeepEquals, params.RelationSettings{"entirely": "different"})
   482  }
   483  
   484  func (s *ContextRelationSuite) TestNonMemberCaching(c *gc.C) {
   485  	unit, err := s.svc.AddUnit()
   486  	c.Assert(err, gc.IsNil)
   487  	ru, err := s.rel.Unit(unit)
   488  	c.Assert(err, gc.IsNil)
   489  	err = ru.EnterScope(map[string]interface{}{"blib": "blob"})
   490  	c.Assert(err, gc.IsNil)
   491  	settings, err := ru.Settings()
   492  	c.Assert(err, gc.IsNil)
   493  	settings.Set("ping", "pong")
   494  	_, err = settings.Write()
   495  	c.Assert(err, gc.IsNil)
   496  	ctx := uniter.NewContextRelation(s.apiRelUnit, nil)
   497  
   498  	// Check that settings are read from state.
   499  	m, err := ctx.ReadSettings("u/1")
   500  	c.Assert(err, gc.IsNil)
   501  	expectMap := settings.Map()
   502  	expectSettings := convertMap(expectMap)
   503  	c.Assert(m, gc.DeepEquals, expectSettings)
   504  
   505  	// Check that changes to state do not affect the obtained settings...
   506  	settings.Set("ping", "pow")
   507  	_, err = settings.Write()
   508  	c.Assert(err, gc.IsNil)
   509  	m, err = ctx.ReadSettings("u/1")
   510  	c.Assert(err, gc.IsNil)
   511  	c.Assert(m, gc.DeepEquals, expectSettings)
   512  
   513  	// ...until the caches are cleared.
   514  	ctx.ClearCache()
   515  	c.Assert(err, gc.IsNil)
   516  	m, err = ctx.ReadSettings("u/1")
   517  	c.Assert(err, gc.IsNil)
   518  	c.Assert(m["ping"], gc.Equals, "pow")
   519  }
   520  
   521  func (s *ContextRelationSuite) TestSettings(c *gc.C) {
   522  	ctx := uniter.NewContextRelation(s.apiRelUnit, nil)
   523  
   524  	// Change Settings, then clear cache without writing.
   525  	node, err := ctx.Settings()
   526  	c.Assert(err, gc.IsNil)
   527  	expectSettings := node.Map()
   528  	expectMap := convertSettings(expectSettings)
   529  	node.Set("change", "exciting")
   530  	ctx.ClearCache()
   531  
   532  	// Check that the change is not cached...
   533  	node, err = ctx.Settings()
   534  	c.Assert(err, gc.IsNil)
   535  	c.Assert(node.Map(), gc.DeepEquals, expectSettings)
   536  
   537  	// ...and not written to state.
   538  	settings, err := s.ru.ReadSettings("u/0")
   539  	c.Assert(err, gc.IsNil)
   540  	c.Assert(settings, gc.DeepEquals, expectMap)
   541  
   542  	// Change again, write settings, and clear caches.
   543  	node.Set("change", "exciting")
   544  	err = ctx.WriteSettings()
   545  	c.Assert(err, gc.IsNil)
   546  	ctx.ClearCache()
   547  
   548  	// Check that the change is reflected in Settings...
   549  	expectSettings["change"] = "exciting"
   550  	expectMap["change"] = expectSettings["change"]
   551  	node, err = ctx.Settings()
   552  	c.Assert(err, gc.IsNil)
   553  	c.Assert(node.Map(), gc.DeepEquals, expectSettings)
   554  
   555  	// ...and was written to state.
   556  	settings, err = s.ru.ReadSettings("u/0")
   557  	c.Assert(err, gc.IsNil)
   558  	c.Assert(settings, gc.DeepEquals, expectMap)
   559  }
   560  
   561  type InterfaceSuite struct {
   562  	HookContextSuite
   563  }
   564  
   565  var _ = gc.Suite(&InterfaceSuite{})
   566  
   567  func (s *InterfaceSuite) GetContext(c *gc.C, relId int,
   568  	remoteName string) jujuc.Context {
   569  	uuid, err := utils.NewUUID()
   570  	c.Assert(err, gc.IsNil)
   571  	return s.HookContextSuite.getHookContext(c, uuid.String(), relId, remoteName, noProxies)
   572  }
   573  
   574  func (s *InterfaceSuite) TestUtils(c *gc.C) {
   575  	ctx := s.GetContext(c, -1, "")
   576  	c.Assert(ctx.UnitName(), gc.Equals, "u/0")
   577  	r, found := ctx.HookRelation()
   578  	c.Assert(found, jc.IsFalse)
   579  	c.Assert(r, gc.IsNil)
   580  	name, found := ctx.RemoteUnitName()
   581  	c.Assert(found, jc.IsFalse)
   582  	c.Assert(name, gc.Equals, "")
   583  	c.Assert(ctx.RelationIds(), gc.HasLen, 2)
   584  	r, found = ctx.Relation(0)
   585  	c.Assert(found, jc.IsTrue)
   586  	c.Assert(r.Name(), gc.Equals, "db")
   587  	c.Assert(r.FakeId(), gc.Equals, "db:0")
   588  	r, found = ctx.Relation(123)
   589  	c.Assert(found, jc.IsFalse)
   590  	c.Assert(r, gc.IsNil)
   591  
   592  	ctx = s.GetContext(c, 1, "")
   593  	r, found = ctx.HookRelation()
   594  	c.Assert(found, jc.IsTrue)
   595  	c.Assert(r.Name(), gc.Equals, "db")
   596  	c.Assert(r.FakeId(), gc.Equals, "db:1")
   597  
   598  	ctx = s.GetContext(c, 1, "u/123")
   599  	name, found = ctx.RemoteUnitName()
   600  	c.Assert(found, jc.IsTrue)
   601  	c.Assert(name, gc.Equals, "u/123")
   602  }
   603  
   604  func (s *InterfaceSuite) TestUnitCaching(c *gc.C) {
   605  	ctx := s.GetContext(c, -1, "")
   606  	pr, ok := ctx.PrivateAddress()
   607  	c.Assert(ok, jc.IsTrue)
   608  	c.Assert(pr, gc.Equals, "u-0.testing.invalid")
   609  	pa, ok := ctx.PublicAddress()
   610  	c.Assert(ok, jc.IsTrue)
   611  	// Initially the public address is the same as the private address since
   612  	// the "most public" address is chosen.
   613  	c.Assert(pr, gc.Equals, pa)
   614  
   615  	// Change remote state.
   616  	err := s.machine.SetAddresses(
   617  		instance.NewAddress("blah.testing.invalid", instance.NetworkPublic))
   618  	c.Assert(err, gc.IsNil)
   619  
   620  	// Local view is unchanged.
   621  	pr, ok = ctx.PrivateAddress()
   622  	c.Assert(ok, jc.IsTrue)
   623  	c.Assert(pr, gc.Equals, "u-0.testing.invalid")
   624  	pa, ok = ctx.PublicAddress()
   625  	c.Assert(ok, jc.IsTrue)
   626  	c.Assert(pr, gc.Equals, pa)
   627  }
   628  
   629  func (s *InterfaceSuite) TestConfigCaching(c *gc.C) {
   630  	ctx := s.GetContext(c, -1, "")
   631  	settings, err := ctx.ConfigSettings()
   632  	c.Assert(err, gc.IsNil)
   633  	c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title"})
   634  
   635  	// Change remote config.
   636  	err = s.service.UpdateConfigSettings(charm.Settings{
   637  		"blog-title": "Something Else",
   638  	})
   639  	c.Assert(err, gc.IsNil)
   640  
   641  	// Local view is not changed.
   642  	settings, err = ctx.ConfigSettings()
   643  	c.Assert(err, gc.IsNil)
   644  	c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title"})
   645  }
   646  
   647  type HookContextSuite struct {
   648  	testing.JujuConnSuite
   649  	service  *state.Service
   650  	unit     *state.Unit
   651  	machine  *state.Machine
   652  	relch    *state.Charm
   653  	relunits map[int]*state.RelationUnit
   654  	relctxs  map[int]*uniter.ContextRelation
   655  
   656  	st      *api.State
   657  	uniter  *apiuniter.State
   658  	apiUnit *apiuniter.Unit
   659  }
   660  
   661  func (s *HookContextSuite) SetUpTest(c *gc.C) {
   662  	s.JujuConnSuite.SetUpTest(c)
   663  	var err error
   664  	sch := s.AddTestingCharm(c, "wordpress")
   665  	s.service = s.AddTestingService(c, "u", sch)
   666  	s.unit = s.AddUnit(c, s.service)
   667  
   668  	password, err := utils.RandomPassword()
   669  	err = s.unit.SetPassword(password)
   670  	c.Assert(err, gc.IsNil)
   671  	s.st = s.OpenAPIAs(c, s.unit.Tag(), password)
   672  	s.uniter = s.st.Uniter()
   673  	c.Assert(s.uniter, gc.NotNil)
   674  
   675  	// Note: The unit must always have a charm URL set, because this
   676  	// happens as part of the installation process (that happens
   677  	// before the initial install hook).
   678  	err = s.unit.SetCharmURL(sch.URL())
   679  	c.Assert(err, gc.IsNil)
   680  	s.relch = s.AddTestingCharm(c, "mysql")
   681  	s.relunits = map[int]*state.RelationUnit{}
   682  	s.relctxs = map[int]*uniter.ContextRelation{}
   683  	s.AddContextRelation(c, "db0")
   684  	s.AddContextRelation(c, "db1")
   685  }
   686  
   687  func (s *HookContextSuite) AddUnit(c *gc.C, svc *state.Service) *state.Unit {
   688  	unit, err := svc.AddUnit()
   689  	c.Assert(err, gc.IsNil)
   690  	s.machine, err = s.State.AddMachine("quantal", state.JobHostUnits)
   691  	c.Assert(err, gc.IsNil)
   692  	err = unit.AssignToMachine(s.machine)
   693  	c.Assert(err, gc.IsNil)
   694  	name := strings.Replace(unit.Name(), "/", "-", 1)
   695  	privateAddr := instance.NewAddress(name+".testing.invalid", instance.NetworkCloudLocal)
   696  	err = s.machine.SetAddresses(privateAddr)
   697  	c.Assert(err, gc.IsNil)
   698  	return unit
   699  }
   700  
   701  func (s *HookContextSuite) AddContextRelation(c *gc.C, name string) {
   702  	s.AddTestingService(c, name, s.relch)
   703  	eps, err := s.State.InferEndpoints([]string{"u", name})
   704  	c.Assert(err, gc.IsNil)
   705  	rel, err := s.State.AddRelation(eps...)
   706  	c.Assert(err, gc.IsNil)
   707  	ru, err := rel.Unit(s.unit)
   708  	c.Assert(err, gc.IsNil)
   709  	s.relunits[rel.Id()] = ru
   710  	err = ru.EnterScope(map[string]interface{}{"relation-name": name})
   711  	c.Assert(err, gc.IsNil)
   712  	s.apiUnit, err = s.uniter.Unit(s.unit.Tag())
   713  	c.Assert(err, gc.IsNil)
   714  	apiRel, err := s.uniter.Relation(rel.Tag())
   715  	c.Assert(err, gc.IsNil)
   716  	apiRelUnit, err := apiRel.Unit(s.apiUnit)
   717  	c.Assert(err, gc.IsNil)
   718  	s.relctxs[rel.Id()] = uniter.NewContextRelation(apiRelUnit, nil)
   719  }
   720  
   721  func (s *HookContextSuite) getHookContext(c *gc.C, uuid string, relid int,
   722  	remote string, proxies proxy.Settings) *uniter.HookContext {
   723  	if relid != -1 {
   724  		_, found := s.relctxs[relid]
   725  		c.Assert(found, jc.IsTrue)
   726  	}
   727  	context, err := uniter.NewHookContext(s.apiUnit, "TestCtx", uuid,
   728  		"test-env-name", relid, remote, s.relctxs, apiAddrs, "test-owner",
   729  		proxies)
   730  	c.Assert(err, gc.IsNil)
   731  	return context
   732  }
   733  
   734  func convertSettings(settings params.RelationSettings) map[string]interface{} {
   735  	result := make(map[string]interface{})
   736  	for k, v := range settings {
   737  		result[k] = v
   738  	}
   739  	return result
   740  }
   741  
   742  func convertMap(settingsMap map[string]interface{}) params.RelationSettings {
   743  	result := make(params.RelationSettings)
   744  	for k, v := range settingsMap {
   745  		result[k] = v.(string)
   746  	}
   747  	return result
   748  }
   749  
   750  type RunCommandSuite struct {
   751  	HookContextSuite
   752  }
   753  
   754  var _ = gc.Suite(&RunCommandSuite{})
   755  
   756  func (s *RunCommandSuite) getHookContext(c *gc.C) *uniter.HookContext {
   757  	uuid, err := utils.NewUUID()
   758  	c.Assert(err, gc.IsNil)
   759  	return s.HookContextSuite.getHookContext(c, uuid.String(), -1, "", noProxies)
   760  }
   761  
   762  func (s *RunCommandSuite) TestRunCommandsHasEnvironSet(c *gc.C) {
   763  	context := s.getHookContext(c)
   764  	charmDir := c.MkDir()
   765  	result, err := context.RunCommands("env | sort", charmDir, "/path/to/tools", "/path/to/socket")
   766  	c.Assert(err, gc.IsNil)
   767  
   768  	executionEnvironment := map[string]string{}
   769  	for _, value := range strings.Split(string(result.Stdout), "\n") {
   770  		bits := strings.SplitN(value, "=", 2)
   771  		if len(bits) == 2 {
   772  			executionEnvironment[bits[0]] = bits[1]
   773  		}
   774  	}
   775  	expected := map[string]string{
   776  		"APT_LISTCHANGES_FRONTEND": "none",
   777  		"DEBIAN_FRONTEND":          "noninteractive",
   778  		"CHARM_DIR":                charmDir,
   779  		"JUJU_CONTEXT_ID":          "TestCtx",
   780  		"JUJU_AGENT_SOCKET":        "/path/to/socket",
   781  		"JUJU_UNIT_NAME":           "u/0",
   782  		"JUJU_ENV_NAME":            "test-env-name",
   783  	}
   784  	for key, value := range expected {
   785  		c.Check(executionEnvironment[key], gc.Equals, value)
   786  	}
   787  }
   788  
   789  func (s *RunCommandSuite) TestRunCommandsStdOutAndErrAndRC(c *gc.C) {
   790  	context := s.getHookContext(c)
   791  	charmDir := c.MkDir()
   792  	commands := `
   793  echo this is standard out
   794  echo this is standard err >&2
   795  exit 42
   796  `
   797  	result, err := context.RunCommands(commands, charmDir, "/path/to/tools", "/path/to/socket")
   798  	c.Assert(err, gc.IsNil)
   799  
   800  	c.Assert(result.Code, gc.Equals, 42)
   801  	c.Assert(string(result.Stdout), gc.Equals, "this is standard out\n")
   802  	c.Assert(string(result.Stderr), gc.Equals, "this is standard err\n")
   803  }