bridge: automatic bridge:
Add a new "auto" bridge which tries D-Bus and fall back to PB in case of problem. This is the defaut reactor, but can also be explicitely used.
This commit is contained in:
parent
0d57c1bbed
commit
84321be4cc
13 changed files with 222 additions and 88 deletions
|
|
@ -30,7 +30,7 @@ log = getLogger(__name__)
|
|||
|
||||
# Interface prefix
|
||||
conf = config.parse_main_conf()
|
||||
bridge_conf = conf.common.get_bridge_conf(config.DBusBridgeSettings)
|
||||
bridge_conf = conf.get_bridge_conf(config.DBusBridgeSettings)
|
||||
const_INT_PREFIX = bridge_conf.int_prefix
|
||||
const_ERROR_PREFIX = const_INT_PREFIX + ".error"
|
||||
const_OBJ_PATH = "/org/libervia/Libervia/bridge"
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ log = getLogger(__name__)
|
|||
|
||||
# Interface prefix
|
||||
conf = config.parse_main_conf()
|
||||
bridge_conf = conf.common.get_bridge_conf(config.DBusBridgeSettings)
|
||||
bridge_conf = conf.get_bridge_conf(config.DBusBridgeSettings)
|
||||
const_INT_PREFIX = bridge_conf.int_prefix
|
||||
const_ERROR_PREFIX = const_INT_PREFIX + ".error"
|
||||
const_OBJ_PATH = "/org/libervia/Libervia/bridge"
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ class bridge(object):
|
|||
log.info("Init Perspective Broker...")
|
||||
self.root = PBRoot()
|
||||
conf = config.parse_main_conf()
|
||||
bridge_conf = conf.common.get_bridge_conf(config.PbBridgePbSettings)
|
||||
bridge_conf = conf.get_bridge_conf(config.PbBridgeSettings)
|
||||
conn_type = bridge_conf.connection_type
|
||||
if conn_type == "unix_socket":
|
||||
socket_path = bridge_conf.socket_dir / "bridge_pb"
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ class bridge(object):
|
|||
def bridge_connect(self, callback, errback):
|
||||
factory = pb.PBClientFactory()
|
||||
conf = config.parse_main_conf()
|
||||
bridge_conf = conf.common.get_bridge_conf(config.PbBridgePbSettings)
|
||||
bridge_conf = conf.get_bridge_conf(config.PbBridgeSettings)
|
||||
conn_type = bridge_conf.connection_type
|
||||
if conn_type == "unix_socket":
|
||||
socket_path = bridge_conf.socket_dir / "bridge_pb"
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ log = getLogger(__name__)
|
|||
|
||||
# Interface prefix
|
||||
conf = config.parse_main_conf()
|
||||
bridge_conf = conf.common.get_bridge_conf(config.DBusBridgeSettings)
|
||||
bridge_conf = conf.get_bridge_conf(config.DBusBridgeSettings)
|
||||
const_INT_PREFIX = bridge_conf.int_prefix
|
||||
const_ERROR_PREFIX = const_INT_PREFIX + ".error"
|
||||
const_OBJ_PATH = "/org/libervia/Libervia/bridge"
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ class bridge(object):
|
|||
log.info("Init Perspective Broker...")
|
||||
self.root = PBRoot()
|
||||
conf = config.parse_main_conf()
|
||||
bridge_conf = conf.common.get_bridge_conf(config.PbBridgePbSettings)
|
||||
bridge_conf = conf.get_bridge_conf(config.PbBridgeSettings)
|
||||
conn_type = bridge_conf.connection_type
|
||||
if conn_type == "unix_socket":
|
||||
socket_path = bridge_conf.socket_dir / "bridge_pb"
|
||||
|
|
|
|||
|
|
@ -29,6 +29,11 @@ from libervia import backend
|
|||
from libervia.backend.core.i18n import _, D_, language_switch
|
||||
from libervia.backend.core import patches
|
||||
from libervia.backend.models.tor import TorData
|
||||
from libervia.backend.tools.config import (
|
||||
BridgeSettings,
|
||||
DBusBridgeSettings,
|
||||
PbBridgeSettings,
|
||||
)
|
||||
|
||||
patches.apply()
|
||||
from twisted.application import app, service
|
||||
|
|
@ -78,6 +83,7 @@ class LiberviaBackend(service.Service):
|
|||
self.initialised = defer.Deferred()
|
||||
self.profiles = {}
|
||||
self.plugins = {}
|
||||
self._bridge = None
|
||||
# map for short name to whole namespace,
|
||||
# extended by plugins with register_namespace
|
||||
self.ns_map = {
|
||||
|
|
@ -85,28 +91,12 @@ class LiberviaBackend(service.Service):
|
|||
}
|
||||
|
||||
self.memory = memory.Memory(self)
|
||||
config = self.memory.config
|
||||
|
||||
# trigger are used to change Libervia behaviour
|
||||
self.trigger = trigger.TriggerManager()
|
||||
|
||||
bridges = config.common.bridges
|
||||
# FIXME: We only handle first bridge for now.
|
||||
bridge = next(iter(bridges))
|
||||
bridge_name: str = bridge.type
|
||||
bridge_module = dynamic_import.bridge(bridge_name)
|
||||
if bridge_module is None:
|
||||
log.error(f"Can't find bridge module of name {bridge_name}")
|
||||
sys.exit(1)
|
||||
log.info(f"using {bridge_name} bridge")
|
||||
try:
|
||||
self.bridge = bridge_module.bridge()
|
||||
except exceptions.BridgeInitError:
|
||||
log.exception("bridge can't be initialised, can't start Libervia Backend")
|
||||
sys.exit(1)
|
||||
|
||||
# FIXME: Seems not used anymore, maybe delete?
|
||||
if config.common.use_local_shared_tmp:
|
||||
if self.memory.config.common.use_local_shared_tmp:
|
||||
self.local_shared_path = self.get_local_path(
|
||||
None, C.CACHE_DIR, C.LOCAL_SHARED_DIR
|
||||
)
|
||||
|
|
@ -141,23 +131,25 @@ class LiberviaBackend(service.Service):
|
|||
else:
|
||||
return version
|
||||
|
||||
@property
|
||||
def bridge(self):
|
||||
assert self._bridge is not None
|
||||
return self._bridge
|
||||
|
||||
@property
|
||||
def bridge_name(self):
|
||||
return os.path.splitext(os.path.basename(self.bridge.__file__))[0]
|
||||
|
||||
async def _post_init(self):
|
||||
try:
|
||||
bridge_pi = self.bridge.post_init
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
await bridge_pi()
|
||||
except Exception:
|
||||
log.exception("Could not initialize bridge")
|
||||
# because init is not complete at this stage, we use callLater
|
||||
reactor.callLater(0, self.stop)
|
||||
return
|
||||
self._bridge, __ = await dynamic_import.load_and_init_bridge(
|
||||
G.config, is_backend=True
|
||||
)
|
||||
except exceptions.BridgeInitError:
|
||||
log.exception("bridge can't be initialised, can't start Libervia Backend")
|
||||
# because init is not complete at this stage, we use callLater
|
||||
reactor.callLater(0, self.stop)
|
||||
return
|
||||
|
||||
self.bridge.register_method("init_pre_script", lambda: self.init_pre_script)
|
||||
self.bridge.register_method("ready_get", lambda: self.initialised)
|
||||
|
|
|
|||
|
|
@ -19,17 +19,28 @@
|
|||
"""tools dynamic import"""
|
||||
|
||||
from importlib import import_module
|
||||
from types import ModuleType
|
||||
from typing import Any, Literal
|
||||
|
||||
from libervia.backend.core import exceptions
|
||||
from libervia.backend.core.log import getLogger
|
||||
from libervia.backend.tools.config import (
|
||||
BridgeSettings,
|
||||
DBusBridgeSettings,
|
||||
PbBridgeSettings,
|
||||
Settings,
|
||||
)
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
module_path = "libervia.frontends.bridge"
|
||||
|
||||
def bridge(name, module_path="libervia.backend.bridge"):
|
||||
|
||||
def import_bridge(name: str) -> ModuleType | None:
|
||||
"""import bridge module
|
||||
|
||||
@param module_path(str): path of the module to import
|
||||
@param name(str): name of the bridge to import (e.g.: dbus)
|
||||
@return (module, None): imported module or None if nothing is found
|
||||
@param name: name of the bridge to import (e.g.: dbus)
|
||||
@return: imported module or None if nothing is found
|
||||
"""
|
||||
try:
|
||||
bridge_module = import_module(module_path + "." + name)
|
||||
|
|
@ -40,3 +51,120 @@ def bridge(name, module_path="libervia.backend.bridge"):
|
|||
log.warning(f"Can't import bridge {name!r}: {e}")
|
||||
bridge_module = None
|
||||
return bridge_module
|
||||
|
||||
|
||||
def _load_bridge(bridge_settings: BridgeSettings, verbose: bool = True):
|
||||
"""Load requested bridge.
|
||||
|
||||
@raise BridgeInitError: Bridge can't be initialised.
|
||||
"""
|
||||
bridge_name: str = bridge_settings.type
|
||||
bridge_module = import_bridge(bridge_name)
|
||||
if bridge_module is None:
|
||||
log.error(f"Can't find bridge module of name {bridge_name}")
|
||||
raise exceptions.BridgeInitError
|
||||
if verbose:
|
||||
log.info(f"using {bridge_name} bridge")
|
||||
|
||||
# FIXME: temporary class selection, bridges need to be refactored to always use async
|
||||
# classes (and to not use a class starting with a lower case)
|
||||
try:
|
||||
bridge_cls = bridge_module.AIOBridge
|
||||
except AttributeError:
|
||||
bridge_cls = bridge_module.bridge
|
||||
try:
|
||||
return bridge_cls()
|
||||
except exceptions.BridgeInitError as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
log.exception(f"Can't load bridge {bridge_name}")
|
||||
raise exceptions.BridgeInitError from e
|
||||
|
||||
|
||||
def load_bridge(config: Settings, verbose: bool = True) -> tuple[Any, BridgeSettings]:
|
||||
"""Load and initialise the bridge.
|
||||
|
||||
"auto" bridge is handled
|
||||
|
||||
@param config: Libervia Settings
|
||||
@param is_backend: Set to True if calling from backend.
|
||||
@return: initialised bridge and bridge settings
|
||||
@raise BridgeInitError: Can't initialise this bridge.
|
||||
"""
|
||||
bridges = config.bridges
|
||||
# FIXME: We only handle first bridge for now.
|
||||
bridge_settings = next(iter(bridges))
|
||||
bridge = None
|
||||
if bridge_settings.type == "auto":
|
||||
try:
|
||||
bridge_settings = DBusBridgeSettings()
|
||||
config.bridges = [bridge_settings]
|
||||
bridge = _load_bridge(bridge_settings, verbose=verbose)
|
||||
except exceptions.BridgeInitError as e:
|
||||
if verbose:
|
||||
log.warning(f"Can't load D-Bus bridge: {e}")
|
||||
log.info("Falling back to PB bridge.")
|
||||
bridge_settings = PbBridgeSettings()
|
||||
config.bridges = [bridge_settings]
|
||||
|
||||
if bridge is None:
|
||||
bridge = _load_bridge(bridge_settings, verbose=verbose)
|
||||
|
||||
return bridge, bridge_settings
|
||||
|
||||
|
||||
async def _load_and_init_bridge(bridge_settings: BridgeSettings):
|
||||
"""Load and set requested bridge.
|
||||
|
||||
@raise BridgeInitError: Bridge can't be initialised.
|
||||
"""
|
||||
bridge = _load_bridge(bridge_settings)
|
||||
try:
|
||||
bridge_post_init = bridge.post_init
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
await bridge_post_init()
|
||||
except Exception as e:
|
||||
log.warning(f"Could not initialize bridge: {e}")
|
||||
raise exceptions.BridgeInitError from e
|
||||
return bridge
|
||||
|
||||
|
||||
async def load_and_init_bridge(
|
||||
config: Settings, is_backend=False
|
||||
) -> tuple[Any, BridgeSettings]:
|
||||
"""Load and initialise the bridge.
|
||||
|
||||
"auto" bridge is handled
|
||||
|
||||
@param config: Libervia Settings
|
||||
@param is_backend: Set to True if calling from backend.
|
||||
@return: initialised bridge and bridge settings
|
||||
@raise BridgeInitError: Can't initialise this bridge.
|
||||
"""
|
||||
|
||||
if is_backend:
|
||||
global module_path
|
||||
module_path = "libervia.backend.bridge"
|
||||
|
||||
bridges = config.bridges
|
||||
# FIXME: We only handle first bridge for now.
|
||||
bridge_settings = next(iter(bridges))
|
||||
bridge = None
|
||||
if bridge_settings.type == "auto":
|
||||
try:
|
||||
bridge_settings = DBusBridgeSettings()
|
||||
config.bridges = [bridge_settings]
|
||||
bridge = await _load_and_init_bridge(bridge_settings)
|
||||
except exceptions.BridgeInitError as e:
|
||||
log.warning(f"Can't load D-Bus bridge: {e}")
|
||||
log.info("Falling back to PB bridge.")
|
||||
bridge_settings = PbBridgeSettings()
|
||||
config.bridges = [bridge_settings]
|
||||
|
||||
if bridge is None:
|
||||
bridge = await _load_and_init_bridge(bridge_settings)
|
||||
|
||||
return bridge, bridge_settings
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ class FrontendSettings(DynamicSettingsBase):
|
|||
D = TypeVar("D", bound=DynamicSettingsBase)
|
||||
|
||||
|
||||
class PbBridgePbSettings(ConfigOptions):
|
||||
class PbBridgeSettings(ConfigOptions):
|
||||
type: Literal["pb"] = "pb"
|
||||
connection_type: Literal["unix_socket", "socket"] = Field(
|
||||
default="unix_socket", description="type of connection to use."
|
||||
|
|
@ -185,8 +185,18 @@ class DBusBridgeSettings(ConfigOptions):
|
|||
)
|
||||
|
||||
|
||||
class AutoBridgeSettings(ConfigOptions):
|
||||
"""Automatic Bridge.
|
||||
|
||||
Will try D-Bus first, then fall back to Pb if necessary.
|
||||
"""
|
||||
|
||||
type: Literal["auto"] = "auto"
|
||||
|
||||
|
||||
BridgeSettings = Annotated[
|
||||
DBusBridgeSettings | PbBridgePbSettings, Field(discriminator="type")
|
||||
DBusBridgeSettings | PbBridgeSettings | AutoBridgeSettings,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
B = TypeVar("B", bound=BridgeSettings)
|
||||
|
|
@ -252,14 +262,6 @@ class CommonSettings(ConfigOptions):
|
|||
default=None, description="Logging level (debug, info, warning, error)"
|
||||
)
|
||||
log_fmt: str | None = Field(default=None, description="Log message format")
|
||||
bridges: list[BridgeSettings] = Field(
|
||||
default_factory=list,
|
||||
description=(
|
||||
"Bridge is the name of the IPC used to communicate between backend and"
|
||||
" frontends. Leave empty to let Libervia determine the best one. If you"
|
||||
" indicate several bridges, they will run in parallel."
|
||||
),
|
||||
)
|
||||
pid_dir: DirectoryPathAuto = Field(
|
||||
default=C.TEMP_DIR,
|
||||
description=(
|
||||
|
|
@ -346,12 +348,47 @@ class CommonSettings(ConfigOptions):
|
|||
description="JID of the entity used for group chat searches.",
|
||||
)
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
extra="allow",
|
||||
env_prefix="LIBERVIA_",
|
||||
env_nested_delimiter="__",
|
||||
toml_file=C.CONFIG_FILES,
|
||||
)
|
||||
|
||||
common: CommonSettings = Field(default_factory=CommonSettings)
|
||||
bridges: list[BridgeSettings] = Field(
|
||||
default_factory=list,
|
||||
description=(
|
||||
"Bridge is the name of the IPC used to communicate between backend and"
|
||||
" frontends. Leave empty to let Libervia determine the best one. If you"
|
||||
" indicate several bridges, they will run in parallel."
|
||||
),
|
||||
)
|
||||
email: EmailSettings = Field(default_factory=EmailSettings)
|
||||
tls: TLSSettings = Field(default_factory=TLSSettings)
|
||||
tor: TorSettings = Field(default_factory=TorSettings)
|
||||
application: dict[str, dict[str, str]] = Field(
|
||||
default_factory=dict,
|
||||
description="Application specific settings, used by app manager.",
|
||||
)
|
||||
plugin: dict = Field(default_factory=dict, description="Plugin specific settings.")
|
||||
component: dict = Field(
|
||||
default_factory=dict, description="Component specific settings."
|
||||
)
|
||||
|
||||
@field_validator("bridges", mode="after")
|
||||
@classmethod
|
||||
def set_bridges_auto(cls, bridges: list[BridgeSettings]) -> list[BridgeSettings]:
|
||||
if not bridges:
|
||||
# FIXME: We default to DBus for now.
|
||||
bridges = [DBusBridgeSettings()]
|
||||
bridges = [AutoBridgeSettings()]
|
||||
elif any(isinstance(b, AutoBridgeSettings) for b in bridges) and any(
|
||||
isinstance(b, (DBusBridgeSettings, PbBridgeSettings)) for b in bridges
|
||||
):
|
||||
raise ValueError(
|
||||
'"auto" bridge is mutally exclusive with "dbus" and "pb" bridges.'
|
||||
)
|
||||
return bridges
|
||||
|
||||
def get_bridge_conf(self, bridge_type: type[B]) -> B:
|
||||
|
|
@ -368,31 +405,9 @@ class CommonSettings(ConfigOptions):
|
|||
# We raise internal error because there should not be any request for conf if the
|
||||
# bridge is not in "bridges" and thus instanciating.
|
||||
raise exceptions.InternalError(
|
||||
f"No bridge configuration found for type {type!r}."
|
||||
f"No bridge configuration found for type {bridge_type}."
|
||||
)
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
extra="allow",
|
||||
env_prefix="LIBERVIA_",
|
||||
env_nested_delimiter="__",
|
||||
toml_file=C.CONFIG_FILES,
|
||||
)
|
||||
|
||||
common: CommonSettings = Field(default_factory=CommonSettings)
|
||||
email: EmailSettings = Field(default_factory=EmailSettings)
|
||||
tls: TLSSettings = Field(default_factory=TLSSettings)
|
||||
tor: TorSettings = Field(default_factory=TorSettings)
|
||||
application: dict[str, dict[str, str]] = Field(
|
||||
default_factory=dict,
|
||||
description="Application specific settings, used by app manager.",
|
||||
)
|
||||
plugin: dict = Field(default_factory=dict, description="Plugin specific settings.")
|
||||
component: dict = Field(
|
||||
default_factory=dict, description="Component specific settings."
|
||||
)
|
||||
|
||||
def get_settings(self, dyn_settings_cls: type[D]) -> D:
|
||||
"""Get dynamic settings instance.
|
||||
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ class CLISettings(config.FrontendSettings, key="cli"):
|
|||
)
|
||||
|
||||
|
||||
## bridge handling
|
||||
# we get bridge name from conf and initialise the right class accordingly
|
||||
main_config = config.parse_main_conf(ignore_cache=True)
|
||||
bridge_name = next(iter(main_config.common.bridges)).type
|
||||
LiberviaCLILoop = get_libervia_cli_loop(bridge_name)
|
||||
|
||||
|
||||
try:
|
||||
import progressbar
|
||||
except ImportError:
|
||||
|
|
@ -145,16 +138,21 @@ class LiberviaCli:
|
|||
@attribute progress_failure(callable): method to call when progress failed
|
||||
by default display a message
|
||||
"""
|
||||
main_config = config.parse_main_conf(ignore_cache=True)
|
||||
|
||||
try:
|
||||
bridge, self.bridge_settings = dynamic_import.load_bridge(
|
||||
main_config, verbose=False
|
||||
)
|
||||
except exceptions.BridgeInitError:
|
||||
log.exception("Can't import bridge")
|
||||
sys.exit(1)
|
||||
self.console = console.Console(theme=C.THEME_DEFAULT)
|
||||
self.settings = main_config
|
||||
self.cli_settings = main_config.get_settings(CLISettings)
|
||||
self.set_color_theme()
|
||||
bridge_module = dynamic_import.bridge(bridge_name, "libervia.frontends.bridge")
|
||||
if bridge_module is None:
|
||||
log.error("Can't import {} bridge".format(bridge_name))
|
||||
sys.exit(1)
|
||||
self.bridge = bridge
|
||||
|
||||
self.bridge = bridge_module.AIOBridge()
|
||||
self._on_quit_callbacks = []
|
||||
|
||||
def guess_background(self):
|
||||
|
|
@ -856,6 +854,7 @@ class LiberviaCli:
|
|||
return
|
||||
|
||||
def _run(self, args=None, namespace=None):
|
||||
LiberviaCLILoop = get_libervia_cli_loop(self.bridge_settings.type)
|
||||
self.loop = LiberviaCLILoop()
|
||||
try:
|
||||
self.loop.run(self, args, namespace)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ log = getLogger(__name__)
|
|||
|
||||
# Interface prefix
|
||||
conf = config.parse_main_conf()
|
||||
bridge_conf = conf.common.get_bridge_conf(config.DBusBridgeSettings)
|
||||
bridge_conf = conf.get_bridge_conf(config.DBusBridgeSettings)
|
||||
const_INT_PREFIX = bridge_conf.int_prefix
|
||||
const_ERROR_PREFIX = const_INT_PREFIX + ".error"
|
||||
const_OBJ_PATH = "/org/libervia/Libervia/bridge"
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ class bridge(object):
|
|||
def bridge_connect(self, callback, errback):
|
||||
factory = pb.PBClientFactory()
|
||||
conf = config.parse_main_conf()
|
||||
bridge_conf = conf.common.get_bridge_conf(config.PbBridgePbSettings)
|
||||
bridge_conf = conf.get_bridge_conf(config.PbBridgeSettings)
|
||||
conn_type = bridge_conf.connection_type
|
||||
if conn_type == "unix_socket":
|
||||
socket_path = bridge_conf.socket_dir / "bridge_pb"
|
||||
|
|
|
|||
|
|
@ -338,7 +338,7 @@ class LiberviaTUIApp(QuickApp, InputHistory):
|
|||
AVATARS_HANDLER = False
|
||||
|
||||
def __init__(self):
|
||||
bridge_module = dynamic_import.bridge(bridge_name, "libervia.frontends.bridge")
|
||||
bridge_module = dynamic_import.import_bridge(bridge_name, "libervia.frontends.bridge")
|
||||
if bridge_module is None:
|
||||
log.error("Can't import {} bridge".format(bridge_name))
|
||||
sys.exit(3)
|
||||
|
|
|
|||
Loading…
Reference in a new issue