summary refs log tree commit diff
path: root/src/core/devices/ovs
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/devices/ovs')
-rw-r--r--src/core/devices/ovs/meson.build32
-rw-r--r--src/core/devices/ovs/nm-device-ovs-bridge.c164
-rw-r--r--src/core/devices/ovs/nm-device-ovs-bridge.h30
-rw-r--r--src/core/devices/ovs/nm-device-ovs-interface.c445
-rw-r--r--src/core/devices/ovs/nm-device-ovs-interface.h26
-rw-r--r--src/core/devices/ovs/nm-device-ovs-port.c196
-rw-r--r--src/core/devices/ovs/nm-device-ovs-port.h25
-rw-r--r--src/core/devices/ovs/nm-ovs-factory.c324
-rw-r--r--src/core/devices/ovs/nm-ovsdb.c2658
-rw-r--r--src/core/devices/ovs/nm-ovsdb.h61
10 files changed, 3961 insertions, 0 deletions
diff --git a/src/core/devices/ovs/meson.build b/src/core/devices/ovs/meson.build
new file mode 100644
index 00000000..81c29bd6
--- /dev/null
+++ b/src/core/devices/ovs/meson.build
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+libnm_device_plugin_ovs = shared_module(
+  'nm-device-plugin-ovs',
+  sources: files(
+    'nm-device-ovs-bridge.c',
+    'nm-device-ovs-interface.c',
+    'nm-device-ovs-port.c',
+    'nm-ovsdb.c',
+    'nm-ovs-factory.c',
+  ),
+  dependencies: [
+    core_plugin_dep,
+    jansson_dep,
+  ],
+  c_args: daemon_c_flags,
+  link_args: ldflags_linker_script_devices,
+  link_depends: linker_script_devices,
+  install: true,
+  install_dir: nm_plugindir,
+)
+
+core_plugins += libnm_device_plugin_ovs
+
+test(
+  'check-local-devices-ovs',
+  check_exports,
+  args: [
+    libnm_device_plugin_ovs.full_path(),
+    linker_script_devices,
+  ],
+)
diff --git a/src/core/devices/ovs/nm-device-ovs-bridge.c b/src/core/devices/ovs/nm-device-ovs-bridge.c
new file mode 100644
index 00000000..3ae8a481
--- /dev/null
+++ b/src/core/devices/ovs/nm-device-ovs-bridge.c
@@ -0,0 +1,164 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-device-ovs-bridge.h"
+
+#include "nm-device-ovs-interface.h"
+#include "nm-device-ovs-port.h"
+#include "nm-ovsdb.h"
+
+#include "devices/nm-device-private.h"
+#include "nm-active-connection.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-ovs-bridge.h"
+#include "nm-setting-ovs-external-ids.h"
+#include "nm-core-internal.h"
+
+#define _NMLOG_DEVICE_TYPE NMDeviceOvsBridge
+#include "devices/nm-device-logging.h"
+
+/*****************************************************************************/
+
+struct _NMDeviceOvsBridge {
+    NMDevice parent;
+};
+
+struct _NMDeviceOvsBridgeClass {
+    NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE(NMDeviceOvsBridge, nm_device_ovs_bridge, NM_TYPE_DEVICE)
+
+/*****************************************************************************/
+
+static const char *
+get_type_description(NMDevice *device)
+{
+    return "ovs-bridge";
+}
+
+static gboolean
+create_and_realize(NMDevice *             device,
+                   NMConnection *         connection,
+                   NMDevice *             parent,
+                   const NMPlatformLink **out_plink,
+                   GError **              error)
+{
+    /* The actual backing resources will be created on enslavement by the port
+     * when it can identify the port and the bridge. */
+
+    return TRUE;
+}
+
+static gboolean
+unrealize(NMDevice *device, GError **error)
+{
+    return TRUE;
+}
+
+static NMDeviceCapabilities
+get_generic_capabilities(NMDevice *device)
+{
+    return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+static NMActStageReturn
+act_stage3_ip_config_start(NMDevice *           device,
+                           int                  addr_family,
+                           gpointer *           out_config,
+                           NMDeviceStateReason *out_failure_reason)
+{
+    return NM_ACT_STAGE_RETURN_IP_FAIL;
+}
+
+static gboolean
+enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
+{
+    if (!configure)
+        return TRUE;
+
+    if (!NM_IS_DEVICE_OVS_PORT(slave))
+        return FALSE;
+
+    return TRUE;
+}
+
+static void
+release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
+{}
+
+void
+nm_device_ovs_reapply_connection(NMDevice *self, NMConnection *con_old, NMConnection *con_new)
+{
+    NMDeviceType device_type;
+    GType        type;
+
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(g_type_parent(G_TYPE_FROM_INSTANCE(self)) == NM_TYPE_DEVICE);
+
+    /* NMDevice's reapply_connection() doesn't do anything. No need to call the parent
+     * implementation. */
+
+    _LOGD(LOGD_DEVICE, "reapplying settings for OVS device");
+
+    type = G_OBJECT_TYPE(self);
+    if (type == NM_TYPE_DEVICE_OVS_INTERFACE)
+        device_type = NM_DEVICE_TYPE_OVS_INTERFACE;
+    else if (type == NM_TYPE_DEVICE_OVS_PORT)
+        device_type = NM_DEVICE_TYPE_OVS_PORT;
+    else {
+        nm_assert(type == NM_TYPE_DEVICE_OVS_BRIDGE);
+        device_type = NM_DEVICE_TYPE_OVS_BRIDGE;
+    }
+
+    nm_ovsdb_set_external_ids(
+        nm_ovsdb_get(),
+        device_type,
+        nm_device_get_ip_iface(self),
+        nm_connection_get_uuid(con_new),
+        _nm_connection_get_setting(con_old, NM_TYPE_SETTING_OVS_EXTERNAL_IDS),
+        _nm_connection_get_setting(con_new, NM_TYPE_SETTING_OVS_EXTERNAL_IDS));
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_ovs_bridge_init(NMDeviceOvsBridge *self)
+{}
+
+static const NMDBusInterfaceInfoExtended interface_info_device_ovs_bridge = {
+    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
+        NM_DBUS_INTERFACE_DEVICE_OVS_BRIDGE,
+        .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ),
+        .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), ),
+    .legacy_property_changed = TRUE,
+};
+
+static void
+nm_device_ovs_bridge_class_init(NMDeviceOvsBridgeClass *klass)
+{
+    NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
+    NMDeviceClass *    device_class      = NM_DEVICE_CLASS(klass);
+
+    dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_ovs_bridge);
+
+    device_class->connection_type_supported        = NM_SETTING_OVS_BRIDGE_SETTING_NAME;
+    device_class->connection_type_check_compatible = NM_SETTING_OVS_BRIDGE_SETTING_NAME;
+    device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES();
+
+    device_class->is_master                           = TRUE;
+    device_class->get_type_description                = get_type_description;
+    device_class->create_and_realize                  = create_and_realize;
+    device_class->unrealize                           = unrealize;
+    device_class->get_generic_capabilities            = get_generic_capabilities;
+    device_class->act_stage3_ip_config_start          = act_stage3_ip_config_start;
+    device_class->enslave_slave                       = enslave_slave;
+    device_class->release_slave                       = release_slave;
+    device_class->can_reapply_change_ovs_external_ids = TRUE;
+    device_class->reapply_connection                  = nm_device_ovs_reapply_connection;
+}
diff --git a/src/core/devices/ovs/nm-device-ovs-bridge.h b/src/core/devices/ovs/nm-device-ovs-bridge.h
new file mode 100644
index 00000000..2b893343
--- /dev/null
+++ b/src/core/devices/ovs/nm-device-ovs-bridge.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__
+#define __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__
+
+#define NM_TYPE_DEVICE_OVS_BRIDGE (nm_device_ovs_bridge_get_type())
+#define NM_DEVICE_OVS_BRIDGE(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_OVS_BRIDGE, NMDeviceOvsBridge))
+#define NM_DEVICE_OVS_BRIDGE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_OVS_BRIDGE, NMDeviceOvsBridgeClass))
+#define NM_IS_DEVICE_OVS_BRIDGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_OVS_BRIDGE))
+#define NM_IS_DEVICE_OVS_BRIDGE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_OVS_BRIDGE))
+#define NM_DEVICE_OVS_BRIDGE_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_OVS_BRIDGE, NMDeviceOvsBridgeClass))
+
+typedef struct _NMDeviceOvsBridge      NMDeviceOvsBridge;
+typedef struct _NMDeviceOvsBridgeClass NMDeviceOvsBridgeClass;
+
+GType nm_device_ovs_bridge_get_type(void);
+
+/*****************************************************************************/
+
+void
+nm_device_ovs_reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_new);
+
+#endif /* __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__ */
diff --git a/src/core/devices/ovs/nm-device-ovs-interface.c b/src/core/devices/ovs/nm-device-ovs-interface.c
new file mode 100644
index 00000000..5d07c211
--- /dev/null
+++ b/src/core/devices/ovs/nm-device-ovs-interface.c
@@ -0,0 +1,445 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-device-ovs-interface.h"
+
+#include "nm-device-ovs-bridge.h"
+#include "nm-ovsdb.h"
+
+#include "devices/nm-device-private.h"
+#include "nm-active-connection.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-ovs-interface.h"
+#include "nm-setting-ovs-port.h"
+
+#define _NMLOG_DEVICE_TYPE NMDeviceOvsInterface
+#include "devices/nm-device-logging.h"
+
+/*****************************************************************************/
+
+typedef struct {
+    NMOvsdb *ovsdb;
+    bool     waiting_for_interface : 1;
+} NMDeviceOvsInterfacePrivate;
+
+struct _NMDeviceOvsInterface {
+    NMDevice                    parent;
+    NMDeviceOvsInterfacePrivate _priv;
+};
+
+struct _NMDeviceOvsInterfaceClass {
+    NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE(NMDeviceOvsInterface, nm_device_ovs_interface, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMDeviceOvsInterface, NM_IS_DEVICE_OVS_INTERFACE, NMDevice)
+
+/*****************************************************************************/
+
+static const char *
+get_type_description(NMDevice *device)
+{
+    return "ovs-interface";
+}
+
+static gboolean
+create_and_realize(NMDevice *             device,
+                   NMConnection *         connection,
+                   NMDevice *             parent,
+                   const NMPlatformLink **out_plink,
+                   GError **              error)
+{
+    /* The actual backing resources will be created once an interface is
+     * added to a port of ours, since there can be neither an empty port nor
+     * an empty bridge. */
+
+    return TRUE;
+}
+
+static NMDeviceCapabilities
+get_generic_capabilities(NMDevice *device)
+{
+    return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+static gboolean
+is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
+{
+    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+
+    return nm_ovsdb_is_ready(priv->ovsdb);
+}
+
+static gboolean
+check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+{
+    NMSettingOvsInterface *s_ovs_iface;
+
+    if (!NM_DEVICE_CLASS(nm_device_ovs_interface_parent_class)
+             ->check_connection_compatible(device, connection, error))
+        return FALSE;
+
+    s_ovs_iface = nm_connection_get_setting_ovs_interface(connection);
+
+    if (!NM_IN_STRSET(nm_setting_ovs_interface_get_interface_type(s_ovs_iface),
+                      "dpdk",
+                      "internal",
+                      "patch")) {
+        nm_utils_error_set_literal(error,
+                                   NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                   "unsupported OVS interface type in profile");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static void
+link_changed(NMDevice *device, const NMPlatformLink *pllink)
+{
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(device);
+
+    if (!pllink || !priv->waiting_for_interface)
+        return;
+
+    priv->waiting_for_interface = FALSE;
+
+    if (nm_device_get_state(device) == NM_DEVICE_STATE_IP_CONFIG) {
+        if (!nm_device_hw_addr_set_cloned(device,
+                                          nm_device_get_applied_connection(device),
+                                          FALSE)) {
+            nm_device_state_changed(device,
+                                    NM_DEVICE_STATE_FAILED,
+                                    NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+            return;
+        }
+        nm_device_bring_up(device, TRUE, NULL);
+        nm_device_activate_schedule_stage3_ip_config_start(device);
+    }
+}
+
+static gboolean
+_is_internal_interface(NMDevice *device)
+{
+    NMSettingOvsInterface *s_ovs_iface;
+
+    s_ovs_iface = nm_device_get_applied_setting(device, NM_TYPE_SETTING_OVS_INTERFACE);
+
+    g_return_val_if_fail(s_ovs_iface, FALSE);
+
+    return nm_streq(nm_setting_ovs_interface_get_interface_type(s_ovs_iface), "internal");
+}
+
+static void
+set_platform_mtu_cb(GError *error, gpointer user_data)
+{
+    NMDevice *            device = user_data;
+    NMDeviceOvsInterface *self   = NM_DEVICE_OVS_INTERFACE(device);
+
+    if (error && !g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) {
+        _LOGW(LOGD_DEVICE,
+              "could not change mtu of '%s': %s",
+              nm_device_get_iface(device),
+              error->message);
+    }
+
+    g_object_unref(device);
+}
+
+static gboolean
+set_platform_mtu(NMDevice *device, guint32 mtu)
+{
+    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+
+    /*
+     * If the MTU is not set in ovsdb, Open vSwitch will change
+     * the MTU of an internal interface to match the minimum of
+     * the other interfaces in the bridge.
+     */
+    /* FIXME(shutdown): the function should become cancellable so
+     * that it doesn't need to hold a reference to the device, and
+     * it can be stopped during shutdown.
+     */
+    if (_is_internal_interface(device)) {
+        nm_ovsdb_set_interface_mtu(priv->ovsdb,
+                                   nm_device_get_ip_iface(device),
+                                   mtu,
+                                   set_platform_mtu_cb,
+                                   g_object_ref(device));
+    }
+
+    return NM_DEVICE_CLASS(nm_device_ovs_interface_parent_class)->set_platform_mtu(device, mtu);
+}
+
+static NMActStageReturn
+act_stage3_ip_config_start(NMDevice *           device,
+                           int                  addr_family,
+                           gpointer *           out_config,
+                           NMDeviceStateReason *out_failure_reason)
+{
+    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(device);
+
+    if (!_is_internal_interface(device))
+        return NM_ACT_STAGE_RETURN_IP_FAIL;
+
+    if (nm_device_get_ip_ifindex(device) <= 0) {
+        _LOGT(LOGD_DEVICE, "waiting for link to appear");
+        priv->waiting_for_interface = TRUE;
+        return NM_ACT_STAGE_RETURN_POSTPONE;
+    }
+
+    if (!nm_device_hw_addr_set_cloned(device, nm_device_get_applied_connection(device), FALSE)) {
+        *out_failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED;
+        return NM_ACT_STAGE_RETURN_FAILURE;
+    }
+
+    return NM_DEVICE_CLASS(nm_device_ovs_interface_parent_class)
+        ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason);
+}
+
+static gboolean
+can_unmanaged_external_down(NMDevice *self)
+{
+    return FALSE;
+}
+
+static void
+deactivate(NMDevice *device)
+{
+    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+
+    priv->waiting_for_interface = FALSE;
+}
+
+typedef struct {
+    NMDeviceOvsInterface *     self;
+    GCancellable *             cancellable;
+    NMDeviceDeactivateCallback callback;
+    gpointer                   callback_user_data;
+    gulong                     link_changed_id;
+    gulong                     cancelled_id;
+    guint                      link_timeout_id;
+} DeactivateData;
+
+static void
+deactivate_invoke_cb(DeactivateData *data, GError *error)
+{
+    NMDeviceOvsInterface *self = data->self;
+
+    _LOGT(LOGD_CORE, "deactivate: async callback (%s)", error ? error->message : "success");
+    data->callback(NM_DEVICE(data->self), error, data->callback_user_data);
+
+    nm_clear_g_signal_handler(nm_device_get_platform(NM_DEVICE(data->self)),
+                              &data->link_changed_id);
+    nm_clear_g_signal_handler(data->cancellable, &data->cancelled_id);
+    nm_clear_g_source(&data->link_timeout_id);
+    g_object_unref(data->self);
+    g_object_unref(data->cancellable);
+    nm_g_slice_free(data);
+}
+
+static void
+deactivate_link_changed_cb(NMPlatform *    platform,
+                           int             obj_type_i,
+                           int             ifindex,
+                           NMPlatformLink *info,
+                           int             change_type_i,
+                           DeactivateData *data)
+{
+    NMDeviceOvsInterface *           self        = data->self;
+    const NMPlatformSignalChangeType change_type = change_type_i;
+
+    if (change_type == NM_PLATFORM_SIGNAL_REMOVED
+        && nm_streq0(info->name, nm_device_get_iface(NM_DEVICE(self)))) {
+        _LOGT(LOGD_DEVICE, "deactivate: link removed, proceeding");
+        nm_device_update_from_platform_link(NM_DEVICE(self), NULL);
+        deactivate_invoke_cb(data, NULL);
+        return;
+    }
+}
+
+static gboolean
+deactivate_link_timeout(gpointer user_data)
+{
+    DeactivateData *      data = user_data;
+    NMDeviceOvsInterface *self = data->self;
+
+    _LOGT(LOGD_DEVICE, "deactivate: timeout waiting link removal");
+    deactivate_invoke_cb(data, NULL);
+    return G_SOURCE_REMOVE;
+}
+
+static void
+deactivate_cancelled_cb(GCancellable *cancellable, gpointer user_data)
+{
+    gs_free_error GError *error = NULL;
+
+    nm_utils_error_set_cancelled(&error, FALSE, NULL);
+    deactivate_invoke_cb((DeactivateData *) user_data, error);
+}
+
+static void
+deactivate_cb_on_idle(gpointer user_data, GCancellable *cancellable)
+{
+    DeactivateData *data                  = user_data;
+    gs_free_error GError *cancelled_error = NULL;
+
+    g_cancellable_set_error_if_cancelled(data->cancellable, &cancelled_error);
+    deactivate_invoke_cb(data, cancelled_error);
+}
+
+static void
+deactivate_async(NMDevice *                 device,
+                 GCancellable *             cancellable,
+                 NMDeviceDeactivateCallback callback,
+                 gpointer                   callback_user_data)
+{
+    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+    DeactivateData *             data;
+
+    _LOGT(LOGD_CORE, "deactivate: start async");
+
+    /* We want to ensure that the kernel link for this device is
+     * removed upon disconnection so that it will not interfere with
+     * later activations of the same device. Unfortunately there is
+     * no synchronization mechanism with vswitchd, we only update
+     * ovsdb and wait that changes are picked up.
+     */
+
+    data  = g_slice_new(DeactivateData);
+    *data = (DeactivateData){
+        .self               = g_object_ref(self),
+        .cancellable        = g_object_ref(cancellable),
+        .callback           = callback,
+        .callback_user_data = callback_user_data,
+    };
+
+    if (!priv->waiting_for_interface
+        && !nm_platform_link_get_by_ifname(nm_device_get_platform(device),
+                                           nm_device_get_iface(device))) {
+        _LOGT(LOGD_CORE, "deactivate: link not present, proceeding");
+        nm_device_update_from_platform_link(NM_DEVICE(self), NULL);
+        nm_utils_invoke_on_idle(cancellable, deactivate_cb_on_idle, data);
+        return;
+    }
+
+    if (priv->waiting_for_interface) {
+        /* At this point we have issued an INSERT and a DELETE
+         * command for the interface to ovsdb. We don't know if
+         * vswitchd will see the two updates or only one. We
+         * must add a timeout to avoid waiting forever in case
+         * the link doesn't appear.
+         */
+        data->link_timeout_id = g_timeout_add(6000, deactivate_link_timeout, data);
+        _LOGT(LOGD_DEVICE, "deactivate: waiting for link to disappear in 6 seconds");
+    } else
+        _LOGT(LOGD_DEVICE, "deactivate: waiting for link to disappear");
+
+    data->cancelled_id =
+        g_cancellable_connect(cancellable, G_CALLBACK(deactivate_cancelled_cb), data, NULL);
+    data->link_changed_id = g_signal_connect(nm_device_get_platform(device),
+                                             NM_PLATFORM_SIGNAL_LINK_CHANGED,
+                                             G_CALLBACK(deactivate_link_changed_cb),
+                                             data);
+}
+
+static gboolean
+can_update_from_platform_link(NMDevice *device, const NMPlatformLink *plink)
+{
+    /* If the device is deactivating, we already sent the
+     * deletion command to ovsdb and we don't want to deal
+     * with any new link appearing from the previous
+     * activation.
+     */
+    return !plink || nm_device_get_state(device) != NM_DEVICE_STATE_DEACTIVATING;
+}
+
+/*****************************************************************************/
+
+static void
+ovsdb_ready(NMOvsdb *ovsdb, NMDeviceOvsInterface *self)
+{
+    NMDevice *device = NM_DEVICE(self);
+
+    nm_device_queue_recheck_available(device,
+                                      NM_DEVICE_STATE_REASON_NONE,
+                                      NM_DEVICE_STATE_REASON_NONE);
+    nm_device_recheck_available_connections(device);
+    nm_device_emit_recheck_auto_activate(device);
+}
+
+static void
+nm_device_ovs_interface_init(NMDeviceOvsInterface *self)
+{
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+
+    priv->ovsdb = g_object_ref(nm_ovsdb_get());
+
+    if (!nm_ovsdb_is_ready(priv->ovsdb))
+        g_signal_connect(priv->ovsdb, NM_OVSDB_READY, G_CALLBACK(ovsdb_ready), self);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(object);
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+
+    if (priv->ovsdb) {
+        g_signal_handlers_disconnect_by_func(priv->ovsdb, G_CALLBACK(ovsdb_ready), self);
+        g_clear_object(&priv->ovsdb);
+    }
+
+    G_OBJECT_CLASS(nm_device_ovs_interface_parent_class)->dispose(object);
+}
+
+static const NMDBusInterfaceInfoExtended interface_info_device_ovs_interface = {
+    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
+        NM_DBUS_INTERFACE_DEVICE_OVS_INTERFACE,
+        .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), ),
+    .legacy_property_changed = TRUE,
+};
+
+static void
+nm_device_ovs_interface_class_init(NMDeviceOvsInterfaceClass *klass)
+{
+    GObjectClass *     object_class      = G_OBJECT_CLASS(klass);
+    NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
+    NMDeviceClass *    device_class      = NM_DEVICE_CLASS(klass);
+
+    object_class->dispose = dispose;
+
+    dbus_object_class->interface_infos =
+        NM_DBUS_INTERFACE_INFOS(&interface_info_device_ovs_interface);
+
+    device_class->connection_type_supported        = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
+    device_class->connection_type_check_compatible = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
+    device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_OPENVSWITCH);
+
+    device_class->can_update_from_platform_link       = can_update_from_platform_link;
+    device_class->deactivate                          = deactivate;
+    device_class->deactivate_async                    = deactivate_async;
+    device_class->get_type_description                = get_type_description;
+    device_class->create_and_realize                  = create_and_realize;
+    device_class->get_generic_capabilities            = get_generic_capabilities;
+    device_class->is_available                        = is_available;
+    device_class->check_connection_compatible         = check_connection_compatible;
+    device_class->link_changed                        = link_changed;
+    device_class->act_stage3_ip_config_start          = act_stage3_ip_config_start;
+    device_class->can_unmanaged_external_down         = can_unmanaged_external_down;
+    device_class->set_platform_mtu                    = set_platform_mtu;
+    device_class->get_configured_mtu                  = nm_device_get_configured_mtu_for_wired;
+    device_class->can_reapply_change_ovs_external_ids = TRUE;
+    device_class->reapply_connection                  = nm_device_ovs_reapply_connection;
+}
diff --git a/src/core/devices/ovs/nm-device-ovs-interface.h b/src/core/devices/ovs/nm-device-ovs-interface.h
new file mode 100644
index 00000000..03b0e9a4
--- /dev/null
+++ b/src/core/devices/ovs/nm-device-ovs-interface.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_OVS_INTERFACE_H__
+#define __NETWORKMANAGER_DEVICE_OVS_INTERFACE_H__
+
+#define NM_TYPE_DEVICE_OVS_INTERFACE (nm_device_ovs_interface_get_type())
+#define NM_DEVICE_OVS_INTERFACE(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_OVS_INTERFACE, NMDeviceOvsInterface))
+#define NM_DEVICE_OVS_INTERFACE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_OVS_INTERFACE, NMDeviceOvsInterfaceClass))
+#define NM_IS_DEVICE_OVS_INTERFACE(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_OVS_INTERFACE))
+#define NM_IS_DEVICE_OVS_INTERFACE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_OVS_INTERFACE))
+#define NM_DEVICE_OVS_INTERFACE_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_OVS_INTERFACE, NMDeviceOvsInterfaceClass))
+
+typedef struct _NMDeviceOvsInterface      NMDeviceOvsInterface;
+typedef struct _NMDeviceOvsInterfaceClass NMDeviceOvsInterfaceClass;
+
+GType nm_device_ovs_interface_get_type(void);
+
+#endif /* __NETWORKMANAGER_DEVICE_OVS_INTERFACE_H__ */
diff --git a/src/core/devices/ovs/nm-device-ovs-port.c b/src/core/devices/ovs/nm-device-ovs-port.c
new file mode 100644
index 00000000..2ecb95e8
--- /dev/null
+++ b/src/core/devices/ovs/nm-device-ovs-port.c
@@ -0,0 +1,196 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-device-ovs-port.h"
+
+#include "nm-device-ovs-interface.h"
+#include "nm-device-ovs-bridge.h"
+#include "nm-ovsdb.h"
+
+#include "devices/nm-device-private.h"
+#include "nm-active-connection.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-ovs-port.h"
+#include "nm-setting-ovs-port.h"
+
+#define _NMLOG_DEVICE_TYPE NMDeviceOvsPort
+#include "devices/nm-device-logging.h"
+
+/*****************************************************************************/
+
+struct _NMDeviceOvsPort {
+    NMDevice parent;
+};
+
+struct _NMDeviceOvsPortClass {
+    NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE(NMDeviceOvsPort, nm_device_ovs_port, NM_TYPE_DEVICE)
+
+/*****************************************************************************/
+
+static const char *
+get_type_description(NMDevice *device)
+{
+    return "ovs-port";
+}
+
+static gboolean
+create_and_realize(NMDevice *             device,
+                   NMConnection *         connection,
+                   NMDevice *             parent,
+                   const NMPlatformLink **out_plink,
+                   GError **              error)
+{
+    /* The port will be added to ovsdb when an interface is enslaved,
+     * because there's no such thing like an empty port. */
+
+    return TRUE;
+}
+
+static NMDeviceCapabilities
+get_generic_capabilities(NMDevice *device)
+{
+    return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+static NMActStageReturn
+act_stage3_ip_config_start(NMDevice *           device,
+                           int                  addr_family,
+                           gpointer *           out_config,
+                           NMDeviceStateReason *out_failure_reason)
+{
+    return NM_ACT_STAGE_RETURN_IP_FAIL;
+}
+
+static void
+add_iface_cb(GError *error, gpointer user_data)
+{
+    NMDevice *slave = user_data;
+
+    if (error && !g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) {
+        nm_log_warn(LOGD_DEVICE,
+                    "device %s could not be added to a ovs port: %s",
+                    nm_device_get_iface(slave),
+                    error->message);
+        nm_device_state_changed(slave, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_OVSDB_FAILED);
+    }
+
+    g_object_unref(slave);
+}
+
+static gboolean
+enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
+{
+    NMDeviceOvsPort *   self      = NM_DEVICE_OVS_PORT(device);
+    NMActiveConnection *ac_port   = NULL;
+    NMActiveConnection *ac_bridge = NULL;
+    NMDevice *          bridge_device;
+
+    if (!configure)
+        return TRUE;
+
+    ac_port   = NM_ACTIVE_CONNECTION(nm_device_get_act_request(device));
+    ac_bridge = nm_active_connection_get_master(ac_port);
+    if (!ac_bridge) {
+        _LOGW(LOGD_DEVICE,
+              "can't enslave %s: bridge active-connection not found",
+              nm_device_get_iface(slave));
+        return FALSE;
+    }
+
+    bridge_device = nm_active_connection_get_device(ac_bridge);
+    if (!bridge_device) {
+        _LOGW(LOGD_DEVICE, "can't enslave %s: bridge device not found", nm_device_get_iface(slave));
+        return FALSE;
+    }
+
+    nm_ovsdb_add_interface(nm_ovsdb_get(),
+                           nm_active_connection_get_applied_connection(ac_bridge),
+                           nm_device_get_applied_connection(device),
+                           nm_device_get_applied_connection(slave),
+                           bridge_device,
+                           slave,
+                           add_iface_cb,
+                           g_object_ref(slave));
+
+    return TRUE;
+}
+
+static void
+del_iface_cb(GError *error, gpointer user_data)
+{
+    NMDevice *slave = user_data;
+
+    if (error && !g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) {
+        nm_log_warn(LOGD_DEVICE,
+                    "device %s could not be removed from a ovs port: %s",
+                    nm_device_get_iface(slave),
+                    error->message);
+        nm_device_state_changed(slave, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_OVSDB_FAILED);
+    }
+
+    g_object_unref(slave);
+}
+
+static void
+release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
+{
+    NMDeviceOvsPort *self = NM_DEVICE_OVS_PORT(device);
+
+    if (configure) {
+        _LOGI(LOGD_DEVICE, "releasing ovs interface %s", nm_device_get_ip_iface(slave));
+        nm_ovsdb_del_interface(nm_ovsdb_get(),
+                               nm_device_get_iface(slave),
+                               del_iface_cb,
+                               g_object_ref(slave));
+        /* Open VSwitch is going to delete this one. We must ignore what happens
+         * next with the interface. */
+        if (NM_IS_DEVICE_OVS_INTERFACE(slave))
+            nm_device_update_from_platform_link(slave, NULL);
+    } else
+        _LOGI(LOGD_DEVICE, "ovs interface %s was released", nm_device_get_ip_iface(slave));
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_ovs_port_init(NMDeviceOvsPort *self)
+{}
+
+static const NMDBusInterfaceInfoExtended interface_info_device_ovs_port = {
+    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
+        NM_DBUS_INTERFACE_DEVICE_OVS_PORT,
+        .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ),
+        .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), ),
+    .legacy_property_changed = TRUE,
+};
+
+static void
+nm_device_ovs_port_class_init(NMDeviceOvsPortClass *klass)
+{
+    NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
+    NMDeviceClass *    device_class      = NM_DEVICE_CLASS(klass);
+
+    dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_ovs_port);
+
+    device_class->connection_type_supported        = NM_SETTING_OVS_PORT_SETTING_NAME;
+    device_class->connection_type_check_compatible = NM_SETTING_OVS_PORT_SETTING_NAME;
+    device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES();
+
+    device_class->is_master                           = TRUE;
+    device_class->get_type_description                = get_type_description;
+    device_class->create_and_realize                  = create_and_realize;
+    device_class->get_generic_capabilities            = get_generic_capabilities;
+    device_class->act_stage3_ip_config_start          = act_stage3_ip_config_start;
+    device_class->enslave_slave                       = enslave_slave;
+    device_class->release_slave                       = release_slave;
+    device_class->can_reapply_change_ovs_external_ids = TRUE;
+    device_class->reapply_connection                  = nm_device_ovs_reapply_connection;
+}
diff --git a/src/core/devices/ovs/nm-device-ovs-port.h b/src/core/devices/ovs/nm-device-ovs-port.h
new file mode 100644
index 00000000..f4516c01
--- /dev/null
+++ b/src/core/devices/ovs/nm-device-ovs-port.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_OVS_PORT_H__
+#define __NETWORKMANAGER_DEVICE_OVS_PORT_H__
+
+#define NM_TYPE_DEVICE_OVS_PORT (nm_device_ovs_port_get_type())
+#define NM_DEVICE_OVS_PORT(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_OVS_PORT, NMDeviceOvsPort))
+#define NM_DEVICE_OVS_PORT_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_OVS_PORT, NMDeviceOvsPortClass))
+#define NM_IS_DEVICE_OVS_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_OVS_PORT))
+#define NM_IS_DEVICE_OVS_PORT_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_OVS_PORT))
+#define NM_DEVICE_OVS_PORT_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_OVS_PORT, NMDeviceOvsPortClass))
+
+typedef struct _NMDeviceOvsPort      NMDeviceOvsPort;
+typedef struct _NMDeviceOvsPortClass NMDeviceOvsPortClass;
+
+GType nm_device_ovs_port_get_type(void);
+
+#endif /* __NETWORKMANAGER_DEVICE_OVS_PORT_H__ */
diff --git a/src/core/devices/ovs/nm-ovs-factory.c b/src/core/devices/ovs/nm-ovs-factory.c
new file mode 100644
index 00000000..e7af38d8
--- /dev/null
+++ b/src/core/devices/ovs/nm-ovs-factory.c
@@ -0,0 +1,324 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-manager.h"
+#include "nm-ovsdb.h"
+#include "nm-device-ovs-interface.h"
+#include "nm-device-ovs-port.h"
+#include "nm-device-ovs-bridge.h"
+#include "platform/nm-platform.h"
+#include "nm-core-internal.h"
+#include "settings/nm-settings.h"
+#include "devices/nm-device-factory.h"
+#include "devices/nm-device-private.h"
+
+/*****************************************************************************/
+
+typedef struct {
+    NMDeviceFactory parent;
+} NMOvsFactory;
+
+typedef struct {
+    NMDeviceFactoryClass parent;
+} NMOvsFactoryClass;
+
+#define NM_TYPE_OVS_FACTORY (nm_ovs_factory_get_type())
+#define NM_OVS_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_OVS_FACTORY, NMOvsFactory))
+#define NM_OVS_FACTORY_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_OVS_FACTORY, NMOvsFactoryClass))
+#define NM_IS_OVS_FACTORY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_OVS_FACTORY))
+#define NM_IS_OVS_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_OVS_FACTORY))
+#define NM_OVS_FACTORY_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_OVS_FACTORY, NMOvsFactoryClass))
+
+static GType nm_ovs_factory_get_type(void);
+G_DEFINE_TYPE(NMOvsFactory, nm_ovs_factory, NM_TYPE_DEVICE_FACTORY)
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN LOGD_DEVICE
+#define _NMLOG(level, ifname, con_uuid, ...)                                                  \
+    G_STMT_START                                                                              \
+    {                                                                                         \
+        nm_log((level),                                                                       \
+               _NMLOG_DOMAIN,                                                                 \
+               (ifname),                                                                      \
+               (con_uuid),                                                                    \
+               "ovs: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    }                                                                                         \
+    G_STMT_END
+
+/*****************************************************************************/
+
+NM_DEVICE_FACTORY_DECLARE_TYPES(
+    NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_OPENVSWITCH)
+        NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_OVS_BRIDGE_SETTING_NAME,
+                                                NM_SETTING_OVS_INTERFACE_SETTING_NAME,
+                                                NM_SETTING_OVS_PORT_SETTING_NAME))
+
+G_MODULE_EXPORT NMDeviceFactory *
+                nm_device_factory_create(GError **error)
+{
+    nm_manager_set_capability(NM_MANAGER_GET, NM_CAPABILITY_OVS);
+    return g_object_new(NM_TYPE_OVS_FACTORY, NULL);
+}
+
+static NMDevice *
+new_device_from_type(const char *name, NMDeviceType device_type)
+{
+    GType       type;
+    const char *type_desc;
+    NMLinkType  link_type = NM_LINK_TYPE_NONE;
+
+    if (nm_manager_get_device(NM_MANAGER_GET, name, device_type))
+        return NULL;
+
+    if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE) {
+        type      = NM_TYPE_DEVICE_OVS_INTERFACE;
+        type_desc = "Open vSwitch Interface";
+        link_type = NM_LINK_TYPE_OPENVSWITCH;
+    } else if (device_type == NM_DEVICE_TYPE_OVS_PORT) {
+        type      = NM_TYPE_DEVICE_OVS_PORT;
+        type_desc = "Open vSwitch Port";
+    } else if (device_type == NM_DEVICE_TYPE_OVS_BRIDGE) {
+        type      = NM_TYPE_DEVICE_OVS_BRIDGE;
+        type_desc = "Open vSwitch Bridge";
+    } else {
+        return NULL;
+    }
+
+    return g_object_new(type,
+                        NM_DEVICE_IFACE,
+                        name,
+                        NM_DEVICE_DRIVER,
+                        "openvswitch",
+                        NM_DEVICE_DEVICE_TYPE,
+                        device_type,
+                        NM_DEVICE_TYPE_DESC,
+                        type_desc,
+                        NM_DEVICE_LINK_TYPE,
+                        link_type,
+                        NULL);
+}
+
+static void
+ovsdb_device_added(NMOvsdb *        ovsdb,
+                   const char *     name,
+                   guint            device_type_i,
+                   const char *     subtype,
+                   NMDeviceFactory *self)
+{
+    const NMDeviceType device_type = device_type_i;
+    NMDevice *         device;
+
+    if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE
+        && !NM_IN_STRSET(subtype, "internal", "patch")) {
+        /* system interfaces refer to kernel devices and
+         * don't need to be created by this factory. Ignore
+         * anything that is not an internal or patch
+         * interface. */
+        return;
+    }
+
+    device = new_device_from_type(name, device_type);
+    if (!device)
+        return;
+
+    g_signal_emit_by_name(self, NM_DEVICE_FACTORY_DEVICE_ADDED, device);
+    g_object_unref(device);
+}
+
+static void
+ovsdb_device_removed(NMOvsdb *        ovsdb,
+                     const char *     name,
+                     guint            device_type_i,
+                     const char *     subtype,
+                     NMDeviceFactory *self)
+{
+    const NMDeviceType device_type = device_type_i;
+    NMDevice *         device      = NULL;
+    NMDeviceState      device_state;
+    gboolean           is_system_interface = FALSE;
+
+    if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE
+        && !NM_IN_STRSET(subtype, "internal", "patch", "system"))
+        return;
+
+    if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE && nm_streq0(subtype, "system")) {
+        NMDevice *             d;
+        const CList *          list;
+        NMSettingOvsInterface *s_ovs_int;
+
+        /* The device associated to an OVS system interface can be of
+         * any kind. Find an interface with the same name and which has
+         * the OVS-interface setting. */
+        is_system_interface = TRUE;
+        nm_manager_for_each_device (NM_MANAGER_GET, d, list) {
+            if (!nm_streq0(nm_device_get_iface(d), name))
+                continue;
+            s_ovs_int = nm_device_get_applied_setting(d, NM_TYPE_SETTING_OVS_INTERFACE);
+            if (!s_ovs_int)
+                continue;
+            if (!nm_streq0(nm_setting_ovs_interface_get_interface_type(s_ovs_int), "system"))
+                continue;
+            /* Failing the system interface device is almost always the right
+             * thing to do when the ovsdb entry is removed. However, to avoid
+             * that a late device-removed signal tears down a different,
+             * newly-activated connection, let's also check that we have a master.
+             * Or in alternative, that the device is assumed/external: in such
+             * case it's always fine to fail the device.
+             */
+            if (!nm_device_get_master(d) && !nm_device_sys_iface_state_is_external_or_assume(d))
+                continue;
+
+            device = d;
+        }
+    } else {
+        device = nm_manager_get_device(NM_MANAGER_GET, name, device_type);
+    }
+
+    if (!device)
+        return;
+
+    device_state = nm_device_get_state(device);
+
+    if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE && nm_device_get_act_request(device)
+        && device_state < NM_DEVICE_STATE_DEACTIVATING) {
+        nm_device_state_changed(device,
+                                NM_DEVICE_STATE_DEACTIVATING,
+                                NM_DEVICE_STATE_REASON_REMOVED);
+        return;
+    }
+
+    /* OVS system interfaces still exist even without the ovsdb entry */
+    if (!is_system_interface && device_state == NM_DEVICE_STATE_UNMANAGED) {
+        nm_device_unrealize(device, TRUE, NULL);
+    }
+}
+
+static void
+ovsdb_interface_failed(NMOvsdb *        ovsdb,
+                       const char *     name,
+                       const char *     connection_uuid,
+                       const char *     error,
+                       NMDeviceFactory *self)
+{
+    NMDevice *             device     = NULL;
+    NMSettingsConnection * connection = NULL;
+    NMConnection *         c;
+    const char *           type;
+    NMSettingOvsInterface *s_ovs_int;
+    gboolean               is_patch = FALSE;
+    gboolean               ignore;
+
+    device = nm_manager_get_device(NM_MANAGER_GET, name, NM_DEVICE_TYPE_OVS_INTERFACE);
+    if (device && connection_uuid) {
+        connection =
+            nm_settings_get_connection_by_uuid(nm_device_get_settings(device), connection_uuid);
+    }
+
+    /* The patch interface which gets created first is expected to
+     * fail because the second patch doesn't exist yet. Ignore all
+     * failures of patch interfaces. */
+    if (connection && (c = nm_settings_connection_get_connection(connection))
+        && (type = nm_connection_get_connection_type(c))
+        && nm_streq0(type, NM_SETTING_OVS_INTERFACE_SETTING_NAME)
+        && (s_ovs_int = nm_connection_get_setting_ovs_interface(c))
+        && nm_streq0(nm_setting_ovs_interface_get_interface_type(s_ovs_int), "patch"))
+        is_patch = TRUE;
+
+    ignore = !device || is_patch;
+
+    _NMLOG(ignore ? LOGL_DEBUG : LOGL_INFO,
+           name,
+           connection_uuid,
+           "ovs interface \"%s\" (%s) failed%s: %s",
+           name,
+           connection_uuid,
+           ignore ? " (ignored)" : "",
+           error);
+
+    if (ignore)
+        return;
+
+    if (connection) {
+        nm_settings_connection_autoconnect_blocked_reason_set(
+            connection,
+            NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+            TRUE);
+    }
+
+    nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_OVSDB_FAILED);
+}
+
+static void
+start(NMDeviceFactory *self)
+{
+    NMOvsdb *ovsdb;
+
+    ovsdb = nm_ovsdb_get();
+
+    g_signal_connect_object(ovsdb,
+                            NM_OVSDB_DEVICE_ADDED,
+                            G_CALLBACK(ovsdb_device_added),
+                            self,
+                            (GConnectFlags) 0);
+    g_signal_connect_object(ovsdb,
+                            NM_OVSDB_DEVICE_REMOVED,
+                            G_CALLBACK(ovsdb_device_removed),
+                            self,
+                            (GConnectFlags) 0);
+    g_signal_connect_object(ovsdb,
+                            NM_OVSDB_INTERFACE_FAILED,
+                            G_CALLBACK(ovsdb_interface_failed),
+                            self,
+                            (GConnectFlags) 0);
+}
+
+static NMDevice *
+create_device(NMDeviceFactory *     self,
+              const char *          iface,
+              const NMPlatformLink *plink,
+              NMConnection *        connection,
+              gboolean *            out_ignore)
+{
+    NMDeviceType device_type     = NM_DEVICE_TYPE_UNKNOWN;
+    const char * connection_type = NULL;
+
+    if (g_strcmp0(iface, "ovs-system") == 0) {
+        *out_ignore = TRUE;
+        return NULL;
+    }
+
+    if (connection)
+        connection_type = nm_connection_get_connection_type(connection);
+
+    if (plink)
+        device_type = NM_DEVICE_TYPE_OVS_INTERFACE;
+    else if (g_strcmp0(connection_type, NM_SETTING_OVS_INTERFACE_SETTING_NAME) == 0)
+        device_type = NM_DEVICE_TYPE_OVS_INTERFACE;
+    else if (g_strcmp0(connection_type, NM_SETTING_OVS_PORT_SETTING_NAME) == 0)
+        device_type = NM_DEVICE_TYPE_OVS_PORT;
+    else if (g_strcmp0(connection_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME) == 0)
+        device_type = NM_DEVICE_TYPE_OVS_BRIDGE;
+
+    return new_device_from_type(iface, device_type);
+}
+
+static void
+nm_ovs_factory_init(NMOvsFactory *self)
+{}
+
+static void
+nm_ovs_factory_class_init(NMOvsFactoryClass *klass)
+{
+    NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS(klass);
+
+    factory_class->get_supported_types = get_supported_types;
+    factory_class->start               = start;
+    factory_class->create_device       = create_device;
+}
diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c
new file mode 100644
index 00000000..da3a7989
--- /dev/null
+++ b/src/core/devices/ovs/nm-ovsdb.c
@@ -0,0 +1,2658 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-ovsdb.h"
+
+#include <gmodule.h>
+#include <gio/gunixsocketaddress.h>
+
+#include "nm-glib-aux/nm-jansson.h"
+#include "nm-glib-aux/nm-str-buf.h"
+#include "nm-core-utils.h"
+#include "nm-core-internal.h"
+#include "devices/nm-device.h"
+#include "nm-manager.h"
+#include "nm-setting-ovs-external-ids.h"
+
+/*****************************************************************************/
+
+#define OVSDB_MAX_FAILURES 3
+
+/*****************************************************************************/
+
+#if JANSSON_VERSION_HEX < 0x020400
+    #warning "requires at least libjansson 2.4"
+#endif
+
+typedef struct {
+    char *     port_uuid;
+    char *     name;
+    char *     connection_uuid;
+    GPtrArray *interfaces; /* interface uuids */
+    GArray *   external_ids;
+} OpenvswitchPort;
+
+typedef struct {
+    char *     bridge_uuid;
+    char *     name;
+    char *     connection_uuid;
+    GPtrArray *ports; /* port uuids */
+    GArray *   external_ids;
+} OpenvswitchBridge;
+
+typedef struct {
+    char *  interface_uuid;
+    char *  name;
+    char *  type;
+    char *  connection_uuid;
+    GArray *external_ids;
+} OpenvswitchInterface;
+
+/*****************************************************************************/
+
+typedef void (*OvsdbMethodCallback)(NMOvsdb *self,
+                                    json_t * response,
+                                    GError * error,
+                                    gpointer user_data);
+
+typedef enum {
+    OVSDB_MONITOR,
+    OVSDB_ADD_INTERFACE,
+    OVSDB_DEL_INTERFACE,
+    OVSDB_SET_INTERFACE_MTU,
+    OVSDB_SET_EXTERNAL_IDS,
+} OvsdbCommand;
+
+#define CALL_ID_UNSPEC G_MAXUINT64
+
+typedef union {
+    struct {
+    } monitor;
+    struct {
+        NMConnection *bridge;
+        NMConnection *port;
+        NMConnection *interface;
+        NMDevice *    bridge_device;
+        NMDevice *    interface_device;
+    } add_interface;
+    struct {
+        char *ifname;
+    } del_interface;
+    struct {
+        char *  ifname;
+        guint32 mtu;
+    } set_interface_mtu;
+    struct {
+        NMDeviceType device_type;
+        char *       ifname;
+        char *       connection_uuid;
+        GHashTable * exid_old;
+        GHashTable * exid_new;
+    } set_external_ids;
+} OvsdbMethodPayload;
+
+typedef struct {
+    NMOvsdb *           self;
+    CList               calls_lst;
+    guint64             call_id;
+    OvsdbCommand        command;
+    OvsdbMethodCallback callback;
+    gpointer            user_data;
+    OvsdbMethodPayload  payload;
+} OvsdbMethodCall;
+
+/*****************************************************************************/
+
+enum {
+    DEVICE_ADDED,
+    DEVICE_REMOVED,
+    INTERFACE_FAILED,
+    READY,
+    LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+typedef struct {
+    GSocketClient *    client;
+    GSocketConnection *conn;
+    GCancellable *     cancellable;
+    char               buf[4096]; /* Input buffer */
+    size_t             bufp;      /* Last decoded byte in the input buffer. */
+    GString *          input;     /* JSON stream waiting for decoding. */
+    GString *          output;    /* JSON stream to be sent. */
+    guint64            call_id_counter;
+
+    CList calls_lst_head;
+
+    GHashTable *interfaces; /* interface uuid => OpenvswitchInterface */
+    GHashTable *ports;      /* port uuid => OpenvswitchPort */
+    GHashTable *bridges;    /* bridge uuid => OpenvswitchBridge */
+    char *      db_uuid;
+    guint       num_failures;
+    guint       num_pending_deletions;
+    bool        ready : 1;
+} NMOvsdbPrivate;
+
+struct _NMOvsdb {
+    GObject        parent;
+    NMOvsdbPrivate _priv;
+};
+
+struct _NMOvsdbClass {
+    GObjectClass parent;
+};
+
+G_DEFINE_TYPE(NMOvsdb, nm_ovsdb, G_TYPE_OBJECT)
+
+#define NM_OVSDB_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMOvsdb, NM_IS_OVSDB)
+
+NM_DEFINE_SINGLETON_GETTER(NMOvsdb, nm_ovsdb_get, NM_TYPE_OVSDB);
+
+/*****************************************************************************/
+
+static void ovsdb_try_connect(NMOvsdb *self);
+static void ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing);
+static void ovsdb_read(NMOvsdb *self);
+static void ovsdb_write(NMOvsdb *self);
+static void ovsdb_next_command(NMOvsdb *self);
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_DEVICE
+#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "ovsdb", __VA_ARGS__)
+
+#define _NMLOG_call(level, call, ...)                                                  \
+    _NMLOG((level),                                                                    \
+           "call[" NM_HASH_OBFUSCATE_PTR_FMT "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+           NM_HASH_OBFUSCATE_PTR((call)) _NM_UTILS_MACRO_REST(__VA_ARGS__))
+
+#define _LOGT_call(call, ...) _NMLOG_call(LOGL_TRACE, (call), __VA_ARGS__)
+
+/*****************************************************************************/
+
+#define OVSDB_METHOD_PAYLOAD_MONITOR() \
+    (&((const OvsdbMethodPayload){     \
+        .monitor = {},                 \
+    }))
+
+#define OVSDB_METHOD_PAYLOAD_ADD_INTERFACE(xbridge,           \
+                                           xport,             \
+                                           xinterface,        \
+                                           xbridge_device,    \
+                                           xinterface_device) \
+    (&((const OvsdbMethodPayload){                            \
+        .add_interface =                                      \
+            {                                                 \
+                .bridge           = (xbridge),                \
+                .port             = (xport),                  \
+                .interface        = (xinterface),             \
+                .bridge_device    = (xbridge_device),         \
+                .interface_device = (xinterface_device),      \
+            },                                                \
+    }))
+
+#define OVSDB_METHOD_PAYLOAD_DEL_INTERFACE(xifname)               \
+    (&((const OvsdbMethodPayload){                                \
+        .del_interface =                                          \
+            {                                                     \
+                .ifname = (char *) NM_CONSTCAST(char, (xifname)), \
+            },                                                    \
+    }))
+
+#define OVSDB_METHOD_PAYLOAD_SET_INTERFACE_MTU(xifname, xmtu)     \
+    (&((const OvsdbMethodPayload){                                \
+        .set_interface_mtu =                                      \
+            {                                                     \
+                .ifname = (char *) NM_CONSTCAST(char, (xifname)), \
+                .mtu    = (xmtu),                                 \
+            },                                                    \
+    }))
+
+#define OVSDB_METHOD_PAYLOAD_SET_EXTERNAL_IDS(xdevice_type,                         \
+                                              xifname,                              \
+                                              xconnection_uuid,                     \
+                                              xexid_old,                            \
+                                              xexid_new)                            \
+    (&((const OvsdbMethodPayload){                                                  \
+        .set_external_ids =                                                         \
+            {                                                                       \
+                .device_type     = xdevice_type,                                    \
+                .ifname          = (char *) NM_CONSTCAST(char, (xifname)),          \
+                .connection_uuid = (char *) NM_CONSTCAST(char, (xconnection_uuid)), \
+                .exid_old        = (xexid_old),                                     \
+                .exid_new        = (xexid_new),                                     \
+            },                                                                      \
+    }))
+
+/*****************************************************************************/
+
+static NM_UTILS_LOOKUP_STR_DEFINE(_device_type_to_table,
+                                  NMDeviceType,
+                                  NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(NULL),
+                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_TYPE_OVS_BRIDGE, "Bridge"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_TYPE_OVS_PORT, "Port"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_TYPE_OVS_INTERFACE,
+                                                           "Interface"),
+                                  NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), );
+
+/*****************************************************************************/
+
+static void
+_call_complete(OvsdbMethodCall *call, json_t *response, GError *error)
+{
+    if (response) {
+        gs_free char *str = NULL;
+
+        str = json_dumps(response, 0);
+        if (error)
+            _LOGT_call(call, "completed: %s ; error: %s", str, error->message);
+        else
+            _LOGT_call(call, "completed: %s", str);
+    } else {
+        nm_assert(error);
+        _LOGT_call(call, "completed: error: %s", error->message);
+    }
+
+    c_list_unlink_stale(&call->calls_lst);
+
+    if (call->callback)
+        call->callback(call->self, response, error, call->user_data);
+
+    switch (call->command) {
+    case OVSDB_MONITOR:
+        break;
+    case OVSDB_ADD_INTERFACE:
+        g_clear_object(&call->payload.add_interface.bridge);
+        g_clear_object(&call->payload.add_interface.port);
+        g_clear_object(&call->payload.add_interface.interface);
+        g_clear_object(&call->payload.add_interface.bridge_device);
+        g_clear_object(&call->payload.add_interface.interface_device);
+        break;
+    case OVSDB_DEL_INTERFACE:
+        nm_clear_g_free(&call->payload.del_interface.ifname);
+        break;
+    case OVSDB_SET_INTERFACE_MTU:
+        nm_clear_g_free(&call->payload.set_interface_mtu.ifname);
+        break;
+    case OVSDB_SET_EXTERNAL_IDS:
+        nm_clear_g_free(&call->payload.set_external_ids.ifname);
+        nm_clear_g_free(&call->payload.set_external_ids.connection_uuid);
+        nm_clear_pointer(&call->payload.set_external_ids.exid_old, g_hash_table_destroy);
+        nm_clear_pointer(&call->payload.set_external_ids.exid_new, g_hash_table_destroy);
+        break;
+    }
+
+    nm_g_slice_free(call);
+}
+
+/*****************************************************************************/
+
+static void
+_free_bridge(OpenvswitchBridge *ovs_bridge)
+{
+    g_free(ovs_bridge->bridge_uuid);
+    g_free(ovs_bridge->name);
+    g_free(ovs_bridge->connection_uuid);
+    g_ptr_array_free(ovs_bridge->ports, TRUE);
+    nm_g_array_unref(ovs_bridge->external_ids);
+    nm_g_slice_free(ovs_bridge);
+}
+
+static void
+_free_port(OpenvswitchPort *ovs_port)
+{
+    g_free(ovs_port->port_uuid);
+    g_free(ovs_port->name);
+    g_free(ovs_port->connection_uuid);
+    g_ptr_array_free(ovs_port->interfaces, TRUE);
+    nm_g_array_unref(ovs_port->external_ids);
+    nm_g_slice_free(ovs_port);
+}
+
+static void
+_free_interface(OpenvswitchInterface *ovs_interface)
+{
+    g_free(ovs_interface->interface_uuid);
+    g_free(ovs_interface->name);
+    g_free(ovs_interface->connection_uuid);
+    g_free(ovs_interface->type);
+    nm_g_array_unref(ovs_interface->external_ids);
+    nm_g_slice_free(ovs_interface);
+}
+
+/*****************************************************************************/
+
+static void
+_signal_emit_device_added(NMOvsdb *    self,
+                          const char * name,
+                          NMDeviceType device_type,
+                          const char * device_subtype)
+{
+    g_signal_emit(self, signals[DEVICE_ADDED], 0, name, (guint) device_type, device_subtype);
+}
+
+static void
+_signal_emit_device_removed(NMOvsdb *    self,
+                            const char * name,
+                            NMDeviceType device_type,
+                            const char * device_subtype)
+{
+    g_signal_emit(self, signals[DEVICE_REMOVED], 0, name, (guint) device_type, device_subtype);
+}
+
+static void
+_signal_emit_interface_failed(NMOvsdb *   self,
+                              const char *name,
+                              const char *connection_uuid,
+                              const char *error)
+{
+    g_signal_emit(self, signals[INTERFACE_FAILED], 0, name, connection_uuid, error);
+}
+
+/*****************************************************************************/
+
+/**
+ * ovsdb_call_method:
+ *
+ * Queues the ovsdb command. Eventually fires the command right away if
+ * there's no command pending completion.
+ */
+static void
+ovsdb_call_method(NMOvsdb *                 self,
+                  OvsdbMethodCallback       callback,
+                  gpointer                  user_data,
+                  gboolean                  add_first,
+                  OvsdbCommand              command,
+                  const OvsdbMethodPayload *payload)
+{
+    NMOvsdbPrivate * priv = NM_OVSDB_GET_PRIVATE(self);
+    OvsdbMethodCall *call;
+
+    /* Ensure we're not unsynchronized before we queue the method call. */
+    ovsdb_try_connect(self);
+
+    call  = g_slice_new(OvsdbMethodCall);
+    *call = (OvsdbMethodCall){
+        .self      = self,
+        .call_id   = CALL_ID_UNSPEC,
+        .command   = command,
+        .callback  = callback,
+        .user_data = user_data,
+    };
+
+    if (add_first)
+        c_list_link_front(&priv->calls_lst_head, &call->calls_lst);
+    else
+        c_list_link_tail(&priv->calls_lst_head, &call->calls_lst);
+
+    /* Migrate the arguments from @payload to @call->payload. Technically,
+     * this is not a plain copy, because
+     * - call->payload is not initialized (thus no need to free the previous data).
+     * - payload does not own the data. It is merely initialized using the
+     *   OVSDB_METHOD_PAYLOAD_*() macros. */
+    switch (command) {
+    case OVSDB_MONITOR:
+        _LOGT_call(call, "new: monitor");
+        break;
+    case OVSDB_ADD_INTERFACE:
+        /* FIXME(applied-connection-immutable): we should not modify the applied
+         *   connection, consequently there is no need to clone the connections. */
+        call->payload.add_interface.bridge =
+            nm_simple_connection_new_clone(payload->add_interface.bridge);
+        call->payload.add_interface.port =
+            nm_simple_connection_new_clone(payload->add_interface.port);
+        call->payload.add_interface.interface =
+            nm_simple_connection_new_clone(payload->add_interface.interface);
+        call->payload.add_interface.bridge_device =
+            g_object_ref(payload->add_interface.bridge_device);
+        call->payload.add_interface.interface_device =
+            g_object_ref(payload->add_interface.interface_device);
+        _LOGT_call(call,
+                   "new: add-interface bridge=%s port=%s interface=%s",
+                   nm_connection_get_interface_name(call->payload.add_interface.bridge),
+                   nm_connection_get_interface_name(call->payload.add_interface.port),
+                   nm_connection_get_interface_name(call->payload.add_interface.interface));
+        break;
+    case OVSDB_DEL_INTERFACE:
+        call->payload.del_interface.ifname = g_strdup(payload->del_interface.ifname);
+        _LOGT_call(call, "new: del-interface interface=%s", call->payload.del_interface.ifname);
+        break;
+    case OVSDB_SET_INTERFACE_MTU:
+        call->payload.set_interface_mtu.ifname = g_strdup(payload->set_interface_mtu.ifname);
+        call->payload.set_interface_mtu.mtu    = payload->set_interface_mtu.mtu;
+        _LOGT_call(call,
+                   "new: set-interface-mtu interface=%s mtu=%u",
+                   call->payload.set_interface_mtu.ifname,
+                   call->payload.set_interface_mtu.mtu);
+        break;
+    case OVSDB_SET_EXTERNAL_IDS:
+        call->payload.set_external_ids.device_type = payload->set_external_ids.device_type;
+        call->payload.set_external_ids.ifname      = g_strdup(payload->set_external_ids.ifname);
+        call->payload.set_external_ids.connection_uuid =
+            g_strdup(payload->set_external_ids.connection_uuid);
+        call->payload.set_external_ids.exid_old =
+            nm_g_hash_table_ref(payload->set_external_ids.exid_old);
+        call->payload.set_external_ids.exid_new =
+            nm_g_hash_table_ref(payload->set_external_ids.exid_new);
+        _LOGT_call(call,
+                   "new: set-external-ids con-uuid=%s, interface=%s",
+                   call->payload.set_external_ids.connection_uuid,
+                   call->payload.set_external_ids.ifname);
+        break;
+    }
+
+    ovsdb_next_command(self);
+}
+
+/*****************************************************************************/
+
+/* Create and process the JSON-RPC messages from ovsdb. */
+
+/**
+ * _expect_ovs_bridges:
+ *
+ * Return a command that will fail the transaction if the actual set of
+ * bridges doesn't match @bridges. This is a way of detecting race conditions
+ * with other ovsdb clients that might be adding or removing bridges
+ * at the same time.
+ */
+static void
+_expect_ovs_bridges(json_t *params, const char *db_uuid, json_t *bridges)
+{
+    json_array_append_new(
+        params,
+        json_pack("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, O]}], s:[[s, s, [s, s]]]}",
+                  "op",
+                  "wait",
+                  "table",
+                  "Open_vSwitch",
+                  "timeout",
+                  0,
+                  "columns",
+                  "bridges",
+                  "until",
+                  "==",
+                  "rows",
+                  "bridges",
+                  "set",
+                  bridges,
+                  "where",
+                  "_uuid",
+                  "==",
+                  "uuid",
+                  db_uuid));
+}
+
+/**
+ * _set_ovs_bridges:
+ *
+ * Return a command that will update the list of bridges in @db_uuid
+ * database to @new_bridges.
+ */
+static void
+_set_ovs_bridges(json_t *params, const char *db_uuid, json_t *new_bridges)
+{
+    json_array_append_new(params,
+                          json_pack("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, [s, s]]]}",
+                                    "op",
+                                    "update",
+                                    "table",
+                                    "Open_vSwitch",
+                                    "row",
+                                    "bridges",
+                                    "set",
+                                    new_bridges,
+                                    "where",
+                                    "_uuid",
+                                    "==",
+                                    "uuid",
+                                    db_uuid));
+}
+
+/**
+ * _expect_bridge_ports:
+ *
+ * Return a command that will fail the transaction if the actual set of
+ * ports in bridge @ifname doesn't match @ports. This is a way of detecting
+ * race conditions with other ovsdb clients that might be adding or removing
+ * bridge ports at the same time.
+ */
+static void
+_expect_bridge_ports(json_t *params, const char *ifname, json_t *ports)
+{
+    json_array_append_new(params,
+                          json_pack("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, O]}], s:[[s, s, s]]}",
+                                    "op",
+                                    "wait",
+                                    "table",
+                                    "Bridge",
+                                    "timeout",
+                                    0,
+                                    "columns",
+                                    "ports",
+                                    "until",
+                                    "==",
+                                    "rows",
+                                    "ports",
+                                    "set",
+                                    ports,
+                                    "where",
+                                    "name",
+                                    "==",
+                                    ifname));
+}
+
+/**
+ * _set_bridge_ports:
+ *
+ * Return a command that will update the list of ports of bridge
+ * @ifname to @new_ports.
+ */
+static void
+_set_bridge_ports(json_t *params, const char *ifname, json_t *new_ports)
+{
+    json_array_append_new(params,
+                          json_pack("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, s]]}",
+                                    "op",
+                                    "update",
+                                    "table",
+                                    "Bridge",
+                                    "row",
+                                    "ports",
+                                    "set",
+                                    new_ports,
+                                    "where",
+                                    "name",
+                                    "==",
+                                    ifname));
+}
+
+static void
+_set_bridge_mac(json_t *params, const char *ifname, const char *mac)
+{
+    json_array_append_new(params,
+                          json_pack("{s:s, s:s, s:{s:[s, [[s, s]]]}, s:[[s, s, s]]}",
+                                    "op",
+                                    "update",
+                                    "table",
+                                    "Bridge",
+                                    "row",
+                                    "other_config",
+                                    "map",
+                                    "hwaddr",
+                                    mac,
+                                    "where",
+                                    "name",
+                                    "==",
+                                    ifname));
+}
+
+/**
+ * _expect_port_interfaces:
+ *
+ * Return a command that will fail the transaction if the actual set of
+ * interfaces in port @ifname doesn't match @interfaces. This is a way of
+ * detecting race conditions with other ovsdb clients that might be adding
+ * or removing port interfaces at the same time.
+ */
+static void
+_expect_port_interfaces(json_t *params, const char *ifname, json_t *interfaces)
+{
+    json_array_append_new(params,
+                          json_pack("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, O]}], s:[[s, s, s]]}",
+                                    "op",
+                                    "wait",
+                                    "table",
+                                    "Port",
+                                    "timeout",
+                                    0,
+                                    "columns",
+                                    "interfaces",
+                                    "until",
+                                    "==",
+                                    "rows",
+                                    "interfaces",
+                                    "set",
+                                    interfaces,
+                                    "where",
+                                    "name",
+                                    "==",
+                                    ifname));
+}
+
+/**
+ * _set_port_interfaces:
+ *
+ * Return a command that will update the list of interfaces of port @ifname
+ * to @new_interfaces.
+ */
+static void
+_set_port_interfaces(json_t *params, const char *ifname, json_t *new_interfaces)
+{
+    json_array_append_new(params,
+                          json_pack("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, s]]}",
+                                    "op",
+                                    "update",
+                                    "table",
+                                    "Port",
+                                    "row",
+                                    "interfaces",
+                                    "set",
+                                    new_interfaces,
+                                    "where",
+                                    "name",
+                                    "==",
+                                    ifname));
+}
+
+static json_t *
+_j_create_external_ids_array_new(NMConnection *connection)
+{
+    json_t *                 array;
+    const char *const *      external_ids   = NULL;
+    guint                    n_external_ids = 0;
+    guint                    i;
+    const char *             uuid;
+    NMSettingOvsExternalIDs *s_exid;
+
+    nm_assert(NM_IS_CONNECTION(connection));
+
+    array = json_array();
+
+    uuid = nm_connection_get_uuid(connection);
+    nm_assert(uuid);
+    json_array_append_new(array, json_pack("[s, s]", NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID, uuid));
+
+    s_exid = _nm_connection_get_setting(connection, NM_TYPE_SETTING_OVS_EXTERNAL_IDS);
+    if (s_exid)
+        external_ids = nm_setting_ovs_external_ids_get_data_keys(s_exid, &n_external_ids);
+    for (i = 0; i < n_external_ids; i++) {
+        const char *k = external_ids[i];
+
+        json_array_append_new(
+            array,
+            json_pack("[s, s]", k, nm_setting_ovs_external_ids_get_data(s_exid, k)));
+    }
+
+    return json_pack("[s, o]", "map", array);
+}
+
+static json_t *
+_j_create_external_ids_array_update(const char *connection_uuid,
+                                    GHashTable *exid_old,
+                                    GHashTable *exid_new)
+{
+    GHashTableIter iter;
+    json_t *       mutations;
+    json_t *       array;
+    const char *   key;
+    const char *   val;
+
+    nm_assert(connection_uuid);
+
+    mutations = json_array();
+
+    if (exid_old) {
+        array = NULL;
+        g_hash_table_iter_init(&iter, exid_old);
+        while (g_hash_table_iter_next(&iter, (gpointer *) &key, NULL)) {
+            if (nm_g_hash_table_contains(exid_new, key))
+                continue;
+            if (NM_STR_HAS_PREFIX(key, NM_OVS_EXTERNAL_ID_NM_PREFIX))
+                continue;
+
+            if (!array)
+                array = json_array();
+
+            json_array_append_new(array, json_string(key));
+        }
+        if (array) {
+            json_array_append_new(
+                mutations,
+                json_pack("[s, s, [s, o]]", "external_ids", "delete", "set", array));
+        }
+    }
+
+    array = json_array();
+
+    json_array_append_new(
+        array,
+        json_pack("[s, s]", NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID, connection_uuid));
+
+    if (exid_new) {
+        g_hash_table_iter_init(&iter, exid_new);
+        while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) {
+            if (NM_STR_HAS_PREFIX(key, NM_OVS_EXTERNAL_ID_NM_PREFIX))
+                continue;
+            json_array_append_new(array, json_pack("[s, s]", key, val));
+        }
+    }
+
+    json_array_append_new(mutations,
+                          json_pack("[s, s, [s, o]]", "external_ids", "insert", "map", array));
+    return mutations;
+}
+
+/**
+ * _insert_interface:
+ *
+ * Returns an commands that adds new interface from a given connection.
+ */
+static void
+_insert_interface(json_t *      params,
+                  NMConnection *interface,
+                  NMDevice *    interface_device,
+                  const char *  cloned_mac)
+{
+    const char *           type = NULL;
+    NMSettingOvsInterface *s_ovs_iface;
+    NMSettingOvsDpdk *     s_ovs_dpdk;
+    NMSettingOvsPatch *    s_ovs_patch;
+    json_t *               options = json_array();
+    json_t *               row;
+    guint32                mtu = 0;
+
+    s_ovs_iface = nm_connection_get_setting_ovs_interface(interface);
+    if (s_ovs_iface)
+        type = nm_setting_ovs_interface_get_interface_type(s_ovs_iface);
+
+    if (nm_streq0(type, "internal")) {
+        NMSettingWired *s_wired;
+
+        s_wired = _nm_connection_get_setting(interface, NM_TYPE_SETTING_WIRED);
+        if (s_wired)
+            mtu = nm_setting_wired_get_mtu(s_wired);
+    }
+
+    json_array_append_new(options, json_string("map"));
+
+    s_ovs_dpdk =
+        (NMSettingOvsDpdk *) nm_connection_get_setting(interface, NM_TYPE_SETTING_OVS_DPDK);
+    if (!s_ovs_dpdk)
+        s_ovs_patch = nm_connection_get_setting_ovs_patch(interface);
+
+    if (s_ovs_dpdk) {
+        json_array_append_new(
+            options,
+            json_pack("[[s, s]]", "dpdk-devargs", nm_setting_ovs_dpdk_get_devargs(s_ovs_dpdk)));
+    } else if (s_ovs_patch) {
+        json_array_append_new(
+            options,
+            json_pack("[[s, s]]", "peer", nm_setting_ovs_patch_get_peer(s_ovs_patch)));
+    } else {
+        json_array_append_new(options, json_array());
+    }
+
+    row = json_pack("{s:s, s:s, s:o, s:o}",
+                    "name",
+                    nm_connection_get_interface_name(interface),
+                    "type",
+                    type ?: "",
+                    "options",
+                    options,
+                    "external_ids",
+                    _j_create_external_ids_array_new(interface));
+
+    if (cloned_mac)
+        json_object_set_new(row, "mac", json_string(cloned_mac));
+
+    if (mtu != 0)
+        json_object_set_new(row, "mtu_request", json_integer(mtu));
+
+    json_array_append_new(params,
+                          json_pack("{s:s, s:s, s:o, s:s}",
+                                    "op",
+                                    "insert",
+                                    "table",
+                                    "Interface",
+                                    "row",
+                                    row,
+                                    "uuid-name",
+                                    "rowInterface"));
+}
+
+/**
+ * _insert_port:
+ *
+ * Returns an commands that adds new port from a given connection.
+ */
+static void
+_insert_port(json_t *params, NMConnection *port, json_t *new_interfaces)
+{
+    NMSettingOvsPort *s_ovs_port;
+    const char *      vlan_mode      = NULL;
+    guint             tag            = 0;
+    const char *      lacp           = NULL;
+    const char *      bond_mode      = NULL;
+    guint             bond_updelay   = 0;
+    guint             bond_downdelay = 0;
+    json_t *          row;
+
+    s_ovs_port = nm_connection_get_setting_ovs_port(port);
+
+    row = json_object();
+
+    if (s_ovs_port) {
+        vlan_mode      = nm_setting_ovs_port_get_vlan_mode(s_ovs_port);
+        tag            = nm_setting_ovs_port_get_tag(s_ovs_port);
+        lacp           = nm_setting_ovs_port_get_lacp(s_ovs_port);
+        bond_mode      = nm_setting_ovs_port_get_bond_mode(s_ovs_port);
+        bond_updelay   = nm_setting_ovs_port_get_bond_updelay(s_ovs_port);
+        bond_downdelay = nm_setting_ovs_port_get_bond_downdelay(s_ovs_port);
+    }
+
+    if (vlan_mode)
+        json_object_set_new(row, "vlan_mode", json_string(vlan_mode));
+    if (tag)
+        json_object_set_new(row, "tag", json_integer(tag));
+    if (lacp)
+        json_object_set_new(row, "lacp", json_string(lacp));
+    if (bond_mode)
+        json_object_set_new(row, "bond_mode", json_string(bond_mode));
+    if (bond_updelay)
+        json_object_set_new(row, "bond_updelay", json_integer(bond_updelay));
+    if (bond_downdelay)
+        json_object_set_new(row, "bond_downdelay", json_integer(bond_downdelay));
+
+    json_object_set_new(row, "name", json_string(nm_connection_get_interface_name(port)));
+    json_object_set_new(row, "interfaces", json_pack("[s, O]", "set", new_interfaces));
+    json_object_set_new(row, "external_ids", _j_create_external_ids_array_new(port));
+
+    /* Create a new one. */
+    json_array_append_new(params,
+                          json_pack("{s:s, s:s, s:o, s:s}",
+                                    "op",
+                                    "insert",
+                                    "table",
+                                    "Port",
+                                    "row",
+                                    row,
+                                    "uuid-name",
+                                    "rowPort"));
+}
+
+/**
+ * _insert_bridge:
+ *
+ * Returns an commands that adds new bridge from a given connection.
+ */
+static void
+_insert_bridge(json_t *      params,
+               NMConnection *bridge,
+               NMDevice *    bridge_device,
+               json_t *      new_ports,
+               const char *  cloned_mac)
+{
+    NMSettingOvsBridge *s_ovs_bridge;
+    const char *        fail_mode             = NULL;
+    gboolean            mcast_snooping_enable = FALSE;
+    gboolean            rstp_enable           = FALSE;
+    gboolean            stp_enable            = FALSE;
+    const char *        datapath_type         = NULL;
+    json_t *            row;
+
+    s_ovs_bridge = nm_connection_get_setting_ovs_bridge(bridge);
+
+    row = json_object();
+
+    if (s_ovs_bridge) {
+        fail_mode             = nm_setting_ovs_bridge_get_fail_mode(s_ovs_bridge);
+        mcast_snooping_enable = nm_setting_ovs_bridge_get_mcast_snooping_enable(s_ovs_bridge);
+        rstp_enable           = nm_setting_ovs_bridge_get_rstp_enable(s_ovs_bridge);
+        stp_enable            = nm_setting_ovs_bridge_get_stp_enable(s_ovs_bridge);
+        datapath_type         = nm_setting_ovs_bridge_get_datapath_type(s_ovs_bridge);
+    }
+
+    if (fail_mode)
+        json_object_set_new(row, "fail_mode", json_string(fail_mode));
+    if (mcast_snooping_enable)
+        json_object_set_new(row, "mcast_snooping_enable", json_boolean(mcast_snooping_enable));
+    if (rstp_enable)
+        json_object_set_new(row, "rstp_enable", json_boolean(rstp_enable));
+    if (stp_enable)
+        json_object_set_new(row, "stp_enable", json_boolean(stp_enable));
+    if (datapath_type)
+        json_object_set_new(row, "datapath_type", json_string(datapath_type));
+
+    json_object_set_new(row, "name", json_string(nm_connection_get_interface_name(bridge)));
+    json_object_set_new(row, "ports", json_pack("[s, O]", "set", new_ports));
+    json_object_set_new(row, "external_ids", _j_create_external_ids_array_new(bridge));
+
+    if (cloned_mac) {
+        json_object_set_new(row,
+                            "other_config",
+                            json_pack("[s, [[s, s]]]", "map", "hwaddr", cloned_mac));
+    }
+
+    /* Create a new one. */
+    json_array_append_new(params,
+                          json_pack("{s:s, s:s, s:o, s:s}",
+                                    "op",
+                                    "insert",
+                                    "table",
+                                    "Bridge",
+                                    "row",
+                                    row,
+                                    "uuid-name",
+                                    "rowBridge"));
+}
+
+/**
+ * _inc_next_cfg:
+ *
+ * Returns an mutate command that bumps next_cfg upon successful completion
+ * of the transaction it is in.
+ */
+static json_t *
+_inc_next_cfg(const char *db_uuid)
+{
+    return json_pack("{s:s, s:s, s:[[s, s, i]], s:[[s, s, [s, s]]]}",
+                     "op",
+                     "mutate",
+                     "table",
+                     "Open_vSwitch",
+                     "mutations",
+                     "next_cfg",
+                     "+=",
+                     1,
+                     "where",
+                     "_uuid",
+                     "==",
+                     "uuid",
+                     db_uuid);
+}
+
+/**
+ * _add_interface:
+ *
+ * Adds an interface as specified by @interface connection, optionally creating
+ * a parent @port and @bridge if needed.
+ */
+static void
+_add_interface(NMOvsdb *     self,
+               json_t *      params,
+               NMConnection *bridge,
+               NMConnection *port,
+               NMConnection *interface,
+               NMDevice *    bridge_device,
+               NMDevice *    interface_device)
+{
+    NMOvsdbPrivate *      priv = NM_OVSDB_GET_PRIVATE(self);
+    GHashTableIter        iter;
+    const char *          port_uuid;
+    const char *          interface_uuid;
+    const char *          bridge_name;
+    const char *          port_name;
+    const char *          interface_name;
+    OpenvswitchBridge *   ovs_bridge           = NULL;
+    OpenvswitchPort *     ovs_port             = NULL;
+    OpenvswitchInterface *ovs_interface        = NULL;
+    nm_auto_decref_json json_t *bridges        = NULL;
+    nm_auto_decref_json json_t *new_bridges    = NULL;
+    nm_auto_decref_json json_t *ports          = NULL;
+    nm_auto_decref_json json_t *new_ports      = NULL;
+    nm_auto_decref_json json_t *interfaces     = NULL;
+    nm_auto_decref_json json_t *new_interfaces = NULL;
+    gboolean                    has_interface  = FALSE;
+    gboolean                    interface_is_local;
+    gs_free char *              bridge_cloned_mac    = NULL;
+    gs_free char *              interface_cloned_mac = NULL;
+    GError *                    error                = NULL;
+    int                         pi;
+    int                         ii;
+
+    bridges        = json_array();
+    ports          = json_array();
+    interfaces     = json_array();
+    new_bridges    = json_array();
+    new_ports      = json_array();
+    new_interfaces = json_array();
+
+    bridge_name        = nm_connection_get_interface_name(bridge);
+    port_name          = nm_connection_get_interface_name(port);
+    interface_name     = nm_connection_get_interface_name(interface);
+    interface_is_local = nm_streq0(bridge_name, interface_name);
+
+    /* Determine cloned MAC addresses */
+    if (!nm_device_hw_addr_get_cloned(bridge_device,
+                                      bridge,
+                                      FALSE,
+                                      &bridge_cloned_mac,
+                                      NULL,
+                                      &error)) {
+        _LOGW("Cannot determine cloned MAC for OVS %s '%s': %s",
+              "bridge",
+              bridge_name,
+              error->message);
+        g_clear_error(&error);
+    }
+
+    if (!nm_device_hw_addr_get_cloned(interface_device,
+                                      interface,
+                                      FALSE,
+                                      &interface_cloned_mac,
+                                      NULL,
+                                      &error)) {
+        _LOGW("Cannot determine cloned MAC for OVS %s '%s': %s",
+              "interface",
+              interface_name,
+              error->message);
+        g_clear_error(&error);
+    }
+
+    /* For local interfaces, ovs complains if it finds a
+     * MAC address in the Interface table because it only takes
+     * the MAC from the Bridge table.
+     * Set any cloned MAC present in a local interface connection
+     * into the Bridge table, unless conflicting with the bridge MAC. */
+    if (interface_is_local && interface_cloned_mac) {
+        if (bridge_cloned_mac && !nm_streq(interface_cloned_mac, bridge_cloned_mac)) {
+            _LOGW("Cloned MAC '%s' of local ovs-interface '%s' conflicts with MAC '%s' of bridge "
+                  "'%s'",
+                  interface_cloned_mac,
+                  interface_name,
+                  bridge_cloned_mac,
+                  bridge_name);
+            nm_clear_g_free(&interface_cloned_mac);
+        } else {
+            nm_clear_g_free(&bridge_cloned_mac);
+            bridge_cloned_mac = g_steal_pointer(&interface_cloned_mac);
+            _LOGT("'%s' is a local ovs-interface, the MAC will be set on ovs-bridge '%s'",
+                  interface_name,
+                  bridge_name);
+        }
+    }
+
+    g_hash_table_iter_init(&iter, priv->bridges);
+    while (g_hash_table_iter_next(&iter, (gpointer) &ovs_bridge, NULL)) {
+        json_array_append_new(bridges, json_pack("[s, s]", "uuid", ovs_bridge->bridge_uuid));
+
+        if (!nm_streq0(ovs_bridge->name, bridge_name)
+            || !nm_streq0(ovs_bridge->connection_uuid, nm_connection_get_uuid(bridge)))
+            continue;
+
+        for (pi = 0; pi < ovs_bridge->ports->len; pi++) {
+            port_uuid = g_ptr_array_index(ovs_bridge->ports, pi);
+            ovs_port  = g_hash_table_lookup(priv->ports, &port_uuid);
+
+            json_array_append_new(ports, json_pack("[s, s]", "uuid", port_uuid));
+
+            if (!ovs_port) {
+                /* This would be a violation of ovsdb's reference integrity (a bug). */
+                _LOGW("Unknown port '%s' in bridge '%s'", port_uuid, ovs_bridge->bridge_uuid);
+                continue;
+            }
+
+            if (!nm_streq(ovs_port->name, port_name)
+                || !nm_streq0(ovs_port->connection_uuid, nm_connection_get_uuid(port)))
+                continue;
+
+            for (ii = 0; ii < ovs_port->interfaces->len; ii++) {
+                interface_uuid = g_ptr_array_index(ovs_port->interfaces, ii);
+                ovs_interface  = g_hash_table_lookup(priv->interfaces, &interface_uuid);
+
+                json_array_append_new(interfaces, json_pack("[s, s]", "uuid", interface_uuid));
+
+                if (!ovs_interface) {
+                    /* This would be a violation of ovsdb's reference integrity (a bug). */
+                    _LOGW("Unknown interface '%s' in port '%s'", interface_uuid, port_uuid);
+                    continue;
+                }
+                if (nm_streq(ovs_interface->name, interface_name)
+                    && nm_streq0(ovs_interface->connection_uuid, nm_connection_get_uuid(interface)))
+                    has_interface = TRUE;
+            }
+
+            break;
+        }
+
+        break;
+    }
+
+    json_array_extend(new_bridges, bridges);
+    json_array_extend(new_ports, ports);
+    json_array_extend(new_interfaces, interfaces);
+
+    if (json_array_size(interfaces) == 0) {
+        /* Need to create a port. */
+        if (json_array_size(ports) == 0) {
+            /* Need to create a bridge. */
+            _expect_ovs_bridges(params, priv->db_uuid, bridges);
+            json_array_append_new(new_bridges, json_pack("[s, s]", "named-uuid", "rowBridge"));
+            _set_ovs_bridges(params, priv->db_uuid, new_bridges);
+            _insert_bridge(params, bridge, bridge_device, new_ports, bridge_cloned_mac);
+        } else {
+            /* Bridge already exists. */
+            g_return_if_fail(ovs_bridge);
+            _expect_bridge_ports(params, ovs_bridge->name, ports);
+            _set_bridge_ports(params, bridge_name, new_ports);
+            if (bridge_cloned_mac && interface_is_local)
+                _set_bridge_mac(params, bridge_name, bridge_cloned_mac);
+        }
+
+        json_array_append_new(new_ports, json_pack("[s, s]", "named-uuid", "rowPort"));
+        _insert_port(params, port, new_interfaces);
+    } else {
+        /* Port already exists */
+        g_return_if_fail(ovs_port);
+        _expect_port_interfaces(params, ovs_port->name, interfaces);
+        _set_port_interfaces(params, port_name, new_interfaces);
+    }
+
+    if (!has_interface) {
+        _insert_interface(params, interface, interface_device, interface_cloned_mac);
+        json_array_append_new(new_interfaces, json_pack("[s, s]", "named-uuid", "rowInterface"));
+    }
+}
+
+/**
+ * _delete_interface:
+ *
+ * Removes an interface of @ifname name, collecting empty ports and bridge
+ * if last item is removed from them.
+ */
+static void
+_delete_interface(NMOvsdb *self, json_t *params, const char *ifname)
+{
+    NMOvsdbPrivate *      priv = NM_OVSDB_GET_PRIVATE(self);
+    GHashTableIter        iter;
+    char *                port_uuid;
+    char *                interface_uuid;
+    OpenvswitchBridge *   ovs_bridge;
+    OpenvswitchPort *     ovs_port;
+    OpenvswitchInterface *ovs_interface;
+    nm_auto_decref_json json_t *bridges     = NULL;
+    nm_auto_decref_json json_t *new_bridges = NULL;
+    gboolean                    bridges_changed;
+    gboolean                    ports_changed;
+    gboolean                    interfaces_changed;
+    int                         pi;
+    int                         ii;
+
+    bridges         = json_array();
+    new_bridges     = json_array();
+    bridges_changed = FALSE;
+
+    g_hash_table_iter_init(&iter, priv->bridges);
+    while (g_hash_table_iter_next(&iter, (gpointer) &ovs_bridge, NULL)) {
+        nm_auto_decref_json json_t *ports     = NULL;
+        nm_auto_decref_json json_t *new_ports = NULL;
+
+        ports         = json_array();
+        new_ports     = json_array();
+        ports_changed = FALSE;
+
+        json_array_append_new(bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid));
+
+        for (pi = 0; pi < ovs_bridge->ports->len; pi++) {
+            nm_auto_decref_json json_t *interfaces     = NULL;
+            nm_auto_decref_json json_t *new_interfaces = NULL;
+
+            interfaces     = json_array();
+            new_interfaces = json_array();
+            port_uuid      = g_ptr_array_index(ovs_bridge->ports, pi);
+            ovs_port       = g_hash_table_lookup(priv->ports, &port_uuid);
+
+            json_array_append_new(ports, json_pack("[s,s]", "uuid", port_uuid));
+
+            interfaces_changed = FALSE;
+
+            if (!ovs_port) {
+                /* This would be a violation of ovsdb's reference integrity (a bug). */
+                _LOGW("Unknown port '%s' in bridge '%s'", port_uuid, ovs_bridge->bridge_uuid);
+                continue;
+            }
+
+            for (ii = 0; ii < ovs_port->interfaces->len; ii++) {
+                interface_uuid = g_ptr_array_index(ovs_port->interfaces, ii);
+                ovs_interface  = g_hash_table_lookup(priv->interfaces, &interface_uuid);
+
+                json_array_append_new(interfaces, json_pack("[s,s]", "uuid", interface_uuid));
+
+                if (ovs_interface) {
+                    if (nm_streq(ovs_interface->name, ifname)) {
+                        /* skip the interface */
+                        interfaces_changed = TRUE;
+                        continue;
+                    }
+                } else {
+                    /* This would be a violation of ovsdb's reference integrity (a bug). */
+                    _LOGW("Unknown interface '%s' in port '%s'", interface_uuid, port_uuid);
+                }
+
+                json_array_append_new(new_interfaces, json_pack("[s,s]", "uuid", interface_uuid));
+            }
+
+            if (json_array_size(new_interfaces) == 0) {
+                ports_changed = TRUE;
+            } else {
+                if (interfaces_changed) {
+                    _expect_port_interfaces(params, ovs_port->name, interfaces);
+                    _set_port_interfaces(params, ovs_port->name, new_interfaces);
+                }
+                json_array_append_new(new_ports, json_pack("[s,s]", "uuid", port_uuid));
+            }
+        }
+
+        if (json_array_size(new_ports) == 0) {
+            bridges_changed = TRUE;
+        } else {
+            if (ports_changed) {
+                _expect_bridge_ports(params, ovs_bridge->name, ports);
+                _set_bridge_ports(params, ovs_bridge->name, new_ports);
+            }
+            json_array_append_new(new_bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid));
+        }
+    }
+
+    if (bridges_changed) {
+        _expect_ovs_bridges(params, priv->db_uuid, bridges);
+        _set_ovs_bridges(params, priv->db_uuid, new_bridges);
+    }
+}
+
+/**
+ * ovsdb_next_command:
+ *
+ * Translates a higher level operation (add/remove bridge/port) to a RFC 7047
+ * command serialized into JSON ands sends it over to the database.
+
+ * Only called when no command is waiting for a response, since the serialized
+ * command might depend on result of a previous one (add and remove need to
+ * include an up to date bridge list in their transactions to rule out races).
+ */
+static void
+ovsdb_next_command(NMOvsdb *self)
+{
+    NMOvsdbPrivate *    priv = NM_OVSDB_GET_PRIVATE(self);
+    OvsdbMethodCall *   call;
+    char *              cmd;
+    nm_auto_decref_json json_t *msg = NULL;
+
+    if (!priv->conn)
+        return;
+
+    if (c_list_is_empty(&priv->calls_lst_head))
+        return;
+
+    call = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst);
+    if (call->call_id != CALL_ID_UNSPEC)
+        return;
+
+    call->call_id = ++priv->call_id_counter;
+
+    switch (call->command) {
+    case OVSDB_MONITOR:
+        msg = json_pack("{s:I, s:s, s:[s, n, {"
+                        "  s:[{s:[s, s, s]}],"
+                        "  s:[{s:[s, s, s]}],"
+                        "  s:[{s:[s, s, s, s]}],"
+                        "  s:[{s:[]}]"
+                        "}]}",
+                        "id",
+                        (json_int_t) call->call_id,
+                        "method",
+                        "monitor",
+                        "params",
+                        "Open_vSwitch",
+                        "Bridge",
+                        "columns",
+                        "name",
+                        "ports",
+                        "external_ids",
+                        "Port",
+                        "columns",
+                        "name",
+                        "interfaces",
+                        "external_ids",
+                        "Interface",
+                        "columns",
+                        "name",
+                        "type",
+                        "external_ids",
+                        "error",
+                        "Open_vSwitch",
+                        "columns");
+        break;
+    default:
+    {
+        json_t *params = NULL;
+
+        params = json_array();
+        json_array_append_new(params, json_string("Open_vSwitch"));
+        json_array_append_new(params, _inc_next_cfg(priv->db_uuid));
+
+        switch (call->command) {
+        case OVSDB_ADD_INTERFACE:
+            _add_interface(self,
+                           params,
+                           call->payload.add_interface.bridge,
+                           call->payload.add_interface.port,
+                           call->payload.add_interface.interface,
+                           call->payload.add_interface.bridge_device,
+                           call->payload.add_interface.interface_device);
+            break;
+        case OVSDB_DEL_INTERFACE:
+            _delete_interface(self, params, call->payload.del_interface.ifname);
+            break;
+        case OVSDB_SET_INTERFACE_MTU:
+            json_array_append_new(params,
+                                  json_pack("{s:s, s:s, s:{s: I}, s:[[s, s, s]]}",
+                                            "op",
+                                            "update",
+                                            "table",
+                                            "Interface",
+                                            "row",
+                                            "mtu_request",
+                                            (json_int_t) call->payload.set_interface_mtu.mtu,
+                                            "where",
+                                            "name",
+                                            "==",
+                                            call->payload.set_interface_mtu.ifname));
+            break;
+        case OVSDB_SET_EXTERNAL_IDS:
+            json_array_append_new(
+                params,
+                json_pack("{s:s, s:s, s:o, s:[[s, s, s]]}",
+                          "op",
+                          "mutate",
+                          "table",
+                          _device_type_to_table(call->payload.set_external_ids.device_type),
+                          "mutations",
+                          _j_create_external_ids_array_update(
+                              call->payload.set_external_ids.connection_uuid,
+                              call->payload.set_external_ids.exid_old,
+                              call->payload.set_external_ids.exid_new),
+                          "where",
+                          "name",
+                          "==",
+                          call->payload.set_external_ids.ifname));
+            break;
+
+        default:
+            nm_assert_not_reached();
+            break;
+        }
+
+        msg = json_pack("{s:I, s:s, s:o}",
+                        "id",
+                        (json_int_t) call->call_id,
+                        "method",
+                        "transact",
+                        "params",
+                        params);
+        break;
+    }
+    }
+
+    g_return_if_fail(msg);
+
+    cmd = json_dumps(msg, 0);
+    _LOGT_call(call, "send: call-id=%" G_GUINT64_FORMAT ", %s", call->call_id, cmd);
+    g_string_append(priv->output, cmd);
+    free(cmd);
+
+    ovsdb_write(self);
+}
+
+/**
+ * _uuids_to_array:
+ *
+ * This tidies up the somewhat non-straightforward way ovsdb represents an array
+ * of UUID elements. The single element is a tuple (called <atom> in RFC7047),
+ *
+ *   [ "uuid", "aa095ffb-e1f1-0fc4-8038-82c1ea7e4797" ]
+ *
+ * while the list of multiple UUIDs are turned into a set of such tuples ("atoms"):
+ *
+ *   [ "set", [ [ "uuid", "aa095ffb-e1f1-0fc4-8038-82c1ea7e4797" ],
+ *              [ "uuid", "185c93f6-0b39-424e-8587-77d074aa7ce0" ], ... ] ]
+ */
+static void
+_uuids_to_array_inplace(GPtrArray *array, const json_t *items)
+{
+    const char *key;
+    json_t *    value;
+    size_t      index = 0;
+    json_t *    set_value;
+    size_t      set_index;
+
+    while (index < json_array_size(items)) {
+        key = json_string_value(json_array_get(items, index));
+        index++;
+        value = json_array_get(items, index);
+        index++;
+
+        if (!value || !key)
+            return;
+
+        if (nm_streq(key, "uuid")) {
+            if (json_is_string(value))
+                g_ptr_array_add(array, g_strdup(json_string_value(value)));
+            continue;
+        }
+        if (nm_streq(key, "set")) {
+            if (json_is_array(value)) {
+                json_array_foreach (value, set_index, set_value)
+                    _uuids_to_array_inplace(array, set_value);
+            }
+            continue;
+        }
+    }
+}
+
+static GPtrArray *
+_uuids_to_array(const json_t *items)
+{
+    GPtrArray *array;
+
+    array = g_ptr_array_new_with_free_func(g_free);
+    _uuids_to_array_inplace(array, items);
+    return array;
+}
+
+static void
+_external_ids_extract(json_t *external_ids, GArray **out_array, const char **out_connection_uuid)
+{
+    json_t *array;
+    json_t *value;
+    gsize   index;
+
+    nm_assert(out_array && !*out_array);
+    nm_assert(!out_connection_uuid || !*out_connection_uuid);
+
+    if (!nm_streq0("map", json_string_value(json_array_get(external_ids, 0))))
+        return;
+
+    array = json_array_get(external_ids, 1);
+
+    json_array_foreach (array, index, value) {
+        const char *       key = json_string_value(json_array_get(value, 0));
+        const char *       val = json_string_value(json_array_get(value, 1));
+        NMUtilsNamedValue *v;
+
+        if (!key || !val)
+            continue;
+
+        if (!*out_array) {
+            *out_array = g_array_new(FALSE, FALSE, sizeof(NMUtilsNamedValue));
+            g_array_set_clear_func(*out_array,
+                                   (GDestroyNotify) nm_utils_named_value_clear_with_g_free);
+        }
+
+        v  = nm_g_array_append_new(*out_array, NMUtilsNamedValue);
+        *v = (NMUtilsNamedValue){
+            .name      = g_strdup(key),
+            .value_str = g_strdup(val),
+        };
+
+        if (out_connection_uuid && nm_streq(v->name, NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID)) {
+            *out_connection_uuid = v->value_str;
+            out_connection_uuid  = NULL;
+        }
+    }
+}
+
+static gboolean
+_external_ids_equal(const GArray *arr1, const GArray *arr2)
+{
+    guint n;
+    guint i;
+
+    n = nm_g_array_len(arr1);
+
+    if (n != nm_g_array_len(arr2))
+        return FALSE;
+    for (i = 0; i < n; i++) {
+        const NMUtilsNamedValue *n1 = &g_array_index(arr1, NMUtilsNamedValue, i);
+        const NMUtilsNamedValue *n2 = &g_array_index(arr2, NMUtilsNamedValue, i);
+
+        if (!nm_streq0(n1->name, n2->name))
+            return FALSE;
+        if (!nm_streq0(n1->value_str, n2->value_str))
+            return FALSE;
+    }
+    return TRUE;
+}
+
+static char *
+_external_ids_to_string(const GArray *arr)
+{
+    NMStrBuf strbuf;
+    guint    i;
+
+    if (!arr)
+        return g_strdup("empty");
+
+    nm_str_buf_init(&strbuf, NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE);
+    nm_str_buf_append(&strbuf, "[");
+    for (i = 0; i < arr->len; i++) {
+        const NMUtilsNamedValue *n = &g_array_index(arr, NMUtilsNamedValue, i);
+
+        if (i > 0)
+            nm_str_buf_append_c(&strbuf, ',');
+        nm_str_buf_append_printf(&strbuf, " \"%s\" = \"%s\"]", n->name, n->value_str);
+    }
+    nm_str_buf_append(&strbuf, " ]");
+
+    return nm_str_buf_finalize(&strbuf, NULL);
+}
+
+/*****************************************************************************/
+
+/**
+ * ovsdb_got_update:
+ *
+ * Called when we've got an "update" method call (we asked for it with the monitor
+ * command). We use it to maintain a consistent view of bridge list regardless of
+ * whether the changes are done by us or externally.
+ */
+static void
+ovsdb_got_update(NMOvsdb *self, json_t *msg)
+{
+    NMOvsdbPrivate *priv      = NM_OVSDB_GET_PRIVATE(self);
+    json_t *        ovs       = NULL;
+    json_t *        bridge    = NULL;
+    json_t *        port      = NULL;
+    json_t *        interface = NULL;
+    json_t *        items;
+    json_t *        external_ids;
+    json_error_t    json_error = {
+        0,
+    };
+    void *      iter;
+    const char *name;
+    const char *key;
+    const char *type;
+    json_t *    value;
+
+    if (json_unpack_ex(msg,
+                       &json_error,
+                       0,
+                       "{s?:o, s?:o, s?:o, s?:o}",
+                       "Open_vSwitch",
+                       &ovs,
+                       "Bridge",
+                       &bridge,
+                       "Port",
+                       &port,
+                       "Interface",
+                       &interface)
+        == -1) {
+        /* This doesn't really have to be an error; the key might
+         * be missing if there really are no bridges present. */
+        _LOGD("Bad update: %s", json_error.text);
+    }
+
+    if (ovs) {
+        const char *s;
+
+        iter = json_object_iter(ovs);
+        s    = json_object_iter_key(iter);
+        if (s)
+            nm_utils_strdup_reset(&priv->db_uuid, s);
+    }
+
+    json_object_foreach (interface, key, value) {
+        OpenvswitchInterface *ovs_interface;
+        gs_unref_array GArray *external_ids_arr = NULL;
+        const char *           connection_uuid  = NULL;
+        json_t *               error            = NULL;
+        int                    r;
+
+        r = json_unpack(value,
+                        "{s:{s:s, s:s, s?:o, s:o}}",
+                        "new",
+                        "name",
+                        &name,
+                        "type",
+                        &type,
+                        "error",
+                        &error,
+                        "external_ids",
+                        &external_ids);
+        if (r != 0) {
+            gpointer unused;
+
+            r = json_unpack(value, "{s:{}}", "old");
+            if (r != 0)
+                continue;
+
+            if (!g_hash_table_steal_extended(priv->interfaces,
+                                             &key,
+                                             (gpointer *) &ovs_interface,
+                                             &unused))
+                continue;
+
+            _LOGT("obj[iface:%s]: removed an '%s' interface: %s%s%s",
+                  key,
+                  ovs_interface->type,
+                  ovs_interface->name,
+                  NM_PRINT_FMT_QUOTED2(ovs_interface->connection_uuid,
+                                       ", ",
+                                       ovs_interface->connection_uuid,
+                                       ""));
+            _signal_emit_device_removed(self,
+                                        ovs_interface->name,
+                                        NM_DEVICE_TYPE_OVS_INTERFACE,
+                                        ovs_interface->type);
+            _free_interface(ovs_interface);
+            continue;
+        }
+
+        ovs_interface = g_hash_table_lookup(priv->interfaces, &key);
+
+        if (ovs_interface
+            && (!nm_streq0(ovs_interface->name, name) || !nm_streq0(ovs_interface->type, type))) {
+            if (!g_hash_table_steal(priv->interfaces, ovs_interface))
+                nm_assert_not_reached();
+            _signal_emit_device_removed(self,
+                                        ovs_interface->name,
+                                        NM_DEVICE_TYPE_OVS_INTERFACE,
+                                        ovs_interface->type);
+            nm_clear_pointer(&ovs_interface, _free_interface);
+        }
+
+        _external_ids_extract(external_ids, &external_ids_arr, &connection_uuid);
+
+        if (ovs_interface) {
+            gboolean changed = FALSE;
+
+            nm_assert(nm_streq0(ovs_interface->name, name));
+
+            changed |= nm_utils_strdup_reset(&ovs_interface->type, type);
+            changed |= nm_utils_strdup_reset(&ovs_interface->connection_uuid, connection_uuid);
+            if (!_external_ids_equal(ovs_interface->external_ids, external_ids_arr)) {
+                NM_SWAP(&ovs_interface->external_ids, &external_ids_arr);
+                changed = TRUE;
+            }
+            if (changed) {
+                gs_free char *strtmp = NULL;
+
+                _LOGT("obj[iface:%s]: changed an '%s' interface: %s%s%s, external-ids=%s",
+                      key,
+                      type,
+                      ovs_interface->name,
+                      NM_PRINT_FMT_QUOTED2(ovs_interface->connection_uuid,
+                                           ", ",
+                                           ovs_interface->connection_uuid,
+                                           ""),
+                      (strtmp = _external_ids_to_string(ovs_interface->external_ids)));
+            }
+        } else {
+            gs_free char *strtmp = NULL;
+
+            ovs_interface  = g_slice_new(OpenvswitchInterface);
+            *ovs_interface = (OpenvswitchInterface){
+                .interface_uuid  = g_strdup(key),
+                .name            = g_strdup(name),
+                .type            = g_strdup(type),
+                .connection_uuid = g_strdup(connection_uuid),
+                .external_ids    = g_steal_pointer(&external_ids_arr),
+            };
+            g_hash_table_add(priv->interfaces, ovs_interface);
+            _LOGT("obj[iface:%s]: added an '%s' interface: %s%s%s, external-ids=%s",
+                  key,
+                  ovs_interface->type,
+                  ovs_interface->name,
+                  NM_PRINT_FMT_QUOTED2(ovs_interface->connection_uuid,
+                                       ", ",
+                                       ovs_interface->connection_uuid,
+                                       ""),
+                  (strtmp = _external_ids_to_string(ovs_interface->external_ids)));
+            _signal_emit_device_added(self,
+                                      ovs_interface->name,
+                                      NM_DEVICE_TYPE_OVS_INTERFACE,
+                                      ovs_interface->type);
+        }
+
+        /* The error is a string. No error is indicated by an empty set,
+         * Why not: [ "set": [] ] ? */
+        if (error && json_is_string(error)) {
+            _signal_emit_interface_failed(self,
+                                          ovs_interface->name,
+                                          ovs_interface->connection_uuid,
+                                          json_string_value(error));
+        }
+    }
+
+    json_object_foreach (port, key, value) {
+        gs_unref_ptrarray GPtrArray *interfaces = NULL;
+        OpenvswitchPort *            ovs_port;
+        gs_unref_array GArray *external_ids_arr = NULL;
+        const char *           connection_uuid  = NULL;
+        int                    r;
+
+        r = json_unpack(value,
+                        "{s:{s:s, s:o, s:o}}",
+                        "new",
+                        "name",
+                        &name,
+                        "external_ids",
+                        &external_ids,
+                        "interfaces",
+                        &items);
+        if (r != 0) {
+            gpointer unused;
+
+            r = json_unpack(value, "{s:{}}", "old");
+            if (r != 0)
+                continue;
+
+            if (!g_hash_table_steal_extended(priv->ports, &key, (gpointer *) &ovs_port, &unused))
+                continue;
+
+            _LOGT("obj[port:%s]: removed a port: %s%s%s",
+                  key,
+                  ovs_port->name,
+                  NM_PRINT_FMT_QUOTED2(ovs_port->connection_uuid,
+                                       ", ",
+                                       ovs_port->connection_uuid,
+                                       ""));
+            _signal_emit_device_removed(self, ovs_port->name, NM_DEVICE_TYPE_OVS_PORT, NULL);
+            _free_port(ovs_port);
+            continue;
+        }
+
+        ovs_port = g_hash_table_lookup(priv->ports, &key);
+
+        if (ovs_port && !nm_streq0(ovs_port->name, name)) {
+            if (!g_hash_table_steal(priv->ports, ovs_port))
+                nm_assert_not_reached();
+            _signal_emit_device_removed(self, ovs_port->name, NM_DEVICE_TYPE_OVS_PORT, NULL);
+            nm_clear_pointer(&ovs_port, _free_port);
+        }
+
+        _external_ids_extract(external_ids, &external_ids_arr, &connection_uuid);
+        interfaces = _uuids_to_array(items);
+
+        if (ovs_port) {
+            gboolean changed = FALSE;
+
+            nm_assert(nm_streq0(ovs_port->name, name));
+
+            changed |= nm_utils_strdup_reset(&ovs_port->name, name);
+            changed |= nm_utils_strdup_reset(&ovs_port->connection_uuid, connection_uuid);
+            if (nm_strv_ptrarray_cmp(ovs_port->interfaces, interfaces) != 0) {
+                NM_SWAP(&ovs_port->interfaces, &interfaces);
+                changed = TRUE;
+            }
+            if (!_external_ids_equal(ovs_port->external_ids, external_ids_arr)) {
+                NM_SWAP(&ovs_port->external_ids, &external_ids_arr);
+                changed = TRUE;
+            }
+            if (changed) {
+                gs_free char *strtmp = NULL;
+
+                _LOGT("obj[port:%s]: changed a port: %s%s%s, external-ids=%s",
+                      key,
+                      ovs_port->name,
+                      NM_PRINT_FMT_QUOTED2(ovs_port->connection_uuid,
+                                           ", ",
+                                           ovs_port->connection_uuid,
+                                           ""),
+                      (strtmp = _external_ids_to_string(ovs_port->external_ids)));
+            }
+        } else {
+            gs_free char *strtmp = NULL;
+
+            ovs_port  = g_slice_new(OpenvswitchPort);
+            *ovs_port = (OpenvswitchPort){
+                .port_uuid       = g_strdup(key),
+                .name            = g_strdup(name),
+                .connection_uuid = g_strdup(connection_uuid),
+                .interfaces      = g_steal_pointer(&interfaces),
+                .external_ids    = g_steal_pointer(&external_ids_arr),
+            };
+            g_hash_table_add(priv->ports, ovs_port);
+            _LOGT("obj[port:%s]: added a port: %s%s%s, external-ids=%s",
+                  key,
+                  ovs_port->name,
+                  NM_PRINT_FMT_QUOTED2(ovs_port->connection_uuid,
+                                       ", ",
+                                       ovs_port->connection_uuid,
+                                       ""),
+                  (strtmp = _external_ids_to_string(ovs_port->external_ids)));
+            _signal_emit_device_added(self, ovs_port->name, NM_DEVICE_TYPE_OVS_PORT, NULL);
+        }
+    }
+
+    json_object_foreach (bridge, key, value) {
+        gs_unref_ptrarray GPtrArray *ports = NULL;
+        OpenvswitchBridge *          ovs_bridge;
+        gs_unref_array GArray *external_ids_arr = NULL;
+        const char *           connection_uuid  = NULL;
+        int                    r;
+
+        r = json_unpack(value,
+                        "{s:{s:s, s:o, s:o}}",
+                        "new",
+                        "name",
+                        &name,
+                        "external_ids",
+                        &external_ids,
+                        "ports",
+                        &items);
+
+        if (r != 0) {
+            gpointer unused;
+
+            r = json_unpack(value, "{s:{}}", "old");
+            if (r != 0)
+                continue;
+
+            if (!g_hash_table_steal_extended(priv->bridges,
+                                             &key,
+                                             (gpointer *) &ovs_bridge,
+                                             &unused))
+                continue;
+
+            _LOGT("obj[bridge:%s]: removed a bridge: %s%s%s",
+                  key,
+                  ovs_bridge->name,
+                  NM_PRINT_FMT_QUOTED2(ovs_bridge->connection_uuid,
+                                       ", ",
+                                       ovs_bridge->connection_uuid,
+                                       ""));
+            _signal_emit_device_removed(self, ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE, NULL);
+            _free_bridge(ovs_bridge);
+            continue;
+        }
+
+        ovs_bridge = g_hash_table_lookup(priv->bridges, &key);
+
+        if (ovs_bridge && !nm_streq0(ovs_bridge->name, name)) {
+            if (!g_hash_table_steal(priv->bridges, ovs_bridge))
+                nm_assert_not_reached();
+            _signal_emit_device_removed(self, ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE, NULL);
+            nm_clear_pointer(&ovs_bridge, _free_bridge);
+        }
+
+        _external_ids_extract(external_ids, &external_ids_arr, &connection_uuid);
+        ports = _uuids_to_array(items);
+
+        if (ovs_bridge) {
+            gboolean changed = FALSE;
+
+            nm_assert(nm_streq0(ovs_bridge->name, name));
+
+            changed = nm_utils_strdup_reset(&ovs_bridge->name, name);
+            changed = nm_utils_strdup_reset(&ovs_bridge->connection_uuid, connection_uuid);
+            if (nm_strv_ptrarray_cmp(ovs_bridge->ports, ports) != 0) {
+                NM_SWAP(&ovs_bridge->ports, &ports);
+                changed = TRUE;
+            }
+            if (!_external_ids_equal(ovs_bridge->external_ids, external_ids_arr)) {
+                NM_SWAP(&ovs_bridge->external_ids, &external_ids_arr);
+                changed = TRUE;
+            }
+            if (changed) {
+                gs_free char *strtmp = NULL;
+
+                _LOGT("obj[bridge:%s]: changed a bridge: %s%s%s, external-ids=%s",
+                      key,
+                      ovs_bridge->name,
+                      NM_PRINT_FMT_QUOTED2(ovs_bridge->connection_uuid,
+                                           ", ",
+                                           ovs_bridge->connection_uuid,
+                                           ""),
+                      (strtmp = _external_ids_to_string(ovs_bridge->external_ids)));
+            }
+        } else {
+            gs_free char *strtmp = NULL;
+
+            ovs_bridge  = g_slice_new(OpenvswitchBridge);
+            *ovs_bridge = (OpenvswitchBridge){
+                .bridge_uuid     = g_strdup(key),
+                .name            = g_strdup(name),
+                .connection_uuid = g_strdup(connection_uuid),
+                .ports           = g_steal_pointer(&ports),
+                .external_ids    = g_steal_pointer(&external_ids_arr),
+            };
+            g_hash_table_add(priv->bridges, ovs_bridge);
+            _LOGT("obj[bridge:%s]: added a bridge: %s%s%s, external-ids=%s",
+                  key,
+                  ovs_bridge->name,
+                  NM_PRINT_FMT_QUOTED2(ovs_bridge->connection_uuid,
+                                       ", ",
+                                       ovs_bridge->connection_uuid,
+                                       ""),
+                  (strtmp = _external_ids_to_string(ovs_bridge->external_ids)));
+            _signal_emit_device_added(self, ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE, NULL);
+        }
+    }
+}
+
+/**
+ * ovsdb_got_echo:
+ *
+ * Only implemented because the specification mandates it. Actual ovsdb hasn't been
+ * seen doing this.
+ */
+static void
+ovsdb_got_echo(NMOvsdb *self, json_int_t id, json_t *data)
+{
+    NMOvsdbPrivate *    priv        = NM_OVSDB_GET_PRIVATE(self);
+    nm_auto_decref_json json_t *msg = NULL;
+    char *                      reply;
+    gboolean                    output_was_empty;
+
+    output_was_empty = priv->output->len == 0;
+
+    msg   = json_pack("{s:I, s:O}", "id", id, "result", data);
+    reply = json_dumps(msg, 0);
+    g_string_append(priv->output, reply);
+    free(reply);
+
+    if (output_was_empty)
+        ovsdb_write(self);
+}
+
+/**
+ * ovsdb_got_msg::
+ *
+ * Called when a complete JSON object was seen and unmarshalled.
+ * Either finishes a method call or processes a method call.
+ */
+static void
+ovsdb_got_msg(NMOvsdb *self, json_t *msg)
+{
+    NMOvsdbPrivate *priv       = NM_OVSDB_GET_PRIVATE(self);
+    json_error_t    json_error = {
+        0,
+    };
+    json_t *    json_id = NULL;
+    json_int_t  id      = (json_int_t) -1;
+    const char *method  = NULL;
+    json_t *    params  = NULL;
+    json_t *    result  = NULL;
+    json_t *    error   = NULL;
+
+    if (json_unpack_ex(msg,
+                       &json_error,
+                       0,
+                       "{s?:o, s?:s, s?:o, s?:o, s?:o}",
+                       "id",
+                       &json_id,
+                       "method",
+                       &method,
+                       "params",
+                       &params,
+                       "result",
+                       &result,
+                       "error",
+                       &error)
+        == -1) {
+        _LOGW("couldn't grok the message: %s", json_error.text);
+        ovsdb_disconnect(self, FALSE, FALSE);
+        return;
+    }
+
+    if (json_is_number(json_id))
+        id = json_integer_value(json_id);
+
+    if (method) {
+        /* It's a method call! */
+        if (!params) {
+            _LOGW("a method call with no params: '%s'", method);
+            ovsdb_disconnect(self, FALSE, FALSE);
+            return;
+        }
+
+        if (nm_streq0(method, "update")) {
+            /* This is a update method call. */
+            ovsdb_got_update(self, json_array_get(params, 1));
+        } else if (nm_streq0(method, "echo")) {
+            /* This is an echo request. */
+            ovsdb_got_echo(self, id, params);
+        } else {
+            _LOGW("got an unknown method call: '%s'", method);
+        }
+        return;
+    }
+
+    if (id >= 0) {
+        OvsdbMethodCall *call;
+        gs_free_error GError *local      = NULL;
+        gs_free char *        msg_as_str = NULL;
+
+        /* This is a response to a method call. */
+        if (c_list_is_empty(&priv->calls_lst_head)) {
+            _LOGE("there are no queued calls expecting response %" G_GUINT64_FORMAT, (guint64) id);
+            ovsdb_disconnect(self, FALSE, FALSE);
+            return;
+        }
+        call = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst);
+        if (call->call_id != id) {
+            _LOGE("expected a response to call %" G_GUINT64_FORMAT ", not %" G_GUINT64_FORMAT,
+                  call->call_id,
+                  (guint64) id);
+            ovsdb_disconnect(self, FALSE, FALSE);
+            return;
+        }
+        /* Cool, we found a corresponding call. Finish it. */
+
+        _LOGT_call(call, "response: %s", (msg_as_str = json_dumps(msg, 0)));
+
+        if (!json_is_null(error)) {
+            /* The response contains an error. */
+            g_set_error(&local,
+                        G_IO_ERROR,
+                        G_IO_ERROR_FAILED,
+                        "Error call to OVSDB returned an error: %s",
+                        json_string_value(error));
+        }
+
+        _call_complete(call, result, local);
+
+        priv->num_failures = 0;
+
+        /* Don't progress further commands in case the callback hit an error
+         * and disconnected us. */
+        if (!priv->conn)
+            return;
+
+        /* Now we're free to serialize and send the next command, if any. */
+        ovsdb_next_command(self);
+
+        return;
+    }
+
+    /* This is a message we are not interested in. */
+    _LOGW("got an unknown message, ignoring");
+}
+
+/*****************************************************************************/
+
+/* Lower level marshalling and demarshalling of the JSON-RPC traffic on the
+ * ovsdb socket. */
+
+static size_t
+_json_callback(void *buffer, size_t buflen, void *user_data)
+{
+    NMOvsdb *       self = NM_OVSDB(user_data);
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+
+    if (priv->bufp == priv->input->len) {
+        /* No more bytes buffered for decoding. */
+        return 0;
+    }
+
+    /* Pass one more byte to the JSON decoder. */
+    *(char *) buffer = priv->input->str[priv->bufp];
+    priv->bufp++;
+
+    return (size_t) 1;
+}
+
+/**
+ * ovsdb_read_cb:
+ *
+ * Read out the data available from the ovsdb socket and try to deserialize
+ * the JSON. If we see a complete object, pass it upwards to ovsdb_got_msg().
+ */
+static void
+ovsdb_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+    NMOvsdb *       self   = NM_OVSDB(user_data);
+    NMOvsdbPrivate *priv   = NM_OVSDB_GET_PRIVATE(self);
+    GInputStream *  stream = G_INPUT_STREAM(source_object);
+    GError *        error  = NULL;
+    gssize          size;
+    json_t *        msg;
+    json_error_t    json_error = {
+        0,
+    };
+
+    size = g_input_stream_read_finish(stream, res, &error);
+    if (size == -1) {
+        /* ovsdb-server was possibly restarted */
+        _LOGW("short read from ovsdb: %s", error->message);
+        priv->num_failures++;
+        g_clear_error(&error);
+        ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE);
+        return;
+    }
+
+    g_string_append_len(priv->input, priv->buf, size);
+    do {
+        priv->bufp = 0;
+        /* The callback always eats up only up to a single byte. This makes
+         * it possible for us to identify complete JSON objects in spite of
+         * us not knowing the length in advance. */
+        msg = json_load_callback(_json_callback, self, JSON_DISABLE_EOF_CHECK, &json_error);
+        if (msg) {
+            ovsdb_got_msg(self, msg);
+            g_string_erase(priv->input, 0, priv->bufp);
+        }
+        json_decref(msg);
+    } while (msg);
+
+    if (!priv->conn)
+        return;
+
+    if (size)
+        ovsdb_read(self);
+}
+
+static void
+ovsdb_read(NMOvsdb *self)
+{
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+
+    g_input_stream_read_async(g_io_stream_get_input_stream(G_IO_STREAM(priv->conn)),
+                              priv->buf,
+                              sizeof(priv->buf),
+                              G_PRIORITY_DEFAULT,
+                              NULL,
+                              ovsdb_read_cb,
+                              self);
+}
+
+static void
+ovsdb_write_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+    GOutputStream * stream = G_OUTPUT_STREAM(source_object);
+    NMOvsdb *       self   = NM_OVSDB(user_data);
+    NMOvsdbPrivate *priv   = NM_OVSDB_GET_PRIVATE(self);
+    GError *        error  = NULL;
+    gssize          size;
+
+    size = g_output_stream_write_finish(stream, res, &error);
+    if (size == -1) {
+        /* ovsdb-server was possibly restarted */
+        _LOGW("short write to ovsdb: %s", error->message);
+        priv->num_failures++;
+        g_clear_error(&error);
+        ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE);
+        return;
+    }
+
+    if (!priv->conn)
+        return;
+
+    g_string_erase(priv->output, 0, size);
+
+    ovsdb_write(self);
+}
+
+static void
+ovsdb_write(NMOvsdb *self)
+{
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+    GOutputStream * stream;
+
+    if (!priv->output->len)
+        return;
+
+    stream = g_io_stream_get_output_stream(G_IO_STREAM(priv->conn));
+    if (g_output_stream_has_pending(stream))
+        return;
+
+    g_output_stream_write_async(stream,
+                                priv->output->str,
+                                priv->output->len,
+                                G_PRIORITY_DEFAULT,
+                                NULL,
+                                ovsdb_write_cb,
+                                self);
+}
+
+/*****************************************************************************/
+
+/* Routines to maintain the ovsdb connection. */
+
+/**
+ * ovsdb_disconnect:
+ *
+ * Clean up the internal state to the point equivalent to before connecting.
+ * Apart from clean shutdown this is a good response to unexpected trouble,
+ * since the next method call attempt a will trigger reconnect which hopefully
+ * puts us back in sync.
+ */
+static void
+ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing)
+{
+    NMOvsdbPrivate * priv = NM_OVSDB_GET_PRIVATE(self);
+    OvsdbMethodCall *call;
+
+    nm_assert(!retry || !is_disposing);
+
+    if (!priv->client)
+        return;
+
+    _LOGD("disconnecting from ovsdb, retry %d", retry);
+
+    /* FIXME(shutdown): NMOvsdb should process the pending calls before
+     * shutting down, and cancel the remaining calls after the timeout. */
+
+    if (retry) {
+        if (!c_list_is_empty(&priv->calls_lst_head)) {
+            call          = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst);
+            call->call_id = CALL_ID_UNSPEC;
+        }
+    } else {
+        gs_free_error GError *error = NULL;
+
+        if (is_disposing)
+            nm_utils_error_set_cancelled(&error, is_disposing, "NMOvsdb");
+        else
+            nm_utils_error_set(&error, NM_UTILS_ERROR_NOT_READY, "disconnected from ovsdb");
+        while ((call = c_list_last_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst)))
+            _call_complete(call, NULL, error);
+    }
+
+    priv->bufp = 0;
+    g_string_truncate(priv->input, 0);
+    g_string_truncate(priv->output, 0);
+    g_clear_object(&priv->client);
+    g_clear_object(&priv->conn);
+    nm_clear_g_free(&priv->db_uuid);
+    nm_clear_g_cancellable(&priv->cancellable);
+
+    if (retry)
+        ovsdb_try_connect(self);
+}
+
+static void
+_check_ready(NMOvsdb *self)
+{
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+
+    nm_assert(!priv->ready);
+
+    if (priv->num_pending_deletions == 0) {
+        priv->ready = TRUE;
+        g_signal_emit(self, signals[READY], 0);
+        nm_manager_unblock_failed_ovs_interfaces(nm_manager_get());
+    }
+}
+
+static void
+_del_initial_iface_cb(GError *error, gpointer user_data)
+{
+    NMOvsdb *       self;
+    gs_free char *  ifname = NULL;
+    NMOvsdbPrivate *priv;
+
+    nm_utils_user_data_unpack(user_data, &self, &ifname);
+
+    if (nm_utils_error_is_cancelled_or_disposing(error))
+        return;
+
+    priv = NM_OVSDB_GET_PRIVATE(self);
+    nm_assert(priv->num_pending_deletions > 0);
+    priv->num_pending_deletions--;
+
+    _LOGD("delete initial interface '%s': %s %s%s%s, pending %u",
+          ifname,
+          error ? "error" : "success",
+          error ? "(" : "",
+          error ? error->message : "",
+          error ? ")" : "",
+          priv->num_pending_deletions);
+
+    _check_ready(self);
+}
+
+static void
+ovsdb_cleanup_initial_interfaces(NMOvsdb *self)
+{
+    NMOvsdbPrivate *            priv = NM_OVSDB_GET_PRIVATE(self);
+    const OpenvswitchInterface *interface;
+    NMUtilsUserData *           data;
+    GHashTableIter              iter;
+
+    if (priv->ready || priv->num_pending_deletions != 0)
+        return;
+
+    /* Delete OVS interfaces added by NM. Bridges and ports and
+     * not considered because they are deleted automatically
+     * when no interface is present. */
+    g_hash_table_iter_init(&iter, self->_priv.interfaces);
+    while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &interface)) {
+        if (interface->connection_uuid) {
+            priv->num_pending_deletions++;
+            _LOGD("deleting initial interface '%s' (pending: %u)",
+                  interface->name,
+                  priv->num_pending_deletions);
+            data = nm_utils_user_data_pack(self, g_strdup(interface->name));
+            nm_ovsdb_del_interface(self, interface->name, _del_initial_iface_cb, data);
+        }
+    }
+
+    _check_ready(self);
+}
+
+static void
+_monitor_bridges_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_data)
+{
+    if (error) {
+        if (!nm_utils_error_is_cancelled_or_disposing(error)) {
+            _LOGI("%s", error->message);
+            ovsdb_disconnect(self, FALSE, FALSE);
+        }
+        return;
+    }
+
+    /* Treat the first response the same as the subsequent "update"
+     * messages we eventually get. */
+    ovsdb_got_update(self, result);
+
+    ovsdb_cleanup_initial_interfaces(self);
+}
+
+static void
+_client_connect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+    GSocketClient *    client = G_SOCKET_CLIENT(source_object);
+    NMOvsdb *          self   = NM_OVSDB(user_data);
+    NMOvsdbPrivate *   priv;
+    GError *           error = NULL;
+    GSocketConnection *conn;
+
+    conn = g_socket_client_connect_finish(client, res, &error);
+    if (conn == NULL) {
+        if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+            _LOGI("%s", error->message);
+
+        ovsdb_disconnect(self, FALSE, FALSE);
+        g_clear_error(&error);
+        return;
+    }
+
+    priv       = NM_OVSDB_GET_PRIVATE(self);
+    priv->conn = conn;
+    g_clear_object(&priv->cancellable);
+
+    ovsdb_read(self);
+    ovsdb_next_command(self);
+}
+
+/**
+ * ovsdb_try_connect:
+ *
+ * Establish a connection to ovsdb unless it's already established or being
+ * established. Queues a monitor command as a very first one so that we're in
+ * sync when other commands are issued.
+ */
+static void
+ovsdb_try_connect(NMOvsdb *self)
+{
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+    GSocketAddress *addr;
+
+    if (priv->client)
+        return;
+
+    /* TODO: This should probably be made configurable via NetworkManager.conf */
+    addr = g_unix_socket_address_new(RUNSTATEDIR "/openvswitch/db.sock");
+
+    priv->client      = g_socket_client_new();
+    priv->cancellable = g_cancellable_new();
+    g_socket_client_connect_async(priv->client,
+                                  G_SOCKET_CONNECTABLE(addr),
+                                  priv->cancellable,
+                                  _client_connect_cb,
+                                  self);
+    g_object_unref(addr);
+
+    /* Queue a monitor call before any other command, ensuring that we have an up
+     * to date view of existing bridged that we need for add and remove ops. */
+    ovsdb_call_method(self,
+                      _monitor_bridges_cb,
+                      NULL,
+                      TRUE,
+                      OVSDB_MONITOR,
+                      OVSDB_METHOD_PAYLOAD_MONITOR());
+}
+
+/*****************************************************************************/
+
+/* Public functions useful for NMDeviceOpenvswitch to maintain the life cycle of
+ * their ovsdb entries without having to deal with ovsdb complexities themselves. */
+
+typedef struct {
+    NMOvsdbCallback callback;
+    gpointer        user_data;
+} OvsdbCall;
+
+static void
+_transact_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_data)
+{
+    OvsdbCall * call = user_data;
+    const char *err;
+    const char *err_details;
+    size_t      index;
+    json_t *    value;
+
+    if (error)
+        goto out;
+
+    json_array_foreach (result, index, value) {
+        if (json_unpack(value, "{s:s, s:s}", "error", &err, "details", &err_details) == 0) {
+            g_set_error(&error,
+                        G_IO_ERROR,
+                        G_IO_ERROR_FAILED,
+                        "Error running the transaction: %s: %s",
+                        err,
+                        err_details);
+            goto out;
+        }
+    }
+
+out:
+    call->callback(error, call->user_data);
+    nm_g_slice_free(call);
+}
+
+static OvsdbCall *
+ovsdb_call_new(NMOvsdbCallback callback, gpointer user_data)
+{
+    OvsdbCall *call;
+
+    call  = g_slice_new(OvsdbCall);
+    *call = (OvsdbCall){
+        .callback  = callback,
+        .user_data = user_data,
+    };
+    return call;
+}
+
+gboolean
+nm_ovsdb_is_ready(NMOvsdb *self)
+{
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+
+    return priv->ready;
+}
+
+void
+nm_ovsdb_add_interface(NMOvsdb *       self,
+                       NMConnection *  bridge,
+                       NMConnection *  port,
+                       NMConnection *  interface,
+                       NMDevice *      bridge_device,
+                       NMDevice *      interface_device,
+                       NMOvsdbCallback callback,
+                       gpointer        user_data)
+{
+    ovsdb_call_method(self,
+                      _transact_cb,
+                      ovsdb_call_new(callback, user_data),
+                      FALSE,
+                      OVSDB_ADD_INTERFACE,
+                      OVSDB_METHOD_PAYLOAD_ADD_INTERFACE(bridge,
+                                                         port,
+                                                         interface,
+                                                         bridge_device,
+                                                         interface_device));
+}
+
+void
+nm_ovsdb_del_interface(NMOvsdb *       self,
+                       const char *    ifname,
+                       NMOvsdbCallback callback,
+                       gpointer        user_data)
+{
+    ovsdb_call_method(self,
+                      _transact_cb,
+                      ovsdb_call_new(callback, user_data),
+                      FALSE,
+                      OVSDB_DEL_INTERFACE,
+                      OVSDB_METHOD_PAYLOAD_DEL_INTERFACE(ifname));
+}
+
+void
+nm_ovsdb_set_interface_mtu(NMOvsdb *       self,
+                           const char *    ifname,
+                           guint32         mtu,
+                           NMOvsdbCallback callback,
+                           gpointer        user_data)
+{
+    ovsdb_call_method(self,
+                      _transact_cb,
+                      ovsdb_call_new(callback, user_data),
+                      FALSE,
+                      OVSDB_SET_INTERFACE_MTU,
+                      OVSDB_METHOD_PAYLOAD_SET_INTERFACE_MTU(ifname, mtu));
+}
+
+void
+nm_ovsdb_set_external_ids(NMOvsdb *                self,
+                          NMDeviceType             device_type,
+                          const char *             ifname,
+                          const char *             connection_uuid,
+                          NMSettingOvsExternalIDs *s_exid_old,
+                          NMSettingOvsExternalIDs *s_exid_new)
+{
+    gs_unref_hashtable GHashTable *exid_old = NULL;
+    gs_unref_hashtable GHashTable *exid_new = NULL;
+
+    exid_old = s_exid_old
+                   ? nm_utils_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_exid_old))
+                   : NULL;
+    exid_new = s_exid_new
+                   ? nm_utils_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_exid_new))
+                   : NULL;
+
+    ovsdb_call_method(self,
+                      NULL,
+                      NULL,
+                      FALSE,
+                      OVSDB_SET_EXTERNAL_IDS,
+                      OVSDB_METHOD_PAYLOAD_SET_EXTERNAL_IDS(device_type,
+                                                            ifname,
+                                                            connection_uuid,
+                                                            exid_old,
+                                                            exid_new));
+}
+
+/*****************************************************************************/
+
+static void
+nm_ovsdb_init(NMOvsdb *self)
+{
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+
+    c_list_init(&priv->calls_lst_head);
+
+    priv->input  = g_string_new(NULL);
+    priv->output = g_string_new(NULL);
+    priv->bridges =
+        g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_bridge, NULL);
+    priv->ports =
+        g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_port, NULL);
+    priv->interfaces =
+        g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_interface, NULL);
+
+    ovsdb_try_connect(self);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMOvsdb *       self = NM_OVSDB(object);
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+
+    ovsdb_disconnect(self, FALSE, TRUE);
+
+    nm_assert(c_list_is_empty(&priv->calls_lst_head));
+
+    if (priv->input) {
+        g_string_free(priv->input, TRUE);
+        priv->input = NULL;
+    }
+    if (priv->output) {
+        g_string_free(priv->output, TRUE);
+        priv->output = NULL;
+    }
+
+    nm_clear_pointer(&priv->bridges, g_hash_table_destroy);
+    nm_clear_pointer(&priv->ports, g_hash_table_destroy);
+    nm_clear_pointer(&priv->interfaces, g_hash_table_destroy);
+
+    G_OBJECT_CLASS(nm_ovsdb_parent_class)->dispose(object);
+}
+
+static void
+nm_ovsdb_class_init(NMOvsdbClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    object_class->dispose = dispose;
+
+    signals[DEVICE_ADDED] = g_signal_new(NM_OVSDB_DEVICE_ADDED,
+                                         G_OBJECT_CLASS_TYPE(object_class),
+                                         G_SIGNAL_RUN_LAST,
+                                         0,
+                                         NULL,
+                                         NULL,
+                                         NULL,
+                                         G_TYPE_NONE,
+                                         3,
+                                         G_TYPE_STRING,
+                                         G_TYPE_UINT,
+                                         G_TYPE_STRING);
+
+    signals[DEVICE_REMOVED] = g_signal_new(NM_OVSDB_DEVICE_REMOVED,
+                                           G_OBJECT_CLASS_TYPE(object_class),
+                                           G_SIGNAL_RUN_LAST,
+                                           0,
+                                           NULL,
+                                           NULL,
+                                           NULL,
+                                           G_TYPE_NONE,
+                                           3,
+                                           G_TYPE_STRING,
+                                           G_TYPE_UINT,
+                                           G_TYPE_STRING);
+
+    signals[INTERFACE_FAILED] = g_signal_new(NM_OVSDB_INTERFACE_FAILED,
+                                             G_OBJECT_CLASS_TYPE(object_class),
+                                             G_SIGNAL_RUN_LAST,
+                                             0,
+                                             NULL,
+                                             NULL,
+                                             NULL,
+                                             G_TYPE_NONE,
+                                             3,
+                                             G_TYPE_STRING,
+                                             G_TYPE_STRING,
+                                             G_TYPE_STRING);
+
+    signals[READY] = g_signal_new(NM_OVSDB_READY,
+                                  G_OBJECT_CLASS_TYPE(object_class),
+                                  G_SIGNAL_RUN_LAST,
+                                  0,
+                                  NULL,
+                                  NULL,
+                                  NULL,
+                                  G_TYPE_NONE,
+                                  0);
+}
diff --git a/src/core/devices/ovs/nm-ovsdb.h b/src/core/devices/ovs/nm-ovsdb.h
new file mode 100644
index 00000000..d0b4d19d
--- /dev/null
+++ b/src/core/devices/ovs/nm-ovsdb.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_OVSDB_H__
+#define __NETWORKMANAGER_OVSDB_H__
+
+#define NM_TYPE_OVSDB            (nm_ovsdb_get_type())
+#define NM_OVSDB(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_OVSDB, NMOvsdb))
+#define NM_OVSDB_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_OVSDB, NMOvsdbClass))
+#define NM_IS_OVSDB(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_OVSDB))
+#define NM_IS_OVSDB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_OVSDB))
+#define NM_OVSDB_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_OVSDB, NMOvsdbClass))
+
+#define NM_OVSDB_DEVICE_ADDED     "device-added"
+#define NM_OVSDB_DEVICE_REMOVED   "device-removed"
+#define NM_OVSDB_INTERFACE_FAILED "interface-failed"
+#define NM_OVSDB_READY            "ready"
+
+typedef struct _NMOvsdb      NMOvsdb;
+typedef struct _NMOvsdbClass NMOvsdbClass;
+
+typedef void (*NMOvsdbCallback)(GError *error, gpointer user_data);
+
+NMOvsdb *nm_ovsdb_get(void);
+
+GType nm_ovsdb_get_type(void);
+
+void nm_ovsdb_add_interface(NMOvsdb *       self,
+                            NMConnection *  bridge,
+                            NMConnection *  port,
+                            NMConnection *  interface,
+                            NMDevice *      bridge_device,
+                            NMDevice *      interface_device,
+                            NMOvsdbCallback callback,
+                            gpointer        user_data);
+
+void nm_ovsdb_del_interface(NMOvsdb *       self,
+                            const char *    ifname,
+                            NMOvsdbCallback callback,
+                            gpointer        user_data);
+
+void nm_ovsdb_set_interface_mtu(NMOvsdb *       self,
+                                const char *    ifname,
+                                guint32         mtu,
+                                NMOvsdbCallback callback,
+                                gpointer        user_data);
+
+struct _NMSettingOvsExternalIDs;
+
+void nm_ovsdb_set_external_ids(NMOvsdb *                        self,
+                               NMDeviceType                     device_type,
+                               const char *                     ifname,
+                               const char *                     connection_uuid,
+                               struct _NMSettingOvsExternalIDs *s_exid_old,
+                               struct _NMSettingOvsExternalIDs *s_exid_new);
+
+gboolean nm_ovsdb_is_ready(NMOvsdb *self);
+
+#endif /* __NETWORKMANAGER_OVSDB_H__ */