From 46d06d311f49a4ae3f6eb7a3bba9ea5bd91ea8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 5 Mar 2024 16:32:13 -0500 Subject: [PATCH 1/8] incusd/network/ovs: Port GetOVNSouthboundDBRemoteAddress to OVSDB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber Sponsored-by: Luizalabs (https://luizalabs.com) --- internal/server/network/ovs/ovs_actions.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/server/network/ovs/ovs_actions.go b/internal/server/network/ovs/ovs_actions.go index 826eaec3b76..94965c70f69 100644 --- a/internal/server/network/ovs/ovs_actions.go +++ b/internal/server/network/ovs/ovs_actions.go @@ -484,17 +484,18 @@ func (o *VSwitch) HardwareOffloadingEnabled() bool { return offload == "true" } -// OVNSouthboundDBRemoteAddress gets the address of the southbound ovn database. -func (o *VSwitch) OVNSouthboundDBRemoteAddress() (string, error) { - result, err := subprocess.RunCommand("ovs-vsctl", "get", "open_vswitch", ".", "external_ids:ovn-remote") - if err != nil { - return "", err +// GetOVNSouthboundDBRemoteAddress gets the address of the southbound ovn database. +func (o *VSwitch) GetOVNSouthboundDBRemoteAddress(ctx context.Context) (string, error) { + vSwitch := &ovsSwitch.OpenvSwitch{ + UUID: o.rootUUID, } - addr, err := unquote(strings.TrimSuffix(result, "\n")) + err := o.client.Get(ctx, vSwitch) if err != nil { return "", err } - return addr, nil + val := vSwitch.ExternalIDs["ovn-remote"] + + return val, nil } From 69dd1510d39e8e37a0d3ceadabaac127ee98e5fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 5 Mar 2024 16:32:45 -0500 Subject: [PATCH 2/8] incusd/network/ovs: Port DeleteBridgePort to OVSDB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber Sponsored-by: Luizalabs (https://luizalabs.com) --- internal/server/network/ovs/ovs_actions.go | 53 ++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/internal/server/network/ovs/ovs_actions.go b/internal/server/network/ovs/ovs_actions.go index 94965c70f69..f8fbfa56a0a 100644 --- a/internal/server/network/ovs/ovs_actions.go +++ b/internal/server/network/ovs/ovs_actions.go @@ -250,9 +250,56 @@ func (o *VSwitch) CreateBridgePort(ctx context.Context, bridgeName string, portN return nil } -// BridgePortDelete deletes a port from the bridge (if already detached does nothing). -func (o *VSwitch) BridgePortDelete(bridgeName string, portName string) error { - _, err := subprocess.RunCommand("ovs-vsctl", "--if-exists", "del-port", bridgeName, portName) +// DeleteBridgePort deletes a port from the bridge (if already detached does nothing). +func (o *VSwitch) DeleteBridgePort(ctx context.Context, bridgeName string, portName string) error { + operations := []ovsdb.Operation{} + + // Get the bridge port. + bridgePort := ovsSwitch.Port{ + Name: string(portName), + } + + err := o.client.Get(ctx, &bridgePort) + if err != nil { + // Logical switch port is already gone. + if err == ErrNotFound { + return nil + } + + return err + } + + // Remove the port from the bridge. + bridge := ovsSwitch.Bridge{ + Name: string(bridgeName), + } + + updateOps, err := o.client.Where(&bridge).Mutate(&bridge, ovsdbModel.Mutation{ + Field: &bridge.Ports, + Mutator: ovsdb.MutateOperationDelete, + Value: []string{bridgePort.UUID}, + }) + if err != nil { + return err + } + + operations = append(operations, updateOps...) + + // Delete the port itself. + deleteOps, err := o.client.Where(&bridgePort).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 } From a5acd11d8b9647362b2da58995b16cce259d7688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 5 Mar 2024 16:33:07 -0500 Subject: [PATCH 3/8] incusd/network/ovs: Port GetInterfaceAssociatedOVNSwitchPort to OVSDB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber Sponsored-by: Luizalabs (https://luizalabs.com) --- internal/server/network/ovs/ovs_actions.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/internal/server/network/ovs/ovs_actions.go b/internal/server/network/ovs/ovs_actions.go index f8fbfa56a0a..3d55a557c33 100644 --- a/internal/server/network/ovs/ovs_actions.go +++ b/internal/server/network/ovs/ovs_actions.go @@ -350,14 +350,20 @@ func (o *VSwitch) InterfaceAssociateOVNSwitchPort(interfaceName string, ovnSwitc return nil } -// InterfaceAssociatedOVNSwitchPort returns the OVN switch port associated to the interface. -func (o *VSwitch) InterfaceAssociatedOVNSwitchPort(interfaceName string) (string, error) { - ovnSwitchPort, err := subprocess.RunCommand("ovs-vsctl", "get", "interface", interfaceName, "external_ids:iface-id") +// GetInterfaceAssociatedOVNSwitchPort returns the OVN switch port associated to the interface. +func (o *VSwitch) GetInterfaceAssociatedOVNSwitchPort(ctx context.Context, interfaceName string) (string, error) { + // Get the OVS interface. + ovsInterface := ovsSwitch.Interface{ + Name: interfaceName, + } + + err := o.client.Get(ctx, &ovsInterface) if err != nil { return "", err } - return strings.TrimSpace(ovnSwitchPort), nil + // Return the iface-id. + return ovsInterface.ExternalIDs["iface-id"], nil } // GetChassisID returns the local chassis ID. From d668ecd295fa53a1fda85b6db6a19646d8205862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 5 Mar 2024 16:33:21 -0500 Subject: [PATCH 4/8] incusd/network/ovs: Align GetChassisID with other functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber Sponsored-by: Luizalabs (https://luizalabs.com) --- internal/server/network/ovs/ovs_actions.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/server/network/ovs/ovs_actions.go b/internal/server/network/ovs/ovs_actions.go index 3d55a557c33..24a39bf743b 100644 --- a/internal/server/network/ovs/ovs_actions.go +++ b/internal/server/network/ovs/ovs_actions.go @@ -368,6 +368,7 @@ func (o *VSwitch) GetInterfaceAssociatedOVNSwitchPort(ctx context.Context, inter // GetChassisID returns the local chassis ID. func (o *VSwitch) GetChassisID(ctx context.Context) (string, error) { + // Get the root switch. vSwitch := &ovsSwitch.OpenvSwitch{ UUID: o.rootUUID, } @@ -377,8 +378,8 @@ func (o *VSwitch) GetChassisID(ctx context.Context) (string, error) { return "", err } - val := vSwitch.ExternalIDs["system-id"] - return val, nil + // Return the system-id. + return vSwitch.ExternalIDs["system-id"], nil } // OVNEncapIP returns the enscapsulation IP used for OVN underlay tunnels. From 8915b13e5972d084fabb5ee0fed8e315bf603e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Tue, 5 Mar 2024 16:34:32 -0500 Subject: [PATCH 5/8] incusd: Update for OVS function changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber Sponsored-by: Luizalabs (https://luizalabs.com) --- cmd/incusd/daemon.go | 2 +- internal/server/device/nic_ovn.go | 6 +++--- internal/server/network/network_utils_bridge.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/incusd/daemon.go b/cmd/incusd/daemon.go index 8676c7bca69..5e465e484ba 100644 --- a/cmd/incusd/daemon.go +++ b/cmd/incusd/daemon.go @@ -2449,7 +2449,7 @@ func (d *Daemon) setupOVN() error { } // Get the OVN southbound address. - ovnSBAddr, err := vswitch.OVNSouthboundDBRemoteAddress() + ovnSBAddr, err := vswitch.GetOVNSouthboundDBRemoteAddress(d.shutdownCtx) if err != nil { return fmt.Errorf("Failed to get OVN southbound connection string: %w", err) } diff --git a/internal/server/device/nic_ovn.go b/internal/server/device/nic_ovn.go index f29f4375a71..ed25ed5a48e 100644 --- a/internal/server/device/nic_ovn.go +++ b/internal/server/device/nic_ovn.go @@ -862,7 +862,7 @@ func (d *nicOVN) Stop() (*deviceConfig.RunConfig, error) { var ovsExternalOVNPort string if d.config["nested"] == "" { - ovsExternalOVNPort, err = vswitch.InterfaceAssociatedOVNSwitchPort(d.config["host_name"]) + ovsExternalOVNPort, err = vswitch.GetInterfaceAssociatedOVNSwitchPort(context.TODO(), d.config["host_name"]) if err != nil { d.logger.Warn("Could not find OVN Switch port associated to OVS interface", logger.Ctx{"interface": d.config["host_name"]}) } @@ -884,7 +884,7 @@ func (d *nicOVN) Stop() (*deviceConfig.RunConfig, error) { integrationBridge := d.state.GlobalConfig.NetworkOVNIntegrationBridge() // Detach host-side end of veth pair from OVS integration bridge. - err = vswitch.BridgePortDelete(integrationBridge, integrationBridgeNICName) + err = vswitch.DeleteBridgePort(context.TODO(), integrationBridge, integrationBridgeNICName) if err != nil { // Don't fail here as we want the postStop hook to run to clean up the local veth pair. d.logger.Error("Failed detaching interface from OVS integration bridge", logger.Ctx{"interface": integrationBridgeNICName, "bridge": integrationBridge, "err": err}) @@ -1166,7 +1166,7 @@ func (d *nicOVN) setupHostNIC(hostName string, ovnPortName ovn.OVNSwitchPort, up return nil, err } - revert.Add(func() { _ = vswitch.BridgePortDelete(integrationBridge, hostName) }) + revert.Add(func() { _ = vswitch.DeleteBridgePort(context.TODO(), integrationBridge, hostName) }) // Link OVS port to OVN logical port. err = vswitch.InterfaceAssociateOVNSwitchPort(hostName, string(ovnPortName)) diff --git a/internal/server/network/network_utils_bridge.go b/internal/server/network/network_utils_bridge.go index bd742b72988..3ea1a18a891 100644 --- a/internal/server/network/network_utils_bridge.go +++ b/internal/server/network/network_utils_bridge.go @@ -93,7 +93,7 @@ func DetachInterface(bridgeName string, devName string) error { return fmt.Errorf("Failed to connect to OVS: %w", err) } - err = vswitch.BridgePortDelete(bridgeName, devName) + err = vswitch.DeleteBridgePort(context.TODO(), bridgeName, devName) if err != nil { return err } From ce93f1fe1439add593217530f844113b5f12889c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Wed, 6 Mar 2024 00:54:41 -0500 Subject: [PATCH 6/8] incusd/network/ovn/icsb: Fix bad DB schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber Sponsored-by: Luizalabs (https://luizalabs.com) --- internal/server/network/ovn/ovn_icsb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/server/network/ovn/ovn_icsb.go b/internal/server/network/ovn/ovn_icsb.go index 7a8e488dcdc..89b62e8faa1 100644 --- a/internal/server/network/ovn/ovn_icsb.go +++ b/internal/server/network/ovn/ovn_icsb.go @@ -13,7 +13,7 @@ import ( ovsdbClient "github.com/ovn-org/libovsdb/client" ovsdbModel "github.com/ovn-org/libovsdb/model" - ovnICSB "github.com/lxc/incus/internal/server/network/ovn/schema/ovn-ic-nb" + ovnICSB "github.com/lxc/incus/internal/server/network/ovn/schema/ovn-ic-sb" ) // ICSB client. From 598eb38c5d43661fe52e29fd5841f153acc19d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Wed, 6 Mar 2024 00:55:01 -0500 Subject: [PATCH 7/8] incusd/network/ovn/nb: Introduce GetLogicalRouterPort MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber Sponsored-by: Luizalabs (https://luizalabs.com) --- internal/server/network/ovn/ovn_nb_actions.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/internal/server/network/ovn/ovn_nb_actions.go b/internal/server/network/ovn/ovn_nb_actions.go index 10108e81e7a..d5c2c2eb5dd 100644 --- a/internal/server/network/ovn/ovn_nb_actions.go +++ b/internal/server/network/ovn/ovn_nb_actions.go @@ -348,6 +348,20 @@ func (o *NB) LogicalRouterRouteDelete(routerName OVNRouter, prefixes ...net.IPNe 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. From 398504c34770f3fac39a7029224ba15d1c195bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Wed, 6 Mar 2024 00:55:39 -0500 Subject: [PATCH 8/8] incusd/network/ovn/nb: Extend OVNSwitchPortOpts to handle router ports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber Sponsored-by: Luizalabs (https://luizalabs.com) --- internal/server/network/ovn/ovn_nb_actions.go | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/internal/server/network/ovn/ovn_nb_actions.go b/internal/server/network/ovn/ovn_nb_actions.go index d5c2c2eb5dd..232cea01e0e 100644 --- a/internal/server/network/ovn/ovn_nb_actions.go +++ b/internal/server/network/ovn/ovn_nb_actions.go @@ -124,6 +124,7 @@ type OVNSwitchPortOpts struct { 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 string // Optional, the name of the associated logical router port. } // OVNACLRule represents an ACL rule that can be added to a logical switch or port group. @@ -1219,21 +1220,27 @@ func (o *NB) CreateLogicalSwitchPort(ctx context.Context, switchName OVNSwitch, logicalSwitchPort.Tag = &tag } - ipStr := make([]string, 0, len(opts.IPs)) - for _, ip := range opts.IPs { - ipStr = append(ipStr, ip.String()) - } - - var addresses string - if opts.MAC != nil && len(ipStr) > 0 { - addresses = fmt.Sprintf("%s %s", opts.MAC.String(), strings.Join(ipStr, " ")) - } else if opts.MAC != nil && len(ipStr) <= 0 { - addresses = fmt.Sprintf("%s %s", opts.MAC.String(), "dynamic") + if opts.RouterPort != "" { + logicalSwitchPort.Type = "router" + logicalSwitchPort.Addresses = []string{"router"} + logicalSwitchPort.Options = map[string]string{"router-port": opts.RouterPort} } else { - addresses = "dynamic" - } + ipStr := make([]string, 0, len(opts.IPs)) + for _, ip := range opts.IPs { + ipStr = append(ipStr, ip.String()) + } - logicalSwitchPort.Addresses = []string{addresses} + var addresses string + if opts.MAC != nil && len(ipStr) > 0 { + addresses = fmt.Sprintf("%s %s", opts.MAC.String(), strings.Join(ipStr, " ")) + } else if opts.MAC != nil && len(ipStr) <= 0 { + addresses = fmt.Sprintf("%s %s", opts.MAC.String(), "dynamic") + } else { + addresses = "dynamic" + } + + logicalSwitchPort.Addresses = []string{addresses} + } if opts.DHCPv4OptsID != "" { dhcp4opts := string(opts.DHCPv4OptsID)