forked from hyperledger/indy-node
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request hyperledger#1129 from ArtObr/indy_1859
INDY-1859: Config handler implementation and test
- Loading branch information
Showing
5 changed files
with
311 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from indy_node.server.pool_config import PoolConfig | ||
|
||
from indy_common.constants import CONFIG_LEDGER_ID | ||
from indy_node.server.upgrader import Upgrader | ||
from plenum.common.txn_util import is_forced | ||
from plenum.server.batch_handlers.batch_request_handler import BatchRequestHandler | ||
from plenum.server.database_manager import DatabaseManager | ||
|
||
|
||
class ConfigBatchHandler(BatchRequestHandler): | ||
def __init__(self, database_manager: DatabaseManager, | ||
upgrader: Upgrader, pool_config: PoolConfig): | ||
super().__init__(database_manager, CONFIG_LEDGER_ID) | ||
self.upgrader = upgrader | ||
self.pool_config = pool_config | ||
|
||
def commit_batch(self, txn_count, state_root, txn_root, pp_time, prev_result): | ||
committed_txns = super().commit_batch(txn_count, state_root, txn_root, pp_time, prev_result) | ||
for txn in committed_txns: | ||
# Handle POOL_UPGRADE or POOL_CONFIG transaction here | ||
# only in case it is not forced. | ||
# If it is forced then it was handled earlier | ||
# in applyForced method. | ||
if not is_forced(txn): | ||
self.upgrader.handleUpgradeTxn(txn) | ||
self.pool_config.handleConfigTxn(txn) | ||
return committed_txns |
14 changes: 14 additions & 0 deletions
14
indy_node/server/request_handlers/config_req_handlers/config_write_request_handler.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from indy_common.types import Request | ||
from plenum.common.txn_util import append_txn_metadata | ||
from plenum.server.request_handlers.handler_interfaces.write_request_handler import WriteRequestHandler | ||
|
||
|
||
class ConfigWriteRequestHandler(WriteRequestHandler): | ||
|
||
def apply_request(self, request: Request, batch_ts, prev_result): | ||
self._validate_request_type(request) | ||
txn = append_txn_metadata(self._req_to_txn(request), | ||
txn_time=batch_ts) | ||
self.ledger.append_txns_metadata([txn]) | ||
(start, _), _ = self.ledger.appendTxns([txn]) | ||
return start, txn |
44 changes: 41 additions & 3 deletions
44
indy_node/server/request_handlers/config_req_handlers/pool_config_handler.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,43 @@ | ||
from plenum.server.request_handlers.handler_interfaces.write_request_handler import WriteRequestHandler | ||
from indy_node.server.pool_config import PoolConfig | ||
|
||
from indy_common.authorize.auth_actions import AuthActionEdit | ||
from indy_common.authorize.auth_request_validator import WriteRequestValidator | ||
from indy_common.constants import POOL_CONFIG, CONFIG_LEDGER_ID, ACTION | ||
from indy_node.server.request_handlers.config_req_handlers.config_write_request_handler import ConfigWriteRequestHandler | ||
from plenum.common.request import Request | ||
from plenum.server.database_manager import DatabaseManager | ||
|
||
class PoolConfigHandler(WriteRequestHandler): | ||
pass | ||
|
||
class PoolConfigHandler(ConfigWriteRequestHandler): | ||
|
||
def __init__(self, database_manager: DatabaseManager, | ||
write_request_validator: WriteRequestValidator, | ||
pool_config: PoolConfig): | ||
super().__init__(database_manager, POOL_CONFIG, CONFIG_LEDGER_ID) | ||
self.write_request_validator = write_request_validator | ||
self.pool_config = pool_config | ||
|
||
def static_validation(self, request: Request): | ||
self._validate_request_type(request) | ||
|
||
def dynamic_validation(self, request: Request): | ||
self._validate_request_type(request) | ||
action = '*' | ||
status = '*' | ||
self.write_request_validator.validate(request, | ||
[AuthActionEdit(txn_type=self.txn_type, | ||
field=ACTION, | ||
old_value=status, | ||
new_value=action)]) | ||
|
||
def apply_forced_request(self, req: Request): | ||
super().apply_forced_request(req) | ||
txn = self._req_to_txn(req) | ||
self.pool_config.handleConfigTxn(txn) | ||
|
||
# Config handler don't use state for any validation for now | ||
def update_state(self, txn, prev_result, is_committed=False): | ||
pass | ||
|
||
def gen_state_key(self, txn): | ||
pass |
120 changes: 117 additions & 3 deletions
120
indy_node/server/request_handlers/config_req_handlers/pool_upgrade_handler.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,119 @@ | ||
from plenum.server.request_handlers.handler_interfaces.write_request_handler import WriteRequestHandler | ||
from indy_common.authorize.auth_actions import AuthActionAdd, AuthActionEdit | ||
from indy_node.server.request_handlers.config_req_handlers.config_write_request_handler import ConfigWriteRequestHandler | ||
from indy_node.utils.node_control_utils import NodeControlUtil | ||
|
||
from indy_common.config_util import getConfig | ||
|
||
class PoolUpgrade(WriteRequestHandler): | ||
pass | ||
from indy_common.constants import CONFIG_LEDGER_ID, POOL_UPGRADE, \ | ||
ACTION, CANCEL, START, SCHEDULE, PACKAGE, APP_NAME, REINSTALL | ||
|
||
from indy_common.authorize.auth_request_validator import WriteRequestValidator | ||
from indy_node.server.upgrader import Upgrader | ||
from plenum.common.constants import FORCE, VERSION, NAME | ||
from plenum.common.exceptions import InvalidClientRequest | ||
from plenum.common.request import Request | ||
from plenum.common.txn_util import get_request_data, get_payload_data | ||
from plenum.server.database_manager import DatabaseManager | ||
from plenum.server.pool_manager import TxnPoolManager | ||
|
||
|
||
class PoolUpgradeHandler(ConfigWriteRequestHandler): | ||
|
||
def __init__(self, database_manager: DatabaseManager, | ||
upgrader: Upgrader, | ||
write_request_validator: WriteRequestValidator, | ||
pool_manager: TxnPoolManager): | ||
super().__init__(database_manager, POOL_UPGRADE, CONFIG_LEDGER_ID) | ||
self.upgrader = upgrader | ||
self.write_request_validator = write_request_validator | ||
self.pool_manager = pool_manager | ||
|
||
def static_validation(self, request: Request): | ||
self._validate_request_type(request) | ||
identifier, req_id, operation = get_request_data(request) | ||
action = operation.get(ACTION) | ||
if action not in (START, CANCEL): | ||
raise InvalidClientRequest(identifier, req_id, | ||
"{} not a valid action". | ||
format(action)) | ||
if action == START: | ||
schedule = operation.get(SCHEDULE, {}) | ||
force = operation.get(FORCE) | ||
force = str(force) == 'True' | ||
isValid, msg = self.upgrader.isScheduleValid( | ||
schedule, self.pool_manager.getNodesServices(), force) | ||
if not isValid: | ||
raise InvalidClientRequest(identifier, req_id, | ||
"{} not a valid schedule since {}". | ||
format(schedule, msg)) | ||
|
||
def dynamic_validation(self, request: Request): | ||
self._validate_request_type(request) | ||
identifier, req_id, operation = get_request_data(request) | ||
status = '*' | ||
pkt_to_upgrade = operation.get(PACKAGE, getConfig().UPGRADE_ENTRY) | ||
if pkt_to_upgrade: | ||
currentVersion, cur_deps = self.curr_pkt_info(pkt_to_upgrade) | ||
if not currentVersion: | ||
raise InvalidClientRequest(identifier, req_id, | ||
"Packet {} is not installed and cannot be upgraded". | ||
format(pkt_to_upgrade)) | ||
if all([APP_NAME not in d for d in cur_deps]): | ||
raise InvalidClientRequest(identifier, req_id, | ||
"Packet {} doesn't belong to pool".format(pkt_to_upgrade)) | ||
else: | ||
raise InvalidClientRequest(identifier, req_id, "Upgrade packet name is empty") | ||
|
||
targetVersion = operation[VERSION] | ||
reinstall = operation.get(REINSTALL, False) | ||
if not Upgrader.is_version_upgradable(currentVersion, targetVersion, reinstall): | ||
# currentVersion > targetVersion | ||
raise InvalidClientRequest(identifier, req_id, "Version is not upgradable") | ||
|
||
action = operation.get(ACTION) | ||
# TODO: Some validation needed for making sure name and version | ||
# present | ||
txn = self.upgrader.get_upgrade_txn( | ||
lambda txn: get_payload_data(txn).get( | ||
NAME, | ||
None) == operation.get( | ||
NAME, | ||
None) and get_payload_data(txn).get(VERSION) == operation.get(VERSION), | ||
reverse=True) | ||
if txn: | ||
status = get_payload_data(txn).get(ACTION, '*') | ||
|
||
if status == START and action == START: | ||
raise InvalidClientRequest( | ||
identifier, | ||
req_id, | ||
"Upgrade '{}' is already scheduled".format( | ||
operation.get(NAME))) | ||
if status == '*': | ||
auth_action = AuthActionAdd(txn_type=POOL_UPGRADE, | ||
field=ACTION, | ||
value=action) | ||
else: | ||
auth_action = AuthActionEdit(txn_type=POOL_UPGRADE, | ||
field=ACTION, | ||
old_value=status, | ||
new_value=action) | ||
self.write_request_validator.validate(request, | ||
[auth_action]) | ||
|
||
def apply_forced_request(self, req: Request): | ||
super().apply_forced_request(req) | ||
txn = self._req_to_txn(req) | ||
self.upgrader.handleUpgradeTxn(txn) | ||
|
||
def curr_pkt_info(self, pkg_name): | ||
if pkg_name == APP_NAME: | ||
return Upgrader.getVersion(), [APP_NAME] | ||
return NodeControlUtil.curr_pkt_info(pkg_name) | ||
|
||
# Config handler don't use state for any validation for now | ||
def update_state(self, txn, prev_result, is_committed=False): | ||
pass | ||
|
||
def gen_state_key(self, txn): | ||
pass |
112 changes: 112 additions & 0 deletions
112
indy_node/test/request_handlers/test_pool_upgrade_handler.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import pytest | ||
from indy_common.constants import POOL_UPGRADE, ACTION, START, PACKAGE, APP_NAME, REINSTALL | ||
from indy_node.server.request_handlers.config_req_handlers.pool_upgrade_handler import PoolUpgradeHandler | ||
from plenum.common.constants import VERSION, TXN_PAYLOAD, TXN_PAYLOAD_DATA | ||
from plenum.common.exceptions import InvalidClientRequest | ||
|
||
from plenum.common.request import Request | ||
from plenum.common.util import randomString | ||
from plenum.test.testing_utils import FakeSomething | ||
|
||
|
||
@pytest.fixture(scope='function') | ||
def pool_upgrade_request(): | ||
return Request(identifier=randomString(), | ||
reqId=5, | ||
operation={ | ||
'type': POOL_UPGRADE, | ||
ACTION: START, | ||
PACKAGE: 'smth' | ||
}) | ||
|
||
|
||
@pytest.fixture(scope='function') | ||
def pool_upgrade_handler(): | ||
return PoolUpgradeHandler(None, | ||
FakeSomething(), | ||
FakeSomething(), | ||
FakeSomething()) | ||
|
||
|
||
def test_pool_upgrade_static_validation_fails_action(pool_upgrade_handler, | ||
pool_upgrade_request): | ||
pool_upgrade_request.operation[ACTION] = 'smth' | ||
with pytest.raises(InvalidClientRequest) as e: | ||
pool_upgrade_handler.static_validation(pool_upgrade_request) | ||
e.match('not a valid action') | ||
|
||
|
||
def test_pool_upgrade_static_validation_fails_schedule(pool_upgrade_handler, | ||
pool_upgrade_request): | ||
pool_upgrade_handler.pool_manager.getNodesServices = lambda: 1 | ||
pool_upgrade_handler.upgrader.isScheduleValid = lambda schedule, node_srvs, force: (False, '') | ||
with pytest.raises(InvalidClientRequest) as e: | ||
pool_upgrade_handler.static_validation(pool_upgrade_request) | ||
e.match('not a valid schedule since') | ||
|
||
|
||
def test_pool_upgrade_static_validation_passes(pool_upgrade_handler, | ||
pool_upgrade_request): | ||
pool_upgrade_handler.pool_manager.getNodesServices = lambda: 1 | ||
pool_upgrade_handler.upgrader.isScheduleValid = lambda schedule, node_srvs, force: (True, '') | ||
pool_upgrade_handler.static_validation(pool_upgrade_request) | ||
|
||
|
||
def test_pool_upgrade_dynamic_validation_fails_pckg(pool_upgrade_handler, | ||
pool_upgrade_request): | ||
pool_upgrade_request.operation[PACKAGE] = '' | ||
with pytest.raises(InvalidClientRequest) as e: | ||
pool_upgrade_handler.dynamic_validation(pool_upgrade_request) | ||
e.match('Upgrade packet name is empty') | ||
|
||
|
||
def test_pool_upgrade_dynamic_validation_fails_not_installed(pool_upgrade_handler, | ||
pool_upgrade_request): | ||
pool_upgrade_handler.curr_pkt_info = lambda pkt: (None, None) | ||
with pytest.raises(InvalidClientRequest) as e: | ||
pool_upgrade_handler.dynamic_validation(pool_upgrade_request) | ||
e.match('is not installed and cannot be upgraded') | ||
|
||
|
||
def test_pool_upgrade_dynamic_validation_fails_belong(pool_upgrade_handler, | ||
pool_upgrade_request): | ||
pool_upgrade_handler.curr_pkt_info = lambda pkt: ('1.1.1', ['some_pckg']) | ||
with pytest.raises(InvalidClientRequest) as e: | ||
pool_upgrade_handler.dynamic_validation(pool_upgrade_request) | ||
e.match('doesn\'t belong to pool') | ||
|
||
|
||
def test_pool_upgrade_dynamic_validation_fails_upgradable(pool_upgrade_handler, | ||
pool_upgrade_request): | ||
pool_upgrade_handler.curr_pkt_info = lambda pkt: ('1.1.1', [APP_NAME]) | ||
pool_upgrade_request.operation[VERSION] = '1.1.1' | ||
pool_upgrade_request.operation[REINSTALL] = False | ||
with pytest.raises(InvalidClientRequest) as e: | ||
pool_upgrade_handler.dynamic_validation(pool_upgrade_request) | ||
e.match('Version is not upgradable') | ||
|
||
|
||
def test_pool_upgrade_dynamic_validation_fails_scheduled(pool_upgrade_handler, | ||
pool_upgrade_request): | ||
pool_upgrade_handler.curr_pkt_info = lambda pkt: ('1.1.1', [APP_NAME]) | ||
pool_upgrade_request.operation[VERSION] = '1.1.1' | ||
pool_upgrade_request.operation[REINSTALL] = True | ||
pool_upgrade_handler.upgrader.get_upgrade_txn = \ | ||
lambda predicate, reverse: \ | ||
{TXN_PAYLOAD: {TXN_PAYLOAD_DATA: {ACTION: START}}} | ||
|
||
with pytest.raises(InvalidClientRequest) as e: | ||
pool_upgrade_handler.dynamic_validation(pool_upgrade_request) | ||
e.match('is already scheduled') | ||
|
||
|
||
def test_pool_upgrade_dynamic_validation_passes(pool_upgrade_handler, | ||
pool_upgrade_request): | ||
pool_upgrade_handler.curr_pkt_info = lambda pkt: ('1.1.1', [APP_NAME]) | ||
pool_upgrade_request.operation[VERSION] = '1.1.1' | ||
pool_upgrade_request.operation[REINSTALL] = True | ||
pool_upgrade_handler.upgrader.get_upgrade_txn = \ | ||
lambda predicate, reverse: \ | ||
{TXN_PAYLOAD: {TXN_PAYLOAD_DATA: {}}} | ||
pool_upgrade_handler.write_request_validator.validate = lambda a, b: 0 | ||
pool_upgrade_handler.dynamic_validation(pool_upgrade_request) |