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