github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/state/subnets_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"sort"
     8  	"sync"
     9  	"sync/atomic"
    10  
    11  	"github.com/juju/errors"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	"github.com/juju/juju/network"
    16  	"github.com/juju/juju/state"
    17  )
    18  
    19  type SubnetSuite struct {
    20  	ConnSuite
    21  }
    22  
    23  var _ = gc.Suite(&SubnetSuite{})
    24  
    25  func (s *SubnetSuite) TestAddSubnet(c *gc.C) {
    26  	subnetInfo := state.SubnetInfo{
    27  		ProviderId:        "foo",
    28  		CIDR:              "192.168.1.0/24",
    29  		VLANTag:           79,
    30  		AllocatableIPLow:  "192.168.1.0",
    31  		AllocatableIPHigh: "192.168.1.1",
    32  		AvailabilityZone:  "Timbuktu",
    33  		SpaceName:         "foo",
    34  	}
    35  
    36  	assertSubnet := func(subnet *state.Subnet) {
    37  		c.Assert(subnet.ProviderId(), gc.Equals, "foo")
    38  		c.Assert(subnet.CIDR(), gc.Equals, "192.168.1.0/24")
    39  		c.Assert(subnet.VLANTag(), gc.Equals, 79)
    40  		c.Assert(subnet.AllocatableIPLow(), gc.Equals, "192.168.1.0")
    41  		c.Assert(subnet.AllocatableIPHigh(), gc.Equals, "192.168.1.1")
    42  		c.Assert(subnet.AvailabilityZone(), gc.Equals, "Timbuktu")
    43  		c.Assert(subnet.String(), gc.Equals, "192.168.1.0/24")
    44  		c.Assert(subnet.GoString(), gc.Equals, "192.168.1.0/24")
    45  		c.Assert(subnet.SpaceName(), gc.Equals, "foo")
    46  	}
    47  
    48  	subnet, err := s.State.AddSubnet(subnetInfo)
    49  	c.Assert(err, jc.ErrorIsNil)
    50  	assertSubnet(subnet)
    51  
    52  	// check it's been stored in state by fetching it back again
    53  	subnetFromDB, err := s.State.Subnet("192.168.1.0/24")
    54  	c.Assert(err, jc.ErrorIsNil)
    55  	assertSubnet(subnetFromDB)
    56  }
    57  
    58  func (s *SubnetSuite) TestAddSubnetErrors(c *gc.C) {
    59  	subnetInfo := state.SubnetInfo{}
    60  	_, err := s.State.AddSubnet(subnetInfo)
    61  	c.Assert(err, gc.ErrorMatches, `adding subnet "": missing CIDR`)
    62  
    63  	subnetInfo.CIDR = "foobar"
    64  	_, err = s.State.AddSubnet(subnetInfo)
    65  	c.Assert(err, gc.ErrorMatches,
    66  		`adding subnet "foobar": invalid CIDR address: foobar`,
    67  	)
    68  
    69  	errPrefix := `adding subnet "192.168.0.1/24": `
    70  	subnetInfo.CIDR = "192.168.0.1/24"
    71  	subnetInfo.VLANTag = 4095
    72  	_, err = s.State.AddSubnet(subnetInfo)
    73  	c.Assert(err, gc.ErrorMatches,
    74  		errPrefix+"invalid VLAN tag 4095: must be between 0 and 4094",
    75  	)
    76  
    77  	eitherOrMsg := errPrefix + "either both AllocatableIPLow and AllocatableIPHigh must be set or neither set"
    78  	subnetInfo.VLANTag = 0
    79  	subnetInfo.AllocatableIPHigh = "192.168.0.1"
    80  	_, err = s.State.AddSubnet(subnetInfo)
    81  	c.Assert(err, gc.ErrorMatches, eitherOrMsg)
    82  
    83  	subnetInfo.AllocatableIPLow = "192.168.0.1"
    84  	subnetInfo.AllocatableIPHigh = ""
    85  	_, err = s.State.AddSubnet(subnetInfo)
    86  	c.Assert(err, gc.ErrorMatches, eitherOrMsg)
    87  
    88  	// invalid IP address
    89  	subnetInfo.AllocatableIPHigh = "foobar"
    90  	_, err = s.State.AddSubnet(subnetInfo)
    91  	c.Assert(err, gc.ErrorMatches, errPrefix+`invalid AllocatableIPHigh "foobar"`)
    92  
    93  	// invalid IP address
    94  	subnetInfo.AllocatableIPLow = "foobar"
    95  	subnetInfo.AllocatableIPHigh = "192.168.0.1"
    96  	_, err = s.State.AddSubnet(subnetInfo)
    97  	c.Assert(err, gc.ErrorMatches, errPrefix+`invalid AllocatableIPLow "foobar"`)
    98  
    99  	// IP address out of range
   100  	subnetInfo.AllocatableIPHigh = "172.168.1.0"
   101  	_, err = s.State.AddSubnet(subnetInfo)
   102  	c.Assert(err, gc.ErrorMatches, errPrefix+`invalid AllocatableIPHigh "172.168.1.0"`)
   103  
   104  	// IP address out of range
   105  	subnetInfo.AllocatableIPHigh = "192.168.0.1"
   106  	subnetInfo.AllocatableIPLow = "172.168.1.0"
   107  	_, err = s.State.AddSubnet(subnetInfo)
   108  	c.Assert(err, gc.ErrorMatches, errPrefix+`invalid AllocatableIPLow "172.168.1.0"`)
   109  
   110  	// valid case
   111  	subnetInfo.AllocatableIPLow = "192.168.0.1"
   112  	subnetInfo.ProviderId = "testing uniqueness"
   113  	_, err = s.State.AddSubnet(subnetInfo)
   114  	c.Assert(err, jc.ErrorIsNil)
   115  
   116  	_, err = s.State.AddSubnet(subnetInfo)
   117  	c.Assert(err, jc.Satisfies, errors.IsAlreadyExists)
   118  
   119  	// ProviderId should be unique as well as CIDR
   120  	subnetInfo.CIDR = "192.0.0.0/0"
   121  	_, err = s.State.AddSubnet(subnetInfo)
   122  	c.Assert(err, gc.ErrorMatches,
   123  		`adding subnet "192.0.0.0/0": ProviderId "testing uniqueness" not unique`,
   124  	)
   125  
   126  	// empty provider id should be allowed to be not unique
   127  	subnetInfo.ProviderId = ""
   128  	_, err = s.State.AddSubnet(subnetInfo)
   129  	c.Assert(err, jc.ErrorIsNil)
   130  	subnetInfo.CIDR = "192.0.0.1/1"
   131  	_, err = s.State.AddSubnet(subnetInfo)
   132  	c.Assert(err, jc.ErrorIsNil)
   133  }
   134  
   135  func (s *SubnetSuite) TestSubnetEnsureDeadRemove(c *gc.C) {
   136  	subnetInfo := state.SubnetInfo{CIDR: "192.168.1.0/24"}
   137  
   138  	subnet, err := s.State.AddSubnet(subnetInfo)
   139  	c.Assert(err, jc.ErrorIsNil)
   140  	c.Assert(subnet.Life(), gc.Equals, state.Alive)
   141  
   142  	// This should fail - not dead yet!
   143  	err = subnet.Remove()
   144  	c.Assert(err, gc.ErrorMatches,
   145  		`cannot remove subnet "192.168.1.0/24": subnet is not dead`,
   146  	)
   147  
   148  	err = subnet.EnsureDead()
   149  	c.Assert(err, jc.ErrorIsNil)
   150  	c.Assert(subnet.Life(), gc.Equals, state.Dead)
   151  
   152  	// EnsureDead a second time should also not be an error
   153  	err = subnet.EnsureDead()
   154  	c.Assert(err, jc.ErrorIsNil)
   155  	c.Assert(subnet.Life(), gc.Equals, state.Dead)
   156  
   157  	// check the change was persisted
   158  	subnetCopy, err := s.State.Subnet("192.168.1.0/24")
   159  	c.Assert(err, jc.ErrorIsNil)
   160  	c.Assert(subnetCopy.Life(), gc.Equals, state.Dead)
   161  
   162  	// Remove should now work
   163  	err = subnet.Remove()
   164  	c.Assert(err, jc.ErrorIsNil)
   165  
   166  	_, err = s.State.Subnet("192.168.1.0/24")
   167  	c.Assert(err, gc.ErrorMatches, `subnet "192.168.1.0/24" not found`)
   168  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   169  
   170  	// removing a second time should be a no-op
   171  	err = subnet.Remove()
   172  	c.Assert(err, jc.ErrorIsNil)
   173  }
   174  
   175  func (s *SubnetSuite) TestSubnetRemoveKillsAddresses(c *gc.C) {
   176  	subnetInfo := state.SubnetInfo{CIDR: "192.168.1.0/24"}
   177  	subnet, err := s.State.AddSubnet(subnetInfo)
   178  	c.Assert(err, jc.ErrorIsNil)
   179  
   180  	_, err = s.State.AddIPAddress(
   181  		network.NewAddress("192.168.1.0"),
   182  		subnet.ID(),
   183  	)
   184  	c.Assert(err, jc.ErrorIsNil)
   185  	_, err = s.State.AddIPAddress(
   186  		network.NewAddress("192.168.1.1"),
   187  		subnet.ID(),
   188  	)
   189  	c.Assert(err, jc.ErrorIsNil)
   190  
   191  	err = subnet.EnsureDead()
   192  	c.Assert(err, jc.ErrorIsNil)
   193  	err = subnet.Remove()
   194  	c.Assert(err, jc.ErrorIsNil)
   195  
   196  	_, err = s.State.IPAddress("192.168.1.0")
   197  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   198  	_, err = s.State.IPAddress("192.168.1.1")
   199  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   200  }
   201  
   202  func (s *SubnetSuite) TestRefresh(c *gc.C) {
   203  	subnetInfo := state.SubnetInfo{CIDR: "192.168.1.0/24"}
   204  
   205  	subnet, err := s.State.AddSubnet(subnetInfo)
   206  	c.Assert(err, jc.ErrorIsNil)
   207  
   208  	subnetCopy, err := s.State.Subnet("192.168.1.0/24")
   209  	c.Assert(err, jc.ErrorIsNil)
   210  
   211  	err = subnet.EnsureDead()
   212  	c.Assert(err, jc.ErrorIsNil)
   213  
   214  	c.Assert(subnetCopy.Life(), gc.Equals, state.Alive)
   215  	err = subnetCopy.Refresh()
   216  	c.Assert(err, jc.ErrorIsNil)
   217  	c.Assert(subnetCopy.Life(), gc.Equals, state.Dead)
   218  }
   219  
   220  func (s *SubnetSuite) TestAllSubnets(c *gc.C) {
   221  	subnetInfos := []state.SubnetInfo{
   222  		{CIDR: "192.168.1.0/24"},
   223  		{CIDR: "8.8.8.0/24", SpaceName: "bar"},
   224  		{CIDR: "10.0.2.0/24", ProviderId: "foo"},
   225  		{CIDR: "2001:db8::/64", AvailabilityZone: "zone1"},
   226  	}
   227  
   228  	for _, info := range subnetInfos {
   229  		_, err := s.State.AddSubnet(info)
   230  		c.Assert(err, jc.ErrorIsNil)
   231  	}
   232  
   233  	subnets, err := s.State.AllSubnets()
   234  	c.Assert(err, jc.ErrorIsNil)
   235  	c.Assert(subnets, gc.HasLen, len(subnetInfos))
   236  
   237  	for i, subnet := range subnets {
   238  		c.Assert(subnet.CIDR(), gc.Equals, subnetInfos[i].CIDR)
   239  		c.Assert(subnet.ProviderId(), gc.Equals, subnetInfos[i].ProviderId)
   240  		c.Assert(subnet.SpaceName(), gc.Equals, subnetInfos[i].SpaceName)
   241  		c.Assert(subnet.AvailabilityZone(), gc.Equals, subnetInfos[i].AvailabilityZone)
   242  	}
   243  }
   244  
   245  func (s *SubnetSuite) TestPickNewAddressNoAddresses(c *gc.C) {
   246  	subnetInfo := state.SubnetInfo{
   247  		CIDR:              "192.168.1.0/24",
   248  		AllocatableIPLow:  "",
   249  		AllocatableIPHigh: "",
   250  	}
   251  	subnet, err := s.State.AddSubnet(subnetInfo)
   252  	c.Assert(err, jc.ErrorIsNil)
   253  
   254  	_, err = subnet.PickNewAddress()
   255  	c.Assert(err, gc.ErrorMatches, "no allocatable IP addresses for subnet .*")
   256  }
   257  
   258  func (s *SubnetSuite) getSubnetForAddressPicking(c *gc.C, allocatableHigh string) *state.Subnet {
   259  	subnetInfo := state.SubnetInfo{
   260  		CIDR:              "192.168.1.0/24",
   261  		AllocatableIPLow:  "192.168.1.0",
   262  		AllocatableIPHigh: allocatableHigh,
   263  	}
   264  	subnet, err := s.State.AddSubnet(subnetInfo)
   265  	c.Assert(err, jc.ErrorIsNil)
   266  	return subnet
   267  }
   268  
   269  func (s *SubnetSuite) TestPickNewAddressWhenSubnetIsDead(c *gc.C) {
   270  	subnet := s.getSubnetForAddressPicking(c, "192.168.1.0")
   271  	err := subnet.EnsureDead()
   272  	c.Assert(err, jc.ErrorIsNil)
   273  
   274  	// Calling it twice is ok.
   275  	err = subnet.EnsureDead()
   276  	c.Assert(err, jc.ErrorIsNil)
   277  
   278  	_, err = subnet.PickNewAddress()
   279  	c.Assert(err, gc.ErrorMatches,
   280  		`cannot pick address: subnet "192.168.1.0/24" is not alive`,
   281  	)
   282  }
   283  
   284  func (s *SubnetSuite) TestPickNewAddressAddressesExhausted(c *gc.C) {
   285  	subnet := s.getSubnetForAddressPicking(c, "192.168.1.0")
   286  	addr := network.NewAddress("192.168.1.0")
   287  	_, err := s.State.AddIPAddress(addr, subnet.ID())
   288  
   289  	_, err = subnet.PickNewAddress()
   290  	c.Assert(err, gc.ErrorMatches, "allocatable IP addresses exhausted for subnet .*")
   291  }
   292  
   293  func (s *SubnetSuite) TestPickNewAddressOneAddress(c *gc.C) {
   294  	subnet := s.getSubnetForAddressPicking(c, "192.168.1.0")
   295  
   296  	addr, err := subnet.PickNewAddress()
   297  	c.Assert(err, jc.ErrorIsNil)
   298  	c.Assert(addr.Value(), gc.Equals, "192.168.1.0")
   299  }
   300  
   301  func (s *SubnetSuite) TestPickNewAddressSkipsAllocated(c *gc.C) {
   302  	subnet := s.getSubnetForAddressPicking(c, "192.168.1.1")
   303  
   304  	addr := network.NewAddress("192.168.1.0")
   305  	_, err := s.State.AddIPAddress(addr, subnet.ID())
   306  
   307  	ipAddr, err := subnet.PickNewAddress()
   308  	c.Assert(err, jc.ErrorIsNil)
   309  	c.Assert(ipAddr.Value(), gc.Equals, "192.168.1.1")
   310  }
   311  
   312  func (s *SubnetSuite) TestPickNewAddressRace(c *gc.C) {
   313  	// represents 192.168.1.0
   314  	initialIP := uint32(3232235776)
   315  	var index int32 = -1
   316  	addresses := []uint32{initialIP, initialIP, initialIP + 1}
   317  
   318  	// the first two calls will get the same address (which simulates the
   319  	// inherent race condition in the code). The third call will get
   320  	// a new one. We should see two different addresses come out of the
   321  	// two calls: i.e. we will have detected the race condition and tried
   322  	// again.
   323  	mockPickAddress := func(_, _ uint32, _ map[uint32]bool) uint32 {
   324  		theIndex := atomic.AddInt32(&index, 1)
   325  		return addresses[theIndex]
   326  	}
   327  	s.PatchValue(&state.PickAddress, &mockPickAddress)
   328  
   329  	// 192.168.1.0 and 192.168.1.1 are the only valid addresses
   330  	subnet := s.getSubnetForAddressPicking(c, "192.168.1.1")
   331  
   332  	waiter := sync.WaitGroup{}
   333  	waiter.Add(2)
   334  
   335  	var firstResult *state.IPAddress
   336  	var firstError error
   337  	var secondResult *state.IPAddress
   338  	var secondError error
   339  	go func() {
   340  		firstResult, firstError = subnet.PickNewAddress()
   341  		waiter.Done()
   342  	}()
   343  	go func() {
   344  		secondResult, secondError = subnet.PickNewAddress()
   345  		waiter.Done()
   346  	}()
   347  	waiter.Wait()
   348  
   349  	c.Assert(firstError, jc.ErrorIsNil)
   350  	c.Assert(secondError, jc.ErrorIsNil)
   351  	c.Assert(firstResult, gc.NotNil)
   352  	c.Assert(secondResult, gc.NotNil)
   353  
   354  	ipAddresses := []string{firstResult.Value(), secondResult.Value()}
   355  	sort.Strings(ipAddresses)
   356  
   357  	expected := []string{"192.168.1.0", "192.168.1.1"}
   358  	c.Assert(ipAddresses, jc.DeepEquals, expected)
   359  }