Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lxd-to-incus: Add support for OVN database mangling #231

Merged
merged 1 commit into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 45 additions & 15 deletions cmd/lxd-to-incus/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,38 @@ func (c *cmdMigrate) Run(app *cobra.Command, args []string) error {
}
}

// Mangle OVS/OVN.
srcServerInfo, _, err := srcClient.GetServer()
if err != nil {
return fmt.Errorf("Failed to get source server info: %w", err)
}

ovnNB, ok := srcServerInfo.Config["network.ovn.northbound_connection"].(string)
if ok && ovnNB != "" {
if !c.flagClusterMember {
out, err := subprocess.RunCommand("ovs-vsctl", "get", "open_vswitch", ".", "external_ids:ovn-remote")
if err != nil {
return fmt.Errorf("Failed to get OVN southbound database address: %w", err)
}

ovnSB := strings.TrimSpace(strings.Replace(out, "\"", "", -1))

commands, err := ovnConvert(ovnNB, ovnSB)
if err != nil {
return fmt.Errorf("Failed to prepare OVN conversion: %v", err)
}

rewriteCommands = append(rewriteCommands, commands...)
}

commands, err := ovsConvert()
if err != nil {
return fmt.Errorf("Failed to prepare OVS conversion: %v", err)
}

rewriteCommands = append(rewriteCommands, commands...)
}

