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 }