Compare commits

...
Author SHA1 Message Date
roysc 27ee95d0ff test stack does not need laconicd
Lint Checks / Run linter (pull_request) Successful in 13m27s
Deploy Test / Run deploy test suite (pull_request) Successful in 18m17s
K8s Deploy Test / Run deploy test suite on kind/k8s (pull_request) Successful in 22m7s
K8s Deployment Control Test / Run deployment control suite on kind/k8s (pull_request) Successful in 20m36s
Webapp Test / Run webapp test suite (pull_request) Successful in 20m25s
Smoke Test / Run basic test suite (pull_request) Successful in 19m33s
2025-10-19 14:36:49 +08:00
roysc 0aca6d0938 patch 2025-10-19 14:36:44 +08:00
roysc b1a489ef1b Add DeploymentContext.modify_yaml helper
Lint Checks / Run linter (pull_request) Successful in 3m37s
Deploy Test / Run deploy test suite (pull_request) Failing after 7m29s
K8s Deploy Test / Run deploy test suite on kind/k8s (pull_request) Failing after 8m48s
K8s Deployment Control Test / Run deployment control suite on kind/k8s (pull_request) Failing after 11m22s
Webapp Test / Run webapp test suite (pull_request) Successful in 14m39s
Smoke Test / Run basic test suite (pull_request) Successful in 17m17s
For custom deploy commands
2025-10-19 13:59:08 +08:00
roysc 8f92e2700c Pass extra args to custom create command 2025-10-19 02:08:19 +08:00
roysc 34f3b719e4 Path is not a context manager in python 3.13 (#971)
Publish / Build and publish (push) Successful in 15m24s
Deploy Test / Run deploy test suite (push) Successful in 17m26s
Webapp Test / Run webapp test suite (push) Successful in 19m19s
Smoke Test / Run basic test suite (push) Successful in 21m22s
Lint Checks / Run linter (push) Failing after 11s
Fixturenet-Laconicd-Test / Run Laconicd fixturenet and Laconic CLI tests (push) Successful in 13m22s
K8s Deploy Test / Run deploy test suite on kind/k8s (push) Successful in 4m33s
Database Test / Run database hosting test on kind/k8s (push) Successful in 4m47s
Container Registry Test / Run contaier registry hosting test on kind/k8s (push) Successful in 2m3s
External Stack Test / Run external stack test suite (push) Successful in 2m33s
Reviewed-on: #971
Reviewed-by: rachmaninovquar <rachmaninovquar@noreply.git.vdb.to>
2025-10-16 17:55:44 +00:00
nabarun 0e814bd4da Support custom build and run for NextJS app (#969)
Lint Checks / Run linter (push) Successful in 17s
Publish / Build and publish (push) Successful in 31s
Smoke Test / Run basic test suite (push) Successful in 1m54s
Webapp Test / Run webapp test suite (push) Successful in 2m2s
Deploy Test / Run deploy test suite (push) Successful in 2m48s
Database Test / Run database hosting test on kind/k8s (push) Successful in 7m53s
Container Registry Test / Run contaier registry hosting test on kind/k8s (push) Failing after 4m43s
External Stack Test / Run external stack test suite (push) Successful in 5m11s
Fixturenet-Laconicd-Test / Run Laconicd fixturenet and Laconic CLI tests (push) Successful in 16m15s
K8s Deploy Test / Run deploy test suite on kind/k8s (push) Successful in 7m21s
Part of https://www.notion.so/Laconic-Mainnet-Plan-1eca6b22d47280569cd0d1e6d711d949
For deploying frontend https://git.vdb.to/NasSharaf/laconic-deployer-frontend

Reviewed-on: #969
Reviewed-by: ashwin <ashwin@noreply.git.vdb.to>
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2025-08-20 12:26:11 +00:00
prathamesh 873a6d472c Update webapp deployment flow for supporting custom domains (#963)
Lint Checks / Run linter (push) Successful in 38s
Publish / Build and publish (push) Successful in 1m7s
Smoke Test / Run basic test suite (push) Successful in 4m1s
Webapp Test / Run webapp test suite (push) Successful in 4m44s
Deploy Test / Run deploy test suite (push) Successful in 4m44s
Fixturenet-Laconicd-Test / Run Laconicd fixturenet and Laconic CLI tests (push) Successful in 13m33s
K8s Deploy Test / Run deploy test suite on kind/k8s (push) Successful in 4m32s
Database Test / Run database hosting test on kind/k8s (push) Successful in 5m22s
Container Registry Test / Run contaier registry hosting test on kind/k8s (push) Successful in 2m18s
External Stack Test / Run external stack test suite (push) Successful in 2m32s
Part of https://www.notion.so/Support-custom-domains-in-deploy-laconic-com-18aa6b22d4728067a44ae27090c02ce5 and cerc-io/snowballtools-base#47

- Set `value` (IP address for `A` resource) in the DNS records
- Update `deploy-webapp-from-registry` command with an option to pass k8s cluster IP address (only required with fqdn policy `allow`)

Reviewed-on: #963
Reviewed-by: ashwin <ashwin@noreply.git.vdb.to>
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2025-02-04 13:26:31 +00:00
prathamesh 39df4683ac Allow payment reuse for same app LRN (#961)
Lint Checks / Run linter (push) Successful in 33s
Publish / Build and publish (push) Successful in 1m12s
Deploy Test / Run deploy test suite (push) Successful in 4m54s
Smoke Test / Run basic test suite (push) Successful in 3m52s
Webapp Test / Run webapp test suite (push) Successful in 4m38s
K8s Deploy Test / Run deploy test suite on kind/k8s (push) Successful in 8m21s
Database Test / Run database hosting test on kind/k8s (push) Successful in 9m53s
Container Registry Test / Run contaier registry hosting test on kind/k8s (push) Successful in 3m58s
External Stack Test / Run external stack test suite (push) Successful in 4m51s
Fixturenet-Laconicd-Test / Run Laconicd fixturenet and Laconic CLI tests (push) Successful in 19m22s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Reviewed-on: #961
Reviewed-by: ashwin <ashwin@noreply.git.vdb.to>
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-10-29 11:30:03 +00:00
prathamesh 23ca4c4341 Allow payment reuse for application redeployment (#960)
Lint Checks / Run linter (push) Successful in 39s
Publish / Build and publish (push) Successful in 1m10s
Smoke Test / Run basic test suite (push) Successful in 3m54s
Webapp Test / Run webapp test suite (push) Successful in 4m40s
Deploy Test / Run deploy test suite (push) Successful in 4m51s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Reviewed-on: #960
Reviewed-by: ashwin <ashwin@noreply.git.vdb.to>
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-10-29 06:51:48 +00:00
prathamesh f64ef5d128 Use file existence for registry mutex (#959)
Lint Checks / Run linter (push) Successful in 1m1s
Publish / Build and publish (push) Successful in 1m27s
Webapp Test / Run webapp test suite (push) Successful in 4m59s
Smoke Test / Run basic test suite (push) Successful in 4m10s
Deploy Test / Run deploy test suite (push) Successful in 5m33s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Reviewed-on: #959
Reviewed-by: ashwin <ashwin@noreply.git.vdb.to>
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-10-29 04:05:35 +00:00
prathamesh 5f8e809b2d Add mutex lock file path to registry CLI wrapper class (#958)
Lint Checks / Run linter (push) Successful in 33s
Publish / Build and publish (push) Successful in 1m24s
Deploy Test / Run deploy test suite (push) Successful in 4m53s
Webapp Test / Run webapp test suite (push) Successful in 4m39s
Smoke Test / Run basic test suite (push) Successful in 3m58s
Fixturenet-Laconicd-Test / Run Laconicd fixturenet and Laconic CLI tests (push) Successful in 19m38s
K8s Deploy Test / Run deploy test suite on kind/k8s (push) Successful in 6m59s
Database Test / Run database hosting test on kind/k8s (push) Successful in 9m59s
Container Registry Test / Run contaier registry hosting test on kind/k8s (push) Successful in 3m49s
External Stack Test / Run external stack test suite (push) Successful in 4m38s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)
Follows #957

Reviewed-on: #958
Reviewed-by: ashwin <ashwin@noreply.git.vdb.to>
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-10-28 06:03:13 +00:00
prathamesh 4a7df2de33 Use a mutex for registry CLI txs in webapp deployment commands (#957)
Lint Checks / Run linter (push) Successful in 37s
Publish / Build and publish (push) Successful in 1m19s
Webapp Test / Run webapp test suite (push) Successful in 4m45s
Smoke Test / Run basic test suite (push) Successful in 4m16s
Deploy Test / Run deploy test suite (push) Successful in 4m58s
Fixturenet-Laconicd-Test / Run Laconicd fixturenet and Laconic CLI tests (push) Successful in 19m17s
K8s Deploy Test / Run deploy test suite on kind/k8s (push) Successful in 7m33s
Database Test / Run database hosting test on kind/k8s (push) Successful in 9m41s
Container Registry Test / Run contaier registry hosting test on kind/k8s (push) Successful in 3m36s
External Stack Test / Run external stack test suite (push) Successful in 4m43s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75) and #948

- Add a registry mutex decorator over tx methods in `LaconicRegistryClient` wrapper
- Required to allow multiple process to run webapp deployment tooling without running into account sequence errors when sending laconicd txs

Reviewed-on: #957
Reviewed-by: ashwin <ashwin@noreply.git.vdb.to>
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-10-25 08:40:54 +00:00
17 changed files with 240 additions and 120 deletions

No files matched your search

@@ -26,8 +26,14 @@ fi
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
WORK_DIR="${1:-/app}"
if [ -f "${WORK_DIR}/build-webapp.sh" ]; then
echo "Building webapp with ${WORK_DIR}/build-webapp.sh ..."
cd "${WORK_DIR}" || exit 1
./build-webapp.sh || exit 1
exit 0
fi
if [ -f "next.config.mjs" ]; then
NEXT_CONFIG_JS="next.config.mjs"
IMPORT_OR_REQUIRE="import"
@@ -30,36 +30,44 @@ fi
CERC_WEBAPP_FILES_DIR="${CERC_WEBAPP_FILES_DIR:-/app}"
cd "$CERC_WEBAPP_FILES_DIR"
"$SCRIPT_DIR/apply-runtime-env.sh" "`pwd`" .next .next-r
mv .next .next.old
mv .next-r/.next .
if [ -f "./run-webapp.sh" ]; then
echo "Running webapp with run-webapp.sh ..."
cd "${WORK_DIR}" || exit 1
./run-webapp.sh &
tpid=$!
wait $tpid
else
"$SCRIPT_DIR/apply-runtime-env.sh" "`pwd`" .next .next-r
mv .next .next.old
mv .next-r/.next .
if [ "$CERC_NEXTJS_SKIP_GENERATE" != "true" ]; then
jq -e '.scripts.cerc_generate' package.json >/dev/null
if [ $? -eq 0 ]; then
npm run cerc_generate > gen.out 2>&1 &
tail -f gen.out &
tpid=$!
if [ "$CERC_NEXTJS_SKIP_GENERATE" != "true" ]; then
jq -e '.scripts.cerc_generate' package.json >/dev/null
if [ $? -eq 0 ]; then
npm run cerc_generate > gen.out 2>&1 &
tail -f gen.out &
tpid=$!
count=0
generate_done="false"
while [ $count -lt $CERC_MAX_GENERATE_TIME ] && [ "$generate_done" == "false" ]; do
sleep 1
count=$((count + 1))
grep 'rendered as static' gen.out > /dev/null
if [ $? -eq 0 ]; then
generate_done="true"
count=0
generate_done="false"
while [ $count -lt $CERC_MAX_GENERATE_TIME ] && [ "$generate_done" == "false" ]; do
sleep 1
count=$((count + 1))
grep 'rendered as static' gen.out > /dev/null
if [ $? -eq 0 ]; then
generate_done="true"
fi
done
if [ $generate_done != "true" ]; then
echo "ERROR: 'npm run cerc_generate' not successful within CERC_MAX_GENERATE_TIME" 1>&2
exit 1
fi
done
if [ $generate_done != "true" ]; then
echo "ERROR: 'npm run cerc_generate' not successful within CERC_MAX_GENERATE_TIME" 1>&2
exit 1
kill $tpid $(ps -ef | grep node | grep next | grep generate | awk '{print $2}') 2>/dev/null
tpid=""
fi
kill $tpid $(ps -ef | grep node | grep next | grep generate | awk '{print $2}') 2>/dev/null
tpid=""
fi
fi
$CERC_BUILD_TOOL start . -- -p ${CERC_LISTEN_PORT:-80}
$CERC_BUILD_TOOL start . -- -p ${CERC_LISTEN_PORT:-80}
fi
@@ -14,7 +14,6 @@
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
from stack_orchestrator.deploy.deployment_context import DeploymentContext
from ruamel.yaml import YAML
def create(context: DeploymentContext, extra_args):
@@ -23,17 +22,12 @@ def create(context: DeploymentContext, extra_args):
# deterministic-deployment-proxy contract, which itself is a prereq for Optimism contract deployment
fixturenet_eth_compose_file = context.deployment_dir.joinpath('compose', 'docker-compose-fixturenet-eth.yml')
with open(fixturenet_eth_compose_file, 'r') as yaml_file:
yaml = YAML()
yaml_data = yaml.load(yaml_file)
new_script = '../config/fixturenet-optimism/run-geth.sh:/opt/testnet/run.sh'
if new_script not in yaml_data['services']['fixturenet-eth-geth-1']['volumes']:
yaml_data['services']['fixturenet-eth-geth-1']['volumes'].append(new_script)
def add_geth_volume(yaml_data):
if new_script not in yaml_data['services']['fixturenet-eth-geth-1']['volumes']:
yaml_data['services']['fixturenet-eth-geth-1']['volumes'].append(new_script)
with open(fixturenet_eth_compose_file, 'w') as yaml_file:
yaml = YAML()
yaml.dump(yaml_data, yaml_file)
context.modify_yaml(fixturenet_eth_compose_file, add_geth_volume)
return None
@@ -2,7 +2,6 @@ version: "1.0"
name: test
description: "A test stack"
repos:
- git.vdb.to/cerc-io/laconicd
- git.vdb.to/cerc-io/test-project@test-branch
containers:
- cerc/test-container
@@ -45,20 +45,22 @@ class DeploymentContext:
def get_compose_dir(self):
return self.deployment_dir.joinpath(constants.compose_dir_name)
def get_compose_file(self, name: str):
return self.get_compose_dir() / f"docker-compose-{name}.yml"
def get_cluster_id(self):
return self.id
def init(self, dir):
self.deployment_dir = dir
def init(self, dir: Path):
self.deployment_dir = dir.absolute()
self.spec = Spec()
self.spec.init_from_file(self.get_spec_file())
self.stack = Stack(self.spec.obj["stack"])
self.stack.init_from_file(self.get_stack_file())
deployment_file_path = self.get_deployment_file()
if deployment_file_path.exists():
with deployment_file_path:
obj = get_yaml().load(open(deployment_file_path, "r"))
self.id = obj[constants.cluster_id_key]
obj = get_yaml().load(open(deployment_file_path, "r"))
self.id = obj[constants.cluster_id_key]
# Handle the case of a legacy deployment with no file
# Code below is intended to match the output from _make_default_cluster_name()
# TODO: remove when we no longer need to support legacy deployments
@@ -67,3 +69,19 @@ class DeploymentContext:
unique_cluster_descriptor = f"{path},{self.get_stack_file()},None,None"
hash = hashlib.md5(unique_cluster_descriptor.encode()).hexdigest()[:16]
self.id = f"{constants.cluster_name_prefix}{hash}"
def modify_yaml(self, file_path: Path, modifier_func):
"""
Load a YAML from the deployment, apply a modification function, and write it back.
"""
if not file_path.absolute().is_relative_to(self.deployment_dir):
raise ValueError(f"File is not inside deployment directory: {file_path}")
yaml = get_yaml()
with open(file_path, 'r') as f:
yaml_data = yaml.load(f)
modifier_func(yaml_data)
with open(file_path, 'w') as f:
yaml.dump(yaml_data, f)
@@ -443,18 +443,16 @@ def _check_volume_definitions(spec):
@click.command()
@click.option("--spec-file", required=True, help="Spec file to use to create this deployment")
@click.option("--deployment-dir", help="Create deployment files in this directory")
# TODO: Hack
@click.option("--network-dir", help="Network configuration supplied in this directory")
@click.option("--initial-peers", help="Initial set of persistent peers")
@click.argument('extra_args', nargs=-1, type=click.UNPROCESSED)
@click.pass_context
def create(ctx, spec_file, deployment_dir, network_dir, initial_peers):
def create(ctx, spec_file, deployment_dir, extra_args):
deployment_command_context = ctx.obj
return create_operation(deployment_command_context, spec_file, deployment_dir, network_dir, initial_peers)
return create_operation(deployment_command_context, spec_file, deployment_dir, extra_args)
# The init command's implementation is in a separate function so that we can
# call it from other commands, bypassing the click decoration stuff
def create_operation(deployment_command_context, spec_file, deployment_dir, network_dir, initial_peers):
def create_operation(deployment_command_context, spec_file, deployment_dir, extra_args):
parsed_spec = Spec(os.path.abspath(spec_file), get_parsed_deployment_spec(spec_file))
_check_volume_definitions(parsed_spec)
stack_name = parsed_spec["stack"]
@@ -541,7 +539,7 @@ def create_operation(deployment_command_context, spec_file, deployment_dir, netw
deployer_config_generator = getDeployerConfigGenerator(deployment_type, deployment_context)
# TODO: make deployment_dir_path a Path above
deployer_config_generator.generate(deployment_dir_path)
call_stack_deploy_create(deployment_context, [network_dir, initial_peers, deployment_command_context])
call_stack_deploy_create(deployment_context, extra_args)
# TODO: this code should be in the stack .py files but
+2 -3
View File
@@ -92,9 +92,8 @@ class Spec:
return self.obj.get(item, default)
def init_from_file(self, file_path: Path):
with file_path:
self.obj = get_yaml().load(open(file_path, "r"))
self.file_path = file_path
self.obj = get_yaml().load(open(file_path, "r"))
self.file_path = file_path
def get_image_registry(self):
return self.obj.get(constants.image_registry_key)
+1 -2
View File
@@ -27,5 +27,4 @@ class Stack:
self.name = name
def init_from_file(self, file_path: Path):
with file_path:
self.obj = get_yaml().load(open(file_path, "r"))
self.obj = get_yaml().load(open(file_path, "r"))
@@ -92,7 +92,6 @@ def create_deployment(ctx, deployment_dir, image, url, kube_config, image_regist
spec_file_name,
deployment_dir,
None,
None
)
# Fix up the container tag inside the deployment compose file
_fixup_container_tag(deployment_dir, image)
@@ -54,6 +54,7 @@ def process_app_deployment_request(
deployment_record_namespace,
dns_record_namespace,
default_dns_suffix,
dns_value,
deployment_parent_dir,
kube_config,
image_registry,
@@ -251,6 +252,7 @@ def process_app_deployment_request(
dns_record,
dns_lrn,
deployment_dir,
dns_value,
app_deployment_request,
webapp_deployer_record,
logger,
@@ -304,6 +306,7 @@ def dump_known_requests(filename, requests, status="SEEN"):
help="How to handle requests with an FQDN: prohibit, allow, preexisting",
default="prohibit",
)
@click.option("--ip", help="IP address of the k8s deployment (to be set in DNS record)", default=None)
@click.option("--record-namespace-dns", help="eg, lrn://laconic/dns", required=True)
@click.option(
"--record-namespace-deployments",
@@ -360,6 +363,9 @@ def dump_known_requests(filename, requests, status="SEEN"):
@click.option(
"--private-key-file", help="The private key for decrypting config.", required=True
)
@click.option(
"--registry-lock-file", help="File path to use for registry mutex lock", default=None
)
@click.option(
"--private-key-passphrase",
help="The passphrase for the private key.",
@@ -378,6 +384,7 @@ def command( # noqa: C901
only_update_state,
dns_suffix,
fqdn_policy,
ip,
record_namespace_dns,
record_namespace_deployments,
dry_run,
@@ -393,6 +400,7 @@ def command( # noqa: C901
private_key_passphrase,
all_requests,
auction_requests,
registry_lock_file,
):
if request_id and discover:
print("Cannot specify both --request-id and --discover", file=sys.stderr)
@@ -425,6 +433,13 @@ def command( # noqa: C901
)
sys.exit(2)
if fqdn_policy == "allow" and not ip:
print(
"--ip is required with 'allow' fqdn-policy",
file=sys.stderr,
)
sys.exit(2)
tempdir = tempfile.mkdtemp()
gpg = gnupg.GPG(gnupghome=tempdir)
@@ -444,7 +459,7 @@ def command( # noqa: C901
include_tags = [tag.strip() for tag in include_tags.split(",") if tag]
exclude_tags = [tag.strip() for tag in exclude_tags.split(",") if tag]
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr)
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr, mutex_lock_file=registry_lock_file)
webapp_deployer_record = laconic.get_record(lrn, require=True)
payment_address = webapp_deployer_record.attributes.paymentAddress
main_logger.log(f"Payment address: {payment_address}")
@@ -649,7 +664,7 @@ def command( # noqa: C901
)
run_log_file = open(run_log_file_path, "wt")
run_reg_client = LaconicRegistryClient(
laconic_config, log_file=run_log_file
laconic_config, log_file=run_log_file, mutex_lock_file=registry_lock_file
)
build_logger = TimedLogger(run_id, run_log_file)
@@ -661,6 +676,7 @@ def command( # noqa: C901
record_namespace_deployments,
record_namespace_dns,
dns_suffix,
ip,
os.path.abspath(deployment_parent_dir),
kube_config,
image_registry,
@@ -120,6 +120,9 @@ def dump_known_auction_requests(filename, requests, status="SEEN"):
help="Bid to place on application deployment auctions (in alnt)",
required=True,
)
@click.option(
"--registry-lock-file", help="File path to use for registry mutex lock", default=None
)
@click.option(
"--dry-run", help="Don't do anything, just report what would be done.", is_flag=True
)
@@ -129,6 +132,7 @@ def command(
laconic_config,
state_file,
bid_amount,
registry_lock_file,
dry_run,
):
if int(bid_amount) < 0:
@@ -138,7 +142,7 @@ def command(
logger = TimedLogger(file=sys.stderr)
try:
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr)
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr, mutex_lock_file=registry_lock_file)
auctions_requests = laconic.app_deployment_auctions()
previous_requests = {}
@@ -102,7 +102,7 @@ def command(
"max_price": max_price,
"num_providers": num_providers,
}
auction_id = laconic.create_auction(provider_auction_params)
auction_id = laconic.create_deployment_auction(provider_auction_params)
print("Deployment auction created:", auction_id)
if not auction_id:
@@ -0,0 +1,77 @@
from functools import wraps
import os
import time
# Define default file path for the lock
DEFAULT_LOCK_FILE_PATH = "/tmp/registry_mutex_lock_file"
LOCK_TIMEOUT = 30
LOCK_RETRY_INTERVAL = 3
def acquire_lock(client, lock_file_path, timeout):
# Lock alreay acquired by the current client
if client.mutex_lock_acquired:
return
while True:
try:
# Check if lock file exists and is potentially stale
if os.path.exists(lock_file_path):
with open(lock_file_path, 'r') as lock_file:
timestamp = float(lock_file.read().strip())
# If lock is stale, remove the lock file
if time.time() - timestamp > timeout:
print(f"Stale lock detected, removing lock file {lock_file_path}")
os.remove(lock_file_path)
else:
print(f"Lock file {lock_file_path} exists and is recent, waiting...")
time.sleep(LOCK_RETRY_INTERVAL)
continue
# Try to create a new lock file with the current timestamp
fd = os.open(lock_file_path, os.O_CREAT | os.O_EXCL | os.O_RDWR)
with os.fdopen(fd, 'w') as lock_file:
lock_file.write(str(time.time()))
client.mutex_lock_acquired = True
print(f"Registry lock acquired, {lock_file_path}")
# Lock successfully acquired
return
except FileExistsError:
print(f"Lock file {lock_file_path} exists, waiting...")
time.sleep(LOCK_RETRY_INTERVAL)
def release_lock(client, lock_file_path):
try:
os.remove(lock_file_path)
client.mutex_lock_acquired = False
print(f"Registry lock released, {lock_file_path}")
except FileNotFoundError:
# Lock file already removed
pass
def registry_mutex():
def decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
lock_file_path = DEFAULT_LOCK_FILE_PATH
if self.mutex_lock_file:
lock_file_path = self.mutex_lock_file
# Acquire the lock before running the function
acquire_lock(self, lock_file_path, LOCK_TIMEOUT)
try:
return func(self, *args, **kwargs)
finally:
# Release the lock after the function completes
release_lock(self, lock_file_path)
return wrapper
return decorator
@@ -178,6 +178,9 @@ def dump_known_requests(filename, requests):
"my payment address are examined).",
is_flag=True,
)
@click.option(
"--registry-lock-file", help="File path to use for registry mutex lock", default=None
)
@click.pass_context
def command( # noqa: C901
ctx,
@@ -195,6 +198,7 @@ def command( # noqa: C901
min_required_payment,
lrn,
all_requests,
registry_lock_file,
):
if request_id and discover:
print("Cannot specify both --request-id and --discover", file=sys.stderr)
@@ -212,7 +216,7 @@ def command( # noqa: C901
include_tags = [tag.strip() for tag in include_tags.split(",") if tag]
exclude_tags = [tag.strip() for tag in exclude_tags.split(",") if tag]
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr)
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr, mutex_lock_file=registry_lock_file)
deployer_record = laconic.get_record(lrn, require=True)
payment_address = deployer_record.attributes.paymentAddress
main_logger.log(f"Payment address: {payment_address}")
+51 -55
View File
@@ -26,6 +26,8 @@ import yaml
from enum import Enum
from stack_orchestrator.deploy.webapp.registry_mutex import registry_mutex
class AuctionStatus(str, Enum):
COMMIT = "commit"
@@ -112,7 +114,7 @@ def is_id(name_or_id: str):
class LaconicRegistryClient:
def __init__(self, config_file, log_file=None):
def __init__(self, config_file, log_file=None, mutex_lock_file=None):
self.config_file = config_file
self.log_file = log_file
self.cache = AttrDict(
@@ -123,6 +125,9 @@ class LaconicRegistryClient:
}
)
self.mutex_lock_file = mutex_lock_file
self.mutex_lock_acquired = False
def whoami(self, refresh=False):
if not refresh and "whoami" in self.cache:
return self.cache["whoami"]
@@ -391,6 +396,7 @@ class LaconicRegistryClient:
criteria["type"] = "ApplicationDeploymentAuction"
return self.list_records(criteria, all)
@registry_mutex()
def publish(self, record, names=None):
if names is None:
names = []
@@ -421,6 +427,7 @@ class LaconicRegistryClient:
finally:
logged_cmd(self.log_file, "rm", "-rf", tmpdir)
@registry_mutex()
def set_name(self, name, record_id):
logged_cmd(
self.log_file,
@@ -434,6 +441,7 @@ class LaconicRegistryClient:
record_id,
)
@registry_mutex()
def delete_name(self, name):
logged_cmd(
self.log_file,
@@ -446,6 +454,7 @@ class LaconicRegistryClient:
name,
)
@registry_mutex()
def send_tokens(self, address, amount, type="alnt"):
args = [
"laconic",
@@ -464,58 +473,36 @@ class LaconicRegistryClient:
return AttrDict(json.loads(logged_cmd(self.log_file, *args)))
def create_auction(self, auction):
if auction["kind"] == AUCTION_KIND_PROVIDER:
args = [
"laconic",
"-c",
self.config_file,
"registry",
"auction",
"create",
"--kind",
auction["kind"],
"--commits-duration",
str(auction["commits_duration"]),
"--reveals-duration",
str(auction["reveals_duration"]),
"--denom",
auction["denom"],
"--commit-fee",
str(auction["commit_fee"]),
"--reveal-fee",
str(auction["reveal_fee"]),
"--max-price",
str(auction["max_price"]),
"--num-providers",
str(auction["num_providers"])
]
else:
args = [
"laconic",
"-c",
self.config_file,
"registry",
"auction",
"create",
"--kind",
auction["kind"],
"--commits-duration",
str(auction["commits_duration"]),
"--reveals-duration",
str(auction["reveals_duration"]),
"--denom",
auction["denom"],
"--commit-fee",
str(auction["commit_fee"]),
"--reveal-fee",
str(auction["reveal_fee"]),
"--minimum-bid",
str(auction["minimum_bid"])
]
@registry_mutex()
def create_deployment_auction(self, auction):
args = [
"laconic",
"-c",
self.config_file,
"registry",
"auction",
"create",
"--kind",
auction["kind"],
"--commits-duration",
str(auction["commits_duration"]),
"--reveals-duration",
str(auction["reveals_duration"]),
"--denom",
auction["denom"],
"--commit-fee",
str(auction["commit_fee"]),
"--reveal-fee",
str(auction["reveal_fee"]),
"--max-price",
str(auction["max_price"]),
"--num-providers",
str(auction["num_providers"])
]
return json.loads(logged_cmd(self.log_file, *args))["auctionId"]
@registry_mutex()
def commit_bid(self, auction_id, amount, type="alnt"):
args = [
"laconic",
@@ -532,6 +519,7 @@ class LaconicRegistryClient:
return json.loads(logged_cmd(self.log_file, *args))["reveal_file"]
@registry_mutex()
def reveal_bid(self, auction_id, reveal_file_path):
logged_cmd(
self.log_file,
@@ -701,6 +689,7 @@ def publish_deployment(
dns_record,
dns_lrn,
deployment_dir,
dns_value=None,
app_deployment_request=None,
webapp_deployer_record=None,
logger=None,
@@ -733,6 +722,8 @@ def publish_deployment(
}
if app_deployment_request:
new_dns_record["record"]["request"] = app_deployment_request.id
if dns_value:
new_dns_record["record"]["value"] = dns_value
if logger:
logger.log("Publishing DnsRecord.")
@@ -858,16 +849,21 @@ def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min
)
return False
# Check if the payment was already used on a
# Check if the payment was already used on a deployment
used = laconic.app_deployments(
{"deployer": payment_address, "payment": tx.hash}, all=True
{"deployer": record.attributes.deployer, "payment": tx.hash}, all=True
)
if len(used):
logger.log(f"{record.id}: payment {tx.hash} already used on deployment {used}")
return False
# Fetch the app name from request record
used_request = laconic.get_record(used[0].attributes.request, require=True)
# Check that payment was used for deployment of same application
if record.attributes.application != used_request.attributes.application:
logger.log(f"{record.id}: payment {tx.hash} already used on a different application deployment {used}")
return False
used = laconic.app_deployment_removals(
{"deployer": payment_address, "payment": tx.hash}, all=True
{"deployer": record.attributes.deployer, "payment": tx.hash}, all=True
)
if len(used):
logger.log(
+1 -3
View File
@@ -180,9 +180,7 @@ def get_k8s_dir():
def get_parsed_deployment_spec(spec_file):
spec_file_path = Path(spec_file)
try:
with spec_file_path:
deploy_spec = get_yaml().load(open(spec_file_path, "r"))
return deploy_spec
return get_yaml().load(open(spec_file_path, "r"))
except FileNotFoundError as error:
# We try here to generate a useful diagnostic error
print(f"Error: spec file: {spec_file_path} does not exist")
+7 -2
View File
@@ -14,8 +14,13 @@ delete_cluster_exit () {
# Test basic stack-orchestrator deploy
echo "Running stack-orchestrator deploy test"
# Bit of a hack, test the most recent package
TEST_TARGET_SO=$( ls -t1 ./package/laconic-so* | head -1 )
if [ "$1" == "from-path" ]; then
TEST_TARGET_SO="laconic-so"
else
TEST_TARGET_SO=$( ls -t1 ./package/laconic-so* | head -1 )
fi
# Set a non-default repo dir
export CERC_REPO_BASE_DIR=~/stack-orchestrator-test/repo-base-dir
echo "Testing this package: $TEST_TARGET_SO"