github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/application_ports.go (about) 1 // Copyright 2022 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "github.com/juju/collections/set" 8 "github.com/juju/collections/transform" 9 "github.com/juju/errors" 10 "github.com/juju/mgo/v3" 11 "github.com/juju/mgo/v3/bson" 12 "github.com/juju/mgo/v3/txn" 13 jujutxn "github.com/juju/txn/v3" 14 15 "github.com/juju/juju/core/network" 16 ) 17 18 // applicationPortRangesDoc represents the state of ports opened for an application. 19 type applicationPortRangesDoc struct { 20 DocID string `bson:"_id"` 21 ModelUUID string `bson:"model-uuid"` 22 ApplicationName string `bson:"application-name"` 23 24 // PortRanges is the application port ranges that are open for all units. 25 PortRanges network.GroupedPortRanges `bson:"port-ranges"` 26 UnitRanges map[string]network.GroupedPortRanges `bson:"unit-port-ranges"` 27 TxnRevno int64 `bson:"txn-revno"` 28 } 29 30 func newApplicationPortRangesDoc(docID, modelUUID, appName string) applicationPortRangesDoc { 31 return applicationPortRangesDoc{ 32 DocID: docID, 33 ModelUUID: modelUUID, 34 ApplicationName: appName, 35 UnitRanges: make(map[string]network.GroupedPortRanges), 36 } 37 } 38 39 var _ ApplicationPortRanges = (*applicationPortRanges)(nil) 40 41 type applicationPortRanges struct { 42 st *State 43 doc applicationPortRangesDoc 44 45 // docExists is false if the port range doc has not yet been persisted 46 // to the backing store. 47 docExists bool 48 49 // The set of pending port ranges that have not yet been persisted. 50 pendingOpenRanges network.GroupedPortRanges 51 pendingCloseRanges network.GroupedPortRanges 52 } 53 54 // Changes returns a ModelOperation for applying any changes that were made to 55 // this port range instance. 56 func (p *applicationPortRanges) Changes() ModelOperation { 57 // The application scope opened port range is not implemented yet. 58 // We manage(open/close) ports by units using "unitPortRanges.Open|Close|Changes()". 59 return nil 60 } 61 62 // Persisted returns true if the underlying document for this instance exists 63 // in the database. 64 func (p *applicationPortRanges) Persisted() bool { 65 return p.docExists 66 } 67 68 // ForUnit returns the set of port ranges opened by the specified unit. 69 func (p *applicationPortRanges) ForUnit(unitName string) UnitPortRanges { 70 return &unitPortRanges{ 71 unitName: unitName, 72 apg: p, 73 } 74 } 75 76 // ByUnit returns the set of port ranges opened by each unit grouped by unit name. 77 func (p *applicationPortRanges) ByUnit() map[string]UnitPortRanges { 78 if len(p.doc.UnitRanges) == 0 { 79 return nil 80 } 81 res := make(map[string]UnitPortRanges) 82 for unitName := range p.doc.UnitRanges { 83 res[unitName] = newUnitPortRanges(unitName, p) 84 } 85 return res 86 } 87 88 // ByEndpoint returns the list of open port ranges grouped by endpoint. 89 func (p *applicationPortRanges) ByEndpoint() network.GroupedPortRanges { 90 out := make(network.GroupedPortRanges) 91 for _, gpg := range p.doc.UnitRanges { 92 for endpoint, prs := range gpg { 93 out[endpoint] = append(out[endpoint], prs...) 94 } 95 } 96 return out 97 } 98 99 // UniquePortRanges returns a slice of unique open PortRanges all units. 100 func (p *applicationPortRanges) UniquePortRanges() []network.PortRange { 101 allRanges := make(network.GroupedPortRanges) 102 for _, unitRanges := range p.ByUnit() { 103 allRanges[""] = append(allRanges[""], unitRanges.UniquePortRanges()...) 104 } 105 return allRanges.UniquePortRanges() 106 } 107 108 func (p *applicationPortRanges) clearPendingRecords() { 109 p.pendingOpenRanges = make(network.GroupedPortRanges) 110 p.pendingCloseRanges = make(network.GroupedPortRanges) 111 } 112 113 // ApplicationName returns the application name associated with this set of port ranges. 114 func (p *applicationPortRanges) ApplicationName() string { 115 return p.doc.ApplicationName 116 } 117 118 func (p *applicationPortRanges) Remove() error { 119 doc := &applicationPortRanges{st: p.st, doc: p.doc, docExists: p.docExists} 120 buildTxn := func(attempt int) ([]txn.Op, error) { 121 if attempt > 0 { 122 err := doc.Refresh() 123 if errors.Is(err, errors.NotFound) { 124 return nil, jujutxn.ErrNoOperations 125 } else if err != nil { 126 return nil, errors.Trace(err) 127 } 128 } 129 return doc.removeOps(), nil 130 } 131 if err := p.st.db().Run(buildTxn); err != nil { 132 return errors.Trace(err) 133 } 134 135 p.docExists = false 136 return nil 137 } 138 139 // Refresh refreshes the port document from state. 140 func (p *applicationPortRanges) Refresh() error { 141 openedPorts, closer := p.st.db().GetCollection(openedPortsC) 142 defer closer() 143 144 err := openedPorts.FindId(p.doc.DocID).One(&p.doc) 145 if err == mgo.ErrNotFound { 146 p.docExists = false 147 return errors.NotFoundf("open port ranges for application %q", p.ApplicationName()) 148 } 149 if err != nil { 150 return errors.Annotatef(err, "refresh open port ranges for application %q", p.ApplicationName()) 151 } 152 p.docExists = true 153 return nil 154 } 155 156 func (p *applicationPortRanges) removeOps() []txn.Op { 157 if !p.docExists { 158 return nil 159 } 160 return []txn.Op{{ 161 C: openedPortsC, 162 Id: p.doc.DocID, 163 Assert: txn.DocExists, 164 Remove: true, 165 }} 166 } 167 168 var _ UnitPortRanges = (*unitPortRanges)(nil) 169 170 type unitPortRanges struct { 171 unitName string 172 apg *applicationPortRanges 173 } 174 175 func newUnitPortRanges(unitName string, apg *applicationPortRanges) UnitPortRanges { 176 return &unitPortRanges{ 177 unitName: unitName, 178 apg: apg, 179 } 180 } 181 182 // UnitName returns the unit name associated with this set of ports. 183 func (p *unitPortRanges) UnitName() string { 184 return p.unitName 185 } 186 187 // Open records a request for opening a particular port range for the specified 188 // endpoint. 189 func (p *unitPortRanges) Open(endpoint string, portRange network.PortRange) { 190 if p.apg.pendingOpenRanges == nil { 191 p.apg.pendingOpenRanges = make(network.GroupedPortRanges) 192 } 193 194 p.apg.pendingOpenRanges[endpoint] = append(p.apg.pendingOpenRanges[endpoint], portRange) 195 } 196 197 // Close records a request for closing a particular port range for the 198 // specified endpoint. 199 func (p *unitPortRanges) Close(endpoint string, portRange network.PortRange) { 200 if p.apg.pendingCloseRanges == nil { 201 p.apg.pendingCloseRanges = make(network.GroupedPortRanges) 202 } 203 204 p.apg.pendingCloseRanges[endpoint] = append(p.apg.pendingCloseRanges[endpoint], portRange) 205 } 206 207 // Changes returns a ModelOperation for applying any changes that were made to 208 // this port range instance. 209 func (p *unitPortRanges) Changes() ModelOperation { 210 return newApplicationPortRangesOperation(p.apg, p.unitName) 211 } 212 213 // UniquePortRanges returns a slice of unique open PortRanges for the unit. 214 func (p *unitPortRanges) UniquePortRanges() []network.PortRange { 215 allRanges := p.apg.doc.UnitRanges[p.unitName].UniquePortRanges() 216 network.SortPortRanges(allRanges) 217 return allRanges 218 } 219 220 // ByEndpoint returns the list of open port ranges grouped by endpoint. 221 func (p *unitPortRanges) ByEndpoint() network.GroupedPortRanges { 222 return p.apg.doc.UnitRanges[p.unitName] 223 } 224 225 // ForEndpoint returns a list of port ranges that the unit has opened for the 226 // specified endpoint. 227 func (p *unitPortRanges) ForEndpoint(endpointName string) []network.PortRange { 228 unitPortRange := p.apg.doc.UnitRanges[p.unitName] 229 if len(unitPortRange) == 0 || len(unitPortRange[endpointName]) == 0 { 230 return nil 231 } 232 res := append([]network.PortRange(nil), unitPortRange[endpointName]...) 233 network.SortPortRanges(res) 234 return res 235 } 236 237 var _ ModelOperation = (*applicationPortRangesOperation)(nil) 238 239 type applicationPortRangesOperation struct { 240 unitName string 241 apr *applicationPortRanges 242 updatedUnitPortRanges map[string]network.GroupedPortRanges 243 } 244 245 func newApplicationPortRangesOperation(apr *applicationPortRanges, unitName string) ModelOperation { 246 op := &applicationPortRangesOperation{apr: apr, unitName: unitName} 247 op.cloneExistingUnitPortRanges() 248 return op 249 } 250 251 func (op *applicationPortRangesOperation) cloneExistingUnitPortRanges() { 252 op.updatedUnitPortRanges = make(map[string]network.GroupedPortRanges) 253 for unitName, existingDoc := range op.apr.doc.UnitRanges { 254 newDoc := make(network.GroupedPortRanges) 255 for endpointName, portRanges := range existingDoc { 256 newDoc[endpointName] = append([]network.PortRange(nil), portRanges...) 257 } 258 op.updatedUnitPortRanges[unitName] = newDoc 259 } 260 } 261 262 func (op *applicationPortRangesOperation) Build(attempt int) ([]txn.Op, error) { 263 defer op.apr.clearPendingRecords() 264 265 if op.unitName == "" { 266 return nil, errors.NotValidf("empty unit name") 267 } 268 269 if err := checkModelNotDead(op.apr.st); err != nil { 270 return nil, errors.Annotate(err, "cannot open/close ports") 271 } 272 273 var createDoc = !op.apr.docExists 274 if attempt > 0 { 275 if err := op.apr.Refresh(); err != nil { 276 if !errors.Is(err, errors.NotFound) { 277 return nil, errors.Annotate(err, "cannot open/close ports") 278 } 279 280 // Doc not found; we need to create it. 281 createDoc = true 282 } 283 } 284 285 op.cloneExistingUnitPortRanges() 286 287 if err := op.validatePendingChanges(); err != nil { 288 return nil, errors.Annotate(err, "cannot open/close ports") 289 } 290 291 ops := []txn.Op{ 292 assertModelNotDeadOp(op.apr.st.ModelUUID()), 293 assertApplicationAliveOp(op.apr.st.docID(op.apr.ApplicationName())), 294 assertUnitNotDeadOp(op.apr.st, op.unitName), 295 } 296 297 portListModified, err := op.mergePendingOpenPortRanges() 298 if err != nil { 299 return nil, errors.Trace(err) 300 } 301 modified, err := op.mergePendingClosePortRanges() 302 if err != nil { 303 return nil, errors.Trace(err) 304 } 305 portListModified = portListModified || modified 306 307 if !portListModified || (createDoc && len(op.updatedUnitPortRanges) == 0) { 308 return nil, jujutxn.ErrNoOperations 309 } 310 311 if createDoc { 312 assert := txn.DocMissing 313 ops = append(ops, insertAppPortRangesDocOps(op.apr.st, &op.apr.doc, assert, op.updatedUnitPortRanges)...) 314 } else if len(op.updatedUnitPortRanges) == 0 { 315 // Port list is empty; get rid of ports document. 316 ops = append(ops, op.apr.removeOps()...) 317 } else { 318 assert := bson.D{ 319 {Name: "txn-revno", Value: op.apr.doc.TxnRevno}, 320 } 321 ops = append(ops, updateAppPortRangesDocOps(op.apr.st, &op.apr.doc, assert, op.updatedUnitPortRanges)...) 322 } 323 return ops, nil 324 } 325 326 func (op *applicationPortRangesOperation) mergePendingOpenPortRanges() (bool, error) { 327 var modified bool 328 for endpointName, pendingRanges := range op.apr.pendingOpenRanges { 329 for _, pendingRange := range pendingRanges { 330 if op.rangeExistsForEndpoint(endpointName, pendingRange) { 331 // Exists, no op for opening. 332 continue 333 } 334 op.addPortRanges(endpointName, true, pendingRange) 335 modified = true 336 } 337 } 338 return modified, nil 339 } 340 341 func (op *applicationPortRangesOperation) mergePendingClosePortRanges() (bool, error) { 342 var modified bool 343 for endpointName, pendingRanges := range op.apr.pendingCloseRanges { 344 for _, pendingRange := range pendingRanges { 345 if !op.rangeExistsForEndpoint(endpointName, pendingRange) { 346 // Not exists, no op for closing. 347 continue 348 } 349 modified = op.removePortRange(endpointName, pendingRange) 350 } 351 } 352 return modified, nil 353 } 354 355 func (op *applicationPortRangesOperation) addPortRanges(endpointName string, merge bool, portRanges ...network.PortRange) { 356 if op.updatedUnitPortRanges[op.unitName] == nil { 357 op.updatedUnitPortRanges[op.unitName] = make(network.GroupedPortRanges) 358 } 359 if !merge { 360 op.updatedUnitPortRanges[op.unitName][endpointName] = portRanges 361 return 362 } 363 op.updatedUnitPortRanges[op.unitName][endpointName] = append(op.updatedUnitPortRanges[op.unitName][endpointName], portRanges...) 364 } 365 366 func (op *applicationPortRangesOperation) removePortRange(endpointName string, portRange network.PortRange) bool { 367 if op.updatedUnitPortRanges[op.unitName] == nil || op.updatedUnitPortRanges[op.unitName][endpointName] == nil { 368 return false 369 } 370 var modified bool 371 existingRanges := op.updatedUnitPortRanges[op.unitName][endpointName] 372 for i, v := range existingRanges { 373 if v != portRange { 374 continue 375 } 376 existingRanges = append(existingRanges[:i], existingRanges[i+1:]...) 377 if len(existingRanges) == 0 { 378 delete(op.updatedUnitPortRanges[op.unitName], endpointName) 379 } else { 380 op.addPortRanges(endpointName, false, existingRanges...) 381 } 382 modified = true 383 } 384 return modified 385 } 386 387 func (op *applicationPortRangesOperation) rangeExistsForEndpoint(endpointName string, portRange network.PortRange) bool { 388 // For k8s applications, no endpoint level portrange supported currently. 389 // There is only one endpoint(which is empty string - ""). 390 if len(op.updatedUnitPortRanges[op.unitName][endpointName]) == 0 { 391 return false 392 } 393 394 for _, existingRange := range op.updatedUnitPortRanges[op.unitName][endpointName] { 395 if existingRange == portRange { 396 return true 397 } 398 } 399 return false 400 } 401 402 func (op *applicationPortRangesOperation) getEndpointBindings() (set.Strings, error) { 403 appEndpoints := set.NewStrings() 404 endpointToSpaceIDMap, _, err := readEndpointBindings(op.apr.st, applicationGlobalKey(op.apr.ApplicationName())) 405 if errors.Is(err, errors.NotFound) { 406 return appEndpoints, nil 407 } 408 if err != nil { 409 return appEndpoints, errors.Trace(err) 410 } 411 for endpointName := range endpointToSpaceIDMap { 412 if endpointName == "" { 413 continue 414 } 415 appEndpoints.Add(endpointName) 416 } 417 return appEndpoints, nil 418 } 419 420 func (op *applicationPortRangesOperation) validatePendingChanges() error { 421 endpointsNames, err := op.getEndpointBindings() 422 if err != nil { 423 return errors.Trace(err) 424 } 425 426 for endpointName := range op.apr.pendingOpenRanges { 427 if len(endpointName) > 0 && !endpointsNames.Contains(endpointName) { 428 return errors.NotFoundf("open port range: endpoint %q for application %q", endpointName, op.apr.ApplicationName()) 429 } 430 } 431 for endpointName := range op.apr.pendingCloseRanges { 432 if len(endpointName) > 0 && !endpointsNames.Contains(endpointName) { 433 return errors.NotFoundf("close port range: endpoint %q for application %q", endpointName, op.apr.ApplicationName()) 434 } 435 } 436 return nil 437 } 438 439 // Done implements ModelOperation. 440 func (op *applicationPortRangesOperation) Done(err error) error { 441 if err != nil { 442 return err 443 } 444 // Document has been persisted to state. 445 op.apr.docExists = true 446 op.apr.doc.UnitRanges = op.updatedUnitPortRanges 447 448 op.apr.pendingOpenRanges = nil 449 op.apr.pendingCloseRanges = nil 450 return nil 451 } 452 453 func insertAppPortRangesDocOps( 454 st *State, doc *applicationPortRangesDoc, asserts interface{}, unitRanges map[string]network.GroupedPortRanges, 455 ) (o []txn.Op) { 456 // As the following insert operation might be rolled back, we should 457 // not mutate our internal doc but instead work on a copy of the 458 // applicationPortRangesDoc. 459 docCopy := new(applicationPortRangesDoc) 460 *docCopy = *doc 461 docCopy.UnitRanges = unitRanges 462 return []txn.Op{ 463 { 464 C: openedPortsC, 465 Id: docCopy.DocID, 466 Assert: asserts, 467 Insert: docCopy, 468 }, 469 } 470 } 471 472 func updateAppPortRangesDocOps( 473 st *State, doc *applicationPortRangesDoc, asserts interface{}, unitRanges map[string]network.GroupedPortRanges, 474 ) (o []txn.Op) { 475 return []txn.Op{ 476 { 477 C: openedPortsC, 478 Id: doc.DocID, 479 Assert: asserts, 480 Update: bson.D{{Name: "$set", Value: bson.D{{Name: "unit-port-ranges", Value: unitRanges}}}}, 481 }, 482 } 483 } 484 485 func removeApplicationPortsForUnitOps(st *State, unit *Unit) ([]txn.Op, error) { 486 unitName := unit.Name() 487 appPortRanges, err := getApplicationPortRanges(st, unit.ApplicationName()) 488 if err != nil { 489 return nil, errors.Trace(err) 490 } 491 if !appPortRanges.docExists { 492 return nil, nil 493 } 494 if appPortRanges.doc.UnitRanges == nil || appPortRanges.doc.UnitRanges[unitName] == nil { 495 // No entry for the unit; nothing to do here. 496 return nil, nil 497 } 498 // Drop unit rules and write the doc back if non-empty or remove it if empty. 499 delete(appPortRanges.doc.UnitRanges, unitName) 500 if len(appPortRanges.doc.UnitRanges) != 0 { 501 assert := bson.D{{"txn-revno", appPortRanges.doc.TxnRevno}} 502 return updateAppPortRangesDocOps(st, &appPortRanges.doc, assert, appPortRanges.doc.UnitRanges), nil 503 } 504 return appPortRanges.removeOps(), nil 505 } 506 507 // getApplicationPortRanges attempts to retrieve the set of opened ports for 508 // a particular embedded k8s application. If the underlying document does not exist, a blank 509 // applicationPortRanges instance with the docExists flag set to false will be 510 // returned instead. 511 func getApplicationPortRanges(st *State, appName string) (*applicationPortRanges, error) { 512 openedPorts, closer := st.db().GetCollection(openedPortsC) 513 defer closer() 514 515 docID := st.docID(applicationGlobalKey(appName)) 516 var doc applicationPortRangesDoc 517 if err := openedPorts.FindId(docID).One(&doc); err != nil { 518 if err != mgo.ErrNotFound { 519 return nil, errors.Annotatef(err, "cannot get opened port ranges for application %q", appName) 520 } 521 return &applicationPortRanges{ 522 st: st, 523 doc: newApplicationPortRangesDoc(docID, st.ModelUUID(), appName), 524 docExists: false, 525 }, nil 526 } 527 528 return &applicationPortRanges{ 529 st: st, 530 doc: doc, 531 docExists: true, 532 }, nil 533 } 534 535 // getOpenedApplicationPortRanges attempts to retrieve the set of opened ports for 536 // a particular embedded k8s unit. If the underlying document does not exist, a blank 537 // unitPortRanges instance with the docExists flag set to false will be 538 // returned instead. 539 func getUnitPortRanges(st *State, appName, unitName string) (*unitPortRanges, error) { 540 apg, err := getApplicationPortRanges(st, appName) 541 if err != nil { 542 return nil, errors.Trace(err) 543 } 544 return &unitPortRanges{unitName: unitName, apg: apg}, nil 545 } 546 547 // OpenedPortRangesForAllApplications returns a slice of opened port ranges for all 548 // applications managed by this model. 549 func (m *Model) OpenedPortRangesForAllApplications() ([]ApplicationPortRanges, error) { 550 mprResults, err := getOpenedApplicationPortRangesForAllApplications(m.st) 551 if err != nil { 552 return nil, errors.Trace(err) 553 } 554 555 return transform.Slice(mprResults, func(agr *applicationPortRanges) ApplicationPortRanges { 556 return agr 557 }), nil 558 } 559 560 // getOpenedApplicationPortRangesForAllApplications is used for migration export. 561 func getOpenedApplicationPortRangesForAllApplications(st *State) ([]*applicationPortRanges, error) { 562 apps, err := st.AllApplications() 563 if err != nil { 564 return nil, errors.Trace(err) 565 } 566 var appNames []string 567 for _, app := range apps { 568 appNames = append(appNames, app.Name()) 569 } 570 openedPorts, closer := st.db().GetCollection(openedPortsC) 571 defer closer() 572 docs := []applicationPortRangesDoc{} 573 err = openedPorts.Find(bson.D{{"application-name", bson.D{{"$in", appNames}}}}).All(&docs) 574 if err != nil { 575 return nil, errors.Trace(err) 576 } 577 results := make([]*applicationPortRanges, len(docs)) 578 for i, doc := range docs { 579 results[i] = &applicationPortRanges{ 580 st: st, 581 doc: doc, 582 docExists: true, 583 } 584 } 585 return results, nil 586 }