github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/subnets.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "strconv" 8 9 "github.com/juju/collections/set" 10 "github.com/juju/errors" 11 "github.com/juju/mgo/v3" 12 "github.com/juju/mgo/v3/bson" 13 "github.com/juju/mgo/v3/txn" 14 jujutxn "github.com/juju/txn/v3" 15 16 "github.com/juju/juju/core/network" 17 "github.com/juju/juju/mongo" 18 ) 19 20 type Subnet struct { 21 st *State 22 doc subnetDoc 23 // spaceID is either the space ID from the subnet's FanLocalUnderlay, 24 // or this subnet's space ID. 25 spaceID string 26 } 27 28 type subnetDoc struct { 29 DocID string `bson:"_id"` 30 TxnRevno int64 `bson:"txn-revno"` 31 ID string `bson:"subnet-id"` 32 ModelUUID string `bson:"model-uuid"` 33 Life Life `bson:"life"` 34 ProviderId string `bson:"providerid,omitempty"` 35 ProviderNetworkId string `bson:"provider-network-id,omitempty"` 36 CIDR string `bson:"cidr"` 37 VLANTag int `bson:"vlantag,omitempty"` 38 AvailabilityZones []string `bson:"availability-zones,omitempty"` 39 IsPublic bool `bson:"is-public,omitempty"` 40 SpaceID string `bson:"space-id,omitempty"` 41 FanLocalUnderlay string `bson:"fan-local-underlay,omitempty"` 42 FanOverlay string `bson:"fan-overlay,omitempty"` 43 } 44 45 // Life returns whether the subnet is Alive, Dying or Dead. 46 func (s *Subnet) Life() Life { 47 return s.doc.Life 48 } 49 50 // ID returns the unique ID for the subnet. 51 func (s *Subnet) ID() string { 52 return s.doc.ID 53 } 54 55 // String implements fmt.Stringer. 56 func (s *Subnet) String() string { 57 return s.CIDR() 58 } 59 60 // GoString implements fmt.GoStringer. 61 func (s *Subnet) GoString() string { 62 return s.String() 63 } 64 65 func (s *Subnet) FanOverlay() string { 66 return s.doc.FanOverlay 67 } 68 69 func (s *Subnet) FanLocalUnderlay() string { 70 return s.doc.FanLocalUnderlay 71 } 72 73 // IsPublic returns true if the subnet is public. 74 func (s *Subnet) IsPublic() bool { 75 return s.doc.IsPublic 76 } 77 78 // EnsureDead sets the Life of the subnet to Dead if it is Alive. 79 // If the subnet is already Dead, no error is returned. 80 // When the subnet is no longer Alive or already removed, 81 // errNotAlive is returned. 82 func (s *Subnet) EnsureDead() (err error) { 83 defer errors.DeferredAnnotatef(&err, "cannot set subnet %q to dead", s) 84 85 if s.doc.Life == Dead { 86 return nil 87 } 88 89 ops := []txn.Op{{ 90 C: subnetsC, 91 Id: s.doc.DocID, 92 Update: bson.D{{"$set", bson.D{{"life", Dead}}}}, 93 Assert: isAliveDoc, 94 }} 95 96 txnErr := s.st.db().RunTransaction(ops) 97 if txnErr == nil { 98 s.doc.Life = Dead 99 return nil 100 } 101 return onAbort(txnErr, subnetNotAliveErr) 102 } 103 104 // Remove removes a Dead subnet. If the subnet is not Dead or it is already 105 // removed, an error is returned. On success, all IP addresses added to the 106 // subnet are also removed. 107 func (s *Subnet) Remove() (err error) { 108 defer errors.DeferredAnnotatef(&err, "cannot remove subnet %q", s) 109 110 if s.doc.Life != Dead { 111 return errors.New("subnet is not dead") 112 } 113 114 ops := []txn.Op{{ 115 C: subnetsC, 116 Id: s.doc.DocID, 117 Remove: true, 118 Assert: isDeadDoc, 119 }} 120 if s.doc.ProviderId != "" { 121 op := s.st.networkEntityGlobalKeyRemoveOp("subnet", s.ProviderId()) 122 ops = append(ops, op) 123 } 124 125 txnErr := s.st.db().RunTransaction(ops) 126 if txnErr == nil { 127 return nil 128 } 129 return onAbort(txnErr, errors.New("not found or not dead")) 130 } 131 132 // ProviderId returns the provider-specific ID of the subnet. 133 func (s *Subnet) ProviderId() network.Id { 134 return network.Id(s.doc.ProviderId) 135 } 136 137 // CIDR returns the subnet CIDR (e.g. 192.168.50.0/24). 138 func (s *Subnet) CIDR() string { 139 return s.doc.CIDR 140 } 141 142 // VLANTag returns the subnet VLAN tag. It's a number between 1 and 143 // 4094 for VLANs and 0 if the network is not a VLAN. 144 func (s *Subnet) VLANTag() int { 145 return s.doc.VLANTag 146 } 147 148 // AvailabilityZones returns the availability zones of the subnet. 149 // If the subnet is not associated with an availability zones 150 // it will return an the empty slice. 151 func (s *Subnet) AvailabilityZones() []string { 152 return s.doc.AvailabilityZones 153 } 154 155 // SpaceName returns the space the subnet is associated with. If no 156 // space is associated, return the default space and log an error. 157 func (s *Subnet) SpaceName() string { 158 if s.spaceID == "" { 159 logger.Errorf("subnet %q has no spaceID", s.spaceID) 160 return network.AlphaSpaceName 161 } 162 sp, err := s.st.Space(s.spaceID) 163 if err != nil { 164 logger.Errorf("error finding space %q: %s", s.spaceID, err) 165 return network.AlphaSpaceName 166 } 167 return sp.Name() 168 } 169 170 // SpaceID returns the ID of the space the subnet is associated with. 171 // If the subnet is not associated with a space it will return 172 // network.AlphaSpaceId. 173 func (s *Subnet) SpaceID() string { 174 return s.spaceID 175 } 176 177 // UpdateSubnetSpaceOps returns operations that will ensure that 178 // the subnet is in the input space, provided the space exists. 179 func (st *State) UpdateSubnetSpaceOps(subnetID, spaceID string) []txn.Op { 180 if subnet, err := st.Subnet(subnetID); err == nil && subnet.SpaceID() == spaceID { 181 return nil 182 } 183 return []txn.Op{ 184 { 185 C: spacesC, 186 Id: st.docID(spaceID), 187 Assert: txn.DocExists, 188 }, 189 { 190 C: subnetsC, 191 Id: st.docID(subnetID), 192 Update: bson.D{{"$set", bson.D{{"space-id", spaceID}}}}, 193 Assert: isAliveDoc, 194 }, 195 } 196 } 197 198 // ProviderNetworkId returns the provider id of the network containing 199 // this subnet. 200 func (s *Subnet) ProviderNetworkId() network.Id { 201 return network.Id(s.doc.ProviderNetworkId) 202 } 203 204 // Refresh refreshes the contents of the Subnet from the underlying 205 // state. It an error that satisfies errors.IsNotFound if the SubnetByCIDR has 206 // been removed. 207 func (s *Subnet) Refresh() error { 208 subnets, closer := s.st.db().GetCollection(subnetsC) 209 defer closer() 210 211 err := subnets.FindId(s.doc.DocID).One(&s.doc) 212 if err == mgo.ErrNotFound { 213 return errors.NotFoundf("subnet %q", s) 214 } 215 if err != nil { 216 return errors.Errorf("cannot refresh subnet %q: %v", s, err) 217 } 218 if err = s.setSpace(subnets); err != nil { 219 return err 220 } 221 return nil 222 } 223 224 func (s *Subnet) setSpace(subnets mongo.Collection) error { 225 s.spaceID = s.doc.SpaceID 226 if s.doc.FanLocalUnderlay == "" { 227 return nil 228 } 229 if subnets == nil { 230 // Some callers have the mongo subnet collection already; some do not. 231 var closer SessionCloser 232 subnets, closer = s.st.db().GetCollection(subnetsC) 233 defer closer() 234 } 235 overlayDoc := &subnetDoc{} 236 // TODO: (hml) 2019-08-06 237 // Rethink the bson logic once multiple subnets can have the same cidr. 238 err := subnets.Find(bson.M{"cidr": s.doc.FanLocalUnderlay}).One(overlayDoc) 239 if err == mgo.ErrNotFound { 240 logger.Errorf("unable to update spaceID for subnet %q %q: underlay network %q: %s", 241 s.doc.ID, s.doc.CIDR, s.doc.FanLocalUnderlay, err.Error()) 242 return nil 243 } 244 if err != nil { 245 return errors.Annotatef(err, "underlay network %v for FAN %v", s.doc.FanLocalUnderlay, s.doc.CIDR) 246 } 247 s.spaceID = overlayDoc.SpaceID 248 return nil 249 } 250 251 // Update adds new info to the subnet based on input info. 252 // Currently no data is changed unless it is the "undefined" space from MAAS. 253 // There are restrictions on the additions allowed: 254 // - No change to CIDR; more work is required to determine how to handle. 255 // - No change to ProviderId nor ProviderNetworkID; these are immutable. 256 func (s *Subnet) Update(args network.SubnetInfo) error { 257 buildTxn := func(attempt int) ([]txn.Op, error) { 258 if attempt != 0 { 259 if err := s.Refresh(); err != nil { 260 if errors.IsNotFound(err) { 261 return nil, errors.Errorf("ProviderId %q not unique", args.ProviderId) 262 } 263 return nil, errors.Trace(err) 264 } 265 } 266 makeSpaceNameUpdate, err := s.updateSpaceName(args.SpaceName) 267 if err != nil { 268 return nil, err 269 } 270 var bsonSet bson.D 271 if makeSpaceNameUpdate { 272 // TODO (hml) 2019-07-25 273 // Update for SpaceID once SubnetInfo Updated 274 sp, err := s.st.SpaceByName(args.SpaceName) 275 if err != nil { 276 return nil, errors.Trace(err) 277 } 278 bsonSet = append(bsonSet, bson.DocElem{Name: "space-id", Value: sp.Id()}) 279 } 280 if len(args.AvailabilityZones) > 0 { 281 currentAZ := set.NewStrings(args.AvailabilityZones...) 282 newAZ := currentAZ.Difference(set.NewStrings(s.doc.AvailabilityZones...)) 283 if !newAZ.IsEmpty() { 284 bsonSet = append(bsonSet, 285 bson.DocElem{Name: "availability-zones", Value: append(s.doc.AvailabilityZones, newAZ.Values()...)}) 286 } 287 } 288 if s.doc.VLANTag == 0 && args.VLANTag > 0 { 289 bsonSet = append(bsonSet, bson.DocElem{Name: "vlantag", Value: args.VLANTag}) 290 } 291 if len(bsonSet) == 0 { 292 return nil, jujutxn.ErrNoOperations 293 } 294 return []txn.Op{{ 295 C: subnetsC, 296 Id: s.doc.DocID, 297 Assert: bson.D{{"txn-revno", s.doc.TxnRevno}}, 298 Update: bson.D{{"$set", bsonSet}}, 299 }}, nil 300 } 301 return errors.Trace(s.st.db().Run(buildTxn)) 302 } 303 304 func (s *Subnet) updateSpaceName(spaceName string) (bool, error) { 305 var spaceNameChange bool 306 sp, err := s.st.Space(s.doc.SpaceID) 307 switch { 308 case err != nil && !errors.IsNotFound(err): 309 return false, errors.Trace(err) 310 case errors.IsNotFound(err): 311 spaceNameChange = true 312 case err == nil: 313 // Only change space name it's a default one at this time. 314 // 315 // The undefined space from MAAS has a providerId of -1. 316 // The juju default space will be 0. 317 spaceNameChange = sp.doc.ProviderId == "-1" || sp.doc.Id == network.AlphaSpaceId 318 } 319 // TODO (hml) 2019-07-25 320 // Update when there is a s.doc.spaceID, which has done the calculation of 321 // ID from the CIDR or FAN. 322 return spaceNameChange && spaceName != "" && s.doc.FanLocalUnderlay == "", nil 323 } 324 325 // AllSubnetInfos returns SubnetInfos for all subnets in the model. 326 func (st *State) AllSubnetInfos() (network.SubnetInfos, error) { 327 subs, err := st.AllSubnets() 328 if err != nil { 329 return nil, errors.Trace(err) 330 } 331 332 result := make(network.SubnetInfos, len(subs)) 333 for i, sub := range subs { 334 result[i] = sub.networkSubnet() 335 } 336 return result, nil 337 } 338 339 // networkSubnet maps the subnet fields into a network.SubnetInfo. 340 // Note that this method should not be exported. 341 // It is only called on subnets that are guaranteed, if Fan overlays, 342 // to have had their space IDs correctly set based on their underlays. 343 // Calling it on an overlay not processed in this way will yield a 344 // space ID of "0", which may be incorrect. 345 func (s *Subnet) networkSubnet() network.SubnetInfo { 346 var fanInfo *network.FanCIDRs 347 if s.doc.FanLocalUnderlay != "" || s.doc.FanOverlay != "" { 348 fanInfo = &network.FanCIDRs{ 349 FanLocalUnderlay: s.doc.FanLocalUnderlay, 350 FanOverlay: s.doc.FanOverlay, 351 } 352 } 353 354 sInfo := network.SubnetInfo{ 355 ID: network.Id(s.doc.ID), 356 CIDR: s.doc.CIDR, 357 ProviderId: network.Id(s.doc.ProviderId), 358 ProviderNetworkId: network.Id(s.doc.ProviderNetworkId), 359 VLANTag: s.doc.VLANTag, 360 AvailabilityZones: s.doc.AvailabilityZones, 361 FanInfo: fanInfo, 362 IsPublic: s.doc.IsPublic, 363 SpaceID: s.spaceID, 364 Life: s.Life().Value(), 365 // SpaceName and ProviderSpaceID are populated by Space.NetworkSpace(). 366 // For now, we do not look them up here. 367 } 368 369 return sInfo 370 } 371 372 // SubnetUpdate adds new info to the subnet based on provided info. 373 func (st *State) SubnetUpdate(args network.SubnetInfo) error { 374 s, err := st.SubnetByCIDR(args.CIDR) 375 if err != nil { 376 return errors.Trace(err) 377 } 378 return s.Update(args) 379 } 380 381 // AddSubnet creates and returns a new subnet. 382 func (st *State) AddSubnet(args network.SubnetInfo) (subnet *Subnet, err error) { 383 defer errors.DeferredAnnotatef(&err, "adding subnet %q", args.CIDR) 384 385 if err := args.Validate(); err != nil { 386 return nil, errors.Trace(err) 387 } 388 389 var seq int 390 if seq, err = sequence(st, "subnet"); err != nil { 391 return nil, errors.Trace(err) 392 } 393 394 buildTxn := func(attempt int) ([]txn.Op, error) { 395 if attempt != 0 { 396 if err := checkModelActive(st); err != nil { 397 return nil, errors.Trace(err) 398 } 399 if _, err = st.Subnet(subnet.ID()); err == nil { 400 return nil, errors.AlreadyExistsf("subnet %q", args.CIDR) 401 } 402 if err := subnet.Refresh(); err != nil { 403 if errors.IsNotFound(err) { 404 return nil, errors.Errorf("provider ID %q not unique", args.ProviderId) 405 } 406 return nil, errors.Trace(err) 407 } 408 } 409 var ops []txn.Op 410 var subDoc subnetDoc 411 subDoc, ops, err = st.addSubnetOps(strconv.Itoa(seq), args) 412 if err != nil { 413 return nil, errors.Trace(err) 414 } 415 subnet = &Subnet{st: st, doc: subDoc} 416 ops = append(ops, assertModelActiveOp(st.ModelUUID())) 417 return ops, nil 418 } 419 err = st.db().Run(buildTxn) 420 if err != nil { 421 return nil, errors.Trace(err) 422 } 423 if err := subnet.setSpace(nil); err != nil { 424 return nil, errors.Trace(err) 425 } 426 return subnet, nil 427 } 428 429 // AddSubnetOps returns transaction operations required to ensure that the 430 // input subnet is added to state. 431 func (st *State) AddSubnetOps(args network.SubnetInfo) ([]txn.Op, error) { 432 seq, err := sequence(st, "subnet") 433 if err != nil { 434 return nil, errors.Trace(err) 435 } 436 437 _, ops, err := st.addSubnetOps(strconv.Itoa(seq), args) 438 return ops, errors.Trace(err) 439 } 440 441 func (st *State) addSubnetOps(id string, args network.SubnetInfo) (subnetDoc, []txn.Op, error) { 442 unique, err := st.uniqueSubnet(args.CIDR, string(args.ProviderId)) 443 if err != nil { 444 return subnetDoc{}, nil, errors.Trace(err) 445 } 446 if !unique { 447 return subnetDoc{}, nil, errors.AlreadyExistsf("subnet %q", args.CIDR) 448 } 449 450 // Unless explicitly placed, new subnets go into the alpha space. 451 // TODO (manadart 2020-08-12): We should determine the model's configured 452 // default space and put it there instead. 453 if args.SpaceID == "" { 454 args.SpaceID = network.AlphaSpaceId 455 } 456 457 subDoc := subnetDoc{ 458 DocID: st.docID(id), 459 ID: id, 460 ModelUUID: st.ModelUUID(), 461 Life: Alive, 462 CIDR: args.CIDR, 463 VLANTag: args.VLANTag, 464 ProviderId: string(args.ProviderId), 465 ProviderNetworkId: string(args.ProviderNetworkId), 466 AvailabilityZones: args.AvailabilityZones, 467 SpaceID: args.SpaceID, 468 FanLocalUnderlay: args.FanLocalUnderlay(), 469 FanOverlay: args.FanOverlay(), 470 IsPublic: args.IsPublic, 471 } 472 ops := []txn.Op{ 473 { 474 C: subnetsC, 475 Id: subDoc.DocID, 476 Assert: txn.DocMissing, 477 Insert: subDoc, 478 }, 479 } 480 if args.ProviderId != "" { 481 ops = append(ops, st.networkEntityGlobalKeyOp("subnet", args.ProviderId)) 482 } 483 return subDoc, ops, nil 484 } 485 486 func (st *State) uniqueSubnet(cidr, providerID string) (bool, error) { 487 subnets, closer := st.db().GetCollection(subnetsC) 488 defer closer() 489 490 pID := bson.D{{"providerid", providerID}} 491 if providerID == "" { 492 pID = bson.D{{"providerid", bson.D{{"$exists", false}}}} 493 } 494 495 count, err := subnets.Find( 496 bson.D{{"$and", 497 []bson.D{ 498 {{"cidr", cidr}}, 499 pID, 500 }, 501 }}).Count() 502 503 if err == mgo.ErrNotFound { 504 return false, errors.NotFoundf("subnet CIDR %q", cidr) 505 } 506 if err != nil { 507 return false, errors.Annotatef(err, "querying subnet CIDR %q", cidr) 508 } 509 return count == 0, nil 510 } 511 512 // Subnet returns the subnet identified by the input ID, 513 // or an error if it is not found. 514 func (st *State) Subnet(id string) (*Subnet, error) { 515 subnets, err := st.subnets(bson.M{"subnet-id": id}) 516 if err != nil { 517 return nil, errors.Annotatef(err, "retrieving subnet with ID %q", id) 518 } 519 if len(subnets) == 0 { 520 return nil, errors.NotFoundf("subnet %q", id) 521 } 522 return subnets[0], nil 523 } 524 525 // SubnetByCIDR returns a unique subnet matching the input CIDR. 526 // If no unique match is achieved, an error is returned. 527 // TODO (manadart 2020-03-11): As of this date, CIDR remains a unique 528 // identifier for a subnet due to how we constrain provider networking 529 // implementations. When this changes, callers relying on this method to return 530 // a unique match will need attention. 531 // Usage of this method should probably be phased out. 532 func (st *State) SubnetByCIDR(cidr string) (*Subnet, error) { 533 subnets, err := st.subnets(bson.M{"cidr": cidr}) 534 if err != nil { 535 return nil, errors.Annotatef(err, "retrieving subnet with CIDR %q", cidr) 536 } 537 if len(subnets) == 0 { 538 return nil, errors.NotFoundf("subnet %q", cidr) 539 } 540 if len(subnets) > 1 { 541 return nil, errors.Errorf("multiple subnets matching %q", cidr) 542 } 543 return subnets[0], nil 544 } 545 546 // SubnetsByCIDR returns the subnets matching the input CIDR. 547 func (st *State) SubnetsByCIDR(cidr string) ([]*Subnet, error) { 548 subnets, err := st.subnets(bson.M{"cidr": cidr}) 549 return subnets, errors.Annotatef(err, "retrieving subnets with CIDR %q", cidr) 550 } 551 552 func (st *State) subnets(exp bson.M) ([]*Subnet, error) { 553 col, closer := st.db().GetCollection(subnetsC) 554 defer closer() 555 556 var docs []subnetDoc 557 err := col.Find(exp).All(&docs) 558 if err != nil { 559 return nil, errors.Trace(err) 560 } 561 if len(docs) == 0 { 562 return nil, nil 563 } 564 565 subnets := make([]*Subnet, len(docs)) 566 for i, doc := range docs { 567 subnets[i] = &Subnet{st: st, doc: doc} 568 if err := subnets[i].setSpace(col); err != nil { 569 return nil, errors.Trace(err) 570 } 571 } 572 return subnets, nil 573 } 574 575 // AllSubnets returns all known subnets in the model. 576 func (st *State) AllSubnets() (subnets []*Subnet, err error) { 577 subnetsCollection, closer := st.db().GetCollection(subnetsC) 578 defer closer() 579 580 var docs []subnetDoc 581 err = subnetsCollection.Find(nil).All(&docs) 582 if err != nil { 583 return nil, errors.Annotatef(err, "cannot get all subnets") 584 } 585 cidrToSpace := make(map[string]string) 586 for _, doc := range docs { 587 cidrToSpace[doc.CIDR] = doc.SpaceID 588 } 589 for _, doc := range docs { 590 spaceID := doc.SpaceID 591 if doc.FanLocalUnderlay != "" { 592 if space, ok := cidrToSpace[doc.FanLocalUnderlay]; ok { 593 spaceID = space 594 } 595 } 596 subnets = append(subnets, &Subnet{st: st, doc: doc, spaceID: spaceID}) 597 } 598 return subnets, nil 599 }