Allow optional bridge process in HDStack

master
Simon 3 weeks ago
parent 89e629fd4e
commit d7f119af7f

@ -24,7 +24,7 @@ services:
mem_reservation: 600m
volumes:
- '/etc/freedmr/freedmr.cfg:/opt/freedmr/freedmr.cfg'
# HDSTACK=2..5 requires the separate bridge inputs below.
# HDSTACK=2..5 requires these unless HDSTACK_BRIDGE=0.
#- '/etc/freedmr/freedmr-bridge.cfg:/opt/freedmr/freedmr-bridge.cfg:ro'
#- '/etc/freedmr/rules-bridge.py:/opt/freedmr/rules.py:ro'
#Write JSON files outside of container
@ -54,6 +54,8 @@ services:
# Optional 2..5 worker HDStack deployment. Omit for current mode.
#- HDSTACK=2
#- HDSTACK_BASEID=23400
# Disable the separate bridge.py process when it is not required.
#- HDSTACK_BRIDGE=0
#- HDSTACK_BRIDGE_CONFIG=/opt/freedmr/freedmr-bridge.cfg
#- HDSTACK_BRIDGE_RULES=/opt/freedmr/rules.py
tmpfs: /tmp

@ -20,6 +20,7 @@
cd /opt/freedmr
HDSTACK_COUNT="${HDSTACK:-1}"
HDSTACK_BRIDGE="${HDSTACK_BRIDGE:-1}"
HDSTACK_OUTPUT_DIR="${HDSTACK_OUTPUT_DIR:-/dev/shm/freedmr-hdstack}"
HDSTACK_BRIDGE_CONFIG="${HDSTACK_BRIDGE_CONFIG:-/opt/freedmr/freedmr-bridge.cfg}"
HDSTACK_BRIDGE_RULES="${HDSTACK_BRIDGE_RULES:-/opt/freedmr/rules.py}"
@ -52,7 +53,8 @@ then
--bridge-rules "$HDSTACK_BRIDGE_RULES" \
--output-dir "$HDSTACK_OUTPUT_DIR" \
--instances "$HDSTACK_COUNT" \
--base-id "$HDSTACK_BASEID"
--base-id "$HDSTACK_BASEID" \
--bridge-enabled "$HDSTACK_BRIDGE"
status=$?
if [ "$status" -ne 0 ]
then

