github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/spaces.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 "net" 9 "strconv" 10 "strings" 11 12 "github.com/juju/collections/set" 13 "github.com/juju/errors" 14 "github.com/juju/mgo/v3" 15 "github.com/juju/mgo/v3/bson" 16 "github.com/juju/mgo/v3/txn" 17 "github.com/juju/names/v5" 18 19 "github.com/juju/juju/core/network" 20 stateerrors "github.com/juju/juju/state/errors" 21 ) 22 23 // Space represents the state of a juju network space. 24 type Space struct { 25 st *State 26 doc spaceDoc 27 } 28 29 type spaceDoc struct { 30 DocId string `bson:"_id"` 31 Id string `bson:"spaceid"` 32 Life Life `bson:"life"` 33 Name string `bson:"name"` 34 IsPublic bool `bson:"is-public"` 35 ProviderId string `bson:"providerid,omitempty"` 36 } 37 38 // Id returns the space ID. 39 func (s *Space) Id() string { 40 return s.doc.Id 41 } 42 43 // Life returns whether the space is Alive, Dying or Dead. 44 func (s *Space) Life() Life { 45 return s.doc.Life 46 } 47 48 // String implements fmt.Stringer. 49 func (s *Space) String() string { 50 return s.doc.Name 51 } 52 53 // Name returns the name of the Space. 54 func (s *Space) Name() string { 55 return s.doc.Name 56 } 57 58 // IsPublic returns whether the space is public or not. 59 func (s *Space) IsPublic() bool { 60 return s.doc.IsPublic 61 } 62 63 // ProviderId returns the provider id of the space. This will be the empty 64 // string except on substrates that directly support spaces. 65 func (s *Space) ProviderId() network.Id { 66 return network.Id(s.doc.ProviderId) 67 } 68 69 // NetworkSpace maps the space fields into a network.SpaceInfo. 70 // This method materialises subnets for each call. 71 // If calling multiple times, consider using AllSpaceInfos and filtering 72 // in-place. 73 func (s *Space) NetworkSpace() (network.SpaceInfo, error) { 74 subs, err := s.st.AllSubnetInfos() 75 if err != nil { 76 return network.SpaceInfo{}, errors.Trace(err) 77 } 78 79 space, err := s.networkSpace(subs) 80 return space, errors.Trace(err) 81 } 82 83 // networkSpace transforms a Space into a network.SpaceInfo using the 84 // materialised subnet information. 85 func (s *Space) networkSpace(subnets network.SubnetInfos) (network.SpaceInfo, error) { 86 spaceSubs, err := subnets.GetBySpaceID(s.Id()) 87 if err != nil { 88 return network.SpaceInfo{}, errors.Trace(err) 89 } 90 91 for i := range spaceSubs { 92 spaceSubs[i].SpaceID = s.Id() 93 spaceSubs[i].SpaceName = s.Name() 94 spaceSubs[i].ProviderSpaceId = s.ProviderId() 95 } 96 97 return network.SpaceInfo{ 98 ID: s.Id(), 99 Name: network.SpaceName(s.Name()), 100 ProviderId: s.ProviderId(), 101 Subnets: spaceSubs, 102 }, nil 103 } 104 105 // RemoveSpaceOps returns txn.Ops to remove the space 106 func (s *Space) RemoveSpaceOps() ([]txn.Op, error) { 107 spaceInfo, err := s.NetworkSpace() 108 if err != nil { 109 return nil, errors.Trace(err) 110 } 111 var ops []txn.Op 112 for _, subnet := range spaceInfo.Subnets { 113 ops = append(ops, s.st.UpdateSubnetSpaceOps(subnet.ID.String(), s.Id())...) 114 } 115 116 return append(ops, txn.Op{ 117 C: spacesC, 118 Id: s.doc.DocId, 119 Assert: txn.DocExists, 120 Remove: true, 121 }), nil 122 } 123 124 // RenameSpaceOps returns the database transaction operations required to 125 // rename the input space `fromName` to input `toName`. 126 func (s *Space) RenameSpaceOps(toName string) []txn.Op { 127 renameSpaceOps := []txn.Op{{ 128 C: spacesC, 129 Id: s.doc.DocId, 130 Update: bson.D{{"$set", bson.D{{"name", toName}}}}, 131 }} 132 return renameSpaceOps 133 } 134 135 // AddSpace creates and returns a new space. 136 func (st *State) AddSpace( 137 name string, providerId network.Id, subnetIDs []string, isPublic bool) (newSpace *Space, err error, 138 ) { 139 defer errors.DeferredAnnotatef(&err, "adding space %q", name) 140 if !names.IsValidSpace(name) { 141 return nil, errors.NewNotValid(nil, "invalid space name") 142 } 143 144 buildTxn := func(attempt int) ([]txn.Op, error) { 145 if _, err := st.SpaceByName(name); err != nil { 146 if !errors.IsNotFound(err) { 147 return nil, errors.Annotatef(err, "checking for existing space") 148 } 149 } else { 150 return nil, errors.AlreadyExistsf("space %q", name) 151 } 152 153 for _, subnetId := range subnetIDs { 154 subnet, err := st.Subnet(subnetId) 155 if err != nil { 156 return nil, errors.Trace(err) 157 } 158 if subnet.FanLocalUnderlay() != "" { 159 return nil, errors.Errorf( 160 "cannot set space for FAN subnet %q - it is always inherited from underlay", subnet.CIDR()) 161 } 162 } 163 164 // The ops will assert that the ID is unique, 165 // but we check explicitly in order to return an indicative error. 166 if providerId != "" { 167 exists, err := st.networkEntityGlobalKeyExists("space", providerId) 168 if err != nil { 169 return nil, errors.Trace(err) 170 } 171 if exists { 172 return nil, errors.Errorf("provider ID %q not unique", providerId) 173 } 174 } 175 176 ops, err := st.addSpaceWithSubnetsTxnOps(name, providerId, subnetIDs, isPublic) 177 return ops, errors.Trace(err) 178 } 179 180 err = st.db().Run(buildTxn) 181 if err != nil { 182 err = onAbort(err, stateerrors.ErrDead) 183 logger.Errorf("cannot add space to the model: %v", err) 184 return nil, errors.Trace(err) 185 } 186 187 space, err := st.SpaceByName(name) 188 return space, errors.Trace(err) 189 } 190 191 func (st *State) addSpaceWithSubnetsTxnOps( 192 name string, providerId network.Id, subnetIDs []string, isPublic bool, 193 ) ([]txn.Op, error) { 194 // Space with ID zero is the default space; start at 1. 195 seq, err := sequenceWithMin(st, "space", 1) 196 if err != nil { 197 return nil, err 198 } 199 id := strconv.Itoa(seq) 200 201 ops := st.addSpaceTxnOps(id, name, providerId, isPublic) 202 203 for _, subnetID := range subnetIDs { 204 // TODO:(mfoord) once we have refcounting for subnets we should 205 // also assert that the refcount is zero as moving the space of a 206 // subnet in use is not permitted. 207 ops = append(ops, txn.Op{ 208 C: subnetsC, 209 Id: subnetID, 210 Assert: bson.D{bson.DocElem{Name: "fan-local-underlay", Value: bson.D{{"$exists", false}}}}, 211 Update: bson.D{{"$set", bson.D{{"space-id", id}}}}, 212 }) 213 } 214 215 return ops, nil 216 } 217 218 func (st *State) addSpaceTxnOps(id, name string, providerId network.Id, isPublic bool) []txn.Op { 219 doc := spaceDoc{ 220 DocId: st.docID(id), 221 Id: id, 222 Life: Alive, 223 Name: name, 224 IsPublic: isPublic, 225 ProviderId: string(providerId), 226 } 227 228 ops := []txn.Op{{ 229 C: spacesC, 230 Id: doc.DocId, 231 Assert: txn.DocMissing, 232 Insert: doc, 233 }} 234 235 if providerId != "" { 236 ops = append(ops, st.networkEntityGlobalKeyOp("space", providerId)) 237 } 238 239 return ops 240 } 241 242 // Space returns a space from state that matches the input ID. 243 // An error is returned if the space does not exist or if there was a problem 244 // accessing its information. 245 func (st *State) Space(id string) (*Space, error) { 246 spaces, closer := st.db().GetCollection(spacesC) 247 defer closer() 248 249 var doc spaceDoc 250 err := spaces.Find(bson.M{"spaceid": id}).One(&doc) 251 if err == mgo.ErrNotFound { 252 return nil, errors.NotFoundf("space id %q", id) 253 } 254 if err != nil { 255 return nil, errors.Annotatef(err, "cannot get space id %q", id) 256 } 257 return &Space{st, doc}, nil 258 } 259 260 // SpaceByName returns a space from state that matches the input name. 261 // An error is returned if the space does not exist or if there was a problem 262 // accessing its information. 263 func (st *State) SpaceByName(name string) (*Space, error) { 264 spaces, closer := st.db().GetCollection(spacesC) 265 defer closer() 266 267 var doc spaceDoc 268 err := spaces.Find(bson.M{"name": name}).One(&doc) 269 if err == mgo.ErrNotFound { 270 return nil, errors.NotFoundf("space %q", name) 271 } 272 if err != nil { 273 return nil, errors.Annotatef(err, "cannot get space %q", name) 274 } 275 return &Space{st, doc}, nil 276 } 277 278 // AllSpaceInfos returns SpaceInfos for all spaces in the model. 279 func (st *State) AllSpaceInfos() (network.SpaceInfos, error) { 280 spaces, err := st.AllSpaces() 281 if err != nil { 282 return nil, errors.Trace(err) 283 } 284 285 subs, err := st.AllSubnetInfos() 286 if err != nil { 287 return nil, errors.Trace(err) 288 } 289 290 result := make(network.SpaceInfos, len(spaces)) 291 for i, space := range spaces { 292 if result[i], err = space.networkSpace(subs); err != nil { 293 return nil, err 294 } 295 } 296 return result, nil 297 } 298 299 // AllSpaces returns all spaces for the model. 300 func (st *State) AllSpaces() ([]*Space, error) { 301 spacesCollection, closer := st.db().GetCollection(spacesC) 302 defer closer() 303 304 var docs []spaceDoc 305 err := spacesCollection.Find(nil).All(&docs) 306 if err != nil { 307 return nil, errors.Annotatef(err, "cannot get all spaces") 308 } 309 spaces := make([]*Space, len(docs)) 310 for i, doc := range docs { 311 spaces[i] = &Space{st: st, doc: doc} 312 } 313 return spaces, nil 314 } 315 316 // EnsureDead sets the Life of the space to Dead, if it's Alive. If the space is 317 // already Dead, no error is returned. When the space is no longer Alive or 318 // already removed, errNotAlive is returned. 319 func (s *Space) EnsureDead() (err error) { 320 defer errors.DeferredAnnotatef(&err, "cannot set space %q to dead", s) 321 322 if s.doc.Life == Dead { 323 return nil 324 } 325 326 ops := []txn.Op{{ 327 C: spacesC, 328 Id: s.doc.DocId, 329 Update: bson.D{{"$set", bson.D{{"life", Dead}}}}, 330 Assert: isAliveDoc, 331 }} 332 333 txnErr := s.st.db().RunTransaction(ops) 334 if txnErr == nil { 335 s.doc.Life = Dead 336 return nil 337 } 338 return onAbort(txnErr, spaceNotAliveErr) 339 } 340 341 // Remove removes a Dead space. If the space is not Dead or it is already 342 // removed, an error is returned. 343 func (s *Space) Remove() (err error) { 344 defer errors.DeferredAnnotatef(&err, "cannot remove space %q", s) 345 346 if s.doc.Life != Dead { 347 return errors.New("space is not dead") 348 } 349 350 ops := []txn.Op{{ 351 C: spacesC, 352 Id: s.doc.Id, 353 Remove: true, 354 Assert: isDeadDoc, 355 }} 356 if s.ProviderId() != "" { 357 ops = append(ops, s.st.networkEntityGlobalKeyRemoveOp("space", s.ProviderId())) 358 } 359 360 txnErr := s.st.db().RunTransaction(ops) 361 if txnErr == nil { 362 return nil 363 } 364 return onAbort(txnErr, errors.New("not found or not dead")) 365 } 366 367 // Refresh refreshes the contents of the Space from the underlying state. It 368 // returns an error that satisfies errors.IsNotFound if the Space has been 369 // removed. 370 func (s *Space) Refresh() error { 371 spaces, closer := s.st.db().GetCollection(spacesC) 372 defer closer() 373 374 var doc spaceDoc 375 err := spaces.FindId(s.doc.Id).One(&doc) 376 if err == mgo.ErrNotFound { 377 return errors.NotFoundf("space %q", s) 378 } else if err != nil { 379 return errors.Errorf("cannot refresh space %q: %v", s, err) 380 } 381 s.doc = doc 382 return nil 383 } 384 385 // createDefaultSpaceOp returns a transaction operation 386 // that creates the default space (id=0). 387 func (st *State) createDefaultSpaceOp() txn.Op { 388 return txn.Op{ 389 C: spacesC, 390 Id: st.docID(network.AlphaSpaceId), 391 Assert: txn.DocMissing, 392 Insert: spaceDoc{ 393 Id: network.AlphaSpaceId, 394 Life: Alive, 395 Name: network.AlphaSpaceName, 396 IsPublic: true, 397 }, 398 } 399 } 400 401 func (st *State) getModelSubnets() (set.Strings, error) { 402 subnets, err := st.AllSubnets() 403 if err != nil { 404 return nil, errors.Trace(err) 405 } 406 modelSubnetIds := make(set.Strings) 407 for _, subnet := range subnets { 408 modelSubnetIds.Add(string(subnet.ProviderId())) 409 } 410 return modelSubnetIds, nil 411 } 412 413 // SaveProviderSubnets loads subnets into state. 414 // Currently it does not delete removed subnets. 415 func (st *State) SaveProviderSubnets(subnets []network.SubnetInfo, spaceID string) error { 416 modelSubnetIds, err := st.getModelSubnets() 417 if err != nil { 418 return errors.Trace(err) 419 } 420 421 for _, subnet := range subnets { 422 ip, _, err := net.ParseCIDR(subnet.CIDR) 423 if err != nil { 424 return errors.Trace(err) 425 } 426 if ip.IsInterfaceLocalMulticast() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() { 427 continue 428 } 429 430 subnet.SpaceID = spaceID 431 if modelSubnetIds.Contains(string(subnet.ProviderId)) { 432 err = st.SubnetUpdate(subnet) 433 } else { 434 _, err = st.AddSubnet(subnet) 435 } 436 437 if err != nil { 438 return errors.Trace(err) 439 } 440 } 441 442 // We process FAN subnets separately for clarity. 443 m, err := st.Model() 444 if err != nil { 445 return errors.Trace(err) 446 } 447 cfg, err := m.ModelConfig() 448 if err != nil { 449 return errors.Trace(err) 450 } 451 fans, err := cfg.FanConfig() 452 if err != nil { 453 return errors.Trace(err) 454 } 455 if len(fans) == 0 { 456 return nil 457 } 458 459 for _, subnet := range subnets { 460 for _, fan := range fans { 461 _, subnetNet, err := net.ParseCIDR(subnet.CIDR) 462 if err != nil { 463 return errors.Trace(err) 464 } 465 subnetWithDashes := strings.Replace(strings.Replace(subnetNet.String(), ".", "-", -1), "/", "-", -1) 466 id := fmt.Sprintf("%s-%s-%s", subnet.ProviderId, network.InFan, subnetWithDashes) 467 if modelSubnetIds.Contains(id) { 468 continue 469 } 470 if subnetNet.IP.To4() == nil { 471 logger.Debugf("%s address is not an IPv4 address.", subnetNet.IP) 472 continue 473 } 474 overlaySegment, err := network.CalculateOverlaySegment(subnet.CIDR, fan) 475 if err != nil { 476 return errors.Trace(err) 477 } 478 if overlaySegment != nil { 479 subnet.ProviderId = network.Id(id) 480 subnet.SpaceID = spaceID 481 subnet.SetFan(subnet.CIDR, fan.Overlay.String()) 482 subnet.CIDR = overlaySegment.String() 483 484 _, err := st.AddSubnet(subnet) 485 if err != nil { 486 return errors.Trace(err) 487 } 488 } 489 } 490 } 491 492 return nil 493 }