// Confirm migration.
if !c.flagClusterMember && !c.flagYes {
if !clustered {
Expand Down Expand Up @@ -392,23 +424,21 @@ Instead this tool will be providing specific commands for each of the servers.
return fmt.Errorf("Failed to migrate database in %q: %w", filepath.Join(targetPaths.Daemon, "database"), err)
}

// Apply custom SQL statements.
if !c.flagClusterMember {
if len(rewriteStatements) > 0 {
fmt.Println("=> Writing database patch")
err = os.WriteFile(filepath.Join(targetPaths.Daemon, "database", "patch.global.sql"), []byte(strings.Join(rewriteStatements, "\n")+"\n"), 0600)
if err != nil {
return fmt.Errorf("Failed to write database path: %w", err)
}
// Apply custom migration statements.
if len(rewriteStatements) > 0 {
fmt.Println("=> Writing database patch")
err = os.WriteFile(filepath.Join(targetPaths.Daemon, "database", "patch.global.sql"), []byte(strings.Join(rewriteStatements, "\n")+"\n"), 0600)
if err != nil {
return fmt.Errorf("Failed to write database path: %w", err)
}
}

if len(rewriteCommands) > 0 {
fmt.Println("=> Running data migration commands")
for _, cmd := range rewriteCommands {
_, err := subprocess.RunCommand(cmd[0], cmd[1:]...)
if err != nil {
return err
}
if len(rewriteCommands) > 0 {
fmt.Println("=> Running data migration commands")
for _, cmd := range rewriteCommands {
_, err := subprocess.RunCommand(cmd[0], cmd[1:]...)
if err != nil {
return err
}
}
}
Expand Down
181 changes: 181 additions & 0 deletions cmd/lxd-to-incus/ovn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package main

import (
"encoding/csv"
"fmt"
"strings"

"github.com/lxc/incus/shared/subprocess"
)

func ovsConvert() ([][]string, error) {
commands := [][]string{}

output, err := subprocess.RunCommand("ovs-vsctl", "get", "open_vswitch", ".", "external-ids:ovn-bridge-mappings")
if err != nil {
return nil, err
}

oldValue := strings.TrimSpace(strings.Replace(output, "\"", "", -1))

values := strings.Split(oldValue, ",")
for i, value := range values {
fields := strings.Split(value, ":")
fields[1] = strings.Replace(fields[1], "lxdovn", "incusovn", -1)
values[i] = strings.Join(fields, ":")
}

newValue := strings.Join(values, ",")

if oldValue != newValue {
commands = append(commands, []string{"ovs-vsctl", "set", "openv_vswitch", ".", fmt.Sprintf("external-ids:ovn-bridge-mappings=%s", newValue)})
}

return commands, nil
}

func ovnConvert(nbDB string, sbDB string) ([][]string, error) {
commands := [][]string{}

// Patch the Northbound records.
output, err := subprocess.RunCommand("ovsdb-client", "dump", "-f", "csv", nbDB, "OVN_Northbound")
if err != nil {
return nil, err
}

data, err := ovnParseDump(output)
if err != nil {
return nil, err
}

for table, records := range data {
for _, record := range records {
for k, v := range record {
needsFixing, newValue, err := ovnCheckValue(table, k, v)
if err != nil {
return nil, err
}

if needsFixing {
commands = append(commands, []string{"ovn-nbctl", "--db", nbDB, "set", table, record["_uuid"], fmt.Sprintf("%s=%s", k, newValue)})
}
}
}
}

// Patch the Southbound records.
output, err = subprocess.RunCommand("ovsdb-client", "dump", "-f", "csv", sbDB, "OVN_Southbound")
if err != nil {
return nil, err
}

data, err = ovnParseDump(output)
if err != nil {
return nil, err
}

for table, records := range data {
for _, record := range records {
for k, v := range record {
needsFixing, newValue, err := ovnCheckValue(table, k, v)
if err != nil {
return nil, err
}

if needsFixing {
commands = append(commands, []string{"ovn-sbctl", "--db", sbDB, "set", table, record["_uuid"], fmt.Sprintf("%s=%s", k, newValue)})
}
}
}
}

return commands, nil
}

func ovnCheckValue(table string, k string, v string) (bool, string, error) {
if !strings.Contains(v, "lxd") {
return false, "", nil
}

if table == "DNS" && k == "records" {
return false, "", nil
}

if table == "Chassis" && k == "other_config" {
return false, "", nil
}

if table == "Logical_Flow" && k == "actions" {
return false, "", nil
}

if table == "DHCP_Options" && k == "options" {
return false, "", nil
}

if table == "Logical_Router_Port" && k == "ipv6_ra_configs" {
return false, "", nil
}

newValue := strings.Replace(v, "lxd-net", "incus-net", -1)
newValue = strings.Replace(newValue, "lxd_acl", "incus_acl", -1)
newValue = strings.Replace(newValue, "lxd_location", "incus_location", -1)
newValue = strings.Replace(newValue, "lxd_net", "incus_net", -1)
newValue = strings.Replace(newValue, "lxd_port_group", "incus_port_group", -1)
newValue = strings.Replace(newValue, "lxd_project_id", "incus_project_id", -1)
newValue = strings.Replace(newValue, "lxd_switch", "incus_switch", -1)
newValue = strings.Replace(newValue, "lxd_switch_port", "incus_switch_port", -1)

if v == newValue {
return true, "", fmt.Errorf("Couldn't convert value %q for key %q in table %q", v, k, table)
}

return true, newValue, nil
}

func ovnParseDump(data string) (map[string][]map[string]string, error) {
output := map[string][]map[string]string{}

tableName := ""
fields := []string{}
newTable := false
for _, line := range strings.Split(data, "\n") {
if line == "" {
continue
}

if !strings.Contains(line, ",") && strings.HasSuffix(line, " table") {
newTable = true
tableName = strings.Split(line, " ")[0]
output[tableName] = []map[string]string{}
continue
}

if newTable {
newTable = false

var err error
fields, err = csv.NewReader(strings.NewReader(line)).Read()
if err != nil {
return nil, err
}

continue
}

record := map[string]string{}

entry, err := csv.NewReader(strings.NewReader(line)).Read()
if err != nil {
return nil, err
}

for k, v := range entry {
record[fields[k]] = v
}

output[tableName] = append(output[tableName], record)
}

return output, nil
}
Loading