github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/apiserver/provisioner/container_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provisioner_test
     5  
     6  import (
     7  	"encoding/hex"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/apiserver/provisioner"
    19  	apiservertesting "github.com/juju/juju/apiserver/testing"
    20  	"github.com/juju/juju/instance"
    21  	"github.com/juju/juju/network"
    22  	"github.com/juju/juju/state"
    23  )
    24  
    25  // containerSuite has methods useful to tests working with containers. Notably
    26  // around testing PrepareContainerInterfaceInfo and ReleaseContainerAddresses.
    27  type containerSuite struct {
    28  	provisionerSuite
    29  
    30  	provAPI *provisioner.ProvisionerAPI
    31  }
    32  
    33  func (s *containerSuite) SetUpTest(c *gc.C) {
    34  	s.setUpTest(c, false)
    35  	// Reset any "broken" dummy provider methods.
    36  	s.breakEnvironMethods(c)
    37  }
    38  
    39  func (s *containerSuite) newCustomAPI(c *gc.C, hostInstId instance.Id, addContainer, provisionContainer bool) *state.Machine {
    40  	anAuthorizer := s.authorizer
    41  	anAuthorizer.EnvironManager = false
    42  	anAuthorizer.Tag = s.machines[0].Tag()
    43  	aProvisioner, err := provisioner.NewProvisionerAPI(s.State, s.resources, anAuthorizer)
    44  	c.Assert(err, jc.ErrorIsNil)
    45  	c.Assert(aProvisioner, gc.NotNil)
    46  	s.provAPI = aProvisioner
    47  
    48  	if hostInstId != "" {
    49  		err = s.machines[0].SetProvisioned(hostInstId, "fake_nonce", nil)
    50  		c.Assert(err, jc.ErrorIsNil)
    51  	}
    52  
    53  	if !addContainer {
    54  		return nil
    55  	}
    56  	container, err := s.State.AddMachineInsideMachine(
    57  		state.MachineTemplate{
    58  			Series: "quantal",
    59  			Jobs:   []state.MachineJob{state.JobHostUnits},
    60  		},
    61  		s.machines[0].Id(),
    62  		instance.LXC,
    63  	)
    64  	c.Assert(err, jc.ErrorIsNil)
    65  	if provisionContainer {
    66  		password, err := utils.RandomPassword()
    67  		c.Assert(err, jc.ErrorIsNil)
    68  		err = container.SetPassword(password)
    69  		c.Assert(err, jc.ErrorIsNil)
    70  		err = container.SetProvisioned("foo", "fake_nonce", nil)
    71  		c.Assert(err, jc.ErrorIsNil)
    72  	}
    73  	return container
    74  }
    75  
    76  func (s *containerSuite) makeArgs(machines ...*state.Machine) params.Entities {
    77  	args := params.Entities{Entities: make([]params.Entity, len(machines))}
    78  	for i, m := range machines {
    79  		args.Entities[i].Tag = m.Tag().String()
    80  	}
    81  	return args
    82  }
    83  
    84  func (s *containerSuite) breakEnvironMethods(c *gc.C, methods ...string) {
    85  	s.AssertConfigParameterUpdated(c, "broken", strings.Join(methods, " "))
    86  }
    87  
    88  // prepareSuite contains only tests around
    89  // PrepareContainerInterfaceInfo method.
    90  type prepareSuite struct {
    91  	containerSuite
    92  }
    93  
    94  var _ = gc.Suite(&prepareSuite{})
    95  
    96  func (s *prepareSuite) newAPI(c *gc.C, provisionHost, addContainer bool) *state.Machine {
    97  	var hostInstId instance.Id
    98  	if provisionHost {
    99  		hostInstId = "i-host"
   100  	}
   101  	return s.newCustomAPI(c, hostInstId, addContainer, false)
   102  }
   103  
   104  func (s *prepareSuite) makeErrors(errors ...*params.Error) *params.MachineNetworkConfigResults {
   105  	results := &params.MachineNetworkConfigResults{
   106  		Results: make([]params.MachineNetworkConfigResult, len(errors)),
   107  	}
   108  	for i, err := range errors {
   109  		results.Results[i].Error = err
   110  	}
   111  	return results
   112  }
   113  
   114  func (s *prepareSuite) makeResults(cfgs ...[]params.NetworkConfig) *params.MachineNetworkConfigResults {
   115  	results := &params.MachineNetworkConfigResults{
   116  		Results: make([]params.MachineNetworkConfigResult, len(cfgs)),
   117  	}
   118  	for i, cfg := range cfgs {
   119  		results.Results[i].Config = cfg
   120  	}
   121  	return results
   122  }
   123  
   124  func (s *prepareSuite) assertCall(c *gc.C, args params.Entities, expectResults *params.MachineNetworkConfigResults, expectErr string) (error, []loggo.TestLogValues) {
   125  
   126  	// Capture the logs for later inspection.
   127  	logger := loggo.GetLogger("juju.apiserver.provisioner")
   128  	defer logger.SetLogLevel(logger.LogLevel())
   129  	logger.SetLogLevel(loggo.TRACE)
   130  	var tw loggo.TestWriter
   131  	c.Assert(loggo.RegisterWriter("test", &tw, loggo.TRACE), gc.IsNil)
   132  	defer loggo.RemoveWriter("test")
   133  
   134  	results, err := s.provAPI.PrepareContainerInterfaceInfo(args)
   135  	c.Logf("PrepareContainerInterfaceInfo returned: err=%v, results=%v", err, results)
   136  	c.Assert(results.Results, gc.HasLen, len(args.Entities))
   137  	if expectErr == "" {
   138  		c.Assert(err, jc.ErrorIsNil)
   139  		c.Assert(expectResults, gc.NotNil)
   140  		c.Assert(results.Results, gc.HasLen, len(expectResults.Results))
   141  		// Check for any "regex:" prefixes first. Then replace
   142  		// addresses in expected with the actual ones, so we can use
   143  		// jc.DeepEquals on the whole result below.
   144  		// Also check MAC addresses are valid, but as they're randomly
   145  		// generated we can't test specific values.
   146  		for i, expect := range expectResults.Results {
   147  			cfg := results.Results[i].Config
   148  			c.Assert(cfg, gc.HasLen, len(expect.Config))
   149  			for j, expCfg := range expect.Config {
   150  				if strings.HasPrefix(expCfg.Address, "regex:") {
   151  					rex := strings.TrimPrefix(expCfg.Address, "regex:")
   152  					c.Assert(cfg[j].Address, gc.Matches, rex)
   153  					expectResults.Results[i].Config[j].Address = cfg[j].Address
   154  				}
   155  				macAddress := cfg[j].MACAddress
   156  				c.Assert(macAddress[:8], gc.Equals, provisioner.MACAddressTemplate[:8])
   157  				remainder := strings.Replace(macAddress[8:], ":", "", 3)
   158  				c.Assert(remainder, gc.HasLen, 6)
   159  				_, err = hex.DecodeString(remainder)
   160  				c.Assert(err, jc.ErrorIsNil)
   161  				expectResults.Results[i].Config[j].MACAddress = macAddress
   162  			}
   163  		}
   164  
   165  		c.Assert(results, jc.DeepEquals, *expectResults)
   166  	} else {
   167  		c.Assert(err, gc.ErrorMatches, expectErr)
   168  		if len(args.Entities) > 0 {
   169  			result := results.Results[0]
   170  			// Not using jc.ErrorIsNil below because
   171  			// (*params.Error)(nil) does not satisfy the error
   172  			// interface.
   173  			c.Assert(result.Error, gc.IsNil)
   174  			c.Assert(result.Config, gc.IsNil)
   175  		}
   176  	}
   177  	return err, tw.Log()
   178  }
   179  
   180  func (s *prepareSuite) TestErrorWitnNoFeatureFlag(c *gc.C) {
   181  	s.SetFeatureFlags() // clear the flags.
   182  	container := s.newAPI(c, true, true)
   183  	args := s.makeArgs(container)
   184  	s.assertCall(c, args, &params.MachineNetworkConfigResults{},
   185  		`address allocation not supported`,
   186  	)
   187  }
   188  
   189  func (s *prepareSuite) TestErrorWithNonProvisionedHost(c *gc.C) {
   190  	container := s.newAPI(c, false, true)
   191  	args := s.makeArgs(container)
   192  	s.assertCall(c, args, nil,
   193  		`cannot allocate addresses: host machine "0" not provisioned`,
   194  	)
   195  }
   196  
   197  func (s *prepareSuite) TestErrorWithProvisionedContainer(c *gc.C) {
   198  	container := s.newAPI(c, true, true)
   199  	err := container.SetProvisioned("i-foo", "fake_nonce", nil)
   200  	c.Assert(err, jc.ErrorIsNil)
   201  	args := s.makeArgs(container)
   202  	s.assertCall(c, args, s.makeErrors(
   203  		apiservertesting.ServerError(
   204  			`container "0/lxc/0" already provisioned as "i-foo"`,
   205  		),
   206  	), "")
   207  }
   208  
   209  func (s *prepareSuite) TestErrorWithHostInsteadOfContainer(c *gc.C) {
   210  	s.newAPI(c, true, false)
   211  	args := s.makeArgs(s.machines[0])
   212  	s.assertCall(c, args, s.makeErrors(
   213  		apiservertesting.ServerError(
   214  			`cannot allocate address for "machine-0": not a container`,
   215  		),
   216  	), "")
   217  }
   218  
   219  func (s *prepareSuite) TestErrorsWithDifferentHosts(c *gc.C) {
   220  	s.newAPI(c, true, false)
   221  	args := s.makeArgs(s.machines[1], s.machines[2])
   222  	s.assertCall(c, args, s.makeErrors(
   223  		apiservertesting.ErrUnauthorized,
   224  		apiservertesting.ErrUnauthorized,
   225  	), "")
   226  }
   227  
   228  func (s *prepareSuite) TestErrorsWithContainersOnDifferentHost(c *gc.C) {
   229  	s.newAPI(c, true, false)
   230  	var containers []*state.Machine
   231  	for i := 0; i < 2; i++ {
   232  		container, err := s.State.AddMachineInsideMachine(
   233  			state.MachineTemplate{
   234  				Series: "quantal",
   235  				Jobs:   []state.MachineJob{state.JobHostUnits},
   236  			},
   237  			s.machines[1].Id(),
   238  			instance.LXC,
   239  		)
   240  		c.Assert(err, jc.ErrorIsNil)
   241  		containers = append(containers, container)
   242  	}
   243  	args := s.makeArgs(containers...)
   244  	s.assertCall(c, args, s.makeErrors(
   245  		apiservertesting.ErrUnauthorized,
   246  		apiservertesting.ErrUnauthorized,
   247  	), "")
   248  }
   249  
   250  func (s *prepareSuite) TestErrorsWithNonMachineOrInvalidTags(c *gc.C) {
   251  	s.newAPI(c, true, false)
   252  	args := params.Entities{Entities: []params.Entity{
   253  		{Tag: "unit-wordpress-0"},
   254  		{Tag: "service-wordpress"},
   255  		{Tag: "network-foo"},
   256  		{Tag: "anything-invalid"},
   257  		{Tag: "42"},
   258  		{Tag: "machine-42"},
   259  		{Tag: ""},
   260  	}}
   261  
   262  	s.assertCall(c, args, s.makeErrors(
   263  		apiservertesting.ServerError(
   264  			`"unit-wordpress-0" is not a valid machine tag`),
   265  		apiservertesting.ServerError(
   266  			`"service-wordpress" is not a valid machine tag`),
   267  		apiservertesting.ServerError(
   268  			`"network-foo" is not a valid machine tag`),
   269  		apiservertesting.ServerError(
   270  			`"anything-invalid" is not a valid tag`),
   271  		apiservertesting.ServerError(
   272  			`"42" is not a valid tag`),
   273  		apiservertesting.ErrUnauthorized,
   274  		apiservertesting.ServerError(
   275  			`"" is not a valid tag`),
   276  	), "")
   277  }
   278  
   279  func (s *prepareSuite) fillSubnet(c *gc.C, numAllocated int) {
   280  	// Create the 0.10.0.0/24 subnet in state and pre-allocate up to
   281  	// numAllocated of the range. This ensures the tests will run
   282  	// quickly, rather than retrying potentiallu until the full /24
   283  	// range is exhausted.
   284  	subInfo := state.SubnetInfo{
   285  		ProviderId:        "dummy-private",
   286  		CIDR:              "0.10.0.0/24",
   287  		VLANTag:           0,
   288  		AllocatableIPLow:  "0.10.0.0",
   289  		AllocatableIPHigh: "0.10.0.10", // Intentionally use shorter range.
   290  	}
   291  	sub, err := s.BackingState.AddSubnet(subInfo)
   292  	c.Assert(err, jc.ErrorIsNil)
   293  	for i := 0; i <= numAllocated; i++ {
   294  		addr := network.NewAddress(fmt.Sprintf("0.10.0.%d", i))
   295  		ipaddr, err := s.BackingState.AddIPAddress(addr, sub.ID())
   296  		c.Check(err, jc.ErrorIsNil)
   297  		err = ipaddr.SetState(state.AddressStateAllocated)
   298  		c.Check(err, jc.ErrorIsNil)
   299  	}
   300  }
   301  
   302  func (s *prepareSuite) TestErrorWithEnvironMethodsFailing(c *gc.C) {
   303  	container := s.newAPI(c, true, true)
   304  	args := s.makeArgs(container)
   305  
   306  	s.fillSubnet(c, 10)
   307  
   308  	// NOTE: We're testing AllocateAddress and ReleaseAddress separately.
   309  	for i, test := range []struct {
   310  		method   string
   311  		err      string
   312  		errCheck func(error) bool
   313  	}{{
   314  		method: "NetworkInterfaces",
   315  		err:    "cannot allocate addresses: dummy.NetworkInterfaces is broken",
   316  	}, {
   317  		method: "Subnets",
   318  		err:    "cannot allocate addresses: dummy.Subnets is broken",
   319  	}, {
   320  		method:   "SupportsAddressAllocation",
   321  		err:      "cannot allocate addresses: address allocation on any available subnets is not supported",
   322  		errCheck: errors.IsNotSupported,
   323  	}} {
   324  		c.Logf("test %d: broken %q", i, test.method)
   325  		s.breakEnvironMethods(c, test.method)
   326  		var err error
   327  		if test.err != "" {
   328  			err, _ = s.assertCall(c, args, nil, test.err)
   329  		}
   330  		if test.errCheck != nil {
   331  			c.Check(err, jc.Satisfies, test.errCheck)
   332  		}
   333  	}
   334  }
   335  
   336  func (s *prepareSuite) TestRetryingOnAllocateAddressFailure(c *gc.C) {
   337  	// This test verifies the retrying logic when AllocateAddress
   338  	// and/or setAddrState return errors.
   339  
   340  	// Pre-allocate the first 5 addresses.
   341  	s.fillSubnet(c, 5)
   342  
   343  	// Now break AllocateAddress so it returns an error to verify the
   344  	// retry logic kicks in. Because it will always fail, the end
   345  	// result will always be an address exhaustion error.
   346  	s.breakEnvironMethods(c, "AllocateAddress")
   347  
   348  	container := s.newAPI(c, true, true)
   349  	args := s.makeArgs(container)
   350  
   351  	// Record each time setAddrState is called along with the address
   352  	// to verify the logs later.
   353  	var addresses []string
   354  	origSetAddrState := *provisioner.SetAddrState
   355  	s.PatchValue(provisioner.SetAddrState, func(ip *state.IPAddress, st state.AddressState) error {
   356  		c.Logf("setAddrState called for address %q, state %q", ip.String(), st)
   357  		c.Assert(st, gc.Equals, state.AddressStateUnavailable)
   358  		addresses = append(addresses, ip.Value())
   359  
   360  		// Return an error every other call to test it's handled ok.
   361  		if len(addresses)%2 == 0 {
   362  			return errors.New("pow!")
   363  		}
   364  		return origSetAddrState(ip, st)
   365  	})
   366  
   367  	_, testLog := s.assertCall(c, args, s.makeErrors(apiservertesting.ServerError(
   368  		`failed to allocate an address for "0/lxc/0": `+
   369  			`allocatable IP addresses exhausted for subnet "0.10.0.0/24"`,
   370  	)), "")
   371  
   372  	// Verify the expected addresses, ignoring the order as the
   373  	// addresses are picked at random.
   374  	c.Assert(addresses, jc.SameContents, []string{
   375  		"0.10.0.6",
   376  		"0.10.0.7",
   377  		"0.10.0.8",
   378  		"0.10.0.9",
   379  		"0.10.0.10",
   380  	})
   381  
   382  	// Now verify the logs.
   383  	c.Assert(testLog, jc.LogMatches, jc.SimpleMessages{{
   384  		loggo.WARNING,
   385  		`allocating address ".+" on instance ".+" and subnet ".+" failed: ` +
   386  			`dummy.AllocateAddress is broken \(retrying\)`,
   387  	}, {
   388  		loggo.TRACE,
   389  		`setting address ".+" to "unavailable" and retrying`,
   390  	}, {
   391  		loggo.TRACE,
   392  		`picked new address ".+" on subnet ".+"`,
   393  	}, {
   394  		loggo.WARNING,
   395  		`allocating address ".+" on instance ".+" and subnet ".+" failed: ` +
   396  			`dummy.AllocateAddress is broken \(retrying\)`,
   397  	}, {
   398  		loggo.WARNING,
   399  		`cannot set address ".+" to "unavailable": pow! \(ignoring and retrying\)`,
   400  	}})
   401  }
   402  
   403  func (s *prepareSuite) TestReleaseAndCleanupWhenAllocateAndOrSetFail(c *gc.C) {
   404  	// This test verifies the retrying, releasing, and cleanup logic
   405  	// when AllocateAddress succeeds, but both ReleaseAddress and
   406  	// setAddrsTo fail, and allocateAddrTo either succeeds or fails.
   407  
   408  	// Pre-allocate the first 5 addresses.
   409  	s.fillSubnet(c, 5)
   410  
   411  	// Now break ReleaseAddress to test the how it's handled during
   412  	// the release/cleanup loop.
   413  	s.breakEnvironMethods(c, "ReleaseAddress")
   414  
   415  	container := s.newAPI(c, true, true)
   416  	args := s.makeArgs(container)
   417  
   418  	// Record each time allocateAddrTo, setAddrsTo, and setAddrState
   419  	// are called along with the addresses to verify the logs later.
   420  	var allocAttemptedAddrs, allocAddrsOK, setAddrs, releasedAddrs []string
   421  	s.PatchValue(provisioner.AllocateAddrTo, func(ip *state.IPAddress, m *state.Machine, mac string) error {
   422  		c.Logf("allocateAddrTo called for address %q, machine %q", ip.String(), m)
   423  		c.Assert(m.Id(), gc.Equals, container.Id())
   424  		allocAttemptedAddrs = append(allocAttemptedAddrs, ip.Value())
   425  
   426  		// Succeed on every other call to give a chance to call
   427  		// setAddrsTo as well.
   428  		if len(allocAttemptedAddrs)%2 == 0 {
   429  			allocAddrsOK = append(allocAddrsOK, ip.Value())
   430  			return nil
   431  		}
   432  		return errors.New("crash!")
   433  	})
   434  	s.PatchValue(provisioner.SetAddrsTo, func(ip *state.IPAddress, m *state.Machine) error {
   435  		c.Logf("setAddrsTo called for address %q, machine %q", ip.String(), m)
   436  		c.Assert(m.Id(), gc.Equals, container.Id())
   437  		setAddrs = append(setAddrs, ip.Value())
   438  		return errors.New("boom!")
   439  	})
   440  	s.PatchValue(provisioner.SetAddrState, func(ip *state.IPAddress, st state.AddressState) error {
   441  		c.Logf("setAddrState called for address %q, state %q", ip.String(), st)
   442  		c.Assert(st, gc.Equals, state.AddressStateUnavailable)
   443  		releasedAddrs = append(releasedAddrs, ip.Value())
   444  		return nil
   445  	})
   446  
   447  	_, testLog := s.assertCall(c, args, s.makeErrors(apiservertesting.ServerError(
   448  		`failed to allocate an address for "0/lxc/0": `+
   449  			`allocatable IP addresses exhausted for subnet "0.10.0.0/24"`,
   450  	)), "")
   451  
   452  	// Verify the expected addresses, ignoring the order as the
   453  	// addresses are picked at random.
   454  	expectAddrs := []string{
   455  		"0.10.0.6",
   456  		"0.10.0.7",
   457  		"0.10.0.8",
   458  		"0.10.0.9",
   459  		"0.10.0.10",
   460  	}
   461  	// Verify that for each allocated address an attempt is made to
   462  	// assign it to the container by calling allocateAddrTo
   463  	// (successful or not doesn't matter).
   464  	c.Check(allocAttemptedAddrs, jc.SameContents, expectAddrs)
   465  
   466  	// Verify that for each allocated address an attempt is made to do
   467  	// release/cleanup by calling setAddrState(unavailable), after
   468  	// either allocateAddrTo or setAddrsTo fails.
   469  	c.Check(releasedAddrs, jc.SameContents, expectAddrs)
   470  
   471  	// Verify that for every allocateAddrTo call that passed, the
   472  	// corresponding setAddrsTo was also called.
   473  	c.Check(allocAddrsOK, jc.SameContents, setAddrs)
   474  
   475  	// Now verify the logs.
   476  	c.Assert(testLog, jc.LogMatches, jc.SimpleMessages{{
   477  		loggo.INFO,
   478  		`allocated address "public:.+" on instance "i-host" and subnet "dummy-private"`,
   479  	}, {
   480  		loggo.WARNING,
   481  		`failed to mark address ".+" as "allocated" to container ".*": crash! \(releasing and retrying\)`,
   482  	}, {
   483  		loggo.WARNING,
   484  		`failed to release address ".+" on instance "i-host" and subnet ".+": ` +
   485  			`dummy.ReleaseAddress is broken \(ignoring and retrying\)`,
   486  	}, {
   487  		loggo.INFO,
   488  		`allocated address "public:.+" on instance "i-host" and subnet "dummy-private"`,
   489  	}})
   490  }
   491  
   492  func (s *prepareSuite) TestReleaseAndRetryWhenSetOnlyFails(c *gc.C) {
   493  	// This test verifies the releasing, and cleanup, as well as
   494  	// retrying logic when AllocateAddress and allocateAddrTo succeed,
   495  	// but then both setAddrsTo and setAddrState fail.
   496  
   497  	// Pre-allocate the first 9 addresses, so the only address left
   498  	// will be 0.10.0.10.
   499  	s.fillSubnet(c, 9)
   500  
   501  	container := s.newAPI(c, true, true)
   502  	args := s.makeArgs(container)
   503  
   504  	s.PatchValue(provisioner.SetAddrsTo, func(ip *state.IPAddress, m *state.Machine) error {
   505  		c.Logf("setAddrsTo called for address %q, machine %q", ip.String(), m)
   506  		c.Assert(m.Id(), gc.Equals, container.Id())
   507  		c.Assert(ip.Value(), gc.Equals, "0.10.0.10")
   508  		return errors.New("boom!")
   509  	})
   510  	s.PatchValue(provisioner.SetAddrState, func(ip *state.IPAddress, st state.AddressState) error {
   511  		c.Logf("setAddrState called for address %q, state %q", ip.String(), st)
   512  		c.Assert(st, gc.Equals, state.AddressStateUnavailable)
   513  		c.Assert(ip.Value(), gc.Equals, "0.10.0.10")
   514  		return errors.New("pow!")
   515  	})
   516  
   517  	// After failing twice, we'll successfully release the address and retry to succeed.
   518  	_, testLog := s.assertCall(c, args, s.makeResults([]params.NetworkConfig{{
   519  		ProviderId:       "dummy-eth0",
   520  		ProviderSubnetId: "dummy-private",
   521  		NetworkName:      "juju-private",
   522  		CIDR:             "0.10.0.0/24",
   523  		DeviceIndex:      0,
   524  		InterfaceName:    "eth0",
   525  		VLANTag:          0,
   526  		Disabled:         false,
   527  		NoAutoStart:      false,
   528  		ConfigType:       "static",
   529  		Address:          "0.10.0.10",
   530  		DNSServers:       []string{"ns1.dummy", "ns2.dummy"},
   531  		GatewayAddress:   "0.10.0.2",
   532  		ExtraConfig:      nil,
   533  	}}), "")
   534  
   535  	c.Assert(testLog, jc.LogMatches, jc.SimpleMessages{{
   536  		loggo.INFO,
   537  		`allocated address "public:0.10.0.10" on instance "i-host" and subnet "dummy-private"`,
   538  	}, {
   539  		loggo.WARNING,
   540  		`failed to mark address ".+" as "allocated" to container ".*": boom! \(releasing and retrying\)`,
   541  	}, {
   542  		loggo.WARNING,
   543  		`cannot set address "public:0.10.0.10" to "unavailable": pow! \(ignoring and releasing\)`,
   544  	}, {
   545  		loggo.INFO,
   546  		`address "public:0.10.0.10" released; trying to allocate new`,
   547  	}})
   548  }
   549  
   550  func (s *prepareSuite) TestErrorWhenNoSubnetsAvailable(c *gc.C) {
   551  	// The magic "i-no-subnets-" instance id prefix for the host
   552  	// causes the dummy provider to return no results and no errors
   553  	// from Subnets().
   554  	container := s.newCustomAPI(c, "i-no-subnets-here", true, false)
   555  	args := s.makeArgs(container)
   556  	s.assertCall(c, args, nil, "cannot allocate addresses: no subnets available")
   557  }
   558  
   559  func (s *prepareSuite) TestErrorWithDisabledNIC(c *gc.C) {
   560  	// The magic "i-disabled-nic-" instance id prefix for the host
   561  	// causes the dummy provider to return a disabled NIC from
   562  	// NetworkInterfaces(), which should not be used for the container.
   563  	container := s.newCustomAPI(c, "i-no-subnets-here", true, false)
   564  	args := s.makeArgs(container)
   565  	s.assertCall(c, args, nil, "cannot allocate addresses: no subnets available")
   566  }
   567  
   568  func (s *prepareSuite) TestErrorWhenNoAllocatableSubnetsAvailable(c *gc.C) {
   569  	// The magic "i-no-alloc-all" instance id for the host causes the
   570  	// dummy provider's Subnets() method to return all subnets without
   571  	// an allocatable range
   572  	container := s.newCustomAPI(c, "i-no-alloc-all", true, false)
   573  	args := s.makeArgs(container)
   574  	err, _ := s.assertCall(c, args, nil, "cannot allocate addresses: address allocation on any available subnets is not supported")
   575  	c.Assert(err, jc.Satisfies, errors.IsNotSupported)
   576  }
   577  
   578  func (s *prepareSuite) TestErrorWhenNoNICSAvailable(c *gc.C) {
   579  	// The magic "i-no-nics-" instance id prefix for the host
   580  	// causes the dummy provider to return no results and no errors
   581  	// from NetworkInterfaces().
   582  	container := s.newCustomAPI(c, "i-no-nics-here", true, false)
   583  	args := s.makeArgs(container)
   584  	s.assertCall(c, args, nil, "cannot allocate addresses: no interfaces available")
   585  }
   586  
   587  func (s *prepareSuite) TestErrorWithNICNoSubnetAvailable(c *gc.C) {
   588  	// The magic "i-nic-no-subnet-" instance id prefix for the host
   589  	// causes the dummy provider to return a nic that has no associated
   590  	// subnet from NetworkInterfaces().
   591  	container := s.newCustomAPI(c, "i-nic-no-subnet-here", true, false)
   592  	args := s.makeArgs(container)
   593  	s.assertCall(c, args, nil, "cannot allocate addresses: no subnets available")
   594  }
   595  
   596  func (s *prepareSuite) TestSuccessWithSingleContainer(c *gc.C) {
   597  	container := s.newAPI(c, true, true)
   598  	args := s.makeArgs(container)
   599  	_, testLog := s.assertCall(c, args, s.makeResults([]params.NetworkConfig{{
   600  		ProviderId:       "dummy-eth0",
   601  		ProviderSubnetId: "dummy-private",
   602  		NetworkName:      "juju-private",
   603  		CIDR:             "0.10.0.0/24",
   604  		DeviceIndex:      0,
   605  		InterfaceName:    "eth0",
   606  		VLANTag:          0,
   607  		Disabled:         false,
   608  		NoAutoStart:      false,
   609  		ConfigType:       "static",
   610  		Address:          "regex:0.10.0.[0-9]{1,3}", // we don't care about the actual value.
   611  		DNSServers:       []string{"ns1.dummy", "ns2.dummy"},
   612  		GatewayAddress:   "0.10.0.2",
   613  		ExtraConfig:      nil,
   614  	}}), "")
   615  
   616  	c.Assert(testLog, jc.LogMatches, jc.SimpleMessages{{
   617  		loggo.INFO,
   618  		`allocated address ".+" on instance "i-host" and subnet "dummy-private"`,
   619  	}, {
   620  		loggo.INFO,
   621  		`assigned address ".+" to container "0/lxc/0"`,
   622  	}})
   623  }
   624  
   625  func (s *prepareSuite) TestSuccessWhenFirstSubnetNotAllocatable(c *gc.C) {
   626  	// Using "i-no-alloc-0" for the host instance id will cause the
   627  	// dummy provider to change the Subnets() results to return no
   628  	// allocatable range for the first subnet (dummy-private), and
   629  	// also change its ProviderId to "noalloc-private", which in turn
   630  	// will cause SupportsAddressAllocation() to return false for it.
   631  	// We test here that we keep looking for other allocatable
   632  	// subnets.
   633  	container := s.newCustomAPI(c, "i-no-alloc-0", true, false)
   634  	args := s.makeArgs(container)
   635  	_, testLog := s.assertCall(c, args, s.makeResults([]params.NetworkConfig{{
   636  		ProviderId:       "dummy-eth1",
   637  		ProviderSubnetId: "dummy-public",
   638  		NetworkName:      "juju-public",
   639  		CIDR:             "0.20.0.0/24",
   640  		DeviceIndex:      1,
   641  		InterfaceName:    "eth1",
   642  		VLANTag:          1,
   643  		Disabled:         false,
   644  		NoAutoStart:      true,
   645  		ConfigType:       "static",
   646  		Address:          "regex:0.20.0.[0-9]{1,3}", // we don't care about the actual value.
   647  		DNSServers:       []string{"ns1.dummy", "ns2.dummy"},
   648  		GatewayAddress:   "0.20.0.2",
   649  		ExtraConfig:      nil,
   650  	}}), "")
   651  
   652  	c.Assert(testLog, jc.LogMatches, jc.SimpleMessages{{
   653  		loggo.TRACE,
   654  		`ignoring subnet "noalloc-private" - no allocatable range set`,
   655  	}, {
   656  		loggo.INFO,
   657  		`allocated address ".+" on instance "i-no-alloc-0" and subnet "dummy-public"`,
   658  	}, {
   659  		loggo.INFO,
   660  		`assigned address ".+" to container "0/lxc/0"`,
   661  	}})
   662  }
   663  
   664  // releaseSuite contains only tests around
   665  // ReleaseContainerAddresses method.
   666  type releaseSuite struct {
   667  	containerSuite
   668  }
   669  
   670  var _ = gc.Suite(&releaseSuite{})
   671  
   672  func (s *releaseSuite) newAPI(c *gc.C, provisionHost, addContainer bool) *state.Machine {
   673  	var hostInstId instance.Id
   674  	if provisionHost {
   675  		hostInstId = "i-host"
   676  	}
   677  	return s.newCustomAPI(c, hostInstId, addContainer, true)
   678  }
   679  
   680  func (s *releaseSuite) makeErrors(errors ...*params.Error) *params.ErrorResults {
   681  	results := &params.ErrorResults{
   682  		Results: make([]params.ErrorResult, len(errors)),
   683  	}
   684  	for i, err := range errors {
   685  		results.Results[i].Error = err
   686  	}
   687  	return results
   688  }
   689  
   690  func (s *releaseSuite) assertCall(c *gc.C, args params.Entities, expectResults *params.ErrorResults, expectErr string) error {
   691  	results, err := s.provAPI.ReleaseContainerAddresses(args)
   692  	c.Logf("ReleaseContainerAddresses returned: err=%v, results=%v", err, results)
   693  	c.Assert(results.Results, gc.HasLen, len(args.Entities))
   694  	if expectErr == "" {
   695  		c.Assert(err, jc.ErrorIsNil)
   696  		c.Assert(expectResults, gc.NotNil)
   697  		c.Assert(results.Results, gc.HasLen, len(expectResults.Results))
   698  		c.Assert(results, jc.DeepEquals, *expectResults)
   699  	} else {
   700  		c.Assert(err, gc.ErrorMatches, expectErr)
   701  		if len(args.Entities) > 0 {
   702  			result := results.Results[0]
   703  			// Not using jc.ErrorIsNil below because
   704  			// (*params.Error)(nil) does not satisfy the error
   705  			// interface.
   706  			c.Assert(result.Error, gc.IsNil)
   707  		}
   708  	}
   709  	return err
   710  }
   711  
   712  func (s *releaseSuite) TestErrorWithNoFeatureFlag(c *gc.C) {
   713  	s.SetFeatureFlags() // clear the flags.
   714  	s.newAPI(c, true, false)
   715  	args := s.makeArgs(s.machines[0])
   716  	s.assertCall(c, args, &params.ErrorResults{},
   717  		"address allocation not supported",
   718  	)
   719  }
   720  
   721  func (s *releaseSuite) TestErrorWithHostInsteadOfContainer(c *gc.C) {
   722  	s.newAPI(c, true, false)
   723  	args := s.makeArgs(s.machines[0])
   724  	err := s.assertCall(c, args, s.makeErrors(
   725  		apiservertesting.ServerError(
   726  			`cannot mark addresses for removal for "machine-0": not a container`,
   727  		),
   728  	), "")
   729  	c.Assert(err, jc.ErrorIsNil)
   730  }
   731  
   732  func (s *releaseSuite) TestErrorsWithDifferentHosts(c *gc.C) {
   733  	s.newAPI(c, true, false)
   734  	args := s.makeArgs(s.machines[1], s.machines[2])
   735  	err := s.assertCall(c, args, s.makeErrors(
   736  		apiservertesting.ErrUnauthorized,
   737  		apiservertesting.ErrUnauthorized,
   738  	), "")
   739  	c.Assert(err, jc.ErrorIsNil)
   740  }
   741  
   742  func (s *releaseSuite) TestErrorsWithContainersOnDifferentHost(c *gc.C) {
   743  	s.newAPI(c, true, false)
   744  	var containers []*state.Machine
   745  	for i := 0; i < 2; i++ {
   746  		container, err := s.State.AddMachineInsideMachine(
   747  			state.MachineTemplate{
   748  				Series: "quantal",
   749  				Jobs:   []state.MachineJob{state.JobHostUnits},
   750  			},
   751  			s.machines[1].Id(),
   752  			instance.LXC,
   753  		)
   754  		c.Assert(err, jc.ErrorIsNil)
   755  		containers = append(containers, container)
   756  	}
   757  	args := s.makeArgs(containers...)
   758  	err := s.assertCall(c, args, s.makeErrors(
   759  		apiservertesting.ErrUnauthorized,
   760  		apiservertesting.ErrUnauthorized,
   761  	), "")
   762  	c.Assert(err, jc.ErrorIsNil)
   763  }
   764  
   765  func (s *releaseSuite) TestErrorsWithNonMachineOrInvalidTags(c *gc.C) {
   766  	s.newAPI(c, true, false)
   767  	args := params.Entities{Entities: []params.Entity{
   768  		{Tag: "unit-wordpress-0"},
   769  		{Tag: "service-wordpress"},
   770  		{Tag: "network-foo"},
   771  		{Tag: "anything-invalid"},
   772  		{Tag: "42"},
   773  		{Tag: "machine-42"},
   774  		{Tag: ""},
   775  	}}
   776  
   777  	err := s.assertCall(c, args, s.makeErrors(
   778  		apiservertesting.ErrUnauthorized,
   779  		apiservertesting.ErrUnauthorized,
   780  		apiservertesting.ErrUnauthorized,
   781  		apiservertesting.ErrUnauthorized,
   782  		apiservertesting.ErrUnauthorized,
   783  		apiservertesting.ErrUnauthorized,
   784  		apiservertesting.ErrUnauthorized,
   785  	), "")
   786  	c.Assert(err, jc.ErrorIsNil)
   787  }
   788  
   789  func (s *releaseSuite) allocateAddresses(c *gc.C, containerId string, numAllocated int) {
   790  	// Create the 0.10.0.0/24 subnet in state and pre-allocate up to
   791  	// numAllocated of the range. It also allocates them to the specified
   792  	// container.
   793  	subInfo := state.SubnetInfo{
   794  		ProviderId:        "dummy-private",
   795  		CIDR:              "0.10.0.0/24",
   796  		VLANTag:           0,
   797  		AllocatableIPLow:  "0.10.0.0",
   798  		AllocatableIPHigh: "0.10.0.10",
   799  	}
   800  	sub, err := s.BackingState.AddSubnet(subInfo)
   801  	c.Assert(err, jc.ErrorIsNil)
   802  	for i := 0; i < numAllocated; i++ {
   803  		addr := network.NewAddress(fmt.Sprintf("0.10.0.%d", i))
   804  		ipaddr, err := s.BackingState.AddIPAddress(addr, sub.ID())
   805  		c.Check(err, jc.ErrorIsNil)
   806  		err = ipaddr.AllocateTo(containerId, "", "")
   807  		c.Check(err, jc.ErrorIsNil)
   808  	}
   809  }
   810  
   811  func (s *releaseSuite) TestSuccess(c *gc.C) {
   812  	container := s.newAPI(c, true, true)
   813  	args := s.makeArgs(container)
   814  
   815  	s.allocateAddresses(c, container.Id(), 2)
   816  	err := s.assertCall(c, args, s.makeErrors(nil), "")
   817  	c.Assert(err, jc.ErrorIsNil)
   818  	addresses, err := s.BackingState.AllocatedIPAddresses(container.Id())
   819  	c.Assert(err, jc.ErrorIsNil)
   820  	c.Assert(addresses, gc.HasLen, 2)
   821  	for _, addr := range addresses {
   822  		c.Assert(addr.Life(), gc.Equals, state.Dead)
   823  	}
   824  }