github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/network/containerizer/bridgepolicy.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package containerizer 5 6 import ( 7 "fmt" 8 "hash/crc32" 9 "sort" 10 "strings" 11 12 "github.com/juju/collections/set" 13 "github.com/juju/errors" 14 "github.com/juju/loggo" 15 16 "github.com/juju/juju/core/instance" 17 corenetwork "github.com/juju/juju/core/network" 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/network" 20 "github.com/juju/juju/state" 21 ) 22 23 var logger = loggo.GetLogger("juju.network.containerizer") 24 25 var skippedDeviceNames = set.NewStrings( 26 network.DefaultLXDBridge, 27 network.DefaultKVMBridge, 28 ) 29 30 // namedNICsBySpace is a type alias for a map of link-layer devices 31 // keyed by name, keyed in turn by the space they are in. 32 type namedNICsBySpace = map[string]map[string]LinkLayerDevice 33 34 // BridgePolicy defines functionality that helps us create and define bridges 35 // for guests inside a host machine, along with the creation of network 36 // devices on those bridges for the containers to use. 37 type BridgePolicy struct { 38 // spaces is a slice of SpaceInfos. 39 spaces corenetwork.SpaceInfos 40 41 // netBondReconfigureDelay is how much of a delay to inject if we see that 42 // one of the devices being bridged is a BondDevice. This exists because of 43 // https://bugs.launchpad.net/juju/+bug/1657579 44 netBondReconfigureDelay int 45 46 // containerNetworkingMethod defines the way containers are networked. 47 // It's one of: 48 // - fan 49 // - provider 50 // - local 51 containerNetworkingMethod string 52 } 53 54 // NewBridgePolicy returns a new BridgePolicy for the input environ config 55 // getter and state indirection. 56 func NewBridgePolicy(cfgGetter environs.ConfigGetter, st SpaceBacking) (*BridgePolicy, error) { 57 cfg := cfgGetter.Config() 58 59 spaces, err := st.AllSpaceInfos() 60 if err != nil { 61 return nil, errors.Annotate(err, "getting space infos") 62 } 63 64 return &BridgePolicy{ 65 spaces: spaces, 66 netBondReconfigureDelay: cfg.NetBondReconfigureDelay(), 67 containerNetworkingMethod: cfg.ContainerNetworkingMethod(), 68 }, nil 69 } 70 71 // FindMissingBridgesForContainer looks at the spaces that the container should 72 // have access to, and returns any host devices need to be bridged for use as 73 // the container network. 74 // This will return an Error if the container requires a space that the host 75 // machine cannot provide. 76 func (p *BridgePolicy) FindMissingBridgesForContainer( 77 host Machine, guest Container, 78 ) ([]network.DeviceToBridge, int, error) { 79 guestSpaceInfos, devicesPerSpace, err := p.findSpacesAndDevicesForContainer(host, guest) 80 if err != nil { 81 return nil, 0, errors.Trace(err) 82 } 83 logger.Debugf("FindMissingBridgesForContainer(%q) spaces %s devices %v", 84 guest.Id(), guestSpaceInfos, formatDeviceMap(devicesPerSpace)) 85 86 spacesFound := make(corenetwork.SpaceInfos, 0) 87 fanSpacesFound := make(corenetwork.SpaceInfos, 0) 88 for spaceID, devices := range devicesPerSpace { 89 for _, device := range devices { 90 if device.Type() == corenetwork.BridgeDevice { 91 if p.containerNetworkingMethod != "local" && skippedDeviceNames.Contains(device.Name()) { 92 continue 93 } 94 if strings.HasPrefix(device.Name(), "fan-") { 95 addInfo := p.spaces.GetByID(spaceID) 96 fanSpacesFound = append(fanSpacesFound, *addInfo) 97 } else { 98 addInfo := p.spaces.GetByID(spaceID) 99 spacesFound = append(spacesFound, *addInfo) 100 } 101 } 102 } 103 } 104 105 notFound := guestSpaceInfos.Minus(spacesFound) 106 fanNotFound := guestSpaceInfos.Minus(fanSpacesFound) 107 108 if p.containerNetworkingMethod == "fan" { 109 if len(fanNotFound) == 0 { 110 // Nothing to do; just return success. 111 return nil, 0, nil 112 } 113 return nil, 0, errors.Errorf("host machine %q has no available FAN devices in space(s) %s", 114 host.Id(), fanNotFound) 115 } 116 117 if len(notFound) == 0 { 118 // Nothing to do; just return success. 119 return nil, 0, nil 120 } 121 122 hostDeviceNamesToBridge := make([]string, 0) 123 reconfigureDelay := 0 124 hostDeviceByName := make(map[string]LinkLayerDevice, 0) 125 for _, spaceInfo := range notFound { 126 hostDeviceNames := make([]string, 0) 127 for _, hostDevice := range devicesPerSpace[spaceInfo.ID] { 128 possible, err := possibleBridgeTarget(hostDevice) 129 if err != nil { 130 return nil, 0, err 131 } 132 if !possible { 133 continue 134 } 135 hostDeviceNames = append(hostDeviceNames, hostDevice.Name()) 136 hostDeviceByName[hostDevice.Name()] = hostDevice 137 spacesFound = append(spacesFound, spaceInfo) 138 } 139 if len(hostDeviceNames) > 0 { 140 if spaceInfo.ID == corenetwork.AlphaSpaceId { 141 // When we are bridging unknown space devices, we bridge all 142 // of them. Both because this is a fallback, and because we 143 // don't know what the exact spaces are going to be. 144 for _, deviceName := range hostDeviceNames { 145 hostDeviceNamesToBridge = append(hostDeviceNamesToBridge, deviceName) 146 if hostDeviceByName[deviceName].Type() == corenetwork.BondDevice { 147 if reconfigureDelay < p.netBondReconfigureDelay { 148 reconfigureDelay = p.netBondReconfigureDelay 149 } 150 } 151 } 152 } else { 153 // This should already be sorted from 154 // LinkLayerDevicesForSpaces but sorting to be sure we stably 155 // pick the host device 156 hostDeviceNames = network.NaturallySortDeviceNames(hostDeviceNames...) 157 hostDeviceNamesToBridge = append(hostDeviceNamesToBridge, hostDeviceNames[0]) 158 if hostDeviceByName[hostDeviceNames[0]].Type() == corenetwork.BondDevice { 159 if reconfigureDelay < p.netBondReconfigureDelay { 160 reconfigureDelay = p.netBondReconfigureDelay 161 } 162 } 163 } 164 } 165 } 166 notFound = notFound.Minus(spacesFound) 167 if len(notFound) != 0 { 168 hostSpaces, err := host.AllSpaces() 169 if err != nil { 170 // log it, but we're returning another error right now 171 logger.Warningf("got error looking for spaces for host machine %q: %v", 172 host.Id(), err) 173 } 174 notFoundNames := notFound.String() 175 logger.Warningf("container %q wants spaces %s, but host machine %q has %s missing %s", 176 guest.Id(), guestSpaceInfos, 177 host.Id(), p.spaceNamesForPrinting(hostSpaces), notFoundNames) 178 return nil, 0, errors.Errorf("host machine %q has no available device in space(s) %s", 179 host.Id(), notFoundNames) 180 } 181 182 hostToBridge := make([]network.DeviceToBridge, 0, len(hostDeviceNamesToBridge)) 183 for _, hostName := range network.NaturallySortDeviceNames(hostDeviceNamesToBridge...) { 184 hostToBridge = append(hostToBridge, network.DeviceToBridge{ 185 DeviceName: hostName, 186 BridgeName: BridgeNameForDevice(hostName), 187 MACAddress: hostDeviceByName[hostName].MACAddress(), 188 }) 189 } 190 return hostToBridge, reconfigureDelay, nil 191 } 192 193 // findSpacesAndDevicesForContainer looks up what spaces the container wants 194 // to be in, and what spaces the host machine is already in, and tries to 195 // find the devices on the host that are useful for the container. 196 func (p *BridgePolicy) findSpacesAndDevicesForContainer( 197 host Machine, guest Container, 198 ) (corenetwork.SpaceInfos, map[string][]LinkLayerDevice, error) { 199 containerSpaces, err := p.determineContainerSpaces(host, guest) 200 if err != nil { 201 return nil, nil, errors.Trace(err) 202 } 203 devicesPerSpace, err := linkLayerDevicesForSpaces(host, containerSpaces) 204 if err != nil { 205 logger.Errorf("findSpacesAndDevicesForContainer(%q) got error looking for host spaces: %v", 206 guest.Id(), err) 207 return nil, nil, errors.Trace(err) 208 } 209 210 // OVS bridges expose one of the internal ports as a device with the 211 // same name as the bridge. These special interfaces are not detected 212 // as bridge devices but rather appear as regular NICs. If the configured 213 // networking method is "provider", we need to patch the type of these 214 // devices so they appear as bridges to allow the bridge policy logic 215 // to make use of them. 216 if p.containerNetworkingMethod == "provider" { 217 for spaceID, devsInSpace := range devicesPerSpace { 218 for devIdx, dev := range devsInSpace { 219 if dev.VirtualPortType() != corenetwork.OvsPort { 220 continue 221 } 222 223 devicesPerSpace[spaceID][devIdx] = ovsBridgeDevice{ 224 wrappedDev: dev, 225 } 226 } 227 } 228 } 229 230 return containerSpaces, devicesPerSpace, nil 231 } 232 233 // linkLayerDevicesForSpaces takes a list of SpaceInfos, and returns 234 // the devices on this machine that are in those spaces that we feel 235 // would be useful for containers to know about. (eg, if there is a 236 // host device that has been bridged, we return the bridge, rather 237 // than the underlying device, but if we have only the host device, 238 // we return that.) 239 // Note that devices like 'lxdbr0' that are bridges that might not be 240 // externally accessible may be returned if the default space is 241 // listed as one of the desired spaces. 242 func linkLayerDevicesForSpaces(host Machine, spaces corenetwork.SpaceInfos) (map[string][]LinkLayerDevice, error) { 243 deviceByName, err := linkLayerDevicesByName(host) 244 if err != nil { 245 return nil, errors.Trace(err) 246 } 247 248 addresses, err := host.AllDeviceAddresses() 249 if err != nil { 250 return nil, errors.Trace(err) 251 } 252 253 // Iterate all addresses and key them by the address device name. 254 addressByDeviceName := make(map[string]Address) 255 for _, addr := range addresses { 256 addressByDeviceName[addr.DeviceName()] = addr 257 } 258 259 // Iterate the devices by name, lookup the associated spaces, and 260 // gather the devices. 261 spaceToDevices := make(namedNICsBySpace, 0) 262 for _, device := range deviceByName { 263 addr, ok := addressByDeviceName[device.Name()] 264 if !ok { 265 logger.Infof("device %q has no addresses, ignoring", device.Name()) 266 continue 267 } 268 269 // Loopback devices are not considered part of the empty space. 270 if device.Type() == corenetwork.LoopbackDevice { 271 continue 272 } 273 274 spaceID := corenetwork.AlphaSpaceId 275 276 subnet, err := addr.Subnet() 277 if err != nil { 278 if !errors.IsNotFound(err) { 279 // We don't understand the error, so error out for now 280 return nil, errors.Trace(err) 281 } 282 } else { 283 spaceID = subnet.SpaceID() 284 } 285 spaceToDevices = includeDevice(spaceToDevices, spaceID, device) 286 } 287 288 result := make(map[string][]LinkLayerDevice, len(spaceToDevices)) 289 for spaceID, deviceMap := range spaceToDevices { 290 if !spaces.ContainsID(spaceID) { 291 continue 292 } 293 result[spaceID] = deviceMapToSortedList(deviceMap) 294 } 295 return result, nil 296 } 297 298 func linkLayerDevicesByName(host Machine) (map[string]LinkLayerDevice, error) { 299 devices, err := host.AllLinkLayerDevices() 300 if err != nil { 301 return nil, errors.Trace(err) 302 } 303 deviceByName := make(map[string]LinkLayerDevice, len(devices)) 304 for _, dev := range devices { 305 deviceByName[dev.Name()] = dev 306 } 307 return deviceByName, nil 308 } 309 310 func includeDevice(spaceToDevices namedNICsBySpace, spaceID string, device LinkLayerDevice) namedNICsBySpace { 311 spaceInfo, ok := spaceToDevices[spaceID] 312 if !ok { 313 spaceInfo = make(map[string]LinkLayerDevice) 314 spaceToDevices[spaceID] = spaceInfo 315 } 316 spaceInfo[device.Name()] = device 317 return spaceToDevices 318 } 319 320 // deviceMapToSortedList takes a map from device name to LinkLayerDevice 321 // object, and returns the list of LinkLayerDevice object using 322 // NaturallySortDeviceNames 323 func deviceMapToSortedList(deviceMap map[string]LinkLayerDevice) []LinkLayerDevice { 324 names := make([]string, 0, len(deviceMap)) 325 for name := range deviceMap { 326 // name must == device.Name() 327 names = append(names, name) 328 } 329 sortedNames := network.NaturallySortDeviceNames(names...) 330 result := make([]LinkLayerDevice, len(sortedNames)) 331 for i, name := range sortedNames { 332 result[i] = deviceMap[name] 333 } 334 return result 335 } 336 337 // determineContainerSpaces tries to use the direct information about a 338 // container to find what spaces it should be in, and then falls back to what 339 // we know about the host machine. 340 func (p *BridgePolicy) determineContainerSpaces( 341 host Machine, guest Container, 342 ) (corenetwork.SpaceInfos, error) { 343 // Gather any *positive* space constraints for the guest. 344 cons, err := guest.Constraints() 345 if err != nil { 346 return nil, errors.Trace(err) 347 } 348 349 spaces := make(corenetwork.SpaceInfos, 0) 350 // Constraints have been left in space name form, 351 // as they are human-readable and can be changed. 352 for _, spaceName := range cons.IncludeSpaces() { 353 if space := p.spaces.GetByName(spaceName); space != nil { 354 spaces = append(spaces, *space) 355 } 356 } 357 358 logger.Debugf("for container %q, found desired spaces: %s", guest.Id(), spaces) 359 360 if len(spaces) == 0 { 361 // We have determined that the container doesn't have any useful 362 // constraints set on it. So lets see if we can come up with 363 // something useful. 364 spaces, err = p.inferContainerSpaces(host, guest.Id()) 365 if err != nil { 366 return nil, errors.Trace(err) 367 } 368 } 369 370 return spaces, nil 371 } 372 373 // spaceNamesForPrinting return a sorted, comma delimited, string containing 374 // the space names for each of the given space ids. 375 func (p *BridgePolicy) spaceNamesForPrinting(ids set.Strings) string { 376 if ids.Size() == 0 { 377 return "<none>" 378 } 379 names := set.NewStrings() 380 for _, id := range ids.Values() { 381 if info := p.spaces.GetByID(id); info != nil { 382 names.Add(fmt.Sprintf("%q", info.Name)) 383 } else { 384 // fallback, in case we do not have a name for the given 385 // id. 386 names.Add(fmt.Sprintf("%q", id)) 387 } 388 } 389 return strings.Join(names.SortedValues(), ", ") 390 } 391 392 // inferContainerSpaces tries to find a valid space for the container to be in. 393 // This should only be used when the container itself doesn't have any valid 394 // constraints on what spaces it should be in. 395 // If containerNetworkingMethod is 'local' we fall back to the default space 396 // and use lxdbr0. 397 // If this machine is in a single space, then that space is used. 398 // If the machine is in multiple spaces, we return an error with the possible 399 // spaces that the user can use to constrain connectivity. 400 func (p *BridgePolicy) inferContainerSpaces(host Machine, containerId string) (corenetwork.SpaceInfos, error) { 401 if p.containerNetworkingMethod == "local" { 402 alphaInfo := p.spaces.GetByID(corenetwork.AlphaSpaceId) 403 return corenetwork.SpaceInfos{*alphaInfo}, nil 404 } 405 406 hostSpaces, err := host.AllSpaces() 407 if err != nil { 408 return nil, errors.Trace(err) 409 } 410 namesHostSpaces := p.spaceNamesForPrinting(hostSpaces) 411 logger.Debugf("container %q not qualified to a space, host machine %q is using spaces %s", 412 containerId, host.Id(), namesHostSpaces) 413 414 if len(hostSpaces) == 1 { 415 hostInfo := p.spaces.GetByID(hostSpaces.Values()[0]) 416 return corenetwork.SpaceInfos{*hostInfo}, nil 417 } 418 if len(hostSpaces) == 0 { 419 logger.Debugf("container has no desired spaces, " + 420 "and host has no known spaces, triggering fallback " + 421 "to bridge all devices") 422 alphaInfo := p.spaces.GetByID(corenetwork.AlphaSpaceId) 423 return corenetwork.SpaceInfos{*alphaInfo}, nil 424 } 425 return nil, errors.Errorf("no obvious space for container %q, host machine has spaces: %s", 426 containerId, namesHostSpaces) 427 } 428 429 func possibleBridgeTarget(dev LinkLayerDevice) (bool, error) { 430 // LoopbackDevices can never be bridged 431 if dev.Type() == corenetwork.LoopbackDevice || dev.Type() == corenetwork.BridgeDevice { 432 return false, nil 433 } 434 // Devices that have no parent entry are direct host devices that can be 435 // bridged. 436 if dev.ParentName() == "" { 437 return true, nil 438 } 439 // TODO(jam): 2016-12-22 This feels dirty, but it falls out of how we are 440 // currently modeling VLAN objects. see bug https://pad.lv/1652049 441 if dev.Type() != corenetwork.VLAN8021QDevice { 442 // Only VLAN8021QDevice have parents that still allow us to 443 // bridge them. 444 // When anything else has a parent set, it shouldn't be used. 445 return false, nil 446 } 447 parentDevice, err := dev.ParentDevice() 448 if err != nil { 449 // If we got an error here, we have some sort of 450 // database inconsistency error. 451 return false, err 452 } 453 if parentDevice.Type() == corenetwork.EthernetDevice || parentDevice.Type() == corenetwork.BondDevice { 454 // A plain VLAN device with a direct parent 455 // of its underlying ethernet device. 456 return true, nil 457 } 458 return false, nil 459 } 460 461 // BridgeNameForDevice returns a name to use for a new device that bridges the 462 // device with the input name. The policy is to: 463 // 1. Add br- to device name (to keep current behaviour), 464 // if it does not fit in 15 characters then: 465 // 2. Add b- to device name, if it doesn't fit in 15 characters then: 466 // 467 // 3a. For devices starting in 'en' remove 'en' and add 'b-' 468 // 3b. For all other devices 469 // 470 // 'b-' + 6-char hash of name + '-' + last 6 chars of name 471 // 4. If using the device name directly always replace '.' with '-' 472 // to make sure that bridges from VLANs won't break 473 func BridgeNameForDevice(device string) string { 474 device = strings.Replace(device, ".", "-", -1) 475 switch { 476 case len(device) < 13: 477 return fmt.Sprintf("br-%s", device) 478 case len(device) == 13: 479 return fmt.Sprintf("b-%s", device) 480 case device[:2] == "en": 481 return fmt.Sprintf("b-%s", device[2:]) 482 default: 483 hash := crc32.Checksum([]byte(device), crc32.IEEETable) & 0xffffff 484 return fmt.Sprintf("b-%0.6x-%s", hash, device[len(device)-6:]) 485 } 486 } 487 488 // PopulateContainerLinkLayerDevices generates and returns link-layer devices 489 // for the input guest, setting each device to be a child of the corresponding 490 // bridge on the host machine. 491 // It also records when one of the desired spaces is available on the host 492 // machine, but not currently bridged. 493 func (p *BridgePolicy) PopulateContainerLinkLayerDevices( 494 host Machine, guest Container, askProviderForAddress bool, 495 ) (corenetwork.InterfaceInfos, error) { 496 guestSpaces, devicesPerSpace, err := p.findSpacesAndDevicesForContainer(host, guest) 497 if err != nil { 498 return nil, errors.Trace(err) 499 } 500 logger.Debugf("for container %q, found host devices spaces: %s", guest.Id(), formatDeviceMap(devicesPerSpace)) 501 spacesFound := make(corenetwork.SpaceInfos, 0) 502 devicesByName := make(map[string]LinkLayerDevice) 503 bridgeDeviceNames := make([]string, 0) 504 505 for spaceID, hostDevices := range devicesPerSpace { 506 for _, hostDevice := range hostDevices { 507 isFan := strings.HasPrefix(hostDevice.Name(), "fan-") 508 if !(isFan == (p.containerNetworkingMethod == "fan")) { 509 // This is not a suitable bridge for 510 // our container networking method. 511 continue 512 } 513 514 name := hostDevice.Name() 515 if hostDevice.Type() == corenetwork.BridgeDevice && !skippedDeviceNames.Contains(name) { 516 devicesByName[name] = hostDevice 517 bridgeDeviceNames = append(bridgeDeviceNames, name) 518 spaceInfo := p.spaces.GetByID(spaceID) 519 spacesFound = append(spacesFound, *spaceInfo) 520 } 521 } 522 } 523 524 missingSpaces := guestSpaces.Minus(spacesFound) 525 526 // Check if we are missing the default space and can fill it in with a local bridge 527 if len(missingSpaces) == 1 && 528 missingSpaces.ContainsID(corenetwork.AlphaSpaceId) && 529 p.containerNetworkingMethod == "local" { 530 531 localBridgeName := network.DefaultLXDBridge 532 if guest.ContainerType() == instance.KVM { 533 localBridgeName = network.DefaultKVMBridge 534 } 535 536 for _, hostDevice := range devicesPerSpace[corenetwork.AlphaSpaceId] { 537 name := hostDevice.Name() 538 if hostDevice.Type() == corenetwork.BridgeDevice && name == localBridgeName { 539 alphaInfo := p.spaces.GetByID(corenetwork.AlphaSpaceId) 540 missingSpaces = missingSpaces.Minus(corenetwork.SpaceInfos{*alphaInfo}) 541 devicesByName[name] = hostDevice 542 bridgeDeviceNames = append(bridgeDeviceNames, name) 543 spacesFound = append(spacesFound, *alphaInfo) 544 } 545 } 546 } 547 548 if len(missingSpaces) > 0 && len(bridgeDeviceNames) == 0 { 549 missingSpacesNames := missingSpaces.String() 550 logger.Warningf("container %q wants spaces %s could not find host %q bridges for %s, found bridges %s", 551 guest.Id(), guestSpaces, 552 host.Id(), missingSpacesNames, bridgeDeviceNames) 553 return nil, errors.Errorf("unable to find host bridge for space(s) %s for container %q", 554 missingSpacesNames, guest.Id()) 555 } 556 557 sortedBridgeDeviceNames := network.NaturallySortDeviceNames(bridgeDeviceNames...) 558 logger.Debugf("for container %q using host machine %q bridge devices: %s", 559 guest.Id(), host.Id(), network.QuoteSpaces(sortedBridgeDeviceNames)) 560 561 interfaces := make(corenetwork.InterfaceInfos, len(bridgeDeviceNames)) 562 563 for i, hostBridgeName := range sortedBridgeDeviceNames { 564 hostBridge := devicesByName[hostBridgeName] 565 newDevice, err := hostBridge.EthernetDeviceForBridge(fmt.Sprintf("eth%d", i), askProviderForAddress) 566 if err != nil { 567 return nil, errors.Trace(err) 568 } 569 interfaces[i] = newDevice 570 } 571 572 logger.Debugf("prepared container %q network config: %+v", guest.Id(), interfaces) 573 return interfaces, nil 574 } 575 576 func formatDeviceMap(spacesToDevices map[string][]LinkLayerDevice) string { 577 spaceIDs := make([]string, len(spacesToDevices)) 578 i := 0 579 for spaceID := range spacesToDevices { 580 spaceIDs[i] = spaceID 581 i++ 582 } 583 sort.Strings(spaceIDs) 584 var out []string 585 for _, id := range spaceIDs { 586 start := fmt.Sprintf("%q:[", id) 587 devices := spacesToDevices[id] 588 deviceNames := make([]string, len(devices)) 589 for i, dev := range devices { 590 deviceNames[i] = dev.Name() 591 } 592 deviceNames = network.NaturallySortDeviceNames(deviceNames...) 593 quotedNames := make([]string, len(deviceNames)) 594 for i, name := range deviceNames { 595 quotedNames[i] = fmt.Sprintf("%q", name) 596 } 597 out = append(out, start+strings.Join(quotedNames, ",")+"]") 598 } 599 return "map{" + strings.Join(out, ", ") + "}" 600 } 601 602 // ovsBridgeDevice wraps a LinkLayerDevice and overrides its reported type to 603 // BridgeDevice. 604 type ovsBridgeDevice struct { 605 wrappedDev LinkLayerDevice 606 } 607 608 // Type ensures the wrapped device's type is always reported as bridge. 609 func (dev ovsBridgeDevice) Type() corenetwork.LinkLayerDeviceType { return corenetwork.BridgeDevice } 610 func (dev ovsBridgeDevice) Name() string { return dev.wrappedDev.Name() } 611 func (dev ovsBridgeDevice) MACAddress() string { return dev.wrappedDev.MACAddress() } 612 func (dev ovsBridgeDevice) ParentName() string { return dev.wrappedDev.ParentName() } 613 func (dev ovsBridgeDevice) ParentDevice() (LinkLayerDevice, error) { 614 return dev.wrappedDev.ParentDevice() 615 } 616 func (dev ovsBridgeDevice) Addresses() ([]*state.Address, error) { return dev.wrappedDev.Addresses() } 617 func (dev ovsBridgeDevice) MTU() uint { return dev.wrappedDev.MTU() } 618 func (dev ovsBridgeDevice) IsUp() bool { return dev.wrappedDev.IsUp() } 619 func (dev ovsBridgeDevice) IsAutoStart() bool { return dev.wrappedDev.IsAutoStart() } 620 func (dev ovsBridgeDevice) EthernetDeviceForBridge( 621 name string, askForProviderAddress bool, 622 ) (corenetwork.InterfaceInfo, error) { 623 return dev.wrappedDev.EthernetDeviceForBridge(name, askForProviderAddress) 624 } 625 func (dev ovsBridgeDevice) VirtualPortType() corenetwork.VirtualPortType { 626 return dev.wrappedDev.VirtualPortType() 627 }