diff --git a/stack_orchestrator/deploy/webapp/handle_deployment_auction.py b/stack_orchestrator/deploy/webapp/handle_deployment_auction.py index 950a468a..19df4216 100644 --- a/stack_orchestrator/deploy/webapp/handle_deployment_auction.py +++ b/stack_orchestrator/deploy/webapp/handle_deployment_auction.py @@ -22,6 +22,8 @@ from stack_orchestrator.deploy.webapp.util import ( LaconicRegistryClient, TimedLogger, load_known_requests, + AUCTION_KIND_PROVIDER, + AuctionStatus, ) @@ -30,20 +32,65 @@ def process_app_deployment_auction( laconic: LaconicRegistryClient, request, current_status, + reveal_file_path, bid_amount, logger, ): - logger.log("BEGIN - process_app_deployment_auction") - status = current_status + # Fetch auction details + auction_id = request.auction + auction = laconic.get_auction(auction_id) + if not auction: + raise Exception(f"Unable to locate auction: {auction_id}") - # TODO: - # Check max_price, skip if bid_amount > max_price - # Check auction status - # Commit bid if auction in commit state - # Reveal bid if auction in reveal state + # Check auction kind + if auction.kind != AUCTION_KIND_PROVIDER: + raise Exception(f"Auction kind needs to be ${AUCTION_KIND_PROVIDER}, got {auction.kind}") - logger.log("END - process_app_deployment_auction") - return status, "" + if current_status == "PENDING": + # Skip if pending auction not in commit state + if auction.status != AuctionStatus.COMMIT: + logger(f"Skipping pending request, auction {auction_id} status: {auction.status}") + return "SKIP", "" + + # Check max_price + if auction.max_price < bid_amount: + logger(f"Skipping auction {auction_id} with max_price ({auction.max_price}) less than bid_amount {bid_amount}") + return "SKIP", "" + + # Bid on the auction + reveal_file_path = laconic.commit_bid(auction_id, bid_amount) + logger(f"Commited bid on auction {auction_id} with amount {bid_amount}") + + return "COMMIT", reveal_file_path + + if current_status == "COMMIT": + # Return if auction still in commit state + if auction.status == AuctionStatus.COMMIT: + logger(f"Auction {auction_id} status: {auction.status}") + return current_status, reveal_file_path + + # Reveal bid + if auction.status == AuctionStatus.REVEAL: + laconic.reveal_bid(auction_id, reveal_file_path) + logger(f"Revealed bid on auction {auction_id}") + + return "REVEAL", reveal_file_path + + raise Exception(f"Unexpected auction {auction_id} status: {auction.status}") + + if current_status == "REVEAL": + # Return if auction still in reveal state + if auction.status == AuctionStatus.REVEAL: + logger(f"Auction {auction_id} status: {auction.status}") + return current_status, reveal_file_path + + # Return if auction still in completed state + if auction.status == AuctionStatus.COMPLETED: + return "COMPLETED", "" + + raise Exception(f"Unexpected auction {auction_id} status: {auction.status}") + + raise Exception(f"Got request with unexpected status: {current_status}") def dump_known_auction_requests(filename, requests, status="SEEN"): @@ -103,6 +150,7 @@ def command( for r in auctions_requests: logger.log(f"BEGIN: Examining request {r.id}") result_status = "PENDING" + reveal_file_path = "" try: application = r.attributes.application @@ -115,7 +163,8 @@ def command( logger.log(f"Skipping request {r.id}, we've already seen it.") continue - logger.log(f"Found existing auction {r.id} for application ${application}, status {current_status}.") + reveal_file_path = previous_requests[r.id].get("revealFile", "") + logger.log(f"Found existing auction request {r.id} for application {application}, status {current_status}.") else: # It's a fresh request, check application record app = laconic.get_record(application) @@ -127,7 +176,7 @@ def command( logger.log(f"Found pending auction request {r.id} for application {application}.") # Add requests to be processed - requests_to_execute.append((r, result_status)) + requests_to_execute.append((r, result_status, reveal_file_path)) except Exception as e: result_status = "ERROR" @@ -135,12 +184,16 @@ def command( finally: logger.log(f"DONE Examining request {r.id} with result {result_status}.") if result_status in ["ERROR"]: - dump_known_auction_requests(state_file, [AttrDict({"id": r.id, "revealFile": ""})], status=result_status) + dump_known_auction_requests( + state_file, + [AttrDict({"id": r.id, "revealFile": reveal_file_path})], + status=result_status + ) logger.log(f"Found {len(requests_to_execute)} request(s) to process.") if not dry_run: - for r, current_status in requests_to_execute: + for r, current_status, reveal_file_path in requests_to_execute: logger.log(f"Processing {r.id}: BEGIN") result_status = "ERROR" try: @@ -149,6 +202,7 @@ def command( laconic, r, current_status, + reveal_file_path, bid_amount, logger, ) diff --git a/stack_orchestrator/deploy/webapp/request_webapp_deployment.py b/stack_orchestrator/deploy/webapp/request_webapp_deployment.py index e846381b..dd6e9273 100644 --- a/stack_orchestrator/deploy/webapp/request_webapp_deployment.py +++ b/stack_orchestrator/deploy/webapp/request_webapp_deployment.py @@ -187,14 +187,13 @@ def command( # noqa: C901 "application": app, "version": "1.0.0", "name": f"{app_record.attributes.name}@{app_record.attributes.version}", + "deployer": deployer, "meta": {"when": str(datetime.utcnow())}, } } if auction_id: deployment_request["record"]["auction"] = auction_id - else: - deployment_request["record"]["deployer"] = deployer if config_ref: deployment_request["record"]["config"] = {"ref": config_ref} diff --git a/stack_orchestrator/deploy/webapp/util.py b/stack_orchestrator/deploy/webapp/util.py index 296c0389..e94ceeba 100644 --- a/stack_orchestrator/deploy/webapp/util.py +++ b/stack_orchestrator/deploy/webapp/util.py @@ -24,9 +24,18 @@ import tempfile import uuid import yaml +from enum import Enum + + +class AuctionStatus(str, Enum): + COMMIT = "commit" + REVEAL = "reveal" + COMPLETED = "completed" + EXPIRED = "expired" + + TOKEN_DENOM = "alnt" AUCTION_KIND_PROVIDER = "provider" -AUCTION_STATUS_COMPLETED = "completed" class AttrDict(dict): @@ -507,6 +516,36 @@ class LaconicRegistryClient: return json.loads(logged_cmd(self.log_file, *args))["auctionId"] + def commit_bid(self, auction_id, amount, type="alnt"): + args = [ + "laconic", + "-c", + self.config_file, + "registry", + "auction", + "bid", + "commit", + auction_id, + str(amount), + type, + ] + + return json.loads(logged_cmd(self.log_file, *args))["reveal_file"] + + def reveal_bid(self, auction_id, reveal_file_path): + logged_cmd( + self.log_file, + "laconic", + "-c", + self.config_file, + "registry", + "auction", + "bid", + "reveal", + auction_id, + reveal_file_path, + ) + def file_hash(filename): return hashlib.sha1(open(filename).read().encode()).hexdigest()