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