Skip to content

Commit 59b06e4

Browse files
authored
Deeploy chainstore response peers (#427)
* docs: update chainstore response mixin comments * fix: restrict deeploy response reset peers * refactor: delegate deeploy response resets to mixin * chore: increment version * test: resolve deeploy chainstore mixin source path * test: load renamed deeploy chainstore mixin * chore: increment version
1 parent cc32fc1 commit 59b06e4

5 files changed

Lines changed: 166 additions & 18 deletions

File tree

‎extensions/business/container_apps/container_utils.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,14 @@ def _get_chainstore_response_data(self):
145145
Returns:
146146
dict: Response data including container details, ports, and timing info.
147147
"""
148-
# Start with base plugin data (from _ChainstoreResponseMixin)
149-
# Note: Since this mixin is used alongside _ChainstoreResponseMixin,
148+
# Start with base plugin data (from _DeeployChainstoreResponseMixin)
149+
# Note: Since this mixin is used alongside _DeeployChainstoreResponseMixin,
150150
# we should check if super() provides base data
151151
try:
152-
# Try to get base data if _ChainstoreResponseMixin is in the MRO
152+
# Try to get base data if _DeeployChainstoreResponseMixin is in the MRO
153153
data = super()._get_chainstore_response_data()
154154
except (AttributeError, TypeError):
155-
# Fallback if _ChainstoreResponseMixin is not in the inheritance chain
155+
# Fallback if _DeeployChainstoreResponseMixin is not in the inheritance chain
156156
data = {
157157
'plugin_signature': self.__class__.__name__,
158158
'instance_id': getattr(self, 'cfg_instance_id', None),

‎extensions/business/deeploy/deeploy_mixin.py‎

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,13 +1547,10 @@ def _reset_chainstore_response_keys(self, response_keys, context: str = "pipelin
15471547
return normalized_keys
15481548

15491549
self.P(f"Resetting response keys in chainstore before dispatching {context}...")
1550+
reset_kwargs = self._get_chainstore_response_local_reset_write_kwargs()
15501551
for _, node_response_keys in normalized_keys.items():
15511552
for response_key in node_response_keys:
1552-
try:
1553-
self.chainstore_set(response_key, None)
1554-
except Exception as e:
1555-
self.P(f"Error resetting response key {response_key} in chainstore: {e}", color='r')
1556-
# end try
1553+
self._reset_chainstore_response_key(response_key, write_kwargs=reset_kwargs)
15571554
# end for
15581555
# end for
15591556

@@ -2830,13 +2827,11 @@ def scale_up_job(self, new_nodes, update_nodes, job_id, owner, running_apps_for_
28302827
self.P(f"Prepared chainstore response keys: {self.json_dumps(chainstore_response_keys)}")
28312828

28322829
# RESET chainstore_response_keys here
2833-
try:
2834-
self.P(f"Resetting chainstore keys: {self.json_dumps(chainstore_response_keys)}")
2835-
for node_addr, response_keys in chainstore_response_keys.items():
2836-
for response_key in response_keys:
2837-
self.chainstore_set(response_key, None)
2838-
except Exception as e:
2839-
self.P(f"Error resetting chainstore keys: {e}", color='r')
2830+
self.P(f"Resetting chainstore keys: {self.json_dumps(chainstore_response_keys)}")
2831+
self._reset_chainstore_response_keys(
2832+
chainstore_response_keys,
2833+
context=f"scale up job {job_id}",
2834+
)
28402835

28412836
# Start pipelines on nodes.
28422837
self._start_create_update_pipelines(create_pipelines=create_pipelines,

‎extensions/business/deeploy/tests/support.py‎

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import copy
2+
import importlib.util
3+
from pathlib import Path
24
import sys
35
import types
46
from types import SimpleNamespace
@@ -13,6 +15,83 @@
1315
from extensions.business.deeploy.deeploy_mixin import _DeeployMixin
1416

1517

18+
def _load_deeploy_chainstore_response_mixin():
19+
"""
20+
Load only the chainstore-response mixin module for lightweight Deeploy tests.
21+
22+
Importing `naeural_core.business.mixins_base` executes package-level imports
23+
that require optional runtime dependencies such as cv2, which are unrelated
24+
to these focused tests.
25+
"""
26+
checked_paths = []
27+
for module_path in _iter_deeploy_chainstore_response_mixin_paths():
28+
if module_path in checked_paths:
29+
continue
30+
checked_paths.append(module_path)
31+
if not module_path.is_file():
32+
continue
33+
spec = importlib.util.spec_from_file_location(
34+
"deeploy_chainstore_response_mixin_for_tests",
35+
module_path,
36+
)
37+
module = importlib.util.module_from_spec(spec)
38+
spec.loader.exec_module(module)
39+
if hasattr(module, "_DeeployChainstoreResponseMixin"):
40+
return module._DeeployChainstoreResponseMixin
41+
42+
formatted_paths = "\n".join(f"- {path}" for path in checked_paths)
43+
raise FileNotFoundError(
44+
"Could not locate _DeeployChainstoreResponseMixin for Deeploy tests. "
45+
f"Checked:\n{formatted_paths}"
46+
)
47+
48+
49+
def _iter_deeploy_chainstore_response_mixin_paths():
50+
"""
51+
Yield likely source paths for the installed, nested, or sibling naeural_core.
52+
"""
53+
constants_file = getattr(ct, "__file__", None)
54+
if constants_file:
55+
yield (
56+
Path(constants_file).resolve().parent
57+
/ "business"
58+
/ "mixins_base"
59+
/ "deeploy_chainstore_response_mixin.py"
60+
)
61+
62+
edge_root = Path(__file__).resolve().parents[4]
63+
yield (
64+
edge_root
65+
/ "naeural_core"
66+
/ "naeural_core"
67+
/ "business"
68+
/ "mixins_base"
69+
/ "deeploy_chainstore_response_mixin.py"
70+
)
71+
yield (
72+
edge_root.parent
73+
/ "naeural_core"
74+
/ "naeural_core"
75+
/ "business"
76+
/ "mixins_base"
77+
/ "deeploy_chainstore_response_mixin.py"
78+
)
79+
80+
for entry in sys.path:
81+
if not entry:
82+
continue
83+
yield (
84+
Path(entry).resolve()
85+
/ "naeural_core"
86+
/ "business"
87+
/ "mixins_base"
88+
/ "deeploy_chainstore_response_mixin.py"
89+
)
90+
91+
92+
_DeeployChainstoreResponseMixin = _load_deeploy_chainstore_response_mixin()
93+
94+
1695
class InputsStub(dict):
1796
def __getattr__(self, item):
1897
try:
@@ -21,7 +100,7 @@ def __getattr__(self, item):
21100
raise AttributeError(item) from exc
22101

23102

24-
class _TestDeeployPlugin(_DeeployMixin):
103+
class _TestDeeployPlugin(_DeeployMixin, _DeeployChainstoreResponseMixin):
25104
pass
26105

27106

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import unittest
2+
3+
from extensions.business.deeploy.tests.support import make_deeploy_plugin
4+
5+
6+
class DeeployChainstoreResponseResetTests(unittest.TestCase):
7+
8+
def _make_plugin(self, seed_nodes=None, selected_seed=None):
9+
plugin = make_deeploy_plugin()
10+
plugin.ee_addr = "initiating-oracle"
11+
plugin.seed_nodes = seed_nodes or ["seed-oracle-1", "seed-oracle-2"]
12+
plugin.selected_seed = selected_seed or plugin.seed_nodes[0]
13+
plugin.chainstore_calls = []
14+
plugin._get_chainstore_response_seed_nodes = lambda: plugin.seed_nodes
15+
16+
def select_seed(seed_peers):
17+
plugin.seed_selection_input = list(seed_peers)
18+
if plugin.selected_seed in seed_peers:
19+
return plugin.selected_seed
20+
return seed_peers[0] if seed_peers else None
21+
22+
def chainstore_set(*args, **kwargs):
23+
plugin.chainstore_calls.append((args, kwargs))
24+
return True
25+
26+
plugin._select_chainstore_response_seed_peer = select_seed
27+
plugin.chainstore_set = chainstore_set
28+
return plugin
29+
30+
def test_reset_chainstore_response_keys_targets_one_seed_oracle_only(self):
31+
plugin = self._make_plugin(selected_seed="seed-oracle-2")
32+
33+
result = plugin._reset_chainstore_response_keys({
34+
"target-chainstore-peer": ["response-key-1", "response-key-2"],
35+
})
36+
37+
self.assertEqual(result, {
38+
"target-chainstore-peer": ["response-key-1", "response-key-2"],
39+
})
40+
self.assertEqual(len(plugin.chainstore_calls), 2)
41+
for args, kwargs in plugin.chainstore_calls:
42+
self.assertEqual(args[1], None)
43+
self.assertEqual(kwargs["extra_peers"], ["seed-oracle-2"])
44+
self.assertEqual(kwargs["include_default_peers"], False)
45+
self.assertEqual(kwargs["include_configured_peers"], False)
46+
self.assertEqual(kwargs["debug"], True)
47+
48+
def test_reset_peer_selection_excludes_current_oracle_when_possible(self):
49+
plugin = self._make_plugin(
50+
seed_nodes=["initiating-oracle", "seed-oracle-2"],
51+
selected_seed="seed-oracle-2",
52+
)
53+
54+
peers = plugin._get_chainstore_response_local_reset_peers()
55+
56+
self.assertEqual(peers, ["seed-oracle-2"])
57+
self.assertEqual(plugin.seed_selection_input, ["seed-oracle-2"])
58+
59+
def test_reset_chainstore_response_keys_does_not_use_chainstore_peer_addresses(self):
60+
plugin = self._make_plugin(selected_seed="seed-oracle-1")
61+
62+
plugin._reset_chainstore_response_keys({
63+
"app-chainstore-peer-1": ["response-key-1"],
64+
"app-chainstore-peer-2": ["response-key-2"],
65+
})
66+
67+
for _, kwargs in plugin.chainstore_calls:
68+
self.assertEqual(kwargs["extra_peers"], ["seed-oracle-1"])
69+
self.assertNotIn("app-chainstore-peer-1", kwargs["extra_peers"])
70+
self.assertNotIn("app-chainstore-peer-2", kwargs["extra_peers"])
71+
72+
73+
if __name__ == "__main__":
74+
unittest.main()

‎ver.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__VER__ = '2.10.283'
1+
__VER__ = '2.10.284'

0 commit comments

Comments
 (0)