github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 9 "github.com/juju/errors" 10 "github.com/juju/mgo/v3/bson" 11 "github.com/juju/mgo/v3/txn" 12 "github.com/juju/names/v5" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 16 "github.com/juju/juju/core/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 space, err := s.State.AddSpace("foo", "4", nil, true) 28 c.Assert(err, jc.ErrorIsNil) 29 30 fanOverlaySubnetInfo := network.SubnetInfo{ 31 ProviderId: "foo2", 32 CIDR: "10.0.0.0/8", 33 SpaceID: space.Id(), 34 } 35 subnet, err := s.State.AddSubnet(fanOverlaySubnetInfo) 36 c.Assert(err, jc.ErrorIsNil) 37 s.assertSubnetMatchesInfo(c, subnet, fanOverlaySubnetInfo) 38 subnetInfo := network.SubnetInfo{ 39 ProviderId: "foo", 40 CIDR: "192.168.1.0/24", 41 VLANTag: 79, 42 AvailabilityZones: []string{"Timbuktu"}, 43 ProviderNetworkId: "wildbirds", 44 IsPublic: true, 45 } 46 subnetInfo.SetFan("10.0.0.0/8", "172.16.0.0/16") 47 48 subnet, err = s.State.AddSubnet(subnetInfo) 49 c.Assert(err, jc.ErrorIsNil) 50 51 // Set the expected space after adding the subnet to state. 52 // When retrieved, it should inherit the space of its underlay. 53 subnetInfo.SpaceID = space.Id() 54 55 s.assertSubnetMatchesInfo(c, subnet, subnetInfo) 56 57 // check it's been stored in state by fetching it back again 58 subnetFromDB, err := s.State.SubnetByCIDR("192.168.1.0/24") 59 c.Assert(err, jc.ErrorIsNil) 60 s.assertSubnetMatchesInfo(c, subnetFromDB, subnetInfo) 61 } 62 63 func (s *SubnetSuite) assertSubnetMatchesInfo(c *gc.C, subnet *state.Subnet, info network.SubnetInfo) { 64 c.Assert(subnet.ProviderId(), gc.Equals, info.ProviderId) 65 c.Assert(subnet.CIDR(), gc.Equals, info.CIDR) 66 c.Assert(subnet.VLANTag(), gc.Equals, info.VLANTag) 67 c.Assert(subnet.AvailabilityZones(), gc.DeepEquals, info.AvailabilityZones) 68 c.Assert(subnet.String(), gc.Equals, info.CIDR) 69 c.Assert(subnet.GoString(), gc.Equals, info.CIDR) 70 expectedSubnetID := info.SpaceID 71 if expectedSubnetID == "" { 72 expectedSubnetID = "0" 73 } 74 c.Check(subnet.SpaceID(), gc.Equals, expectedSubnetID) 75 c.Assert(subnet.ProviderNetworkId(), gc.Equals, info.ProviderNetworkId) 76 c.Assert(subnet.FanLocalUnderlay(), gc.Equals, info.FanLocalUnderlay()) 77 c.Assert(subnet.FanOverlay(), gc.Equals, info.FanOverlay()) 78 c.Assert(subnet.IsPublic(), gc.Equals, info.IsPublic) 79 } 80 81 func (s *SubnetSuite) TestAddSubnetFailsWithEmptyCIDR(c *gc.C) { 82 subnetInfo := network.SubnetInfo{} 83 _ = s.assertAddSubnetForInfoFailsWithSuffix(c, subnetInfo, "missing CIDR") 84 } 85 86 func (s *SubnetSuite) assertAddSubnetForInfoFailsWithSuffix(c *gc.C, subnetInfo network.SubnetInfo, errorSuffix string) error { 87 subnet, err := s.State.AddSubnet(subnetInfo) 88 errorMessage := fmt.Sprintf("adding subnet %q: %s", subnetInfo.CIDR, errorSuffix) 89 c.Assert(err, gc.ErrorMatches, errorMessage) 90 c.Assert(subnet, gc.IsNil) 91 return err 92 } 93 94 func (s *SubnetSuite) TestAddSubnetFailsWithInvalidCIDR(c *gc.C) { 95 subnetInfo := network.SubnetInfo{CIDR: "foobar"} 96 _ = s.assertAddSubnetForInfoFailsWithSuffix(c, subnetInfo, "invalid CIDR address: foobar") 97 } 98 99 func (s *SubnetSuite) TestAddSubnetFailsWithOutOfRangeVLANTag(c *gc.C) { 100 subnetInfo := network.SubnetInfo{CIDR: "192.168.0.1/24", VLANTag: 4095} 101 _ = s.assertAddSubnetForInfoFailsWithSuffix(c, subnetInfo, "invalid VLAN tag 4095: must be between 0 and 4094") 102 } 103 104 func (s *SubnetSuite) TestAddSubnetFailsWithAlreadyExistsForDuplicateCIDRInSameModel(c *gc.C) { 105 subnetInfo := network.SubnetInfo{CIDR: "192.168.0.1/24"} 106 subnet, err := s.State.AddSubnet(subnetInfo) 107 c.Assert(err, jc.ErrorIsNil) 108 s.assertSubnetMatchesInfo(c, subnet, subnetInfo) 109 110 err = s.assertAddSubnetForInfoFailsWithSuffix(c, subnetInfo, `subnet "192.168.0.1/24" already exists`) 111 c.Assert(err, jc.Satisfies, errors.IsAlreadyExists) 112 } 113 114 func (s *SubnetSuite) TestAddSubnetSuccessForDuplicateCIDRDiffProviderIDInSameModel(c *gc.C) { 115 subnetInfo := network.SubnetInfo{CIDR: "192.168.0.1/24"} 116 subnet, err := s.State.AddSubnet(subnetInfo) 117 c.Assert(err, jc.ErrorIsNil) 118 s.assertSubnetMatchesInfo(c, subnet, subnetInfo) 119 120 subnetInfo.ProviderId = "testme" 121 subnet2, err := s.State.AddSubnet(subnetInfo) 122 c.Assert(err, jc.ErrorIsNil) 123 c.Assert(subnet.ID(), gc.Not(gc.Equals), subnet2.ID()) 124 c.Assert(subnet.CIDR(), gc.Equals, subnet2.CIDR()) 125 } 126 127 func (s *SubnetSuite) TestAddSubnetSucceedsForDuplicateCIDRInDifferentModels(c *gc.C) { 128 subnetInfo1 := network.SubnetInfo{CIDR: "192.168.0.1/24"} 129 subnetInfo2 := network.SubnetInfo{CIDR: "10.0.0.0/24"} 130 subnet1State := s.NewStateForModelNamed(c, "other-model") 131 132 subnet1, subnet2 := s.addTwoSubnetsInDifferentModelsAssertSuccessAndReturnBoth(c, subnetInfo1, subnetInfo2, subnet1State) 133 s.assertSubnetMatchesInfo(c, subnet1, subnetInfo1) 134 s.assertSubnetMatchesInfo(c, subnet2, subnetInfo2) 135 } 136 137 func (s *SubnetSuite) addTwoSubnetsInDifferentModelsAssertSuccessAndReturnBoth(c *gc.C, info1, info2 network.SubnetInfo, otherState *state.State) (*state.Subnet, *state.Subnet) { 138 subnet1, err := otherState.AddSubnet(info1) 139 c.Assert(err, jc.ErrorIsNil) 140 subnet2, err := s.State.AddSubnet(info2) 141 c.Assert(err, jc.ErrorIsNil) 142 143 return subnet1, subnet2 144 } 145 146 func (s *SubnetSuite) TestAddSubnetFailsWhenProviderIdNotUniqueInSameModel(c *gc.C) { 147 subnetInfo1 := network.SubnetInfo{CIDR: "192.168.0.1/24", ProviderId: "foo"} 148 subnetInfo2 := network.SubnetInfo{CIDR: "10.0.0.0/24", ProviderId: "foo"} 149 150 s.addTwoSubnetsAndAssertSecondFailsWithSuffix(c, subnetInfo1, subnetInfo2, `provider ID "foo" not unique`) 151 } 152 153 func (s *SubnetSuite) addTwoSubnetsAndAssertSecondFailsWithSuffix(c *gc.C, info1, info2 network.SubnetInfo, errorSuffix string) { 154 s.addTwoSubnetsInDifferentModelsAndAssertSecondFailsWithSuffix(c, info1, info2, s.State, errorSuffix) 155 } 156 157 func (s *SubnetSuite) addTwoSubnetsInDifferentModelsAndAssertSecondFailsWithSuffix(c *gc.C, info1, info2 network.SubnetInfo, otherState *state.State, errorSuffix string) { 158 _, err := otherState.AddSubnet(info1) 159 c.Assert(err, jc.ErrorIsNil) 160 161 _ = s.assertAddSubnetForInfoFailsWithSuffix(c, info2, errorSuffix) 162 } 163 164 func (s *SubnetSuite) TestAddSubnetSucceedsWhenProviderIdNotUniqueInDifferentModels(c *gc.C) { 165 subnetInfo1 := network.SubnetInfo{CIDR: "192.168.0.1/24", ProviderId: "foo"} 166 subnetInfo2 := network.SubnetInfo{CIDR: "10.0.0.0/24", ProviderId: "foo"} 167 subnet1State := s.NewStateForModelNamed(c, "other-model") 168 169 subnet1, subnet2 := s.addTwoSubnetsInDifferentModelsAssertSuccessAndReturnBoth(c, subnetInfo1, subnetInfo2, subnet1State) 170 s.assertSubnetMatchesInfo(c, subnet1, subnetInfo1) 171 s.assertSubnetMatchesInfo(c, subnet2, subnetInfo2) 172 } 173 174 func (s *SubnetSuite) TestAddSubnetSucceedsForDifferentCIDRsAndEmptyProviderIdInSameModel(c *gc.C) { 175 subnetInfo1 := network.SubnetInfo{CIDR: "192.168.0.1/24", ProviderId: ""} 176 subnetInfo2 := network.SubnetInfo{CIDR: "10.0.0.0/24", ProviderId: ""} 177 178 subnet1, subnet2 := s.addTwoSubnetsAssertSuccessAndReturnBoth(c, subnetInfo1, subnetInfo2) 179 s.assertSubnetMatchesInfo(c, subnet1, subnetInfo1) 180 s.assertSubnetMatchesInfo(c, subnet2, subnetInfo2) 181 } 182 183 func (s *SubnetSuite) addTwoSubnetsAssertSuccessAndReturnBoth(c *gc.C, info1, info2 network.SubnetInfo) (*state.Subnet, *state.Subnet) { 184 return s.addTwoSubnetsInDifferentModelsAssertSuccessAndReturnBoth(c, info1, info2, s.State) 185 } 186 187 func (s *SubnetSuite) TestAddSubnetSucceedsForDifferentCIDRsAndEmptyProviderIdInDifferentModels(c *gc.C) { 188 subnetInfo1 := network.SubnetInfo{CIDR: "192.168.0.1/24", ProviderId: ""} 189 subnetInfo2 := network.SubnetInfo{CIDR: "10.0.0.0/24", ProviderId: ""} 190 subnet1State := s.NewStateForModelNamed(c, "other-model") 191 192 subnet1, subnet2 := s.addTwoSubnetsInDifferentModelsAssertSuccessAndReturnBoth(c, subnetInfo1, subnetInfo2, subnet1State) 193 s.assertSubnetMatchesInfo(c, subnet1, subnetInfo1) 194 s.assertSubnetMatchesInfo(c, subnet2, subnetInfo2) 195 } 196 197 func (s *SubnetSuite) TestEnsureDeadSetsLifeToDeadWhenAlive(c *gc.C) { 198 subnet := s.addAliveSubnet(c, "192.168.0.1/24") 199 200 s.ensureDeadAndAssertLifeIsDead(c, subnet) 201 s.refreshAndAssertSubnetLifeIs(c, subnet, state.Dead) 202 } 203 204 func (s *SubnetSuite) addAliveSubnet(c *gc.C, cidr string) *state.Subnet { 205 subnetInfo := network.SubnetInfo{CIDR: cidr} 206 subnet, err := s.State.AddSubnet(subnetInfo) 207 c.Assert(err, jc.ErrorIsNil) 208 c.Assert(subnet.Life(), gc.Equals, state.Alive) 209 210 return subnet 211 } 212 213 func (s *SubnetSuite) ensureDeadAndAssertLifeIsDead(c *gc.C, subnet *state.Subnet) { 214 err := subnet.EnsureDead() 215 c.Assert(err, jc.ErrorIsNil) 216 c.Assert(subnet.Life(), gc.Equals, state.Dead) 217 } 218 219 func (s *SubnetSuite) refreshAndAssertSubnetLifeIs(c *gc.C, subnet *state.Subnet, expectedLife state.Life) { 220 err := subnet.Refresh() 221 c.Assert(err, jc.ErrorIsNil) 222 c.Assert(subnet.Life(), gc.Equals, expectedLife) 223 } 224 225 func (s *SubnetSuite) TestEnsureDeadSetsLifeToDeadWhenNotAlive(c *gc.C) { 226 subnet := s.addAliveSubnet(c, "192.168.0.1/24") 227 s.ensureDeadAndAssertLifeIsDead(c, subnet) 228 229 s.ensureDeadAndAssertLifeIsDead(c, subnet) 230 } 231 232 func (s *SubnetSuite) TestRemoveFailsIfStillAlive(c *gc.C) { 233 subnet := s.addAliveSubnet(c, "192.168.0.1/24") 234 235 err := subnet.Remove() 236 c.Assert(err, gc.ErrorMatches, `cannot remove subnet "192.168.0.1/24": subnet is not dead`) 237 s.refreshAndAssertSubnetLifeIs(c, subnet, state.Alive) 238 } 239 240 func (s *SubnetSuite) TestRemoveSucceedsWhenSubnetIsNotAlive(c *gc.C) { 241 subnet := s.addAliveSubnet(c, "192.168.0.1/24") 242 s.ensureDeadAndAssertLifeIsDead(c, subnet) 243 244 s.removeSubnetAndAssertNotFound(c, subnet) 245 } 246 247 func (s *SubnetSuite) removeSubnetAndAssertNotFound(c *gc.C, subnet *state.Subnet) { 248 err := subnet.Remove() 249 c.Assert(err, jc.ErrorIsNil) 250 s.assertSubnetWithCIDRNotFound(c, subnet.CIDR()) 251 } 252 253 func (s *SubnetSuite) assertSubnetWithCIDRNotFound(c *gc.C, cidr string) { 254 _, err := s.State.SubnetByCIDR(cidr) 255 s.assertSubnetNotFoundError(c, err) 256 } 257 258 func (s *SubnetSuite) assertSubnetNotFoundError(c *gc.C, err error) { 259 c.Assert(err, gc.ErrorMatches, "subnet .* not found") 260 c.Assert(err, jc.Satisfies, errors.IsNotFound) 261 } 262 263 func (s *SubnetSuite) TestRemoveSucceedsWhenCalledTwice(c *gc.C) { 264 subnet := s.addAliveSubnet(c, "192.168.0.1/24") 265 s.ensureDeadAndAssertLifeIsDead(c, subnet) 266 s.removeSubnetAndAssertNotFound(c, subnet) 267 268 err := subnet.Remove() 269 c.Assert(err, gc.ErrorMatches, `cannot remove subnet "192.168.0.1/24": not found or not dead`) 270 } 271 272 func (s *SubnetSuite) TestRefreshUpdatesStaleDocData(c *gc.C) { 273 subnet := s.addAliveSubnet(c, "fc00::/64") 274 subnetCopy, err := s.State.SubnetByCIDR("fc00::/64") 275 c.Assert(err, jc.ErrorIsNil) 276 277 s.ensureDeadAndAssertLifeIsDead(c, subnet) 278 c.Assert(subnetCopy.Life(), gc.Equals, state.Alive) 279 280 err = subnetCopy.Refresh() 281 c.Assert(err, jc.ErrorIsNil) 282 c.Assert(subnetCopy.Life(), gc.Equals, state.Dead) 283 } 284 285 func (s *SubnetSuite) TestRefreshFailsWithNotFoundWhenRemoved(c *gc.C) { 286 subnet := s.addAliveSubnet(c, "192.168.1.0/24") 287 s.ensureDeadAndAssertLifeIsDead(c, subnet) 288 s.removeSubnetAndAssertNotFound(c, subnet) 289 290 err := subnet.Refresh() 291 s.assertSubnetNotFoundError(c, err) 292 } 293 294 func (s *SubnetSuite) TestAllSubnets(c *gc.C) { 295 space1, err := s.State.AddSpace("bar", "4", nil, true) 296 c.Assert(err, jc.ErrorIsNil) 297 space2, err := s.State.AddSpace("notreally", "5", nil, true) 298 c.Assert(err, jc.ErrorIsNil) 299 subnetInfos := []network.SubnetInfo{ 300 {CIDR: "192.168.1.0/24"}, 301 {CIDR: "8.8.8.0/24", SpaceID: space1.Id()}, 302 {CIDR: "10.0.2.0/24", ProviderId: "foo"}, 303 {CIDR: "2001:db8::/64", AvailabilityZones: []string{"zone1"}}, 304 {CIDR: "253.0.0.0/8", SpaceID: space2.Id()}, 305 } 306 subnetInfos[4].SetFan("8.8.8.0/24", "") 307 308 for _, info := range subnetInfos { 309 _, err := s.State.AddSubnet(info) 310 c.Assert(err, jc.ErrorIsNil) 311 } 312 313 subnets, err := s.State.AllSubnets() 314 c.Assert(err, jc.ErrorIsNil) 315 c.Assert(subnets, gc.HasLen, len(subnetInfos)) 316 317 for i, subnet := range subnets { 318 c.Check(subnet.CIDR(), gc.Equals, subnetInfos[i].CIDR) 319 c.Check(subnet.ProviderId(), gc.Equals, subnetInfos[i].ProviderId) 320 if subnet.FanLocalUnderlay() == "" { 321 expectedSubnetID := subnetInfos[i].SpaceID 322 if expectedSubnetID == "" { 323 expectedSubnetID = "0" 324 } 325 c.Check(subnet.SpaceID(), gc.Equals, expectedSubnetID) 326 } else { 327 // Special case 328 c.Check(subnet.SpaceID(), gc.Equals, space1.Id()) 329 } 330 c.Check(subnet.AvailabilityZones(), gc.DeepEquals, subnetInfos[i].AvailabilityZones) 331 } 332 } 333 334 func (s *SubnetSuite) TestAllSubnetInfosPopulatesOverlaySpace(c *gc.C) { 335 space1, err := s.State.AddSpace("bar", "4", nil, true) 336 c.Assert(err, jc.ErrorIsNil) 337 338 subnetInfos := []network.SubnetInfo{ 339 {CIDR: "8.8.8.0/24", SpaceID: space1.Id()}, 340 {CIDR: "253.0.0.0/8"}, 341 } 342 subnetInfos[1].SetFan("8.8.8.0/24", "") 343 344 for _, info := range subnetInfos { 345 _, err := s.State.AddSubnet(info) 346 c.Assert(err, jc.ErrorIsNil) 347 } 348 349 subnets, err := s.State.AllSubnetInfos() 350 c.Assert(err, jc.ErrorIsNil) 351 c.Assert(subnets, gc.HasLen, len(subnetInfos)) 352 353 for _, subnet := range subnets { 354 if subnet.FanLocalUnderlay() == "" { 355 c.Check(subnet.SpaceID, gc.Equals, space1.Id()) 356 } 357 } 358 } 359 360 func (s *SubnetSuite) TestUpdateMAASUndefinedSpace(c *gc.C) { 361 subnetInfo := network.SubnetInfo{CIDR: "8.8.8.0/24"} 362 subnet, err := s.State.AddSubnet(subnetInfo) 363 c.Assert(err, jc.ErrorIsNil) 364 _, err = s.State.AddSpace(names.NewSpaceTag("undefined").Id(), "-1", []string{subnet.ID()}, false) 365 c.Assert(err, jc.ErrorIsNil) 366 367 subnetInfo.SpaceName = "testme" 368 _, err = s.State.AddSpace(names.NewSpaceTag(subnetInfo.SpaceName).Id(), "2", []string{}, false) 369 c.Assert(err, jc.ErrorIsNil) 370 371 err = subnet.Update(subnetInfo) 372 c.Assert(err, jc.ErrorIsNil) 373 374 err = subnet.Refresh() 375 c.Assert(err, jc.ErrorIsNil) 376 c.Assert(subnet.SpaceName(), gc.Equals, subnetInfo.SpaceName) 377 } 378 379 func (s *SubnetSuite) TestUpdateEmpty(c *gc.C) { 380 subnetInfo := network.SubnetInfo{CIDR: "8.8.8.0/24"} 381 subnet, err := s.State.AddSubnet(subnetInfo) 382 c.Assert(err, jc.ErrorIsNil) 383 384 subnetInfo.VLANTag = 76 385 subnetInfo.AvailabilityZones = []string{"testme-az"} 386 subnetInfo.SpaceName = "testme" 387 _, err = s.State.AddSpace(names.NewSpaceTag(subnetInfo.SpaceName).Id(), "2", []string{}, false) 388 c.Assert(err, jc.ErrorIsNil) 389 390 err = subnet.Update(subnetInfo) 391 c.Assert(err, jc.ErrorIsNil) 392 393 err = subnet.Refresh() 394 c.Assert(err, jc.ErrorIsNil) 395 c.Assert(subnet.VLANTag(), gc.Equals, subnetInfo.VLANTag) 396 c.Assert(subnet.AvailabilityZones(), gc.DeepEquals, subnetInfo.AvailabilityZones) 397 c.Assert(subnet.SpaceName(), gc.Equals, subnetInfo.SpaceName) 398 } 399 400 func (s *SubnetSuite) TestUpdateNonEmpty(c *gc.C) { 401 expectedSubnetInfo := network.SubnetInfo{ 402 CIDR: "8.8.8.0/24", VLANTag: 42, AvailabilityZones: []string{"changeme-az", "testme-az"}} 403 subnet, err := s.State.AddSubnet(expectedSubnetInfo) 404 c.Assert(err, jc.ErrorIsNil) 405 406 expectedSpace, err := s.State.AddSpace("changeme", "2", []string{subnet.ID()}, false) 407 c.Assert(err, jc.ErrorIsNil) 408 409 newSubnetInfo := network.SubnetInfo{ 410 CIDR: subnet.CIDR(), 411 SpaceName: "testme", 412 VLANTag: 76, 413 AvailabilityZones: []string{"testme-az"}, 414 } 415 _, err = s.State.AddSpace(names.NewSpaceTag(newSubnetInfo.SpaceName).Id(), "7", []string{}, false) 416 c.Assert(err, jc.ErrorIsNil) 417 418 err = subnet.Update(newSubnetInfo) 419 c.Assert(err, jc.ErrorIsNil) 420 421 err = subnet.Refresh() 422 c.Assert(err, jc.ErrorIsNil) 423 c.Assert(subnet.SpaceID(), gc.Equals, expectedSpace.Id()) 424 c.Assert(subnet.VLANTag(), gc.Equals, expectedSubnetInfo.VLANTag) 425 c.Assert(subnet.AvailabilityZones(), gc.DeepEquals, expectedSubnetInfo.AvailabilityZones) 426 } 427 428 func (s *SubnetSuite) TestUniqueAdditionAndRetrievalByCIDR(c *gc.C) { 429 cidr := "1.1.1.1/24" 430 431 sub1 := network.SubnetInfo{ 432 CIDR: cidr, 433 ProviderId: "1", 434 ProviderNetworkId: "1", 435 } 436 _, err := s.State.AddSubnet(sub1) 437 c.Assert(err, jc.ErrorIsNil) 438 439 sub2 := network.SubnetInfo{ 440 CIDR: cidr, 441 ProviderId: "2", 442 ProviderNetworkId: "2", 443 } 444 _, err = s.State.AddSubnet(sub2) 445 c.Assert(err, jc.ErrorIsNil) 446 447 subs, err := s.State.SubnetsByCIDR(cidr) 448 c.Assert(err, jc.ErrorIsNil) 449 c.Check(subs, gc.HasLen, 2) 450 451 _, err = s.State.SubnetByCIDR(cidr) 452 c.Check(err, gc.ErrorMatches, fmt.Sprintf("multiple subnets matching %q", cidr)) 453 } 454 455 func (s *SubnetSuite) TestUpdateSubnetSpaceOps(c *gc.C) { 456 space, err := s.State.AddSpace("space-0", "0", []string{}, false) 457 c.Assert(err, jc.ErrorIsNil) 458 459 arg := network.SubnetInfo{ 460 CIDR: "10.10.10.0/24", 461 ProviderId: "1", 462 ProviderNetworkId: "1", 463 SpaceID: space.Id(), 464 } 465 466 sub, err := s.State.AddSubnet(arg) 467 c.Assert(err, jc.ErrorIsNil) 468 c.Check(s.State.UpdateSubnetSpaceOps(sub.ID(), space.Id()), gc.IsNil) 469 470 ops := s.State.UpdateSubnetSpaceOps(sub.ID(), "666") 471 c.Assert(ops, gc.HasLen, 2) 472 for _, op := range ops { 473 if op.C == "spaces" { 474 c.Check(op.Id, gc.Equals, fmt.Sprintf("%s:666", s.State.ModelUUID())) 475 c.Check(op.Assert, gc.DeepEquals, txn.DocExists) 476 } else if op.C == "subnets" { 477 c.Check(op.Id, gc.Equals, fmt.Sprintf("%s:%s", s.State.ModelUUID(), sub.ID())) 478 c.Check(op.Update, gc.DeepEquals, bson.D{{"$set", bson.D{{"space-id", "666"}}}}) 479 c.Check(op.Assert, gc.DeepEquals, bson.D{{"life", state.Alive}}) 480 } else { 481 c.Fatalf("unexpected txn.Op collection: %q", op.C) 482 } 483 } 484 }