Unverified Commit 7c2f1b35 authored by themylogin's avatar themylogin Committed by GitHub
Browse files

Network interface `link_address_b` (#11497)

Showing with 104 additions and 112 deletions
+104 -112
......@@ -696,6 +696,8 @@ class FailoverService(Service):
logger.warning('Initializing KMIP keys')
self.run_call('kmip.initialize_keys')
self.run_call('interface.persist_link_addresses')
logger.warning('Failover event complete.')
except AlreadyLocked:
logger.warning('Failover event handler failed to aquire master lockfile')
......@@ -890,6 +892,8 @@ class FailoverService(Service):
# Sync GELI and/or ZFS encryption keys from MASTER node
self.middleware.call_sync('failover.sync_keys_from_remote_node')
self.run_call('failover.call_remote', 'interface.persist_link_addresses')
# if we're the backup controller then it means
# the SED drives have already been unlocked so
# set this accordingly so we don't try to unlock
......
......@@ -29,7 +29,7 @@ timezone = UTC
# version location specification; this defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path
version_locations = alembic/versions/12.0 alembic/versions/13.0
version_locations = alembic/versions/13.1 alembic/versions/13.0 alembic/versions/12.0
# the output encoding used when revision files
# are written from script.py.mako
......
"""Network interface link_address_b
Revision ID: b412304844e1
Revises: 88bfe11b5be5
Create Date: 2023-06-13 12:04:07.420120+00:00
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'b412304844e1'
down_revision = '88bfe11b5be5'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('network_interfaces', schema=None) as batch_op:
batch_op.add_column(sa.Column('int_link_address_b', sa.String(length=17), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('network_interfaces', schema=None) as batch_op:
batch_op.drop_column('int_link_address_b')
# ### end Alembic commands ###
import re
from middlewared.service import private, Service
from middlewared.utils import osc
RE_FREEBSD_BRIDGE = re.compile(r"bridge([0-9]+)$")
RE_FREEBSD_LAGG = re.compile(r"lagg([0-9]+)$")
class InterfaceService(Service):
class Config:
namespace_alias = "interfaces"
@private
async def persist_link_addresses(self):
try:
if await self.middleware.call("failover.node") == "B":
local_key = "link_address_b"
remote_key = "link_address"
else:
local_key = "link_address"
remote_key = "link_address_b"
real_interfaces = RealInterfaceCollection(
await self.middleware.call("interface.query", [["fake", "!=", True]]),
)
real_interfaces_remote = None
if await self.middleware.call("failover.status") == "MASTER":
try:
real_interfaces_remote = RealInterfaceCollection(
await self.middleware.call("failover.call_remote", "interface.query", [[["fake", "!=", True]]]),
)
except Exception as e:
self.middleware.logger.warning(f"Exception while retrieving remote network interfaces: {e!r}")
db_interfaces = DatabaseInterfaceCollection(
await self.middleware.call("datastore.query", "network.interfaces", [], {"prefix": "int_"}),
)
# Update link addresses for interfaces in the database
for db_interface in db_interfaces:
update = {}
self.__handle_update(real_interfaces, db_interface, local_key, update)
if real_interfaces_remote is not None:
self.__handle_update(real_interfaces_remote, db_interface, remote_key, update)
if update:
await self.middleware.call("datastore.update", "network.interfaces", db_interface["id"],
update, {"prefix": "int_"})
except Exception:
self.middleware.logger.error("Unhandled exception while persisting network interfaces link addresses",
exc_info=True)
def __handle_update(self, real_interfaces, db_interface, key, update):
real_interface = real_interfaces.by_name.get(db_interface["interface"])
if real_interface is None:
link_address_local = None
else:
link_address_local = real_interface["state"]["link_address"]
if db_interface[key] != link_address_local:
self.middleware.logger.debug(
f"Setting interface {db_interface['interface']!r} {key} = {link_address_local!r}",
)
update[key] = link_address_local
class InterfaceCollection:
......@@ -14,127 +69,22 @@ class InterfaceCollection:
def by_name(self):
return {self.get_name(i): i for i in self.interfaces}
@property
def by_link_address(self):
return {self.get_link_address(i): i for i in self.interfaces}
def __iter__(self):
return iter(self.interfaces)
def get_name(self, i):
raise NotImplementedError
def get_link_address(self, i):
raise NotImplementedError
class DatabaseInterfaceCollection(InterfaceCollection):
def get_name(self, i):
return i["interface"]
def get_link_address(self, i):
return i["link_address"]
class RealInterfaceCollection(InterfaceCollection):
def get_name(self, i):
return i["name"]
def get_link_address(self, i):
return i["state"]["link_address"]
async def rename_interface(middleware, db_interface, name):
middleware.logger.info("Renaming interface %r to %r", db_interface["interface"], name)
await middleware.call("datastore.update", "network.interfaces", db_interface["id"],
{"interface": name}, {"prefix": "int_"})
for bridge in await middleware.call("datastore.query", "network.bridge"):
try:
index = bridge["members"].index(db_interface["interface"])
except ValueError:
continue
bridge["members"][index] = name
middleware.logger.info("Setting bridge %r members: %r", bridge["id"], bridge["members"])
await middleware.call("datastore.update", "network.bridge", bridge["id"], {"members": bridge["members"]})
for lagg_member in await middleware.call("datastore.query", "network.lagginterfacemembers"):
if lagg_member["lagg_physnic"] == db_interface["interface"]:
middleware.logger.info("Setting LAGG member %r physical NIC %r", lagg_member["id"], name)
await middleware.call("datastore.update", "network.lagginterfacemembers", lagg_member["id"],
{"lagg_physnic": name})
for vlan in await middleware.call("datastore.query", "network.vlan"):
if vlan["vlan_pint"] == db_interface["interface"]:
middleware.logger.info("Setting VLAN %r parent NIC %r", vlan["vlan_vint"], vlan["vlan_pint"])
await middleware.call("datastore.update", "network.vlan", vlan["id"],
{"vlan_pint": name})
async def setup(middleware):
try:
real_interfaces = RealInterfaceCollection(await middleware.call("interface.query", [["fake", "!=", True]]))
db_interfaces = DatabaseInterfaceCollection(
await middleware.call("datastore.query", "network.interfaces", [], {"prefix": "int_"}),
)
# Migrate BSD network interfaces to Linux
if osc.IS_LINUX:
for db_interface in db_interfaces:
if m := RE_FREEBSD_BRIDGE.match(db_interface["interface"]):
name = f"br{m.group(1)}"
await rename_interface(middleware, db_interface, name)
db_interface["interface"] = name
if m := RE_FREEBSD_LAGG.match(db_interface["interface"]):
name = f"bond{m.group(1)}"
await rename_interface(middleware, db_interface, name)
db_interface["interface"] = name
if db_interface["link_address"] is not None:
if real_interfaces.by_name.get(db_interface["interface"]) is not None:
# There already is an interface that matches DB cached one, doing nothing
continue
real_interface_by_link_address = real_interfaces.by_link_address.get(db_interface["link_address"])
if real_interface_by_link_address is None:
middleware.logger.warning(
"Interface with link address %r does not exist anymore (its name was %r)",
db_interface["link_address"], db_interface["interface"],
)
continue
db_interface_for_real_interface = db_interfaces.by_name.get(real_interface_by_link_address["name"])
if db_interface_for_real_interface is not None:
if db_interface_for_real_interface != db_interface:
middleware.logger.warning(
"Database already has interface %r (we wanted to set that name for interface %r "
"because it matches its link address %r)",
real_interface_by_link_address["name"], db_interface["interface"],
db_interface["link_address"],
)
continue
middleware.logger.info(
"Interface %r is now %r (matched by link address %r)",
db_interface["interface"], real_interface_by_link_address["name"], db_interface["link_address"],
)
await rename_interface(middleware, db_interface, real_interface_by_link_address["name"])
db_interface["interface"] = real_interface_by_link_address["name"]
# Update link addresses for interfaces in the database
for db_interface in db_interfaces:
real_interface = real_interfaces.by_name.get(db_interface["interface"])
if real_interface is None:
link_address = None
else:
link_address = real_interface["state"]["link_address"]
if db_interface["link_address"] != link_address:
middleware.logger.debug("Setting link address %r for interface %r",
link_address, db_interface["interface"])
await middleware.call("datastore.update", "network.interfaces", db_interface["id"],
{"link_address": link_address}, {"prefix": "int_"})
except Exception:
middleware.logger.error("Unhandled exception while migrating network interfaces", exc_info=True)
await middleware.call("interface.persist_link_addresses")
......@@ -487,6 +487,7 @@ class NetworkInterfaceModel(sa.Model):
int_mtu = sa.Column(sa.Integer(), nullable=True)
int_disable_offload_capabilities = sa.Column(sa.Boolean(), default=False)
int_link_address = sa.Column(sa.String(17), nullable=True)
int_link_address_b = sa.Column(sa.String(17), nullable=True)
class NetworkLaggInterfaceModel(sa.Model):
......@@ -1561,11 +1562,16 @@ class InterfaceService(CRUDService):
{'int_interface': new['name']},
)
link_address_update = {'int_link_address': iface['state']['link_address']}
if await self.middleware.call('system.is_enterprise_ix_hardware'):
if await self.middleware.call('failover.node') == 'B':
link_address_update = {'int_link_address_b': iface['state']['link_address']}
await self.middleware.call(
'datastore.update',
'network.interfaces',
config['id'],
{'int_link_address': iface['state']['link_address']},
link_address_update,
)
if iface['type'] == 'BRIDGE':
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment