github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/address.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 "net" 9 "reflect" 10 "sort" 11 "strconv" 12 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 jujutxn "github.com/juju/txn/v3" 18 19 k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants" 20 "github.com/juju/juju/core/network" 21 "github.com/juju/juju/mongo" 22 ) 23 24 // controllerAddresses returns the list of internal addresses of the state 25 // server machines. 26 func (st *State) controllerAddresses() ([]string, error) { 27 cinfo, err := st.ControllerInfo() 28 if err != nil { 29 return nil, errors.Trace(err) 30 } 31 32 var machines mongo.Collection 33 var closer SessionCloser 34 model, err := st.Model() 35 if err != nil { 36 return nil, errors.Trace(err) 37 } 38 if model.ModelTag() == cinfo.ModelTag { 39 machines, closer = st.db().GetCollection(machinesC) 40 } else { 41 machines, closer = st.db().GetCollectionFor(cinfo.ModelTag.Id(), machinesC) 42 } 43 defer closer() 44 45 type addressMachine struct { 46 Addresses []address 47 } 48 var allAddresses []addressMachine 49 // TODO(rog) 2013/10/14 index machines on jobs. 50 err = machines.Find(bson.D{{"jobs", JobManageModel}}).All(&allAddresses) 51 if err != nil { 52 return nil, err 53 } 54 if len(allAddresses) == 0 { 55 return nil, errors.New("no controller machines found") 56 } 57 apiAddrs := make([]string, 0, len(allAddresses)) 58 for _, addrs := range allAddresses { 59 addr, ok := networkAddresses(addrs.Addresses).OneMatchingScope(network.ScopeMatchCloudLocal) 60 if ok { 61 apiAddrs = append(apiAddrs, addr.Value) 62 } 63 } 64 if len(apiAddrs) == 0 { 65 return nil, errors.New("no controller machines with addresses found") 66 } 67 return apiAddrs, nil 68 } 69 70 func appendPort(addrs []string, port int) []string { 71 newAddrs := make([]string, len(addrs)) 72 for i, addr := range addrs { 73 newAddrs[i] = net.JoinHostPort(addr, strconv.Itoa(port)) 74 } 75 return newAddrs 76 } 77 78 // Addresses returns the list of cloud-internal addresses that 79 // can be used to connect to the state. 80 func (st *State) Addresses() ([]string, error) { 81 addrs, err := st.controllerAddresses() 82 if err != nil { 83 return nil, errors.Trace(err) 84 } 85 config, err := st.ControllerConfig() 86 if err != nil { 87 return nil, errors.Trace(err) 88 } 89 return appendPort(addrs, config.StatePort()), nil 90 } 91 92 const ( 93 // Key for *all* addresses at which controllers are accessible. 94 apiHostPortsKey = "apiHostPorts" 95 // Key for addresses at which controllers are accessible by agents. 96 apiHostPortsForAgentsKey = "apiHostPortsForAgents" 97 ) 98 99 type apiHostPortsDoc struct { 100 APIHostPorts [][]hostPort `bson:"apihostports"` 101 TxnRevno int64 `bson:"txn-revno"` 102 } 103 104 // SetAPIHostPorts sets the addresses, if changed, of two collections: 105 // - The list of *all* addresses at which the API is accessible. 106 // - The list of addresses at which the API can be accessed by agents according 107 // to the controller management space configuration. 108 // 109 // Each server is represented by one element in the top level slice. 110 func (st *State) SetAPIHostPorts(newHostPorts []network.SpaceHostPorts) error { 111 controllers, closer := st.db().GetCollection(controllersC) 112 defer closer() 113 114 buildTxn := func(attempt int) ([]txn.Op, error) { 115 ops, err := st.getOpsForHostPortsChange(controllers, apiHostPortsKey, newHostPorts) 116 if err != nil { 117 return nil, errors.Trace(err) 118 } 119 120 newHostPortsForAgents, err := st.filterHostPortsForManagementSpace(newHostPorts) 121 if err != nil { 122 return nil, errors.Trace(err) 123 } 124 agentAddrOps, err := st.getOpsForHostPortsChange(controllers, apiHostPortsForAgentsKey, newHostPortsForAgents) 125 if err != nil { 126 return nil, errors.Trace(err) 127 } 128 ops = append(ops, agentAddrOps...) 129 130 if len(ops) == 0 { 131 return nil, jujutxn.ErrNoOperations 132 } 133 return ops, nil 134 } 135 136 if err := st.db().Run(buildTxn); err != nil { 137 return errors.Annotate(err, "cannot set API addresses") 138 } 139 return nil 140 } 141 142 // getOpsForHostPortsChange returns a slice of operations used to update an 143 // API host/collection in the DB. 144 // If the current document indicates the same host/port collection as the 145 // input, no operations are returned. 146 func (st *State) getOpsForHostPortsChange( 147 mc mongo.Collection, key string, newHostPorts []network.SpaceHostPorts, 148 ) ([]txn.Op, error) { 149 var ops []txn.Op 150 151 // Retrieve the current document. Return an insert operation if not found. 152 var extantHostPortDoc apiHostPortsDoc 153 err := mc.Find(bson.D{{"_id", key}}).One(&extantHostPortDoc) 154 if err != nil { 155 if err == mgo.ErrNotFound { 156 return []txn.Op{{ 157 C: controllersC, 158 Id: key, 159 Insert: bson.D{{"apihostports", fromNetworkHostsPorts(newHostPorts)}}, 160 }}, nil 161 } 162 return ops, err 163 } 164 165 // Queue an update operation if the host/port collections differ. 166 extantHostPorts := networkHostsPorts(extantHostPortDoc.APIHostPorts) 167 if !hostsPortsEqual(newHostPorts, extantHostPorts) { 168 ops = []txn.Op{{ 169 C: controllersC, 170 Id: key, 171 Assert: bson.D{{ 172 "txn-revno", extantHostPortDoc.TxnRevno, 173 }}, 174 Update: bson.D{{ 175 "$set", bson.D{{"apihostports", fromNetworkHostsPorts(newHostPorts)}}, 176 }}, 177 }} 178 logger.Debugf("setting %s: %v", key, newHostPorts) 179 } 180 return ops, nil 181 } 182 183 // filterHostPortsForManagementSpace filters the collection of API addresses 184 // based on the configured management space for the controller. 185 // If there is no space configured, or if one of the slices is filtered down 186 // to zero elements, just use the unfiltered slice for safety - we do not 187 // want to cut off communication to the controller based on erroneous config. 188 func (st *State) filterHostPortsForManagementSpace( 189 apiHostPorts []network.SpaceHostPorts, 190 ) ([]network.SpaceHostPorts, error) { 191 config, err := st.ControllerConfig() 192 if err != nil { 193 return nil, errors.Trace(err) 194 } 195 196 var hostPortsForAgents []network.SpaceHostPorts 197 if mgmtSpace := config.JujuManagementSpace(); mgmtSpace != "" { 198 sp, err := st.SpaceByName(mgmtSpace) 199 if err != nil { 200 return nil, errors.Trace(err) 201 } 202 spaceInfo, err := sp.NetworkSpace() 203 if err != nil { 204 return nil, errors.Trace(err) 205 } 206 207 hostPortsForAgents = make([]network.SpaceHostPorts, len(apiHostPorts)) 208 for i := range apiHostPorts { 209 if filtered, ok := apiHostPorts[i].InSpaces(spaceInfo); ok { 210 hostPortsForAgents[i] = filtered 211 } else { 212 hostPortsForAgents[i] = apiHostPorts[i] 213 } 214 } 215 } else { 216 hostPortsForAgents = apiHostPorts 217 } 218 219 return hostPortsForAgents, nil 220 } 221 222 // APIHostPortsForClients returns the collection of *all* known API addresses. 223 func (st *State) APIHostPortsForClients() ([]network.SpaceHostPorts, error) { 224 isCAASCtrl, err := st.isCAASController() 225 if err != nil { 226 return nil, errors.Trace(err) 227 } 228 if isCAASCtrl { 229 // TODO(caas): add test for this once we have the replacement for Jujuconnsuite. 230 return st.apiHostPortsForCAAS(true) 231 } 232 233 hp, err := st.apiHostPortsForKey(apiHostPortsKey) 234 if err != nil { 235 err = errors.Trace(err) 236 } 237 return hp, err 238 } 239 240 // APIHostPortsForAgents returns the collection of API addresses that should 241 // be used by agents. 242 // If the controller model is CAAS type, the return will be the controller 243 // k8s service addresses in cloud service. 244 // If there is no management network space configured for the controller, 245 // or if the space is misconfigured, the return will be the same as 246 // APIHostPortsForClients. 247 // Otherwise the returned addresses will correspond with the management net space. 248 // If there is no document at all, we simply fall back to APIHostPortsForClients. 249 func (st *State) APIHostPortsForAgents() ([]network.SpaceHostPorts, error) { 250 isCAASCtrl, err := st.isCAASController() 251 if err != nil { 252 return nil, errors.Trace(err) 253 } 254 if isCAASCtrl { 255 // TODO(caas): add test for this once we have the replacement for Jujuconnsuite. 256 return st.apiHostPortsForCAAS(false) 257 } 258 259 hps, err := st.apiHostPortsForKey(apiHostPortsForAgentsKey) 260 if err != nil { 261 if err == mgo.ErrNotFound { 262 logger.Debugf("No document for %s; using %s", apiHostPortsForAgentsKey, apiHostPortsKey) 263 return st.APIHostPortsForClients() 264 } 265 return nil, errors.Trace(err) 266 } 267 return hps, nil 268 } 269 270 func (st *State) isCAASController() (bool, error) { 271 m := &Model{st: st} 272 if err := m.refresh(st.ControllerModelUUID()); err != nil { 273 return false, errors.Trace(err) 274 } 275 return m.IsControllerModel() && m.Type() == ModelTypeCAAS, nil 276 } 277 278 func (st *State) apiHostPortsForCAAS(public bool) (addresses []network.SpaceHostPorts, err error) { 279 defer func() { 280 logger.Debugf("getting api hostports for CAAS: public %t, addresses %v", public, addresses) 281 }() 282 283 if st.ModelUUID() != st.controllerModelTag.Id() { 284 return nil, errors.Errorf("CAAS API host ports only available on the controller model, not %q", st.ModelUUID()) 285 } 286 287 controllerConfig, err := st.ControllerConfig() 288 if err != nil { 289 return nil, errors.Trace(err) 290 } 291 292 apiPort := controllerConfig.APIPort() 293 svc, err := st.CloudService(controllerConfig.ControllerUUID()) 294 if err != nil { 295 return nil, errors.Trace(err) 296 } 297 addrs := svc.Addresses() 298 299 addrsToHostPorts := func(addrs ...network.SpaceAddress) []network.SpaceHostPorts { 300 return []network.SpaceHostPorts{network.SpaceAddressesWithPort(addrs, apiPort)} 301 } 302 303 // Return all publicly available addresses for clients. 304 // Scope matching falls back through a hierarchy, 305 // so these may actually be local-cloud addresses. 306 publicAddrs := addrs.AllMatchingScope(network.ScopeMatchPublic) 307 if public { 308 return addrsToHostPorts(publicAddrs...), nil 309 } 310 311 var hostAddresses network.SpaceAddresses 312 // Add in the FQDN of the controller service for agents to use as an option. 313 controllerName := controllerConfig.ControllerName() 314 if controllerName != "" { 315 hostAddresses = append( 316 hostAddresses, network.NewSpaceAddress( 317 fmt.Sprintf(k8sconstants.ControllerServiceFQDNTemplate, controllerName), 318 network.WithScope(network.ScopeCloudLocal), 319 )) 320 } 321 322 // TODO(wallyworld) - for now, return all addresses for agents to try, public last. 323 324 // If we are after local-cloud addresses and those were all that public 325 // matching turned up, just return those. 326 if len(publicAddrs) > 0 && publicAddrs[0].Scope == network.ScopeCloudLocal { 327 return addrsToHostPorts(append(hostAddresses, publicAddrs...)...), nil 328 } 329 330 localAddrs := addrs.AllMatchingScope(network.ScopeMatchCloudLocal) 331 332 // If there were no local-cloud addresses, return the public ones. 333 if len(localAddrs) == 0 || localAddrs[0].Scope == network.ScopePublic { 334 return addrsToHostPorts(append(hostAddresses, publicAddrs...)...), nil 335 } 336 337 // Otherwise return everything, local-cloud first. 338 hostAddresses = append(hostAddresses, localAddrs...) 339 hostAddresses = append(hostAddresses, publicAddrs...) 340 return addrsToHostPorts(hostAddresses...), nil 341 } 342 343 // apiHostPortsForKey returns API addresses extracted from the document 344 // identified by the input key. 345 func (st *State) apiHostPortsForKey(key string) ([]network.SpaceHostPorts, error) { 346 var doc apiHostPortsDoc 347 controllers, closer := st.db().GetCollection(controllersC) 348 defer closer() 349 err := controllers.Find(bson.D{{"_id", key}}).One(&doc) 350 if err != nil { 351 return nil, err 352 } 353 return networkHostsPorts(doc.APIHostPorts), nil 354 } 355 356 // address represents the location of a machine, including metadata 357 // about what kind of location the address describes. 358 // 359 // TODO(dimitern) Make sure we integrate this with other networking 360 // stuff at some point. We want to use juju-specific network names 361 // that point to existing documents in the networks collection. 362 type address struct { 363 Value string `bson:"value"` 364 AddressType string `bson:"addresstype"` 365 Scope string `bson:"networkscope,omitempty"` 366 Origin string `bson:"origin,omitempty"` 367 SpaceID string `bson:"spaceid,omitempty"` 368 } 369 370 // fromNetworkAddress is a convenience helper to create a state type 371 // out of the network type, here for Address with a given Origin. 372 func fromNetworkAddress(netAddr network.SpaceAddress, origin network.Origin) address { 373 return address{ 374 Value: netAddr.Value, 375 AddressType: string(netAddr.Type), 376 Scope: string(netAddr.Scope), 377 Origin: string(origin), 378 SpaceID: netAddr.SpaceID, 379 } 380 } 381 382 // networkAddress is a convenience helper to return the state type 383 // as network type, here for Address. 384 func (addr *address) networkAddress() network.SpaceAddress { 385 return network.SpaceAddress{ 386 MachineAddress: network.MachineAddress{ 387 Value: addr.Value, 388 Type: network.AddressType(addr.AddressType), 389 Scope: network.Scope(addr.Scope), 390 }, 391 SpaceID: addr.SpaceID, 392 } 393 } 394 395 // fromNetworkAddresses is a convenience helper to create a state type 396 // out of the network type, here for a slice of Address with a given origin. 397 func fromNetworkAddresses(netAddrs network.SpaceAddresses, origin network.Origin) []address { 398 addrs := make([]address, len(netAddrs)) 399 for i, netAddr := range netAddrs { 400 addrs[i] = fromNetworkAddress(netAddr, origin) 401 } 402 return addrs 403 } 404 405 // networkAddresses is a convenience helper to return the state type 406 // as network type, here for a slice of Address. 407 func networkAddresses(addrs []address) network.SpaceAddresses { 408 netAddrs := make(network.SpaceAddresses, len(addrs)) 409 for i, addr := range addrs { 410 netAddrs[i] = addr.networkAddress() 411 } 412 return netAddrs 413 } 414 415 // hostPort associates an address with a port. See also network.SpaceHostPort, 416 // from/to which this is transformed. 417 // 418 // TODO(dimitern) Make sure we integrate this with other networking 419 // stuff at some point. We want to use juju-specific network names 420 // that point to existing documents in the networks collection. 421 type hostPort struct { 422 Value string `bson:"value"` 423 AddressType string `bson:"addresstype"` 424 Scope string `bson:"networkscope,omitempty"` 425 Port int `bson:"port"` 426 SpaceID string `bson:"spaceid,omitempty"` 427 } 428 429 // fromNetworkHostPort is a convenience helper to create a state type 430 // out of the network type, here for SpaceHostPort. 431 func fromNetworkHostPort(netHostPort network.SpaceHostPort) hostPort { 432 return hostPort{ 433 Value: netHostPort.Value, 434 AddressType: string(netHostPort.Type), 435 Scope: string(netHostPort.Scope), 436 Port: netHostPort.Port(), 437 SpaceID: netHostPort.SpaceID, 438 } 439 } 440 441 // networkHostPort is a convenience helper to return the state type 442 // as network type, here for SpaceHostPort. 443 func (hp *hostPort) networkHostPort() network.SpaceHostPort { 444 return network.SpaceHostPort{ 445 SpaceAddress: network.SpaceAddress{ 446 MachineAddress: network.MachineAddress{ 447 Value: hp.Value, 448 Type: network.AddressType(hp.AddressType), 449 Scope: network.Scope(hp.Scope), 450 }, 451 SpaceID: hp.SpaceID, 452 }, 453 NetPort: network.NetPort(hp.Port), 454 } 455 } 456 457 // fromNetworkHostsPorts is a helper to create a state type 458 // out of the network type, here for a nested slice of SpaceHostPort. 459 func fromNetworkHostsPorts(netHostsPorts []network.SpaceHostPorts) [][]hostPort { 460 hsps := make([][]hostPort, len(netHostsPorts)) 461 for i, netHostPorts := range netHostsPorts { 462 hsps[i] = make([]hostPort, len(netHostPorts)) 463 for j, netHostPort := range netHostPorts { 464 hsps[i][j] = fromNetworkHostPort(netHostPort) 465 } 466 } 467 return hsps 468 } 469 470 // networkHostsPorts is a convenience helper to return the state type 471 // as network type, here for a nested slice of SpaceHostPort. 472 func networkHostsPorts(hsps [][]hostPort) []network.SpaceHostPorts { 473 netHostsPorts := make([]network.SpaceHostPorts, len(hsps)) 474 for i, hps := range hsps { 475 netHostsPorts[i] = make(network.SpaceHostPorts, len(hps)) 476 for j, hp := range hps { 477 netHostsPorts[i][j] = hp.networkHostPort() 478 } 479 } 480 return netHostsPorts 481 } 482 483 // addressEqual checks that two slices of network addresses are equal. 484 func addressesEqual(a, b []network.SpaceAddress) bool { 485 return reflect.DeepEqual(a, b) 486 } 487 488 func dupeAndSort(a []network.SpaceHostPorts) []network.SpaceHostPorts { 489 var result []network.SpaceHostPorts 490 491 for _, val := range a { 492 var inner network.SpaceHostPorts 493 inner = append(inner, val...) 494 sort.Sort(inner) 495 result = append(result, inner) 496 } 497 sort.Sort(hostsPortsSlice(result)) 498 return result 499 } 500 501 type hostsPortsSlice []network.SpaceHostPorts 502 503 func (hp hostsPortsSlice) Len() int { return len(hp) } 504 func (hp hostsPortsSlice) Swap(i, j int) { hp[i], hp[j] = hp[j], hp[i] } 505 func (hp hostsPortsSlice) Less(i, j int) bool { 506 lhs := (hostPortsSlice)(hp[i]).String() 507 rhs := (hostPortsSlice)(hp[j]).String() 508 return lhs < rhs 509 } 510 511 type hostPortsSlice []network.SpaceHostPort 512 513 func (hp hostPortsSlice) String() string { 514 var result string 515 for _, val := range hp { 516 result += fmt.Sprintf("%s-%d ", val.SpaceAddress, val.Port()) 517 } 518 return result 519 } 520 521 // hostsPortsEqual checks that two arrays of network hostports are equal. 522 func hostsPortsEqual(a, b []network.SpaceHostPorts) bool { 523 // Make a copy of all the values so we don't mutate the args in order 524 // to determine if they are the same while we mutate the slice to order them. 525 aPrime := dupeAndSort(a) 526 bPrime := dupeAndSort(b) 527 return reflect.DeepEqual(aPrime, bPrime) 528 } 529 530 func (st *State) ConvertSpaceHostPorts(sHPs network.SpaceHostPorts) (network.ProviderHostPorts, error) { 531 addrs := make(network.ProviderHostPorts, len(sHPs)) 532 for i, sAddr := range sHPs { 533 var err error 534 if addrs[i], err = st.ConvertSpaceHostPort(sAddr); err != nil { 535 return nil, errors.Trace(err) 536 } 537 } 538 return addrs, nil 539 } 540 541 func (st *State) ConvertSpaceHostPort(sHP network.SpaceHostPort) (network.ProviderHostPort, error) { 542 hp := network.ProviderHostPort{ 543 ProviderAddress: network.ProviderAddress{MachineAddress: sHP.MachineAddress}, 544 NetPort: sHP.NetPort, 545 } 546 if sHP.SpaceID != "" { 547 space, err := st.Space(sHP.SpaceID) 548 if err != nil { 549 return hp, errors.Trace(err) 550 } 551 hp.SpaceName = network.SpaceName(space.Name()) 552 } 553 return hp, nil 554 }