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