github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "fmt" 8 "sort" 9 "sync" 10 "sync/atomic" 11 12 "github.com/juju/errors" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 16 "github.com/juju/juju/network" 17 "github.com/juju/juju/state" 18 ) 19 20 type SubnetSuite struct { 21 ConnSuite 22 } 23 24 var _ = gc.Suite(&SubnetSuite{}) 25 26 func (s *SubnetSuite) TestAddSubnetSucceedsWithFullyPopulatedInfo(c *gc.C) { 27 subnetInfo := state.SubnetInfo{ 28 ProviderId: "foo", 29 CIDR: "192.168.1.0/24", 30 VLANTag: 79, 31 AllocatableIPLow: "192.168.1.0", 32 AllocatableIPHigh: "192.168.1.1", 33 AvailabilityZone: "Timbuktu", 34 SpaceName: "foo", 35 } 36 37 subnet, err := s.State.AddSubnet(subnetInfo) 38 c.Assert(err, jc.ErrorIsNil) 39 s.assertSubnetMatchesInfo(c, subnet, subnetInfo) 40 41 // check it's been stored in state by fetching it back again 42 subnetFromDB, err := s.State.Subnet("192.168.1.0/24") 43 c.Assert(err, jc.ErrorIsNil) 44 s.assertSubnetMatchesInfo(c, subnetFromDB, subnetInfo) 45 } 46 47 func (s *SubnetSuite) assertSubnetMatchesInfo(c *gc.C, subnet *state.Subnet, info state.SubnetInfo) { 48 c.Assert(subnet.ProviderId(), gc.Equals, info.ProviderId) 49 c.Assert(subnet.CIDR(), gc.Equals, info.CIDR) 50 c.Assert(subnet.VLANTag(), gc.Equals, info.VLANTag) 51 c.Assert(subnet.AllocatableIPLow(), gc.Equals, info.AllocatableIPLow) 52 c.Assert(subnet.AllocatableIPHigh(), gc.Equals, info.AllocatableIPHigh) 53 c.Assert(subnet.AvailabilityZone(), gc.Equals, info.AvailabilityZone) 54 c.Assert(subnet.String(), gc.Equals, info.CIDR) 55 c.Assert(subnet.GoString(), gc.Equals, info.CIDR) 56 c.Assert(subnet.SpaceName(), gc.Equals, info.SpaceName) 57 } 58 59 func (s *SubnetSuite) TestAddSubnetFailsWithEmptyCIDR(c *gc.C) { 60 subnetInfo := state.SubnetInfo{} 61 s.assertAddSubnetForInfoFailsWithSuffix(c, subnetInfo, "missing CIDR") 62 } 63 64 func (s *SubnetSuite) assertAddSubnetForInfoFailsWithSuffix(c *gc.C, subnetInfo state.SubnetInfo, errorSuffix string) error { 65 subnet, err := s.State.AddSubnet(subnetInfo) 66 errorMessage := fmt.Sprintf("adding subnet %q: %s", subnetInfo.CIDR, errorSuffix) 67 c.Assert(err, gc.ErrorMatches, errorMessage) 68 c.Assert(subnet, gc.IsNil) 69 return err 70 } 71 72 func (s *SubnetSuite) TestAddSubnetFailsWithInvalidCIDR(c *gc.C) { 73 subnetInfo := state.SubnetInfo{CIDR: "foobar"} 74 s.assertAddSubnetForInfoFailsWithSuffix(c, subnetInfo, "invalid CIDR address: foobar") 75 } 76 77 func (s *SubnetSuite) TestAddSubnetFailsWithOutOfRangeVLANTag(c *gc.C) { 78 subnetInfo := state.SubnetInfo{CIDR: "192.168.0.1/24", VLANTag: 4095} 79 s.assertAddSubnetForInfoFailsWithSuffix(c, subnetInfo, "invalid VLAN tag 4095: must be between 0 and 4094") 80 } 81 82 func (s *SubnetSuite) TestAddSubnetFailsWhenAllocatableIPHighSetButAllocatableIPLowNotSet(c *gc.C) { 83 subnetInfo := state.SubnetInfo{CIDR: "192.168.0.1/24", AllocatableIPHigh: "192.168.0.1"} 84 s.assertAddSubnetForInfoFailsWithSuffix( 85 c, subnetInfo, 86 "either both AllocatableIPLow and AllocatableIPHigh must be set or neither set", 87 ) 88 } 89 90 func (s *SubnetSuite) TestAddSubnetFailsWhenAllocatableIPLowSetButAllocatableIPHighNotSet(c *gc.C) { 91 subnetInfo := state.SubnetInfo{CIDR: "192.168.0.1/24", AllocatableIPLow: "192.168.0.1"} 92 s.assertAddSubnetForInfoFailsWithSuffix( 93 c, subnetInfo, 94 "either both AllocatableIPLow and AllocatableIPHigh must be set or neither set", 95 ) 96 } 97 98 func (s *SubnetSuite) TestAddSubnetFailsWithInvalidAllocatableIPHigh(c *gc.C) { 99 subnetInfo := state.SubnetInfo{ 100 CIDR: "192.168.0.1/24", 101 AllocatableIPLow: "192.168.0.1", 102 AllocatableIPHigh: "foobar", 103 } 104 s.assertAddSubnetForInfoFailsWithSuffix(c, subnetInfo, `invalid AllocatableIPHigh "foobar"`) 105 } 106 107 func (s *SubnetSuite) TestAddSubnetFailsWithInvalidAllocatableIPLow(c *gc.C) { 108 subnetInfo := state.SubnetInfo{ 109 CIDR: "192.168.0.1/24", 110 AllocatableIPLow: "foobar", 111 AllocatableIPHigh: "192.168.0.1", 112 } 113 s.assertAddSubnetForInfoFailsWithSuffix(c, subnetInfo, `invalid AllocatableIPLow "foobar"`) 114 } 115 116 func (s *SubnetSuite) TestAddSubnetFailsWithOutOfRangeAllocatableIPHigh(c *gc.C) { 117 subnetInfo := state.SubnetInfo{ 118 CIDR: "192.168.0.1/24", 119 AllocatableIPLow: "192.168.0.1", 120 AllocatableIPHigh: "172.168.1.0", 121 } 122 s.assertAddSubnetForInfoFailsWithSuffix(c, subnetInfo, `invalid AllocatableIPHigh "172.168.1.0"`) 123 } 124 125 func (s *SubnetSuite) TestAddSubnetFailsWithOutOfRangeAllocatableIPLow(c *gc.C) { 126 subnetInfo := state.SubnetInfo{ 127 CIDR: "192.168.0.1/24", 128 AllocatableIPLow: "172.168.1.0", 129 AllocatableIPHigh: "192.168.0.10", 130 } 131 s.assertAddSubnetForInfoFailsWithSuffix(c, subnetInfo, `invalid AllocatableIPLow "172.168.1.0"`) 132 } 133 134 func (s *SubnetSuite) TestAddSubnetFailsWithAlreadyExistsForDuplicateCIDRInSameModel(c *gc.C) { 135 subnetInfo := state.SubnetInfo{CIDR: "192.168.0.1/24"} 136 subnet, err := s.State.AddSubnet(subnetInfo) 137 c.Assert(err, jc.ErrorIsNil) 138 s.assertSubnetMatchesInfo(c, subnet, subnetInfo) 139 140 err = s.assertAddSubnetForInfoFailsWithSuffix(c, subnetInfo, `subnet "192.168.0.1/24" already exists`) 141 c.Assert(err, jc.Satisfies, errors.IsAlreadyExists) 142 } 143 144 func (s *SubnetSuite) TestAddSubnetSucceedsForDuplicateCIDRInDifferentModels(c *gc.C) { 145 subnetInfo1 := state.SubnetInfo{CIDR: "192.168.0.1/24"} 146 subnetInfo2 := state.SubnetInfo{CIDR: "10.0.0.0/24"} 147 subnet1State := s.NewStateForModelNamed(c, "other-model") 148 149 subnet1, subnet2 := s.addTwoSubnetsInDifferentModelsAssertSuccessAndReturnBoth(c, subnetInfo1, subnetInfo2, subnet1State) 150 s.assertSubnetMatchesInfo(c, subnet1, subnetInfo1) 151 s.assertSubnetMatchesInfo(c, subnet2, subnetInfo2) 152 } 153 154 func (s *SubnetSuite) addTwoSubnetsInDifferentModelsAssertSuccessAndReturnBoth(c *gc.C, info1, info2 state.SubnetInfo, otherState *state.State) (*state.Subnet, *state.Subnet) { 155 subnet1, err := otherState.AddSubnet(info1) 156 c.Assert(err, jc.ErrorIsNil) 157 subnet2, err := s.State.AddSubnet(info2) 158 c.Assert(err, jc.ErrorIsNil) 159 160 return subnet1, subnet2 161 } 162 163 func (s *SubnetSuite) TestAddSubnetFailsWhenProviderIdNotUniqueInSameModel(c *gc.C) { 164 subnetInfo1 := state.SubnetInfo{CIDR: "192.168.0.1/24", ProviderId: "foo"} 165 subnetInfo2 := state.SubnetInfo{CIDR: "10.0.0.0/24", ProviderId: "foo"} 166 167 s.addTwoSubnetsAndAssertSecondFailsWithSuffix(c, subnetInfo1, subnetInfo2, `ProviderId "foo" not unique`) 168 } 169 170 func (s *SubnetSuite) addTwoSubnetsAndAssertSecondFailsWithSuffix(c *gc.C, info1, info2 state.SubnetInfo, errorSuffix string) { 171 s.addTwoSubnetsInDifferentModelsAndAssertSecondFailsWithSuffix(c, info1, info2, s.State, errorSuffix) 172 } 173 174 func (s *SubnetSuite) addTwoSubnetsInDifferentModelsAndAssertSecondFailsWithSuffix(c *gc.C, info1, info2 state.SubnetInfo, otherState *state.State, errorSuffix string) { 175 _, err := otherState.AddSubnet(info1) 176 c.Assert(err, jc.ErrorIsNil) 177 178 s.assertAddSubnetForInfoFailsWithSuffix(c, info2, errorSuffix) 179 } 180 181 func (s *SubnetSuite) TestAddSubnetSucceedsWhenProviderIdNotUniqueInDifferentModels(c *gc.C) { 182 subnetInfo1 := state.SubnetInfo{CIDR: "192.168.0.1/24", ProviderId: "foo"} 183 subnetInfo2 := state.SubnetInfo{CIDR: "10.0.0.0/24", ProviderId: "foo"} 184 subnet1State := s.NewStateForModelNamed(c, "other-model") 185 186 subnet1, subnet2 := s.addTwoSubnetsInDifferentModelsAssertSuccessAndReturnBoth(c, subnetInfo1, subnetInfo2, subnet1State) 187 s.assertSubnetMatchesInfo(c, subnet1, subnetInfo1) 188 s.assertSubnetMatchesInfo(c, subnet2, subnetInfo2) 189 } 190 191 func (s *SubnetSuite) TestAddSubnetSucceedsForDifferentCIDRsAndEmptyProviderIdInSameModel(c *gc.C) { 192 subnetInfo1 := state.SubnetInfo{CIDR: "192.168.0.1/24", ProviderId: ""} 193 subnetInfo2 := state.SubnetInfo{CIDR: "10.0.0.0/24", ProviderId: ""} 194 195 subnet1, subnet2 := s.addTwoSubnetsAssertSuccessAndReturnBoth(c, subnetInfo1, subnetInfo2) 196 s.assertSubnetMatchesInfo(c, subnet1, subnetInfo1) 197 s.assertSubnetMatchesInfo(c, subnet2, subnetInfo2) 198 } 199 200 func (s *SubnetSuite) addTwoSubnetsAssertSuccessAndReturnBoth(c *gc.C, info1, info2 state.SubnetInfo) (*state.Subnet, *state.Subnet) { 201 return s.addTwoSubnetsInDifferentModelsAssertSuccessAndReturnBoth(c, info1, info2, s.State) 202 } 203 204 func (s *SubnetSuite) TestAddSubnetSucceedsForDifferentCIDRsAndEmptyProviderIdInDifferentModels(c *gc.C) { 205 subnetInfo1 := state.SubnetInfo{CIDR: "192.168.0.1/24", ProviderId: ""} 206 subnetInfo2 := state.SubnetInfo{CIDR: "10.0.0.0/24", ProviderId: ""} 207 subnet1State := s.NewStateForModelNamed(c, "other-model") 208 209 subnet1, subnet2 := s.addTwoSubnetsInDifferentModelsAssertSuccessAndReturnBoth(c, subnetInfo1, subnetInfo2, subnet1State) 210 s.assertSubnetMatchesInfo(c, subnet1, subnetInfo1) 211 s.assertSubnetMatchesInfo(c, subnet2, subnetInfo2) 212 } 213 214 func (s *SubnetSuite) TestEnsureDeadSetsLifeToDeadWhenAlive(c *gc.C) { 215 subnet := s.addAliveSubnet(c, "192.168.0.1/24") 216 217 s.ensureDeadAndAssertLifeIsDead(c, subnet) 218 s.refreshAndAssertSubnetLifeIs(c, subnet, state.Dead) 219 } 220 221 func (s *SubnetSuite) addAliveSubnet(c *gc.C, cidr string) *state.Subnet { 222 subnetInfo := state.SubnetInfo{CIDR: cidr} 223 subnet, err := s.State.AddSubnet(subnetInfo) 224 c.Assert(err, jc.ErrorIsNil) 225 c.Assert(subnet.Life(), gc.Equals, state.Alive) 226 227 return subnet 228 } 229 230 func (s *SubnetSuite) ensureDeadAndAssertLifeIsDead(c *gc.C, subnet *state.Subnet) { 231 err := subnet.EnsureDead() 232 c.Assert(err, jc.ErrorIsNil) 233 c.Assert(subnet.Life(), gc.Equals, state.Dead) 234 } 235 236 func (s *SubnetSuite) refreshAndAssertSubnetLifeIs(c *gc.C, subnet *state.Subnet, expectedLife state.Life) { 237 err := subnet.Refresh() 238 c.Assert(err, jc.ErrorIsNil) 239 c.Assert(subnet.Life(), gc.Equals, expectedLife) 240 } 241 242 func (s *SubnetSuite) TestEnsureDeadSetsLifeToDeadWhenNotAlive(c *gc.C) { 243 subnet := s.addAliveSubnet(c, "192.168.0.1/24") 244 s.ensureDeadAndAssertLifeIsDead(c, subnet) 245 246 s.ensureDeadAndAssertLifeIsDead(c, subnet) 247 } 248 249 func (s *SubnetSuite) TestRemoveFailsIfStillAlive(c *gc.C) { 250 subnet := s.addAliveSubnet(c, "192.168.0.1/24") 251 252 err := subnet.Remove() 253 c.Assert(err, gc.ErrorMatches, `cannot remove subnet "192.168.0.1/24": subnet is not dead`) 254 s.refreshAndAssertSubnetLifeIs(c, subnet, state.Alive) 255 } 256 257 func (s *SubnetSuite) TestRemoveSucceedsWhenSubnetIsNotAlive(c *gc.C) { 258 subnet := s.addAliveSubnet(c, "192.168.0.1/24") 259 s.ensureDeadAndAssertLifeIsDead(c, subnet) 260 261 s.removeSubnetAndAssertNotFound(c, subnet) 262 } 263 264 func (s *SubnetSuite) removeSubnetAndAssertNotFound(c *gc.C, subnet *state.Subnet) { 265 err := subnet.Remove() 266 c.Assert(err, jc.ErrorIsNil) 267 s.assertSubnetWithCIDRNotFound(c, subnet.CIDR()) 268 } 269 270 func (s *SubnetSuite) assertSubnetWithCIDRNotFound(c *gc.C, cidr string) { 271 _, err := s.State.Subnet(cidr) 272 s.assertSubnetNotFoundError(c, err) 273 } 274 275 func (s *SubnetSuite) assertSubnetNotFoundError(c *gc.C, err error) { 276 c.Assert(err, gc.ErrorMatches, "subnet .* not found") 277 c.Assert(err, jc.Satisfies, errors.IsNotFound) 278 } 279 280 func (s *SubnetSuite) TestRemoveSucceedsWhenCalledTwice(c *gc.C) { 281 subnet := s.addAliveSubnet(c, "192.168.0.1/24") 282 s.ensureDeadAndAssertLifeIsDead(c, subnet) 283 s.removeSubnetAndAssertNotFound(c, subnet) 284 285 err := subnet.Remove() 286 c.Assert(err, gc.ErrorMatches, `cannot remove subnet "192.168.0.1/24": not found or not dead`) 287 } 288 289 func (s *SubnetSuite) TestRemoveKillsAddedIPAddresses(c *gc.C) { 290 subnet := s.addAliveSubnet(c, "192.168.1.0/24") 291 s.addIPAddressForSubnet(c, "192.168.1.0", subnet) 292 s.addIPAddressForSubnet(c, "192.168.1.1", subnet) 293 294 err := subnet.EnsureDead() 295 c.Assert(err, jc.ErrorIsNil) 296 err = subnet.Remove() 297 c.Assert(err, jc.ErrorIsNil) 298 299 s.assertIPAddressNotFound(c, "192.168.1.0") 300 s.assertIPAddressNotFound(c, "192.168.1.1") 301 } 302 303 func (s *SubnetSuite) addIPAddressForSubnet(c *gc.C, ipAddress string, subnet *state.Subnet) { 304 _, err := s.State.AddIPAddress(network.NewAddress(ipAddress), subnet.ID()) 305 c.Assert(err, jc.ErrorIsNil) 306 } 307 308 func (s *SubnetSuite) assertIPAddressNotFound(c *gc.C, ipAddress string) { 309 _, err := s.State.IPAddress(ipAddress) 310 c.Assert(err, jc.Satisfies, errors.IsNotFound) 311 } 312 313 func (s *SubnetSuite) TestRefreshUpdatesStaleDocData(c *gc.C) { 314 subnet := s.addAliveSubnet(c, "fc00::/64") 315 subnetCopy, err := s.State.Subnet("fc00::/64") 316 c.Assert(err, jc.ErrorIsNil) 317 318 s.ensureDeadAndAssertLifeIsDead(c, subnet) 319 c.Assert(subnetCopy.Life(), gc.Equals, state.Alive) 320 321 err = subnetCopy.Refresh() 322 c.Assert(err, jc.ErrorIsNil) 323 c.Assert(subnetCopy.Life(), gc.Equals, state.Dead) 324 } 325 326 func (s *SubnetSuite) TestRefreshFailsWithNotFoundWhenRemoved(c *gc.C) { 327 subnet := s.addAliveSubnet(c, "192.168.1.0/24") 328 s.ensureDeadAndAssertLifeIsDead(c, subnet) 329 s.removeSubnetAndAssertNotFound(c, subnet) 330 331 err := subnet.Refresh() 332 s.assertSubnetNotFoundError(c, err) 333 } 334 335 func (s *SubnetSuite) TestAllSubnets(c *gc.C) { 336 subnetInfos := []state.SubnetInfo{ 337 {CIDR: "192.168.1.0/24"}, 338 {CIDR: "8.8.8.0/24", SpaceName: "bar"}, 339 {CIDR: "10.0.2.0/24", ProviderId: "foo"}, 340 {CIDR: "2001:db8::/64", AvailabilityZone: "zone1"}, 341 } 342 343 for _, info := range subnetInfos { 344 _, err := s.State.AddSubnet(info) 345 c.Assert(err, jc.ErrorIsNil) 346 } 347 348 subnets, err := s.State.AllSubnets() 349 c.Assert(err, jc.ErrorIsNil) 350 c.Assert(subnets, gc.HasLen, len(subnetInfos)) 351 352 for i, subnet := range subnets { 353 c.Assert(subnet.CIDR(), gc.Equals, subnetInfos[i].CIDR) 354 c.Assert(subnet.ProviderId(), gc.Equals, subnetInfos[i].ProviderId) 355 c.Assert(subnet.SpaceName(), gc.Equals, subnetInfos[i].SpaceName) 356 c.Assert(subnet.AvailabilityZone(), gc.Equals, subnetInfos[i].AvailabilityZone) 357 } 358 } 359 360 func (s *SubnetSuite) TestPickNewAddressNoAddresses(c *gc.C) { 361 subnet := s.addAliveSubnet(c, "192.168.1.0/24") 362 363 _, err := subnet.PickNewAddress() 364 c.Assert(err, gc.ErrorMatches, "no allocatable IP addresses for subnet .*") 365 } 366 367 func (s *SubnetSuite) TestPickNewAddressWhenSubnetIsDead(c *gc.C) { 368 subnet := s.addSubnetWithAllocatableIPHigh(c, "192.168.1.0") 369 s.ensureDeadAndAssertLifeIsDead(c, subnet) 370 371 _, err := subnet.PickNewAddress() 372 c.Assert(err, gc.ErrorMatches, 373 `cannot pick address: subnet "192.168.1.0/24" is not alive`, 374 ) 375 } 376 377 func (s *SubnetSuite) addSubnetWithAllocatableIPHigh(c *gc.C, allocatableHigh string) *state.Subnet { 378 subnetInfo := state.SubnetInfo{ 379 CIDR: "192.168.1.0/24", 380 AllocatableIPLow: "192.168.1.0", 381 AllocatableIPHigh: allocatableHigh, 382 } 383 subnet, err := s.State.AddSubnet(subnetInfo) 384 c.Assert(err, jc.ErrorIsNil) 385 return subnet 386 } 387 388 func (s *SubnetSuite) TestPickNewAddressAddressesExhausted(c *gc.C) { 389 subnet := s.addSubnetWithAllocatableIPHigh(c, "192.168.1.0") 390 s.addIPAddressForSubnet(c, "192.168.1.0", subnet) 391 392 _, err := subnet.PickNewAddress() 393 c.Assert(err, gc.ErrorMatches, "allocatable IP addresses exhausted for subnet .*") 394 } 395 396 func (s *SubnetSuite) TestPickNewAddressOneAddress(c *gc.C) { 397 subnet := s.addSubnetWithAllocatableIPHigh(c, "192.168.1.0") 398 399 addr, err := subnet.PickNewAddress() 400 c.Assert(err, jc.ErrorIsNil) 401 c.Assert(addr.Value(), gc.Equals, "192.168.1.0") 402 } 403 404 func (s *SubnetSuite) TestPickNewAddressSkipsAllocated(c *gc.C) { 405 subnet := s.addSubnetWithAllocatableIPHigh(c, "192.168.1.1") 406 s.addIPAddressForSubnet(c, "192.168.1.0", subnet) 407 408 ipAddr, err := subnet.PickNewAddress() 409 c.Assert(err, jc.ErrorIsNil) 410 c.Assert(ipAddr.Value(), gc.Equals, "192.168.1.1") 411 } 412 413 func (s *SubnetSuite) TestPickNewAddressRace(c *gc.C) { 414 // represents 192.168.1.0 415 initialIP := uint32(3232235776) 416 var index int32 = -1 417 addresses := []uint32{initialIP, initialIP, initialIP + 1} 418 419 // the first two calls will get the same address (which simulates the 420 // inherent race condition in the code). The third call will get 421 // a new one. We should see two different addresses come out of the 422 // two calls: i.e. we will have detected the race condition and tried 423 // again. 424 mockPickAddress := func(_, _ uint32, _ map[uint32]bool) uint32 { 425 theIndex := atomic.AddInt32(&index, 1) 426 return addresses[theIndex] 427 } 428 s.PatchValue(&state.PickAddress, &mockPickAddress) 429 430 // 192.168.1.0 and 192.168.1.1 are the only valid addresses 431 subnet := s.addSubnetWithAllocatableIPHigh(c, "192.168.1.1") 432 433 waiter := sync.WaitGroup{} 434 waiter.Add(2) 435 436 var firstResult *state.IPAddress 437 var firstError error 438 var secondResult *state.IPAddress 439 var secondError error 440 go func() { 441 firstResult, firstError = subnet.PickNewAddress() 442 waiter.Done() 443 }() 444 go func() { 445 secondResult, secondError = subnet.PickNewAddress() 446 waiter.Done() 447 }() 448 waiter.Wait() 449 450 c.Assert(firstError, jc.ErrorIsNil) 451 c.Assert(secondError, jc.ErrorIsNil) 452 c.Assert(firstResult, gc.NotNil) 453 c.Assert(secondResult, gc.NotNil) 454 455 ipAddresses := []string{firstResult.Value(), secondResult.Value()} 456 sort.Strings(ipAddresses) 457 458 expected := []string{"192.168.1.0", "192.168.1.1"} 459 c.Assert(ipAddresses, jc.DeepEquals, expected) 460 }