github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/common/firewall/egressaddresswatcher.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package firewall 5 6 import ( 7 "github.com/juju/collections/set" 8 "github.com/juju/errors" 9 "github.com/juju/worker/v3" 10 "github.com/juju/worker/v3/catacomb" 11 12 "github.com/juju/juju/core/network" 13 "github.com/juju/juju/core/watcher" 14 ) 15 16 // EgressAddressWatcher reports changes to addresses 17 // for local units in a given relation. 18 // Each event contains the entire set of addresses which 19 // are required for ingress on the remote side of the relation. 20 type EgressAddressWatcher struct { 21 catacomb catacomb.Catacomb 22 23 backend State 24 appName string 25 rel Relation 26 27 out chan []string 28 29 // Channel for machineAddressWatchers to report individual machine 30 // updates. 31 addressChanges chan string 32 33 // A map of machine id to machine data. 34 machines map[string]*machineData 35 36 // A map of machine id by unit name - this is needed because we 37 // might not be able to retrieve the machine name when a unit 38 // leaves scope if it's been completely removed by the time we look. 39 unitToMachine map[string]string 40 41 // A map of known unit addresses, keyed on unit name. 42 known map[string]string 43 44 // A set of known egress cidrs for the model. 45 knownModelEgress set.Strings 46 47 // A set of known egress cidrs for the relation. 48 knownRelationEgress set.Strings 49 } 50 51 // machineData holds the information we track at the machine level. 52 type machineData struct { 53 units set.Strings 54 worker *machineAddressWorker 55 } 56 57 // NewEgressAddressWatcher creates an EgressAddressWatcher. 58 func NewEgressAddressWatcher(backend State, rel Relation, appName string) (*EgressAddressWatcher, error) { 59 w := &EgressAddressWatcher{ 60 backend: backend, 61 appName: appName, 62 rel: rel, 63 known: make(map[string]string), 64 out: make(chan []string), 65 addressChanges: make(chan string), 66 machines: make(map[string]*machineData), 67 unitToMachine: make(map[string]string), 68 knownModelEgress: set.NewStrings(), 69 } 70 err := catacomb.Invoke(catacomb.Plan{ 71 Site: &w.catacomb, 72 Work: w.loop, 73 }) 74 return w, err 75 } 76 77 func (w *EgressAddressWatcher) loop() error { 78 defer close(w.out) 79 80 ruw, err := w.rel.WatchUnits(w.appName) 81 if errors.IsNotFound(err) { 82 return nil 83 } 84 if err != nil { 85 return errors.Trace(err) 86 } 87 if err := w.catacomb.Add(ruw); err != nil { 88 return errors.Trace(err) 89 } 90 91 // TODO(wallyworld) - we just want to watch for egress 92 // address changes but right now can only watch for 93 // any model config change. 94 mw := w.backend.WatchForModelConfigChanges() 95 if err := w.catacomb.Add(mw); err != nil { 96 return errors.Trace(err) 97 } 98 99 rw := w.rel.WatchRelationEgressNetworks() 100 if err := w.catacomb.Add(rw); err != nil { 101 return errors.Trace(err) 102 } 103 104 var ( 105 changed bool 106 sentInitial bool 107 out chan<- []string 108 addresses set.Strings 109 lastAddresses set.Strings 110 addressesCIDR []string 111 ) 112 113 // Wait for each of the watchers started above to 114 // send an initial change before sending any changes 115 // from this watcher. 116 var haveInitialRelationUnits bool 117 var haveInitialRelationEgressNetworks bool 118 var haveInitialModelConfig bool 119 120 for { 121 var ready bool 122 if !sentInitial { 123 ready = haveInitialRelationUnits && haveInitialRelationEgressNetworks && haveInitialModelConfig 124 } 125 if ready || changed { 126 addresses = nil 127 if len(w.known) > 0 { 128 // Egress CIDRs, if configured, override unit 129 // machine addresses. Relation CIDRs take 130 // precedence over those specified in model 131 // config. 132 addresses = set.NewStrings(w.knownRelationEgress.Values()...) 133 if addresses.Size() == 0 { 134 addresses = set.NewStrings(w.knownModelEgress.Values()...) 135 } 136 if addresses.Size() == 0 { 137 // No user configured egress so just use the unit addresses. 138 for _, addr := range w.known { 139 addresses.Add(addr) 140 } 141 } 142 } 143 changed = false 144 if !setEquals(addresses, lastAddresses) { 145 addressesCIDR = network.SubnetsForAddresses(addresses.Values()) 146 ready = ready || sentInitial 147 } 148 } 149 if ready { 150 out = w.out 151 } 152 153 select { 154 case <-w.catacomb.Dying(): 155 return w.catacomb.ErrDying() 156 157 case out <- addressesCIDR: 158 sentInitial = true 159 lastAddresses = addresses 160 out = nil 161 162 case _, ok := <-mw.Changes(): 163 if !ok { 164 return w.catacomb.ErrDying() 165 } 166 cfg, err := w.backend.ModelConfig() 167 if err != nil { 168 return err 169 } 170 haveInitialModelConfig = true 171 egress := set.NewStrings(cfg.EgressSubnets()...) 172 if !setEquals(egress, w.knownModelEgress) { 173 logger.Debugf( 174 "model config egress subnets changed to %s (was %s)", 175 egress.SortedValues(), 176 w.knownModelEgress.SortedValues(), 177 ) 178 changed = true 179 w.knownModelEgress = egress 180 } 181 182 case changes, ok := <-rw.Changes(): 183 if !ok { 184 return w.catacomb.ErrDying() 185 } 186 haveInitialRelationEgressNetworks = true 187 egress := set.NewStrings(changes...) 188 if !setEquals(egress, w.knownRelationEgress) { 189 logger.Debugf( 190 "relation egress subnets changed to %s (was %s)", 191 egress.SortedValues(), 192 w.knownRelationEgress.SortedValues(), 193 ) 194 changed = true 195 w.knownRelationEgress = egress 196 } 197 198 case c, ok := <-ruw.Changes(): 199 if !ok { 200 return w.catacomb.ErrDying() 201 } 202 // A unit has entered or left scope. 203 // Get the new set of addresses resulting from that 204 // change, and if different to what we know, send the change. 205 haveInitialRelationUnits = true 206 addressesChanged, err := w.processUnitChanges(c) 207 if err != nil { 208 return err 209 } 210 changed = changed || addressesChanged 211 212 case machineId, ok := <-w.addressChanges: 213 if !ok { 214 continue 215 } 216 addressesChanged, err := w.processMachineAddresses(machineId) 217 if err != nil { 218 return errors.Trace(err) 219 } 220 changed = changed || addressesChanged 221 } 222 } 223 } 224 225 func (w *EgressAddressWatcher) unitAddress(unit Unit) (string, bool, error) { 226 addr, err := unit.PublicAddress() 227 if errors.IsNotAssigned(err) { 228 logger.Debugf("unit %s is not assigned to a machine, can't get address", unit.Name()) 229 return "", false, nil 230 } 231 if network.IsNoAddressError(err) { 232 logger.Debugf("unit %s has no public address", unit.Name()) 233 return "", false, nil 234 } 235 if err != nil { 236 return "", false, err 237 } 238 logger.Debugf("unit %q has public address %q", unit.Name(), addr.Value) 239 return addr.Value, true, nil 240 } 241 242 func (w *EgressAddressWatcher) processUnitChanges(c watcher.RelationUnitsChange) (bool, error) { 243 changed := false 244 for name := range c.Changed { 245 246 u, err := w.backend.Unit(name) 247 if errors.IsNotFound(err) { 248 continue 249 } 250 if err != nil { 251 return false, err 252 } 253 254 if err := w.trackUnit(u); err != nil { 255 return false, errors.Trace(err) 256 } 257 258 // We need to know whether to look at the public or cloud local address. 259 // For now, we'll use the public address and later if needed use a watcher 260 // parameter to look at the cloud local address. 261 addr, ok, err := w.unitAddress(u) 262 if err != nil { 263 return false, err 264 } 265 if !ok { 266 continue 267 } 268 if w.known[name] != addr { 269 w.known[name] = addr 270 changed = true 271 } 272 } 273 for _, name := range c.Departed { 274 if err := w.untrackUnit(name); err != nil { 275 return false, errors.Trace(err) 276 } 277 // If the unit is departing and we have seen its address, 278 // remove the address. 279 address, ok := w.known[name] 280 if !ok { 281 continue 282 } 283 delete(w.known, name) 284 285 // See if the address is still used by another unit. 286 inUse := false 287 for unit, addr := range w.known { 288 if name != unit && addr == address { 289 inUse = true 290 break 291 } 292 } 293 if !inUse { 294 changed = true 295 } 296 } 297 return changed, nil 298 } 299 300 func (w *EgressAddressWatcher) trackUnit(unit Unit) error { 301 machine, err := w.assignedMachine(unit) 302 if errors.IsNotAssigned(err) { 303 logger.Errorf("unit %q entered scope without a machine assigned - addresses will not be tracked", unit) 304 return nil 305 } 306 if err != nil { 307 return errors.Trace(err) 308 } 309 310 w.unitToMachine[unit.Name()] = machine.Id() 311 mData, ok := w.machines[machine.Id()] 312 if ok { 313 // We're already watching the machine, just add this unit. 314 mData.units.Add(unit.Name()) 315 return nil 316 } 317 318 addressWorker, err := newMachineAddressWorker(machine, w.addressChanges) 319 if err != nil { 320 return errors.Trace(err) 321 } 322 w.machines[machine.Id()] = &machineData{ 323 units: set.NewStrings(unit.Name()), 324 worker: addressWorker, 325 } 326 err = w.catacomb.Add(addressWorker) 327 if err != nil { 328 return errors.Trace(err) 329 } 330 return nil 331 } 332 333 func (w *EgressAddressWatcher) untrackUnit(unitName string) error { 334 machineId, ok := w.unitToMachine[unitName] 335 if !ok { 336 logger.Errorf("missing machine id for unit %q", unitName) 337 return nil 338 } 339 delete(w.unitToMachine, unitName) 340 341 mData, ok := w.machines[machineId] 342 if !ok { 343 logger.Debugf("missing machine data for machine %q (hosting unit %q)", machineId, unitName) 344 return nil 345 } 346 mData.units.Remove(unitName) 347 if mData.units.Size() > 0 { 348 // No need to stop the watcher - there are still units on the 349 // machine. 350 return nil 351 } 352 353 err := worker.Stop(mData.worker) 354 if err != nil { 355 return errors.Trace(err) 356 } 357 delete(w.machines, machineId) 358 return nil 359 } 360 361 func (w *EgressAddressWatcher) assignedMachine(unit Unit) (Machine, error) { 362 machineId, err := unit.AssignedMachineId() 363 if err != nil { 364 return nil, errors.Trace(err) 365 } 366 machine, err := w.backend.Machine(machineId) 367 if err != nil { 368 return nil, errors.Trace(err) 369 } 370 return machine, nil 371 } 372 373 func (w *EgressAddressWatcher) processMachineAddresses(machineId string) (changed bool, err error) { 374 mData, ok := w.machines[machineId] 375 if !ok { 376 return false, errors.Errorf("missing machineData for machine %q", machineId) 377 } 378 for unitName := range mData.units { 379 unit, err := w.backend.Unit(unitName) 380 if errors.IsNotFound(err) { 381 continue 382 } 383 if err != nil { 384 return false, errors.Trace(err) 385 } 386 address, _, err := w.unitAddress(unit) 387 if err != nil { 388 return false, errors.Trace(err) 389 } 390 existingAddress := w.known[unitName] 391 if existingAddress != address { 392 w.known[unitName] = address 393 changed = true 394 } 395 } 396 return changed, nil 397 } 398 399 // Changes returns the event channel for this watcher. 400 func (w *EgressAddressWatcher) Changes() <-chan []string { 401 return w.out 402 } 403 404 // Kill asks the watcher to stop without waiting for it do so. 405 func (w *EgressAddressWatcher) Kill() { 406 w.catacomb.Kill(nil) 407 } 408 409 // Wait waits for the watcher to die and returns any 410 // error encountered when it was running. 411 func (w *EgressAddressWatcher) Wait() error { 412 return w.catacomb.Wait() 413 } 414 415 // Stop kills the watcher, then waits for it to die. 416 func (w *EgressAddressWatcher) Stop() error { 417 w.Kill() 418 return w.Wait() 419 } 420 421 // Err returns any error encountered while the watcher 422 // has been running. 423 func (w *EgressAddressWatcher) Err() error { 424 return w.catacomb.Err() 425 } 426 427 func newMachineAddressWorker(machine Machine, out chan<- string) (*machineAddressWorker, error) { 428 w := &machineAddressWorker{ 429 machine: machine, 430 out: out, 431 } 432 err := catacomb.Invoke(catacomb.Plan{ 433 Site: &w.catacomb, 434 Work: w.loop, 435 }) 436 return w, errors.Trace(err) 437 } 438 439 // machineAddressWorker watches for machine address changes and 440 // notifies the dest channel when it sees them. 441 type machineAddressWorker struct { 442 catacomb catacomb.Catacomb 443 machine Machine 444 out chan<- string 445 } 446 447 func (w *machineAddressWorker) loop() error { 448 aw := w.machine.WatchAddresses() 449 if err := w.catacomb.Add(aw); err != nil { 450 return errors.Trace(err) 451 } 452 machineId := w.machine.Id() 453 var out chan<- string 454 for { 455 select { 456 case <-w.catacomb.Dying(): 457 return w.catacomb.ErrDying() 458 case <-aw.Changes(): 459 out = w.out 460 case out <- machineId: 461 out = nil 462 } 463 } 464 } 465 466 func (w *machineAddressWorker) Kill() { 467 w.catacomb.Kill(nil) 468 } 469 470 func (w *machineAddressWorker) Wait() error { 471 return w.catacomb.Wait() 472 } 473 474 func setEquals(a, b set.Strings) bool { 475 if a.Size() != b.Size() { 476 return false 477 } 478 return a.Intersection(b).Size() == a.Size() 479 }