|
Packit Service |
0535c1 |
#
|
|
Packit Service |
0535c1 |
# Copyright (c) 2020 Red Hat, Inc.
|
|
Packit Service |
0535c1 |
#
|
|
Packit Service |
0535c1 |
# This file is part of nmstate
|
|
Packit Service |
0535c1 |
#
|
|
Packit Service |
0535c1 |
# This program is free software: you can redistribute it and/or modify
|
|
Packit Service |
0535c1 |
# it under the terms of the GNU Lesser General Public License as published by
|
|
Packit Service |
0535c1 |
# the Free Software Foundation, either version 2.1 of the License, or
|
|
Packit Service |
0535c1 |
# (at your option) any later version.
|
|
Packit Service |
0535c1 |
#
|
|
Packit Service |
0535c1 |
# This program is distributed in the hope that it will be useful,
|
|
Packit Service |
0535c1 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit Service |
0535c1 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
Packit Service |
0535c1 |
# GNU Lesser General Public License for more details.
|
|
Packit Service |
0535c1 |
#
|
|
Packit Service |
0535c1 |
# You should have received a copy of the GNU Lesser General Public License
|
|
Packit Service |
0535c1 |
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
Packit Service |
0535c1 |
#
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
import os
|
|
Packit Service |
0535c1 |
import time
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
import ovs
|
|
Packit Service |
0535c1 |
from ovs.db.idl import Transaction, Idl, SchemaHelper
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
from libnmstate.plugin import NmstatePlugin
|
|
Packit Service |
0535c1 |
from libnmstate.schema import Interface
|
|
Packit Service |
0535c1 |
from libnmstate.schema import OVSInterface
|
|
Packit Service |
0535c1 |
from libnmstate.schema import OVSBridge
|
|
Packit Service |
0535c1 |
from libnmstate.schema import OvsDB
|
|
Packit Service |
0535c1 |
from libnmstate.error import NmstateNotImplementedError
|
|
Packit Service |
0535c1 |
from libnmstate.error import NmstateTimeoutError
|
|
Packit Service |
0535c1 |
from libnmstate.error import NmstatePermissionError
|
|
Packit Service |
0535c1 |
from libnmstate.error import NmstateValueError
|
|
Packit Service |
0535c1 |
from libnmstate.error import NmstatePluginError
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
TIMEOUT = 5
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
DEFAULT_OVS_DB_SOCKET_PATH = "/run/openvswitch/db.sock"
|
|
Packit Service |
0535c1 |
DEFAULT_OVS_SCHEMA_PATH = "/usr/share/openvswitch/vswitch.ovsschema"
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
NM_EXTERNAL_ID = "NM.connection.uuid"
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
class _Changes:
|
|
Packit Service |
0535c1 |
def __init__(self, table_name, column_name, row_name, column_value):
|
|
Packit Service |
0535c1 |
self.table_name = table_name
|
|
Packit Service |
0535c1 |
self.column_name = column_name
|
|
Packit Service |
0535c1 |
self.row_name = row_name
|
|
Packit Service |
0535c1 |
self.column_value = column_value
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def __str__(self):
|
|
Packit Service |
0535c1 |
return f"{self.__dict__}"
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
class NmstateOvsdbPlugin(NmstatePlugin):
|
|
Packit Service |
0535c1 |
def __init__(self):
|
|
Packit Service |
0535c1 |
self._schema = None
|
|
Packit Service |
0535c1 |
self._idl = None
|
|
Packit Service |
0535c1 |
self._transaction = None
|
|
Packit Service |
0535c1 |
self._seq_no = 0
|
|
Packit Service |
0535c1 |
self._load_schema()
|
|
Packit Service |
0535c1 |
self._connect_to_ovs_db()
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def unload(self):
|
|
Packit Service |
0535c1 |
if self._transaction:
|
|
Packit Service |
0535c1 |
self._transaction.abort()
|
|
Packit Service |
0535c1 |
self._transaction = None
|
|
Packit Service |
0535c1 |
if self._idl:
|
|
Packit Service |
0535c1 |
self._idl.close()
|
|
Packit Service |
0535c1 |
self._idl = None
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def _load_schema(self):
|
|
Packit Service |
0535c1 |
schema_path = os.environ.get(
|
|
Packit Service |
0535c1 |
"OVS_SCHEMA_PATH", DEFAULT_OVS_SCHEMA_PATH
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
if not os.path.exists(schema_path):
|
|
Packit Service |
0535c1 |
raise NmstateValueError(
|
|
Packit Service |
0535c1 |
f"OVS schema file {schema_path} does not exist, "
|
|
Packit Service |
0535c1 |
"please define the correct one via "
|
|
Packit Service |
0535c1 |
"environment variable 'OVS_SCHEMA_PATH'"
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
if not os.access(schema_path, os.R_OK):
|
|
Packit Service |
0535c1 |
raise NmstatePermissionError(
|
|
Packit Service |
0535c1 |
f"Has no read permission to OVS schema file {schema_path}"
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
self._schema = SchemaHelper(schema_path)
|
|
Packit Service |
0535c1 |
self._schema.register_columns(
|
|
Packit Service |
0535c1 |
"Interface", [OvsDB.EXTERNAL_IDS, "name"]
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
self._schema.register_columns("Bridge", [OvsDB.EXTERNAL_IDS, "name"])
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def _connect_to_ovs_db(self):
|
|
Packit Service |
0535c1 |
socket_path = os.environ.get(
|
|
Packit Service |
0535c1 |
"OVS_DB_UNIX_SOCKET_PATH", DEFAULT_OVS_DB_SOCKET_PATH
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
if not os.path.exists(socket_path):
|
|
Packit Service |
0535c1 |
raise NmstateValueError(
|
|
Packit Service |
0535c1 |
f"OVS database socket file {socket_path} does not exist, "
|
|
Packit Service |
0535c1 |
"please start the OVS daemon or define the socket path via "
|
|
Packit Service |
0535c1 |
"environment variable 'OVS_DB_UNIX_SOCKET_PATH'"
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
if not os.access(socket_path, os.R_OK):
|
|
Packit Service |
0535c1 |
raise NmstatePermissionError(
|
|
Packit Service |
0535c1 |
f"Has no read permission to OVS db socket file {socket_path}"
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
self._idl = Idl(f"unix:{socket_path}", self._schema)
|
|
Packit Service |
0535c1 |
self.refresh_content()
|
|
Packit Service |
0535c1 |
if not self._idl.has_ever_connected():
|
|
Packit Service |
0535c1 |
self._idl = None
|
|
Packit Service |
0535c1 |
raise NmstatePluginError("Failed to connect to OVS DB")
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def refresh_content(self):
|
|
Packit Service |
0535c1 |
if self._idl:
|
|
Packit Service |
0535c1 |
timeout_end = time.time() + TIMEOUT
|
|
Packit Service |
0535c1 |
self._idl.run()
|
|
Packit Service |
0535c1 |
if self._idl.change_seqno == self._seq_no and self._seq_no:
|
|
Packit Service |
0535c1 |
return
|
|
Packit Service |
0535c1 |
while True:
|
|
Packit Service |
0535c1 |
changed = self._idl.run()
|
|
Packit Service |
0535c1 |
cur_seq_no = self._idl.change_seqno
|
|
Packit Service |
0535c1 |
if cur_seq_no != self._seq_no or changed:
|
|
Packit Service |
0535c1 |
self._seq_no = cur_seq_no
|
|
Packit Service |
0535c1 |
return
|
|
Packit Service |
0535c1 |
poller = ovs.poller.Poller()
|
|
Packit Service |
0535c1 |
self._idl.wait(poller)
|
|
Packit Service |
0535c1 |
poller.timer_wait(TIMEOUT * 1000)
|
|
Packit Service |
0535c1 |
poller.block()
|
|
Packit Service |
0535c1 |
if time.time() > timeout_end:
|
|
Packit Service |
0535c1 |
raise NmstateTimeoutError(
|
|
Packit Service |
0535c1 |
f"Plugin {self.name} timeout({TIMEOUT} "
|
|
Packit Service |
0535c1 |
"seconds) when refresh OVS database connection"
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
@property
|
|
Packit Service |
0535c1 |
def name(self):
|
|
Packit Service |
0535c1 |
return "nmstate-plugin-ovsdb"
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
@property
|
|
Packit Service |
0535c1 |
def priority(self):
|
|
Packit Service |
0535c1 |
return NmstatePlugin.DEFAULT_PRIORITY + 1
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
@property
|
|
Packit Service |
0535c1 |
def plugin_capabilities(self):
|
|
Packit Service |
0535c1 |
return NmstatePlugin.PLUGIN_CAPABILITY_IFACE
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def get_interfaces(self):
|
|
Packit Service |
0535c1 |
ifaces = []
|
|
Packit Service |
0535c1 |
for row in list(self._idl.tables["Interface"].rows.values()) + list(
|
|
Packit Service |
0535c1 |
self._idl.tables["Bridge"].rows.values()
|
|
Packit Service |
0535c1 |
):
|
|
Packit Service |
0535c1 |
ifaces.append(
|
|
Packit Service |
0535c1 |
{
|
|
Packit Service |
0535c1 |
Interface.NAME: row.name,
|
|
Packit Service |
0535c1 |
OvsDB.OVS_DB_SUBTREE: {
|
|
Packit Service |
0535c1 |
OvsDB.EXTERNAL_IDS: row.external_ids
|
|
Packit Service |
0535c1 |
},
|
|
Packit Service |
0535c1 |
}
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
return ifaces
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def apply_changes(self, net_state, save_to_disk):
|
|
Packit Service |
5463fa |
# State might changed after other plugin invoked apply_changes()
|
|
Packit Service |
0535c1 |
self.refresh_content()
|
|
Packit Service |
5463fa |
cur_iface_to_ext_ids = {}
|
|
Packit Service |
5463fa |
for iface_info in self.get_interfaces():
|
|
Packit Service |
5463fa |
cur_iface_to_ext_ids[iface_info[Interface.NAME]] = iface_info[
|
|
Packit Service |
5463fa |
OvsDB.OVS_DB_SUBTREE
|
|
Packit Service |
5463fa |
][OvsDB.EXTERNAL_IDS]
|
|
Packit Service |
5463fa |
|
|
Packit Service |
0535c1 |
pending_changes = []
|
|
Packit Service |
0535c1 |
for iface in net_state.ifaces.values():
|
|
Packit Service |
0535c1 |
if not iface.is_changed and not iface.is_desired:
|
|
Packit Service |
0535c1 |
continue
|
|
Packit Service |
0535c1 |
if not iface.is_up:
|
|
Packit Service |
0535c1 |
continue
|
|
Packit Service |
0535c1 |
if iface.type == OVSBridge.TYPE:
|
|
Packit Service |
0535c1 |
table_name = "Bridge"
|
|
Packit Service |
0535c1 |
elif iface.type == OVSInterface.TYPE:
|
|
Packit Service |
0535c1 |
table_name = "Interface"
|
|
Packit Service |
0535c1 |
else:
|
|
Packit Service |
0535c1 |
continue
|
|
Packit Service |
5463fa |
ids_after_nm_applied = cur_iface_to_ext_ids.get(iface.name, {})
|
|
Packit Service |
5463fa |
ids_before_nm_applied = (
|
|
Packit Service |
5463fa |
iface.to_dict()
|
|
Packit Service |
5463fa |
.get(OvsDB.OVS_DB_SUBTREE, {})
|
|
Packit Service |
5463fa |
.get(OvsDB.EXTERNAL_IDS, {})
|
|
Packit Service |
5463fa |
)
|
|
Packit Service |
5463fa |
original_desire_ids = iface.original_dict.get(
|
|
Packit Service |
5463fa |
OvsDB.OVS_DB_SUBTREE, {}
|
|
Packit Service |
5463fa |
).get(OvsDB.EXTERNAL_IDS)
|
|
Packit Service |
5463fa |
|
|
Packit Service |
5463fa |
desire_ids = []
|
|
Packit Service |
5463fa |
|
|
Packit Service |
5463fa |
if original_desire_ids is None:
|
|
Packit Service |
5463fa |
desire_ids = ids_before_nm_applied
|
|
Packit Service |
5463fa |
else:
|
|
Packit Service |
5463fa |
desire_ids = original_desire_ids
|
|
Packit Service |
5463fa |
|
|
Packit Service |
5463fa |
# should include external_id created by NetworkManager.
|
|
Packit Service |
5463fa |
if NM_EXTERNAL_ID in ids_after_nm_applied:
|
|
Packit Service |
5463fa |
desire_ids[NM_EXTERNAL_ID] = ids_after_nm_applied[
|
|
Packit Service |
5463fa |
NM_EXTERNAL_ID
|
|
Packit Service |
5463fa |
]
|
|
Packit Service |
5463fa |
if desire_ids != ids_after_nm_applied:
|
|
Packit Service |
5463fa |
pending_changes.append(
|
|
Packit Service |
5463fa |
_generate_db_change_external_ids(
|
|
Packit Service |
5463fa |
table_name, iface.name, desire_ids
|
|
Packit Service |
5463fa |
)
|
|
Packit Service |
5463fa |
)
|
|
Packit Service |
0535c1 |
if pending_changes:
|
|
Packit Service |
0535c1 |
if not save_to_disk:
|
|
Packit Service |
0535c1 |
raise NmstateNotImplementedError(
|
|
Packit Service |
0535c1 |
"ovsdb plugin does not support memory only changes"
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
elif self._idl:
|
|
Packit Service |
0535c1 |
self._start_transaction()
|
|
Packit Service |
0535c1 |
self._db_write(pending_changes)
|
|
Packit Service |
0535c1 |
self._commit_transaction()
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def _db_write(self, changes):
|
|
Packit Service |
0535c1 |
changes_index = {change.row_name: change for change in changes}
|
|
Packit Service |
0535c1 |
changed_tables = set(change.table_name for change in changes)
|
|
Packit Service |
0535c1 |
for changed_table in changed_tables:
|
|
Packit Service |
0535c1 |
for row in self._idl.tables[changed_table].rows.values():
|
|
Packit Service |
0535c1 |
if row.name in changes_index:
|
|
Packit Service |
0535c1 |
change = changes_index[row.name]
|
|
Packit Service |
0535c1 |
setattr(row, change.column_name, change.column_value)
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def _start_transaction(self):
|
|
Packit Service |
0535c1 |
self._transaction = Transaction(self._idl)
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
def _commit_transaction(self):
|
|
Packit Service |
0535c1 |
if self._transaction:
|
|
Packit Service |
0535c1 |
status = self._transaction.commit()
|
|
Packit Service |
0535c1 |
timeout_end = time.time() + TIMEOUT
|
|
Packit Service |
0535c1 |
while status == Transaction.INCOMPLETE:
|
|
Packit Service |
0535c1 |
self._idl.run()
|
|
Packit Service |
0535c1 |
poller = ovs.poller.Poller()
|
|
Packit Service |
0535c1 |
self._idl.wait(poller)
|
|
Packit Service |
0535c1 |
self._transaction.wait(poller)
|
|
Packit Service |
0535c1 |
poller.timer_wait(TIMEOUT * 1000)
|
|
Packit Service |
0535c1 |
poller.block()
|
|
Packit Service |
0535c1 |
if time.time() > timeout_end:
|
|
Packit Service |
0535c1 |
raise NmstateTimeoutError(
|
|
Packit Service |
0535c1 |
f"Plugin {self.name} timeout({TIMEOUT} "
|
|
Packit Service |
0535c1 |
"seconds) when commit OVS database transaction"
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
status = self._transaction.commit()
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
if status == Transaction.SUCCESS:
|
|
Packit Service |
0535c1 |
self.refresh_content()
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
transaction_error = self._transaction.get_error()
|
|
Packit Service |
0535c1 |
self._transaction = None
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
if status not in (Transaction.SUCCESS, Transaction.UNCHANGED):
|
|
Packit Service |
0535c1 |
raise NmstatePluginError(
|
|
Packit Service |
0535c1 |
f"Plugin {self.name} failure on commiting OVS database "
|
|
Packit Service |
0535c1 |
f"transaction: status: {status} "
|
|
Packit Service |
0535c1 |
f"error: {transaction_error}"
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
else:
|
|
Packit Service |
0535c1 |
raise NmstatePluginError(
|
|
Packit Service |
0535c1 |
"BUG: _commit_transaction() invoked with "
|
|
Packit Service |
0535c1 |
"self._transaction is None"
|
|
Packit Service |
0535c1 |
)
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
5463fa |
def _generate_db_change_external_ids(table_name, iface_name, desire_ids):
|
|
Packit Service |
0535c1 |
if desire_ids and not isinstance(desire_ids, dict):
|
|
Packit Service |
0535c1 |
raise NmstateValueError("Invalid external_ids, should be dictionary")
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
5463fa |
# Convert all value to string
|
|
Packit Service |
5463fa |
for key, value in desire_ids.items():
|
|
Packit Service |
5463fa |
desire_ids[key] = str(value)
|
|
Packit Service |
0f5335 |
|
|
Packit Service |
5463fa |
return _Changes(table_name, OvsDB.EXTERNAL_IDS, iface_name, desire_ids)
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
|
|
Packit Service |
0535c1 |
NMSTATE_PLUGIN = NmstateOvsdbPlugin
|