@ -11,13 +11,13 @@ selects the generated multi-process layout:
```text
hotspots -> proxy -> HBP workers -> no-HBP aggregator -> external FBP/OBP
|
+-> separate bridge.py instance
+-> optional separate bridge.py instance
worker reports -> reporting MUX -> existing dashboard/reporting consumer
```
The aggregator owns all OpenBridge/FBP links from the main `freedmr.cfg` and
has no HBP `MASTER`, `PEER` or `XLXPEER` system. The separate `bridge.py`
has no HBP `MASTER`, `PEER` or `XLXPEER` system. The optional `bridge.py`
process retains its own configuration and rules. The reporting MUX reads only
worker reports; aggregator and bridge reporting are not included.
@ -36,7 +36,20 @@ adding their one-based worker number. For the example above, the aggregator is
`23400` and the two workers are `23401` and `23402`. With `HDSTACK=5`, worker
IDs continue through `23405`.
Multi-process mode also requires the existing separate bridge inputs:
The separate `bridge.py` process is enabled by default. Disable it when the
deployment does not require bridge rules or HBP peer/XLX connections:
```yaml
environment:
- HDSTACK_BRIDGE=0
```
`HDSTACK_BRIDGE` accepts only `0` or `1`. It affects only multi-process mode.
When disabled, no bridge configuration or rules file is required and no
`bridge.py` process is started. Remove or disable any aggregator OpenBridge
section intended only to connect to that bridge instance.
When the bridge is enabled, multi-process mode requires its existing inputs:
```yaml
volumes:
@ -65,10 +78,11 @@ derived. Worker and aggregator configuration files are generated privately in
`/dev/shm/freedmr-hdstack` when the container starts. The mounted source
configuration and bridge files are not changed.
The bridge configuration must contain the reciprocal link to the aggregator
and any `PEER` or `XLXPEER` systems used by `bridge.py`. Its `rules.py` remains
the authority for bridge routing. Bridge reporting must either be disabled or
use a port which does not collide with the worker reports or MUX.
When enabled, the bridge configuration must contain the reciprocal link to the
aggregator and any `PEER` or `XLXPEER` systems used by `bridge.py`. Its
`rules.py` remains the authority for bridge routing. Bridge reporting must
either be disabled or use a port which does not collide with the worker reports
or MUX.
## Worker ports

@ -49,17 +49,23 @@ def generate_hdstack(
output_dir,
instances,
base_server_id,
bridge_enabled=True,
):
"""Generate configs for an aggregator, workers, bridge, proxy and MUX."""
"""Generate configs for an aggregator, workers, optional bridge, proxy and MUX."""
instances = _validated_instances(instances)
base_server_id = _validated_base_id(base_server_id, instances)
bridge_enabled = _validated_bridge_enabled(bridge_enabled)
main_path = _required_file(main_config, 'main FreeDMR configuration')
bridge_path = _required_file(bridge_config, 'bridge configuration')
rules_path = _required_file(bridge_rules, 'bridge rules')
bridge_path = None
rules_path = None
bridge_source = None
if bridge_enabled:
bridge_path = _required_file(bridge_config, 'bridge configuration')
rules_path = _required_file(bridge_rules, 'bridge rules')
bridge_source = _read_config(bridge_path)
output_path = Path(output_dir)
source = _read_config(main_path)
bridge_source = _read_config(bridge_path)
system = _system_template(source)
hbp_port = _integer(system, 'PORT', 54000, 'SYSTEM PORT')
generator_size = _integer(system, 'GENERATOR', 100, 'SYSTEM GENERATOR')
@ -95,6 +101,7 @@ def generate_hdstack(
report_port,
worker_report_ports,
instances,
bridge_enabled,
)
aggregator = _support_config(source)
@ -169,6 +176,7 @@ def generate_hdstack(
report_port,
worker_report_ports,
_report_clients(source),
bridge_enabled,
),
encoding='utf-8',
)
@ -207,6 +215,12 @@ def _validated_base_id(value, instances):
return value
def _validated_bridge_enabled(value):
if not isinstance(value, bool):
raise HDStackConfigurationError('HDSTACK_BRIDGE must be 0 or 1')
return value
def _required_file(value, description):
path = Path(value)
if not path.is_file():
@ -316,6 +330,7 @@ def _validate_generated_port_collisions(
report_port,
worker_report_ports,
instances,
bridge_enabled,
):
generated = {}
@ -346,7 +361,9 @@ def _validate_generated_port_collisions(
reserve(port, 'worker {} reporting'.format(number))
configured = _enabled_bind_ports(source, excluded_sections=('SYSTEM', 'ECHO'))
bridge_ports = _enabled_bind_ports(bridge_source)
bridge_ports = {}
if bridge_enabled:
bridge_ports = _enabled_bind_ports(bridge_source)
for port, sections in configured.items():
if port in generated:
raise HDStackConfigurationError(
@ -366,7 +383,7 @@ def _validate_generated_port_collisions(
)
)
if _boolean(bridge_source, 'REPORTS', 'REPORT', True):
if bridge_enabled and _boolean(bridge_source, 'REPORTS', 'REPORT', True):
bridge_report_port = _section_integer(
bridge_source,
'REPORTS',
@ -463,6 +480,7 @@ def _supervisor_config(
report_port,
worker_report_ports,
report_clients,
bridge_enabled,
):
parts = [
'[supervisord]\n'
@ -488,15 +506,18 @@ def _supervisor_config(
20,
)
)
if bridge_enabled:
parts.append(
_program(
'bridge',
'python /opt/freedmr/bridge.py -c {} -r {}'.format(
shlex.quote(str(bridge_path)),
shlex.quote(str(rules_path)),
),
30,
)
)
parts.extend([
_program(
'bridge',
'python /opt/freedmr/bridge.py -c {} -r {}'.format(
shlex.quote(str(bridge_path)),
shlex.quote(str(rules_path)),
),
30,
),
_program(
'playback',
'/opt/freedmr/playback.py -c /opt/freedmr/loro.cfg',
@ -544,6 +565,7 @@ def main(argv=None):
parser.add_argument('--output-dir', required=True)
parser.add_argument('--instances', required=True, type=int)
parser.add_argument('--base-id', required=True, type=int)
parser.add_argument('--bridge-enabled', choices=('0', '1'), default='1')
args = parser.parse_args(argv)
try:
generate_hdstack(
@ -553,6 +575,7 @@ def main(argv=None):
args.output_dir,
args.instances,
args.base_id,
bridge_enabled=args.bridge_enabled == '1',
)
except HDStackConfigurationError as err:
parser.error(str(err))

@ -23,7 +23,7 @@ class HDStackRuntimeTests(unittest.TestCase):
self.bridge_config.write_text(BRIDGE_CONFIG, encoding='utf-8')
self.bridge_rules.write_text('BRIDGES = {}\n', encoding='utf-8')
def generate(self, instances=2, base_id=23400):
def generate(self, instances=2, base_id=23400, bridge_enabled=True):
return hdstack_runtime.generate_hdstack(
self.main_config,
self.bridge_config,
@ -31,6 +31,7 @@ class HDStackRuntimeTests(unittest.TestCase):
self.root / 'generated',
instances,
base_id,
bridge_enabled=bridge_enabled,
)
def test_two_worker_layout_uses_base_id_plus_one_and_disjoint_ranges(self):
@ -183,6 +184,24 @@ class HDStackRuntimeTests(unittest.TestCase):
supervisor,
)
def test_bridge_can_be_disabled_without_bridge_files(self):
self.bridge_config.unlink()
self.bridge_rules.unlink()
layout = self.generate(bridge_enabled=False)
supervisor = layout.supervisor_config.read_text(encoding='utf-8')
self.assertNotIn('[program:bridge]', supervisor)
for program in (
'hdstack-aggregator',
'hdstack-worker-1',
'hdstack-worker-2',
'playback',
'proxy',
'report-mux',
):
self.assertIn('[program:{}]'.format(program), supervisor)
def test_reporting_disabled_omits_mux_and_worker_reporting(self):
self.main_config.write_text(
MAIN_CONFIG.replace('REPORT: True', 'REPORT: False'),
@ -251,6 +270,9 @@ class HDStackRuntimeTests(unittest.TestCase):
with self.assertRaises(hdstack_runtime.HDStackConfigurationError):
self.generate()
with self.assertRaises(hdstack_runtime.HDStackConfigurationError):
self.generate(bridge_enabled=1)
def test_missing_bridge_files_and_port_collisions_fail_before_generation(self):
self.bridge_rules.unlink()
with self.assertRaises(hdstack_runtime.HDStackConfigurationError):
@ -274,6 +296,8 @@ class HDStackContainerTests(unittest.TestCase):
self.assertIn('[ "$HDSTACK_COUNT" -ge 2 ]', entrypoint)
self.assertIn('[ "$HDSTACK_COUNT" -le 5 ]', entrypoint)
self.assertIn('--base-id "$HDSTACK_BASEID"', entrypoint)
self.assertIn('HDSTACK_BRIDGE="${HDSTACK_BRIDGE:-1}"', entrypoint)
self.assertIn('--bridge-enabled "$HDSTACK_BRIDGE"', entrypoint)
self.assertIn('HDSTACK_BRIDGE_CONFIG', entrypoint)
self.assertIn('HDSTACK_BRIDGE_RULES', entrypoint)
@ -282,6 +306,7 @@ class HDStackContainerTests(unittest.TestCase):
self.assertIn('#- HDSTACK=2', compose)
self.assertIn('#- HDSTACK_BASEID=23400', compose)
self.assertIn('#- HDSTACK_BRIDGE=0', compose)
self.assertIn('#- HDSTACK_BRIDGE_CONFIG=', compose)
self.assertIn('#- HDSTACK_BRIDGE_RULES=', compose)
self.assertIn(

Loading…
Cancel
Save

Powered by TurnKey Linux.