Add a command to handle deployment auctions
All checks were successful
Lint Checks / Run linter (pull_request) Successful in 32s
Deploy Test / Run deploy test suite (pull_request) Successful in 4m53s
K8s Deployment Control Test / Run deployment control suite on kind/k8s (pull_request) Successful in 6m27s
K8s Deploy Test / Run deploy test suite on kind/k8s (pull_request) Successful in 7m55s
Webapp Test / Run webapp test suite (pull_request) Successful in 4m59s
Smoke Test / Run basic test suite (pull_request) Successful in 3m47s
All checks were successful
Lint Checks / Run linter (pull_request) Successful in 32s
Deploy Test / Run deploy test suite (pull_request) Successful in 4m53s
K8s Deployment Control Test / Run deployment control suite on kind/k8s (pull_request) Successful in 6m27s
K8s Deploy Test / Run deploy test suite on kind/k8s (pull_request) Successful in 7m55s
Webapp Test / Run webapp test suite (pull_request) Successful in 4m59s
Smoke Test / Run basic test suite (pull_request) Successful in 3m47s
This commit is contained in:
parent
02fac0feb7
commit
d79834a182
@ -42,6 +42,7 @@ from stack_orchestrator.deploy.webapp.util import (
|
|||||||
match_owner,
|
match_owner,
|
||||||
skip_by_tag,
|
skip_by_tag,
|
||||||
confirm_payment,
|
confirm_payment,
|
||||||
|
load_known_requests,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -257,12 +258,6 @@ def process_app_deployment_request(
|
|||||||
logger.log("END - process_app_deployment_request")
|
logger.log("END - process_app_deployment_request")
|
||||||
|
|
||||||
|
|
||||||
def load_known_requests(filename):
|
|
||||||
if filename and os.path.exists(filename):
|
|
||||||
return json.load(open(filename, "r"))
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def dump_known_requests(filename, requests, status="SEEN"):
|
def dump_known_requests(filename, requests, status="SEEN"):
|
||||||
if not filename:
|
if not filename:
|
||||||
return
|
return
|
||||||
|
162
stack_orchestrator/deploy/webapp/handle_deployment_auction.py
Normal file
162
stack_orchestrator/deploy/webapp/handle_deployment_auction.py
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
# Copyright ©2023 Vulcanize
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
from stack_orchestrator.deploy.webapp.util import (
|
||||||
|
AttrDict,
|
||||||
|
LaconicRegistryClient,
|
||||||
|
TimedLogger,
|
||||||
|
load_known_requests,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def process_app_deployment_auction(
|
||||||
|
ctx,
|
||||||
|
laconic: LaconicRegistryClient,
|
||||||
|
request,
|
||||||
|
current_status,
|
||||||
|
bid_amount,
|
||||||
|
logger,
|
||||||
|
):
|
||||||
|
logger.log("BEGIN - process_app_deployment_auction")
|
||||||
|
status = current_status
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
logger.log("END - process_app_deployment_auction")
|
||||||
|
return status, ""
|
||||||
|
|
||||||
|
|
||||||
|
def dump_known_auction_requests(filename, requests, status="SEEN"):
|
||||||
|
if not filename:
|
||||||
|
return
|
||||||
|
known_requests = load_known_requests(filename)
|
||||||
|
for r in requests:
|
||||||
|
known_requests[r.id] = {"revealFile": r.revealFile, "status": status}
|
||||||
|
with open(filename, "w") as f:
|
||||||
|
json.dump(known_requests, f)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option(
|
||||||
|
"--laconic-config", help="Provide a config file for laconicd", required=True
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--state-file", help="File to store state about previously seen auction requests."
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--bid-amount",
|
||||||
|
help="Bid to place on application deployment auctions (in alnt)",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--dry-run", help="Don't do anything, just report what would be done.", is_flag=True
|
||||||
|
)
|
||||||
|
@click.pass_context
|
||||||
|
def command(
|
||||||
|
ctx,
|
||||||
|
laconic_config,
|
||||||
|
state_file,
|
||||||
|
bid_amount,
|
||||||
|
dry_run,
|
||||||
|
): # noqa: C901
|
||||||
|
if bid_amount < 0:
|
||||||
|
print("--bid-amount cannot be less than 0", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
logger = TimedLogger(file=sys.stderr)
|
||||||
|
|
||||||
|
try:
|
||||||
|
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr)
|
||||||
|
auctions_requests = laconic.app_deployment_auctions()
|
||||||
|
|
||||||
|
previous_requests = {}
|
||||||
|
if state_file:
|
||||||
|
logger.log(f"Loading known auctions from {state_file}...")
|
||||||
|
previous_requests = load_known_requests(state_file)
|
||||||
|
|
||||||
|
# Process new requests first
|
||||||
|
auctions_requests.sort(key=lambda r: r.createTime)
|
||||||
|
auctions_requests.reverse()
|
||||||
|
|
||||||
|
requests_to_execute = []
|
||||||
|
|
||||||
|
for r in auctions_requests:
|
||||||
|
logger.log(f"BEGIN: Examining request {r.id}")
|
||||||
|
result_status = "PENDING"
|
||||||
|
try:
|
||||||
|
application = r.attributes.application
|
||||||
|
|
||||||
|
# Handle already seen requests
|
||||||
|
if r.id in previous_requests:
|
||||||
|
# If it's not in commit or reveal status, skip the request as we've already seen it
|
||||||
|
current_status = previous_requests[r.id].get("status", "")
|
||||||
|
result_status = current_status
|
||||||
|
if current_status not in ["COMMIT", "REVEAL"]:
|
||||||
|
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}.")
|
||||||
|
else:
|
||||||
|
# It's a fresh request, check application record
|
||||||
|
app = laconic.get_record(application)
|
||||||
|
if not app:
|
||||||
|
logger.log(f"Skipping request {r.id}, cannot locate app.")
|
||||||
|
result_status = "ERROR"
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.log(f"Found pending auction request {r.id} for application {application}.")
|
||||||
|
|
||||||
|
# Add requests to be processed
|
||||||
|
requests_to_execute.append((r, result_status))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
result_status = "ERROR"
|
||||||
|
logger.log(f"ERROR examining request {r.id}: " + str(e))
|
||||||
|
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)
|
||||||
|
|
||||||
|
logger.log(f"Found {len(requests_to_execute)} request(s) to process.")
|
||||||
|
|
||||||
|
if not dry_run:
|
||||||
|
for r, current_status in requests_to_execute:
|
||||||
|
logger.log(f"Processing {r.id}: BEGIN")
|
||||||
|
result_status = "ERROR"
|
||||||
|
try:
|
||||||
|
result_status, reveal_file_path = process_app_deployment_auction(
|
||||||
|
ctx,
|
||||||
|
laconic,
|
||||||
|
r,
|
||||||
|
current_status,
|
||||||
|
bid_amount,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.log(f"ERROR {r.id}:" + str(e))
|
||||||
|
finally:
|
||||||
|
logger.log(f"Processing {r.id}: END - {result_status}")
|
||||||
|
dump_known_auction_requests(state_file, [AttrDict({"id": r.id, "revealFile": reveal_file_path})], result_status)
|
||||||
|
except Exception as e:
|
||||||
|
logger.log("UNCAUGHT ERROR:" + str(e))
|
||||||
|
raise e
|
@ -25,10 +25,12 @@ from stack_orchestrator.deploy.webapp.util import (
|
|||||||
LaconicRegistryClient,
|
LaconicRegistryClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def fatal(msg: str):
|
def fatal(msg: str):
|
||||||
print(msg, file=sys.stderr)
|
print(msg, file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
# TODO: Add defaults for auction params
|
# TODO: Add defaults for auction params
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option(
|
@click.option(
|
||||||
|
@ -30,10 +30,12 @@ from stack_orchestrator.deploy.webapp.util import (
|
|||||||
)
|
)
|
||||||
from dotenv import dotenv_values
|
from dotenv import dotenv_values
|
||||||
|
|
||||||
|
|
||||||
def fatal(msg: str):
|
def fatal(msg: str):
|
||||||
print(msg, file=sys.stderr)
|
print(msg, file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option(
|
@click.option(
|
||||||
"--laconic-config", help="Provide a config file for laconicd", required=True
|
"--laconic-config", help="Provide a config file for laconicd", required=True
|
||||||
@ -67,7 +69,7 @@ def fatal(msg: str):
|
|||||||
is_flag=True,
|
is_flag=True,
|
||||||
)
|
)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def command(
|
def command( # noqa: C901
|
||||||
ctx,
|
ctx,
|
||||||
laconic_config,
|
laconic_config,
|
||||||
app,
|
app,
|
||||||
@ -79,7 +81,7 @@ def command(
|
|||||||
use_payment,
|
use_payment,
|
||||||
dns,
|
dns,
|
||||||
dry_run,
|
dry_run,
|
||||||
): # noqa: C901
|
):
|
||||||
if auction_id and deployer:
|
if auction_id and deployer:
|
||||||
print("Cannot specify both --auction-id and --deployer", file=sys.stderr)
|
print("Cannot specify both --auction-id and --deployer", file=sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
@ -124,9 +126,10 @@ def command(
|
|||||||
|
|
||||||
auction_winners = auction.winnerAddresses
|
auction_winners = auction.winnerAddresses
|
||||||
|
|
||||||
# Get deloyer record for all the auction winners
|
# Get deployer record for all the auction winners
|
||||||
for auction_winner in auction_winners:
|
for auction_winner in auction_winners:
|
||||||
deployer_records_by_owner = laconic.webapp_deployers({ "--paymentAddress": auction_winner })
|
# TODO: Match auction winner address with provider address?
|
||||||
|
deployer_records_by_owner = laconic.webapp_deployers({"--paymentAddress": auction_winner})
|
||||||
if len(deployer_records_by_owner) == 0:
|
if len(deployer_records_by_owner) == 0:
|
||||||
print(f"WARNING: Unable to locate deployer for auction winner {auction_winner}")
|
print(f"WARNING: Unable to locate deployer for auction winner {auction_winner}")
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ TOKEN_DENOM = "alnt"
|
|||||||
AUCTION_KIND_PROVIDER = "provider"
|
AUCTION_KIND_PROVIDER = "provider"
|
||||||
AUCTION_STATUS_COMPLETED = "completed"
|
AUCTION_STATUS_COMPLETED = "completed"
|
||||||
|
|
||||||
|
|
||||||
class AttrDict(dict):
|
class AttrDict(dict):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(AttrDict, self).__init__(*args, **kwargs)
|
super(AttrDict, self).__init__(*args, **kwargs)
|
||||||
@ -61,6 +62,12 @@ class TimedLogger:
|
|||||||
self.last = datetime.datetime.now()
|
self.last = datetime.datetime.now()
|
||||||
|
|
||||||
|
|
||||||
|
def load_known_requests(filename):
|
||||||
|
if filename and os.path.exists(filename):
|
||||||
|
return json.load(open(filename, "r"))
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def logged_cmd(log_file, *vargs):
|
def logged_cmd(log_file, *vargs):
|
||||||
result = None
|
result = None
|
||||||
try:
|
try:
|
||||||
@ -94,6 +101,7 @@ def is_lrn(name_or_id: str):
|
|||||||
def is_id(name_or_id: str):
|
def is_id(name_or_id: str):
|
||||||
return not is_lrn(name_or_id)
|
return not is_lrn(name_or_id)
|
||||||
|
|
||||||
|
|
||||||
class LaconicRegistryClient:
|
class LaconicRegistryClient:
|
||||||
def __init__(self, config_file, log_file=None):
|
def __init__(self, config_file, log_file=None):
|
||||||
self.config_file = config_file
|
self.config_file = config_file
|
||||||
@ -321,7 +329,7 @@ class LaconicRegistryClient:
|
|||||||
results = [
|
results = [
|
||||||
AttrDict(r) for r in json.loads(logged_cmd(self.log_file, *args)) if r
|
AttrDict(r) for r in json.loads(logged_cmd(self.log_file, *args)) if r
|
||||||
]
|
]
|
||||||
except:
|
except: # noqa: E722
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if len(results):
|
if len(results):
|
||||||
@ -367,6 +375,13 @@ class LaconicRegistryClient:
|
|||||||
criteria["type"] = "WebappDeployer"
|
criteria["type"] = "WebappDeployer"
|
||||||
return self.list_records(criteria, all)
|
return self.list_records(criteria, all)
|
||||||
|
|
||||||
|
def app_deployment_auctions(self, criteria=None, all=True):
|
||||||
|
if criteria is None:
|
||||||
|
criteria = {}
|
||||||
|
criteria = criteria.copy()
|
||||||
|
criteria["type"] = "ApplicationDeploymentAuction"
|
||||||
|
return self.list_records(criteria, all)
|
||||||
|
|
||||||
def publish(self, record, names=None):
|
def publish(self, record, names=None):
|
||||||
if names is None:
|
if names is None:
|
||||||
names = []
|
names = []
|
||||||
@ -492,6 +507,7 @@ class LaconicRegistryClient:
|
|||||||
|
|
||||||
return json.loads(logged_cmd(self.log_file, *args))["auctionId"]
|
return json.loads(logged_cmd(self.log_file, *args))["auctionId"]
|
||||||
|
|
||||||
|
|
||||||
def file_hash(filename):
|
def file_hash(filename):
|
||||||
return hashlib.sha1(open(filename).read().encode()).hexdigest()
|
return hashlib.sha1(open(filename).read().encode()).hexdigest()
|
||||||
|
|
||||||
@ -751,6 +767,7 @@ def skip_by_tag(r, include_tags, exclude_tags):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min_amount, logger):
|
def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min_amount, logger):
|
||||||
req_owner = laconic.get_owner(record)
|
req_owner = laconic.get_owner(record)
|
||||||
if req_owner == payment_address:
|
if req_owner == payment_address:
|
||||||
|
@ -27,6 +27,7 @@ from stack_orchestrator.deploy.webapp import (run_webapp,
|
|||||||
undeploy_webapp_from_registry,
|
undeploy_webapp_from_registry,
|
||||||
publish_webapp_deployer,
|
publish_webapp_deployer,
|
||||||
publish_deployment_auction,
|
publish_deployment_auction,
|
||||||
|
handle_deployment_auction,
|
||||||
request_webapp_deployment)
|
request_webapp_deployment)
|
||||||
from stack_orchestrator.deploy import deploy
|
from stack_orchestrator.deploy import deploy
|
||||||
from stack_orchestrator import version
|
from stack_orchestrator import version
|
||||||
@ -66,6 +67,7 @@ cli.add_command(deploy_webapp_from_registry.command, "deploy-webapp-from-registr
|
|||||||
cli.add_command(undeploy_webapp_from_registry.command, "undeploy-webapp-from-registry")
|
cli.add_command(undeploy_webapp_from_registry.command, "undeploy-webapp-from-registry")
|
||||||
cli.add_command(publish_webapp_deployer.command, "publish-deployer-to-registry")
|
cli.add_command(publish_webapp_deployer.command, "publish-deployer-to-registry")
|
||||||
cli.add_command(publish_deployment_auction.command, "publish-deployment-auction")
|
cli.add_command(publish_deployment_auction.command, "publish-deployment-auction")
|
||||||
|
cli.add_command(handle_deployment_auction.command, "handle-deployment-auction")
|
||||||
cli.add_command(request_webapp_deployment.command, "request-webapp-deployment")
|
cli.add_command(request_webapp_deployment.command, "request-webapp-deployment")
|
||||||
cli.add_command(deploy.command, "deploy") # deploy is an alias for deploy-system
|
cli.add_command(deploy.command, "deploy") # deploy is an alias for deploy-system
|
||||||
cli.add_command(deploy.command, "deploy-system")
|
cli.add_command(deploy.command, "deploy-system")
|
||||||
|
Loading…
Reference in New Issue
Block a user