incus-mirror/internal/server/network/ovn/ovn_nb_actions.go
Stéphane Graber 066ef03eb9
incusd/network/ovn: Skip empty transactions in SetChassisGroupPriority
Signed-off-by: Stéphane Graber <stgraber@stgraber.org>
2026-07-31 07:53:29 -04:00

4652 lines
113 KiB
Go

package ovn
import (
"context"
"errors"
"fmt"
"maps"
"net"
"slices"
"strings"
"time"
ovsClient "github.com/ovn-kubernetes/libovsdb/client"
ovsModel "github.com/ovn-kubernetes/libovsdb/model"
"github.com/ovn-kubernetes/libovsdb/ovsdb"
"github.com/lxc/incus/v7/internal/iprange"
ovnNB "github.com/lxc/incus/v7/internal/server/network/ovn/schema/ovn-nb"
ovnSB "github.com/lxc/incus/v7/internal/server/network/ovn/schema/ovn-sb"
localUtil "github.com/lxc/incus/v7/internal/server/util"
"github.com/lxc/incus/v7/shared/util"
)
// OVNRouter OVN router name.
type OVNRouter string
// OVNRouterPort OVN router port name.
type OVNRouterPort string
// OVNSwitch OVN switch name.
type OVNSwitch string
// OVNSwitchPort OVN switch port name.
type OVNSwitchPort string
// OVNSwitchPortUUID OVN switch port UUID.
type OVNSwitchPortUUID string
// OVNChassisGroup OVN HA chassis group name.
type OVNChassisGroup string
// OVNDNSUUID OVN DNS record UUID.
type OVNDNSUUID string
// OVNDHCPOptionsUUID DHCP Options set UUID.
type OVNDHCPOptionsUUID string
// OVNPortGroup OVN port group name.
type OVNPortGroup string
// OVNPortGroupUUID OVN port group UUID.
type OVNPortGroupUUID string
// OVNLoadBalancer OVN load balancer name.
type OVNLoadBalancer string
// OVNAddressSet OVN address set for ACLs.
type OVNAddressSet string
// OVNIPAllocationOpts defines IP allocation settings that can be applied to a logical switch.
type OVNIPAllocationOpts struct {
PrefixIPv4 *net.IPNet
PrefixIPv6 *net.IPNet
ExcludeIPv4 []iprange.Range
}
// OVNIPv6AddressMode IPv6 router advertisement address mode.
type OVNIPv6AddressMode string
// OVNIPv6AddressModeSLAAC IPv6 SLAAC mode.
const OVNIPv6AddressModeSLAAC OVNIPv6AddressMode = "slaac"
// OVNIPv6AddressModeDHCPStateful IPv6 DHCPv6 stateful mode.
const OVNIPv6AddressModeDHCPStateful OVNIPv6AddressMode = "dhcpv6_stateful"
// OVNIPv6AddressModeDHCPStateless IPv6 DHCPv6 stateless mode.
const OVNIPv6AddressModeDHCPStateless OVNIPv6AddressMode = "dhcpv6_stateless"
// OVN External ID names used by Incus.
const (
ovnExtIDIncusSwitch = "incus_switch"
ovnExtIDIncusSwitchPort = "incus_switch_port"
ovnExtIDIncusProjectID = "incus_project_id"
ovnExtIDIncusPortGroup = "incus_port_group"
ovnExtIDIncusLocation = "incus_location"
)
// OVNIPv6RAOpts IPv6 router advertisements options that can be applied to a router.
type OVNIPv6RAOpts struct {
SendPeriodic bool
AddressMode OVNIPv6AddressMode
MinInterval time.Duration
MaxInterval time.Duration
RecursiveDNSServer net.IP
DNSSearchList []string
MTU uint32
}
// OVNDHCPOptsSet is an existing DHCP options set in the northbound database.
type OVNDHCPOptsSet struct {
UUID OVNDHCPOptionsUUID
CIDR *net.IPNet
}
// OVNDHCPv4Opts IPv4 DHCP options that can be applied to a switch port.
type OVNDHCPv4Opts struct {
ServerID net.IP
ServerMAC net.HardwareAddr
Router net.IP
RecursiveDNSServer []net.IP
DomainName string
LeaseTime time.Duration
MTU uint32
Netmask string
DNSSearchList []string
StaticRoutes string
}
// OVNDHCPv6Opts IPv6 DHCP option set that can be created (and then applied to a switch port by resulting ID).
type OVNDHCPv6Opts struct {
ServerID net.HardwareAddr
RecursiveDNSServer []net.IP
DNSSearchList []string
DHCPv6Stateless bool
}
// OVNSwitchPortOpts options that can be applied to a switch port.
type OVNSwitchPortOpts struct {
MAC net.HardwareAddr // Optional, if nil will be set to dynamic.
IPV4 string // Optional, if empty, allocate an address, if "none" then disable allocation.
IPV6 string // Optional, if empty, allocate an address, if "none" then disable allocation.
DHCPv4OptsID OVNDHCPOptionsUUID // Optional, if empty, no DHCPv4 enabled on port.
DHCPv6OptsID OVNDHCPOptionsUUID // Optional, if empty, no DHCPv6 enabled on port.
Parent OVNSwitchPort // Optional, if set a nested port is created.
VLAN uint16 // Optional, use with Parent to request a specific VLAN for nested port.
Location string // Optional, use to indicate the name of the server this port is bound to.
RouterPort OVNRouterPort // Optional, the name of the associated logical router port.
Promiscuous bool // Optional, controls whether to allow unknown traffic on the port.
Enabled *bool // Optional, if set controls the administrative state of the port.
}
// OVNACLRule represents an ACL rule that can be added to a logical switch or port group.
type OVNACLRule struct {
Direction string // Either "from-lport" or "to-lport".
Action string // Either "allow-related", "allow", "drop", or "reject".
Match string // Match criteria. See OVN Southbound database's Logical_Flow table match column usage.
Priority int // Priority (between 0 and 32767, inclusive). Higher values take precedence.
Log bool // Whether or not to log matched packets.
LogName string // Log label name (requires Log be true).
}
// OVNQoSRule represents a QoS rule that can be added to a logical switch.
type OVNQoSRule struct {
Direction string
Action map[string]int // Not settable, but ovn includes it in the db model
Bandwidth map[string]int
Match string
Priority int
}
// OVNLoadBalancerTarget represents an OVN load balancer Virtual IP target.
type OVNLoadBalancerTarget struct {
Address net.IP
Port uint64
}
// OVNLoadBalancerHealthCheck represents an OVN load balancer health checker.
type OVNLoadBalancerHealthCheck struct {
Interval int
Timeout int
SuccessCount int
FailureCount int
CheckerIPV4 net.IP
CheckerIPV6 net.IP
}
// OVNLoadBalancerVIP represents a OVN load balancer Virtual IP entry.
type OVNLoadBalancerVIP struct {
HealthCheck *OVNLoadBalancerHealthCheck
Protocol string // Either "tcp" or "udp". But only applies to port based VIPs.
ListenAddress net.IP
ListenPort uint64
Targets []OVNLoadBalancerTarget
}
// OVNRouterRoute represents a static route added to a logical router.
type OVNRouterRoute struct {
Prefix net.IPNet
NextHop net.IP
Port OVNRouterPort
Discard bool
}
// OVNRouterPolicy represents a router policy.
type OVNRouterPolicy struct {
Priority int
Match string
Action string
NextHop net.IP
}
// OVNRouterPeering represents a the configuration of a peering connection between two OVN logical routers.
type OVNRouterPeering struct {
LocalRouter OVNRouter
LocalRouterPort OVNRouterPort
LocalRouterPortMAC net.HardwareAddr
LocalRouterPortIPs []net.IPNet
LocalRouterRoutes []net.IPNet
TargetRouter OVNRouter
TargetRouterPort OVNRouterPort
TargetRouterPortMAC net.HardwareAddr
TargetRouterPortIPs []net.IPNet
TargetRouterRoutes []net.IPNet
}
// CreateLogicalRouter adds a named logical router.
// If mayExist is true, then an existing resource of the same name is not treated as an error.
func (o *NB) CreateLogicalRouter(ctx context.Context, routerName OVNRouter, mayExist bool) error {
logicalRouter := ovnNB.LogicalRouter{
Name: string(routerName),
}
// Check if already exists.
err := o.get(ctx, &logicalRouter)
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
if logicalRouter.UUID != "" {
if !mayExist {
return ErrExists
}
if logicalRouter.Options != nil {
return nil
}
}
// Set some options.
logicalRouter.Options = map[string]string{
"always_learn_from_arp_request": "false",
"dynamic_neigh_routers": "true",
}
// Create the record.
var operations []ovsdb.Operation
if logicalRouter.UUID == "" {
operations, err = o.client.Create(&logicalRouter)
if err != nil {
return err
}
} else {
operations, err = o.client.Where(&logicalRouter).Update(&logicalRouter)
if err != nil {
return err
}
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// DeleteLogicalRouter deletes a named logical router.
func (o *NB) DeleteLogicalRouter(ctx context.Context, routerName OVNRouter) error {
logicalRouter := ovnNB.LogicalRouter{
Name: string(routerName),
}
err := o.get(ctx, &logicalRouter)
if err != nil {
// Logical router is already gone.
if errors.Is(err, ErrNotFound) {
return nil
}
return err
}
operations, err := o.client.Where(&logicalRouter).Delete()
if err != nil {
return err
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// GetLogicalRouter gets the OVN database record for the router.
func (o *NB) GetLogicalRouter(ctx context.Context, routerName OVNRouter) (*ovnNB.LogicalRouter, error) {
logicalRouter := &ovnNB.LogicalRouter{
Name: string(routerName),
}
err := o.get(ctx, logicalRouter)
if err != nil {
return nil, err
}
return logicalRouter, nil
}
// CreateLogicalRouterNAT adds an SNAT or DNAT rule to a logical router to translate packets from intNet to extIP.
func (o *NB) CreateLogicalRouterNAT(ctx context.Context, routerName OVNRouter, natType string, intNet *net.IPNet, extIP net.IP, intIP net.IP, stateless bool, mayExist bool) error {
// Prepare the addresses.
var logicalIP string
var externalIP string
switch natType {
case "snat":
logicalIP = intNet.String()
externalIP = extIP.String()
case "dnat", "dnat_and_snat":
logicalIP = intIP.String()
externalIP = extIP.String()
default:
return fmt.Errorf("Invalid NAT rule type %q", natType)
}
// Get the logical router.
logicalRouter, err := o.GetLogicalRouter(ctx, routerName)
if err != nil {
return err
}
// Check if the rule already exists.
for _, natUUID := range logicalRouter.Nat {
natRule := ovnNB.NAT{
UUID: natUUID,
}
err = o.get(ctx, &natRule)
if err != nil {
return err
}
// Check if rule is of the requested type.
if natRule.Type != natType {
continue
}
// Check if matching our new rule.
if natRule.LogicalIP == logicalIP && natRule.ExternalIP == externalIP {
if mayExist {
return nil
}
return ErrExists
}
}
natRule := ovnNB.NAT{
UUID: "nat",
Options: map[string]string{"stateless": fmt.Sprintf("%v", stateless)},
Type: natType,
LogicalIP: logicalIP,
ExternalIP: externalIP,
}
operations := []ovsdb.Operation{}
createOps, err := o.client.Create(&natRule)
if err != nil {
return err
}
operations = append(operations, createOps...)
// Add it to the router.
updateOps, err := o.client.Where(logicalRouter).Mutate(logicalRouter, ovsModel.Mutation{
Field: &logicalRouter.Nat,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{natRule.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// DeleteLogicalRouterNAT deletes all NAT rules of a particular type from a logical router.
func (o *NB) DeleteLogicalRouterNAT(ctx context.Context, routerName OVNRouter, natType string, all bool, extIPs ...net.IP) error {
// Quick checks.
if all && len(extIPs) != 0 {
return errors.New("Can't ask for all NAT rules to be deleted and specify specific addresses")
}
// Get the logical router.
logicalRouter, err := o.GetLogicalRouter(ctx, routerName)
if err != nil {
return err
}
operations := []ovsdb.Operation{}
// Go through all rules.
for _, natUUID := range logicalRouter.Nat {
natRule := ovnNB.NAT{
UUID: natUUID,
}
err = o.get(ctx, &natRule)
if err != nil {
return err
}
// Check if rule is of the requested type.
if natRule.Type != natType {
continue
}
// Check if the address matches.
if !all {
found := false
for _, extIP := range extIPs {
if natRule.ExternalIP == extIP.String() {
found = true
break
}
}
if !found {
continue
}
}
// Delete the rule.
deleteOps, err := o.client.Where(&natRule).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
// Delete the entry from the logical router.
deleteOps, err = o.client.Where(logicalRouter).Mutate(logicalRouter, ovsModel.Mutation{
Field: &logicalRouter.Nat,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{natRule.UUID},
})
if err != nil {
return err
}
operations = append(operations, deleteOps...)
}
if len(operations) == 0 {
return nil
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// CreateStaticMACBinding ensures a Static_Mac_Binding row exists in NB for this router port/IP.
func (o *NB) CreateStaticMACBinding(ctx context.Context, portName OVNRouterPort, ip net.IP, mac net.HardwareAddr, mayExist bool) error {
binding := ovnNB.StaticMACBinding{
LogicalPort: string(portName),
IP: ip.String(),
MAC: mac.String(),
OverrideDynamicMAC: true,
}
// Check if the binding already exists.
err := o.get(ctx, &binding)
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
if binding.UUID != "" {
if !mayExist {
return ErrExists
}
if binding.LogicalPort == string(portName) && binding.IP == ip.String() && binding.MAC == mac.String() && binding.OverrideDynamicMAC {
return nil
}
}
// Create the record.
var operations []ovsdb.Operation
if binding.UUID == "" {
operations, err = o.client.Create(&binding)
if err != nil {
return err
}
} else {
binding.LogicalPort = string(portName)
binding.IP = ip.String()
binding.MAC = mac.String()
operations, err = o.client.Where(&binding).Update(&binding)
if err != nil {
return err
}
}
// Apply the changes.
reply, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(reply, operations)
if err != nil {
return err
}
return nil
}
// DeleteStaticMACBindings deletes all static MAC bindings from the specified router port.
// It allows filtering what family to flush.
func (o *NB) DeleteStaticMACBindings(ctx context.Context, portName OVNRouterPort, ipv4 bool, ipv6 bool) error {
if !ipv4 && !ipv6 {
return nil
}
// Get all the entries for this router port.
staticMACBindings := []ovnNB.StaticMACBinding{}
err := o.client.WhereCache(func(smb *ovnNB.StaticMACBinding) bool {
return smb.LogicalPort == string(portName)
}).List(ctx, &staticMACBindings)
if err != nil {
return err
}
// Check what needs to be deleted.
var operations []ovsdb.Operation
for _, binding := range staticMACBindings {
ip := net.ParseIP(binding.IP)
if ip != nil && ((ip.To4() != nil && ipv4) || (ip.To4() == nil && ipv6)) {
op, err := o.client.Where(&ovnNB.StaticMACBinding{UUID: binding.UUID}).Delete()
if err != nil {
return err
}
operations = append(operations, op...)
}
}
if len(operations) == 0 {
return nil
}
// Apply the database changes.
reply, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(reply, operations)
if err != nil {
return err
}
return nil
}
// CreateLogicalRouterRoute adds a static route to the logical router.
func (o *NB) CreateLogicalRouterRoute(ctx context.Context, routerName OVNRouter, mayExist bool, routes ...OVNRouterRoute) error {
// Get the logical router.
logicalRouter, err := o.GetLogicalRouter(ctx, routerName)
if err != nil {
return err
}
// Get the existing routes.
existingRoutes := make([]ovnNB.LogicalRouterStaticRoute, 0, len(logicalRouter.StaticRoutes))
for _, uuid := range logicalRouter.StaticRoutes {
route := ovnNB.LogicalRouterStaticRoute{
UUID: uuid,
}
err = o.get(ctx, &route)
if err != nil {
return err
}
existingRoutes = append(existingRoutes, route)
}
// Add the new routes.
operations := []ovsdb.Operation{}
for i, route := range routes {
// Check if already present.
exists := false
for _, existing := range existingRoutes {
if existing.IPPrefix != route.Prefix.String() {
continue
}
if existing.Nexthop == "discard" && !route.Discard {
continue
}
if existing.Nexthop != route.NextHop.String() {
continue
}
if existing.OutputPort == nil {
if string(route.Port) != "" {
continue
}
} else if *existing.OutputPort != string(route.Port) {
continue
}
if mayExist {
exists = true
break
}
return ErrExists
}
// Don't add duplicate entries.
if exists {
continue
}
// Create the new record.
staticRoute := ovnNB.LogicalRouterStaticRoute{
UUID: fmt.Sprintf("route_%d", i),
IPPrefix: route.Prefix.String(),
}
if string(route.Port) != "" {
value := string(route.Port)
staticRoute.OutputPort = &value
}
if route.Discard {
staticRoute.Nexthop = "discard"
} else {
staticRoute.Nexthop = route.NextHop.String()
}
createOps, err := o.client.Create(&staticRoute)
if err != nil {
return err
}
operations = append(operations, createOps...)
// Add it to the router.
updateOps, err := o.client.Where(logicalRouter).Mutate(logicalRouter, ovsModel.Mutation{
Field: &logicalRouter.StaticRoutes,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{staticRoute.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
if len(operations) == 0 {
return nil
}
// Apply the database changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// DeleteLogicalRouterRoute deletes a static route from the logical router.
func (o *NB) DeleteLogicalRouterRoute(ctx context.Context, routerName OVNRouter, prefixes ...net.IPNet) error {
// Get the logical router.
logicalRouter, err := o.GetLogicalRouter(ctx, routerName)
if err != nil {
return err
}
// Get the existing routes.
existingRoutes := make([]ovnNB.LogicalRouterStaticRoute, 0, len(logicalRouter.StaticRoutes))
for _, uuid := range logicalRouter.StaticRoutes {
route := ovnNB.LogicalRouterStaticRoute{
UUID: uuid,
}
err = o.client.Get(ctx, &route)
if err != nil {
return err
}
existingRoutes = append(existingRoutes, route)
}
// Delete the requested routes.
operations := []ovsdb.Operation{}
for _, prefix := range prefixes {
var route ovnNB.LogicalRouterStaticRoute
// Look for a matching entry.
for _, existing := range existingRoutes {
// Normal CIDR entry.
if existing.IPPrefix == prefix.String() {
route = existing
break
}
// IP-only entry.
ones, bits := prefix.Mask.Size()
if ones == bits && existing.IPPrefix == prefix.IP.String() {
route = existing
break
}
}
if route.UUID == "" {
continue
}
// Delete the entry.
deleteOps, err := o.client.Where(&route).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
// Remove from the router.
updateOps, err := o.client.Where(logicalRouter).Mutate(logicalRouter, ovsModel.Mutation{
Field: &logicalRouter.StaticRoutes,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{route.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
if len(operations) == 0 {
return nil
}
// Apply the database changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// GetLogicalRouterPort gets the OVN database record for the logical router port.
func (o *NB) GetLogicalRouterPort(ctx context.Context, portName OVNRouterPort) (*ovnNB.LogicalRouterPort, error) {
logicalRouterPort := &ovnNB.LogicalRouterPort{
Name: string(portName),
}
err := o.get(ctx, logicalRouterPort)
if err != nil {
return nil, err
}
return logicalRouterPort, nil
}
// CreateLogicalRouterPort adds a named logical router port to a logical router.
func (o *NB) CreateLogicalRouterPort(ctx context.Context, routerName OVNRouter, portName OVNRouterPort, mac net.HardwareAddr, gatewayMTU uint32, ipAddr []*net.IPNet, haChassisGroupName OVNChassisGroup, mayExist bool) error {
// Prepare the addresses.
networks := make([]string, 0, len(ipAddr))
for _, addr := range ipAddr {
networks = append(networks, addr.String())
}
// Prepare the new router port entry.
logicalRouterPort := ovnNB.LogicalRouterPort{
Name: string(portName),
UUID: "lrp",
}
// Check if the entry already exists.
err := o.get(ctx, &logicalRouterPort)
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
if logicalRouterPort.UUID != "lrp" && !mayExist {
return ErrExists
}
// Apply the configuration.
logicalRouterPort.MAC = mac.String()
logicalRouterPort.Networks = networks
if haChassisGroupName != "" {
haChassisGroup := ovnNB.HAChassisGroup{
Name: string(haChassisGroupName),
}
err = o.get(ctx, &haChassisGroup)
if err != nil {
return err
}
logicalRouterPort.HaChassisGroup = &haChassisGroup.UUID
}
if gatewayMTU > 0 {
if logicalRouterPort.Options == nil {
logicalRouterPort.Options = map[string]string{}
}
logicalRouterPort.Options["gateway_mtu"] = fmt.Sprintf("%d", gatewayMTU)
}
operations := []ovsdb.Operation{}
if logicalRouterPort.UUID != "lrp" {
// If it already exists, update it.
updateOps, err := o.client.Where(&logicalRouterPort).Update(&logicalRouterPort)
if err != nil {
return err
}
operations = append(operations, updateOps...)
} else {
// Else, create it.
createOps, err := o.client.Create(&logicalRouterPort)
if err != nil {
return err
}
operations = append(operations, createOps...)
// And connect it to the router.
logicalRouter := ovnNB.LogicalRouter{
Name: string(routerName),
}
updateOps, err := o.client.Where(&logicalRouter).Mutate(&logicalRouter, ovsModel.Mutation{
Field: &logicalRouter.Ports,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{logicalRouterPort.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
// Apply the database changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// DeleteLogicalRouterPort deletes a named logical router port from a logical router.
func (o *NB) DeleteLogicalRouterPort(ctx context.Context, routerName OVNRouter, portName OVNRouterPort) error {
operations := []ovsdb.Operation{}
// Get the logical router port.
logicalRouterPort := ovnNB.LogicalRouterPort{
Name: string(portName),
}
err := o.get(ctx, &logicalRouterPort)
if err != nil {
// Logical router port is already gone.
if errors.Is(err, ErrNotFound) {
return nil
}
return err
}
// Remove the port from the router.
logicalRouter := ovnNB.LogicalRouter{
Name: string(routerName),
}
updateOps, err := o.client.Where(&logicalRouter).Mutate(&logicalRouter, ovsModel.Mutation{
Field: &logicalRouter.Ports,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{logicalRouterPort.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
// Delete the port itself.
deleteOps, err := o.client.Where(&logicalRouterPort).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// UpdateLogicalRouterPort updates properties of a logical router port.
func (o *NB) UpdateLogicalRouterPort(ctx context.Context, portName OVNRouterPort, ipv6ra *OVNIPv6RAOpts) error {
lrp, err := o.GetLogicalRouterPort(ctx, portName)
if err != nil {
return err
}
if ipv6ra != nil {
ipv6conf := map[string]string{
"send_periodic": fmt.Sprintf("%t", ipv6ra.SendPeriodic),
}
if ipv6ra.AddressMode != "" {
ipv6conf["address_mode"] = string(ipv6ra.AddressMode)
}
if ipv6ra.MaxInterval > 0 {
ipv6conf["max_interval"] = fmt.Sprintf("%d", ipv6ra.MaxInterval/time.Second)
}
if ipv6ra.MinInterval > 0 {
ipv6conf["min_interval"] = fmt.Sprintf("%d", ipv6ra.MinInterval/time.Second)
}
if ipv6ra.MTU > 0 {
ipv6conf["mtu"] = fmt.Sprintf("%d", ipv6ra.MTU)
}
if len(ipv6ra.DNSSearchList) > 0 {
ipv6conf["dnssl"] = strings.Join(ipv6ra.DNSSearchList, ",")
}
if ipv6ra.RecursiveDNSServer != nil {
ipv6conf["rdnss"] = ipv6ra.RecursiveDNSServer.String()
}
lrp.Ipv6RaConfigs = ipv6conf
}
// Update the record.
operations, err := o.client.Where(lrp).Update(lrp)
if err != nil {
return err
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// GetLogicalSwitch gets the OVN database record for the switch.
func (o *NB) GetLogicalSwitch(ctx context.Context, switchName OVNSwitch) (*ovnNB.LogicalSwitch, error) {
logicalSwitch := &ovnNB.LogicalSwitch{
Name: string(switchName),
}
err := o.get(ctx, logicalSwitch)
if err != nil {
return nil, err
}
return logicalSwitch, nil
}
// CreateLogicalSwitch adds a named logical switch.
// If mayExist is true, then an existing resource of the same name is not treated as an error.
func (o *NB) CreateLogicalSwitch(ctx context.Context, switchName OVNSwitch, mayExist bool) error {
logicalSwitch := ovnNB.LogicalSwitch{
Name: string(switchName),
}
// Check if already exists.
err := o.get(ctx, &logicalSwitch)
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
if logicalSwitch.UUID != "" {
if mayExist {
return nil
}
return ErrExists
}
// Create the record.
operations, err := o.client.Create(&logicalSwitch)
if err != nil {
return err
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// DeleteLogicalSwitch deletes a named logical switch.
func (o *NB) DeleteLogicalSwitch(ctx context.Context, switchName OVNSwitch) error {
ls := ovnNB.LogicalSwitch{
Name: string(switchName),
}
// Check if the switch exists.
err := o.get(ctx, &ls)
if err != nil {
return err
}
// Delete the switch itself.
operations := []ovsdb.Operation{}
deleteOps, err := o.client.Where(&ls).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
// Delete all associated port groups.
portGroups := []ovnNB.PortGroup{}
err = o.client.WhereCache(func(pg *ovnNB.PortGroup) bool {
return pg.ExternalIDs != nil && pg.ExternalIDs[ovnExtIDIncusSwitch] == string(switchName)
}).List(ctx, &portGroups)
if err != nil {
return err
}
for _, pg := range portGroups {
deleteOps, err := o.client.Where(&pg).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
}
// Delete all associated DHCP options.
dhcpOptions := []ovnNB.DHCPOptions{}
err = o.client.WhereCache(func(do *ovnNB.DHCPOptions) bool {
return do.ExternalIDs != nil && do.ExternalIDs[ovnExtIDIncusSwitch] == string(switchName)
}).List(ctx, &dhcpOptions)
if err != nil {
return err
}
for _, do := range dhcpOptions {
deleteOps, err := o.client.Where(&do).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
}
// Delete all associated DNS records.
dnsRecords := []ovnNB.DNS{}
err = o.client.WhereCache(func(dr *ovnNB.DNS) bool {
return dr.ExternalIDs != nil && dr.ExternalIDs[ovnExtIDIncusSwitch] == string(switchName)
}).List(ctx, &dnsRecords)
if err != nil {
return err
}
for _, dr := range dnsRecords {
deleteOps, err := o.client.Where(&dr).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
}
// Apply the database changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// logicalSwitchParseExcludeIPs parses the ips into OVN exclude_ips format.
func (o *NB) logicalSwitchParseExcludeIPs(ips []iprange.Range) ([]string, error) {
excludeIPs := make([]string, 0, len(ips))
for _, v := range ips {
if v.Start == nil || v.Start.To4() == nil {
return nil, errors.New("Invalid exclude IPv4 range start address")
} else if v.End == nil {
excludeIPs = append(excludeIPs, v.Start.String())
} else {
if v.End != nil && v.End.To4() == nil {
return nil, errors.New("Invalid exclude IPv4 range end address")
}
excludeIPs = append(excludeIPs, fmt.Sprintf("%s..%s", v.Start.String(), v.End.String()))
}
}
return excludeIPs, nil
}
// UpdateLogicalSwitchIPAllocation sets the IP allocation config on the logical switch.
func (o *NB) UpdateLogicalSwitchIPAllocation(ctx context.Context, switchName OVNSwitch, opts *OVNIPAllocationOpts) error {
// Get the logical switch.
logicalSwitch, err := o.GetLogicalSwitch(ctx, switchName)
if err != nil {
return err
}
// Update the configuration.
if logicalSwitch.OtherConfig == nil {
logicalSwitch.OtherConfig = map[string]string{}
}
if opts.PrefixIPv4 != nil {
logicalSwitch.OtherConfig["subnet"] = opts.PrefixIPv4.String()
} else {
delete(logicalSwitch.OtherConfig, "subnet")
}
if opts.PrefixIPv6 != nil {
logicalSwitch.OtherConfig["ipv6_prefix"] = opts.PrefixIPv6.String()
} else {
delete(logicalSwitch.OtherConfig, "ipv6_prefix")
}
if len(opts.ExcludeIPv4) > 0 {
excludeIPs, err := o.logicalSwitchParseExcludeIPs(opts.ExcludeIPv4)
if err != nil {
return err
}
logicalSwitch.OtherConfig["exclude_ips"] = strings.Join(excludeIPs, " ")
} else {
delete(logicalSwitch.OtherConfig, "exclude_ips")
}
operations, err := o.client.Where(logicalSwitch).Update(logicalSwitch)
if err != nil {
return err
}
// Apply the database changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// UpdateLogicalSwitchDHCPv4Revervations sets the DHCPv4 IP reservations.
func (o *NB) UpdateLogicalSwitchDHCPv4Revervations(ctx context.Context, switchName OVNSwitch, reservedIPs []iprange.Range) error {
// Get the logical switch.
logicalSwitch, err := o.GetLogicalSwitch(ctx, switchName)
if err != nil {
return err
}
// Update the configuration.
if logicalSwitch.OtherConfig == nil {
logicalSwitch.OtherConfig = map[string]string{}
}
if len(reservedIPs) > 0 {
excludeIPs, err := o.logicalSwitchParseExcludeIPs(reservedIPs)
if err != nil {
return err
}
logicalSwitch.OtherConfig["exclude_ips"] = strings.Join(excludeIPs, " ")
} else {
delete(logicalSwitch.OtherConfig, "exclude_ips")
}
operations, err := o.client.Where(logicalSwitch).Update(logicalSwitch)
if err != nil {
return err
}
// Apply the database changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// GetLogicalSwitchDHCPv4Revervations gets the DHCPv4 IP reservations.
func (o *NB) GetLogicalSwitchDHCPv4Revervations(ctx context.Context, switchName OVNSwitch) ([]iprange.Range, error) {
// Get the logical switch.
logicalSwitch, err := o.GetLogicalSwitch(ctx, switchName)
if err != nil {
return nil, err
}
// Get the list of excluded IPs.
if logicalSwitch.OtherConfig == nil {
return []iprange.Range{}, nil
}
// Check if no dynamic IPs set.
excludeIPsRaw := strings.TrimSpace(logicalSwitch.OtherConfig["exclude_ips"])
if excludeIPsRaw == "" || excludeIPsRaw == "[]" {
return []iprange.Range{}, nil
}
excludeIPsRaw, err = unquote(excludeIPsRaw)
if err != nil {
return nil, fmt.Errorf("Failed unquoting exclude_ips: %w", err)
}
excludeIPsParts := util.SplitNTrimSpace(strings.TrimSpace(excludeIPsRaw), " ", -1, true)
excludeIPs := make([]iprange.Range, 0, len(excludeIPsParts))
for _, excludeIPsPart := range excludeIPsParts {
ip := net.ParseIP(excludeIPsPart) // Check if single IP part.
if ip == nil {
// Check if IP range part.
start, end, found := strings.Cut(excludeIPsPart, "..")
if !found {
return nil, fmt.Errorf("Unrecognised exclude_ips part: %q", excludeIPsPart)
}
startIP := net.ParseIP(start)
endIP := net.ParseIP(end)
if startIP == nil || endIP == nil {
return nil, fmt.Errorf("Invalid exclude_ips range: %q", excludeIPsPart)
}
// Add range IP part to list.
excludeIPs = append(excludeIPs, iprange.Range{Start: startIP, End: endIP})
} else {
// Add single IP part to list.
excludeIPs = append(excludeIPs, iprange.Range{Start: ip})
}
}
return excludeIPs, nil
}
// UpdateLogicalSwitchDHCPv4Options creates or updates a DHCPv4 option set associated with the specified switchName
// and subnet. If uuid is non-empty then the record that exists with that ID is updated, otherwise a new record
// is created.
func (o *NB) UpdateLogicalSwitchDHCPv4Options(ctx context.Context, switchName OVNSwitch, uuid OVNDHCPOptionsUUID, subnet *net.IPNet, opts *OVNDHCPv4Opts) error {
dhcpOption := ovnNB.DHCPOptions{}
if uuid != "" {
// Load the existing record.
dhcpOption.UUID = string(uuid)
err := o.get(ctx, &dhcpOption)
if err != nil {
return err
}
}
if dhcpOption.ExternalIDs == nil {
dhcpOption.ExternalIDs = map[string]string{}
}
if dhcpOption.Options == nil {
dhcpOption.Options = map[string]string{}
}
dhcpOption.ExternalIDs[ovnExtIDIncusSwitch] = string(switchName)
dhcpOption.Cidr = subnet.String()
dhcpOption.Options["server_id"] = opts.ServerID.String()
dhcpOption.Options["server_mac"] = opts.ServerMAC.String()
dhcpOption.Options["lease_time"] = fmt.Sprintf("%d", opts.LeaseTime/time.Second)
if opts.Router != nil {
dhcpOption.Options["router"] = opts.Router.String()
} else {
delete(dhcpOption.Options, "router")
}
if len(opts.DNSSearchList) > 0 {
// Special quoting to allow domain names.
dhcpOption.Options["domain_search_list"] = fmt.Sprintf(`"%s"`, strings.Join(opts.DNSSearchList, ","))
} else {
delete(dhcpOption.Options, "domain_search_list")
}
if len(opts.RecursiveDNSServer) > 0 {
nsIPs := make([]string, 0, len(opts.RecursiveDNSServer))
for _, nsIP := range opts.RecursiveDNSServer {
if nsIP.To4() == nil {
continue // Only include IPv4 addresses.
}
nsIPs = append(nsIPs, nsIP.String())
}
dhcpOption.Options["dns_server"] = fmt.Sprintf("{%s}", strings.Join(nsIPs, ","))
} else {
delete(dhcpOption.Options, "dns_server")
}
if opts.DomainName != "" {
// Special quoting to allow domain names.
dhcpOption.Options["domain_name"] = fmt.Sprintf(`"%s"`, opts.DomainName)
} else {
delete(dhcpOption.Options, "domain_name")
}
if opts.MTU > 0 {
dhcpOption.Options["mtu"] = fmt.Sprintf("%d", opts.MTU)
} else {
delete(dhcpOption.Options, "mtu")
}
if opts.Netmask != "" {
dhcpOption.Options["netmask"] = opts.Netmask
} else {
delete(dhcpOption.Options, "netmask")
}
if opts.StaticRoutes != "" {
dhcpOption.Options["classless_static_route"] = fmt.Sprintf("{%s}", opts.StaticRoutes)
} else {
delete(dhcpOption.Options, "classless_static_route")
}
// Prepare the changes.
operations := []ovsdb.Operation{}
if dhcpOption.UUID == "" {
// Create a new record.
createOps, err := o.client.Create(&dhcpOption)
if err != nil {
return err
}
operations = append(operations, createOps...)
} else {
// Update the record.
updateOps, err := o.client.Where(&dhcpOption).Update(&dhcpOption)
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
// Apply the database changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// UpdateLogicalSwitchDHCPv6Options creates or updates a DHCPv6 option set associated with the specified switchName
// and subnet. If uuid is non-empty then the record that exists with that ID is updated, otherwise a new record
// is created.
func (o *NB) UpdateLogicalSwitchDHCPv6Options(ctx context.Context, switchName OVNSwitch, uuid OVNDHCPOptionsUUID, subnet *net.IPNet, opts *OVNDHCPv6Opts) error {
dhcpOption := ovnNB.DHCPOptions{}
if uuid != "" {
// Load the existing record.
dhcpOption.UUID = string(uuid)
err := o.get(ctx, &dhcpOption)
if err != nil {
return err
}
}
if dhcpOption.ExternalIDs == nil {
dhcpOption.ExternalIDs = map[string]string{}
}
if dhcpOption.Options == nil {
dhcpOption.Options = map[string]string{}
}
dhcpOption.ExternalIDs[ovnExtIDIncusSwitch] = string(switchName)
dhcpOption.Cidr = subnet.String()
dhcpOption.Options["server_id"] = opts.ServerID.String()
if opts.DHCPv6Stateless {
dhcpOption.Options["dhcpv6_stateless"] = "true"
} else {
dhcpOption.Options["dhcpv6_stateless"] = "false"
}
if len(opts.DNSSearchList) > 0 {
// Special quoting to allow domain names.
dhcpOption.Options["domain_search"] = fmt.Sprintf(`"%s"`, strings.Join(opts.DNSSearchList, ","))
} else {
delete(dhcpOption.Options, "domain_search")
}
if opts.RecursiveDNSServer != nil {
nsIPs := make([]string, 0, len(opts.RecursiveDNSServer))
for _, nsIP := range opts.RecursiveDNSServer {
if nsIP.To4() != nil {
continue // Only include IPv6 addresses.
}
nsIPs = append(nsIPs, nsIP.String())
}
dhcpOption.Options["dns_server"] = fmt.Sprintf("{%s}", strings.Join(nsIPs, ","))
} else {
delete(dhcpOption.Options, "dns_server")
}
// Prepare the changes.
operations := []ovsdb.Operation{}
if dhcpOption.UUID == "" {
// Create a new record.
createOps, err := o.client.Create(&dhcpOption)
if err != nil {
return err
}
operations = append(operations, createOps...)
} else {
// Update the record.
updateOps, err := o.client.Where(&dhcpOption).Update(&dhcpOption)
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
// Apply the database changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// GetLogicalSwitchDHCPOptions retrieves the existing DHCP options defined for a logical switch.
func (o *NB) GetLogicalSwitchDHCPOptions(ctx context.Context, switchName OVNSwitch) ([]OVNDHCPOptsSet, error) {
// Get the matching DHCP options.
dhcpOptions := []ovnNB.DHCPOptions{}
err := o.client.WhereCache(func(do *ovnNB.DHCPOptions) bool {
return do.ExternalIDs != nil && do.ExternalIDs[ovnExtIDIncusSwitch] == string(switchName)
}).List(ctx, &dhcpOptions)
if err != nil {
return nil, err
}
dhcpOpts := []OVNDHCPOptsSet{}
for _, dhcpOption := range dhcpOptions {
_, cidr, err := net.ParseCIDR(dhcpOption.Cidr)
if err != nil {
return nil, err
}
dhcpOpts = append(dhcpOpts, OVNDHCPOptsSet{
UUID: OVNDHCPOptionsUUID(dhcpOption.UUID),
CIDR: cidr,
})
}
return dhcpOpts, nil
}
// DeleteLogicalSwitchDHCPOption deletes the specified DHCP options defined for a switch.
func (o *NB) DeleteLogicalSwitchDHCPOption(ctx context.Context, switchName OVNSwitch, uuids ...OVNDHCPOptionsUUID) error {
operations := []ovsdb.Operation{}
// Prepare deletion requests.
for _, uuid := range uuids {
dhcpOption := ovnNB.DHCPOptions{
UUID: string(uuid),
}
deleteOps, err := o.client.Where(&dhcpOption).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
}
// Check if there's anything to do.
if len(operations) == 0 {
return nil
}
// Apply the database changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// UpdateLogicalSwitchACLRules applies a set of rules to the specified logical switch. Any existing rules are removed.
func (o *NB) UpdateLogicalSwitchACLRules(ctx context.Context, switchName OVNSwitch, aclRules ...OVNACLRule) error {
operations := []ovsdb.Operation{}
// Get the logical switch.
ls, err := o.GetLogicalSwitch(ctx, switchName)
if err != nil {
return err
}
// Remove any existing rules assigned to the entity.
for _, aclUUID := range ls.ACLs {
updateOps, err := o.client.Where(ls).Mutate(ls, ovsModel.Mutation{
Field: &ls.ACLs,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{aclUUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
// Add new rules.
externalIDs := map[string]string{
ovnExtIDIncusSwitch: string(switchName),
}
createOps, err := o.aclRuleAddOperations(ctx, "logical_switch", string(switchName), externalIDs, nil, aclRules...)
if err != nil {
return err
}
operations = append(operations, createOps...)
// Apply the database changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// logicalSwitchPortQoSRules returns the QoS rule UUIDs belonging to a logical switch port.
func (o *NB) logicalSwitchPortQoSRules(ctx context.Context, portName OVNSwitchPort) ([]string, error) {
var qosRules []ovnNB.QoS
err := o.client.WhereCache(func(qosRule *ovnNB.QoS) bool {
return qosRule.ExternalIDs != nil && qosRule.ExternalIDs[ovnExtIDIncusSwitchPort] == string(portName)
}).List(ctx, &qosRules)
if err != nil {
return nil, err
}
var ruleUUIDs []string
for _, qosRule := range qosRules {
ruleUUIDs = append(ruleUUIDs, qosRule.UUID)
}
return ruleUUIDs, nil
}
// logicalSwitchPortACLRules returns the ACL rule UUIDs belonging to a logical switch port.
func (o *NB) logicalSwitchPortACLRules(ctx context.Context, portName OVNSwitchPort) ([]string, error) {
acls := []ovnNB.ACL{}
err := o.client.WhereCache(func(acl *ovnNB.ACL) bool {
return acl.ExternalIDs != nil && acl.ExternalIDs[ovnExtIDIncusSwitchPort] == string(portName)
}).List(ctx, &acls)
if err != nil {
return nil, err
}
ruleUUIDs := []string{}
for _, acl := range acls {
ruleUUIDs = append(ruleUUIDs, acl.UUID)
}
return ruleUUIDs, nil
}
// GetLogicalSwitchPorts returns a map of logical switch ports (name and UUID) for a switch.
// Includes non-instance ports, such as the router port.
func (o *NB) GetLogicalSwitchPorts(ctx context.Context, switchName OVNSwitch) (map[OVNSwitchPort]OVNSwitchPortUUID, error) {
// Get the logical switch.
logicalSwitch, err := o.GetLogicalSwitch(ctx, switchName)
if err != nil {
return nil, err
}
ports := make(map[OVNSwitchPort]OVNSwitchPortUUID, len(logicalSwitch.Ports))
for _, portUUID := range logicalSwitch.Ports {
// Get the logical switch port.
lsp := ovnNB.LogicalSwitchPort{
UUID: portUUID,
}
err := o.get(ctx, &lsp)
if err != nil {
return nil, err
}
ports[OVNSwitchPort(lsp.Name)] = OVNSwitchPortUUID(lsp.UUID)
}
return ports, nil
}
// GetLogicalSwitchActivePorts returns a map of enabled logical switch ports (name and UUID) for a switch.
func (o *NB) GetLogicalSwitchActivePorts(ctx context.Context, switchName OVNSwitch) (map[OVNSwitchPort]OVNSwitchPortUUID, error) {
// Get the logical switch.
logicalSwitch, err := o.GetLogicalSwitch(ctx, switchName)
if err != nil {
return nil, err
}
ports := make(map[OVNSwitchPort]OVNSwitchPortUUID, len(logicalSwitch.Ports))
for _, portUUID := range logicalSwitch.Ports {
// Get the logical switch port.
lsp := ovnNB.LogicalSwitchPort{
UUID: portUUID,
}
err := o.get(ctx, &lsp)
if err != nil {
return nil, err
}
if lsp.Enabled != nil && !*lsp.Enabled {
continue
}
ports[OVNSwitchPort(lsp.Name)] = OVNSwitchPortUUID(lsp.UUID)
}
return ports, nil
}
// GetLogicalSwitchIPs returns a list of IPs associated to each port connected to switch.
func (o *NB) GetLogicalSwitchIPs(ctx context.Context, switchName OVNSwitch) (map[OVNSwitchPort][]net.IP, error) {
lsps := []ovnNB.LogicalSwitchPort{}
err := o.client.WhereCache(func(lsp *ovnNB.LogicalSwitchPort) bool {
return lsp.ExternalIDs != nil && lsp.ExternalIDs[ovnExtIDIncusSwitch] == string(switchName)
}).List(ctx, &lsps)
if err != nil {
return nil, err
}
portIPs := make(map[OVNSwitchPort][]net.IP, len(lsps))
for _, lsp := range lsps {
var ips []net.IP
// Extract all addresses from the Addresses field.
for _, address := range lsp.Addresses {
for _, entry := range util.SplitNTrimSpace(address, " ", -1, true) {
ip := net.ParseIP(entry)
if ip != nil {
ips = append(ips, ip)
}
}
}
// Extract all addresses from the DynamicAddresses field.
if lsp.DynamicAddresses != nil {
for _, entry := range util.SplitNTrimSpace(*lsp.DynamicAddresses, " ", -1, true) {
ip := net.ParseIP(entry)
if ip != nil {
ips = append(ips, ip)
}
}
}
portIPs[OVNSwitchPort(lsp.Name)] = ips
}
return portIPs, nil
}
// GetLogicalSwitchPortUUID returns the logical switch port UUID.
func (o *NB) GetLogicalSwitchPortUUID(ctx context.Context, portName OVNSwitchPort) (OVNSwitchPortUUID, error) {
// Get the logical switch port.
lsp := ovnNB.LogicalSwitchPort{
Name: string(portName),
}
err := o.get(ctx, &lsp)
if err != nil {
return "", err
}
return OVNSwitchPortUUID(lsp.UUID), nil
}
// CreateLogicalSwitchPort adds a named logical switch port to a logical switch, and sets options if provided.
// If mayExist is true, then an existing resource of the same name is not treated as an error.
func (o *NB) CreateLogicalSwitchPort(ctx context.Context, switchName OVNSwitch, portName OVNSwitchPort, opts *OVNSwitchPortOpts, mayExist bool) error {
// Prepare the new switch port entry.
logicalSwitchPort := ovnNB.LogicalSwitchPort{
Name: string(portName),
UUID: "lsp",
}
// Check if the entry already exists.
err := o.get(ctx, &logicalSwitchPort)
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
if logicalSwitchPort.UUID != "lsp" && !mayExist {
return ErrExists
}
if logicalSwitchPort.ExternalIDs == nil {
logicalSwitchPort.ExternalIDs = map[string]string{}
}
// Set switch port options if supplied.
if opts != nil {
// Created nested VLAN port if requested.
if opts.Parent != "" {
parentName := string(opts.Parent)
tag := int(opts.VLAN)
logicalSwitchPort.ParentName = &parentName
logicalSwitchPort.Tag = &tag
}
if opts.RouterPort != "" {
logicalSwitchPort.Type = "router"
logicalSwitchPort.Addresses = []string{"router"}
logicalSwitchPort.Options = map[string]string{"router-port": string(opts.RouterPort)}
} else {
if (opts.IPV4 == "none" && opts.IPV6 != "none") || (opts.IPV6 == "none" && opts.IPV4 != "none") {
return errors.New("OVN doesn't support disabling IP allocation on only one protocol")
}
addresses := []string{}
if opts.IPV4 != "none" && opts.IPV6 != "none" {
address := []string{}
if opts.MAC != nil {
address = append(address, opts.MAC.String())
}
if opts.IPV4 == "" && opts.IPV6 == "" {
address = append(address, "dynamic")
} else {
if opts.IPV4 != "" {
address = append(address, opts.IPV4)
}
if opts.IPV6 != "" {
address = append(address, opts.IPV6)
}
}
addresses = append(addresses, strings.Join(address, " "))
}
if opts.DHCPv4OptsID != "" {
dhcp4opts := string(opts.DHCPv4OptsID)
logicalSwitchPort.Dhcpv4Options = &dhcp4opts
}
if opts.DHCPv6OptsID != "" {
dhcp6opts := string(opts.DHCPv6OptsID)
logicalSwitchPort.Dhcpv6Options = &dhcp6opts
}
if opts.Promiscuous {
addresses = append(addresses, "unknown")
}
logicalSwitchPort.Addresses = addresses
}
if opts.Location != "" {
logicalSwitchPort.ExternalIDs[ovnExtIDIncusLocation] = opts.Location
}
if opts.Enabled != nil {
logicalSwitchPort.Enabled = opts.Enabled
}
}
logicalSwitchPort.ExternalIDs[ovnExtIDIncusSwitch] = string(switchName)
// Apply the changes.
operations := []ovsdb.Operation{}
if logicalSwitchPort.UUID != "lsp" {
// If it already exists, update it.
updateOps, err := o.client.Where(&logicalSwitchPort).Update(&logicalSwitchPort)
if err != nil {
return err
}
operations = append(operations, updateOps...)
} else {
// Else, create it.
createOps, err := o.client.Create(&logicalSwitchPort)
if err != nil {
return err
}
operations = append(operations, createOps...)
// And connect it to the switch.
logicalSwitch := ovnNB.LogicalSwitch{
Name: string(switchName),
}
updateOps, err := o.client.Where(&logicalSwitch).Mutate(&logicalSwitch, ovsModel.Mutation{
Field: &logicalSwitch.Ports,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{logicalSwitchPort.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
// Apply the database changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// GetLogicalSwitchPortIPs returns a list of IPs for a switch port.
func (o *NB) GetLogicalSwitchPortIPs(ctx context.Context, portName OVNSwitchPort) ([]net.IP, error) {
lsp := ovnNB.LogicalSwitchPort{
Name: string(portName),
}
err := o.get(ctx, &lsp)
if err != nil {
return nil, err
}
addresses := []net.IP{}
for _, address := range lsp.Addresses {
for _, entry := range strings.Split(address, " ") {
ip := net.ParseIP(entry)
if ip != nil {
addresses = append(addresses, ip)
}
}
}
if lsp.DynamicAddresses != nil {
for _, entry := range strings.Split(*lsp.DynamicAddresses, " ") {
ip := net.ParseIP(entry)
if ip != nil {
addresses = append(addresses, ip)
}
}
}
return addresses, nil
}
// GetLogicalSwitchPortDynamicIPs returns a list of dynamic IPs for a switch port.
func (o *NB) GetLogicalSwitchPortDynamicIPs(ctx context.Context, portName OVNSwitchPort) ([]net.IP, error) {
lsp := &ovnNB.LogicalSwitchPort{
Name: string(portName),
}
err := o.get(ctx, lsp)
if err != nil {
return []net.IP{}, err
}
// Check if no dynamic IPs set.
if lsp.DynamicAddresses == nil {
return []net.IP{}, nil
}
dynamicAddresses := strings.Split(*lsp.DynamicAddresses, " ")
dynamicIPs := make([]net.IP, 0, len(dynamicAddresses))
for _, dynamicAddress := range dynamicAddresses {
ip := net.ParseIP(dynamicAddress)
if ip != nil {
dynamicIPs = append(dynamicIPs, ip)
}
}
return dynamicIPs, nil
}
// GetLogicalSwitchPortLocation returns the last set location of a logical switch port.
func (o *NB) GetLogicalSwitchPortLocation(ctx context.Context, portName OVNSwitchPort) (string, error) {
lsp := ovnNB.LogicalSwitchPort{
Name: string(portName),
}
err := o.get(ctx, &lsp)
if err != nil {
return "", err
}
if lsp.ExternalIDs == nil {
return "", ErrNotFound
}
val, ok := lsp.ExternalIDs[ovnExtIDIncusLocation]
if !ok {
return "", ErrNotFound
}
return val, nil
}
// UpdateLogicalSwitchPortDHCP updates the DHCP options on the logical switch port.
func (o *NB) UpdateLogicalSwitchPortDHCP(ctx context.Context, portName OVNSwitchPort, dhcpV4UUID OVNDHCPOptionsUUID, dhcpV6UUID OVNDHCPOptionsUUID) error {
// Get the logical switch port.
lsp := ovnNB.LogicalSwitchPort{
Name: string(portName),
}
err := o.get(ctx, &lsp)
if err != nil {
return err
}
if dhcpV4UUID != "" {
dhcp4opts := string(dhcpV4UUID)
lsp.Dhcpv4Options = &dhcp4opts
}
if dhcpV6UUID != "" {
dhcp6opts := string(dhcpV6UUID)
lsp.Dhcpv6Options = &dhcp6opts
}
// Update the record.
operations, err := o.client.Where(&lsp).Update(&lsp)
if err != nil {
return err
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// UpdateLogicalSwitchPortOptions sets the options for a logical switch port.
func (o *NB) UpdateLogicalSwitchPortOptions(ctx context.Context, portName OVNSwitchPort, options map[string]string) error {
// Get the logical switch port.
lsp := ovnNB.LogicalSwitchPort{
Name: string(portName),
}
err := o.get(ctx, &lsp)
if err != nil {
return err
}
// Apply the changes.
if lsp.Options == nil {
lsp.Options = map[string]string{}
}
maps.Copy(lsp.Options, options)
// Update the record.
operations, err := o.client.Where(&lsp).Update(&lsp)
if err != nil {
return err
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// UpdateLogicalSwitchPortEnabled sets the administrative state of a logical switch port.
func (o *NB) UpdateLogicalSwitchPortEnabled(ctx context.Context, portName OVNSwitchPort, enabled bool) error {
// Get the logical switch port.
lsp := ovnNB.LogicalSwitchPort{
Name: string(portName),
}
err := o.get(ctx, &lsp)
if err != nil {
return err
}
lsp.Enabled = &enabled
// Update the record.
operations, err := o.client.Where(&lsp).Update(&lsp)
if err != nil {
return err
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// UpdateLogicalSwitchPortDNS sets up the switch port DNS records for the DNS name.
// Returns the DNS record UUID, IPv4 and IPv6 addresses used for DNS records.
func (o *NB) UpdateLogicalSwitchPortDNS(ctx context.Context, switchName OVNSwitch, portName OVNSwitchPort, dnsName string, dnsIPs []net.IP) (OVNDNSUUID, error) {
// Get the logical switch.
ls, err := o.GetLogicalSwitch(ctx, switchName)
if err != nil {
return "", err
}
// Check if existing DNS record exists for switch port.
dnsRecords := []ovnNB.DNS{}
err = o.client.WhereCache(func(dnsRecord *ovnNB.DNS) bool {
return dnsRecord.ExternalIDs != nil && dnsRecord.ExternalIDs[ovnExtIDIncusSwitchPort] == string(portName)
}).List(ctx, &dnsRecords)
if err != nil {
return "", err
}
var dnsRecord ovnNB.DNS
if len(dnsRecords) == 1 {
dnsRecord = dnsRecords[0]
} else if len(dnsRecords) == 0 {
dnsRecord = ovnNB.DNS{}
} else {
return "", errors.New("Found more than one matching DNS record")
}
// Make sure the external IDs are set.
if dnsRecord.ExternalIDs == nil {
dnsRecord.ExternalIDs = map[string]string{}
}
dnsRecord.ExternalIDs[ovnExtIDIncusSwitch] = string(switchName)
dnsRecord.ExternalIDs[ovnExtIDIncusSwitchPort] = string(portName)
// Add the records.
if dnsRecord.Records == nil {
dnsRecord.Records = map[string]string{}
}
// Only include DNS name record if IPs supplied.
if len(dnsIPs) > 0 {
var dnsIPsStr strings.Builder
for i, dnsIP := range dnsIPs {
if i > 0 {
dnsIPsStr.WriteString(" ")
}
dnsIPsStr.WriteString(dnsIP.String())
dnsRecord.Records[strings.TrimSuffix(localUtil.ReverseDNS(dnsIP), ".")] = strings.ToLower(dnsName)
}
dnsRecord.Records[strings.ToLower(dnsName)] = dnsIPsStr.String()
} else {
// Clear any existing DNS name if no IPs supplied.
dnsRecord.Records = map[string]string{}
}
operations := []ovsdb.Operation{}
if dnsRecord.UUID == "" {
// Create a new record.
dnsRecord.UUID = "record"
createOps, err := o.client.Create(&dnsRecord)
if err != nil {
return "", err
}
operations = append(operations, createOps...)
// Add it to the logical switch.
updateOps, err := o.client.Where(ls).Mutate(ls, ovsModel.Mutation{
Field: &ls.DNSRecords,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{dnsRecord.UUID},
})
if err != nil {
return "", err
}
operations = append(operations, updateOps...)
} else {
// Update the record.
updateOps, err := o.client.Where(&dnsRecord).Update(&dnsRecord)
if err != nil {
return "", err
}
operations = append(operations, updateOps...)
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return "", err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return "", err
}
if dnsRecord.UUID == "record" {
dnsRecord.UUID = resp[0].UUID.GoUUID
}
return OVNDNSUUID(dnsRecord.UUID), nil
}
// GetLogicalSwitchPortDNS returns the logical switch port DNS info (UUID, name and IPs).
func (o *NB) GetLogicalSwitchPortDNS(ctx context.Context, portName OVNSwitchPort) (OVNDNSUUID, string, []net.IP, error) {
dnsRecords := []ovnNB.DNS{}
err := o.client.WhereCache(func(dnsRecord *ovnNB.DNS) bool {
return dnsRecord.ExternalIDs != nil && dnsRecord.ExternalIDs[ovnExtIDIncusSwitchPort] == string(portName)
}).List(ctx, &dnsRecords)
if err != nil {
return "", "", nil, err
}
if len(dnsRecords) != 1 {
return "", "", nil, nil
}
var ips []net.IP
var dnsName string
for key, value := range dnsRecords[0].Records {
dnsName = key
for _, ipPart := range strings.Split(value, " ") {
ip := net.ParseIP(strings.TrimSpace(ipPart))
if ip != nil {
ips = append(ips, ip)
}
}
}
return OVNDNSUUID(dnsRecords[0].UUID), dnsName, ips, nil
}
// logicalSwitchPortDeleteDNSOperations returns a list of ovsdb operations to remove DNS records from a switch port.
// If destroyEntry the DNS entry record itself is also removed, otherwise it is just cleared but left in place.
func (o *NB) logicalSwitchPortDeleteDNSOperations(ctx context.Context, switchName OVNSwitch, dnsUUID OVNDNSUUID, destroyEntry bool) ([]ovsdb.Operation, error) {
operations := []ovsdb.Operation{}
// Get the DNS entry.
dnsEntry := ovnNB.DNS{
UUID: string(dnsUUID),
}
err := o.get(ctx, &dnsEntry)
if err != nil {
return nil, err
}
// Get the logical switch.
ls, err := o.GetLogicalSwitch(ctx, switchName)
if err != nil {
return nil, err
}
// Remove from the logical switch.
updateOps, err := o.client.Where(ls).Mutate(ls, ovsModel.Mutation{
Field: &ls.DNSRecords,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{dnsEntry.UUID},
})
if err != nil {
return nil, err
}
operations = append(operations, updateOps...)
if destroyEntry {
deleteOps, err := o.client.Where(&dnsEntry).Delete()
if err != nil {
return nil, err
}
operations = append(operations, deleteOps...)
} else {
dnsEntry.Records = nil
updateOps, err := o.client.Where(&dnsEntry).Update(&dnsEntry)
if err != nil {
return nil, err
}
operations = append(operations, updateOps...)
}
return operations, nil
}
// DeleteLogicalSwitchPortDNS removes DNS records from a switch port.
// If destroyEntry the DNS entry record itself is also removed, otherwise it is just cleared but left in place.
func (o *NB) DeleteLogicalSwitchPortDNS(ctx context.Context, switchName OVNSwitch, dnsUUID OVNDNSUUID, destroyEntry bool) error {
// Remove DNS record association from switch, and remove DNS record entry itself.
operations, err := o.logicalSwitchPortDeleteDNSOperations(ctx, switchName, dnsUUID, destroyEntry)
if err != nil {
return err
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// logicalSwitchPortDeleteAppendArgs adds the commands to delete the specified logical switch port.
func (o *NB) logicalSwitchPortDeleteOperations(ctx context.Context, switchName OVNSwitch, portName OVNSwitchPort) ([]ovsdb.Operation, error) {
operations := []ovsdb.Operation{}
// Get the logical switch port.
logicalSwitchPort := ovnNB.LogicalSwitchPort{
Name: string(portName),
}
err := o.get(ctx, &logicalSwitchPort)
if err != nil {
// Logical switch port is already gone.
if errors.Is(err, ErrNotFound) {
return nil, nil
}
return nil, err
}
// Remove the port from the switch.
logicalSwitch := ovnNB.LogicalSwitch{
Name: string(switchName),
}
updateOps, err := o.client.Where(&logicalSwitch).Mutate(&logicalSwitch, ovsModel.Mutation{
Field: &logicalSwitch.Ports,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{logicalSwitchPort.UUID},
})
if err != nil {
return nil, err
}
operations = append(operations, updateOps...)
// Delete the port itself.
deleteOps, err := o.client.Where(&logicalSwitchPort).Delete()
if err != nil {
return nil, err
}
operations = append(operations, deleteOps...)
return operations, nil
}
// DeleteLogicalSwitchPort deletes a named logical switch port.
func (o *NB) DeleteLogicalSwitchPort(ctx context.Context, switchName OVNSwitch, portName OVNSwitchPort) error {
// Get the delete operations.
operations, err := o.logicalSwitchPortDeleteOperations(ctx, switchName, portName)
if err != nil {
return err
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// CleanupLogicalSwitchPort deletes the named logical switch port and its associated config.
func (o *NB) CleanupLogicalSwitchPort(ctx context.Context, portName OVNSwitchPort, switchName OVNSwitch, switchPortGroupName OVNPortGroup, dnsUUID OVNDNSUUID) error {
operations := []ovsdb.Operation{}
// Remove any existing rules assigned to the entity.
removeACLRuleUUIDs, err := o.logicalSwitchPortACLRules(ctx, portName)
if err != nil {
return err
}
deleteOps, err := o.aclRuleDeleteOperations(ctx, "port_group", string(switchPortGroupName), removeACLRuleUUIDs)
if err != nil {
return err
}
operations = append(operations, deleteOps...)
removeQoSRuleUUIDs, err := o.logicalSwitchPortQoSRules(ctx, portName)
if err != nil {
return err
}
deleteOps, err = o.qosRuleDeleteOperations(ctx, "logical_switch", string(switchName), removeQoSRuleUUIDs)
if err != nil {
return err
}
operations = append(operations, deleteOps...)
// Remove logical switch port.
deleteOps, err = o.logicalSwitchPortDeleteOperations(ctx, switchName, portName)
if err != nil {
return err
}
operations = append(operations, deleteOps...)
// Remove DNS records.
if dnsUUID != "" {
deleteOps, err := o.logicalSwitchPortDeleteDNSOperations(ctx, switchName, dnsUUID, true)
if err != nil {
return err
}
operations = append(operations, deleteOps...)
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// UpdateLogicalSwitchPortLinkRouter links a logical switch port to a logical router port.
func (o *NB) UpdateLogicalSwitchPortLinkRouter(ctx context.Context, switchPortName OVNSwitchPort, routerPortName OVNRouterPort) error {
// Get the logical switch port.
lsp := ovnNB.LogicalSwitchPort{
Name: string(switchPortName),
}
err := o.get(ctx, &lsp)
if err != nil {
return err
}
// Update the fields.
lsp.Type = "router"
lsp.Addresses = []string{"router"}
if lsp.Options == nil {
lsp.Options = map[string]string{}
}
lsp.Options["nat-addresses"] = "router"
lsp.Options["router-port"] = string(routerPortName)
// Update the record.
operations, err := o.client.Where(&lsp).Update(&lsp)
if err != nil {
return err
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// UpdateLogicalSwitchPortLinkProviderNetwork links a logical switch port to a provider network.
func (o *NB) UpdateLogicalSwitchPortLinkProviderNetwork(ctx context.Context, switchPortName OVNSwitchPort, extNetworkName string) error {
// Get the logical switch port.
lsp := ovnNB.LogicalSwitchPort{
Name: string(switchPortName),
}
err := o.get(ctx, &lsp)
if err != nil {
return err
}
// Update the fields.
lsp.Type = "localnet"
lsp.Addresses = []string{"unknown"}
if lsp.Options == nil {
lsp.Options = map[string]string{}
}
lsp.Options["network_name"] = extNetworkName
// Update the record.
operations, err := o.client.Where(&lsp).Update(&lsp)
if err != nil {
return err
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// CreateChassisGroup adds a new HA chassis group.
// If mayExist is true, then an existing resource of the same name is not treated as an error.
func (o *NB) CreateChassisGroup(ctx context.Context, haChassisGroupName OVNChassisGroup, mayExist bool) error {
// Define the new group.
haChassisGroup := ovnNB.HAChassisGroup{
Name: string(haChassisGroupName),
}
// Check if already exists.
err := o.get(ctx, &haChassisGroup)
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
if haChassisGroup.UUID != "" {
if mayExist {
return nil
}
return ErrExists
}
// Create the record.
operations, err := o.client.Create(&haChassisGroup)
if err != nil {
return err
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// DeleteChassisGroup deletes an HA chassis group.
func (o *NB) DeleteChassisGroup(ctx context.Context, haChassisGroupName OVNChassisGroup) error {
// Get the current chassis group.
haChassisGroup := ovnNB.HAChassisGroup{
Name: string(haChassisGroupName),
}
err := o.get(ctx, &haChassisGroup)
if err != nil {
// Already gone.
if errors.Is(err, ErrNotFound) {
return nil
}
return err
}
// Delete the chassis group.
deleteOps, err := o.client.Where(&haChassisGroup).Delete()
if err != nil {
return err
}
resp, err := o.client.Transact(ctx, deleteOps...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, deleteOps)
if err != nil {
return err
}
return nil
}
// SetChassisGroupPriority sets a given priority for the chassis ID in the chassis group..
func (o *NB) SetChassisGroupPriority(ctx context.Context, haChassisGroupName OVNChassisGroup, chassisID string, priority int) error {
operations := []ovsdb.Operation{}
// Get the chassis group.
haGroup := ovnNB.HAChassisGroup{
Name: string(haChassisGroupName),
}
err := o.get(ctx, &haGroup)
if err != nil {
return err
}
// Look for the chassis in the group.
var haChassis ovnNB.HAChassis
for _, entry := range haGroup.HaChassis {
chassis := ovnNB.HAChassis{UUID: entry}
err = o.get(ctx, &chassis)
if err != nil {
return err
}
if chassis.ChassisName == chassisID {
haChassis = chassis
break
}
}
if haChassis.UUID == "" {
// If asked to remove, then we're done.
if priority < 0 {
return nil
}
// No entry found, add a new one.
haChassis = ovnNB.HAChassis{
UUID: "chassis",
ChassisName: chassisID,
Priority: int(priority),
}
createOps, err := o.client.Create(&haChassis)
if err != nil {
return err
}
operations = append(operations, createOps...)
// Add the HA Chassis to the group.
updateOps, err := o.client.Where(&haGroup).Mutate(&haGroup, ovsModel.Mutation{
Field: &haGroup.HaChassis,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{haChassis.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
} else if priority < 0 {
// Proceed with removing the entry.
deleteOps, err := o.client.Where(&haChassis).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
// And removing it from the group.
updateOps, err := o.client.Where(&haGroup).Mutate(&haGroup, ovsModel.Mutation{
Field: &haGroup.HaChassis,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{haChassis.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
} else if haChassis.Priority != priority {
// Found but wrong priority, correct it.
haChassis.Priority = int(priority)
updateOps, err := o.client.Where(&haChassis).Update(&haChassis)
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
// Skip the transaction if nothing changed.
if len(operations) == 0 {
return nil
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// GetPortGroupInfo returns the port group UUID or empty string if port doesn't exist, and whether the port group has
// any ACL rules defined on it.
func (o *NB) GetPortGroupInfo(ctx context.Context, portGroupName OVNPortGroup) (OVNPortGroupUUID, bool, error) {
pg := &ovnNB.PortGroup{
Name: string(portGroupName),
}
err := o.get(ctx, pg)
if err != nil {
if errors.Is(err, ovsClient.ErrNotFound) {
return "", false, nil
}
return "", false, err
}
return OVNPortGroupUUID(pg.UUID), len(pg.ACLs) > 0, nil
}
// CreatePortGroup creates a new port group and optionally adds logical switch ports to the group.
func (o *NB) CreatePortGroup(ctx context.Context, projectID int64, portGroupName OVNPortGroup, associatedPortGroups []OVNPortGroup, associatedSwitch OVNSwitch, initialPortMembers ...OVNSwitchPort) error {
// Resolve the initial members.
members := []string{}
for _, portName := range initialPortMembers {
lsp := ovnNB.LogicalSwitchPort{
Name: string(portName),
}
err := o.get(ctx, &lsp)
if err != nil {
return err
}
members = append(members, lsp.UUID)
}
// Create the port group.
pg := ovnNB.PortGroup{
Name: string(portGroupName),
Ports: members,
ExternalIDs: map[string]string{
ovnExtIDIncusProjectID: fmt.Sprintf("%d", projectID),
},
}
if len(associatedPortGroups) > 0 || associatedSwitch != "" {
if len(associatedPortGroups) > 0 {
var parts []string
for _, v := range associatedPortGroups {
parts = append(parts, string(v))
}
pg.ExternalIDs[ovnExtIDIncusPortGroup] = string(strings.Join(parts, ","))
}
if associatedSwitch != "" {
pg.ExternalIDs[ovnExtIDIncusSwitch] = string(associatedSwitch)
}
}
// Create the record.
operations, err := o.client.Create(&pg)
if err != nil {
return err
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// DeletePortGroup deletes port groups along with their ACL rules.
func (o *NB) DeletePortGroup(ctx context.Context, portGroupNames ...OVNPortGroup) error {
operations := []ovsdb.Operation{}
for _, portGroupName := range portGroupNames {
pg := ovnNB.PortGroup{
Name: string(portGroupName),
}
err := o.get(ctx, &pg)
if err != nil {
if errors.Is(err, ErrNotFound) {
// Already gone.
continue
}
}
deleteOps, err := o.client.Where(&pg).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
}
// Check if we have anything to do.
if len(operations) == 0 {
return nil
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// GetPortGroupsByProject finds the port groups that are associated to the project ID.
func (o *NB) GetPortGroupsByProject(ctx context.Context, projectID int64) ([]OVNPortGroup, error) {
portGroups := []ovnNB.PortGroup{}
err := o.client.WhereCache(func(pg *ovnNB.PortGroup) bool {
return pg.ExternalIDs != nil && pg.ExternalIDs[ovnExtIDIncusProjectID] == fmt.Sprintf("%d", projectID)
}).List(ctx, &portGroups)
if err != nil {
return nil, err
}
pgNames := make([]OVNPortGroup, 0, len(portGroups))
for _, portGroup := range portGroups {
pgNames = append(pgNames, OVNPortGroup(portGroup.Name))
}
return pgNames, nil
}
// GetPortGroupsByPort returns the names of all port groups that contain the given port UUID as a member.
func (o *NB) GetPortGroupsByPort(ctx context.Context, portUUID OVNSwitchPortUUID) ([]OVNPortGroup, error) {
portGroups := []ovnNB.PortGroup{}
err := o.client.WhereCache(func(pg *ovnNB.PortGroup) bool {
return slices.Contains(pg.Ports, string(portUUID))
}).List(ctx, &portGroups)
if err != nil {
return nil, err
}
pgNames := make([]OVNPortGroup, 0, len(portGroups))
for _, portGroup := range portGroups {
pgNames = append(pgNames, OVNPortGroup(portGroup.Name))
}
return pgNames, nil
}
// UpdatePortGroupMembers adds/removes logical switch ports (by UUID) to/from existing port groups.
func (o *NB) UpdatePortGroupMembers(ctx context.Context, addMembers map[OVNPortGroup][]OVNSwitchPortUUID, removeMembers map[OVNPortGroup][]OVNSwitchPortUUID) error {
operations := []ovsdb.Operation{}
for portGroupName, portMemberUUIDs := range addMembers {
pg := ovnNB.PortGroup{
Name: string(portGroupName),
}
for _, portUUID := range portMemberUUIDs {
updateOps, err := o.client.Where(&pg).Mutate(&pg, ovsModel.Mutation{
Field: &pg.Ports,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{string(portUUID)},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
}
for portGroupName, portMemberUUIDs := range removeMembers {
pg := ovnNB.PortGroup{
Name: string(portGroupName),
}
for _, portUUID := range portMemberUUIDs {
updateOps, err := o.client.Where(&pg).Mutate(&pg, ovsModel.Mutation{
Field: &pg.Ports,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{string(portUUID)},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
}
// Check if anything changed.
if len(operations) == 0 {
return nil
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// UpdatePortGroupACLRules applies a set of rules to the specified port group. Any existing rules are removed.
func (o *NB) UpdatePortGroupACLRules(ctx context.Context, portGroupName OVNPortGroup, matchReplace map[string]string, aclRules ...OVNACLRule) error {
operations := []ovsdb.Operation{}
// Get the port group.
pg := ovnNB.PortGroup{
Name: string(portGroupName),
}
err := o.get(ctx, &pg)
if err != nil {
return err
}
// Remove any existing rules assigned to the port group.
for _, aclUUID := range pg.ACLs {
updateOps, err := o.client.Where(&pg).Mutate(&pg, ovsModel.Mutation{
Field: &pg.ACLs,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{aclUUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
// Add new rules.
externalIDs := map[string]string{
ovnExtIDIncusPortGroup: string(portGroupName),
}
createOps, err := o.aclRuleAddOperations(ctx, "port_group", string(portGroupName), externalIDs, matchReplace, aclRules...)
if err != nil {
return err
}
operations = append(operations, createOps...)
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// SetLogicalSwitchQoSRules replaces the set of QoS rules assigned to the specified logical switch port.
func (o *NB) SetLogicalSwitchQoSRules(ctx context.Context, switchName OVNSwitch, switchPortName OVNSwitchPort, qosRules ...OVNQoSRule) error {
var operations []ovsdb.Operation
// Remove any existing rules for the port.
removeQoSRuleUUIDs, err := o.logicalSwitchPortQoSRules(ctx, switchPortName)
if err != nil {
return err
}
deleteOps, err := o.qosRuleDeleteOperations(ctx, "logical_switch", string(switchName), removeQoSRuleUUIDs)
if err != nil {
return err
}
operations = append(operations, deleteOps...)
// Add new rules.
externalIDs := map[string]string{
ovnExtIDIncusSwitch: string(switchName),
ovnExtIDIncusSwitchPort: string(switchPortName),
}
createOps, err := o.qosRuleAddOperations(ctx, "logical_switch", string(switchName), externalIDs, nil, qosRules...)
if err != nil {
return err
}
operations = append(operations, createOps...)
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
func (o *NB) qosRuleAddOperations(ctx context.Context, entityTable string, entityName string, externalIDs map[string]string, matchReplace map[string]string, qosRules ...OVNQoSRule) ([]ovsdb.Operation, error) {
operations := []ovsdb.Operation{}
for i, rule := range qosRules {
// Perform any replacements requested on the Match string.
for find, replace := range matchReplace {
rule.Match = strings.ReplaceAll(rule.Match, find, replace)
}
// Add new QoS.
qos := ovnNB.QoS{
UUID: fmt.Sprintf("qos%d", i),
Action: rule.Action,
Direction: rule.Direction,
Bandwidth: rule.Bandwidth,
Priority: rule.Priority,
Match: rule.Match,
ExternalIDs: map[string]string{},
}
maps.Copy(qos.ExternalIDs, externalIDs)
createOps, err := o.client.Create(&qos)
if err != nil {
return nil, err
}
operations = append(operations, createOps...)
if entityTable != "logical_switch" {
return nil, fmt.Errorf("Unsupported entity table %q", entityTable)
}
ls := ovnNB.LogicalSwitch{
Name: entityName,
}
// Add QOS rule to entity.
updateOps, err := o.client.Where(&ls).Mutate(&ls, ovsModel.Mutation{
Field: &ls.QOSRules,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{qos.UUID},
})
if err != nil {
return nil, err
}
operations = append(operations, updateOps...)
}
return operations, nil
}
func (o *NB) qosRuleDeleteOperations(ctx context.Context, entityTable string, entityName string, qosRuleUUIDs []string) ([]ovsdb.Operation, error) {
var operations []ovsdb.Operation
for _, qosRuleUUID := range qosRuleUUIDs {
// Get the QOS.
qos := ovnNB.QoS{
UUID: qosRuleUUID,
}
err := o.get(ctx, &qos)
if err != nil {
return nil, err
}
// Delete the QOS.
deleteOps, err := o.client.Where(&qos).Delete()
if err != nil {
return nil, err
}
operations = append(operations, deleteOps...)
if entityTable != "logical_switch" {
return nil, fmt.Errorf("Unsupported entity table %q", entityTable)
}
ls := ovnNB.LogicalSwitch{
Name: entityName,
}
// Remove QOS rule from entity.
updateOps, err := o.client.Where(&ls).Mutate(&ls, ovsModel.Mutation{
Field: &ls.QOSRules,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{qos.UUID},
})
if err != nil {
return nil, err
}
operations = append(operations, updateOps...)
}
return operations, nil
}
// aclRuleAddOperations returns the operations to add the provided ACL rules to the specified OVN entity.
func (o *NB) aclRuleAddOperations(ctx context.Context, entityTable string, entityName string, externalIDs map[string]string, matchReplace map[string]string, aclRules ...OVNACLRule) ([]ovsdb.Operation, error) {
operations := []ovsdb.Operation{}
for i, rule := range aclRules {
// Perform any replacements requested on the Match string.
for find, replace := range matchReplace {
rule.Match = strings.ReplaceAll(rule.Match, find, replace)
}
// Add new ACL.
acl := ovnNB.ACL{
UUID: fmt.Sprintf("acl%d", i),
Action: rule.Action,
Direction: rule.Direction,
Priority: rule.Priority,
Match: rule.Match,
ExternalIDs: map[string]string{},
}
if rule.Log {
acl.Log = true
if rule.LogName != "" {
logName := rule.LogName
acl.Name = &logName
}
}
maps.Copy(acl.ExternalIDs, externalIDs)
createOps, err := o.client.Create(&acl)
if err != nil {
return nil, err
}
operations = append(operations, createOps...)
// Add ACL rule to entity.
switch entityTable {
case "logical_switch":
ls := ovnNB.LogicalSwitch{
Name: entityName,
}
updateOps, err := o.client.Where(&ls).Mutate(&ls, ovsModel.Mutation{
Field: &ls.ACLs,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{acl.UUID},
})
if err != nil {
return nil, err
}
operations = append(operations, updateOps...)
case "port_group":
pg := ovnNB.PortGroup{
Name: entityName,
}
updateOps, err := o.client.Where(&pg).Mutate(&pg, ovsModel.Mutation{
Field: &pg.ACLs,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{acl.UUID},
})
if err != nil {
return nil, err
}
operations = append(operations, updateOps...)
default:
return nil, fmt.Errorf("Unsupported entity table %q", entityTable)
}
}
return operations, nil
}
// aclRuleDeleteOperations returns the operations that delete the provided ACL rules from the specified OVN entity.
func (o *NB) aclRuleDeleteOperations(ctx context.Context, entityTable string, entityName string, aclRuleUUIDs []string) ([]ovsdb.Operation, error) {
operations := []ovsdb.Operation{}
for _, aclRuleUUID := range aclRuleUUIDs {
// Get the ACL.
acl := ovnNB.ACL{
UUID: aclRuleUUID,
}
err := o.get(ctx, &acl)
if err != nil {
return nil, err
}
// Delete the ACL.
deleteOps, err := o.client.Where(&acl).Delete()
if err != nil {
return nil, err
}
operations = append(operations, deleteOps...)
// Remove ACL rule from entity.
switch entityTable {
case "logical_switch":
ls := ovnNB.LogicalSwitch{
Name: entityName,
}
updateOps, err := o.client.Where(&ls).Mutate(&ls, ovsModel.Mutation{
Field: &ls.ACLs,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{acl.UUID},
})
if err != nil {
return nil, err
}
operations = append(operations, updateOps...)
case "port_group":
pg := ovnNB.PortGroup{
Name: entityName,
}
updateOps, err := o.client.Where(&pg).Mutate(&pg, ovsModel.Mutation{
Field: &pg.ACLs,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{acl.UUID},
})
if err != nil {
return nil, err
}
operations = append(operations, updateOps...)
default:
return nil, fmt.Errorf("Unsupported entity table %q", entityTable)
}
}
return operations, nil
}
// UpdatePortGroupPortACLRules applies a set of rules for the logical switch port in the specified port group.
// Any existing rules for that logical switch port in the port group are removed.
func (o *NB) UpdatePortGroupPortACLRules(ctx context.Context, portGroupName OVNPortGroup, portName OVNSwitchPort, aclRules ...OVNACLRule) error {
operations := []ovsdb.Operation{}
// Remove any existing rules assigned to the entity.
removeACLRuleUUIDs, err := o.logicalSwitchPortACLRules(ctx, portName)
if err != nil {
return err
}
deleteOps, err := o.aclRuleDeleteOperations(ctx, "port_group", string(portGroupName), removeACLRuleUUIDs)
if err != nil {
return err
}
operations = append(operations, deleteOps...)
// Add new rules.
externalIDs := map[string]string{
ovnExtIDIncusPortGroup: string(portGroupName),
ovnExtIDIncusSwitchPort: string(portName),
}
createOps, err := o.aclRuleAddOperations(ctx, "port_group", string(portGroupName), externalIDs, nil, aclRules...)
if err != nil {
return err
}
operations = append(operations, createOps...)
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// ClearPortGroupPortACLRules clears any rules assigned to the logical switch port in the specified port group.
func (o *NB) ClearPortGroupPortACLRules(ctx context.Context, portGroupName OVNPortGroup, portName OVNSwitchPort) error {
// Remove any existing rules assigned to the entity.
removeACLRuleUUIDs, err := o.logicalSwitchPortACLRules(ctx, portName)
if err != nil {
return err
}
operations, err := o.aclRuleDeleteOperations(ctx, "port_group", string(portGroupName), removeACLRuleUUIDs)
if err != nil {
return err
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// CreateLoadBalancer creates a new load balancer (if doesn't exist) on the specified router and switch.
// Providing an empty set of vips will delete the load balancer.
func (o *NB) CreateLoadBalancer(ctx context.Context, loadBalancerName OVNLoadBalancer, routerName OVNRouter, switchName OVNSwitch, vips ...OVNLoadBalancerVIP) error {
lbTCPName := fmt.Sprintf("%s-tcp", loadBalancerName)
lbUDPName := fmt.Sprintf("%s-udp", loadBalancerName)
operations := []ovsdb.Operation{}
// ipToString wraps IPv6 addresses in square brackets.
ipToString := func(ip net.IP) string {
if ip.To4() == nil {
return fmt.Sprintf("[%s]", ip.String())
}
return ip.String()
}
// Remove existing load balancers if they exist.
for _, name := range []string{lbTCPName, lbUDPName} {
lb := ovnNB.LoadBalancer{
Name: name,
}
err := o.get(ctx, &lb)
if err == nil || errors.Is(err, ErrTooMany) {
// Delete the load balancer (by name in case there are duplicates).
lb := ovnNB.LoadBalancer{
Name: name,
}
deleteOps, err := o.client.Where(&lb).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
} else if !errors.Is(err, ErrNotFound) {
return err
}
}
// Get the logical router.
lr, err := o.GetLogicalRouter(ctx, routerName)
if err != nil {
return err
}
// Get the logical switch.
ls, err := o.GetLogicalSwitch(ctx, switchName)
if err != nil {
return err
}
// Define the new load-balancers.
lbtcp := &ovnNB.LoadBalancer{
UUID: "lbtcp",
Name: lbTCPName,
Protocol: &ovnNB.LoadBalancerProtocolTCP,
}
lbudp := &ovnNB.LoadBalancer{
UUID: "lbudp",
Name: lbUDPName,
Protocol: &ovnNB.LoadBalancerProtocolUDP,
}
// Keep track of health check settings.
healthChecks := map[string]*OVNLoadBalancerHealthCheck{}
// Build up the commands to add VIPs to the load balancer.
for _, r := range vips {
if r.ListenAddress == nil {
return errors.New("Missing VIP listen address")
}
if len(r.Targets) == 0 {
return errors.New("Missing VIP target(s)")
}
for _, lb := range []*ovnNB.LoadBalancer{lbtcp, lbudp} {
if r.Protocol != "" && r.Protocol != *lb.Protocol {
continue
}
if lb.Vips == nil {
lb.Vips = map[string]string{}
}
targetAddresses := []string{}
for _, target := range r.Targets {
if (r.ListenPort > 0 && target.Port <= 0) || (target.Port > 0 && r.ListenPort <= 0) {
return errors.New("The listen and target ports must be specified together")
}
// Determine the target address.
var targetAddress string
if r.ListenPort > 0 {
targetAddress = fmt.Sprintf("%s:%d", ipToString(target.Address), target.Port)
} else {
targetAddress = ipToString(target.Address)
}
targetAddresses = append(targetAddresses, targetAddress)
}
// Determine the listen address.
var listenAddress string
if r.ListenPort > 0 {
listenAddress = fmt.Sprintf("%s:%d", ipToString(r.ListenAddress), r.ListenPort)
} else {
listenAddress = ipToString(r.ListenAddress)
}
lb.Vips[listenAddress] = strings.Join(targetAddresses, ",")
if r.HealthCheck != nil {
healthChecks[listenAddress] = r.HealthCheck
}
}
}
// Create any used load-balancer.
lbhcCount := 0
for _, lb := range []*ovnNB.LoadBalancer{lbtcp, lbudp} {
if len(lb.Vips) == 0 {
continue
}
// Create healthcheck records.
lb.HealthCheck = []string{}
lb.IPPortMappings = map[string]string{}
for vip, targets := range lb.Vips {
// Check if an health check exists for the VIP.
healthCheck := healthChecks[vip]
if healthCheck == nil {
continue
}
// Create the record.
lbhc := &ovnNB.LoadBalancerHealthCheck{
UUID: fmt.Sprintf("lbhc%d", lbhcCount),
Options: map[string]string{},
Vip: vip,
}
// Set some defaults.
if healthCheck.FailureCount == 0 {
healthCheck.FailureCount = 3
}
if healthCheck.SuccessCount == 0 {
healthCheck.SuccessCount = 3
}
if healthCheck.Interval == 0 {
healthCheck.Interval = 10
}
if healthCheck.Timeout == 0 {
healthCheck.Timeout = 30
}
// Apply the settings.
lbhc.Options = map[string]string{
"failure_count": fmt.Sprintf("%d", healthCheck.FailureCount),
"success_count": fmt.Sprintf("%d", healthCheck.SuccessCount),
"interval": fmt.Sprintf("%d", healthCheck.Interval),
"timeout": fmt.Sprintf("%d", healthCheck.Timeout),
}
// Create the load balancer health checker.
createOps, err := o.client.Create(lbhc)
if err != nil {
return err
}
operations = append(operations, createOps...)
// Link LB to LBHC.
lb.HealthCheck = append(lb.HealthCheck, lbhc.UUID)
// Set up the port bindings.
for _, target := range strings.Split(targets, ",") {
// Split host and port.
host, _, err := net.SplitHostPort(target)
if err == nil {
target = host
}
// Skip existing entries.
_, ok := lb.IPPortMappings[target]
if ok {
continue
}
// Lookup the logical switch port.
var lspName string
for _, port := range ls.Ports {
lsp := ovnNB.LogicalSwitchPort{
UUID: port,
}
err = o.get(ctx, &lsp)
if err != nil {
return err
}
if lsp.ExternalIDs == nil {
continue
}
if OVNSwitch(lsp.ExternalIDs[ovnExtIDIncusSwitch]) != switchName {
continue
}
if lsp.DynamicAddresses != nil {
fields := strings.Split(*lsp.DynamicAddresses, " ")
if slices.Contains(fields, target) {
lspName = lsp.Name
break
}
}
for _, address := range lsp.Addresses {
fields := strings.Split(address, " ")
if slices.Contains(fields, target) {
lspName = lsp.Name
break
}
}
}
if lspName == "" {
return fmt.Errorf("Couldn't find a logical switch port for %q", target)
}
ip := net.ParseIP(target)
if ip.To4() == nil {
if healthCheck.CheckerIPV6 == nil {
return errors.New("No IPv6 address for load balancer health check")
}
lb.IPPortMappings[fmt.Sprintf("[%s]", target)] = fmt.Sprintf("%s:[%s]", lspName, healthCheck.CheckerIPV6.String())
} else {
if healthCheck.CheckerIPV4 == nil {
return errors.New("No IPv4 address for load balancer health check")
}
lb.IPPortMappings[target] = fmt.Sprintf("%s:%s", lspName, healthCheck.CheckerIPV4.String())
}
}
lbhcCount++
}
// Create the record.
createOps, err := o.client.Create(lb)
if err != nil {
return err
}
operations = append(operations, createOps...)
// Add to the router.
updateOps, err := o.client.Where(lr).Mutate(lr, ovsModel.Mutation{
Field: &lr.LoadBalancer,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{lb.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
// Add to the switch.
updateOps, err = o.client.Where(ls).Mutate(ls, ovsModel.Mutation{
Field: &ls.LoadBalancer,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{lb.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
// Check if anything to delete.
if len(operations) == 0 {
return nil
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// DeleteLoadBalancer deletes the specified load balancer(s).
func (o *NB) DeleteLoadBalancer(ctx context.Context, loadBalancerNames ...OVNLoadBalancer) error {
operations := []ovsdb.Operation{}
for _, loadBalancerName := range loadBalancerNames {
// Check for a TCP load-balancer.
lb := ovnNB.LoadBalancer{
Name: fmt.Sprintf("%s-tcp", loadBalancerName),
}
err := o.get(ctx, &lb)
if err == nil {
// Delete the load balancer.
deleteOps, err := o.client.Where(&lb).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
} else if !errors.Is(err, ErrNotFound) {
return err
}
// Check for a UDP load-balancer.
lb = ovnNB.LoadBalancer{
Name: fmt.Sprintf("%s-udp", loadBalancerName),
}
err = o.get(ctx, &lb)
if err == nil {
// Delete the load balancer.
deleteOps, err := o.client.Where(&lb).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
} else if !errors.Is(err, ErrNotFound) {
return err
}
}
// Check if anything to delete.
if len(operations) == 0 {
return nil
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// GetLoadBalancer gets the OVN database record for the load balancer.
func (o *NB) GetLoadBalancer(ctx context.Context, lbName OVNLoadBalancer) (*ovnNB.LoadBalancer, error) {
lb := &ovnNB.LoadBalancer{
Name: string(lbName),
}
err := o.get(ctx, lb)
if err != nil {
return nil, err
}
return lb, nil
}
// GetLoadBalancersByStatusUpdate locate load-balancer(s) which are affected by a particular service monitor update.
func (o *NB) GetLoadBalancersByStatusUpdate(ctx context.Context, mon ovnSB.ServiceMonitor) ([]ovnNB.LoadBalancer, error) {
lbs := []ovnNB.LoadBalancer{}
err := o.client.WhereCache(func(lb *ovnNB.LoadBalancer) bool {
if len(lb.HealthCheck) == 0 {
return false
}
if lb.Protocol != nil && mon.Protocol != nil && *lb.Protocol != *mon.Protocol {
return false
}
for k, v := range lb.IPPortMappings {
if k == mon.IP && v == fmt.Sprintf("%s:%s", mon.LogicalPort, mon.SrcIP) {
return true
}
}
return false
}).List(ctx, &lbs)
if err != nil {
return nil, err
}
return lbs, nil
}
// CreateAddressSet creates address sets for IP versions 4 and 6 in the format "<addressSetPrefix>_ip<IP version>".
// Populates them with the relevant addresses supplied.
func (o *NB) CreateAddressSet(ctx context.Context, addressSetPrefix OVNAddressSet, addresses ...net.IPNet) error {
// Define the new address sets.
ipv4Set := ovnNB.AddressSet{
Name: fmt.Sprintf("%s_ip4", addressSetPrefix),
Addresses: []string{},
}
ipv6Set := ovnNB.AddressSet{
Name: fmt.Sprintf("%s_ip6", addressSetPrefix),
Addresses: []string{},
}
// Add addresses.
for _, address := range addresses {
if address.IP.To4() == nil {
ipv6Set.Addresses = append(ipv6Set.Addresses, address.String())
} else {
ipv4Set.Addresses = append(ipv4Set.Addresses, address.String())
}
}
// Create the records.
operations := []ovsdb.Operation{}
createOps, err := o.client.Create(&ipv4Set)
if err != nil {
return err
}
operations = append(operations, createOps...)
createOps, err = o.client.Create(&ipv6Set)
if err != nil {
return err
}
operations = append(operations, createOps...)
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// UpdateAddressSetAdd adds the supplied addresses to the address sets.
// If the set is missing, it will get automatically created.
// The address set name used is "<addressSetPrefix>_ip<IP version>", e.g. "foo_ip4".
func (o *NB) UpdateAddressSetAdd(ctx context.Context, addressSetPrefix OVNAddressSet, addresses ...net.IPNet) error {
// Get the address sets.
ipv4Set := ovnNB.AddressSet{
Name: fmt.Sprintf("%s_ip4", addressSetPrefix),
}
err := o.get(ctx, &ipv4Set)
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
if ipv4Set.Addresses == nil {
ipv4Set.Addresses = []string{}
}
ipv6Set := ovnNB.AddressSet{
Name: fmt.Sprintf("%s_ip6", addressSetPrefix),
}
err = o.get(ctx, &ipv6Set)
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
if ipv6Set.Addresses == nil {
ipv6Set.Addresses = []string{}
}
// Add the addresses.
for _, address := range addresses {
if address.IP.To4() == nil {
if !slices.Contains(ipv6Set.Addresses, address.String()) {
ipv6Set.Addresses = append(ipv6Set.Addresses, address.String())
}
} else {
if !slices.Contains(ipv4Set.Addresses, address.String()) {
ipv4Set.Addresses = append(ipv4Set.Addresses, address.String())
}
}
}
// Prepare the records.
operations := []ovsdb.Operation{}
if ipv4Set.UUID == "" {
createOps, err := o.client.Create(&ipv4Set)
if err != nil {
return err
}
operations = append(operations, createOps...)
} else {
updateOps, err := o.client.Where(&ipv4Set).Update(&ipv4Set)
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
if ipv6Set.UUID == "" {
createOps, err := o.client.Create(&ipv6Set)
if err != nil {
return err
}
operations = append(operations, createOps...)
} else {
updateOps, err := o.client.Where(&ipv6Set).Update(&ipv6Set)
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// UpdateAddressSetRemove removes the supplied addresses from the address set.
// The address set name used is "<addressSetPrefix>_ip<IP version>", e.g. "foo_ip4".
func (o *NB) UpdateAddressSetRemove(ctx context.Context, addressSetPrefix OVNAddressSet, addresses ...net.IPNet) error {
// Get the address sets.
ipv4Set := ovnNB.AddressSet{
Name: fmt.Sprintf("%s_ip4", addressSetPrefix),
}
err := o.get(ctx, &ipv4Set)
if err != nil {
return err
}
ipv6Set := ovnNB.AddressSet{
Name: fmt.Sprintf("%s_ip6", addressSetPrefix),
}
err = o.get(ctx, &ipv6Set)
if err != nil {
return err
}
// Filter entries.
ipv4Addresses := []string{}
for _, entry := range ipv4Set.Addresses {
found := false
for _, address := range addresses {
if entry == address.String() {
found = true
break
}
}
if !found {
ipv4Addresses = append(ipv4Addresses, entry)
}
}
ipv4Set.Addresses = ipv4Addresses
ipv6Addresses := []string{}
for _, entry := range ipv6Set.Addresses {
found := false
for _, address := range addresses {
if entry == address.String() {
found = true
break
}
}
if !found {
ipv6Addresses = append(ipv6Addresses, entry)
}
}
ipv6Set.Addresses = ipv6Addresses
// Prepare the records.
operations := []ovsdb.Operation{}
updateOps, err := o.client.Where(&ipv4Set).Update(&ipv4Set)
if err != nil {
return err
}
operations = append(operations, updateOps...)
updateOps, err = o.client.Where(&ipv6Set).Update(&ipv6Set)
if err != nil {
return err
}
operations = append(operations, updateOps...)
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// DeleteAddressSet deletes address sets for IP versions 4 and 6 in the format "<addressSetPrefix>_ip<IP version>".
func (o *NB) DeleteAddressSet(ctx context.Context, addressSetPrefix OVNAddressSet) error {
// Get the address sets.
ipv4Set := ovnNB.AddressSet{
Name: fmt.Sprintf("%s_ip4", addressSetPrefix),
}
err := o.get(ctx, &ipv4Set)
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
ipv6Set := ovnNB.AddressSet{
Name: fmt.Sprintf("%s_ip6", addressSetPrefix),
}
err = o.get(ctx, &ipv6Set)
if err != nil && !errors.Is(err, ErrNotFound) {
return err
}
// Delete the records.
operations := []ovsdb.Operation{}
if ipv4Set.UUID != "" {
deleteOps, err := o.client.Where(&ipv4Set).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
}
if ipv6Set.UUID != "" {
deleteOps, err := o.client.Where(&ipv6Set).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// GetAddressSet gets the two OVN database records (v4 and v6) for the address set.
func (o *NB) GetAddressSet(ctx context.Context, asName OVNAddressSet) (*ovnNB.AddressSet, *ovnNB.AddressSet, error) {
asV4 := &ovnNB.AddressSet{
Name: fmt.Sprintf("%s_ip4", asName),
}
asV6 := &ovnNB.AddressSet{
Name: fmt.Sprintf("%s_ip6", asName),
}
// Get the entries (we always expect both IPv4 and IPv6 sets to exist).
err := o.get(ctx, asV4)
if err != nil {
return nil, nil, err
}
err = o.get(ctx, asV6)
if err != nil {
return nil, nil, err
}
return asV4, asV6, nil
}
// UpdateLogicalRouterPolicy removes any existing policies and applies the new policies to the specified router.
func (o *NB) UpdateLogicalRouterPolicy(ctx context.Context, routerName OVNRouter, policies ...OVNRouterPolicy) error {
operations := []ovsdb.Operation{}
// Get the logical router.
lr, err := o.GetLogicalRouter(ctx, routerName)
if err != nil {
return err
}
// Clear the existing policies.
for _, policyUUID := range lr.Policies {
// Delete the policy.
policy := ovnNB.LogicalRouterPolicy{
UUID: policyUUID,
}
deleteOps, err := o.client.Where(&policy).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
// Remove from the logical router.
updateOps, err := o.client.Where(lr).Mutate(lr, ovsModel.Mutation{
Field: &lr.Policies,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{policy.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
// Add the new policies.
for i, routerPolicy := range policies {
// Create the policy.
policy := ovnNB.LogicalRouterPolicy{
UUID: fmt.Sprintf("policy%d", i),
Priority: routerPolicy.Priority,
Match: routerPolicy.Match,
Action: routerPolicy.Action,
}
createOps, err := o.client.Create(&policy)
if err != nil {
return err
}
operations = append(operations, createOps...)
// Add to the logical router.
updateOps, err := o.client.Where(lr).Mutate(lr, ovsModel.Mutation{
Field: &lr.Policies,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{policy.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
// Check if anything changed.
if len(operations) == 0 {
return nil
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// GetLogicalRouterRoutes returns a list of static routes in the main route table of the logical router.
func (o *NB) GetLogicalRouterRoutes(ctx context.Context, routerName OVNRouter) ([]OVNRouterRoute, error) {
// Get the logical router.
lr, err := o.GetLogicalRouter(ctx, routerName)
if err != nil {
return nil, err
}
// Get all the routes.
routes := []OVNRouterRoute{}
for _, routeUUID := range lr.StaticRoutes {
// Get the static entry.
route := ovnNB.LogicalRouterStaticRoute{
UUID: routeUUID,
}
err = o.get(ctx, &route)
if err != nil {
return nil, err
}
// Only use the main table.
if route.RouteTable != "" {
continue
}
// Create the route entry.
var routerRoute OVNRouterRoute
// Add CIDR if missing.
ip := net.ParseIP(route.IPPrefix)
if ip != nil {
subnetSize := 32
if ip.To4() == nil {
subnetSize = 128
}
route.IPPrefix = fmt.Sprintf("%s/%d", ip.String(), subnetSize)
}
_, prefix, err := net.ParseCIDR(route.IPPrefix)
if err != nil {
return nil, fmt.Errorf("Invalid static route prefix %q", route.IPPrefix)
}
routerRoute.Prefix = *prefix
routerRoute.NextHop = net.ParseIP(route.Nexthop)
if route.Nexthop == "discard" {
routerRoute.Discard = true
}
if route.OutputPort != nil {
routerRoute.Port = OVNRouterPort(*route.OutputPort)
}
routes = append(routes, routerRoute)
}
return routes, nil
}
// CreateLogicalRouterPeering applies a peering relationship between two logical routers.
func (o *NB) CreateLogicalRouterPeering(ctx context.Context, opts OVNRouterPeering) error {
operations := []ovsdb.Operation{}
if len(opts.LocalRouterPortIPs) <= 0 || len(opts.TargetRouterPortIPs) <= 0 {
return errors.New("IPs not populated for both router ports")
}
// Remove peering router ports and static routes using ports from both routers.
// Run the delete step as a separate command to workaround a bug in OVN.
err := o.DeleteLogicalRouterPeering(ctx, opts)
if err != nil {
return err
}
// Will use the first IP from each family of the router port interfaces.
localRouterGatewayIPs := make(map[uint]net.IP)
targetRouterGatewayIPs := make(map[uint]net.IP)
// Create the local router port.
localPeerName := string(opts.TargetRouterPort)
localLRP := ovnNB.LogicalRouterPort{
UUID: "locallrp",
Name: string(opts.LocalRouterPort),
MAC: opts.LocalRouterPortMAC.String(),
Networks: []string{},
Peer: &localPeerName,
}
for _, ipNet := range opts.LocalRouterPortIPs {
ipVersion := uint(4)
if ipNet.IP.To4() == nil {
ipVersion = 6
}
if localRouterGatewayIPs[ipVersion] == nil {
localRouterGatewayIPs[ipVersion] = ipNet.IP
}
localLRP.Networks = append(localLRP.Networks, ipNet.String())
}
createOps, err := o.client.Create(&localLRP)
if err != nil {
return err
}
operations = append(operations, createOps...)
// And connect it to the router.
localRouter := ovnNB.LogicalRouter{
Name: string(opts.LocalRouter),
}
updateOps, err := o.client.Where(&localRouter).Mutate(&localRouter, ovsModel.Mutation{
Field: &localRouter.Ports,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{localLRP.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
// Create the target router port.
targetPeerName := string(opts.LocalRouterPort)
targetLRP := ovnNB.LogicalRouterPort{
UUID: "targetlrp",
Name: string(opts.TargetRouterPort),
MAC: opts.TargetRouterPortMAC.String(),
Networks: []string{},
Peer: &targetPeerName,
}
for _, ipNet := range opts.TargetRouterPortIPs {
ipVersion := uint(4)
if ipNet.IP.To4() == nil {
ipVersion = 6
}
if targetRouterGatewayIPs[ipVersion] == nil {
targetRouterGatewayIPs[ipVersion] = ipNet.IP
}
targetLRP.Networks = append(targetLRP.Networks, ipNet.String())
}
createOps, err = o.client.Create(&targetLRP)
if err != nil {
return err
}
operations = append(operations, createOps...)
// And connect it to the router.
targetRouter := ovnNB.LogicalRouter{
Name: string(opts.TargetRouter),
}
updateOps, err = o.client.Where(&targetRouter).Mutate(&targetRouter, ovsModel.Mutation{
Field: &targetRouter.Ports,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{targetLRP.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
// Add routes using the first router gateway IP for each family for next hop address.
localOutputPort := string(opts.LocalRouterPort)
for i, route := range opts.LocalRouterRoutes {
// Determine the nexthop.
ipVersion := uint(4)
if route.IP.To4() == nil {
ipVersion = 6
}
nextHopIP := targetRouterGatewayIPs[ipVersion]
if nextHopIP == nil {
return fmt.Errorf("Missing target router port IPv%d address for local route %q nexthop address", ipVersion, route.String())
}
// Prepare the record.
route := ovnNB.LogicalRouterStaticRoute{
UUID: fmt.Sprintf("local%d", i),
IPPrefix: route.String(),
Nexthop: nextHopIP.String(),
OutputPort: &localOutputPort,
}
createOps, err := o.client.Create(&route)
if err != nil {
return err
}
operations = append(operations, createOps...)
// Add it to the router.
updateOps, err := o.client.Where(&localRouter).Mutate(&localRouter, ovsModel.Mutation{
Field: &localRouter.StaticRoutes,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{route.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
targetOutputPort := string(opts.TargetRouterPort)
for i, route := range opts.TargetRouterRoutes {
// Determine the nexthop.
ipVersion := uint(4)
if route.IP.To4() == nil {
ipVersion = 6
}
nextHopIP := localRouterGatewayIPs[ipVersion]
if nextHopIP == nil {
return fmt.Errorf("Missing local router port IPv%d address for target route %q nexthop address", ipVersion, route.String())
}
// Prepare the record.
route := ovnNB.LogicalRouterStaticRoute{
UUID: fmt.Sprintf("target%d", i),
IPPrefix: route.String(),
Nexthop: nextHopIP.String(),
OutputPort: &targetOutputPort,
}
createOps, err := o.client.Create(&route)
if err != nil {
return err
}
operations = append(operations, createOps...)
// Add it to the router.
updateOps, err := o.client.Where(&targetRouter).Mutate(&targetRouter, ovsModel.Mutation{
Field: &targetRouter.StaticRoutes,
Mutator: ovsdb.MutateOperationInsert,
Value: []string{route.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// DeleteLogicalRouterPeering deletes a peering relationship between two logical routers.
// Requires LocalRouter, LocalRouterPort, TargetRouter and TargetRouterPort opts fields to be populated.
func (o *NB) DeleteLogicalRouterPeering(ctx context.Context, opts OVNRouterPeering) error {
operations := []ovsdb.Operation{}
// Remove peering router ports and static routes using ports from both routers.
if opts.LocalRouter == "" || opts.TargetRouter == "" {
return errors.New("Router names not populated for both routers")
}
deleteLogicalRouterPort := func(routerName OVNRouter, portName OVNRouterPort) error {
// Get the logical router port.
logicalRouterPort := ovnNB.LogicalRouterPort{
Name: string(portName),
}
err := o.get(ctx, &logicalRouterPort)
if err != nil {
if errors.Is(err, ErrNotFound) {
// Logical router port is already gone.
return nil
}
return err
}
// Get the logical router.
logicalRouter := ovnNB.LogicalRouter{
Name: string(routerName),
}
err = o.get(ctx, &logicalRouter)
if err != nil {
return err
}
// Remove the port from the router.
updateOps, err := o.client.Where(&logicalRouter).Mutate(&logicalRouter, ovsModel.Mutation{
Field: &logicalRouter.Ports,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{logicalRouterPort.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
// Delete the port itself.
deleteOps, err := o.client.Where(&logicalRouterPort).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
// Remove any associated route entries.
for _, routeUUID := range logicalRouter.StaticRoutes {
// Get the static entry.
route := ovnNB.LogicalRouterStaticRoute{
UUID: routeUUID,
}
err = o.get(ctx, &route)
if err != nil {
return err
}
// Skip over anything that's not tied to the current port.
if route.OutputPort == nil || *route.OutputPort != string(portName) {
continue
}
// Remove the route from the router.
updateOps, err := o.client.Where(&logicalRouter).Mutate(&logicalRouter, ovsModel.Mutation{
Field: &logicalRouter.StaticRoutes,
Mutator: ovsdb.MutateOperationDelete,
Value: []string{route.UUID},
})
if err != nil {
return err
}
operations = append(operations, updateOps...)
// Delete the route itself.
deleteOps, err := o.client.Where(&route).Delete()
if err != nil {
return err
}
operations = append(operations, deleteOps...)
}
return nil
}
// Delete both source and target router ports.
err := deleteLogicalRouterPort(opts.LocalRouter, opts.LocalRouterPort)
if err != nil {
return err
}
err = deleteLogicalRouterPort(opts.TargetRouter, opts.TargetRouterPort)
if err != nil {
return err
}
// Check if anything changed.
if len(operations) == 0 {
return nil
}
// Apply the changes.
resp, err := o.client.Transact(ctx, operations...)
if err != nil {
return err
}
_, err = ovsdb.CheckOperationResults(resp, operations)
if err != nil {
return err
}
return nil
}
// GetLogicalRouterPortHardwareAddress gets the hardware address of the logical router port.
func (o *NB) GetLogicalRouterPortHardwareAddress(ctx context.Context, ovnRouterPort OVNRouterPort) (string, error) {
lrp, err := o.GetLogicalRouterPort(ctx, ovnRouterPort)
if err != nil {
return "", err
}
return lrp.MAC, nil
}
// GetName returns the OVN AZ name.
func (o *NB) GetName(ctx context.Context) (string, error) {
// Get the global configuration.
nbGlobal := []ovnNB.NBGlobal{}
err := o.client.List(ctx, &nbGlobal)
if err != nil {
return "", err
}
// Check that we got a result.
if len(nbGlobal) != 1 {
return "", ovsClient.ErrNotFound
}
return nbGlobal[0].Name, nil
}