Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update collections of JS modules in one proposal #1557

Merged
merged 3 commits into from
Sep 2, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions python/ccf/proposal_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import collections
import inspect
import json
import glob
import os
import sys
from pathlib import PurePosixPath
Expand Down Expand Up @@ -294,6 +295,40 @@ def remove_module(module_name: str, **kwargs):
return build_proposal("remove_module", module_name, **kwargs)


@cli_proposal
def update_modules(module_name_prefix: str, modules_path: Optional[str], **kwargs):
LOG.debug("Generating update_modules proposal")

# Validate module name prefix
module_name_prefix_ = PurePosixPath(module_name_prefix)
if not module_name_prefix_.is_absolute():
raise ValueError("module name prefix must be an absolute path")
if any(folder in [".", ".."] for folder in module_name_prefix_.parents):
raise ValueError("module name prefix must not contain . or .. components")
if not module_name_prefix.endswith("/"):
raise ValueError("module name prefix must end with /")

# Read module files and build relative module names
modules = []
if modules_path:
for path in glob.glob(f"{modules_path}/**/*.js", recursive=True):
rel_module_name = os.path.relpath(path, modules_path)
rel_module_name = rel_module_name.replace("\\", "/") # Windows support
with open(path) as f:
js = f.read()
modules.append({"rel_name": rel_module_name, "module": {"js": js}})

proposal_args = {"prefix": module_name_prefix, "modules": modules}

return build_proposal("update_modules", proposal_args, **kwargs)


@cli_proposal
def remove_modules(module_name_prefix: str, **kwargs):
LOG.debug("Generating update_modules proposal (remove only)")
return update_modules(module_name_prefix, modules_path=None)


@cli_proposal
def trust_node(node_id: int, **kwargs):
return build_proposal("trust_node", node_id, **kwargs)
Expand Down
23 changes: 23 additions & 0 deletions src/runtime_config/gov.lua
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,27 @@ return {
end
end
return true]],

update_modules = [[
tables, args = ...
function starts_with(str, start)
return str:sub(1, #start) == start
end
function remove_modules_with_prefix(prefix)
tables["ccf.modules"]:foreach(function(module_name, _)
if starts_with(module_name, prefix) then
tables["ccf.modules"]:remove(module_name)
end
end)
end
function add_modules_with_prefix(prefix, modules)
for _, module in pairs(modules) do
module_name = prefix .. module.rel_name
tables["ccf.modules"]:put(module_name, module.module)
end
end
remove_modules_with_prefix(args.prefix)
add_modules_with_prefix(args.prefix, args.modules)
return true
]]
}
53 changes: 42 additions & 11 deletions tests/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import http
import subprocess
import os
import glob
import infra.network
import infra.path
import infra.proc
Expand All @@ -18,6 +17,7 @@

THIS_DIR = os.path.dirname(__file__)

MODULE_PREFIX_1 = "/app/"
MODULE_PATH_1 = "/app/foo.js"
MODULE_RETURN_1 = "Hello world!"
MODULE_CONTENT_1 = f"""
Expand Down Expand Up @@ -85,7 +85,7 @@ def make_module_set_proposal(path, content, network):
def test_module_set_and_remove(network, args):
primary, _ = network.find_nodes()

LOG.info("Member makes a module update proposal")
LOG.info("Member makes a module set proposal")
make_module_set_proposal(MODULE_PATH_1, MODULE_CONTENT_1, network)

with primary.client(
Expand All @@ -110,6 +110,35 @@ def test_module_set_and_remove(network, args):
return network


@reqs.description("Test prefix-based modules remove")
def test_modules_remove(network, args):
primary, _ = network.find_nodes()

LOG.info("Member makes a module set proposal")
make_module_set_proposal(MODULE_PATH_1, MODULE_CONTENT_1, network)

with primary.client(
f"member{network.consortium.get_any_active_member().member_id}"
) as c:
r = c.post("/gov/read", {"table": "ccf.modules", "key": MODULE_PATH_1})
assert r.status_code == http.HTTPStatus.OK, r.status_code
assert r.body["js"] == MODULE_CONTENT_1, r.body

LOG.info("Member makes a prefix-based modules remove proposal")
proposal_body, _ = ccf.proposal_generator.remove_modules(MODULE_PREFIX_1)
proposal = network.consortium.get_any_active_member().propose(
primary, proposal_body
)
network.consortium.vote_using_majority(primary, proposal)

with primary.client(
f"member{network.consortium.get_any_active_member().member_id}"
) as c:
r = c.post("/gov/read", {"table": "ccf.modules", "key": MODULE_PATH_1})
assert r.status_code == http.HTTPStatus.BAD_REQUEST, r.status_code
return network


@reqs.description("Test module import")
def test_module_import(network, args):
primary, _ = network.find_nodes()
Expand All @@ -132,7 +161,7 @@ def test_module_import(network, args):
return network


@reqs.description("Test Node.js/npm app")
@reqs.description("Test Node.js/npm app with prefix-based modules update")
def test_npm_app(network, args):
primary, _ = network.find_nodes()

Expand All @@ -142,15 +171,16 @@ def test_npm_app(network, args):
subprocess.run(["npm", "run", "build"], cwd=app_dir, check=True)

LOG.info("Deploying npm app modules")
kv_prefix = "/my-npm-app"
module_name_prefix = "/my-npm-app/"
dist_dir = os.path.join(app_dir, "dist")
for module_path in glob.glob(os.path.join(dist_dir, "**", "*.js"), recursive=True):
module_name = os.path.join(kv_prefix, os.path.relpath(module_path, dist_dir))
proposal_body, _ = ccf.proposal_generator.set_module(module_name, module_path)
proposal = network.consortium.get_any_active_member().propose(
primary, proposal_body
)
network.consortium.vote_using_majority(primary, proposal)

proposal_body, _ = ccf.proposal_generator.update_modules(
module_name_prefix, dist_dir
)
proposal = network.consortium.get_any_active_member().propose(
primary, proposal_body
)
network.consortium.vote_using_majority(primary, proposal)

LOG.info("Deploying endpoint script")
with tempfile.NamedTemporaryFile("w") as f:
Expand Down Expand Up @@ -186,6 +216,7 @@ def run(args):
) as network:
network.start_and_join(args)
network = test_module_set_and_remove(network, args)
network = test_modules_remove(network, args)
network = test_module_import(network, args)
network = test_npm_app(network, args)

Expand Down