diff --git a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py index 7fa2e255..4e57659d 100644 --- a/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py +++ b/stack_orchestrator/deploy/webapp/deploy_webapp_from_registry.py @@ -360,6 +360,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.", @@ -393,6 +396,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) @@ -444,7 +448,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 +653,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) diff --git a/stack_orchestrator/deploy/webapp/handle_deployment_auction.py b/stack_orchestrator/deploy/webapp/handle_deployment_auction.py index 380e8b49..0a3c65c0 100644 --- a/stack_orchestrator/deploy/webapp/handle_deployment_auction.py +++ b/stack_orchestrator/deploy/webapp/handle_deployment_auction.py @@ -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 = {} diff --git a/stack_orchestrator/deploy/webapp/publish_deployment_auction.py b/stack_orchestrator/deploy/webapp/publish_deployment_auction.py index 2a05ad17..bdc12eac 100644 --- a/stack_orchestrator/deploy/webapp/publish_deployment_auction.py +++ b/stack_orchestrator/deploy/webapp/publish_deployment_auction.py @@ -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: diff --git a/stack_orchestrator/deploy/webapp/registry_mutex.py b/stack_orchestrator/deploy/webapp/registry_mutex.py new file mode 100644 index 00000000..392b74d9 --- /dev/null +++ b/stack_orchestrator/deploy/webapp/registry_mutex.py @@ -0,0 +1,31 @@ +import fcntl +from functools import wraps + +# Define default file path for the lock +DEFAULT_LOCK_FILE_PATH = "/tmp/registry_mutex_lock_file" + + +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 is not None: + lock_file_path = self.mutex_lock_file + + with open(lock_file_path, 'w') as lock_file: + try: + # Try to acquire the lock + fcntl.flock(lock_file, fcntl.LOCK_EX) + + # Call the actual function + result = func(self, *args, **kwargs) + finally: + # Always release the lock + fcntl.flock(lock_file, fcntl.LOCK_UN) + + return result + + return wrapper + + return decorator diff --git a/stack_orchestrator/deploy/webapp/undeploy_webapp_from_registry.py b/stack_orchestrator/deploy/webapp/undeploy_webapp_from_registry.py index b134e158..90e62197 100644 --- a/stack_orchestrator/deploy/webapp/undeploy_webapp_from_registry.py +++ b/stack_orchestrator/deploy/webapp/undeploy_webapp_from_registry.py @@ -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}") diff --git a/stack_orchestrator/deploy/webapp/util.py b/stack_orchestrator/deploy/webapp/util.py index 7b8914c0..5f63687c 100644 --- a/stack_orchestrator/deploy/webapp/util.py +++ b/stack_orchestrator/deploy/webapp/util.py @@ -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" @@ -391,6 +393,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 +424,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 +438,7 @@ class LaconicRegistryClient: record_id, ) + @registry_mutex() def delete_name(self, name): logged_cmd( self.log_file, @@ -446,6 +451,7 @@ class LaconicRegistryClient: name, ) + @registry_mutex() def send_tokens(self, address, amount, type="alnt"): args = [ "laconic", @@ -464,58 +470,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 +516,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,