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